diff --git a/.buildkite/ftr_base_serverless_configs.yml b/.buildkite/ftr_base_serverless_configs.yml index 5e7baeb3c3aea..2ff9ba6678462 100644 --- a/.buildkite/ftr_base_serverless_configs.yml +++ b/.buildkite/ftr_base_serverless_configs.yml @@ -1,6 +1,8 @@ disabled: # Base config files, only necessary to inform config finding script + # Serverless deployment-agnostic default config for api-integration tests + - x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts # Serverless base config files - x-pack/test_serverless/api_integration/config.base.ts - x-pack/test_serverless/functional/config.base.ts diff --git a/.buildkite/ftr_oblt_serverless_configs.yml b/.buildkite/ftr_oblt_serverless_configs.yml index 085c25f2d80a6..8fe505ff0e93e 100644 --- a/.buildkite/ftr_oblt_serverless_configs.yml +++ b/.buildkite/ftr_oblt_serverless_configs.yml @@ -26,3 +26,5 @@ enabled: - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts - x-pack/test_serverless/functional/test_suites/observability/config.screenshots.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index 96c15cce513c6..a984afc263170 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -1,4 +1,6 @@ disabled: + # Stateful base config for deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts # Base config files, only necessary to inform config finding script - test/functional/config.base.js - test/functional/firefox/config.base.ts @@ -35,10 +37,6 @@ disabled: - x-pack/test/fleet_cypress/config.ts - x-pack/test/fleet_cypress/visual_config.ts - # http/2 security muted tests - - x-pack/test/security_functional/saml.http2.config.ts - - x-pack/test/security_functional/oidc.http2.config.ts - defaultQueue: 'n2-4-spot' enabled: - test/accessibility/config.ts @@ -159,7 +157,6 @@ enabled: - x-pack/test/api_integration/apis/monitoring/config.ts - x-pack/test/api_integration/apis/monitoring_collection/config.ts - x-pack/test/api_integration/apis/osquery/config.ts - - x-pack/test/api_integration/apis/painless_lab/config.ts - x-pack/test/api_integration/apis/search/config.ts - x-pack/test/api_integration/apis/searchprofiler/config.ts - x-pack/test/api_integration/apis/security/config.ts @@ -326,6 +323,8 @@ enabled: - x-pack/test/security_functional/login_selector.config.ts - x-pack/test/security_functional/oidc.config.ts - x-pack/test/security_functional/saml.config.ts + - x-pack/test/security_functional/saml.http2.config.ts + - x-pack/test/security_functional/oidc.http2.config.ts - x-pack/test/security_functional/insecure_cluster_warning.config.ts - x-pack/test/security_functional/user_profiles.config.ts - x-pack/test/security_functional/expired_session.config.ts @@ -361,3 +360,5 @@ enabled: - x-pack/performance/journeys_e2e/apm_service_inventory.ts - x-pack/performance/journeys_e2e/infra_hosts_view.ts - x-pack/test/custom_branding/config.ts + # stateful config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/stateful.config.ts diff --git a/.buildkite/ftr_search_serverless_configs.yml b/.buildkite/ftr_search_serverless_configs.yml index 73b6238027bce..9a5ce6798dbae 100644 --- a/.buildkite/ftr_search_serverless_configs.yml +++ b/.buildkite/ftr_search_serverless_configs.yml @@ -16,3 +16,5 @@ enabled: - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 3880175623fdd..4c3b037ce9f8a 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -97,3 +97,5 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts - x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts + # serverless config files that run deployment-agnostic tests + - x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts diff --git a/.buildkite/ftr_security_stateful_configs.yml b/.buildkite/ftr_security_stateful_configs.yml index a7931bab0a68d..148d78583a613 100644 --- a/.buildkite/ftr_security_stateful_configs.yml +++ b/.buildkite/ftr_security_stateful_configs.yml @@ -82,4 +82,6 @@ enabled: - x-pack/test/security_solution_endpoint/configs/integrations.config.ts - x-pack/test/api_integration/apis/cloud_security_posture/config.ts - x-pack/test/cloud_security_posture_api/config.ts - - x-pack/test/cloud_security_posture_functional/config.ts \ No newline at end of file + - x-pack/test/cloud_security_posture_functional/config.ts + - x-pack/test/cloud_security_posture_functional/config.agentless.ts + - x-pack/test/cloud_security_posture_functional/data_views/config.ts diff --git a/.buildkite/pipeline-resource-definitions/kibana-fips-daily.yml b/.buildkite/pipeline-resource-definitions/kibana-fips-daily.yml index 6679e3006bb00..5a89b3bfb4f3a 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-fips-daily.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-fips-daily.yml @@ -19,12 +19,12 @@ spec: description: Run Kibana FIPS smoke tests spec: env: - SLACK_NOTIFICATIONS_CHANNEL: "#kibana-fips-ftr-errors" - ELASTIC_SLACK_NOTIFICATIONS_ENABLED: "true" + SLACK_NOTIFICATIONS_CHANNEL: '#kibana-operations-alerts' + ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' repository: elastic/kibana branch_configuration: main default_branch: main - pipeline_file: ".buildkite/pipelines/fips.yml" + pipeline_file: '.buildkite/pipelines/fips.yml' provider_settings: trigger_mode: none schedules: diff --git a/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml b/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml index aae27bd38af0f..cb0b63852ad00 100644 --- a/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml +++ b/.buildkite/pipelines/es_serverless/verify_es_serverless_image.yml @@ -44,7 +44,7 @@ steps: timeout_in_minutes: 60 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 3 - label: "Pick Test Group Run Order (FTR + Integration)" @@ -52,18 +52,18 @@ steps: depends_on: build timeout_in_minutes: 10 env: - FTR_CONFIGS_SCRIPT: 'TEST_ES_SERVERLESS_IMAGE=$ES_SERVERLESS_IMAGE .buildkite/scripts/steps/test/ftr_configs.sh' - JEST_INTEGRATION_SCRIPT: 'TEST_ES_SERVERLESS_IMAGE=$ES_SERVERLESS_IMAGE .buildkite/scripts/steps/test/jest_integration.sh' - FTR_CONFIG_PATTERNS: '**/test_serverless/**' - FTR_EXTRA_ARGS: '$FTR_EXTRA_ARGS' - LIMIT_CONFIG_TYPE: 'functional,integration' + FTR_CONFIGS_SCRIPT: "TEST_ES_SERVERLESS_IMAGE=$ES_SERVERLESS_IMAGE .buildkite/scripts/steps/test/ftr_configs.sh" + JEST_INTEGRATION_SCRIPT: "TEST_ES_SERVERLESS_IMAGE=$ES_SERVERLESS_IMAGE .buildkite/scripts/steps/test/jest_integration.sh" + FTR_CONFIG_PATTERNS: "**/test_serverless/**,**/test/security_solution_api_integration/**/serverless.config.ts" + FTR_EXTRA_ARGS: "$FTR_EXTRA_ARGS" + LIMIT_CONFIG_TYPE: "functional,integration" retry: automatic: - - exit_status: '*' + - exit_status: "*" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_entity_analytics.sh - label: 'Serverless Entity Analytics - Security Solution Cypress Tests' + label: "Serverless Entity Analytics - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -76,11 +76,11 @@ steps: parallelism: 3 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_explore.sh - label: 'Serverless Explore - Security Solution Cypress Tests' + label: "Serverless Explore - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -93,11 +93,11 @@ steps: parallelism: 4 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_investigations.sh - label: 'Serverless Investigations - Security Solution Cypress Tests' + label: "Serverless Investigations - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -110,11 +110,11 @@ steps: parallelism: 8 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_rule_management.sh - label: 'Serverless Rule Management - Security Solution Cypress Tests' + label: "Serverless Rule Management - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -127,11 +127,11 @@ steps: parallelism: 5 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_rule_management_prebuilt_rules.sh - label: 'Serverless Rule Management - Prebuilt Rules - Security Solution Cypress Tests' + label: "Serverless Rule Management - Prebuilt Rules - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -144,11 +144,11 @@ steps: parallelism: 1 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_detection_engine.sh - label: 'Serverless Detection Engine - Security Solution Cypress Tests' + label: "Serverless Detection Engine - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -161,11 +161,11 @@ steps: parallelism: 5 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_detection_engine_exceptions.sh - label: 'Serverless Detection Engine - Exceptions - Security Solution Cypress Tests' + label: "Serverless Detection Engine - Exceptions - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -178,11 +178,11 @@ steps: parallelism: 2 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_ai_assistant.sh - label: 'Serverless AI Assistant - Security Solution Cypress Tests' + label: "Serverless AI Assistant - Security Solution Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -195,11 +195,11 @@ steps: parallelism: 1 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/defend_workflows_serverless.sh - label: 'Defend Workflows Cypress Tests on Serverless' + label: "Defend Workflows Cypress Tests on Serverless" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -214,11 +214,11 @@ steps: parallelism: 12 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - command: .buildkite/scripts/steps/functional/security_serverless_osquery.sh - label: 'Serverless Osquery Cypress Tests' + label: "Serverless Osquery Cypress Tests" if: "build.env('SKIP_CYPRESS') != '1' && build.env('SKIP_CYPRESS') != 'true'" agents: image: family/kibana-ubuntu-2004 @@ -231,7 +231,7 @@ steps: parallelism: 7 retry: automatic: - - exit_status: '-1' + - exit_status: "-1" limit: 1 - wait: ~ @@ -241,6 +241,6 @@ steps: - wait: ~ - - label: 'Post-Build' + - label: "Post-Build" command: .buildkite/scripts/lifecycle/post_build.sh timeout_in_minutes: 10 diff --git a/.buildkite/pipelines/sonarqube.yml b/.buildkite/pipelines/sonarqube.yml index 61e275011140b..20f58df398ddf 100644 --- a/.buildkite/pipelines/sonarqube.yml +++ b/.buildkite/pipelines/sonarqube.yml @@ -2,8 +2,8 @@ env: SKIP_NODE_SETUP: true steps: - - label: ":sonarqube: Continuous Code Inspection" + - label: ':sonarqube: Continuous Code Inspection' agents: image: docker.elastic.co/cloud-ci/sonarqube/buildkite-scanner:latest - memory: 16G + memory: 32G command: /scan-source-code.sh diff --git a/.buildkite/scripts/common/env.sh b/.buildkite/scripts/common/env.sh index 1d401bab4282a..34b71b7004725 100755 --- a/.buildkite/scripts/common/env.sh +++ b/.buildkite/scripts/common/env.sh @@ -23,12 +23,14 @@ if [[ -d /opt/local-ssd/buildkite ]]; then mkdir -p "$TMPDIR" fi -KIBANA_PKG_BRANCH="$(jq -r .branch "$KIBANA_DIR/package.json")" -export KIBANA_PKG_BRANCH -export KIBANA_BASE_BRANCH="$KIBANA_PKG_BRANCH" +if command -v jq >/dev/null 2>&1; then + KIBANA_PKG_BRANCH="$(jq -r .branch "$KIBANA_DIR/package.json")" + export KIBANA_PKG_BRANCH + export KIBANA_BASE_BRANCH="$KIBANA_PKG_BRANCH" -KIBANA_PKG_VERSION="$(jq -r .version "$KIBANA_DIR/package.json")" -export KIBANA_PKG_VERSION + KIBANA_PKG_VERSION="$(jq -r .version "$KIBANA_DIR/package.json")" + export KIBANA_PKG_VERSION +fi # Detects and exports the final target branch when using a merge queue if [[ "${BUILDKITE_BRANCH:-}" == "gh-readonly-queue"* ]]; then diff --git a/.buildkite/scripts/common/util.sh b/.buildkite/scripts/common/util.sh index bd924e95f0bcf..d50fafad6967b 100755 --- a/.buildkite/scripts/common/util.sh +++ b/.buildkite/scripts/common/util.sh @@ -36,7 +36,7 @@ check_for_changed_files() { GIT_CHANGES="$(git status --porcelain -- . ':!:.bazelrc' ':!:config/node.options' ':!config/kibana.yml')" if [ "$GIT_CHANGES" ]; then - if ! is_auto_commit_disabled && [[ "$SHOULD_AUTO_COMMIT_CHANGES" == "true" && "${BUILDKITE_PULL_REQUEST:-}" ]]; then + if ! is_auto_commit_disabled && [[ "$SHOULD_AUTO_COMMIT_CHANGES" == "true" && "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then NEW_COMMIT_MESSAGE="[CI] Auto-commit changed files from '$1'" PREVIOUS_COMMIT_MESSAGE="$(git log -1 --pretty=%B)" diff --git a/.buildkite/scripts/steps/artifacts/publish.sh b/.buildkite/scripts/steps/artifacts/publish.sh index a833f8c663eac..52e58ebb50136 100644 --- a/.buildkite/scripts/steps/artifacts/publish.sh +++ b/.buildkite/scripts/steps/artifacts/publish.sh @@ -23,6 +23,8 @@ download "kibana-$FULL_VERSION-docker-image-aarch64.tar.gz" download "kibana-cloud-$FULL_VERSION-docker-image.tar.gz" download "kibana-cloud-$FULL_VERSION-docker-image-aarch64.tar.gz" download "kibana-ubi-$FULL_VERSION-docker-image.tar.gz" +download "kibana-wolfi-$FULL_VERSION-docker-image.tar.gz" +download "kibana-wolfi-$FULL_VERSION-docker-image-aarch64.tar.gz" download "kibana-$FULL_VERSION-arm64.deb" download "kibana-$FULL_VERSION-amd64.deb" @@ -33,6 +35,7 @@ download "kibana-$FULL_VERSION-docker-build-context.tar.gz" download "kibana-cloud-$FULL_VERSION-docker-build-context.tar.gz" download "kibana-ironbank-$FULL_VERSION-docker-build-context.tar.gz" download "kibana-ubi-$FULL_VERSION-docker-build-context.tar.gz" +download "kibana-wolfi-$FULL_VERSION-docker-build-context.tar.gz" download "kibana-$FULL_VERSION-linux-aarch64.tar.gz" download "kibana-$FULL_VERSION-linux-x86_64.tar.gz" diff --git a/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh b/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh index 63ed00a3513f9..59651402c9d83 100755 --- a/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh +++ b/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh @@ -6,22 +6,19 @@ source .buildkite/scripts/common/util.sh echo --- Security Solution OpenAPI Code Generation -echo -e "\n[Security Solution OpenAPI Code Generation] OpenAPI Common Package" - +echo -e "\n[Security Solution OpenAPI Code Generation] OpenAPI Common Package\n" (cd packages/kbn-openapi-common && yarn openapi:generate) -check_for_changed_files "yarn openapi:generate" true echo -e "\n[Security Solution OpenAPI Code Generation] Lists Common Package\n" - (cd packages/kbn-securitysolution-lists-common && yarn openapi:generate) -check_for_changed_files "yarn openapi:generate" true - -echo -e "\n[Security Solution OpenAPI Code Generation] Exceptions Common Package" +echo -e "\n[Security Solution OpenAPI Code Generation] Exceptions Common Package\n" (cd packages/kbn-securitysolution-exceptions-common && yarn openapi:generate) -check_for_changed_files "yarn openapi:generate" true -echo -e "\n[Security Solution OpenAPI Code Generation] Security Solution Plugin" +echo -e "\n[Security Solution OpenAPI Code Generation] Endpoint Exceptions Common Package\n" +(cd packages/kbn-securitysolution-endpoint-exceptions-common && yarn openapi:generate) +echo -e "\n[Security Solution OpenAPI Code Generation] Security Solution Plugin\n" (cd x-pack/plugins/security_solution && yarn openapi:generate) + check_for_changed_files "yarn openapi:generate" true \ No newline at end of file diff --git a/.buildkite/scripts/steps/openapi_bundling/security_solution_openapi_bundling.sh b/.buildkite/scripts/steps/openapi_bundling/security_solution_openapi_bundling.sh index 91ee63ab11185..2c23d9850afa4 100755 --- a/.buildkite/scripts/steps/openapi_bundling/security_solution_openapi_bundling.sh +++ b/.buildkite/scripts/steps/openapi_bundling/security_solution_openapi_bundling.sh @@ -7,36 +7,30 @@ source .buildkite/scripts/common/util.sh echo --- Security Solution OpenAPI Bundling echo -e "\n[Security Solution OpenAPI Bundling] Detections API\n" - (cd x-pack/plugins/security_solution && yarn openapi:bundle:detections) -check_for_changed_files "yarn openapi:bundle:detections" true -echo -e "\n[Security Solution OpenAPI Bundling] Entity Analytics API\n" +echo -e "\n[Security Solution OpenAPI Bundling] Timeline API\n" +(cd x-pack/plugins/security_solution && yarn openapi:bundle:timeline) +echo -e "\n[Security Solution OpenAPI Bundling] Entity Analytics API\n" (cd x-pack/plugins/security_solution && yarn openapi:bundle:entity-analytics) -check_for_changed_files "yarn openapi:bundle:entity-analytics" true echo -e "\n[Security Solution OpenAPI Bundling] Lists API\n" - -echo -e "\n[Security Solution OpenAPI Bundling] Endpoint Management API\n" - -(cd x-pack/plugins/security_solution && yarn openapi:bundle:endpoint-management) -check_for_changed_files "yarn openapi:bundle:endpoint-management" true - (cd packages/kbn-securitysolution-lists-common && yarn openapi:bundle) -check_for_changed_files "yarn openapi:bundle" true echo -e "\n[Security Solution OpenAPI Bundling] Exceptions API\n" - (cd packages/kbn-securitysolution-exceptions-common && yarn openapi:bundle) -check_for_changed_files "yarn openapi:bundle" true -echo -e "\n[Security Solution OpenAPI Bundling] Elastic Assistant API\n" +echo -e "\n[Security Solution OpenAPI Bundling] Endpoint Exceptions API\n" +(cd packages/kbn-securitysolution-endpoint-exceptions-common && yarn openapi:bundle) + +echo -e "\n[Security Solution OpenAPI Bundling] Endpoint Management API\n" +(cd x-pack/plugins/security_solution && yarn openapi:bundle:endpoint-management) +echo -e "\n[Security Solution OpenAPI Bundling] Elastic Assistant API\n" (cd x-pack/packages/kbn-elastic-assistant-common && yarn openapi:bundle) -check_for_changed_files "yarn openapi:bundle" true echo -e "\n[Security Solution OpenAPI Bundling] Osquery API\n" - (cd x-pack/plugins/osquery && yarn openapi:bundle) + check_for_changed_files "yarn openapi:bundle" true diff --git a/.buildkite/scripts/steps/serverless/deploy.sh b/.buildkite/scripts/steps/serverless/deploy.sh index 325aadf187b5b..d30723393dacd 100644 --- a/.buildkite/scripts/steps/serverless/deploy.sh +++ b/.buildkite/scripts/steps/serverless/deploy.sh @@ -163,8 +163,7 @@ if is_pr_with_label "ci:project-deploy-observability" ; then # Only deploy observability if the PR is targeting main if [[ "$BUILDKITE_PULL_REQUEST_BASE_BRANCH" == "main" ]]; then create_github_issue_oblt_test_environments - echo "--- Deploy observability with Kibana CI" - deploy "observability" + buildkite-agent annotate --context obl-test-info --style info 'See linked [Deploy Serverless Kibana] issue in pull request for project deployment information' fi fi is_pr_with_label "ci:project-deploy-security" && deploy "security" diff --git a/.eslintrc.js b/.eslintrc.js index 853b1549d2b93..2b8c6c819bb3e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -618,7 +618,7 @@ module.exports = { 'test/*/*.config.ts', 'test/*/{tests,test_suites,apis,apps}/**/*', 'test/server_integration/**/*.ts', - 'x-pack/test/*/{tests,test_suites,apis,apps}/**/*', + 'x-pack/test/*/{tests,test_suites,apis,apps,deployment_agnostic}/**/*', 'x-pack/test/*/*config.*ts', 'x-pack/test/saved_object_api_integration/*/apis/**/*', 'x-pack/test/ui_capabilities/*/tests/**/*', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5d6f6a2e43147..e07292b2eef19 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,10 +46,10 @@ x-pack/plugins/observability_solution/apm/ftr_e2e @elastic/obs-ux-infra_services x-pack/plugins/observability_solution/apm @elastic/obs-ux-infra_services-team packages/kbn-apm-synthtrace @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team packages/kbn-apm-synthtrace-client @elastic/obs-ux-infra_services-team @elastic/obs-ux-logs-team +packages/kbn-apm-types @elastic/obs-ux-infra_services-team packages/kbn-apm-utils @elastic/obs-ux-infra_services-team test/plugin_functional/plugins/app_link_test @elastic/kibana-core x-pack/test/usage_collection/plugins/application_usage_test @elastic/kibana-core -x-pack/plugins/observability_solution/assets_data_access @elastic/obs-knowledge-team x-pack/test/security_api_integration/plugins/audit_log @elastic/kibana-security packages/kbn-avc-banner @elastic/security-defend-workflows packages/kbn-axe-config @elastic/kibana-qa @@ -376,7 +376,6 @@ packages/kbn-discover-utils @elastic/kibana-data-discovery packages/kbn-doc-links @elastic/docs packages/kbn-docs-utils @elastic/kibana-operations packages/kbn-dom-drag-drop @elastic/kibana-visualizations @elastic/kibana-data-discovery -packages/analytics/ebt @elastic/kibana-core packages/kbn-ebt-tools @elastic/kibana-core x-pack/packages/security-solution/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore x-pack/plugins/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore @@ -392,8 +391,9 @@ src/plugins/embeddable @elastic/kibana-presentation x-pack/examples/embedded_lens_example @elastic/kibana-visualizations x-pack/plugins/encrypted_saved_objects @elastic/kibana-security x-pack/plugins/enterprise_search @elastic/search-kibana -x-pack/packages/kbn-entities-schema @elastic/obs-knowledge-team -x-pack/plugins/observability_solution/entity_manager @elastic/obs-knowledge-team +x-pack/plugins/observability_solution/entities_data_access @elastic/obs-entities +x-pack/packages/kbn-entities-schema @elastic/obs-entities +x-pack/plugins/observability_solution/entity_manager @elastic/obs-entities examples/error_boundary @elastic/appex-sharedux packages/kbn-es @elastic/kibana-operations packages/kbn-es-archiver @elastic/kibana-operations @elastic/appex-qa @@ -451,7 +451,7 @@ src/plugins/field_formats @elastic/kibana-data-discovery packages/kbn-field-types @elastic/kibana-data-discovery packages/kbn-field-utils @elastic/kibana-data-discovery x-pack/plugins/fields_metadata @elastic/obs-ux-logs-team -x-pack/plugins/file_upload @elastic/kibana-gis +x-pack/plugins/file_upload @elastic/kibana-gis @elastic/ml-ui examples/files_example @elastic/appex-sharedux src/plugins/files_management @elastic/appex-sharedux src/plugins/files @elastic/appex-sharedux @@ -499,6 +499,7 @@ x-pack/packages/index-management @elastic/kibana-management x-pack/plugins/index_management @elastic/kibana-management test/plugin_functional/plugins/index_patterns @elastic/kibana-data-discovery x-pack/packages/ml/inference_integration_flyout @elastic/ml-ui +x-pack/plugins/inference @elastic/kibana-core x-pack/packages/kbn-infra-forge @elastic/obs-ux-management-team x-pack/plugins/observability_solution/infra @elastic/obs-ux-logs-team @elastic/obs-ux-infra_services-team x-pack/plugins/ingest_pipelines @elastic/kibana-management @@ -775,6 +776,7 @@ packages/kbn-securitysolution-t-grid @elastic/security-detection-engine packages/kbn-securitysolution-utils @elastic/security-detection-engine packages/kbn-server-http-tools @elastic/kibana-core packages/kbn-server-route-repository @elastic/obs-knowledge-team +packages/kbn-server-route-repository-client @elastic/obs-knowledge-team packages/kbn-server-route-repository-utils @elastic/obs-knowledge-team x-pack/plugins/serverless @elastic/appex-sharedux packages/serverless/settings/common @elastic/appex-sharedux @elastic/kibana-management @@ -861,6 +863,7 @@ packages/kbn-stdio-dev-helpers @elastic/kibana-operations packages/kbn-storybook @elastic/kibana-operations x-pack/plugins/observability_solution/synthetics/e2e @elastic/obs-ux-management-team x-pack/plugins/observability_solution/synthetics @elastic/obs-ux-management-team +x-pack/packages/kbn-synthetics-private-location @elastic/obs-ux-management-team 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 x-pack/plugins/task_manager @elastic/response-ops @@ -875,9 +878,9 @@ packages/kbn-test-eui-helpers @elastic/kibana-visualizations x-pack/test/licensing_plugin/plugins/test_feature_usage @elastic/kibana-security packages/kbn-test-jest-helpers @elastic/kibana-operations @elastic/appex-qa packages/kbn-test-subj-selector @elastic/kibana-operations @elastic/appex-qa -x-pack/test_serverless @elastic/appex-qa -test @elastic/appex-qa -x-pack/test @elastic/appex-qa +x-pack/test_serverless +test +x-pack/test x-pack/performance @elastic/appex-qa x-pack/examples/testing_embedded_lens @elastic/kibana-visualizations packages/kbn-text-based-editor @elastic/kibana-esql @@ -1263,7 +1266,6 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /test/functional/services/remote @elastic/appex-qa /test/visual_regression @elastic/appex-qa /x-pack/test/visual_regression @elastic/appex-qa -/x-pack/performance @elastic/appex-qa /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 @@ -1273,7 +1275,8 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /x-pack/test_serverless/api_integration/test_suites/common/elasticsearch_api @elastic/appex-qa /x-pack/test_serverless/functional/test_suites/security/ftr/ @elastic/appex-qa /x-pack/test_serverless/functional/test_suites/common/home_page/ @elastic/appex-qa -/x-pack/test_serverless/functional/services/ @elastic/appex-qa +/x-pack/test_serverless/**/services/ @elastic/appex-qa +/packages/kbn-es/src/stateful_resources/roles.yml @elastic/appex-qa # Core /config/ @elastic/kibana-core @@ -1288,6 +1291,7 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /x-pack/test_serverless/**/test_suites/common/saved_objects_management/ @elastic/kibana-core /x-pack/test_serverless/api_integration/test_suites/common/core/ @elastic/kibana-core /x-pack/test_serverless/api_integration/test_suites/**/telemetry/ @elastic/kibana-core +/x-pack/plugins/inference @elastic/kibana-core @elastic/obs-ai-assistant @elastic/security-generative-ai #CC# /src/core/server/csp/ @elastic/kibana-core #CC# /src/plugins/saved_objects/ @elastic/kibana-core #CC# /x-pack/plugins/cloud/ @elastic/kibana-core @@ -1741,8 +1745,9 @@ packages/react @elastic/appex-sharedux /plugins/data_views/docs/openapi @elastic/platform-docs oas_docs/.spectral.yaml @elastic/platform-docs oas_docs/kibana.info.serverless.yaml @elastic/platform-docs -oas_docs/kibana.serverless.yaml @elastic/platform-docs +oas_docs/kibana.info.yaml @elastic/platform-docs oas_docs/makefile @elastic/platform-docs +oas_docs/overlays @elastic/platform-docs # Plugin manifests /src/plugins/**/kibana.jsonc @elastic/kibana-core diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 4673c4326e10a..884120ec4407d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 7d97981deb4a8..50488fc76b2c0 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index 46dc8cf4c8304..7b3917be58317 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.devdocs.json b/api_docs/aiops.devdocs.json index 770e4d7b465c1..47928bc65edd2 100644 --- a/api_docs/aiops.devdocs.json +++ b/api_docs/aiops.devdocs.json @@ -241,23 +241,11 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; }" ], "path": "x-pack/plugins/aiops/public/hooks/use_aiops_app_context.ts", diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 1243f0417a7b8..e6bff13778b2a 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index f6247786ad87e..3b4c372f8ea63 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -4128,7 +4128,15 @@ "section": "def-common.DataViewLazy", "text": "DataViewLazy" }, - ">; createDataViewLazy: (spec: ", + ">; getDataViewLazyFromCache: (id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>; createDataViewLazy: (spec: ", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index d470045dba9d7..e153f8b1a4afd 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 1b0c2db678373..6707c3d01cb69 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -418,7 +418,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/index_pattern\" | \"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/entities/services\" | \"GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries\" | \"GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries\" | \"POST /internal/apm/entities/services/detailed_statistics\" | \"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/transactions\" | \"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/has_entities\" | \"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/mobile-services/{serviceName}/error/http_error_rate\" | \"GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics\" | \"POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/error_terms\" | \"POST /internal/apm/mobile-services/{serviceName}/crashes/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/distribution\" | \"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_downstream_dependencies\" | \"GET /internal/apm/services/{serviceName}/profiling/flamegraph\" | \"GET /internal/apm/profiling/status\" | \"GET /internal/apm/services/{serviceName}/profiling/functions\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/functions\" | \"POST /internal/apm/custom-dashboard\" | \"DELETE /internal/apm/custom-dashboard\" | \"GET /internal/apm/services/{serviceName}/dashboards\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/index_pattern\" | \"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/entities/services\" | \"GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries\" | \"GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries\" | \"POST /internal/apm/entities/services/detailed_statistics\" | \"GET /internal/apm/entities/services/{serviceName}/summary\" | \"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/transactions\" | \"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/has_entities\" | \"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/mobile-services/{serviceName}/error/http_error_rate\" | \"GET /internal/apm/mobile-services/{serviceName}/errors/groups/main_statistics\" | \"POST /internal/apm/mobile-services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/error_terms\" | \"POST /internal/apm/mobile-services/{serviceName}/crashes/groups/detailed_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/groups/main_statistics\" | \"GET /internal/apm/mobile-services/{serviceName}/crashes/distribution\" | \"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_downstream_dependencies\" | \"GET /internal/apm/services/{serviceName}/profiling/flamegraph\" | \"GET /internal/apm/profiling/status\" | \"GET /internal/apm/services/{serviceName}/profiling/functions\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/flamegraph\" | \"GET /internal/apm/services/{serviceName}/profiling/hosts/functions\" | \"POST /internal/apm/custom-dashboard\" | \"DELETE /internal/apm/custom-dashboard\" | \"GET /internal/apm/services/{serviceName}/dashboards\"" ], "path": "x-pack/plugins/observability_solution/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -574,33 +574,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>, ", "TypeC", "<{ startIndex: ", @@ -624,13 +666,37 @@ "text": "NonEmptyStringBrand" }, ">; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; } & { startIndex: number; endIndex: number; } & { kuery: string; }; }; }) => Promise<{ functions: ", { "pluginId": "@kbn/profiling-utils", @@ -682,33 +748,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>, ", "TypeC", "<{ kuery: ", @@ -726,13 +834,37 @@ "text": "NonEmptyStringBrand" }, ">; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; } & { kuery: string; }; }; }) => Promise<{ flamegraph: ", { "pluginId": "@kbn/profiling-utils", @@ -2208,7 +2340,13 @@ "; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { query: { useSpanName: boolean; enableServiceTransactionMetrics: boolean; enableContinuousRollups: boolean; } & { kuery: string; } & { start: number; end: number; }; }; }) => Promise<", - "TimeRangeMetadata", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.TimeRangeMetadata", + "text": "TimeRangeMetadata" + }, ">; } & ", "APMRouteCreateOptions", "; \"GET /internal/apm/debug-telemetry\": { endpoint: \"GET /internal/apm/debug-telemetry\"; params?: undefined; handler: ({}: ", @@ -3996,7 +4134,13 @@ "; }>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params?: { query?: { 'service.name'?: string | undefined; 'service.environment'?: string | undefined; 'transaction.name'?: string | undefined; 'transaction.type'?: string | undefined; } | undefined; } | undefined; }) => Promise<", - "Transaction", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, ">; } & ", "APMRouteCreateOptions", "; \"POST /internal/apm/settings/apm-indices/save\": { endpoint: \"POST /internal/apm/settings/apm-indices/save\"; params?: ", @@ -4658,33 +4802,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>]>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { serviceName: string; }; query: { transactionType: string; bucketSizeInSeconds: number; } & { transactionName?: string | undefined; filters?: ", @@ -4706,13 +4892,37 @@ "text": "NonEmptyStringBrand" }, ">; } & { kuery: string; } & { start: number; end: number; } & { offset?: string | undefined; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; }; }; }) => Promise<", "FailedTransactionRateResponse", ">; } & ", @@ -4936,33 +5146,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { serviceName: string; }; query: { transactionType: string; latencyAggregationType: ", @@ -4986,13 +5238,37 @@ "text": "NonEmptyStringBrand" }, ">; } & { kuery: string; } & { start: number; end: number; } & { offset?: string | undefined; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; }; }; }) => Promise<", "TransactionLatencyResponse", ">; } & ", @@ -5048,29 +5324,65 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>, ", "TypeC", "<{ bucketSizeInSeconds: ", @@ -5110,11 +5422,29 @@ "text": "NonEmptyStringBrand" }, ">; } & { kuery: string; } & { start: number; end: number; } & { offset?: string | undefined; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; } & { bucketSizeInSeconds: number; useDurationSummary: boolean; } & { transactionNames: string[]; transactionType: string; latencyAggregationType: ", "LatencyAggregationType", "; }; }; }) => Promise<", @@ -5188,29 +5518,65 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { serviceName: string; }; query: { searchQuery?: string | undefined; } & { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", @@ -5226,11 +5592,29 @@ ">; } & { start: number; end: number; } & { kuery: string; useDurationSummary: boolean; transactionType: string; latencyAggregationType: ", "LatencyAggregationType", "; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; }; }; }) => Promise<", "MergedServiceTransactionGroupsResponse", ">; } & ", @@ -5254,7 +5638,13 @@ "; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { query: { start: number; end: number; } & { transactionName: string; serviceName: string; }; }; }) => Promise<{ transaction: ", - "Transaction", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, "; }>; } & ", "APMRouteCreateOptions", "; \"GET /internal/apm/traces/{traceId}/spans/{spanId}\": { endpoint: \"GET /internal/apm/traces/{traceId}/spans/{spanId}\"; params?: ", @@ -5284,9 +5674,21 @@ "]>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { traceId: string; spanId: string; }; query: { start: number; end: number; } & { parentTransactionId?: string | undefined; }; }; }) => Promise<{ span?: ", - "Span", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Span", + "text": "Span" + }, " | undefined; parentTransaction?: ", - "Transaction", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, " | undefined; }>; } & ", "APMRouteCreateOptions", "; \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\": { endpoint: \"GET /internal/apm/traces/{traceId}/transactions/{transactionId}\"; params?: ", @@ -5306,7 +5708,13 @@ "; }>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { traceId: string; transactionId: string; }; query: { start: number; end: number; }; }; }) => Promise<", - "Transaction", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, ">; } & ", "APMRouteCreateOptions", "; \"POST /internal/apm/traces/aggregated_critical_path\": { endpoint: \"POST /internal/apm/traces/aggregated_critical_path\"; params?: ", @@ -5460,7 +5868,13 @@ "; }>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { transactionId: string; }; query: { start: number; end: number; }; }; }) => Promise<{ transaction: ", - "Transaction", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, "; }>; } & ", "APMRouteCreateOptions", "; \"GET /internal/apm/traces/{traceId}/root_transaction\": { endpoint: \"GET /internal/apm/traces/{traceId}/root_transaction\"; params?: ", @@ -5478,7 +5892,13 @@ "; }>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { traceId: string; }; query: { start: number; end: number; }; }; }) => Promise<{ transaction: ", - "Transaction", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, "; }>; } & ", "APMRouteCreateOptions", "; \"GET /internal/apm/traces\": { endpoint: \"GET /internal/apm/traces\"; params?: ", @@ -5562,7 +5982,13 @@ " & { params: { path: { traceId: string; }; query: { start: number; end: number; } & { entryTransactionId: string; } & { maxTraceItems?: number | undefined; }; }; }) => Promise<{ traceItems: ", "TraceItems", "; entryTransaction?: ", - "Transaction", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, " | undefined; }>; } & ", "APMRouteCreateOptions", "; \"GET /internal/apm/suggestions\": { endpoint: \"GET /internal/apm/suggestions\"; params?: ", @@ -5675,9 +6101,13 @@ "SavedServiceGroup", "[]; }>; } & ", "APMRouteCreateOptions", - "; \"POST /internal/apm/entities/services/detailed_statistics\": { endpoint: \"POST /internal/apm/entities/services/detailed_statistics\"; params?: ", + "; \"GET /internal/apm/entities/services/{serviceName}/summary\": { endpoint: \"GET /internal/apm/entities/services/{serviceName}/summary\"; params?: ", "TypeC", - "<{ query: ", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", "IntersectionC", "<[", "TypeC", @@ -5701,63 +6131,61 @@ }, ">]>; }>, ", "TypeC", - "<{ kuery: ", - "StringC", - "; }>, ", - "TypeC", "<{ start: ", "Type", "; end: ", "Type", - "; }>, ", + "; }>]>; }> | undefined; handler: ({}: ", + "APMRouteHandlerResources", + " & { params: { path: { serviceName: string; }; query: { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", + "Branded", + "; } & { start: number; end: number; }; }; }) => Promise<", + "ServiceEntities", + ">; } & ", + "APMRouteCreateOptions", + "; \"POST /internal/apm/entities/services/detailed_statistics\": { endpoint: \"POST /internal/apm/entities/services/detailed_statistics\"; params?: ", + "TypeC", + "<{ query: ", "IntersectionC", "<[", - "PartialC", - "<{ offset: ", - "StringC", - "; }>, ", - "TypeC", - "<{ probability: ", - "Type", - "; }>, ", "TypeC", - "<{ documentType: ", - "UnionC", - "<[", - "LiteralC", - "<", - "ApmDocumentType", - ".ServiceTransactionMetric>, ", - "LiteralC", - "<", - "ApmDocumentType", - ".TransactionMetric>, ", - "LiteralC", - "<", - "ApmDocumentType", - ".TransactionEvent>]>; rollupInterval: ", + "<{ environment: ", "UnionC", "<[", "LiteralC", - "<", - "RollupInterval", - ".OneMinute>, ", - "LiteralC", - "<", - "RollupInterval", - ".TenMinutes>, ", - "LiteralC", - "<", - "RollupInterval", - ".SixtyMinutes>, ", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", "<", - "RollupInterval", - ".None>]>; }>]>, ", + "StringC", + ", ", + { + "pluginId": "@kbn/io-ts-utils", + "scope": "common", + "docId": "kibKbnIoTsUtilsPluginApi", + "section": "def-common.NonEmptyStringBrand", + "text": "NonEmptyStringBrand" + }, + ">]>; }>, ", "TypeC", - "<{ bucketSizeInSeconds: ", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", "Type", - "; }>]>; body: ", + "; end: ", + "Type", + "; }>]>; body: ", "TypeC", "<{ serviceNames: ", "Type", @@ -5773,17 +6201,7 @@ "section": "def-common.NonEmptyStringBrand", "text": "NonEmptyStringBrand" }, - ">; } & { kuery: string; } & { start: number; end: number; } & { offset?: string | undefined; } & { probability: number; } & { documentType: ", - "ApmDocumentType", - ".TransactionMetric | ", - "ApmDocumentType", - ".ServiceTransactionMetric | ", - "ApmDocumentType", - ".TransactionEvent; rollupInterval: ", - "RollupInterval", - "; } & { bucketSizeInSeconds: number; }; body: { serviceNames: string[]; }; }; }) => Promise<{ currentPeriod: { apm: { [x: string]: ", - "ServiceTransactionDetailedStat", - "; }; logErrorRate: { [x: string]: { x: number; y: number | null; }[]; }; logRate: { [x: string]: { x: number; y: number | null; }[]; }; }; }>; } & ", + ">; } & { kuery: string; } & { start: number; end: number; }; body: { serviceNames: string[]; }; }; }) => Promise<{ currentPeriod: { [x: string]: { serviceName: string; latency: { x: number; y: number | null; }[]; logErrorRate: { x: number; y: number | null; }[]; logRate: { x: number; y: number | null; }[]; throughput: { x: number; y: number | null; }[]; failedTransactionRate: { x: number; y: number | null; }[]; }; }; }>; } & ", "APMRouteCreateOptions", "; \"GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries\": { endpoint: \"GET /internal/apm/entities/services/{serviceName}/logs_error_rate_timeseries\"; params?: ", "TypeC", @@ -6398,33 +6816,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>]>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { serviceName: string; }; query: { transactionType: string; bucketSizeInSeconds: number; } & { transactionName?: string | undefined; filters?: ", @@ -6446,13 +6906,37 @@ "text": "NonEmptyStringBrand" }, ">; } & { kuery: string; } & { start: number; end: number; } & { offset?: string | undefined; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; }; }; }) => Promise<{ currentPeriod: ", "ServiceThroughputResponse", "; previousPeriod: ", @@ -6612,33 +7096,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { serviceName: string; serviceNodeName: string; }; query: { kuery: string; } & { start: number; end: number; } & { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", @@ -6652,13 +7178,37 @@ "text": "NonEmptyStringBrand" }, ">; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; }; }; }) => Promise<", "ServiceNodeMetadataResponse", ">; } & ", @@ -6684,43 +7234,109 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { path: { serviceName: string; }; query: { start: number; end: number; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; }; }; }) => Promise<", "ServiceTransactionTypesResponse", ">; } & ", @@ -6862,33 +7478,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>]>, ", "TypeC", "<{ bucketSizeInSeconds: ", @@ -6910,13 +7568,37 @@ "text": "NonEmptyStringBrand" }, ">; } & { kuery: string; } & { start: number; end: number; } & { offset?: string | undefined; } & { probability: number; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; } & { bucketSizeInSeconds: number; }; body: { serviceNames: string[]; }; }; }) => Promise<", "ServiceTransactionDetailedStatPeriodsResponse", ">; } & ", @@ -6946,33 +7628,75 @@ "<[", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric>, ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>, ", "TypeC", "<{ useDurationSummary: ", @@ -7010,13 +7734,37 @@ "; }>]>]>; }> | undefined; handler: ({}: ", "APMRouteHandlerResources", " & { params: { query: { searchQuery?: string | undefined; serviceGroup?: string | undefined; } & { probability: number; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".ServiceTransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; } & { useDurationSummary: boolean; } & { environment: \"ENVIRONMENT_NOT_DEFINED\" | \"ENVIRONMENT_ALL\" | ", "Branded", ", ", "LiteralC", "<", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent>]>; rollupInterval: ", "UnionC", "<[", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".OneMinute>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".TenMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".SixtyMinutes>, ", "LiteralC", "<", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, ".None>]>; }>, ", "TypeC", "<{ bucketSizeInSeconds: ", @@ -7484,11 +8268,29 @@ "text": "NonEmptyStringBrand" }, ">; } & { kuery: string; } & { start: number; end: number; } & { serverlessId?: string | undefined; } & { documentType: ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionMetric | ", - "ApmDocumentType", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, ".TransactionEvent; rollupInterval: ", - "RollupInterval", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + }, "; } & { bucketSizeInSeconds: number; }; }; }) => Promise<{ charts: ", "FetchAndTransformMetrics", "[]; }>; } & ", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index ba245493d106a..eaba1415e703b 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/te | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 29 | 0 | 29 | 124 | +| 29 | 0 | 29 | 119 | ## Client diff --git a/api_docs/apm_data_access.devdocs.json b/api_docs/apm_data_access.devdocs.json index d5bd712e80414..7334b593ed85f 100644 --- a/api_docs/apm_data_access.devdocs.json +++ b/api_docs/apm_data_access.devdocs.json @@ -9,9 +9,1878 @@ "objects": [] }, "server": { - "classes": [], + "classes": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient", + "type": "Class", + "tags": [], + "label": "APMEventClient", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.indices", + "type": "Object", + "tags": [], + "label": "indices", + "description": [], + "signature": [ + "{ readonly error: string; readonly transaction: string; readonly span: string; readonly metric: string; readonly onboarding: string; readonly sourcemap: string; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "config", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "server", + "docId": "kibApmDataAccessPluginApi", + "section": "def-server.APMEventClientConfig", + "text": "APMEventClientConfig" + } + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.search", + "type": "Function", + "tags": [], + "label": "search", + "description": [], + "signature": [ + "(operationName: string, params: TParams) => Promise>" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.search.$1", + "type": "string", + "tags": [], + "label": "operationName", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.search.$2", + "type": "Uncategorized", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "TParams" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.logEventSearch", + "type": "Function", + "tags": [], + "label": "logEventSearch", + "description": [], + "signature": [ + "(operationName: string, params: TParams) => Promise>" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.logEventSearch.$1", + "type": "string", + "tags": [], + "label": "operationName", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.logEventSearch.$2", + "type": "Uncategorized", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "TParams" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.msearch", + "type": "Function", + "tags": [], + "label": "msearch", + "description": [], + "signature": [ + "(operationName: string, ...allParams: TParams[]) => Promise>" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.msearch.$1", + "type": "string", + "tags": [], + "label": "operationName", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.msearch.$2", + "type": "Array", + "tags": [], + "label": "allParams", + "description": [], + "signature": [ + "TParams[]" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.eqlSearch", + "type": "Function", + "tags": [], + "label": "eqlSearch", + "description": [], + "signature": [ + "(operationName: string, params: APMEventEqlSearchRequest) => Promise<", + "EqlSearchResponse", + ">" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.eqlSearch.$1", + "type": "string", + "tags": [], + "label": "operationName", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.eqlSearch.$2", + "type": "CompoundType", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "APMEventEqlSearchRequest" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.fieldCaps", + "type": "Function", + "tags": [], + "label": "fieldCaps", + "description": [], + "signature": [ + "(operationName: string, params: APMEventFieldCapsRequest) => Promise<", + "FieldCapsResponse", + ">" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.fieldCaps.$1", + "type": "string", + "tags": [], + "label": "operationName", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.fieldCaps.$2", + "type": "CompoundType", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "APMEventFieldCapsRequest" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.termsEnum", + "type": "Function", + "tags": [], + "label": "termsEnum", + "description": [], + "signature": [ + "(operationName: string, params: APMEventTermsEnumRequest) => Promise<", + "TermsEnumResponse", + ">" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.termsEnum.$1", + "type": "string", + "tags": [], + "label": "operationName", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.termsEnum.$2", + "type": "CompoundType", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "APMEventTermsEnumRequest" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.getIndicesFromProcessorEvent", + "type": "Function", + "tags": [], + "label": "getIndicesFromProcessorEvent", + "description": [], + "signature": [ + "(processorEvent: ", + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProcessorEvent", + "text": "ProcessorEvent" + }, + ") => string[]" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClient.getIndicesFromProcessorEvent.$1", + "type": "Enum", + "tags": [], + "label": "processorEvent", + "description": [], + "signature": [ + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProcessorEvent", + "text": "ProcessorEvent" + } + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.ApmDataAccessServicesParams", + "type": "Interface", + "tags": [], + "label": "ApmDataAccessServicesParams", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_services.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.ApmDataAccessServicesParams.apmEventClient", + "type": "Object", + "tags": [], + "label": "apmEventClient", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "server", + "docId": "kibApmDataAccessPluginApi", + "section": "def-server.APMEventClient", + "text": "APMEventClient" + } + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_services.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClientConfig", + "type": "Interface", + "tags": [], + "label": "APMEventClientConfig", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClientConfig.esClient", + "type": "Object", + "tags": [], + "label": "esClient", + "description": [], + "signature": [ + "{ create: { (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; update: { (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateResponse", + ">; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateResponse", + ", unknown>>; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateResponse", + ">; }; get: { (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetResponse", + ">; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetResponse", + ", unknown>>; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetResponse", + ">; }; delete: { (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; helpers: ", + "default", + "; search: { >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchResponse", + ">; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchResponse", + ", unknown>>; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchResponse", + ">; }; name: string | symbol; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kEsql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kInference]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kQueryRuleset]: symbol | null; [kRollup]: symbol | null; [kSearchApplication]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kSynonyms]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + "default", + "; child: (opts: ", + "ClientOptions", + ") => ", + "default", + "; asyncSearch: ", + "default", + "; autoscaling: ", + "default", + "; bulk: { (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "BulkResponse", + ">; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "BulkResponse", + ", unknown>>; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "BulkResponse", + ">; }; cat: ", + "default", + "; ccr: ", + "default", + "; clearScroll: { (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClearScrollResponse", + ">; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClearScrollResponse", + ", unknown>>; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClearScrollResponse", + ">; }; closePointInTime: { (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClosePointInTimeResponse", + ", unknown>>; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; }; cluster: ", + "default", + "; count: { (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "CountResponse", + ">; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "CountResponse", + ", unknown>>; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "CountResponse", + ">; }; danglingIndices: ", + "default", + "; deleteByQuery: { (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "DeleteByQueryResponse", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; }; deleteByQueryRethrottle: { (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TasksTaskListResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; }; deleteScript: { (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; enrich: ", + "default", + "; eql: ", + "default", + "; esql: ", + "default", + "; exists: { (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; existsSource: { (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; explain: { (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ExplainResponse", + ">; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ExplainResponse", + ", unknown>>; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ExplainResponse", + ">; }; features: ", + "default", + "; fieldCaps: { (this: That, params?: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "FieldCapsResponse", + ">; (this: That, params?: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "FieldCapsResponse", + ", unknown>>; (this: That, params?: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "FieldCapsResponse", + ">; }; fleet: ", + "default", + "; getScript: { (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptResponse", + ">; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptResponse", + ", unknown>>; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptResponse", + ">; }; getScriptContext: { (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptContextResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; }; getScriptLanguages: { (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptLanguagesResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; }; getSource: { (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; graph: ", + "default", + "; healthReport: { (this: That, params?: ", + "HealthReportRequest", + " | ", + "HealthReportRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "HealthReportResponse", + ">; (this: That, params?: ", + "HealthReportRequest", + " | ", + "HealthReportRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "HealthReportResponse", + ", unknown>>; (this: That, params?: ", + "HealthReportRequest", + " | ", + "HealthReportRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "HealthReportResponse", + ">; }; ilm: ", + "default", + "; index: { (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; indices: ", + "default", + "; inference: ", + "default", + "; info: { (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "InfoResponse", + ">; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "InfoResponse", + ", unknown>>; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "InfoResponse", + ">; }; ingest: ", + "default", + "; knnSearch: { (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "KnnSearchResponse", + ">; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "KnnSearchResponse", + ", unknown>>; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "KnnSearchResponse", + ">; }; license: ", + "default", + "; logstash: ", + "default", + "; mget: { (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MgetResponse", + ">; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MgetResponse", + ", unknown>>; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MgetResponse", + ">; }; migration: ", + "default", + "; ml: ", + "default", + "; monitoring: ", + "default", + "; msearch: { >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchResponse", + ">; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchResponse", + ", unknown>>; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchResponse", + ">; }; msearchTemplate: { >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchTemplateResponse", + ", unknown>>; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; }; mtermvectors: { (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MtermvectorsResponse", + ", unknown>>; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; }; nodes: ", + "default", + "; openPointInTime: { (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "OpenPointInTimeResponse", + ", unknown>>; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; }; ping: { (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; putScript: { (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; queryRuleset: ", + "default", + "; rankEval: { (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RankEvalResponse", + ">; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RankEvalResponse", + ", unknown>>; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RankEvalResponse", + ">; }; reindex: { (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexResponse", + ">; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexResponse", + ", unknown>>; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexResponse", + ">; }; reindexRethrottle: { (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexRethrottleResponse", + ", unknown>>; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; }; renderSearchTemplate: { (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RenderSearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; }; rollup: ", + "default", + "; scriptsPainlessExecute: { (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScriptsPainlessExecuteResponse", + ", unknown>>; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; }; scroll: { >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScrollResponse", + ">; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScrollResponse", + ", unknown>>; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScrollResponse", + ">; }; searchApplication: ", + "default", + "; searchMvt: { (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; searchShards: { (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchShardsResponse", + ">; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchShardsResponse", + ", unknown>>; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchShardsResponse", + ">; }; searchTemplate: { (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; }; searchableSnapshots: ", + "default", + "; security: ", + "default", + "; shutdown: ", + "default", + "; slm: ", + "default", + "; snapshot: ", + "default", + "; sql: ", + "default", + "; ssl: ", + "default", + "; synonyms: ", + "default", + "; tasks: ", + "default", + "; termsEnum: { (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermsEnumResponse", + ">; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermsEnumResponse", + ", unknown>>; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermsEnumResponse", + ">; }; termvectors: { (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermvectorsResponse", + ">; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermvectorsResponse", + ", unknown>>; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermvectorsResponse", + ">; }; textStructure: ", + "default", + "; transform: ", + "default", + "; updateByQuery: { (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; }; updateByQueryRethrottle: { (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryRethrottleResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; }; watcher: ", + "default", + "; xpack: ", + "default", + "; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClientConfig.debug", + "type": "boolean", + "tags": [], + "label": "debug", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClientConfig.request", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + "" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClientConfig.indices", + "type": "Object", + "tags": [], + "label": "indices", + "description": [], + "signature": [ + "{ readonly error: string; readonly transaction: string; readonly span: string; readonly metric: string; readonly onboarding: string; readonly sourcemap: string; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventClientConfig.options", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ includeFrozen: boolean; inspectableEsQueriesMap?: WeakMap<", + { + "pluginId": "@kbn/core-http-server", + "scope": "server", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + ", ", + "InspectResponse", + "> | undefined; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.DocumentSourcesRequest", + "type": "Interface", + "tags": [], + "label": "DocumentSourcesRequest", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.DocumentSourcesRequest.apmEventClient", + "type": "Object", + "tags": [], + "label": "apmEventClient", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "server", + "docId": "kibApmDataAccessPluginApi", + "section": "def-server.APMEventClient", + "text": "APMEventClient" + } + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.DocumentSourcesRequest.start", + "type": "number", + "tags": [], + "label": "start", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.DocumentSourcesRequest.end", + "type": "number", + "tags": [], + "label": "end", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.DocumentSourcesRequest.kuery", + "type": "string", + "tags": [], + "label": "kuery", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.DocumentSourcesRequest.enableServiceTransactionMetrics", + "type": "boolean", + "tags": [], + "label": "enableServiceTransactionMetrics", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.DocumentSourcesRequest.enableContinuousRollups", + "type": "boolean", + "tags": [], + "label": "enableContinuousRollups", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ { @@ -29,6 +1898,72 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.ApmDataAccessServices", + "type": "Type", + "tags": [], + "label": "ApmDataAccessServices", + "description": [], + "signature": [ + "{ getDocumentSources: ({ enableContinuousRollups, enableServiceTransactionMetrics, end, kuery, start, }: Omit<", + { + "pluginId": "apmDataAccess", + "scope": "server", + "docId": "kibApmDataAccessPluginApi", + "section": "def-server.DocumentSourcesRequest", + "text": "DocumentSourcesRequest" + }, + ", \"apmEventClient\">) => Promise<(", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSource", + "text": "ApmDataSource" + }, + " & { hasDocs: boolean; hasDurationSummaryField: boolean; })[]>; getHostNames: ({ start, end, size, query, documentSources }: ", + "HostNamesRequest", + ") => Promise; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMEventESSearchRequest", + "type": "Type", + "tags": [], + "label": "APMEventESSearchRequest", + "description": [], + "signature": [ + "Omit<", + "SearchRequest", + ", \"index\"> & { apm: { includeLegacyData?: boolean | undefined; } & ({ events: ", + { + "pluginId": "observability", + "scope": "common", + "docId": "kibObservabilityPluginApi", + "section": "def-common.ProcessorEvent", + "text": "ProcessorEvent" + }, + "[]; } | { sources: ", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSource", + "text": "ApmDataSource" + }, + "[]; }); body: { size: number; track_total_hits: number | boolean; }; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "apmDataAccess", "id": "def-server.APMIndices", @@ -43,6 +1978,23 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.APMLogEventESSearchRequest", + "type": "Type", + "tags": [], + "label": "APMLogEventESSearchRequest", + "description": [], + "signature": [ + "Omit<", + "SearchRequest", + ", \"index\"> & { body: { size: number; track_total_hits: number | boolean; }; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [], @@ -116,6 +2068,69 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-server.ApmDataAccessPluginSetup.getServices", + "type": "Function", + "tags": [], + "label": "getServices", + "description": [], + "signature": [ + "(params: ", + { + "pluginId": "apmDataAccess", + "scope": "server", + "docId": "kibApmDataAccessPluginApi", + "section": "def-server.ApmDataAccessServicesParams", + "text": "ApmDataAccessServicesParams" + }, + ") => { getDocumentSources: ({ enableContinuousRollups, enableServiceTransactionMetrics, end, kuery, start, }: Omit<", + { + "pluginId": "apmDataAccess", + "scope": "server", + "docId": "kibApmDataAccessPluginApi", + "section": "def-server.DocumentSourcesRequest", + "text": "DocumentSourcesRequest" + }, + ", \"apmEventClient\">) => Promise<(", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSource", + "text": "ApmDataSource" + }, + " & { hasDocs: boolean; hasDurationSummaryField: boolean; })[]>; getHostNames: ({ start, end, size, query, documentSources }: ", + "HostNamesRequest", + ") => Promise; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-server.ApmDataAccessPluginSetup.getServices.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "server", + "docId": "kibApmDataAccessPluginApi", + "section": "def-server.ApmDataAccessServicesParams", + "text": "ApmDataAccessServicesParams" + } + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/server/services/get_services.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "lifecycle": "setup", @@ -138,10 +2153,392 @@ }, "common": { "classes": [], - "functions": [], - "interfaces": [], - "enums": [], + "functions": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getBucketSize", + "type": "Function", + "tags": [], + "label": "getBucketSize", + "description": [], + "signature": [ + "({\n start,\n end,\n numBuckets = 50,\n minBucketSize,\n}: { start: number; end: number; numBuckets?: number | undefined; minBucketSize?: number | undefined; }) => { bucketSize: number; intervalString: string; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getBucketSize.$1", + "type": "Object", + "tags": [], + "label": "{\n start,\n end,\n numBuckets = 50,\n minBucketSize,\n}", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getBucketSize.$1.start", + "type": "number", + "tags": [], + "label": "start", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getBucketSize.$1.end", + "type": "number", + "tags": [], + "label": "end", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getBucketSize.$1.numBuckets", + "type": "number", + "tags": [], + "label": "numBuckets", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getBucketSize.$1.minBucketSize", + "type": "number", + "tags": [], + "label": "minBucketSize", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getPreferredBucketSizeAndDataSource", + "type": "Function", + "tags": [], + "label": "getPreferredBucketSizeAndDataSource", + "description": [], + "signature": [ + "({\n sources,\n bucketSizeInSeconds,\n}: { sources: ", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSourceWithSummary", + "text": "ApmDataSourceWithSummary" + }, + "[]; bucketSizeInSeconds: number; }) => { source: ", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSourceWithSummary", + "text": "ApmDataSourceWithSummary" + }, + "; bucketSizeInSeconds: number; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getPreferredBucketSizeAndDataSource.$1", + "type": "Object", + "tags": [], + "label": "{\n sources,\n bucketSizeInSeconds,\n}", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getPreferredBucketSizeAndDataSource.$1.sources", + "type": "Array", + "tags": [], + "label": "sources", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSourceWithSummary", + "text": "ApmDataSourceWithSummary" + }, + "[]" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.getPreferredBucketSizeAndDataSource.$1.bucketSizeInSeconds", + "type": "number", + "tags": [], + "label": "bucketSizeInSeconds", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.ApmDataSource", + "type": "Interface", + "tags": [], + "label": "ApmDataSource", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSource", + "text": "ApmDataSource" + }, + "" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/data_source.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.ApmDataSource.rollupInterval", + "type": "Enum", + "tags": [], + "label": "rollupInterval", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.RollupInterval", + "text": "RollupInterval" + } + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/data_source.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.ApmDataSource.documentType", + "type": "Uncategorized", + "tags": [], + "label": "documentType", + "description": [], + "signature": [ + "TDocumentType" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/data_source.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.TimeRangeMetadata", + "type": "Interface", + "tags": [], + "label": "TimeRangeMetadata", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/time_range_metadata.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.TimeRangeMetadata.isUsingServiceDestinationMetrics", + "type": "boolean", + "tags": [], + "label": "isUsingServiceDestinationMetrics", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/time_range_metadata.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.TimeRangeMetadata.sources", + "type": "Array", + "tags": [], + "label": "sources", + "description": [], + "signature": [ + "(", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSource", + "text": "ApmDataSource" + }, + " & { hasDocs: boolean; hasDurationSummaryField: boolean; })[]" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/time_range_metadata.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.ApmDocumentType", + "type": "Enum", + "tags": [], + "label": "ApmDocumentType", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/document_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.RollupInterval", + "type": "Enum", + "tags": [], + "label": "RollupInterval", + "description": [], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/rollup.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "misc": [ + { + "parentPluginId": "apmDataAccess", + "id": "def-common.ApmDataSourceWithSummary", + "type": "Type", + "tags": [], + "label": "ApmDataSourceWithSummary", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDataSource", + "text": "ApmDataSource" + }, + " & { hasDurationSummaryField: boolean; hasDocs: boolean; }" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/data_source.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.ApmServiceTransactionDocumentType", + "type": "Type", + "tags": [], + "label": "ApmServiceTransactionDocumentType", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, + ".TransactionMetric | ", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, + ".ServiceTransactionMetric | ", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, + ".TransactionEvent" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/document_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "apmDataAccess", + "id": "def-common.ApmTransactionDocumentType", + "type": "Type", + "tags": [], + "label": "ApmTransactionDocumentType", + "description": [], + "signature": [ + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, + ".TransactionMetric | ", + { + "pluginId": "apmDataAccess", + "scope": "common", + "docId": "kibApmDataAccessPluginApi", + "section": "def-common.ApmDocumentType", + "text": "ApmDocumentType" + }, + ".TransactionEvent" + ], + "path": "x-pack/plugins/observability_solution/apm_data_access/common/document_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "apmDataAccess", "id": "def-common.PLUGIN_ID", diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index 5d46b334c4485..c31515e6ab309 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 9 | 0 | 9 | 0 | +| 74 | 0 | 74 | 1 | ## Server @@ -31,11 +31,26 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- ### Start +### Classes + + +### Interfaces + + ### Consts, variables and types ## Common +### Functions + + +### Interfaces + + +### Enums + + ### Consts, variables and types diff --git a/api_docs/assets_data_access.mdx b/api_docs/assets_data_access.mdx index 404c68b3fa990..a189576951681 100644 --- a/api_docs/assets_data_access.mdx +++ b/api_docs/assets_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetsDataAccess title: "assetsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the assetsDataAccess plugin -date: 2024-07-31 +date: 2024-08-08 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetsDataAccess'] --- import assetsDataAccessObj from './assets_data_access.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 6e0a4c5762b01..7137b402d29c7 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: 2024-07-31 +date: 2024-08-09 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 7b736d92c7d06..eebf435c20f31 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: 2024-07-31 +date: 2024-08-09 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 05f51d8433d33..087d4b69a0f11 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index ece2eaad47219..76e44f78a1610 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 4066d1ae659c0..7ef8e8d70a83e 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 18e762c07be0b..2d1e9611366ef 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index e780f6d281789..18cf116a6811a 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: 2024-07-31 +date: 2024-08-09 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 7c7b3675a8005..4299278dfb42e 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: 2024-07-31 +date: 2024-08-09 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 55907b5ad929d..015acb06998c8 100644 --- a/api_docs/cloud_experiments.devdocs.json +++ b/api_docs/cloud_experiments.devdocs.json @@ -109,30 +109,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "spaces", - "path": "x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts" - }, - { - "plugin": "spaces", - "path": "x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts" - }, - { - "plugin": "spaces", - "path": "x-pack/plugins/spaces/public/plugin.tsx" - }, - { - "plugin": "spaces", - "path": "x-pack/plugins/spaces/public/plugin.tsx" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/public/types.ts" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/public/types.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/types.ts" @@ -156,14 +132,6 @@ { "plugin": "observabilityOnboarding", "path": "x-pack/plugins/observability_solution/observability_onboarding/public/plugin.ts" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/server/types.ts" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/server/types.ts" } ], "children": [ @@ -179,20 +147,12 @@ "\nFetch the configuration assigned to variation `configKey`. If nothing is found, fallback to `defaultValue`." ], "signature": [ - "(featureFlagName: \"security-solutions.add-integrations-url\" | \"cloud-chat.enabled\" | \"cloud-chat.chat-variant\" | \"observability_onboarding.experimental_onboarding_flow_enabled\" | \"solutionNavEnabled\", defaultValue: Data) => Promise" + "(featureFlagName: \"security-solutions.add-integrations-url\" | \"cloud-chat.enabled\" | \"cloud-chat.chat-variant\" | \"observability_onboarding.experimental_onboarding_flow_enabled\", defaultValue: Data) => Promise" ], "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "spaces", - "path": "x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/public/plugin.tsx" - }, { "plugin": "cloudChat", "path": "x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts" @@ -200,18 +160,6 @@ { "plugin": "cloudChat", "path": "x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/public/plugin.test.ts" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/public/plugin.test.ts" - }, - { - "plugin": "navigation", - "path": "src/plugins/navigation/public/plugin.test.ts" } ], "children": [ @@ -225,7 +173,7 @@ "The name of the key to find the config variation. {@link CloudExperimentsFeatureFlagNames }." ], "signature": [ - "\"security-solutions.add-integrations-url\" | \"cloud-chat.enabled\" | \"cloud-chat.chat-variant\" | \"observability_onboarding.experimental_onboarding_flow_enabled\" | \"solutionNavEnabled\"" + "\"security-solutions.add-integrations-url\" | \"cloud-chat.enabled\" | \"cloud-chat.chat-variant\" | \"observability_onboarding.experimental_onboarding_flow_enabled\"" ], "path": "x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts", "deprecated": false, @@ -322,7 +270,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\" | \"cloud-chat.enabled\" | \"cloud-chat.chat-variant\" | \"observability_onboarding.experimental_onboarding_flow_enabled\" | \"solutionNavEnabled\"" + "\"security-solutions.add-integrations-url\" | \"cloud-chat.enabled\" | \"cloud-chat.chat-variant\" | \"observability_onboarding.experimental_onboarding_flow_enabled\"" ], "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 24a9a8faac300..b5e1179b11315 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: 2024-07-31 +date: 2024-08-09 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 75762b7bd7ec0..7a4b66e351b76 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.devdocs.json b/api_docs/console.devdocs.json index 8dc7eae8bb8b1..8201b8d4ca4ac 100644 --- a/api_docs/console.devdocs.json +++ b/api_docs/console.devdocs.json @@ -761,6 +761,24 @@ ], "returnComment": [] }, + { + "parentPluginId": "console", + "id": "def-public.ConsolePluginStart.openEmbeddedConsoleAlternateView", + "type": "Function", + "tags": [], + "label": "openEmbeddedConsoleAlternateView", + "description": [ + "\nopenEmbeddedConsoleAlternateView is available if the embedded console can be rendered.\nCalling this function will open the embedded console to the alternative view. If there is no alternative view registered\nthis will open the embedded console." + ], + "signature": [ + "(() => void) | undefined" + ], + "path": "src/plugins/console/public/types/plugin_dependencies.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "console", "id": "def-public.ConsolePluginStart.EmbeddableConsole", diff --git a/api_docs/console.mdx b/api_docs/console.mdx index df600df404f54..55777ee86ec7b 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 38 | 0 | 30 | 0 | +| 39 | 0 | 30 | 0 | ## Client diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index efa9439976552..7ab2cf169aed4 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: 2024-07-31 +date: 2024-08-09 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 19e64620bcc96..d7a2d16003262 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: 2024-07-31 +date: 2024-08-09 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 d7e132068f8fd..ae363d30cfae3 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: 2024-07-31 +date: 2024-08-09 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 c0e53b620e766..7ee85d1689799 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: 2024-07-31 +date: 2024-08-09 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 82262fdabb461..2e3a6f1ee629c 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: 2024-07-31 +date: 2024-08-09 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 e8ff90ab5208e..5af00566bd450 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -8998,79 +8998,79 @@ "\nAggsStart represents the actual external contract as AggsCommonStart\nis only used internally. The difference is that AggsStart includes the\ntypings for the registry with initialized agg types.\n" ], "signature": [ - "{ calculateAutoTimeExpression: (range: ", + "{ types: { get: (name: string) => ", { "pluginId": "data", "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - ") => string | undefined; createAggConfigs: (indexPattern: ", + " | ", { - "pluginId": "dataViews", + "pluginId": "data", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - ", configStates?: ", + " | undefined; getAll: () => { buckets: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.CreateAggConfigParams", - "text": "CreateAggConfigParams" + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - "[] | undefined, options?: Partial<", + "[]; metrics: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigsOptions", - "text": "AggConfigsOptions" + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - "> | undefined) => ", + "[]; }; }; calculateAutoTimeExpression: (range: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigs", - "text": "AggConfigs" + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" }, - "; types: { get: (name: string) => ", + ") => string | undefined; createAggConfigs: (indexPattern: ", { - "pluginId": "data", + "pluginId": "dataViews", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" }, - " | ", + ", configStates?: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.CreateAggConfigParams", + "text": "CreateAggConfigParams" }, - " | undefined; getAll: () => { buckets: ", + "[] | undefined, options?: Partial<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "section": "def-common.AggConfigsOptions", + "text": "AggConfigsOptions" }, - "[]; metrics: ", + "> | undefined) => ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.AggConfigs", + "text": "AggConfigs" }, - "[]; }; }; }" + "; }" ], "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, @@ -14378,6 +14378,46 @@ ], "returnComment": [] }, + { + "parentPluginId": "data", + "id": "def-server.DataViewsService.getDataViewLazyFromCache", + "type": "Function", + "tags": [], + "label": "getDataViewLazyFromCache", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-server.DataViewsService.getDataViewLazyFromCache.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-server.DataViewsService.get", @@ -21787,6 +21827,46 @@ ], "returnComment": [] }, + { + "parentPluginId": "data", + "id": "def-common.DataViewsService.getDataViewLazyFromCache", + "type": "Function", + "tags": [], + "label": "getDataViewLazyFromCache", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "data", + "id": "def-common.DataViewsService.getDataViewLazyFromCache.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-common.DataViewsService.get", @@ -25293,7 +25373,15 @@ "section": "def-common.DataViewLazy", "text": "DataViewLazy" }, - ">; createDataViewLazy: (spec: ", + ">; getDataViewLazyFromCache: (id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>; createDataViewLazy: (spec: ", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 2382cc5caaf0f..8163cbe639019 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: 2024-07-31 +date: 2024-08-09 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 | |-------------------|-----------|------------------------|-----------------| -| 3205 | 31 | 2590 | 24 | +| 3209 | 31 | 2594 | 24 | ## Client diff --git a/api_docs/data_quality.mdx b/api_docs/data_quality.mdx index 759a63f3c730e..78275dd7a5b39 100644 --- a/api_docs/data_quality.mdx +++ b/api_docs/data_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataQuality title: "dataQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the dataQuality plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataQuality'] --- import dataQualityObj from './data_quality.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 0bdba1a7173d3..107edac3a9ca7 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: 2024-07-31 +date: 2024-08-09 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 | |-------------------|-----------|------------------------|-----------------| -| 3205 | 31 | 2590 | 24 | +| 3209 | 31 | 2594 | 24 | ## Client diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index 64fda3452d0bf..0d2eb09086dc9 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -285,79 +285,79 @@ "\nagg config sub service\n{@link AggsStart}\n" ], "signature": [ - "{ calculateAutoTimeExpression: (range: ", + "{ types: { get: (name: string) => ", { "pluginId": "data", "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - ") => string | undefined; createAggConfigs: (indexPattern: ", + " | ", { - "pluginId": "dataViews", + "pluginId": "data", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - ", configStates?: ", + " | undefined; getAll: () => { buckets: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.CreateAggConfigParams", - "text": "CreateAggConfigParams" + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - "[] | undefined, options?: Partial<", + "[]; metrics: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigsOptions", - "text": "AggConfigsOptions" + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - "> | undefined) => ", + "[]; }; }; calculateAutoTimeExpression: (range: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigs", - "text": "AggConfigs" + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" }, - "; types: { get: (name: string) => ", + ") => string | undefined; createAggConfigs: (indexPattern: ", { - "pluginId": "data", + "pluginId": "dataViews", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" }, - " | ", + ", configStates?: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.CreateAggConfigParams", + "text": "CreateAggConfigParams" }, - " | undefined; getAll: () => { buckets: ", + "[] | undefined, options?: Partial<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "section": "def-common.AggConfigsOptions", + "text": "AggConfigsOptions" }, - "[]; metrics: ", + "> | undefined) => ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.AggConfigs", + "text": "AggConfigs" }, - "[]; }; }; }" + "; }" ], "path": "src/plugins/data/public/search/types.ts", "deprecated": false, @@ -29284,79 +29284,79 @@ "label": "aggs", "description": [], "signature": [ - "{ calculateAutoTimeExpression: (range: ", + "{ types: { get: (name: string) => ", { "pluginId": "data", "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - ") => string | undefined; createAggConfigs: (indexPattern: ", + " | ", { - "pluginId": "dataViews", + "pluginId": "data", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - ", configStates?: ", + " | undefined; getAll: () => { buckets: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.CreateAggConfigParams", - "text": "CreateAggConfigParams" + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - "[] | undefined, options?: Partial<", + "[]; metrics: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigsOptions", - "text": "AggConfigsOptions" + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - "> | undefined) => ", + "[]; }; }; calculateAutoTimeExpression: (range: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigs", - "text": "AggConfigs" + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" }, - "; types: { get: (name: string) => ", + ") => string | undefined; createAggConfigs: (indexPattern: ", { - "pluginId": "data", + "pluginId": "dataViews", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" }, - " | ", + ", configStates?: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.CreateAggConfigParams", + "text": "CreateAggConfigParams" }, - " | undefined; getAll: () => { buckets: ", + "[] | undefined, options?: Partial<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "section": "def-common.AggConfigsOptions", + "text": "AggConfigsOptions" }, - "[]; metrics: ", + "> | undefined) => ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.AggConfigs", + "text": "AggConfigs" }, - "[]; }; }; }" + "; }" ], "path": "src/plugins/data/common/search/search_source/search_source.ts", "deprecated": false, @@ -29664,7 +29664,15 @@ "section": "def-common.DataViewLazy", "text": "DataViewLazy" }, - ">; createDataViewLazy: (spec: ", + ">; getDataViewLazyFromCache: (id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>; createDataViewLazy: (spec: ", { "pluginId": "dataViews", "scope": "common", @@ -31148,79 +31156,79 @@ "\nAggsStart represents the actual external contract as AggsCommonStart\nis only used internally. The difference is that AggsStart includes the\ntypings for the registry with initialized agg types.\n" ], "signature": [ - "{ calculateAutoTimeExpression: (range: ", + "{ types: { get: (name: string) => ", { "pluginId": "data", "scope": "common", - "docId": "kibDataQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" + "docId": "kibDataSearchPluginApi", + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - ") => string | undefined; createAggConfigs: (indexPattern: ", + " | ", { - "pluginId": "dataViews", + "pluginId": "data", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibDataSearchPluginApi", + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - ", configStates?: ", + " | undefined; getAll: () => { buckets: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.CreateAggConfigParams", - "text": "CreateAggConfigParams" + "section": "def-common.BucketAggType", + "text": "BucketAggType" }, - "[] | undefined, options?: Partial<", + "[]; metrics: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigsOptions", - "text": "AggConfigsOptions" + "section": "def-common.MetricAggType", + "text": "MetricAggType" }, - "> | undefined) => ", + "[]; }; }; calculateAutoTimeExpression: (range: ", { "pluginId": "data", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.AggConfigs", - "text": "AggConfigs" + "docId": "kibDataQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" }, - "; types: { get: (name: string) => ", + ") => string | undefined; createAggConfigs: (indexPattern: ", { - "pluginId": "data", + "pluginId": "dataViews", "scope": "common", - "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" }, - " | ", + ", configStates?: ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.CreateAggConfigParams", + "text": "CreateAggConfigParams" }, - " | undefined; getAll: () => { buckets: ", + "[] | undefined, options?: Partial<", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.BucketAggType", - "text": "BucketAggType" + "section": "def-common.AggConfigsOptions", + "text": "AggConfigsOptions" }, - "[]; metrics: ", + "> | undefined) => ", { "pluginId": "data", "scope": "common", "docId": "kibDataSearchPluginApi", - "section": "def-common.MetricAggType", - "text": "MetricAggType" + "section": "def-common.AggConfigs", + "text": "AggConfigs" }, - "[]; }; }; }" + "; }" ], "path": "src/plugins/data/common/search/aggs/types.ts", "deprecated": false, diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index 41085ad08472c..f1edf2042a1b7 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.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 | |-------------------|-----------|------------------------|-----------------| -| 3205 | 31 | 2590 | 24 | +| 3209 | 31 | 2594 | 24 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index f4886ab64ea8b..4553e1d8a5305 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.devdocs.json b/api_docs/data_view_field_editor.devdocs.json index 68e920109e0b6..05646d029a3dc 100644 --- a/api_docs/data_view_field_editor.devdocs.json +++ b/api_docs/data_view_field_editor.devdocs.json @@ -558,6 +558,14 @@ "section": "def-common.DataView", "text": "DataView" }, + " | ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, "; }" ], "path": "src/plugins/data_view_field_editor/public/open_delete_modal.tsx", @@ -1077,8 +1085,9 @@ "section": "def-public.OpenFieldDeleteModalOptions", "text": "OpenFieldDeleteModalOptions" }, - ") => ", - "CloseEditor" + ") => Promise<", + "CloseEditor", + ">" ], "path": "src/plugins/data_view_field_editor/public/types.ts", "deprecated": false, diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 01919889e3538..5cd39996587e4 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 4f4d88620ad81..8ff977ac59a64 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index 8ed29368aa2c6..5bce761617486 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -1895,6 +1895,858 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy", + "type": "Class", + "tags": [], + "label": "DataViewLazy", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " extends ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.AbstractDataView", + "text": "AbstractDataView" + } + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.scriptedFieldsEnabled", + "type": "boolean", + "tags": [], + "label": "scriptedFieldsEnabled", + "description": [ + "\nReturns true if scripted fields are enabled" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "config", + "description": [], + "signature": [ + "DataViewDeps" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getFields", + "type": "Function", + "tags": [], + "label": "getFields", + "description": [], + "signature": [ + "({ mapped, scripted, runtime, fieldName, forceRefresh, unmapped, indexFilter, metaFields, }: GetFieldsParams) => Promise<{ getFieldMap: () => { [x: string]: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + "; }; getFieldMapSorted: () => Record; }>" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getFields.$1", + "type": "Object", + "tags": [], + "label": "{\n mapped = true,\n scripted = true,\n runtime = true,\n fieldName,\n forceRefresh = false,\n unmapped,\n indexFilter,\n metaFields = true,\n }", + "description": [], + "signature": [ + "GetFieldsParams" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getRuntimeFields", + "type": "Function", + "tags": [], + "label": "getRuntimeFields", + "description": [], + "signature": [ + "({ fieldName }: Pick) => DataViewFieldMap" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getRuntimeFields.$1", + "type": "Object", + "tags": [], + "label": "{ fieldName = ['*'] }", + "description": [], + "signature": [ + "Pick" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.addRuntimeField", + "type": "Function", + "tags": [], + "label": "addRuntimeField", + "description": [ + "\nAdd a runtime field - Appended to existing mapped field or a new field is\ncreated as appropriate." + ], + "signature": [ + "(name: string, runtimeField: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.RuntimeField", + "text": "RuntimeField" + }, + ") => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + "[]>" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.addRuntimeField.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "Field name" + ], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.addRuntimeField.$2", + "type": "Object", + "tags": [], + "label": "runtimeField", + "description": [ + "Runtime field definition" + ], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.RuntimeField", + "text": "RuntimeField" + } + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.removeRuntimeField", + "type": "Function", + "tags": [], + "label": "removeRuntimeField", + "description": [ + "\nRemove a runtime field - removed from mapped field or removed unmapped\nfield as appropriate. Doesn't clear associated field attributes." + ], + "signature": [ + "(name: string) => void" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.removeRuntimeField.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "- Field name to remove" + ], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getScriptedFields", + "type": "Function", + "tags": [], + "label": "getScriptedFields", + "description": [], + "signature": [ + "({ fieldName }: Pick) => Record" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getScriptedFields.$1", + "type": "Object", + "tags": [], + "label": "{ fieldName = ['*'] }", + "description": [], + "signature": [ + "Pick" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getScriptedFieldsForQuery", + "type": "Function", + "tags": [], + "label": "getScriptedFieldsForQuery", + "description": [], + "signature": [ + "() => Record" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getComputedFields", + "type": "Function", + "tags": [], + "label": "getComputedFields", + "description": [], + "signature": [ + "({ fieldName }: { fieldName: string[]; }) => Promise<{ scriptFields: Record; docvalueFields: { field: string; format: string; }[]; runtimeFields: ", + "MappingRuntimeFields", + "; }>" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getComputedFields.$1", + "type": "Object", + "tags": [], + "label": "{ fieldName = ['*'] }", + "description": [], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getComputedFields.$1.fieldName", + "type": "Array", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string[]" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getRuntimeMappings", + "type": "Function", + "tags": [], + "label": "getRuntimeMappings", + "description": [], + "signature": [ + "() => ", + "MappingRuntimeFields" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.toSpec", + "type": "Function", + "tags": [], + "label": "toSpec", + "description": [ + "\nCreates static representation of the data view." + ], + "signature": [ + "(params?: { fieldParams?: GetFieldsParams | undefined; } | undefined) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + ">" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.toSpec.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.toSpec.$1.fieldParams", + "type": "Object", + "tags": [], + "label": "fieldParams", + "description": [], + "signature": [ + "GetFieldsParams | undefined" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.toMinimalSpec", + "type": "Function", + "tags": [], + "label": "toMinimalSpec", + "description": [ + "\nCreates a minimal static representation of the data view. Fields and popularity scores will be omitted." + ], + "signature": [ + "(params?: { keepFieldAttrs?: (\"customLabel\" | \"customDescription\")[] | undefined; } | undefined) => Omit<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + ", \"fields\">" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.toMinimalSpec.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.toMinimalSpec.$1.keepFieldAttrs", + "type": "Array", + "tags": [], + "label": "keepFieldAttrs", + "description": [], + "signature": [ + "(\"customLabel\" | \"customDescription\")[] | undefined" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.isTSDBMode", + "type": "Function", + "tags": [], + "label": "isTSDBMode", + "description": [ + "\nreturns true if dataview contains TSDB fields" + ], + "signature": [ + "() => Promise" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.removeScriptedField", + "type": "Function", + "tags": [], + "label": "removeScriptedField", + "description": [], + "signature": [ + "(fieldName: string) => void" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.removeScriptedField.$1", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.upsertScriptedField", + "type": "Function", + "tags": [], + "label": "upsertScriptedField", + "description": [], + "signature": [ + "(field: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.FieldSpec", + "text": "FieldSpec" + }, + ") => void" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.upsertScriptedField.$1", + "type": "CompoundType", + "tags": [], + "label": "field", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.FieldSpec", + "text": "FieldSpec" + } + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.isTimeBased", + "type": "Function", + "tags": [], + "label": "isTimeBased", + "description": [], + "signature": [ + "() => Promise" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.isTimeNanosBased", + "type": "Function", + "tags": [], + "label": "isTimeNanosBased", + "description": [], + "signature": [ + "() => Promise" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getTimeField", + "type": "Function", + "tags": [], + "label": "getTimeField", + "description": [], + "signature": [ + "() => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + "> | undefined" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getFieldByName", + "type": "Function", + "tags": [], + "label": "getFieldByName", + "description": [], + "signature": [ + "(name: string, forceRefresh?: boolean) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + ">" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getFieldByName.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.getFieldByName.$2", + "type": "boolean", + "tags": [], + "label": "forceRefresh", + "description": [], + "signature": [ + "boolean" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCustomLabel", + "type": "Function", + "tags": [], + "label": "setFieldCustomLabel", + "description": [ + "\nSet field custom label" + ], + "signature": [ + "(fieldName: string, customLabel: string | null | undefined) => void" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCustomLabel.$1", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [ + "name of field to set custom label on" + ], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCustomLabel.$2", + "type": "CompoundType", + "tags": [], + "label": "customLabel", + "description": [ + "custom label value. If undefined, custom label is removed" + ], + "signature": [ + "string | null | undefined" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCount", + "type": "Function", + "tags": [], + "label": "setFieldCount", + "description": [], + "signature": [ + "(fieldName: string, count: number | null | undefined) => void" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCount.$1", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCount.$2", + "type": "CompoundType", + "tags": [], + "label": "count", + "description": [], + "signature": [ + "number | null | undefined" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCustomDescription", + "type": "Function", + "tags": [], + "label": "setFieldCustomDescription", + "description": [], + "signature": [ + "(fieldName: string, customDescription: string | null | undefined) => void" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCustomDescription.$1", + "type": "string", + "tags": [], + "label": "fieldName", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewLazy.setFieldCustomDescription.$2", + "type": "CompoundType", + "tags": [], + "label": "customDescription", + "description": [], + "signature": [ + "string | null | undefined" + ], + "path": "src/plugins/data_views/common/data_views/data_view_lazy.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "dataViews", "id": "def-public.DataViewsApiClient", @@ -3501,6 +4353,46 @@ ], "returnComment": [] }, + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewsService.getDataViewLazyFromCache", + "type": "Function", + "tags": [], + "label": "getDataViewLazyFromCache", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-public.DataViewsService.getDataViewLazyFromCache.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "dataViews", "id": "def-public.DataViewsService.get", @@ -9435,6 +10327,46 @@ ], "returnComment": [] }, + { + "parentPluginId": "dataViews", + "id": "def-server.DataViewsService.getDataViewLazyFromCache", + "type": "Function", + "tags": [], + "label": "getDataViewLazyFromCache", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-server.DataViewsService.getDataViewLazyFromCache.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "dataViews", "id": "def-server.DataViewsService.get", @@ -10944,7 +11876,7 @@ { "parentPluginId": "dataViews", "id": "def-server.DataViewsServerPluginSetupDependencies.usageCollection", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "usageCollection", "description": [ @@ -19550,6 +20482,46 @@ ], "returnComment": [] }, + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewsService.getDataViewLazyFromCache", + "type": "Function", + "tags": [], + "label": "getDataViewLazyFromCache", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewsService.getDataViewLazyFromCache.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "dataViews", "id": "def-common.DataViewsService.get", @@ -22987,8 +23959,8 @@ "pluginId": "dataViews", "scope": "common", "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "section": "def-common.AbstractDataView", + "text": "AbstractDataView" }, ", saveAttempts?: number | undefined, ignoreErrors?: boolean | undefined, displayErrors?: boolean | undefined) => Promise" ], @@ -23010,8 +23982,8 @@ "pluginId": "dataViews", "scope": "common", "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "section": "def-common.AbstractDataView", + "text": "AbstractDataView" } ], "path": "src/plugins/data_views/common/data_views/data_views.ts", @@ -23293,6 +24265,46 @@ ], "returnComment": [] }, + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewsServicePublicMethods.getDataViewLazyFromCache", + "type": "Function", + "tags": [], + "label": "getDataViewLazyFromCache", + "description": [], + "signature": [ + "(id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "dataViews", + "id": "def-common.DataViewsServicePublicMethods.getDataViewLazyFromCache.$1", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string" + ], + "path": "src/plugins/data_views/common/data_views/data_views.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "dataViews", "id": "def-common.DataViewsServicePublicMethods.createDataViewLazy", @@ -26046,7 +27058,15 @@ "section": "def-common.DataViewLazy", "text": "DataViewLazy" }, - ">; createDataViewLazy: (spec: ", + ">; getDataViewLazyFromCache: (id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>; createDataViewLazy: (spec: ", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 3dc5e73c02863..b9df8d42d41c7 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: 2024-07-31 +date: 2024-08-09 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 | |-------------------|-----------|------------------------|-----------------| -| 1170 | 0 | 401 | 3 | +| 1224 | 0 | 443 | 3 | ## Client diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index d5285fdc76b50..6f7396a898dff 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index 7aa1a0a563b12..3bcf1d1e8bdfb 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 254238c3afb37..9b377d6c3f83a 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -32,7 +32,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core, visualizations, triggersActionsUi | - | | | ruleRegistry, securitySolution, synthetics, slo | - | | | security, actions, alerting, ruleRegistry, files, cases, fleet, securitySolution | - | -| | spaces, navigation, securitySolution, cloudChat, observabilityOnboarding | - | +| | securitySolution, cloudChat, observabilityOnboarding | - | | | alerting, securitySolution | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-api-server-internal, @kbn/core-saved-objects-import-export-server-internal, @kbn/core-saved-objects-server-internal, fleet, graph, lists, osquery, securitySolution, alerting | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-api-server-internal, @kbn/core-saved-objects-import-export-server-internal, @kbn/core-saved-objects-server-internal, fleet, graph, lists, osquery, securitySolution, alerting | - | @@ -44,7 +44,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/securitysolution-data-table, securitySolution | - | | | @kbn/securitysolution-data-table, securitySolution | - | | | securitySolution | - | -| | @kbn/securitysolution-data-table, securitySolution | - | | | @kbn/securitysolution-data-table, securitySolution | - | | | securitySolution | - | | | securitySolution | - | @@ -67,12 +66,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | securitySolution | - | | | securitySolution | - | | | @kbn/monaco, securitySolution | - | -| | fleet, cloudSecurityPosture, exploratoryView, osquery, synthetics | - | +| | cloudSecurityPosture, securitySolution | - | +| | fleet, exploratoryView, osquery, synthetics | - | | | alerting, observabilityAIAssistant, fleet, cloudSecurityPosture, enterpriseSearch, serverlessSearch, transform, upgradeAssistant, apm, entityManager, synthetics, security | - | -| | spaces, navigation, cloudChat | - | -| | @kbn/core-saved-objects-browser-internal, @kbn/core, spaces, savedSearch, visualizations, lens, cases, maps, canvas, graph | - | -| | spaces, savedObjectsManagement | - | -| | @kbn/core-saved-objects-api-server-internal, @kbn/core-saved-objects-migration-server-internal, spaces, data, savedSearch, cloudSecurityPosture, visualizations, dashboard, @kbn/core-test-helpers-so-type-serializer | - | +| | cloudChat | - | | | @kbn/core-elasticsearch-server-internal, @kbn/core-plugins-server-internal, enterpriseSearch, observabilityOnboarding, console | - | | | actions, alerting | - | | | monitoring | - | @@ -98,6 +95,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | +| | @kbn/core-saved-objects-browser-internal, @kbn/core, spaces, savedSearch, visualizations, lens, cases, maps, canvas, graph | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core | - | @@ -108,6 +106,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @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-api-server-internal, @kbn/core-saved-objects-migration-server-internal, spaces, data, savedSearch, cloudSecurityPosture, visualizations, dashboard, @kbn/core-test-helpers-so-type-serializer | - | | | graph, visTypeTimeseries, dataViewManagement, dataViews | - | | | graph, visTypeTimeseries, dataViewManagement, dataViews | - | | | graph, visTypeTimeseries, dataViewManagement | - | @@ -132,6 +131,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | dataViewManagement | - | | | dataViewManagement | - | | | data, discover, imageEmbeddable, embeddable | - | +| | spaces, savedObjectsManagement | - | | | unifiedSearch | - | | | unifiedSearch | - | | | visualizations, lens | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index a356857d36bf9..d0b2daa0c4269 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -426,7 +426,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | ---------------|-----------|-----------| | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedCellValueElementProps), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedCellValueElementProps) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedRowRenderer), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=DeprecatedRowRenderer) | - | -| | [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserField) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/common/types/header_actions/index.ts#:~:text=BrowserFields), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/common/types/header_actions/index.ts#:~:text=BrowserFields), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [helpers.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx#:~:text=BrowserFields), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=BrowserFields), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=BrowserFields), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/components/data_table/index.tsx#:~:text=BrowserFields), [mock_source.ts](https://github.com/elastic/kibana/tree/main/x-pack/packages/security-solution/data_table/mock/mock_source.ts#:~:text=BrowserFields)+ 1 more | - | @@ -597,10 +596,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [overview_tab.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/overview_tab.tsx#:~:text=indexPatternId) | - | | | [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts#:~:text=authc), [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts#:~:text=authc) | - | | | [csp_benchmark_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/saved_objects/csp_benchmark_rule.ts#:~:text=migrations) | - | | | [csp_benchmark_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/saved_objects/csp_benchmark_rule.ts#:~:text=schemas), [csp_settings.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/server/saved_objects/csp_settings.ts#:~:text=schemas) | - | +| | [cloud_security_data_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx#:~:text=externalControlColumns) | - | @@ -1118,15 +1117,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] -## navigation - -| Deprecated API | Reference location(s) | Remove By | -| ---------------|-----------|-----------| -| | [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/public/types.ts#:~:text=CloudExperimentsPluginStart), [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/public/types.ts#:~:text=CloudExperimentsPluginStart), [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/server/types.ts#:~:text=CloudExperimentsPluginStart), [types.ts](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/server/types.ts#:~:text=CloudExperimentsPluginStart) | - | -| | [plugin.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/public/plugin.tsx#:~:text=getVariation), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/public/plugin.test.ts#:~:text=getVariation), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/public/plugin.test.ts#:~:text=getVariation), [plugin.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/navigation/public/plugin.test.ts#:~:text=getVariation) | - | - - - ## observabilityAIAssistant | Deprecated API | Reference location(s) | Remove By | @@ -1377,8 +1367,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=DeprecatedCellValueElementProps), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx#:~:text=DeprecatedCellValueElementProps) | - | | | [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), [columns.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/event_details/columns.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), [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/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), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields)+ 77 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/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), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts#:~:text=BrowserFields)+ 76 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) | - | @@ -1403,6 +1392,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION) | - | | | [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode) | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx#:~:text=externalControlColumns) | - | @@ -1444,8 +1434,6 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [is_solution_nav_enabled.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts#:~:text=CloudExperimentsPluginStart), [is_solution_nav_enabled.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts#:~:text=CloudExperimentsPluginStart), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/public/plugin.tsx#:~:text=CloudExperimentsPluginStart), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/public/plugin.tsx#:~:text=CloudExperimentsPluginStart) | - | -| | [is_solution_nav_enabled.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts#:~:text=getVariation) | - | | | [on_post_auth_interceptor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts#:~:text=getKibanaFeatures), [spaces_usage_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts#:~:text=getKibanaFeatures), [on_post_auth_interceptor.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts#:~:text=getKibanaFeatures) | 8.8.0 | | | [spaces_usage_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [spaces_usage_collector.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts#:~:text=license%24) | 8.8.0 | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/public/legacy_urls/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/public/legacy_urls/types.ts#:~:text=ResolvedSimpleSavedObject) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index d9069ead980de..5606cb16ee691 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -79,8 +79,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| spaces | | [on_post_auth_interceptor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts#:~:text=getKibanaFeatures), [spaces_usage_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts#:~:text=getKibanaFeatures), [on_post_auth_interceptor.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts#:~:text=getKibanaFeatures), [app_authorization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.ts#:~:text=getKibanaFeatures), [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.ts#:~:text=getKibanaFeatures), [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getKibanaFeatures), [app_authorization.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 27 more | 8.8.0 | -| spaces | | [spaces_usage_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [spaces_usage_collector.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/plugin.ts#:~:text=license%24) | 8.8.0 | | security | | [disable_ui_capabilities.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts#:~:text=requiredRoles) | 8.8.0 This is relied on by the reporting feature, and should be removed once reporting @@ -89,10 +87,12 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ This is relied on by the reporting feature, and should be removed once reporting migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/issues/19914 | +| security | | [app_authorization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.ts#:~:text=getKibanaFeatures), [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.ts#:~:text=getKibanaFeatures), [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getKibanaFeatures), [app_authorization.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 27 more | 8.8.0 | | security | | [authorization_service.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/authorization_service.tsx#:~:text=getElasticsearchFeatures) | 8.8.0 | | security | | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | | security | | [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/plugin.tsx#:~:text=license%24) | 8.8.0 | | security | | [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode), [license_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/common/licensing/license_service.test.ts#:~:text=mode) | 8.8.0 | +| security | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/plugin.ts#:~:text=license%24), [spaces_usage_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [spaces_usage_collector.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts#:~:text=license%24) | 8.8.0 | | security | | [logout_app.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/authentication/logout/logout_app.test.ts#:~:text=appBasePath) | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 4e34ac7486ff2..5ea0d01252fcb 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: 2024-07-31 +date: 2024-08-09 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 93d502edc0287..ed0d600efff04 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -985,7 +985,15 @@ "section": "def-common.DataView", "text": "DataView" }, - ") => Promise; onOpenSavedSearch: (savedSearchId: string) => void; onUpdateQuery: (payload: { dateRange: ", + ") => Promise; transitionFromESQLToDataView: (dataViewId: string) => void; transitionFromDataViewToESQL: (dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ") => void; onOpenSavedSearch: (savedSearchId: string) => void; onUpdateQuery: (payload: { dateRange: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -2975,10 +2983,6 @@ "plugin": "fleet", "path": "x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/hooks/use_get_logs_discover_link.tsx" }, - { - "plugin": "cloudSecurityPosture", - "path": "x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/overview_tab.tsx" - }, { "plugin": "exploratoryView", "path": "x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/hooks/use_discover_link.tsx" diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 7b2c6335ef8b9..2cd6f2880551f 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index a32e2d03d4ec0..306ed6475304b 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/discover_shared.mdx b/api_docs/discover_shared.mdx index 5d72870a1145c..b497b8bdef188 100644 --- a/api_docs/discover_shared.mdx +++ b/api_docs/discover_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverShared title: "discoverShared" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverShared plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverShared'] --- import discoverSharedObj from './discover_shared.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index d386ecccede55..a15f430b74bc5 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: 2024-07-31 +date: 2024-08-09 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 cdf8dfe9d6598..7735ad881e1e1 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: 2024-07-31 +date: 2024-08-09 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 8e255f9f49ade..2f366ce07e0ae 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -9679,7 +9679,7 @@ "section": "def-public.PublishesPhaseEvents", "text": "PublishesPhaseEvents" }, - ",", + ",Partial<", { "pluginId": "@kbn/presentation-publishing", "scope": "public", @@ -9687,7 +9687,7 @@ "section": "def-public.PublishesUnsavedChanges", "text": "PublishesUnsavedChanges" }, - ",", + ">,", { "pluginId": "@kbn/presentation-containers", "scope": "public", @@ -12892,7 +12892,7 @@ "\nA required async function that builds your embeddable component and a linked API instance. The API\nand component will be combined together by the ReactEmbeddableRenderer. Initial state will contain the result of\nthe deserialize function.\n\nThe returned API must extend {@link HasSerializableState} which does the opposite of the deserializeState\nfunction." ], "signature": [ - "(initialState: RuntimeState, buildApi: (apiRegistration: ", + "(initialRuntimeState: RuntimeState, buildApi: (apiRegistration: ", "BuildReactEmbeddableApiRegistration", ", comparators: ", { @@ -12912,7 +12912,7 @@ }, ", uuid: string, parentApi: unknown, setApi: (api: ", "SetReactEmbeddableApiRegistration", - ") => Api) => Promise<{ Component: React.FC<{}>; api: Api; }>" + ") => Api, lastSavedRuntimeState: RuntimeState) => Promise<{ Component: React.FC<{}>; api: Api; }>" ], "path": "src/plugins/embeddable/public/react_embeddable_system/types.ts", "deprecated": false, @@ -12923,7 +12923,7 @@ "id": "def-public.ReactEmbeddableFactory.buildEmbeddable.$1", "type": "Uncategorized", "tags": [], - "label": "initialState", + "label": "initialRuntimeState", "description": [], "signature": [ "RuntimeState" @@ -13012,6 +13012,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "embeddable", + "id": "def-public.ReactEmbeddableFactory.buildEmbeddable.$6", + "type": "Uncategorized", + "tags": [], + "label": "lastSavedRuntimeState", + "description": [], + "signature": [ + "RuntimeState" + ], + "path": "src/plugins/embeddable/public/react_embeddable_system/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 006156ba9076e..64cc9677b665e 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: 2024-07-31 +date: 2024-08-09 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 | |-------------------|-----------|------------------------|-----------------| -| 570 | 1 | 460 | 9 | +| 571 | 1 | 461 | 9 | ## Client diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 69ce9c1f0f6ea..b22f23a7cd605 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: 2024-07-31 +date: 2024-08-09 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 f32d9baf0a9c8..27a1c08396cff 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 1025d301965da..97a40e4c49669 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/entities_data_access.devdocs.json b/api_docs/entities_data_access.devdocs.json new file mode 100644 index 0000000000000..26fd0abf8e82b --- /dev/null +++ b/api_docs/entities_data_access.devdocs.json @@ -0,0 +1,43 @@ +{ + "id": "entitiesDataAccess", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "start": { + "parentPluginId": "entitiesDataAccess", + "id": "def-server.EntitiesDataAccessPluginStart", + "type": "Type", + "tags": [], + "label": "EntitiesDataAccessPluginStart", + "description": [], + "signature": [ + "void" + ], + "path": "x-pack/plugins/observability_solution/entities_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/entities_data_access.mdx b/api_docs/entities_data_access.mdx new file mode 100644 index 0000000000000..60fa8fb1a65b5 --- /dev/null +++ b/api_docs/entities_data_access.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: kibEntitiesDataAccessPluginApi +slug: /kibana-dev-docs/api/entitiesDataAccess +title: "entitiesDataAccess" +image: https://source.unsplash.com/400x175/?github +description: API docs for the entitiesDataAccess plugin +date: 2024-08-09 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entitiesDataAccess'] +--- +import entitiesDataAccessObj from './entities_data_access.devdocs.json'; + + + +Contact [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2 | 0 | 2 | 0 | + +## Server + +### Start + + diff --git a/api_docs/entity_manager.devdocs.json b/api_docs/entity_manager.devdocs.json index 145b8f97ff63a..0ec36023ac35f 100644 --- a/api_docs/entity_manager.devdocs.json +++ b/api_docs/entity_manager.devdocs.json @@ -1,7 +1,64 @@ { "id": "entityManager", "client": { - "classes": [], + "classes": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityManagerUnauthorizedError", + "type": "Class", + "tags": [], + "label": "EntityManagerUnauthorizedError", + "description": [], + "signature": [ + { + "pluginId": "entityManager", + "scope": "public", + "docId": "kibEntityManagerPluginApi", + "section": "def-public.EntityManagerUnauthorizedError", + "text": "EntityManagerUnauthorizedError" + }, + " extends Error" + ], + "path": "x-pack/plugins/observability_solution/entity_manager/public/lib/errors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityManagerUnauthorizedError.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/observability_solution/entity_manager/public/lib/errors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "entityManager", + "id": "def-public.EntityManagerUnauthorizedError.Unnamed.$1", + "type": "string", + "tags": [], + "label": "message", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/entity_manager/public/lib/errors.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "functions": [], "interfaces": [ { @@ -152,21 +209,6 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, - { - "parentPluginId": "entityManager", - "id": "def-public.ERROR_USER_NOT_AUTHORIZED", - "type": "string", - "tags": [], - "label": "ERROR_USER_NOT_AUTHORIZED", - "description": [], - "signature": [ - "\"user_not_authorized\"" - ], - "path": "x-pack/plugins/observability_solution/entity_manager/common/errors.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/entity_manager.mdx b/api_docs/entity_manager.mdx index 71f30d30d171b..319f97c72fe77 100644 --- a/api_docs/entity_manager.mdx +++ b/api_docs/entity_manager.mdx @@ -8,23 +8,26 @@ slug: /kibana-dev-docs/api/entityManager title: "entityManager" image: https://source.unsplash.com/400x175/?github description: API docs for the entityManager plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'entityManager'] --- import entityManagerObj from './entity_manager.devdocs.json'; Entity manager plugin for entity assets (inventory, topology, etc) -Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin. +Contact [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) for questions regarding this plugin. **Code health stats** | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 14 | 0 | 14 | 1 | +| 16 | 0 | 16 | 1 | ## Client +### Classes + + ### Interfaces diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 66ba96d6935d9..eb096d199cf08 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/esql.devdocs.json b/api_docs/esql.devdocs.json index 34e545084895c..ada151ff891ef 100644 --- a/api_docs/esql.devdocs.json +++ b/api_docs/esql.devdocs.json @@ -103,7 +103,7 @@ "tags": [], "label": "TextBasedLanguagesEditorProps", "description": [], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -119,7 +119,7 @@ "signature": [ "{ esql: string; }" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -143,7 +143,7 @@ }, ") => void" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -163,7 +163,7 @@ "text": "AggregateQuery" } ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -191,7 +191,7 @@ }, " | undefined, abortController?: AbortController | undefined) => Promise" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -212,7 +212,7 @@ }, " | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -227,7 +227,7 @@ "signature": [ "AbortController | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -235,53 +235,6 @@ ], "returnComment": [] }, - { - "parentPluginId": "esql", - "id": "def-public.TextBasedLanguagesEditorProps.expandCodeEditor", - "type": "Function", - "tags": [], - "label": "expandCodeEditor", - "description": [ - "Can be used to expand/minimize the editor" - ], - "signature": [ - "(status: boolean) => void" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "esql", - "id": "def-public.TextBasedLanguagesEditorProps.expandCodeEditor.$1", - "type": "boolean", - "tags": [], - "label": "status", - "description": [], - "signature": [ - "boolean" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "esql", - "id": "def-public.TextBasedLanguagesEditorProps.isCodeEditorExpanded", - "type": "boolean", - "tags": [], - "label": "isCodeEditorExpanded", - "description": [ - "If it is true, the editor initializes with height EDITOR_INITIAL_HEIGHT_EXPANDED" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "esql", "id": "def-public.TextBasedLanguagesEditorProps.detectedTimestamp", @@ -294,7 +247,7 @@ "signature": [ "string | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -310,7 +263,7 @@ "signature": [ "Error[] | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -326,7 +279,7 @@ "signature": [ "string | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -342,7 +295,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -358,23 +311,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "esql", - "id": "def-public.TextBasedLanguagesEditorProps.isDarkMode", - "type": "CompoundType", - "tags": [], - "label": "isDarkMode", - "description": [ - "Indicator if the editor is on dark mode" - ], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -388,23 +325,7 @@ "signature": [ "string | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "esql", - "id": "def-public.TextBasedLanguagesEditorProps.hideMinimizeButton", - "type": "CompoundType", - "tags": [], - "label": "hideMinimizeButton", - "description": [ - "If true it hides the minimize button and the user can't return to the minimized version\nUseful when the application doesn't want to give this capability" - ], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -420,7 +341,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -436,7 +357,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -452,7 +373,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -468,7 +389,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -484,7 +405,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -500,23 +421,23 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "esql", - "id": "def-public.TextBasedLanguagesEditorProps.hideHeaderWhenExpanded", + "id": "def-public.TextBasedLanguagesEditorProps.hasOutline", "type": "CompoundType", "tags": [], - "label": "hideHeaderWhenExpanded", + "label": "hasOutline", "description": [ - "hide header buttons when editor is expanded" + "adds border in the editor" ], "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false } diff --git a/api_docs/esql.mdx b/api_docs/esql.mdx index 83c548844df5d..d8c687374cb97 100644 --- a/api_docs/esql.mdx +++ b/api_docs/esql.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esql title: "esql" image: https://source.unsplash.com/400x175/?github description: API docs for the esql plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esql'] --- import esqlObj from './esql.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 29 | 0 | 10 | 0 | +| 24 | 0 | 9 | 0 | ## Client diff --git a/api_docs/esql_data_grid.mdx b/api_docs/esql_data_grid.mdx index 9996c45f378c7..886ac72ecd7cb 100644 --- a/api_docs/esql_data_grid.mdx +++ b/api_docs/esql_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esqlDataGrid title: "esqlDataGrid" image: https://source.unsplash.com/400x175/?github description: API docs for the esqlDataGrid plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esqlDataGrid'] --- import esqlDataGridObj from './esql_data_grid.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index cce25a515112d..a026c486ebcd6 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_annotation_listing.mdx b/api_docs/event_annotation_listing.mdx index 0cb37b54402cf..05fb8b1ac4fa7 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotationListing'] --- import eventAnnotationListingObj from './event_annotation_listing.devdocs.json'; diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index eab5ff2bd0eff..a5e73b5043fdf 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/exploratory_view.devdocs.json b/api_docs/exploratory_view.devdocs.json index cc89d74f52692..177d2f188b26e 100644 --- a/api_docs/exploratory_view.devdocs.json +++ b/api_docs/exploratory_view.devdocs.json @@ -1810,7 +1810,7 @@ "label": "dataType", "description": [], "signature": [ - "\"uptime\" | \"alerts\" | \"synthetics\" | \"ux\" | \"infra_logs\" | \"infra_metrics\" | \"apm\" | \"mobile\"" + "\"uptime\" | \"alerts\" | \"apm\" | \"synthetics\" | \"ux\" | \"infra_logs\" | \"infra_metrics\" | \"mobile\"" ], "path": "x-pack/plugins/observability_solution/exploratory_view/public/components/shared/exploratory_view/types.ts", "deprecated": false, diff --git a/api_docs/exploratory_view.mdx b/api_docs/exploratory_view.mdx index 519e9c0c2e269..69ac02b47f290 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: 2024-07-31 +date: 2024-08-09 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 982745ba2ae08..c83f8253e1429 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.devdocs.json b/api_docs/expression_gauge.devdocs.json index e973d03f2d740..16f8d3bf1e5a1 100644 --- a/api_docs/expression_gauge.devdocs.json +++ b/api_docs/expression_gauge.devdocs.json @@ -708,7 +708,7 @@ "label": "labelMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts", "deprecated": false, @@ -1143,7 +1143,7 @@ "label": "GaugeLabelMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/chart_expressions/expression_gauge/common/types/expression_functions.ts", "deprecated": false, diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 3d5ec7a07bc6a..8c67a0eb73b22 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: 2024-07-31 +date: 2024-08-09 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 af30fcec71591..b2e1bead027c2 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: 2024-07-31 +date: 2024-08-09 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 330b39092365b..f982fe3a16ed6 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: 2024-07-31 +date: 2024-08-09 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 d118612fe2a20..ea51a34cf01ab 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: 2024-07-31 +date: 2024-08-09 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 b226ec02edb21..47d9999fd10f6 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: 2024-07-31 +date: 2024-08-09 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 bc9f7fd265b14..366f0319b4be2 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: 2024-07-31 +date: 2024-08-09 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 1339fdceacdd5..dd2a90dbe6766 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: 2024-07-31 +date: 2024-08-09 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 ec3b35dd8382e..ce3fc533ede27 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: 2024-07-31 +date: 2024-08-09 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 dd70371ccdf99..5112e2ae86a76 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: 2024-07-31 +date: 2024-08-09 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 d970e9bd2e64b..0f75c5f89517a 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: 2024-07-31 +date: 2024-08-09 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 a31ba242a245b..c44d3e19563c7 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: 2024-07-31 +date: 2024-08-09 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 12be27753ef64..005250d67dd89 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 02b1eac5cfffa..741974c95b2aa 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.mdx b/api_docs/features.mdx index ba1e7ee0bdcb5..780c6ee08af01 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: 2024-07-31 +date: 2024-08-09 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 fb418da077cb5..ed4b692d9c523 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/fields_metadata.devdocs.json b/api_docs/fields_metadata.devdocs.json index 1c1620c2c72d2..b328d61dc2d34 100644 --- a/api_docs/fields_metadata.devdocs.json +++ b/api_docs/fields_metadata.devdocs.json @@ -635,7 +635,7 @@ "label": "FieldName", "description": [], "signature": [ - "\"@timestamp\" | \"event.sequence\" | \"event.start\" | \"event.end\" | \"event.provider\" | \"event.duration\" | \"event.action\" | \"message\" | \"event.outcome\" | \"tags\" | \"event.kind\" | \"event.original\" | \"agent.name\" | \"container.id\" | \"host.name\" | \"labels\" | \"service.environment\" | \"service.name\" | \"ecs.version\" | \"agent.build.original\" | \"agent.ephemeral_id\" | \"agent.id\" | \"agent.type\" | \"agent.version\" | \"client.address\" | \"client.as.number\" | \"client.as.organization.name\" | \"client.bytes\" | \"client.domain\" | \"client.geo.city_name\" | \"client.geo.continent_code\" | \"client.geo.continent_name\" | \"client.geo.country_iso_code\" | \"client.geo.country_name\" | \"client.geo.location\" | \"client.geo.name\" | \"client.geo.postal_code\" | \"client.geo.region_iso_code\" | \"client.geo.region_name\" | \"client.geo.timezone\" | \"client.ip\" | \"client.mac\" | \"client.nat.ip\" | \"client.nat.port\" | \"client.packets\" | \"client.port\" | \"client.registered_domain\" | \"client.subdomain\" | \"client.top_level_domain\" | \"client.user.domain\" | \"client.user.email\" | \"client.user.full_name\" | \"client.user.group.domain\" | \"client.user.group.id\" | \"client.user.group.name\" | \"client.user.hash\" | \"client.user.id\" | \"client.user.name\" | \"client.user.roles\" | \"cloud.account.id\" | \"cloud.account.name\" | \"cloud.availability_zone\" | \"cloud.instance.id\" | \"cloud.instance.name\" | \"cloud.machine.type\" | \"cloud.origin.account.id\" | \"cloud.origin.account.name\" | \"cloud.origin.availability_zone\" | \"cloud.origin.instance.id\" | \"cloud.origin.instance.name\" | \"cloud.origin.machine.type\" | \"cloud.origin.project.id\" | \"cloud.origin.project.name\" | \"cloud.origin.provider\" | \"cloud.origin.region\" | \"cloud.origin.service.name\" | \"cloud.project.id\" | \"cloud.project.name\" | \"cloud.provider\" | \"cloud.region\" | \"cloud.service.name\" | \"cloud.target.account.id\" | \"cloud.target.account.name\" | \"cloud.target.availability_zone\" | \"cloud.target.instance.id\" | \"cloud.target.instance.name\" | \"cloud.target.machine.type\" | \"cloud.target.project.id\" | \"cloud.target.project.name\" | \"cloud.target.provider\" | \"cloud.target.region\" | \"cloud.target.service.name\" | \"container.cpu.usage\" | \"container.disk.read.bytes\" | \"container.disk.write.bytes\" | \"container.image.hash.all\" | \"container.image.name\" | \"container.image.tag\" | \"container.labels\" | \"container.memory.usage\" | \"container.name\" | \"container.network.egress.bytes\" | \"container.network.ingress.bytes\" | \"container.runtime\" | \"container.security_context.privileged\" | \"destination.address\" | \"destination.as.number\" | \"destination.as.organization.name\" | \"destination.bytes\" | \"destination.domain\" | \"destination.geo.city_name\" | \"destination.geo.continent_code\" | \"destination.geo.continent_name\" | \"destination.geo.country_iso_code\" | \"destination.geo.country_name\" | \"destination.geo.location\" | \"destination.geo.name\" | \"destination.geo.postal_code\" | \"destination.geo.region_iso_code\" | \"destination.geo.region_name\" | \"destination.geo.timezone\" | \"destination.ip\" | \"destination.mac\" | \"destination.nat.ip\" | \"destination.nat.port\" | \"destination.packets\" | \"destination.port\" | \"destination.registered_domain\" | \"destination.subdomain\" | \"destination.top_level_domain\" | \"destination.user.domain\" | \"destination.user.email\" | \"destination.user.full_name\" | \"destination.user.group.domain\" | \"destination.user.group.id\" | \"destination.user.group.name\" | \"destination.user.hash\" | \"destination.user.id\" | \"destination.user.name\" | \"destination.user.roles\" | \"device.id\" | \"device.manufacturer\" | \"device.model.identifier\" | \"device.model.name\" | \"dll.code_signature.digest_algorithm\" | \"dll.code_signature.exists\" | \"dll.code_signature.signing_id\" | \"dll.code_signature.status\" | \"dll.code_signature.subject_name\" | \"dll.code_signature.team_id\" | \"dll.code_signature.timestamp\" | \"dll.code_signature.trusted\" | \"dll.code_signature.valid\" | \"dll.hash.md5\" | \"dll.hash.sha1\" | \"dll.hash.sha256\" | \"dll.hash.sha384\" | \"dll.hash.sha512\" | \"dll.hash.ssdeep\" | \"dll.hash.tlsh\" | \"dll.name\" | \"dll.path\" | \"dll.pe.architecture\" | \"dll.pe.company\" | \"dll.pe.description\" | \"dll.pe.file_version\" | \"dll.pe.go_import_hash\" | \"dll.pe.go_imports\" | \"dll.pe.go_imports_names_entropy\" | \"dll.pe.go_imports_names_var_entropy\" | \"dll.pe.go_stripped\" | \"dll.pe.imphash\" | \"dll.pe.import_hash\" | \"dll.pe.imports\" | \"dll.pe.imports_names_entropy\" | \"dll.pe.imports_names_var_entropy\" | \"dll.pe.original_file_name\" | \"dll.pe.pehash\" | \"dll.pe.product\" | \"dll.pe.sections\" | \"dns.answers\" | \"dns.header_flags\" | \"dns.id\" | \"dns.op_code\" | \"dns.question.class\" | \"dns.question.name\" | \"dns.question.registered_domain\" | \"dns.question.subdomain\" | \"dns.question.top_level_domain\" | \"dns.question.type\" | \"dns.resolved_ip\" | \"dns.response_code\" | \"dns.type\" | \"email.attachments\" | \"file.extension\" | \"file.hash.md5\" | \"file.hash.sha1\" | \"file.hash.sha256\" | \"file.hash.sha384\" | \"file.hash.sha512\" | \"file.hash.ssdeep\" | \"file.hash.tlsh\" | \"file.mime_type\" | \"file.name\" | \"file.size\" | \"email.bcc.address\" | \"email.cc.address\" | \"email.content_type\" | \"email.delivery_timestamp\" | \"email.direction\" | \"email.from.address\" | \"email.local_id\" | \"email.message_id\" | \"email.origination_timestamp\" | \"email.reply_to.address\" | \"email.sender.address\" | \"email.subject\" | \"email.to.address\" | \"email.x_mailer\" | \"error.code\" | \"error.id\" | \"error.message\" | \"error.stack_trace\" | \"error.type\" | \"event.agent_id_status\" | \"event.category\" | \"event.code\" | \"event.created\" | \"event.dataset\" | \"event.hash\" | \"event.id\" | \"event.ingested\" | \"event.module\" | \"event.reason\" | \"event.reference\" | \"event.risk_score\" | \"event.risk_score_norm\" | \"event.severity\" | \"event.timezone\" | \"event.type\" | \"event.url\" | \"faas.coldstart\" | \"faas.execution\" | \"faas.id\" | \"faas.name\" | \"faas.version\" | \"file.accessed\" | \"file.attributes\" | \"file.code_signature.digest_algorithm\" | \"file.code_signature.exists\" | \"file.code_signature.signing_id\" | \"file.code_signature.status\" | \"file.code_signature.subject_name\" | \"file.code_signature.team_id\" | \"file.code_signature.timestamp\" | \"file.code_signature.trusted\" | \"file.code_signature.valid\" | \"file.created\" | \"file.ctime\" | \"file.device\" | \"file.directory\" | \"file.drive_letter\" | \"file.elf.architecture\" | \"file.elf.byte_order\" | \"file.elf.cpu_type\" | \"file.elf.creation_date\" | \"file.elf.exports\" | \"file.elf.go_import_hash\" | \"file.elf.go_imports\" | \"file.elf.go_imports_names_entropy\" | \"file.elf.go_imports_names_var_entropy\" | \"file.elf.go_stripped\" | \"file.elf.header.abi_version\" | \"file.elf.header.class\" | \"file.elf.header.data\" | \"file.elf.header.entrypoint\" | \"file.elf.header.object_version\" | \"file.elf.header.os_abi\" | \"file.elf.header.type\" | \"file.elf.header.version\" | \"file.elf.import_hash\" | \"file.elf.imports\" | \"file.elf.imports_names_entropy\" | \"file.elf.imports_names_var_entropy\" | \"file.elf.sections\" | \"file.elf.segments\" | \"file.elf.shared_libraries\" | \"file.elf.telfhash\" | \"file.fork_name\" | \"file.gid\" | \"file.group\" | \"file.inode\" | \"file.macho.go_import_hash\" | \"file.macho.go_imports\" | \"file.macho.go_imports_names_entropy\" | \"file.macho.go_imports_names_var_entropy\" | \"file.macho.go_stripped\" | \"file.macho.import_hash\" | \"file.macho.imports\" | \"file.macho.imports_names_entropy\" | \"file.macho.imports_names_var_entropy\" | \"file.macho.sections\" | \"file.macho.symhash\" | \"file.mode\" | \"file.mtime\" | \"file.owner\" | \"file.path\" | \"file.pe.architecture\" | \"file.pe.company\" | \"file.pe.description\" | \"file.pe.file_version\" | \"file.pe.go_import_hash\" | \"file.pe.go_imports\" | \"file.pe.go_imports_names_entropy\" | \"file.pe.go_imports_names_var_entropy\" | \"file.pe.go_stripped\" | \"file.pe.imphash\" | \"file.pe.import_hash\" | \"file.pe.imports\" | \"file.pe.imports_names_entropy\" | \"file.pe.imports_names_var_entropy\" | \"file.pe.original_file_name\" | \"file.pe.pehash\" | \"file.pe.product\" | \"file.pe.sections\" | \"file.target_path\" | \"file.type\" | \"file.uid\" | \"file.x509.alternative_names\" | \"file.x509.issuer.common_name\" | \"file.x509.issuer.country\" | \"file.x509.issuer.distinguished_name\" | \"file.x509.issuer.locality\" | \"file.x509.issuer.organization\" | \"file.x509.issuer.organizational_unit\" | \"file.x509.issuer.state_or_province\" | \"file.x509.not_after\" | \"file.x509.not_before\" | \"file.x509.public_key_algorithm\" | \"file.x509.public_key_curve\" | \"file.x509.public_key_exponent\" | \"file.x509.public_key_size\" | \"file.x509.serial_number\" | \"file.x509.signature_algorithm\" | \"file.x509.subject.common_name\" | \"file.x509.subject.country\" | \"file.x509.subject.distinguished_name\" | \"file.x509.subject.locality\" | \"file.x509.subject.organization\" | \"file.x509.subject.organizational_unit\" | \"file.x509.subject.state_or_province\" | \"file.x509.version_number\" | \"group.domain\" | \"group.id\" | \"group.name\" | \"host.architecture\" | \"host.boot.id\" | \"host.cpu.usage\" | \"host.disk.read.bytes\" | \"host.disk.write.bytes\" | \"host.domain\" | \"host.geo.city_name\" | \"host.geo.continent_code\" | \"host.geo.continent_name\" | \"host.geo.country_iso_code\" | \"host.geo.country_name\" | \"host.geo.location\" | \"host.geo.name\" | \"host.geo.postal_code\" | \"host.geo.region_iso_code\" | \"host.geo.region_name\" | \"host.geo.timezone\" | \"host.hostname\" | \"host.id\" | \"host.ip\" | \"host.mac\" | \"host.network.egress.bytes\" | \"host.network.egress.packets\" | \"host.network.ingress.bytes\" | \"host.network.ingress.packets\" | \"host.os.family\" | \"host.os.full\" | \"host.os.kernel\" | \"host.os.name\" | \"host.os.platform\" | \"host.os.type\" | \"host.os.version\" | \"host.pid_ns_ino\" | \"host.risk.calculated_level\" | \"host.risk.calculated_score\" | \"host.risk.calculated_score_norm\" | \"host.risk.static_level\" | \"host.risk.static_score\" | \"host.risk.static_score_norm\" | \"host.type\" | \"host.uptime\" | \"http.request.body.bytes\" | \"http.request.body.content\" | \"http.request.bytes\" | \"http.request.id\" | \"http.request.method\" | \"http.request.mime_type\" | \"http.request.referrer\" | \"http.response.body.bytes\" | \"http.response.body.content\" | \"http.response.bytes\" | \"http.response.mime_type\" | \"http.response.status_code\" | \"http.version\" | \"log.file.path\" | \"log.level\" | \"log.logger\" | \"log.origin.file.line\" | \"log.origin.file.name\" | \"log.origin.function\" | \"log.syslog\" | \"network.application\" | \"network.bytes\" | \"network.community_id\" | \"network.direction\" | \"network.forwarded_ip\" | \"network.iana_number\" | \"network.inner\" | \"network.name\" | \"network.packets\" | \"network.protocol\" | \"network.transport\" | \"network.type\" | \"network.vlan.id\" | \"network.vlan.name\" | \"observer.egress\" | \"observer.geo.city_name\" | \"observer.geo.continent_code\" | \"observer.geo.continent_name\" | \"observer.geo.country_iso_code\" | \"observer.geo.country_name\" | \"observer.geo.location\" | \"observer.geo.name\" | \"observer.geo.postal_code\" | \"observer.geo.region_iso_code\" | \"observer.geo.region_name\" | \"observer.geo.timezone\" | \"observer.hostname\" | \"observer.ingress\" | \"observer.ip\" | \"observer.mac\" | \"observer.name\" | \"observer.os.family\" | \"observer.os.full\" | \"observer.os.kernel\" | \"observer.os.name\" | \"observer.os.platform\" | \"observer.os.type\" | \"observer.os.version\" | \"observer.product\" | \"observer.serial_number\" | \"observer.type\" | \"observer.vendor\" | \"observer.version\" | \"orchestrator.api_version\" | \"orchestrator.cluster.id\" | \"orchestrator.cluster.name\" | \"orchestrator.cluster.url\" | \"orchestrator.cluster.version\" | \"orchestrator.namespace\" | \"orchestrator.organization\" | \"orchestrator.resource.annotation\" | \"orchestrator.resource.id\" | \"orchestrator.resource.ip\" | \"orchestrator.resource.label\" | \"orchestrator.resource.name\" | \"orchestrator.resource.parent.type\" | \"orchestrator.resource.type\" | \"orchestrator.type\" | \"organization.id\" | \"organization.name\" | \"package.architecture\" | \"package.build_version\" | \"package.checksum\" | \"package.description\" | \"package.install_scope\" | \"package.installed\" | \"package.license\" | \"package.name\" | \"package.path\" | \"package.reference\" | \"package.size\" | \"package.type\" | \"package.version\" | \"process.args\" | \"process.args_count\" | \"process.code_signature.digest_algorithm\" | \"process.code_signature.exists\" | \"process.code_signature.signing_id\" | \"process.code_signature.status\" | \"process.code_signature.subject_name\" | \"process.code_signature.team_id\" | \"process.code_signature.timestamp\" | \"process.code_signature.trusted\" | \"process.code_signature.valid\" | \"process.command_line\" | \"process.elf.architecture\" | \"process.elf.byte_order\" | \"process.elf.cpu_type\" | \"process.elf.creation_date\" | \"process.elf.exports\" | \"process.elf.go_import_hash\" | \"process.elf.go_imports\" | \"process.elf.go_imports_names_entropy\" | \"process.elf.go_imports_names_var_entropy\" | \"process.elf.go_stripped\" | \"process.elf.header.abi_version\" | \"process.elf.header.class\" | \"process.elf.header.data\" | \"process.elf.header.entrypoint\" | \"process.elf.header.object_version\" | \"process.elf.header.os_abi\" | \"process.elf.header.type\" | \"process.elf.header.version\" | \"process.elf.import_hash\" | \"process.elf.imports\" | \"process.elf.imports_names_entropy\" | \"process.elf.imports_names_var_entropy\" | \"process.elf.sections\" | \"process.elf.segments\" | \"process.elf.shared_libraries\" | \"process.elf.telfhash\" | \"process.end\" | \"process.entity_id\" | \"process.entry_leader.args\" | \"process.entry_leader.args_count\" | \"process.entry_leader.attested_groups.name\" | \"process.entry_leader.attested_user.id\" | \"process.entry_leader.attested_user.name\" | \"process.entry_leader.command_line\" | \"process.entry_leader.entity_id\" | \"process.entry_leader.entry_meta.source.ip\" | \"process.entry_leader.entry_meta.type\" | \"process.entry_leader.executable\" | \"process.entry_leader.group.id\" | \"process.entry_leader.group.name\" | \"process.entry_leader.interactive\" | \"process.entry_leader.name\" | \"process.entry_leader.parent.entity_id\" | \"process.entry_leader.parent.pid\" | \"process.entry_leader.parent.session_leader.entity_id\" | \"process.entry_leader.parent.session_leader.pid\" | \"process.entry_leader.parent.session_leader.start\" | \"process.entry_leader.parent.session_leader.vpid\" | \"process.entry_leader.parent.start\" | \"process.entry_leader.parent.vpid\" | \"process.entry_leader.pid\" | \"process.entry_leader.real_group.id\" | \"process.entry_leader.real_group.name\" | \"process.entry_leader.real_user.id\" | \"process.entry_leader.real_user.name\" | \"process.entry_leader.same_as_process\" | \"process.entry_leader.saved_group.id\" | \"process.entry_leader.saved_group.name\" | \"process.entry_leader.saved_user.id\" | \"process.entry_leader.saved_user.name\" | \"process.entry_leader.start\" | \"process.entry_leader.supplemental_groups.id\" | \"process.entry_leader.supplemental_groups.name\" | \"process.entry_leader.tty\" | \"process.entry_leader.user.id\" | \"process.entry_leader.user.name\" | \"process.entry_leader.vpid\" | \"process.entry_leader.working_directory\" | \"process.env_vars\" | \"process.executable\" | \"process.exit_code\" | \"process.group_leader.args\" | \"process.group_leader.args_count\" | \"process.group_leader.command_line\" | \"process.group_leader.entity_id\" | \"process.group_leader.executable\" | \"process.group_leader.group.id\" | \"process.group_leader.group.name\" | \"process.group_leader.interactive\" | \"process.group_leader.name\" | \"process.group_leader.pid\" | \"process.group_leader.real_group.id\" | \"process.group_leader.real_group.name\" | \"process.group_leader.real_user.id\" | \"process.group_leader.real_user.name\" | \"process.group_leader.same_as_process\" | \"process.group_leader.saved_group.id\" | \"process.group_leader.saved_group.name\" | \"process.group_leader.saved_user.id\" | \"process.group_leader.saved_user.name\" | \"process.group_leader.start\" | \"process.group_leader.supplemental_groups.id\" | \"process.group_leader.supplemental_groups.name\" | \"process.group_leader.tty\" | \"process.group_leader.user.id\" | \"process.group_leader.user.name\" | \"process.group_leader.vpid\" | \"process.group_leader.working_directory\" | \"process.hash.md5\" | \"process.hash.sha1\" | \"process.hash.sha256\" | \"process.hash.sha384\" | \"process.hash.sha512\" | \"process.hash.ssdeep\" | \"process.hash.tlsh\" | \"process.interactive\" | \"process.io\" | \"process.macho.go_import_hash\" | \"process.macho.go_imports\" | \"process.macho.go_imports_names_entropy\" | \"process.macho.go_imports_names_var_entropy\" | \"process.macho.go_stripped\" | \"process.macho.import_hash\" | \"process.macho.imports\" | \"process.macho.imports_names_entropy\" | \"process.macho.imports_names_var_entropy\" | \"process.macho.sections\" | \"process.macho.symhash\" | \"process.name\" | \"process.parent.args\" | \"process.parent.args_count\" | \"process.parent.code_signature.digest_algorithm\" | \"process.parent.code_signature.exists\" | \"process.parent.code_signature.signing_id\" | \"process.parent.code_signature.status\" | \"process.parent.code_signature.subject_name\" | \"process.parent.code_signature.team_id\" | \"process.parent.code_signature.timestamp\" | \"process.parent.code_signature.trusted\" | \"process.parent.code_signature.valid\" | \"process.parent.command_line\" | \"process.parent.elf.architecture\" | \"process.parent.elf.byte_order\" | \"process.parent.elf.cpu_type\" | \"process.parent.elf.creation_date\" | \"process.parent.elf.exports\" | \"process.parent.elf.go_import_hash\" | \"process.parent.elf.go_imports\" | \"process.parent.elf.go_imports_names_entropy\" | \"process.parent.elf.go_imports_names_var_entropy\" | \"process.parent.elf.go_stripped\" | \"process.parent.elf.header.abi_version\" | \"process.parent.elf.header.class\" | \"process.parent.elf.header.data\" | \"process.parent.elf.header.entrypoint\" | \"process.parent.elf.header.object_version\" | \"process.parent.elf.header.os_abi\" | \"process.parent.elf.header.type\" | \"process.parent.elf.header.version\" | \"process.parent.elf.import_hash\" | \"process.parent.elf.imports\" | \"process.parent.elf.imports_names_entropy\" | \"process.parent.elf.imports_names_var_entropy\" | \"process.parent.elf.sections\" | \"process.parent.elf.segments\" | \"process.parent.elf.shared_libraries\" | \"process.parent.elf.telfhash\" | \"process.parent.end\" | \"process.parent.entity_id\" | \"process.parent.executable\" | \"process.parent.exit_code\" | \"process.parent.group.id\" | \"process.parent.group.name\" | \"process.parent.group_leader.entity_id\" | \"process.parent.group_leader.pid\" | \"process.parent.group_leader.start\" | \"process.parent.group_leader.vpid\" | \"process.parent.hash.md5\" | \"process.parent.hash.sha1\" | \"process.parent.hash.sha256\" | \"process.parent.hash.sha384\" | \"process.parent.hash.sha512\" | \"process.parent.hash.ssdeep\" | \"process.parent.hash.tlsh\" | \"process.parent.interactive\" | \"process.parent.macho.go_import_hash\" | \"process.parent.macho.go_imports\" | \"process.parent.macho.go_imports_names_entropy\" | \"process.parent.macho.go_imports_names_var_entropy\" | \"process.parent.macho.go_stripped\" | \"process.parent.macho.import_hash\" | \"process.parent.macho.imports\" | \"process.parent.macho.imports_names_entropy\" | \"process.parent.macho.imports_names_var_entropy\" | \"process.parent.macho.sections\" | \"process.parent.macho.symhash\" | \"process.parent.name\" | \"process.parent.pe.architecture\" | \"process.parent.pe.company\" | \"process.parent.pe.description\" | \"process.parent.pe.file_version\" | \"process.parent.pe.go_import_hash\" | \"process.parent.pe.go_imports\" | \"process.parent.pe.go_imports_names_entropy\" | \"process.parent.pe.go_imports_names_var_entropy\" | \"process.parent.pe.go_stripped\" | \"process.parent.pe.imphash\" | \"process.parent.pe.import_hash\" | \"process.parent.pe.imports\" | \"process.parent.pe.imports_names_entropy\" | \"process.parent.pe.imports_names_var_entropy\" | \"process.parent.pe.original_file_name\" | \"process.parent.pe.pehash\" | \"process.parent.pe.product\" | \"process.parent.pe.sections\" | \"process.parent.pgid\" | \"process.parent.pid\" | \"process.parent.real_group.id\" | \"process.parent.real_group.name\" | \"process.parent.real_user.id\" | \"process.parent.real_user.name\" | \"process.parent.saved_group.id\" | \"process.parent.saved_group.name\" | \"process.parent.saved_user.id\" | \"process.parent.saved_user.name\" | \"process.parent.start\" | \"process.parent.supplemental_groups.id\" | \"process.parent.supplemental_groups.name\" | \"process.parent.thread.capabilities.effective\" | \"process.parent.thread.capabilities.permitted\" | \"process.parent.thread.id\" | \"process.parent.thread.name\" | \"process.parent.title\" | \"process.parent.tty\" | \"process.parent.uptime\" | \"process.parent.user.id\" | \"process.parent.user.name\" | \"process.parent.vpid\" | \"process.parent.working_directory\" | \"process.pe.architecture\" | \"process.pe.company\" | \"process.pe.description\" | \"process.pe.file_version\" | \"process.pe.go_import_hash\" | \"process.pe.go_imports\" | \"process.pe.go_imports_names_entropy\" | \"process.pe.go_imports_names_var_entropy\" | \"process.pe.go_stripped\" | \"process.pe.imphash\" | \"process.pe.import_hash\" | \"process.pe.imports\" | \"process.pe.imports_names_entropy\" | \"process.pe.imports_names_var_entropy\" | \"process.pe.original_file_name\" | \"process.pe.pehash\" | \"process.pe.product\" | \"process.pe.sections\" | \"process.pgid\" | \"process.pid\" | \"process.previous.args\" | \"process.previous.args_count\" | \"process.previous.executable\" | \"process.real_group.id\" | \"process.real_group.name\" | \"process.real_user.id\" | \"process.real_user.name\" | \"process.saved_group.id\" | \"process.saved_group.name\" | \"process.saved_user.id\" | \"process.saved_user.name\" | \"process.session_leader.args\" | \"process.session_leader.args_count\" | \"process.session_leader.command_line\" | \"process.session_leader.entity_id\" | \"process.session_leader.executable\" | \"process.session_leader.group.id\" | \"process.session_leader.group.name\" | \"process.session_leader.interactive\" | \"process.session_leader.name\" | \"process.session_leader.parent.entity_id\" | \"process.session_leader.parent.pid\" | \"process.session_leader.parent.session_leader.entity_id\" | \"process.session_leader.parent.session_leader.pid\" | \"process.session_leader.parent.session_leader.start\" | \"process.session_leader.parent.session_leader.vpid\" | \"process.session_leader.parent.start\" | \"process.session_leader.parent.vpid\" | \"process.session_leader.pid\" | \"process.session_leader.real_group.id\" | \"process.session_leader.real_group.name\" | \"process.session_leader.real_user.id\" | \"process.session_leader.real_user.name\" | \"process.session_leader.same_as_process\" | \"process.session_leader.saved_group.id\" | \"process.session_leader.saved_group.name\" | \"process.session_leader.saved_user.id\" | \"process.session_leader.saved_user.name\" | \"process.session_leader.start\" | \"process.session_leader.supplemental_groups.id\" | \"process.session_leader.supplemental_groups.name\" | \"process.session_leader.tty\" | \"process.session_leader.user.id\" | \"process.session_leader.user.name\" | \"process.session_leader.vpid\" | \"process.session_leader.working_directory\" | \"process.start\" | \"process.supplemental_groups.id\" | \"process.supplemental_groups.name\" | \"process.thread.capabilities.effective\" | \"process.thread.capabilities.permitted\" | \"process.thread.id\" | \"process.thread.name\" | \"process.title\" | \"process.tty\" | \"process.uptime\" | \"process.user.id\" | \"process.user.name\" | \"process.vpid\" | \"process.working_directory\" | \"registry.data.bytes\" | \"registry.data.strings\" | \"registry.data.type\" | \"registry.hive\" | \"registry.key\" | \"registry.path\" | \"registry.value\" | \"related.hash\" | \"related.hosts\" | \"related.ip\" | \"related.user\" | \"rule.author\" | \"rule.category\" | \"rule.description\" | \"rule.id\" | \"rule.license\" | \"rule.name\" | \"rule.reference\" | \"rule.ruleset\" | \"rule.uuid\" | \"rule.version\" | \"server.address\" | \"server.as.number\" | \"server.as.organization.name\" | \"server.bytes\" | \"server.domain\" | \"server.geo.city_name\" | \"server.geo.continent_code\" | \"server.geo.continent_name\" | \"server.geo.country_iso_code\" | \"server.geo.country_name\" | \"server.geo.location\" | \"server.geo.name\" | \"server.geo.postal_code\" | \"server.geo.region_iso_code\" | \"server.geo.region_name\" | \"server.geo.timezone\" | \"server.ip\" | \"server.mac\" | \"server.nat.ip\" | \"server.nat.port\" | \"server.packets\" | \"server.port\" | \"server.registered_domain\" | \"server.subdomain\" | \"server.top_level_domain\" | \"server.user.domain\" | \"server.user.email\" | \"server.user.full_name\" | \"server.user.group.domain\" | \"server.user.group.id\" | \"server.user.group.name\" | \"server.user.hash\" | \"server.user.id\" | \"server.user.name\" | \"server.user.roles\" | \"service.address\" | \"service.ephemeral_id\" | \"service.id\" | \"service.node.name\" | \"service.node.role\" | \"service.node.roles\" | \"service.origin.address\" | \"service.origin.environment\" | \"service.origin.ephemeral_id\" | \"service.origin.id\" | \"service.origin.name\" | \"service.origin.node.name\" | \"service.origin.node.role\" | \"service.origin.node.roles\" | \"service.origin.state\" | \"service.origin.type\" | \"service.origin.version\" | \"service.state\" | \"service.target.address\" | \"service.target.environment\" | \"service.target.ephemeral_id\" | \"service.target.id\" | \"service.target.name\" | \"service.target.node.name\" | \"service.target.node.role\" | \"service.target.node.roles\" | \"service.target.state\" | \"service.target.type\" | \"service.target.version\" | \"service.type\" | \"service.version\" | \"source.address\" | \"source.as.number\" | \"source.as.organization.name\" | \"source.bytes\" | \"source.domain\" | \"source.geo.city_name\" | \"source.geo.continent_code\" | \"source.geo.continent_name\" | \"source.geo.country_iso_code\" | \"source.geo.country_name\" | \"source.geo.location\" | \"source.geo.name\" | \"source.geo.postal_code\" | \"source.geo.region_iso_code\" | \"source.geo.region_name\" | \"source.geo.timezone\" | \"source.ip\" | \"source.mac\" | \"source.nat.ip\" | \"source.nat.port\" | \"source.packets\" | \"source.port\" | \"source.registered_domain\" | \"source.subdomain\" | \"source.top_level_domain\" | \"source.user.domain\" | \"source.user.email\" | \"source.user.full_name\" | \"source.user.group.domain\" | \"source.user.group.id\" | \"source.user.group.name\" | \"source.user.hash\" | \"source.user.id\" | \"source.user.name\" | \"source.user.roles\" | \"span.id\" | \"threat.enrichments\" | \"threat.feed.dashboard_id\" | \"threat.feed.description\" | \"threat.feed.name\" | \"threat.feed.reference\" | \"threat.framework\" | \"threat.group.alias\" | \"threat.group.id\" | \"threat.group.name\" | \"threat.group.reference\" | \"threat.indicator.as.number\" | \"threat.indicator.as.organization.name\" | \"threat.indicator.confidence\" | \"threat.indicator.description\" | \"threat.indicator.email.address\" | \"threat.indicator.file.accessed\" | \"threat.indicator.file.attributes\" | \"threat.indicator.file.code_signature.digest_algorithm\" | \"threat.indicator.file.code_signature.exists\" | \"threat.indicator.file.code_signature.signing_id\" | \"threat.indicator.file.code_signature.status\" | \"threat.indicator.file.code_signature.subject_name\" | \"threat.indicator.file.code_signature.team_id\" | \"threat.indicator.file.code_signature.timestamp\" | \"threat.indicator.file.code_signature.trusted\" | \"threat.indicator.file.code_signature.valid\" | \"threat.indicator.file.created\" | \"threat.indicator.file.ctime\" | \"threat.indicator.file.device\" | \"threat.indicator.file.directory\" | \"threat.indicator.file.drive_letter\" | \"threat.indicator.file.elf.architecture\" | \"threat.indicator.file.elf.byte_order\" | \"threat.indicator.file.elf.cpu_type\" | \"threat.indicator.file.elf.creation_date\" | \"threat.indicator.file.elf.exports\" | \"threat.indicator.file.elf.go_import_hash\" | \"threat.indicator.file.elf.go_imports\" | \"threat.indicator.file.elf.go_imports_names_entropy\" | \"threat.indicator.file.elf.go_imports_names_var_entropy\" | \"threat.indicator.file.elf.go_stripped\" | \"threat.indicator.file.elf.header.abi_version\" | \"threat.indicator.file.elf.header.class\" | \"threat.indicator.file.elf.header.data\" | \"threat.indicator.file.elf.header.entrypoint\" | \"threat.indicator.file.elf.header.object_version\" | \"threat.indicator.file.elf.header.os_abi\" | \"threat.indicator.file.elf.header.type\" | \"threat.indicator.file.elf.header.version\" | \"threat.indicator.file.elf.import_hash\" | \"threat.indicator.file.elf.imports\" | \"threat.indicator.file.elf.imports_names_entropy\" | \"threat.indicator.file.elf.imports_names_var_entropy\" | \"threat.indicator.file.elf.sections\" | \"threat.indicator.file.elf.segments\" | \"threat.indicator.file.elf.shared_libraries\" | \"threat.indicator.file.elf.telfhash\" | \"threat.indicator.file.extension\" | \"threat.indicator.file.fork_name\" | \"threat.indicator.file.gid\" | \"threat.indicator.file.group\" | \"threat.indicator.file.hash.md5\" | \"threat.indicator.file.hash.sha1\" | \"threat.indicator.file.hash.sha256\" | \"threat.indicator.file.hash.sha384\" | \"threat.indicator.file.hash.sha512\" | \"threat.indicator.file.hash.ssdeep\" | \"threat.indicator.file.hash.tlsh\" | \"threat.indicator.file.inode\" | \"threat.indicator.file.mime_type\" | \"threat.indicator.file.mode\" | \"threat.indicator.file.mtime\" | \"threat.indicator.file.name\" | \"threat.indicator.file.owner\" | \"threat.indicator.file.path\" | \"threat.indicator.file.pe.architecture\" | \"threat.indicator.file.pe.company\" | \"threat.indicator.file.pe.description\" | \"threat.indicator.file.pe.file_version\" | \"threat.indicator.file.pe.go_import_hash\" | \"threat.indicator.file.pe.go_imports\" | \"threat.indicator.file.pe.go_imports_names_entropy\" | \"threat.indicator.file.pe.go_imports_names_var_entropy\" | \"threat.indicator.file.pe.go_stripped\" | \"threat.indicator.file.pe.imphash\" | \"threat.indicator.file.pe.import_hash\" | \"threat.indicator.file.pe.imports\" | \"threat.indicator.file.pe.imports_names_entropy\" | \"threat.indicator.file.pe.imports_names_var_entropy\" | \"threat.indicator.file.pe.original_file_name\" | \"threat.indicator.file.pe.pehash\" | \"threat.indicator.file.pe.product\" | \"threat.indicator.file.pe.sections\" | \"threat.indicator.file.size\" | \"threat.indicator.file.target_path\" | \"threat.indicator.file.type\" | \"threat.indicator.file.uid\" | \"threat.indicator.file.x509.alternative_names\" | \"threat.indicator.file.x509.issuer.common_name\" | \"threat.indicator.file.x509.issuer.country\" | \"threat.indicator.file.x509.issuer.distinguished_name\" | \"threat.indicator.file.x509.issuer.locality\" | \"threat.indicator.file.x509.issuer.organization\" | \"threat.indicator.file.x509.issuer.organizational_unit\" | \"threat.indicator.file.x509.issuer.state_or_province\" | \"threat.indicator.file.x509.not_after\" | \"threat.indicator.file.x509.not_before\" | \"threat.indicator.file.x509.public_key_algorithm\" | \"threat.indicator.file.x509.public_key_curve\" | \"threat.indicator.file.x509.public_key_exponent\" | \"threat.indicator.file.x509.public_key_size\" | \"threat.indicator.file.x509.serial_number\" | \"threat.indicator.file.x509.signature_algorithm\" | \"threat.indicator.file.x509.subject.common_name\" | \"threat.indicator.file.x509.subject.country\" | \"threat.indicator.file.x509.subject.distinguished_name\" | \"threat.indicator.file.x509.subject.locality\" | \"threat.indicator.file.x509.subject.organization\" | \"threat.indicator.file.x509.subject.organizational_unit\" | \"threat.indicator.file.x509.subject.state_or_province\" | \"threat.indicator.file.x509.version_number\" | \"threat.indicator.first_seen\" | \"threat.indicator.geo.city_name\" | \"threat.indicator.geo.continent_code\" | \"threat.indicator.geo.continent_name\" | \"threat.indicator.geo.country_iso_code\" | \"threat.indicator.geo.country_name\" | \"threat.indicator.geo.location\" | \"threat.indicator.geo.name\" | \"threat.indicator.geo.postal_code\" | \"threat.indicator.geo.region_iso_code\" | \"threat.indicator.geo.region_name\" | \"threat.indicator.geo.timezone\" | \"threat.indicator.ip\" | \"threat.indicator.last_seen\" | \"threat.indicator.marking.tlp\" | \"threat.indicator.marking.tlp_version\" | \"threat.indicator.modified_at\" | \"threat.indicator.name\" | \"threat.indicator.port\" | \"threat.indicator.provider\" | \"threat.indicator.reference\" | \"threat.indicator.registry.data.bytes\" | \"threat.indicator.registry.data.strings\" | \"threat.indicator.registry.data.type\" | \"threat.indicator.registry.hive\" | \"threat.indicator.registry.key\" | \"threat.indicator.registry.path\" | \"threat.indicator.registry.value\" | \"threat.indicator.scanner_stats\" | \"threat.indicator.sightings\" | \"threat.indicator.type\" | \"threat.indicator.url.domain\" | \"threat.indicator.url.extension\" | \"threat.indicator.url.fragment\" | \"threat.indicator.url.full\" | \"threat.indicator.url.original\" | \"threat.indicator.url.password\" | \"threat.indicator.url.path\" | \"threat.indicator.url.port\" | \"threat.indicator.url.query\" | \"threat.indicator.url.registered_domain\" | \"threat.indicator.url.scheme\" | \"threat.indicator.url.subdomain\" | \"threat.indicator.url.top_level_domain\" | \"threat.indicator.url.username\" | \"threat.indicator.x509.alternative_names\" | \"threat.indicator.x509.issuer.common_name\" | \"threat.indicator.x509.issuer.country\" | \"threat.indicator.x509.issuer.distinguished_name\" | \"threat.indicator.x509.issuer.locality\" | \"threat.indicator.x509.issuer.organization\" | \"threat.indicator.x509.issuer.organizational_unit\" | \"threat.indicator.x509.issuer.state_or_province\" | \"threat.indicator.x509.not_after\" | \"threat.indicator.x509.not_before\" | \"threat.indicator.x509.public_key_algorithm\" | \"threat.indicator.x509.public_key_curve\" | \"threat.indicator.x509.public_key_exponent\" | \"threat.indicator.x509.public_key_size\" | \"threat.indicator.x509.serial_number\" | \"threat.indicator.x509.signature_algorithm\" | \"threat.indicator.x509.subject.common_name\" | \"threat.indicator.x509.subject.country\" | \"threat.indicator.x509.subject.distinguished_name\" | \"threat.indicator.x509.subject.locality\" | \"threat.indicator.x509.subject.organization\" | \"threat.indicator.x509.subject.organizational_unit\" | \"threat.indicator.x509.subject.state_or_province\" | \"threat.indicator.x509.version_number\" | \"threat.software.alias\" | \"threat.software.id\" | \"threat.software.name\" | \"threat.software.platforms\" | \"threat.software.reference\" | \"threat.software.type\" | \"threat.tactic.id\" | \"threat.tactic.name\" | \"threat.tactic.reference\" | \"threat.technique.id\" | \"threat.technique.name\" | \"threat.technique.reference\" | \"threat.technique.subtechnique.id\" | \"threat.technique.subtechnique.name\" | \"threat.technique.subtechnique.reference\" | \"tls.cipher\" | \"tls.client.certificate\" | \"tls.client.certificate_chain\" | \"tls.client.hash.md5\" | \"tls.client.hash.sha1\" | \"tls.client.hash.sha256\" | \"tls.client.issuer\" | \"tls.client.ja3\" | \"tls.client.not_after\" | \"tls.client.not_before\" | \"tls.client.server_name\" | \"tls.client.subject\" | \"tls.client.supported_ciphers\" | \"tls.client.x509.alternative_names\" | \"tls.client.x509.issuer.common_name\" | \"tls.client.x509.issuer.country\" | \"tls.client.x509.issuer.distinguished_name\" | \"tls.client.x509.issuer.locality\" | \"tls.client.x509.issuer.organization\" | \"tls.client.x509.issuer.organizational_unit\" | \"tls.client.x509.issuer.state_or_province\" | \"tls.client.x509.not_after\" | \"tls.client.x509.not_before\" | \"tls.client.x509.public_key_algorithm\" | \"tls.client.x509.public_key_curve\" | \"tls.client.x509.public_key_exponent\" | \"tls.client.x509.public_key_size\" | \"tls.client.x509.serial_number\" | \"tls.client.x509.signature_algorithm\" | \"tls.client.x509.subject.common_name\" | \"tls.client.x509.subject.country\" | \"tls.client.x509.subject.distinguished_name\" | \"tls.client.x509.subject.locality\" | \"tls.client.x509.subject.organization\" | \"tls.client.x509.subject.organizational_unit\" | \"tls.client.x509.subject.state_or_province\" | \"tls.client.x509.version_number\" | \"tls.curve\" | \"tls.established\" | \"tls.next_protocol\" | \"tls.resumed\" | \"tls.server.certificate\" | \"tls.server.certificate_chain\" | \"tls.server.hash.md5\" | \"tls.server.hash.sha1\" | \"tls.server.hash.sha256\" | \"tls.server.issuer\" | \"tls.server.ja3s\" | \"tls.server.not_after\" | \"tls.server.not_before\" | \"tls.server.subject\" | \"tls.server.x509.alternative_names\" | \"tls.server.x509.issuer.common_name\" | \"tls.server.x509.issuer.country\" | \"tls.server.x509.issuer.distinguished_name\" | \"tls.server.x509.issuer.locality\" | \"tls.server.x509.issuer.organization\" | \"tls.server.x509.issuer.organizational_unit\" | \"tls.server.x509.issuer.state_or_province\" | \"tls.server.x509.not_after\" | \"tls.server.x509.not_before\" | \"tls.server.x509.public_key_algorithm\" | \"tls.server.x509.public_key_curve\" | \"tls.server.x509.public_key_exponent\" | \"tls.server.x509.public_key_size\" | \"tls.server.x509.serial_number\" | \"tls.server.x509.signature_algorithm\" | \"tls.server.x509.subject.common_name\" | \"tls.server.x509.subject.country\" | \"tls.server.x509.subject.distinguished_name\" | \"tls.server.x509.subject.locality\" | \"tls.server.x509.subject.organization\" | \"tls.server.x509.subject.organizational_unit\" | \"tls.server.x509.subject.state_or_province\" | \"tls.server.x509.version_number\" | \"tls.version\" | \"tls.version_protocol\" | \"trace.id\" | \"transaction.id\" | \"url.domain\" | \"url.extension\" | \"url.fragment\" | \"url.full\" | \"url.original\" | \"url.password\" | \"url.path\" | \"url.port\" | \"url.query\" | \"url.registered_domain\" | \"url.scheme\" | \"url.subdomain\" | \"url.top_level_domain\" | \"url.username\" | \"user.changes.domain\" | \"user.changes.email\" | \"user.changes.full_name\" | \"user.changes.group.domain\" | \"user.changes.group.id\" | \"user.changes.group.name\" | \"user.changes.hash\" | \"user.changes.id\" | \"user.changes.name\" | \"user.changes.roles\" | \"user.domain\" | \"user.effective.domain\" | \"user.effective.email\" | \"user.effective.full_name\" | \"user.effective.group.domain\" | \"user.effective.group.id\" | \"user.effective.group.name\" | \"user.effective.hash\" | \"user.effective.id\" | \"user.effective.name\" | \"user.effective.roles\" | \"user.email\" | \"user.full_name\" | \"user.group.domain\" | \"user.group.id\" | \"user.group.name\" | \"user.hash\" | \"user.id\" | \"user.name\" | \"user.risk.calculated_level\" | \"user.risk.calculated_score\" | \"user.risk.calculated_score_norm\" | \"user.risk.static_level\" | \"user.risk.static_score\" | \"user.risk.static_score_norm\" | \"user.roles\" | \"user.target.domain\" | \"user.target.email\" | \"user.target.full_name\" | \"user.target.group.domain\" | \"user.target.group.id\" | \"user.target.group.name\" | \"user.target.hash\" | \"user.target.id\" | \"user.target.name\" | \"user.target.roles\" | \"user_agent.device.name\" | \"user_agent.name\" | \"user_agent.original\" | \"user_agent.os.family\" | \"user_agent.os.full\" | \"user_agent.os.kernel\" | \"user_agent.os.name\" | \"user_agent.os.platform\" | \"user_agent.os.type\" | \"user_agent.os.version\" | \"user_agent.version\" | \"vulnerability.category\" | \"vulnerability.classification\" | \"vulnerability.description\" | \"vulnerability.enumeration\" | \"vulnerability.id\" | \"vulnerability.reference\" | \"vulnerability.report_id\" | \"vulnerability.scanner.vendor\" | \"vulnerability.score.base\" | \"vulnerability.score.environmental\" | \"vulnerability.score.temporal\" | \"vulnerability.score.version\" | \"vulnerability.severity\" | \"_id\" | \"_source\" | \"_index\" | \"_ignored\" | \"_routing\" | ", + "\"@timestamp\" | \"event.sequence\" | \"event.start\" | \"event.end\" | \"event.provider\" | \"event.duration\" | \"event.action\" | \"message\" | \"event.outcome\" | \"tags\" | \"event.kind\" | \"event.original\" | \"agent.name\" | \"container.id\" | \"host.name\" | \"labels\" | \"service.environment\" | \"service.name\" | \"ecs.version\" | \"agent.build.original\" | \"agent.ephemeral_id\" | \"agent.id\" | \"agent.type\" | \"agent.version\" | \"client.address\" | \"client.as.number\" | \"client.as.organization.name\" | \"client.bytes\" | \"client.domain\" | \"client.geo.city_name\" | \"client.geo.continent_code\" | \"client.geo.continent_name\" | \"client.geo.country_iso_code\" | \"client.geo.country_name\" | \"client.geo.location\" | \"client.geo.name\" | \"client.geo.postal_code\" | \"client.geo.region_iso_code\" | \"client.geo.region_name\" | \"client.geo.timezone\" | \"client.ip\" | \"client.mac\" | \"client.nat.ip\" | \"client.nat.port\" | \"client.packets\" | \"client.port\" | \"client.registered_domain\" | \"client.subdomain\" | \"client.top_level_domain\" | \"client.user.domain\" | \"client.user.email\" | \"client.user.full_name\" | \"client.user.group.domain\" | \"client.user.group.id\" | \"client.user.group.name\" | \"client.user.hash\" | \"client.user.id\" | \"client.user.name\" | \"client.user.roles\" | \"cloud.account.id\" | \"cloud.account.name\" | \"cloud.availability_zone\" | \"cloud.instance.id\" | \"cloud.instance.name\" | \"cloud.machine.type\" | \"cloud.origin.account.id\" | \"cloud.origin.account.name\" | \"cloud.origin.availability_zone\" | \"cloud.origin.instance.id\" | \"cloud.origin.instance.name\" | \"cloud.origin.machine.type\" | \"cloud.origin.project.id\" | \"cloud.origin.project.name\" | \"cloud.origin.provider\" | \"cloud.origin.region\" | \"cloud.origin.service.name\" | \"cloud.project.id\" | \"cloud.project.name\" | \"cloud.provider\" | \"cloud.region\" | \"cloud.service.name\" | \"cloud.target.account.id\" | \"cloud.target.account.name\" | \"cloud.target.availability_zone\" | \"cloud.target.instance.id\" | \"cloud.target.instance.name\" | \"cloud.target.machine.type\" | \"cloud.target.project.id\" | \"cloud.target.project.name\" | \"cloud.target.provider\" | \"cloud.target.region\" | \"cloud.target.service.name\" | \"container.cpu.usage\" | \"container.disk.read.bytes\" | \"container.disk.write.bytes\" | \"container.image.hash.all\" | \"container.image.name\" | \"container.image.tag\" | \"container.labels\" | \"container.memory.usage\" | \"container.name\" | \"container.network.egress.bytes\" | \"container.network.ingress.bytes\" | \"container.runtime\" | \"container.security_context.privileged\" | \"destination.address\" | \"destination.as.number\" | \"destination.as.organization.name\" | \"destination.bytes\" | \"destination.domain\" | \"destination.geo.city_name\" | \"destination.geo.continent_code\" | \"destination.geo.continent_name\" | \"destination.geo.country_iso_code\" | \"destination.geo.country_name\" | \"destination.geo.location\" | \"destination.geo.name\" | \"destination.geo.postal_code\" | \"destination.geo.region_iso_code\" | \"destination.geo.region_name\" | \"destination.geo.timezone\" | \"destination.ip\" | \"destination.mac\" | \"destination.nat.ip\" | \"destination.nat.port\" | \"destination.packets\" | \"destination.port\" | \"destination.registered_domain\" | \"destination.subdomain\" | \"destination.top_level_domain\" | \"destination.user.domain\" | \"destination.user.email\" | \"destination.user.full_name\" | \"destination.user.group.domain\" | \"destination.user.group.id\" | \"destination.user.group.name\" | \"destination.user.hash\" | \"destination.user.id\" | \"destination.user.name\" | \"destination.user.roles\" | \"device.id\" | \"device.manufacturer\" | \"device.model.identifier\" | \"device.model.name\" | \"dll.code_signature.digest_algorithm\" | \"dll.code_signature.exists\" | \"dll.code_signature.signing_id\" | \"dll.code_signature.status\" | \"dll.code_signature.subject_name\" | \"dll.code_signature.team_id\" | \"dll.code_signature.timestamp\" | \"dll.code_signature.trusted\" | \"dll.code_signature.valid\" | \"dll.hash.md5\" | \"dll.hash.sha1\" | \"dll.hash.sha256\" | \"dll.hash.sha384\" | \"dll.hash.sha512\" | \"dll.hash.ssdeep\" | \"dll.hash.tlsh\" | \"dll.name\" | \"dll.path\" | \"dll.pe.architecture\" | \"dll.pe.company\" | \"dll.pe.description\" | \"dll.pe.file_version\" | \"dll.pe.go_import_hash\" | \"dll.pe.go_imports\" | \"dll.pe.go_imports_names_entropy\" | \"dll.pe.go_imports_names_var_entropy\" | \"dll.pe.go_stripped\" | \"dll.pe.imphash\" | \"dll.pe.import_hash\" | \"dll.pe.imports\" | \"dll.pe.imports_names_entropy\" | \"dll.pe.imports_names_var_entropy\" | \"dll.pe.original_file_name\" | \"dll.pe.pehash\" | \"dll.pe.product\" | \"dll.pe.sections\" | \"dns.answers\" | \"dns.header_flags\" | \"dns.id\" | \"dns.op_code\" | \"dns.question.class\" | \"dns.question.name\" | \"dns.question.registered_domain\" | \"dns.question.subdomain\" | \"dns.question.top_level_domain\" | \"dns.question.type\" | \"dns.resolved_ip\" | \"dns.response_code\" | \"dns.type\" | \"email.attachments\" | \"file.extension\" | \"file.hash.md5\" | \"file.hash.sha1\" | \"file.hash.sha256\" | \"file.hash.sha384\" | \"file.hash.sha512\" | \"file.hash.ssdeep\" | \"file.hash.tlsh\" | \"file.mime_type\" | \"file.name\" | \"file.size\" | \"email.bcc.address\" | \"email.cc.address\" | \"email.content_type\" | \"email.delivery_timestamp\" | \"email.direction\" | \"email.from.address\" | \"email.local_id\" | \"email.message_id\" | \"email.origination_timestamp\" | \"email.reply_to.address\" | \"email.sender.address\" | \"email.subject\" | \"email.to.address\" | \"email.x_mailer\" | \"error.code\" | \"error.id\" | \"error.message\" | \"error.stack_trace\" | \"error.type\" | \"event.agent_id_status\" | \"event.category\" | \"event.code\" | \"event.created\" | \"event.dataset\" | \"event.hash\" | \"event.id\" | \"event.ingested\" | \"event.module\" | \"event.reason\" | \"event.reference\" | \"event.risk_score\" | \"event.risk_score_norm\" | \"event.severity\" | \"event.timezone\" | \"event.type\" | \"event.url\" | \"faas.coldstart\" | \"faas.execution\" | \"faas.id\" | \"faas.name\" | \"faas.version\" | \"file.accessed\" | \"file.attributes\" | \"file.code_signature.digest_algorithm\" | \"file.code_signature.exists\" | \"file.code_signature.signing_id\" | \"file.code_signature.status\" | \"file.code_signature.subject_name\" | \"file.code_signature.team_id\" | \"file.code_signature.timestamp\" | \"file.code_signature.trusted\" | \"file.code_signature.valid\" | \"file.created\" | \"file.ctime\" | \"file.device\" | \"file.directory\" | \"file.drive_letter\" | \"file.elf.architecture\" | \"file.elf.byte_order\" | \"file.elf.cpu_type\" | \"file.elf.creation_date\" | \"file.elf.exports\" | \"file.elf.go_import_hash\" | \"file.elf.go_imports\" | \"file.elf.go_imports_names_entropy\" | \"file.elf.go_imports_names_var_entropy\" | \"file.elf.go_stripped\" | \"file.elf.header.abi_version\" | \"file.elf.header.class\" | \"file.elf.header.data\" | \"file.elf.header.entrypoint\" | \"file.elf.header.object_version\" | \"file.elf.header.os_abi\" | \"file.elf.header.type\" | \"file.elf.header.version\" | \"file.elf.import_hash\" | \"file.elf.imports\" | \"file.elf.imports_names_entropy\" | \"file.elf.imports_names_var_entropy\" | \"file.elf.sections\" | \"file.elf.segments\" | \"file.elf.shared_libraries\" | \"file.elf.telfhash\" | \"file.fork_name\" | \"file.gid\" | \"file.group\" | \"file.inode\" | \"file.macho.go_import_hash\" | \"file.macho.go_imports\" | \"file.macho.go_imports_names_entropy\" | \"file.macho.go_imports_names_var_entropy\" | \"file.macho.go_stripped\" | \"file.macho.import_hash\" | \"file.macho.imports\" | \"file.macho.imports_names_entropy\" | \"file.macho.imports_names_var_entropy\" | \"file.macho.sections\" | \"file.macho.symhash\" | \"file.mode\" | \"file.mtime\" | \"file.owner\" | \"file.path\" | \"file.pe.architecture\" | \"file.pe.company\" | \"file.pe.description\" | \"file.pe.file_version\" | \"file.pe.go_import_hash\" | \"file.pe.go_imports\" | \"file.pe.go_imports_names_entropy\" | \"file.pe.go_imports_names_var_entropy\" | \"file.pe.go_stripped\" | \"file.pe.imphash\" | \"file.pe.import_hash\" | \"file.pe.imports\" | \"file.pe.imports_names_entropy\" | \"file.pe.imports_names_var_entropy\" | \"file.pe.original_file_name\" | \"file.pe.pehash\" | \"file.pe.product\" | \"file.pe.sections\" | \"file.target_path\" | \"file.type\" | \"file.uid\" | \"file.x509.alternative_names\" | \"file.x509.issuer.common_name\" | \"file.x509.issuer.country\" | \"file.x509.issuer.distinguished_name\" | \"file.x509.issuer.locality\" | \"file.x509.issuer.organization\" | \"file.x509.issuer.organizational_unit\" | \"file.x509.issuer.state_or_province\" | \"file.x509.not_after\" | \"file.x509.not_before\" | \"file.x509.public_key_algorithm\" | \"file.x509.public_key_curve\" | \"file.x509.public_key_exponent\" | \"file.x509.public_key_size\" | \"file.x509.serial_number\" | \"file.x509.signature_algorithm\" | \"file.x509.subject.common_name\" | \"file.x509.subject.country\" | \"file.x509.subject.distinguished_name\" | \"file.x509.subject.locality\" | \"file.x509.subject.organization\" | \"file.x509.subject.organizational_unit\" | \"file.x509.subject.state_or_province\" | \"file.x509.version_number\" | \"group.domain\" | \"group.id\" | \"group.name\" | \"host.architecture\" | \"host.boot.id\" | \"host.cpu.usage\" | \"host.disk.read.bytes\" | \"host.disk.write.bytes\" | \"host.domain\" | \"host.geo.city_name\" | \"host.geo.continent_code\" | \"host.geo.continent_name\" | \"host.geo.country_iso_code\" | \"host.geo.country_name\" | \"host.geo.location\" | \"host.geo.name\" | \"host.geo.postal_code\" | \"host.geo.region_iso_code\" | \"host.geo.region_name\" | \"host.geo.timezone\" | \"host.hostname\" | \"host.id\" | \"host.ip\" | \"host.mac\" | \"host.network.egress.bytes\" | \"host.network.egress.packets\" | \"host.network.ingress.bytes\" | \"host.network.ingress.packets\" | \"host.os.family\" | \"host.os.full\" | \"host.os.kernel\" | \"host.os.name\" | \"host.os.platform\" | \"host.os.type\" | \"host.os.version\" | \"host.pid_ns_ino\" | \"host.risk.calculated_level\" | \"host.risk.calculated_score\" | \"host.risk.calculated_score_norm\" | \"host.risk.static_level\" | \"host.risk.static_score\" | \"host.risk.static_score_norm\" | \"host.type\" | \"host.uptime\" | \"http.request.body.bytes\" | \"http.request.body.content\" | \"http.request.bytes\" | \"http.request.id\" | \"http.request.method\" | \"http.request.mime_type\" | \"http.request.referrer\" | \"http.response.body.bytes\" | \"http.response.body.content\" | \"http.response.bytes\" | \"http.response.mime_type\" | \"http.response.status_code\" | \"http.version\" | \"log.file.path\" | \"log.level\" | \"log.logger\" | \"log.origin.file.line\" | \"log.origin.file.name\" | \"log.origin.function\" | \"log.syslog\" | \"network.application\" | \"network.bytes\" | \"network.community_id\" | \"network.direction\" | \"network.forwarded_ip\" | \"network.iana_number\" | \"network.inner\" | \"network.name\" | \"network.packets\" | \"network.protocol\" | \"network.transport\" | \"network.type\" | \"network.vlan.id\" | \"network.vlan.name\" | \"observer.egress\" | \"observer.geo.city_name\" | \"observer.geo.continent_code\" | \"observer.geo.continent_name\" | \"observer.geo.country_iso_code\" | \"observer.geo.country_name\" | \"observer.geo.location\" | \"observer.geo.name\" | \"observer.geo.postal_code\" | \"observer.geo.region_iso_code\" | \"observer.geo.region_name\" | \"observer.geo.timezone\" | \"observer.hostname\" | \"observer.ingress\" | \"observer.ip\" | \"observer.mac\" | \"observer.name\" | \"observer.os.family\" | \"observer.os.full\" | \"observer.os.kernel\" | \"observer.os.name\" | \"observer.os.platform\" | \"observer.os.type\" | \"observer.os.version\" | \"observer.product\" | \"observer.serial_number\" | \"observer.type\" | \"observer.vendor\" | \"observer.version\" | \"orchestrator.api_version\" | \"orchestrator.cluster.id\" | \"orchestrator.cluster.name\" | \"orchestrator.cluster.url\" | \"orchestrator.cluster.version\" | \"orchestrator.namespace\" | \"orchestrator.organization\" | \"orchestrator.resource.annotation\" | \"orchestrator.resource.id\" | \"orchestrator.resource.ip\" | \"orchestrator.resource.label\" | \"orchestrator.resource.name\" | \"orchestrator.resource.parent.type\" | \"orchestrator.resource.type\" | \"orchestrator.type\" | \"organization.id\" | \"organization.name\" | \"package.architecture\" | \"package.build_version\" | \"package.checksum\" | \"package.description\" | \"package.install_scope\" | \"package.installed\" | \"package.license\" | \"package.name\" | \"package.path\" | \"package.reference\" | \"package.size\" | \"package.type\" | \"package.version\" | \"process.args\" | \"process.args_count\" | \"process.code_signature.digest_algorithm\" | \"process.code_signature.exists\" | \"process.code_signature.signing_id\" | \"process.code_signature.status\" | \"process.code_signature.subject_name\" | \"process.code_signature.team_id\" | \"process.code_signature.timestamp\" | \"process.code_signature.trusted\" | \"process.code_signature.valid\" | \"process.command_line\" | \"process.elf.architecture\" | \"process.elf.byte_order\" | \"process.elf.cpu_type\" | \"process.elf.creation_date\" | \"process.elf.exports\" | \"process.elf.go_import_hash\" | \"process.elf.go_imports\" | \"process.elf.go_imports_names_entropy\" | \"process.elf.go_imports_names_var_entropy\" | \"process.elf.go_stripped\" | \"process.elf.header.abi_version\" | \"process.elf.header.class\" | \"process.elf.header.data\" | \"process.elf.header.entrypoint\" | \"process.elf.header.object_version\" | \"process.elf.header.os_abi\" | \"process.elf.header.type\" | \"process.elf.header.version\" | \"process.elf.import_hash\" | \"process.elf.imports\" | \"process.elf.imports_names_entropy\" | \"process.elf.imports_names_var_entropy\" | \"process.elf.sections\" | \"process.elf.segments\" | \"process.elf.shared_libraries\" | \"process.elf.telfhash\" | \"process.end\" | \"process.entity_id\" | \"process.entry_leader.args\" | \"process.entry_leader.args_count\" | \"process.entry_leader.attested_groups.name\" | \"process.entry_leader.attested_user.id\" | \"process.entry_leader.attested_user.name\" | \"process.entry_leader.command_line\" | \"process.entry_leader.entity_id\" | \"process.entry_leader.entry_meta.source.ip\" | \"process.entry_leader.entry_meta.type\" | \"process.entry_leader.executable\" | \"process.entry_leader.group.id\" | \"process.entry_leader.group.name\" | \"process.entry_leader.interactive\" | \"process.entry_leader.name\" | \"process.entry_leader.parent.entity_id\" | \"process.entry_leader.parent.pid\" | \"process.entry_leader.parent.session_leader.entity_id\" | \"process.entry_leader.parent.session_leader.pid\" | \"process.entry_leader.parent.session_leader.start\" | \"process.entry_leader.parent.session_leader.vpid\" | \"process.entry_leader.parent.start\" | \"process.entry_leader.parent.vpid\" | \"process.entry_leader.pid\" | \"process.entry_leader.real_group.id\" | \"process.entry_leader.real_group.name\" | \"process.entry_leader.real_user.id\" | \"process.entry_leader.real_user.name\" | \"process.entry_leader.same_as_process\" | \"process.entry_leader.saved_group.id\" | \"process.entry_leader.saved_group.name\" | \"process.entry_leader.saved_user.id\" | \"process.entry_leader.saved_user.name\" | \"process.entry_leader.start\" | \"process.entry_leader.supplemental_groups.id\" | \"process.entry_leader.supplemental_groups.name\" | \"process.entry_leader.tty\" | \"process.entry_leader.user.id\" | \"process.entry_leader.user.name\" | \"process.entry_leader.vpid\" | \"process.entry_leader.working_directory\" | \"process.env_vars\" | \"process.executable\" | \"process.exit_code\" | \"process.group_leader.args\" | \"process.group_leader.args_count\" | \"process.group_leader.command_line\" | \"process.group_leader.entity_id\" | \"process.group_leader.executable\" | \"process.group_leader.group.id\" | \"process.group_leader.group.name\" | \"process.group_leader.interactive\" | \"process.group_leader.name\" | \"process.group_leader.pid\" | \"process.group_leader.real_group.id\" | \"process.group_leader.real_group.name\" | \"process.group_leader.real_user.id\" | \"process.group_leader.real_user.name\" | \"process.group_leader.same_as_process\" | \"process.group_leader.saved_group.id\" | \"process.group_leader.saved_group.name\" | \"process.group_leader.saved_user.id\" | \"process.group_leader.saved_user.name\" | \"process.group_leader.start\" | \"process.group_leader.supplemental_groups.id\" | \"process.group_leader.supplemental_groups.name\" | \"process.group_leader.tty\" | \"process.group_leader.user.id\" | \"process.group_leader.user.name\" | \"process.group_leader.vpid\" | \"process.group_leader.working_directory\" | \"process.hash.md5\" | \"process.hash.sha1\" | \"process.hash.sha256\" | \"process.hash.sha384\" | \"process.hash.sha512\" | \"process.hash.ssdeep\" | \"process.hash.tlsh\" | \"process.interactive\" | \"process.io\" | \"process.macho.go_import_hash\" | \"process.macho.go_imports\" | \"process.macho.go_imports_names_entropy\" | \"process.macho.go_imports_names_var_entropy\" | \"process.macho.go_stripped\" | \"process.macho.import_hash\" | \"process.macho.imports\" | \"process.macho.imports_names_entropy\" | \"process.macho.imports_names_var_entropy\" | \"process.macho.sections\" | \"process.macho.symhash\" | \"process.name\" | \"process.parent.args\" | \"process.parent.args_count\" | \"process.parent.code_signature.digest_algorithm\" | \"process.parent.code_signature.exists\" | \"process.parent.code_signature.signing_id\" | \"process.parent.code_signature.status\" | \"process.parent.code_signature.subject_name\" | \"process.parent.code_signature.team_id\" | \"process.parent.code_signature.timestamp\" | \"process.parent.code_signature.trusted\" | \"process.parent.code_signature.valid\" | \"process.parent.command_line\" | \"process.parent.elf.architecture\" | \"process.parent.elf.byte_order\" | \"process.parent.elf.cpu_type\" | \"process.parent.elf.creation_date\" | \"process.parent.elf.exports\" | \"process.parent.elf.go_import_hash\" | \"process.parent.elf.go_imports\" | \"process.parent.elf.go_imports_names_entropy\" | \"process.parent.elf.go_imports_names_var_entropy\" | \"process.parent.elf.go_stripped\" | \"process.parent.elf.header.abi_version\" | \"process.parent.elf.header.class\" | \"process.parent.elf.header.data\" | \"process.parent.elf.header.entrypoint\" | \"process.parent.elf.header.object_version\" | \"process.parent.elf.header.os_abi\" | \"process.parent.elf.header.type\" | \"process.parent.elf.header.version\" | \"process.parent.elf.import_hash\" | \"process.parent.elf.imports\" | \"process.parent.elf.imports_names_entropy\" | \"process.parent.elf.imports_names_var_entropy\" | \"process.parent.elf.sections\" | \"process.parent.elf.segments\" | \"process.parent.elf.shared_libraries\" | \"process.parent.elf.telfhash\" | \"process.parent.end\" | \"process.parent.entity_id\" | \"process.parent.executable\" | \"process.parent.exit_code\" | \"process.parent.group.id\" | \"process.parent.group.name\" | \"process.parent.group_leader.entity_id\" | \"process.parent.group_leader.pid\" | \"process.parent.group_leader.start\" | \"process.parent.group_leader.vpid\" | \"process.parent.hash.md5\" | \"process.parent.hash.sha1\" | \"process.parent.hash.sha256\" | \"process.parent.hash.sha384\" | \"process.parent.hash.sha512\" | \"process.parent.hash.ssdeep\" | \"process.parent.hash.tlsh\" | \"process.parent.interactive\" | \"process.parent.macho.go_import_hash\" | \"process.parent.macho.go_imports\" | \"process.parent.macho.go_imports_names_entropy\" | \"process.parent.macho.go_imports_names_var_entropy\" | \"process.parent.macho.go_stripped\" | \"process.parent.macho.import_hash\" | \"process.parent.macho.imports\" | \"process.parent.macho.imports_names_entropy\" | \"process.parent.macho.imports_names_var_entropy\" | \"process.parent.macho.sections\" | \"process.parent.macho.symhash\" | \"process.parent.name\" | \"process.parent.pe.architecture\" | \"process.parent.pe.company\" | \"process.parent.pe.description\" | \"process.parent.pe.file_version\" | \"process.parent.pe.go_import_hash\" | \"process.parent.pe.go_imports\" | \"process.parent.pe.go_imports_names_entropy\" | \"process.parent.pe.go_imports_names_var_entropy\" | \"process.parent.pe.go_stripped\" | \"process.parent.pe.imphash\" | \"process.parent.pe.import_hash\" | \"process.parent.pe.imports\" | \"process.parent.pe.imports_names_entropy\" | \"process.parent.pe.imports_names_var_entropy\" | \"process.parent.pe.original_file_name\" | \"process.parent.pe.pehash\" | \"process.parent.pe.product\" | \"process.parent.pe.sections\" | \"process.parent.pgid\" | \"process.parent.pid\" | \"process.parent.real_group.id\" | \"process.parent.real_group.name\" | \"process.parent.real_user.id\" | \"process.parent.real_user.name\" | \"process.parent.saved_group.id\" | \"process.parent.saved_group.name\" | \"process.parent.saved_user.id\" | \"process.parent.saved_user.name\" | \"process.parent.start\" | \"process.parent.supplemental_groups.id\" | \"process.parent.supplemental_groups.name\" | \"process.parent.thread.capabilities.effective\" | \"process.parent.thread.capabilities.permitted\" | \"process.parent.thread.id\" | \"process.parent.thread.name\" | \"process.parent.title\" | \"process.parent.tty\" | \"process.parent.uptime\" | \"process.parent.user.id\" | \"process.parent.user.name\" | \"process.parent.vpid\" | \"process.parent.working_directory\" | \"process.pe.architecture\" | \"process.pe.company\" | \"process.pe.description\" | \"process.pe.file_version\" | \"process.pe.go_import_hash\" | \"process.pe.go_imports\" | \"process.pe.go_imports_names_entropy\" | \"process.pe.go_imports_names_var_entropy\" | \"process.pe.go_stripped\" | \"process.pe.imphash\" | \"process.pe.import_hash\" | \"process.pe.imports\" | \"process.pe.imports_names_entropy\" | \"process.pe.imports_names_var_entropy\" | \"process.pe.original_file_name\" | \"process.pe.pehash\" | \"process.pe.product\" | \"process.pe.sections\" | \"process.pgid\" | \"process.pid\" | \"process.previous.args\" | \"process.previous.args_count\" | \"process.previous.executable\" | \"process.real_group.id\" | \"process.real_group.name\" | \"process.real_user.id\" | \"process.real_user.name\" | \"process.saved_group.id\" | \"process.saved_group.name\" | \"process.saved_user.id\" | \"process.saved_user.name\" | \"process.session_leader.args\" | \"process.session_leader.args_count\" | \"process.session_leader.command_line\" | \"process.session_leader.entity_id\" | \"process.session_leader.executable\" | \"process.session_leader.group.id\" | \"process.session_leader.group.name\" | \"process.session_leader.interactive\" | \"process.session_leader.name\" | \"process.session_leader.parent.entity_id\" | \"process.session_leader.parent.pid\" | \"process.session_leader.parent.session_leader.entity_id\" | \"process.session_leader.parent.session_leader.pid\" | \"process.session_leader.parent.session_leader.start\" | \"process.session_leader.parent.session_leader.vpid\" | \"process.session_leader.parent.start\" | \"process.session_leader.parent.vpid\" | \"process.session_leader.pid\" | \"process.session_leader.real_group.id\" | \"process.session_leader.real_group.name\" | \"process.session_leader.real_user.id\" | \"process.session_leader.real_user.name\" | \"process.session_leader.same_as_process\" | \"process.session_leader.saved_group.id\" | \"process.session_leader.saved_group.name\" | \"process.session_leader.saved_user.id\" | \"process.session_leader.saved_user.name\" | \"process.session_leader.start\" | \"process.session_leader.supplemental_groups.id\" | \"process.session_leader.supplemental_groups.name\" | \"process.session_leader.tty\" | \"process.session_leader.user.id\" | \"process.session_leader.user.name\" | \"process.session_leader.vpid\" | \"process.session_leader.working_directory\" | \"process.start\" | \"process.supplemental_groups.id\" | \"process.supplemental_groups.name\" | \"process.thread.capabilities.effective\" | \"process.thread.capabilities.permitted\" | \"process.thread.id\" | \"process.thread.name\" | \"process.title\" | \"process.tty\" | \"process.uptime\" | \"process.user.id\" | \"process.user.name\" | \"process.vpid\" | \"process.working_directory\" | \"registry.data.bytes\" | \"registry.data.strings\" | \"registry.data.type\" | \"registry.hive\" | \"registry.key\" | \"registry.path\" | \"registry.value\" | \"related.hash\" | \"related.hosts\" | \"related.ip\" | \"related.user\" | \"rule.author\" | \"rule.category\" | \"rule.description\" | \"rule.id\" | \"rule.license\" | \"rule.name\" | \"rule.reference\" | \"rule.ruleset\" | \"rule.uuid\" | \"rule.version\" | \"server.address\" | \"server.as.number\" | \"server.as.organization.name\" | \"server.bytes\" | \"server.domain\" | \"server.geo.city_name\" | \"server.geo.continent_code\" | \"server.geo.continent_name\" | \"server.geo.country_iso_code\" | \"server.geo.country_name\" | \"server.geo.location\" | \"server.geo.name\" | \"server.geo.postal_code\" | \"server.geo.region_iso_code\" | \"server.geo.region_name\" | \"server.geo.timezone\" | \"server.ip\" | \"server.mac\" | \"server.nat.ip\" | \"server.nat.port\" | \"server.packets\" | \"server.port\" | \"server.registered_domain\" | \"server.subdomain\" | \"server.top_level_domain\" | \"server.user.domain\" | \"server.user.email\" | \"server.user.full_name\" | \"server.user.group.domain\" | \"server.user.group.id\" | \"server.user.group.name\" | \"server.user.hash\" | \"server.user.id\" | \"server.user.name\" | \"server.user.roles\" | \"service.address\" | \"service.ephemeral_id\" | \"service.id\" | \"service.node.name\" | \"service.node.role\" | \"service.node.roles\" | \"service.origin.address\" | \"service.origin.environment\" | \"service.origin.ephemeral_id\" | \"service.origin.id\" | \"service.origin.name\" | \"service.origin.node.name\" | \"service.origin.node.role\" | \"service.origin.node.roles\" | \"service.origin.state\" | \"service.origin.type\" | \"service.origin.version\" | \"service.state\" | \"service.target.address\" | \"service.target.environment\" | \"service.target.ephemeral_id\" | \"service.target.id\" | \"service.target.name\" | \"service.target.node.name\" | \"service.target.node.role\" | \"service.target.node.roles\" | \"service.target.state\" | \"service.target.type\" | \"service.target.version\" | \"service.type\" | \"service.version\" | \"source.address\" | \"source.as.number\" | \"source.as.organization.name\" | \"source.bytes\" | \"source.domain\" | \"source.geo.city_name\" | \"source.geo.continent_code\" | \"source.geo.continent_name\" | \"source.geo.country_iso_code\" | \"source.geo.country_name\" | \"source.geo.location\" | \"source.geo.name\" | \"source.geo.postal_code\" | \"source.geo.region_iso_code\" | \"source.geo.region_name\" | \"source.geo.timezone\" | \"source.ip\" | \"source.mac\" | \"source.nat.ip\" | \"source.nat.port\" | \"source.packets\" | \"source.port\" | \"source.registered_domain\" | \"source.subdomain\" | \"source.top_level_domain\" | \"source.user.domain\" | \"source.user.email\" | \"source.user.full_name\" | \"source.user.group.domain\" | \"source.user.group.id\" | \"source.user.group.name\" | \"source.user.hash\" | \"source.user.id\" | \"source.user.name\" | \"source.user.roles\" | \"span.id\" | \"threat.enrichments\" | \"threat.feed.dashboard_id\" | \"threat.feed.description\" | \"threat.feed.name\" | \"threat.feed.reference\" | \"threat.framework\" | \"threat.group.alias\" | \"threat.group.id\" | \"threat.group.name\" | \"threat.group.reference\" | \"threat.indicator.as.number\" | \"threat.indicator.as.organization.name\" | \"threat.indicator.confidence\" | \"threat.indicator.description\" | \"threat.indicator.email.address\" | \"threat.indicator.file.accessed\" | \"threat.indicator.file.attributes\" | \"threat.indicator.file.code_signature.digest_algorithm\" | \"threat.indicator.file.code_signature.exists\" | \"threat.indicator.file.code_signature.signing_id\" | \"threat.indicator.file.code_signature.status\" | \"threat.indicator.file.code_signature.subject_name\" | \"threat.indicator.file.code_signature.team_id\" | \"threat.indicator.file.code_signature.timestamp\" | \"threat.indicator.file.code_signature.trusted\" | \"threat.indicator.file.code_signature.valid\" | \"threat.indicator.file.created\" | \"threat.indicator.file.ctime\" | \"threat.indicator.file.device\" | \"threat.indicator.file.directory\" | \"threat.indicator.file.drive_letter\" | \"threat.indicator.file.elf.architecture\" | \"threat.indicator.file.elf.byte_order\" | \"threat.indicator.file.elf.cpu_type\" | \"threat.indicator.file.elf.creation_date\" | \"threat.indicator.file.elf.exports\" | \"threat.indicator.file.elf.go_import_hash\" | \"threat.indicator.file.elf.go_imports\" | \"threat.indicator.file.elf.go_imports_names_entropy\" | \"threat.indicator.file.elf.go_imports_names_var_entropy\" | \"threat.indicator.file.elf.go_stripped\" | \"threat.indicator.file.elf.header.abi_version\" | \"threat.indicator.file.elf.header.class\" | \"threat.indicator.file.elf.header.data\" | \"threat.indicator.file.elf.header.entrypoint\" | \"threat.indicator.file.elf.header.object_version\" | \"threat.indicator.file.elf.header.os_abi\" | \"threat.indicator.file.elf.header.type\" | \"threat.indicator.file.elf.header.version\" | \"threat.indicator.file.elf.import_hash\" | \"threat.indicator.file.elf.imports\" | \"threat.indicator.file.elf.imports_names_entropy\" | \"threat.indicator.file.elf.imports_names_var_entropy\" | \"threat.indicator.file.elf.sections\" | \"threat.indicator.file.elf.segments\" | \"threat.indicator.file.elf.shared_libraries\" | \"threat.indicator.file.elf.telfhash\" | \"threat.indicator.file.extension\" | \"threat.indicator.file.fork_name\" | \"threat.indicator.file.gid\" | \"threat.indicator.file.group\" | \"threat.indicator.file.hash.md5\" | \"threat.indicator.file.hash.sha1\" | \"threat.indicator.file.hash.sha256\" | \"threat.indicator.file.hash.sha384\" | \"threat.indicator.file.hash.sha512\" | \"threat.indicator.file.hash.ssdeep\" | \"threat.indicator.file.hash.tlsh\" | \"threat.indicator.file.inode\" | \"threat.indicator.file.mime_type\" | \"threat.indicator.file.mode\" | \"threat.indicator.file.mtime\" | \"threat.indicator.file.name\" | \"threat.indicator.file.owner\" | \"threat.indicator.file.path\" | \"threat.indicator.file.pe.architecture\" | \"threat.indicator.file.pe.company\" | \"threat.indicator.file.pe.description\" | \"threat.indicator.file.pe.file_version\" | \"threat.indicator.file.pe.go_import_hash\" | \"threat.indicator.file.pe.go_imports\" | \"threat.indicator.file.pe.go_imports_names_entropy\" | \"threat.indicator.file.pe.go_imports_names_var_entropy\" | \"threat.indicator.file.pe.go_stripped\" | \"threat.indicator.file.pe.imphash\" | \"threat.indicator.file.pe.import_hash\" | \"threat.indicator.file.pe.imports\" | \"threat.indicator.file.pe.imports_names_entropy\" | \"threat.indicator.file.pe.imports_names_var_entropy\" | \"threat.indicator.file.pe.original_file_name\" | \"threat.indicator.file.pe.pehash\" | \"threat.indicator.file.pe.product\" | \"threat.indicator.file.pe.sections\" | \"threat.indicator.file.size\" | \"threat.indicator.file.target_path\" | \"threat.indicator.file.type\" | \"threat.indicator.file.uid\" | \"threat.indicator.file.x509.alternative_names\" | \"threat.indicator.file.x509.issuer.common_name\" | \"threat.indicator.file.x509.issuer.country\" | \"threat.indicator.file.x509.issuer.distinguished_name\" | \"threat.indicator.file.x509.issuer.locality\" | \"threat.indicator.file.x509.issuer.organization\" | \"threat.indicator.file.x509.issuer.organizational_unit\" | \"threat.indicator.file.x509.issuer.state_or_province\" | \"threat.indicator.file.x509.not_after\" | \"threat.indicator.file.x509.not_before\" | \"threat.indicator.file.x509.public_key_algorithm\" | \"threat.indicator.file.x509.public_key_curve\" | \"threat.indicator.file.x509.public_key_exponent\" | \"threat.indicator.file.x509.public_key_size\" | \"threat.indicator.file.x509.serial_number\" | \"threat.indicator.file.x509.signature_algorithm\" | \"threat.indicator.file.x509.subject.common_name\" | \"threat.indicator.file.x509.subject.country\" | \"threat.indicator.file.x509.subject.distinguished_name\" | \"threat.indicator.file.x509.subject.locality\" | \"threat.indicator.file.x509.subject.organization\" | \"threat.indicator.file.x509.subject.organizational_unit\" | \"threat.indicator.file.x509.subject.state_or_province\" | \"threat.indicator.file.x509.version_number\" | \"threat.indicator.first_seen\" | \"threat.indicator.geo.city_name\" | \"threat.indicator.geo.continent_code\" | \"threat.indicator.geo.continent_name\" | \"threat.indicator.geo.country_iso_code\" | \"threat.indicator.geo.country_name\" | \"threat.indicator.geo.location\" | \"threat.indicator.geo.name\" | \"threat.indicator.geo.postal_code\" | \"threat.indicator.geo.region_iso_code\" | \"threat.indicator.geo.region_name\" | \"threat.indicator.geo.timezone\" | \"threat.indicator.ip\" | \"threat.indicator.last_seen\" | \"threat.indicator.marking.tlp\" | \"threat.indicator.marking.tlp_version\" | \"threat.indicator.modified_at\" | \"threat.indicator.name\" | \"threat.indicator.port\" | \"threat.indicator.provider\" | \"threat.indicator.reference\" | \"threat.indicator.registry.data.bytes\" | \"threat.indicator.registry.data.strings\" | \"threat.indicator.registry.data.type\" | \"threat.indicator.registry.hive\" | \"threat.indicator.registry.key\" | \"threat.indicator.registry.path\" | \"threat.indicator.registry.value\" | \"threat.indicator.scanner_stats\" | \"threat.indicator.sightings\" | \"threat.indicator.type\" | \"threat.indicator.url.domain\" | \"threat.indicator.url.extension\" | \"threat.indicator.url.fragment\" | \"threat.indicator.url.full\" | \"threat.indicator.url.original\" | \"threat.indicator.url.password\" | \"threat.indicator.url.path\" | \"threat.indicator.url.port\" | \"threat.indicator.url.query\" | \"threat.indicator.url.registered_domain\" | \"threat.indicator.url.scheme\" | \"threat.indicator.url.subdomain\" | \"threat.indicator.url.top_level_domain\" | \"threat.indicator.url.username\" | \"threat.indicator.x509.alternative_names\" | \"threat.indicator.x509.issuer.common_name\" | \"threat.indicator.x509.issuer.country\" | \"threat.indicator.x509.issuer.distinguished_name\" | \"threat.indicator.x509.issuer.locality\" | \"threat.indicator.x509.issuer.organization\" | \"threat.indicator.x509.issuer.organizational_unit\" | \"threat.indicator.x509.issuer.state_or_province\" | \"threat.indicator.x509.not_after\" | \"threat.indicator.x509.not_before\" | \"threat.indicator.x509.public_key_algorithm\" | \"threat.indicator.x509.public_key_curve\" | \"threat.indicator.x509.public_key_exponent\" | \"threat.indicator.x509.public_key_size\" | \"threat.indicator.x509.serial_number\" | \"threat.indicator.x509.signature_algorithm\" | \"threat.indicator.x509.subject.common_name\" | \"threat.indicator.x509.subject.country\" | \"threat.indicator.x509.subject.distinguished_name\" | \"threat.indicator.x509.subject.locality\" | \"threat.indicator.x509.subject.organization\" | \"threat.indicator.x509.subject.organizational_unit\" | \"threat.indicator.x509.subject.state_or_province\" | \"threat.indicator.x509.version_number\" | \"threat.software.alias\" | \"threat.software.id\" | \"threat.software.name\" | \"threat.software.platforms\" | \"threat.software.reference\" | \"threat.software.type\" | \"threat.tactic.id\" | \"threat.tactic.name\" | \"threat.tactic.reference\" | \"threat.technique.id\" | \"threat.technique.name\" | \"threat.technique.reference\" | \"threat.technique.subtechnique.id\" | \"threat.technique.subtechnique.name\" | \"threat.technique.subtechnique.reference\" | \"tls.cipher\" | \"tls.client.certificate\" | \"tls.client.certificate_chain\" | \"tls.client.hash.md5\" | \"tls.client.hash.sha1\" | \"tls.client.hash.sha256\" | \"tls.client.issuer\" | \"tls.client.ja3\" | \"tls.client.not_after\" | \"tls.client.not_before\" | \"tls.client.server_name\" | \"tls.client.subject\" | \"tls.client.supported_ciphers\" | \"tls.client.x509.alternative_names\" | \"tls.client.x509.issuer.common_name\" | \"tls.client.x509.issuer.country\" | \"tls.client.x509.issuer.distinguished_name\" | \"tls.client.x509.issuer.locality\" | \"tls.client.x509.issuer.organization\" | \"tls.client.x509.issuer.organizational_unit\" | \"tls.client.x509.issuer.state_or_province\" | \"tls.client.x509.not_after\" | \"tls.client.x509.not_before\" | \"tls.client.x509.public_key_algorithm\" | \"tls.client.x509.public_key_curve\" | \"tls.client.x509.public_key_exponent\" | \"tls.client.x509.public_key_size\" | \"tls.client.x509.serial_number\" | \"tls.client.x509.signature_algorithm\" | \"tls.client.x509.subject.common_name\" | \"tls.client.x509.subject.country\" | \"tls.client.x509.subject.distinguished_name\" | \"tls.client.x509.subject.locality\" | \"tls.client.x509.subject.organization\" | \"tls.client.x509.subject.organizational_unit\" | \"tls.client.x509.subject.state_or_province\" | \"tls.client.x509.version_number\" | \"tls.curve\" | \"tls.established\" | \"tls.next_protocol\" | \"tls.resumed\" | \"tls.server.certificate\" | \"tls.server.certificate_chain\" | \"tls.server.hash.md5\" | \"tls.server.hash.sha1\" | \"tls.server.hash.sha256\" | \"tls.server.issuer\" | \"tls.server.ja3s\" | \"tls.server.not_after\" | \"tls.server.not_before\" | \"tls.server.subject\" | \"tls.server.x509.alternative_names\" | \"tls.server.x509.issuer.common_name\" | \"tls.server.x509.issuer.country\" | \"tls.server.x509.issuer.distinguished_name\" | \"tls.server.x509.issuer.locality\" | \"tls.server.x509.issuer.organization\" | \"tls.server.x509.issuer.organizational_unit\" | \"tls.server.x509.issuer.state_or_province\" | \"tls.server.x509.not_after\" | \"tls.server.x509.not_before\" | \"tls.server.x509.public_key_algorithm\" | \"tls.server.x509.public_key_curve\" | \"tls.server.x509.public_key_exponent\" | \"tls.server.x509.public_key_size\" | \"tls.server.x509.serial_number\" | \"tls.server.x509.signature_algorithm\" | \"tls.server.x509.subject.common_name\" | \"tls.server.x509.subject.country\" | \"tls.server.x509.subject.distinguished_name\" | \"tls.server.x509.subject.locality\" | \"tls.server.x509.subject.organization\" | \"tls.server.x509.subject.organizational_unit\" | \"tls.server.x509.subject.state_or_province\" | \"tls.server.x509.version_number\" | \"tls.version\" | \"tls.version_protocol\" | \"trace.id\" | \"transaction.id\" | \"url.domain\" | \"url.extension\" | \"url.fragment\" | \"url.full\" | \"url.original\" | \"url.password\" | \"url.path\" | \"url.port\" | \"url.query\" | \"url.registered_domain\" | \"url.scheme\" | \"url.subdomain\" | \"url.top_level_domain\" | \"url.username\" | \"user.changes.domain\" | \"user.changes.email\" | \"user.changes.full_name\" | \"user.changes.group.domain\" | \"user.changes.group.id\" | \"user.changes.group.name\" | \"user.changes.hash\" | \"user.changes.id\" | \"user.changes.name\" | \"user.changes.roles\" | \"user.domain\" | \"user.effective.domain\" | \"user.effective.email\" | \"user.effective.full_name\" | \"user.effective.group.domain\" | \"user.effective.group.id\" | \"user.effective.group.name\" | \"user.effective.hash\" | \"user.effective.id\" | \"user.effective.name\" | \"user.effective.roles\" | \"user.email\" | \"user.full_name\" | \"user.group.domain\" | \"user.group.id\" | \"user.group.name\" | \"user.hash\" | \"user.id\" | \"user.name\" | \"user.risk.calculated_level\" | \"user.risk.calculated_score\" | \"user.risk.calculated_score_norm\" | \"user.risk.static_level\" | \"user.risk.static_score\" | \"user.risk.static_score_norm\" | \"user.roles\" | \"user.target.domain\" | \"user.target.email\" | \"user.target.full_name\" | \"user.target.group.domain\" | \"user.target.group.id\" | \"user.target.group.name\" | \"user.target.hash\" | \"user.target.id\" | \"user.target.name\" | \"user.target.roles\" | \"user_agent.device.name\" | \"user_agent.name\" | \"user_agent.original\" | \"user_agent.os.family\" | \"user_agent.os.full\" | \"user_agent.os.kernel\" | \"user_agent.os.name\" | \"user_agent.os.platform\" | \"user_agent.os.type\" | \"user_agent.os.version\" | \"user_agent.version\" | \"vulnerability.category\" | \"vulnerability.classification\" | \"vulnerability.description\" | \"vulnerability.enumeration\" | \"vulnerability.id\" | \"vulnerability.reference\" | \"vulnerability.report_id\" | \"vulnerability.scanner.vendor\" | \"vulnerability.score.base\" | \"vulnerability.score.environmental\" | \"vulnerability.score.temporal\" | \"vulnerability.score.version\" | \"vulnerability.severity\" | \"_id\" | \"_source\" | \"_index\" | \"_routing\" | \"_ignored\" | ", { "pluginId": "fieldsMetadata", "scope": "common", diff --git a/api_docs/fields_metadata.mdx b/api_docs/fields_metadata.mdx index 92381696d887d..25cbf24dd6aba 100644 --- a/api_docs/fields_metadata.mdx +++ b/api_docs/fields_metadata.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldsMetadata title: "fieldsMetadata" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldsMetadata plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldsMetadata'] --- import fieldsMetadataObj from './fields_metadata.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 3c137b0954fae..0d7404c49413e 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index f66d17574d9a0..f3cc7d80e6a15 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -5400,6 +5400,10 @@ "plugin": "cases", "path": "x-pack/plugins/cases/server/files/index.test.ts" }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/server/files/index.test.ts" + }, { "plugin": "cases", "path": "x-pack/plugins/cases/server/files/index.test.ts" diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 9aa9464fd7b55..07c7ea0447888 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: 2024-07-31 +date: 2024-08-09 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 b357847034c8b..ad392100e992b 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: 2024-07-31 +date: 2024-08-09 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 4fc99a2291eff..54f1b16567154 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -7553,7 +7553,7 @@ { "parentPluginId": "fleet", "id": "def-server.FleetSetupDeps.usageCollection", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "usageCollection", "description": [], @@ -8089,13 +8089,7 @@ "description": [], "signature": [ "(options: { pkgName: string; pkgVersion?: string | undefined; spaceId?: string | undefined; force?: boolean | undefined; }) => Promise<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.Installation", - "text": "Installation" - }, + "EnsurePackageResult", ">" ], "path": "x-pack/plugins/fleet/server/services/epm/package_service.ts", @@ -23223,7 +23217,7 @@ "label": "monitoring_enabled", "description": [], "signature": [ - "(\"metrics\" | \"logs\")[] | undefined" + "(\"metrics\" | \"traces\" | \"logs\")[] | undefined" ], "path": "x-pack/plugins/fleet/common/types/models/agent_policy.ts", "deprecated": false, @@ -27646,7 +27640,7 @@ "label": "PackageSpecCategory", "description": [], "signature": [ - "\"monitoring\" | \"security\" | \"connector\" | \"observability\" | \"infrastructure\" | \"cloud\" | \"custom\" | \"enterprise_search\" | \"advanced_analytics_ueba\" | \"analytics_engine\" | \"application_observability\" | \"app_search\" | \"auditd\" | \"authentication\" | \"aws\" | \"azure\" | \"big_data\" | \"cdn_security\" | \"config_management\" | \"connector_client\" | \"containers\" | \"crawler\" | \"credential_management\" | \"crm\" | \"custom_logs\" | \"database_security\" | \"datastore\" | \"dns_security\" | \"edr_xdr\" | \"cloudsecurity_cdr\" | \"elasticsearch_sdk\" | \"elastic_stack\" | \"email_security\" | \"firewall_security\" | \"google_cloud\" | \"iam\" | \"ids_ips\" | \"java_observability\" | \"kubernetes\" | \"language_client\" | \"languages\" | \"load_balancer\" | \"message_queue\" | \"native_search\" | \"network\" | \"network_security\" | \"notification\" | \"os_system\" | \"process_manager\" | \"productivity\" | \"productivity_security\" | \"proxy_security\" | \"sdk_search\" | \"stream_processing\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"virtualization\" | \"vpn_security\" | \"vulnerability_management\" | \"web\" | \"web_application_firewall\" | \"websphere\" | \"workplace_search_content_source\" | \"workplace_search\"" + "\"monitoring\" | \"security\" | \"connector\" | \"observability\" | \"custom\" | \"infrastructure\" | \"cloud\" | \"enterprise_search\" | \"advanced_analytics_ueba\" | \"analytics_engine\" | \"application_observability\" | \"app_search\" | \"auditd\" | \"authentication\" | \"aws\" | \"azure\" | \"big_data\" | \"cdn_security\" | \"config_management\" | \"connector_client\" | \"containers\" | \"crawler\" | \"credential_management\" | \"crm\" | \"custom_logs\" | \"database_security\" | \"datastore\" | \"dns_security\" | \"edr_xdr\" | \"cloudsecurity_cdr\" | \"elasticsearch_sdk\" | \"elastic_stack\" | \"email_security\" | \"firewall_security\" | \"google_cloud\" | \"iam\" | \"ids_ips\" | \"java_observability\" | \"kubernetes\" | \"language_client\" | \"languages\" | \"load_balancer\" | \"message_queue\" | \"native_search\" | \"network\" | \"network_security\" | \"notification\" | \"os_system\" | \"process_manager\" | \"productivity\" | \"productivity_security\" | \"proxy_security\" | \"sdk_search\" | \"stream_processing\" | \"support\" | \"threat_intel\" | \"ticketing\" | \"version_control\" | \"virtualization\" | \"vpn_security\" | \"vulnerability_management\" | \"web\" | \"web_application_firewall\" | \"websphere\" | \"workplace_search_content_source\" | \"workplace_search\"" ], "path": "x-pack/plugins/fleet/common/types/models/package_spec.ts", "deprecated": false, @@ -27768,7 +27762,7 @@ "label": "RegistrySearchResult", "description": [], "signature": [ - "{ type?: \"input\" | \"integration\" | undefined; version: string; name: string; title: string; description: string; path: string; download: string; internal?: boolean | undefined; icons?: (", + "{ type?: \"input\" | \"integration\" | undefined; version: string; name: string; title: string; description: string; path: string; internal?: boolean | undefined; download: string; icons?: (", { "pluginId": "fleet", "scope": "common", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 7a9eae15d00a4..319503b068c82 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: 2024-07-31 +date: 2024-08-09 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 | |-------------------|-----------|------------------------|-----------------| -| 1351 | 5 | 1229 | 73 | +| 1351 | 5 | 1229 | 74 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 48a860e5c9469..4747686e0c57d 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: 2024-07-31 +date: 2024-08-09 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 986b5a2a4b621..ffd302c8cc856 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 91b93ede14445..f7d1f75abebf1 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index cb784135c14fd..3e435e146860b 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: 2024-07-31 +date: 2024-08-09 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 5d77ced696775..fe5657db2892a 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 49f2066d86eb1..27c7391fd2d78 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/inference.devdocs.json b/api_docs/inference.devdocs.json new file mode 100644 index 0000000000000..642be486024c6 --- /dev/null +++ b/api_docs/inference.devdocs.json @@ -0,0 +1,318 @@ +{ + "id": "inference", + "client": { + "classes": [], + "functions": [ + { + "parentPluginId": "inference", + "id": "def-public.httpResponseIntoObservable", + "type": "Function", + "tags": [], + "label": "httpResponseIntoObservable", + "description": [], + "signature": [ + "() => ", + "OperatorFunction", + "<", + "StreamedHttpResponse", + ", T>" + ], + "path": "x-pack/plugins/inference/public/util/http_response_into_observable.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "inference", + "id": "def-public.InferencePublicSetup", + "type": "Interface", + "tags": [], + "label": "InferencePublicSetup", + "description": [], + "path": "x-pack/plugins/inference/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "inference", + "id": "def-public.InferencePublicStart", + "type": "Interface", + "tags": [], + "label": "InferencePublicStart", + "description": [], + "path": "x-pack/plugins/inference/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "inference", + "id": "def-public.InferencePublicStart.chatComplete", + "type": "Function", + "tags": [], + "label": "chatComplete", + "description": [], + "signature": [ + "(options: { connectorId: string; system?: string | undefined; messages: ", + "Message", + "[]; } & ", + "ToolOptions", + ") => ", + "ChatCompletionResponse", + "<", + "ToolOptions", + ">" + ], + "path": "x-pack/plugins/inference/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "inference", + "id": "def-public.InferencePublicStart.chatComplete.$1", + "type": "CompoundType", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ connectorId: string; system?: string | undefined; messages: ", + "Message", + "[]; } & TToolOptions" + ], + "path": "x-pack/plugins/inference/common/chat_complete/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "inference", + "id": "def-public.InferencePublicStart.output", + "type": "Function", + "tags": [], + "label": "output", + "description": [], + "signature": [ + "(id: TId, options: { connectorId: string; system?: string | undefined; input: string; schema?: TOutputSchema | undefined; }) => ", + "Observable", + "<", + "OutputEvent", + " : undefined>>" + ], + "path": "x-pack/plugins/inference/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "inference", + "id": "def-public.InferencePublicStart.output.$1", + "type": "Uncategorized", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "TId" + ], + "path": "x-pack/plugins/inference/common/output/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "inference", + "id": "def-public.InferencePublicStart.output.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "{ connectorId: string; system?: string | undefined; input: string; schema?: TOutputSchema | undefined; }" + ], + "path": "x-pack/plugins/inference/common/output/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "inference", + "id": "def-public.InferencePublicStart.getConnectors", + "type": "Function", + "tags": [], + "label": "getConnectors", + "description": [], + "signature": [ + "() => Promise<", + "InferenceConnector", + "[]>" + ], + "path": "x-pack/plugins/inference/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [ + { + "parentPluginId": "inference", + "id": "def-server.withoutChunkEvents", + "type": "Function", + "tags": [], + "label": "withoutChunkEvents", + "description": [], + "signature": [ + "() => ", + "OperatorFunction", + ">" + ], + "path": "x-pack/plugins/inference/common/chat_complete/without_chunk_events.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "inference", + "id": "def-server.withoutOutputUpdateEvents", + "type": "Function", + "tags": [], + "label": "withoutOutputUpdateEvents", + "description": [], + "signature": [ + "() => ", + "OperatorFunction", + ">>" + ], + "path": "x-pack/plugins/inference/common/output/without_output_update_events.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "inference", + "id": "def-server.withoutTokenCountEvents", + "type": "Function", + "tags": [], + "label": "withoutTokenCountEvents", + "description": [], + "signature": [ + "() => ", + "OperatorFunction", + ">" + ], + "path": "x-pack/plugins/inference/common/chat_complete/without_token_count_events.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "inference", + "id": "def-server.InferenceServerSetup", + "type": "Interface", + "tags": [], + "label": "InferenceServerSetup", + "description": [], + "path": "x-pack/plugins/inference/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "inference", + "id": "def-server.InferenceServerStart", + "type": "Interface", + "tags": [], + "label": "InferenceServerStart", + "description": [], + "path": "x-pack/plugins/inference/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "inference", + "id": "def-server.InferenceServerStart.getClient", + "type": "Function", + "tags": [], + "label": "getClient", + "description": [ + "\nCreates an inference client, scoped to a request.\n" + ], + "signature": [ + "(options: InferenceClientCreateOptions) => ", + "InferenceClient" + ], + "path": "x-pack/plugins/inference/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "inference", + "id": "def-server.InferenceServerStart.getClient.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "{@link InferenceClientCreateOptions }" + ], + "signature": [ + "InferenceClientCreateOptions" + ], + "path": "x-pack/plugins/inference/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/inference.mdx b/api_docs/inference.mdx new file mode 100644 index 0000000000000..5533a277640a2 --- /dev/null +++ b/api_docs/inference.mdx @@ -0,0 +1,47 @@ +--- +#### +#### 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: kibInferencePluginApi +slug: /kibana-dev-docs/api/inference +title: "inference" +image: https://source.unsplash.com/400x175/?github +description: API docs for the inference plugin +date: 2024-08-09 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inference'] +--- +import inferenceObj from './inference.devdocs.json'; + + + +Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 16 | 0 | 14 | 11 | + +## Client + +### Setup + + +### Start + + +### Functions + + +## Server + +### Setup + + +### Start + + +### Functions + + diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index dcd7eb400b2d3..8e8c2532078b1 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index b1de45de1896e..9029485c0c6f2 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 554d69150deaf..f74be9ae581f3 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx index 6698d38d4f188..ec8c9e146bfc5 100644 --- a/api_docs/integration_assistant.mdx +++ b/api_docs/integration_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/integrationAssistant title: "integrationAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the integrationAssistant plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'integrationAssistant'] --- import integrationAssistantObj from './integration_assistant.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 6f00a4d1f8006..e17e0e6bf5b23 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/investigate.devdocs.json b/api_docs/investigate.devdocs.json index 7048808862f2d..84d3ffbb38d41 100644 --- a/api_docs/investigate.devdocs.json +++ b/api_docs/investigate.devdocs.json @@ -145,7 +145,7 @@ "label": "getEsFilterFromGlobalParameters", "description": [], "signature": [ - "({\n filters,\n timeRange,\n}: Partial<", + "({ timeRange }: Partial<", "GlobalWidgetParameters", ">) => { bool: ", { @@ -166,7 +166,7 @@ "id": "def-public.getEsFilterFromGlobalParameters.$1", "type": "Object", "tags": [], - "label": "{\n filters,\n timeRange,\n}", + "label": "{ timeRange }", "description": [], "signature": [ "Partial<", @@ -487,27 +487,6 @@ "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.GlobalWidgetParameters.filters", - "type": "Array", - "tags": [], - "label": "filters", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -681,17 +660,6 @@ "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.InvestigateWidget.locked", - "type": "boolean", - "tags": [], - "label": "locked", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -749,27 +717,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "investigate", - "id": "def-public.Investigation.revisions", - "type": "Array", - "tags": [], - "label": "revisions", - "description": [], - "signature": [ - { - "pluginId": "investigate", - "scope": "common", - "docId": "kibInvestigatePluginApi", - "section": "def-common.InvestigationRevision", - "text": "InvestigationRevision" - }, - "[]" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "investigate", "id": "def-public.Investigation.title", @@ -783,43 +730,7 @@ }, { "parentPluginId": "investigate", - "id": "def-public.Investigation.revision", - "type": "string", - "tags": [], - "label": "revision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision", - "type": "Interface", - "tags": [], - "label": "InvestigationRevision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision.items", + "id": "def-public.Investigation.items", "type": "Array", "tags": [], "label": "items", @@ -840,7 +751,7 @@ }, { "parentPluginId": "investigate", - "id": "def-public.InvestigationRevision.parameters", + "id": "def-public.Investigation.parameters", "type": "Object", "tags": [], "label": "parameters", @@ -921,7 +832,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", @@ -932,136 +843,6 @@ "trackAdoption": false } ] - }, - { - "parentPluginId": "investigate", - "id": "def-public.WidgetRenderAPI.blocks", - "type": "Object", - "tags": [], - "label": "blocks", - "description": [], - "signature": [ - "{ publish: (blocks: ", - { - "pluginId": "investigate", - "scope": "common", - "docId": "kibInvestigatePluginApi", - "section": "def-common.WorkflowBlock", - "text": "WorkflowBlock" - }, - "[]) => UnregisterFunction; }" - ], - "path": "x-pack/plugins/observability_solution/investigate/public/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock", - "type": "Interface", - "tags": [], - "label": "WorkflowBlock", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.content", - "type": "string", - "tags": [], - "label": "content", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.description", - "type": "string", - "tags": [], - "label": "description", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.loading", - "type": "boolean", - "tags": [], - "label": "loading", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.onClick", - "type": "Function", - "tags": [], - "label": "onClick", - "description": [], - "signature": [ - "(() => void) | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.color", - "type": "CompoundType", - "tags": [], - "label": "color", - "description": [], - "signature": [ - "\"link\" | \"text\" | \"title\" | \"warning\" | \"disabled\" | \"success\" | \"body\" | \"highlight\" | \"primary\" | \"accent\" | \"danger\" | \"primaryText\" | \"accentText\" | \"successText\" | \"warningText\" | \"dangerText\" | \"emptyShade\" | \"lightestShade\" | \"lightShade\" | \"mediumShade\" | \"darkShade\" | \"darkestShade\" | \"fullShade\" | \"disabledText\" | \"shadow\" | \"subduedText\" | \"ghost\" | \"ink\" | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-public.WorkflowBlock.children", - "type": "CompoundType", - "tags": [], - "label": "children", - "description": [], - "signature": [ - "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -1125,7 +906,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", @@ -1175,7 +956,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", @@ -1824,17 +1605,6 @@ "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", "deprecated": false, "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.InvestigateWidget.locked", - "type": "boolean", - "tags": [], - "label": "locked", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false } ], "initialIsOpen": false @@ -1892,27 +1662,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "investigate", - "id": "def-common.Investigation.revisions", - "type": "Array", - "tags": [], - "label": "revisions", - "description": [], - "signature": [ - { - "pluginId": "investigate", - "scope": "common", - "docId": "kibInvestigatePluginApi", - "section": "def-common.InvestigationRevision", - "text": "InvestigationRevision" - }, - "[]" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "investigate", "id": "def-common.Investigation.title", @@ -1926,43 +1675,7 @@ }, { "parentPluginId": "investigate", - "id": "def-common.Investigation.revision", - "type": "string", - "tags": [], - "label": "revision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision", - "type": "Interface", - "tags": [], - "label": "InvestigationRevision", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision.items", + "id": "def-common.Investigation.items", "type": "Array", "tags": [], "label": "items", @@ -1983,7 +1696,7 @@ }, { "parentPluginId": "investigate", - "id": "def-common.InvestigationRevision.parameters", + "id": "def-common.Investigation.parameters", "type": "Object", "tags": [], "label": "parameters", @@ -1997,114 +1710,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock", - "type": "Interface", - "tags": [], - "label": "WorkflowBlock", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.id", - "type": "string", - "tags": [], - "label": "id", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.content", - "type": "string", - "tags": [], - "label": "content", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.description", - "type": "string", - "tags": [], - "label": "description", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.loading", - "type": "boolean", - "tags": [], - "label": "loading", - "description": [], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.onClick", - "type": "Function", - "tags": [], - "label": "onClick", - "description": [], - "signature": [ - "(() => void) | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.color", - "type": "CompoundType", - "tags": [], - "label": "color", - "description": [], - "signature": [ - "\"link\" | \"text\" | \"title\" | \"warning\" | \"disabled\" | \"success\" | \"body\" | \"highlight\" | \"primary\" | \"accent\" | \"danger\" | \"primaryText\" | \"accentText\" | \"successText\" | \"warningText\" | \"dangerText\" | \"emptyShade\" | \"lightestShade\" | \"lightShade\" | \"mediumShade\" | \"darkShade\" | \"darkestShade\" | \"fullShade\" | \"disabledText\" | \"shadow\" | \"subduedText\" | \"ghost\" | \"ink\" | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "investigate", - "id": "def-common.WorkflowBlock.children", - "type": "CompoundType", - "tags": [], - "label": "children", - "description": [], - "signature": [ - "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" - ], - "path": "x-pack/plugins/observability_solution/investigate/common/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [ @@ -2138,7 +1743,7 @@ "section": "def-common.InvestigateWidget", "text": "InvestigateWidget" }, - "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\" | \"locked\"> & { parameters: ", + "<{}, {}>, \"type\" | \"title\" | \"columns\" | \"description\" | \"rows\"> & { parameters: ", "_DeepPartialObject", "<", "GlobalWidgetParameters", diff --git a/api_docs/investigate.mdx b/api_docs/investigate.mdx index f322e678b5049..717c6f28cb803 100644 --- a/api_docs/investigate.mdx +++ b/api_docs/investigate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigate title: "investigate" image: https://source.unsplash.com/400x175/?github description: API docs for the investigate plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigate'] --- import investigateObj from './investigate.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 133 | 0 | 133 | 6 | +| 105 | 0 | 105 | 6 | ## Client diff --git a/api_docs/investigate_app.devdocs.json b/api_docs/investigate_app.devdocs.json index 7c5650360609d..d8b91a87450ee 100644 --- a/api_docs/investigate_app.devdocs.json +++ b/api_docs/investigate_app.devdocs.json @@ -50,7 +50,49 @@ "label": "InvestigateAppServerRouteRepository", "description": [], "signature": [ - "{}" + "{ \"GET /api/observability/investigations/{id} 2023-10-31\": { endpoint: \"GET /api/observability/investigations/{id} 2023-10-31\"; params?: ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + "InvestigateAppRouteHandlerResources", + " & { params: { path: { id: string; }; }; }) => Promise<{ id: string; title: string; createdAt: number; createdBy: string; parameters: { timeRange: { from: number; to: number; }; }; }>; } & ", + "InvestigateAppRouteCreateOptions", + "; \"GET /api/observability/investigations 2023-10-31\": { endpoint: \"GET /api/observability/investigations 2023-10-31\"; params?: ", + "PartialC", + "<{ query: ", + "PartialC", + "<{ page: ", + "StringC", + "; perPage: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + "InvestigateAppRouteHandlerResources", + " & { params?: { query?: { page?: string | undefined; perPage?: string | undefined; } | undefined; } | undefined; }) => Promise<{ page: number; perPage: number; total: number; results: { id: string; title: string; createdAt: number; createdBy: string; parameters: { timeRange: { from: number; to: number; }; }; }[]; }>; } & ", + "InvestigateAppRouteCreateOptions", + "; \"POST /api/observability/investigations 2023-10-31\": { endpoint: \"POST /api/observability/investigations 2023-10-31\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ id: ", + "StringC", + "; title: ", + "StringC", + "; parameters: ", + "TypeC", + "<{ timeRange: ", + "TypeC", + "<{ from: ", + "NumberC", + "; to: ", + "NumberC", + "; }>; }>; }>; }> | undefined; handler: ({}: ", + "InvestigateAppRouteHandlerResources", + " & { params: { body: { id: string; title: string; parameters: { timeRange: { from: number; to: number; }; }; }; }; }) => Promise<{ id: string; title: string; createdAt: number; createdBy: string; parameters: { timeRange: { from: number; to: number; }; }; }>; } & ", + "InvestigateAppRouteCreateOptions", + "; }" ], "path": "x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts", "deprecated": false, diff --git a/api_docs/investigate_app.mdx b/api_docs/investigate_app.mdx index 2be9577c4431d..283cc524dab53 100644 --- a/api_docs/investigate_app.mdx +++ b/api_docs/investigate_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigateApp title: "investigateApp" image: https://source.unsplash.com/400x175/?github description: API docs for the investigateApp plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigateApp'] --- import investigateAppObj from './investigate_app.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 5 | 0 | +| 5 | 0 | 5 | 2 | ## Client diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 875a1410f8007..5b09e6be47824 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index 75001dd0bfc0f..8ee9a22546086 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index be7ae48dadec7..75de3d4ecf3b4 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_pattern_analysis.mdx b/api_docs/kbn_aiops_log_pattern_analysis.mdx index 4214beeb13d6d..b9be6ee6094fb 100644 --- a/api_docs/kbn_aiops_log_pattern_analysis.mdx +++ b/api_docs/kbn_aiops_log_pattern_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-pattern-analysis title: "@kbn/aiops-log-pattern-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-pattern-analysis plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-pattern-analysis'] --- import kbnAiopsLogPatternAnalysisObj from './kbn_aiops_log_pattern_analysis.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_rate_analysis.mdx b/api_docs/kbn_aiops_log_rate_analysis.mdx index a2e78f7f3a667..e02b4abff3319 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.mdx +++ b/api_docs/kbn_aiops_log_rate_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-rate-analysis title: "@kbn/aiops-log-rate-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-rate-analysis plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-rate-analysis'] --- import kbnAiopsLogRateAnalysisObj from './kbn_aiops_log_rate_analysis.devdocs.json'; diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index b0431b6106dab..7765adb08b69c 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: 2024-07-31 +date: 2024-08-09 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_comparators.mdx b/api_docs/kbn_alerting_comparators.mdx index 9eb7c4a8fc904..b3b2c1e95d790 100644 --- a/api_docs/kbn_alerting_comparators.mdx +++ b/api_docs/kbn_alerting_comparators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-comparators title: "@kbn/alerting-comparators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-comparators plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-comparators'] --- import kbnAlertingComparatorsObj from './kbn_alerting_comparators.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 9ec4618474291..f9c6b696406c8 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index 2af04c72dead6..c2ce13f320024 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 7f6f5a6872583..31637fbc455df 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: 2024-07-31 +date: 2024-08-09 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_grouping.mdx b/api_docs/kbn_alerts_grouping.mdx index 1f8d85bb5fd3d..c27c6790f0d32 100644 --- a/api_docs/kbn_alerts_grouping.mdx +++ b/api_docs/kbn_alerts_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-grouping title: "@kbn/alerts-grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-grouping plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-grouping'] --- import kbnAlertsGroupingObj from './kbn_alerts_grouping.devdocs.json'; diff --git a/api_docs/kbn_alerts_ui_shared.devdocs.json b/api_docs/kbn_alerts_ui_shared.devdocs.json index b5c33068763e4..17e09808e3c68 100644 --- a/api_docs/kbn_alerts_ui_shared.devdocs.json +++ b/api_docs/kbn_alerts_ui_shared.devdocs.json @@ -2827,7 +2827,15 @@ "section": "def-common.DataViewLazy", "text": "DataViewLazy" }, - ">; createDataViewLazy: (spec: ", + ">; getDataViewLazyFromCache: (id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>; createDataViewLazy: (spec: ", { "pluginId": "dataViews", "scope": "common", @@ -4746,7 +4754,15 @@ "section": "def-common.DataViewLazy", "text": "DataViewLazy" }, - ">; createDataViewLazy: (spec: ", + ">; getDataViewLazyFromCache: (id: string) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | undefined>; createDataViewLazy: (spec: ", { "pluginId": "dataViews", "scope": "common", diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index 4e41640bd9112..ac998c5bb4316 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: 2024-07-31 +date: 2024-08-09 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 f8434010ccf48..dd5f22a14f394 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index f1ab4a419a622..06b57d303d835 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.devdocs.json b/api_docs/kbn_apm_config_loader.devdocs.json index 81ea00f3dac6d..3af68277a32ea 100644 --- a/api_docs/kbn_apm_config_loader.devdocs.json +++ b/api_docs/kbn_apm_config_loader.devdocs.json @@ -9,18 +9,10 @@ "objects": [] }, "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { "classes": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration", + "id": "def-server.ApmConfiguration", "type": "Class", "tags": [], "label": "ApmConfiguration", @@ -31,7 +23,7 @@ "children": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration.Unnamed", + "id": "def-server.ApmConfiguration.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -45,7 +37,7 @@ "children": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration.Unnamed.$1", + "id": "def-server.ApmConfiguration.Unnamed.$1", "type": "string", "tags": [], "label": "rootDir", @@ -60,7 +52,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration.Unnamed.$2", + "id": "def-server.ApmConfiguration.Unnamed.$2", "type": "Object", "tags": [], "label": "rawKibanaConfig", @@ -75,7 +67,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration.Unnamed.$3", + "id": "def-server.ApmConfiguration.Unnamed.$3", "type": "boolean", "tags": [], "label": "isDistributable", @@ -93,7 +85,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration.getConfig", + "id": "def-server.ApmConfiguration.getConfig", "type": "Function", "tags": [], "label": "getConfig", @@ -108,7 +100,7 @@ "children": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration.getConfig.$1", + "id": "def-server.ApmConfiguration.getConfig.$1", "type": "string", "tags": [], "label": "serviceName", @@ -126,7 +118,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.ApmConfiguration.isUsersRedactionEnabled", + "id": "def-server.ApmConfiguration.isUsersRedactionEnabled", "type": "Function", "tags": [], "label": "isUsersRedactionEnabled", @@ -147,7 +139,7 @@ "functions": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.getConfiguration", + "id": "def-server.getConfiguration", "type": "Function", "tags": [], "label": "getConfiguration", @@ -163,7 +155,7 @@ "children": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.getConfiguration.$1", + "id": "def-server.getConfiguration.$1", "type": "string", "tags": [], "label": "serviceName", @@ -182,7 +174,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.initApm", + "id": "def-server.initApm", "type": "Function", "tags": [], "label": "initApm", @@ -196,7 +188,7 @@ "children": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.initApm.$1", + "id": "def-server.initApm.$1", "type": "Array", "tags": [], "label": "argv", @@ -211,7 +203,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.initApm.$2", + "id": "def-server.initApm.$2", "type": "string", "tags": [], "label": "rootDir", @@ -226,7 +218,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.initApm.$3", + "id": "def-server.initApm.$3", "type": "boolean", "tags": [], "label": "isDistributable", @@ -241,7 +233,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.initApm.$4", + "id": "def-server.initApm.$4", "type": "string", "tags": [], "label": "serviceName", @@ -260,7 +252,7 @@ }, { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.shouldInstrumentClient", + "id": "def-server.shouldInstrumentClient", "type": "Function", "tags": [], "label": "shouldInstrumentClient", @@ -276,7 +268,7 @@ "children": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.shouldInstrumentClient.$1", + "id": "def-server.shouldInstrumentClient.$1", "type": "Object", "tags": [], "label": "config", @@ -301,7 +293,7 @@ "objects": [ { "parentPluginId": "@kbn/apm-config-loader", - "id": "def-common.apmConfigSchema", + "id": "def-server.apmConfigSchema", "type": "Object", "tags": [], "label": "apmConfigSchema", @@ -386,5 +378,13 @@ "initialIsOpen": false } ] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 3eda2f4efff3e..f4b71ba43faf9 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; @@ -23,14 +23,14 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core |-------------------|-----------|------------------------|-----------------| | 18 | 0 | 18 | 0 | -## Common +## Server ### Objects - + ### Functions - + ### Classes - + diff --git a/api_docs/kbn_apm_data_view.mdx b/api_docs/kbn_apm_data_view.mdx index 50459cbc37289..9f46de34ea606 100644 --- a/api_docs/kbn_apm_data_view.mdx +++ b/api_docs/kbn_apm_data_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-data-view title: "@kbn/apm-data-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-data-view plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-data-view'] --- import kbnApmDataViewObj from './kbn_apm_data_view.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 03ad7f700589d..26f74245bed6a 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: 2024-07-31 +date: 2024-08-09 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 12a255ab3a76b..d8f456992fd82 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: 2024-07-31 +date: 2024-08-09 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_types.devdocs.json b/api_docs/kbn_apm_types.devdocs.json new file mode 100644 index 0000000000000..dbc04e93b6e1d --- /dev/null +++ b/api_docs/kbn_apm_types.devdocs.json @@ -0,0 +1,5073 @@ +{ + "id": "@kbn/apm-types", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Agent", + "type": "Interface", + "tags": [], + "label": "Agent", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Agent.ephemeral_id", + "type": "string", + "tags": [], + "label": "ephemeral_id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Agent.name", + "type": "CompoundType", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "\"java\" | \"ruby\" | \"go\" | \"dotnet\" | \"php\" | \"otlp\" | \"android/java\" | \"iOS/swift\" | \"rum-js\" | \"js-base\" | \"nodejs\" | \"python\" | \"opentelemetry/cpp\" | \"opentelemetry/dotnet\" | \"opentelemetry/erlang\" | \"opentelemetry/go\" | \"opentelemetry/java\" | \"opentelemetry/nodejs\" | \"opentelemetry/php\" | \"opentelemetry/python\" | \"opentelemetry/ruby\" | \"opentelemetry/rust\" | \"opentelemetry/swift\" | \"opentelemetry/android\" | \"opentelemetry/webjs\"" + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Agent.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMBaseDoc", + "type": "Interface", + "tags": [], + "label": "APMBaseDoc", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMBaseDoc.timestamp", + "type": "string", + "tags": [], + "label": "'@timestamp'", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMBaseDoc.agent", + "type": "Object", + "tags": [], + "label": "agent", + "description": [], + "signature": [ + "{ name: string; version: string; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMBaseDoc.parent", + "type": "Object", + "tags": [], + "label": "parent", + "description": [], + "signature": [ + "{ id: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMBaseDoc.trace", + "type": "Object", + "tags": [], + "label": "trace", + "description": [], + "signature": [ + "{ id: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMBaseDoc.labels", + "type": "Object", + "tags": [], + "label": "labels", + "description": [], + "signature": [ + "{ [key: string]: string | number | boolean; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMBaseDoc.observer", + "type": "Object", + "tags": [], + "label": "observer", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Observer", + "text": "Observer" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMError", + "type": "Interface", + "tags": [], + "label": "APMError", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.APMError", + "text": "APMError" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.ErrorRaw", + "text": "ErrorRaw" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/apm_error.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APMError.agent", + "type": "Object", + "tags": [], + "label": "agent", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Agent", + "text": "Agent" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/apm_error.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud", + "type": "Interface", + "tags": [], + "label": "Cloud", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.availability_zone", + "type": "string", + "tags": [], + "label": "availability_zone", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.instance", + "type": "Object", + "tags": [], + "label": "instance", + "description": [], + "signature": [ + "{ name: string; id: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.machine", + "type": "Object", + "tags": [], + "label": "machine", + "description": [], + "signature": [ + "{ type: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.project", + "type": "Object", + "tags": [], + "label": "project", + "description": [], + "signature": [ + "{ id: string; name: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.provider", + "type": "string", + "tags": [], + "label": "provider", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.region", + "type": "string", + "tags": [], + "label": "region", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.account", + "type": "Object", + "tags": [], + "label": "account", + "description": [], + "signature": [ + "{ id: string; name: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.image", + "type": "Object", + "tags": [], + "label": "image", + "description": [], + "signature": [ + "{ id: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Cloud.service", + "type": "Object", + "tags": [], + "label": "service", + "description": [], + "signature": [ + "{ name: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Container", + "type": "Interface", + "tags": [], + "label": "Container", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Container.id", + "type": "CompoundType", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string | null | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Container.image", + "type": "CompoundType", + "tags": [], + "label": "image", + "description": [], + "signature": [ + "string | null | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw", + "type": "Interface", + "tags": [], + "label": "ErrorRaw", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.ErrorRaw", + "text": "ErrorRaw" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.APMBaseDoc", + "text": "APMBaseDoc" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.processor", + "type": "Object", + "tags": [], + "label": "processor", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Processor", + "text": "Processor" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.timestamp", + "type": "Object", + "tags": [], + "label": "timestamp", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.TimestampUs", + "text": "TimestampUs" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.transaction", + "type": "Object", + "tags": [], + "label": "transaction", + "description": [], + "signature": [ + "{ id: string; sampled?: boolean | undefined; type: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.error", + "type": "Object", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "{ id: string; culprit?: string | undefined; grouping_key: string; exception?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Exception", + "text": "Exception" + }, + "[] | undefined; page?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Page", + "text": "Page" + }, + " | undefined; log?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Log", + "text": "Log" + }, + " | undefined; stack_trace?: string | undefined; custom?: Record | undefined; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.container", + "type": "Object", + "tags": [], + "label": "container", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Container", + "text": "Container" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.host", + "type": "Object", + "tags": [], + "label": "host", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Host", + "text": "Host" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Http", + "text": "Http" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.kubernetes", + "type": "Object", + "tags": [], + "label": "kubernetes", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Kubernetes", + "text": "Kubernetes" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.process", + "type": "Object", + "tags": [], + "label": "process", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Process", + "text": "Process" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.service", + "type": "Object", + "tags": [], + "label": "service", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Service", + "text": "Service" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.url", + "type": "Object", + "tags": [], + "label": "url", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Url", + "text": "Url" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ErrorRaw.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.User", + "text": "User" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Event", + "type": "Interface", + "tags": [], + "label": "Event", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.EventRaw", + "text": "EventRaw" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/event.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Event.agent", + "type": "Object", + "tags": [], + "label": "agent", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Agent", + "text": "Agent" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/event.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EventRaw", + "type": "Interface", + "tags": [], + "label": "EventRaw", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.EventRaw", + "text": "EventRaw" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.APMBaseDoc", + "text": "APMBaseDoc" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/event_raw.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EventRaw.timestamp", + "type": "Object", + "tags": [], + "label": "timestamp", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.TimestampUs", + "text": "TimestampUs" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/event_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EventRaw.transaction", + "type": "Object", + "tags": [], + "label": "transaction", + "description": [], + "signature": [ + "{ id: string; sampled?: boolean | undefined; type: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/event_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EventRaw.log", + "type": "Object", + "tags": [], + "label": "log", + "description": [], + "signature": [ + "{ message?: string | undefined; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/event_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EventRaw.event", + "type": "Object", + "tags": [], + "label": "event", + "description": [], + "signature": [ + "{ action: string; category: string; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/event_raw.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception", + "type": "Interface", + "tags": [], + "label": "Exception", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception.attributes", + "type": "Object", + "tags": [], + "label": "attributes", + "description": [], + "signature": [ + "{ response?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception.code", + "type": "string", + "tags": [], + "label": "code", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception.message", + "type": "string", + "tags": [], + "label": "message", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception.module", + "type": "string", + "tags": [], + "label": "module", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception.handled", + "type": "CompoundType", + "tags": [], + "label": "handled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Exception.stacktrace", + "type": "Array", + "tags": [], + "label": "stacktrace", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Stackframe", + "text": "Stackframe" + }, + "[] | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Faas", + "type": "Interface", + "tags": [], + "label": "Faas", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/faas.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Faas.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/faas.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Faas.coldstart", + "type": "CompoundType", + "tags": [], + "label": "coldstart", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/faas.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Faas.execution", + "type": "string", + "tags": [], + "label": "execution", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/faas.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Faas.trigger", + "type": "Object", + "tags": [], + "label": "trigger", + "description": [], + "signature": [ + "{ type?: string | undefined; request_id?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/faas.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Host", + "type": "Interface", + "tags": [], + "label": "Host", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Host.architecture", + "type": "string", + "tags": [], + "label": "architecture", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Host.hostname", + "type": "string", + "tags": [], + "label": "hostname", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Host.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Host.ip", + "type": "string", + "tags": [], + "label": "ip", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Host.os", + "type": "Object", + "tags": [], + "label": "os", + "description": [], + "signature": [ + "{ platform?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Http", + "type": "Interface", + "tags": [], + "label": "Http", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Http.request", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + "{ [key: string]: unknown; method: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Http.response", + "type": "Object", + "tags": [], + "label": "response", + "description": [], + "signature": [ + "{ [key: string]: unknown; status_code: number; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Http.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Kubernetes", + "type": "Interface", + "tags": [], + "label": "Kubernetes", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Kubernetes.pod", + "type": "Object", + "tags": [], + "label": "pod", + "description": [], + "signature": [ + "{ [key: string]: unknown; uid?: string | null | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Kubernetes.namespace", + "type": "string", + "tags": [], + "label": "namespace", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Kubernetes.replicaset", + "type": "Object", + "tags": [], + "label": "replicaset", + "description": [], + "signature": [ + "{ name?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Kubernetes.deployment", + "type": "Object", + "tags": [], + "label": "deployment", + "description": [], + "signature": [ + "{ name?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Kubernetes.container", + "type": "Object", + "tags": [], + "label": "container", + "description": [], + "signature": [ + "{ id?: string | undefined; name?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Log", + "type": "Interface", + "tags": [], + "label": "Log", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Log.message", + "type": "string", + "tags": [], + "label": "message", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Log.stacktrace", + "type": "Array", + "tags": [], + "label": "stacktrace", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Stackframe", + "text": "Stackframe" + }, + "[] | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer", + "type": "Interface", + "tags": [], + "label": "Observer", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer.ephemeral_id", + "type": "string", + "tags": [], + "label": "ephemeral_id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer.hostname", + "type": "string", + "tags": [], + "label": "hostname", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Observer.version_major", + "type": "number", + "tags": [], + "label": "version_major", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Page", + "type": "Interface", + "tags": [], + "label": "Page", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Page.url", + "type": "string", + "tags": [], + "label": "url", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Process", + "type": "Interface", + "tags": [], + "label": "Process", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/process.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Process.args", + "type": "Array", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/process.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Process.pid", + "type": "number", + "tags": [], + "label": "pid", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/process.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Process.ppid", + "type": "number", + "tags": [], + "label": "ppid", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/process.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Process.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/process.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Processor", + "type": "Interface", + "tags": [], + "label": "Processor", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Processor.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "\"error\"" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Processor.event", + "type": "string", + "tags": [], + "label": "event", + "description": [], + "signature": [ + "\"error\"" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service", + "type": "Interface", + "tags": [], + "label": "Service", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service.environment", + "type": "string", + "tags": [], + "label": "environment", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service.framework", + "type": "Object", + "tags": [], + "label": "framework", + "description": [], + "signature": [ + "{ name: string; version?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service.node", + "type": "Object", + "tags": [], + "label": "node", + "description": [], + "signature": [ + "{ name?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service.runtime", + "type": "Object", + "tags": [], + "label": "runtime", + "description": [], + "signature": [ + "{ name: string; version: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service.language", + "type": "Object", + "tags": [], + "label": "language", + "description": [], + "signature": [ + "{ name: string; version?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Service.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Span", + "type": "Interface", + "tags": [], + "label": "Span", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Span", + "text": "Span" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.SpanRaw", + "text": "SpanRaw" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/span.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Span.agent", + "type": "Object", + "tags": [], + "label": "agent", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Agent", + "text": "Agent" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/span.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanLink", + "type": "Interface", + "tags": [], + "label": "SpanLink", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/span_links.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanLink.trace", + "type": "Object", + "tags": [], + "label": "trace", + "description": [], + "signature": [ + "{ id: string; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/span_links.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanLink.span", + "type": "Object", + "tags": [], + "label": "span", + "description": [], + "signature": [ + "{ id: string; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/span_links.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw", + "type": "Interface", + "tags": [], + "label": "SpanRaw", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.SpanRaw", + "text": "SpanRaw" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.APMBaseDoc", + "text": "APMBaseDoc" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.processor", + "type": "Object", + "tags": [], + "label": "processor", + "description": [], + "signature": [ + "Processor" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.trace", + "type": "Object", + "tags": [], + "label": "trace", + "description": [], + "signature": [ + "{ id: string; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.event", + "type": "Object", + "tags": [], + "label": "event", + "description": [], + "signature": [ + "{ outcome?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.EventOutcome", + "text": "EventOutcome" + }, + " | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.service", + "type": "Object", + "tags": [], + "label": "service", + "description": [], + "signature": [ + "{ name: string; environment?: string | undefined; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.span", + "type": "Object", + "tags": [], + "label": "span", + "description": [], + "signature": [ + "{ destination?: { service: { resource: string; }; } | undefined; action?: string | undefined; duration: { us: number; }; id: string; name: string; stacktrace?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Stackframe", + "text": "Stackframe" + }, + "[] | undefined; subtype?: string | undefined; sync?: boolean | undefined; type: string; http?: { url?: { original?: string | undefined; } | undefined; response: { status_code: number; }; method?: string | undefined; } | undefined; db?: { statement?: string | undefined; type?: string | undefined; } | undefined; message?: { queue?: { name: string; } | undefined; age?: { ms: number; } | undefined; body?: string | undefined; headers?: Record | undefined; } | undefined; composite?: { count: number; sum: { us: number; }; compression_strategy: string; } | undefined; links?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.SpanLink", + "text": "SpanLink" + }, + "[] | undefined; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.timestamp", + "type": "Object", + "tags": [], + "label": "timestamp", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.TimestampUs", + "text": "TimestampUs" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.transaction", + "type": "Object", + "tags": [], + "label": "transaction", + "description": [], + "signature": [ + "{ id: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.child", + "type": "Object", + "tags": [], + "label": "child", + "description": [], + "signature": [ + "{ id: string[]; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.code", + "type": "Object", + "tags": [], + "label": "code", + "description": [], + "signature": [ + "{ stacktrace?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Http", + "text": "Http" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanRaw.url", + "type": "Object", + "tags": [], + "label": "url", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Url", + "text": "Url" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TimestampUs", + "type": "Interface", + "tags": [], + "label": "TimestampUs", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/timestamp_us.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TimestampUs.us", + "type": "number", + "tags": [], + "label": "us", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/timestamp_us.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Transaction", + "type": "Interface", + "tags": [], + "label": "Transaction", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Transaction", + "text": "Transaction" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.TransactionRaw", + "text": "TransactionRaw" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/transaction.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Transaction.agent", + "type": "Object", + "tags": [], + "label": "agent", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Agent", + "text": "Agent" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/transaction.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Transaction.transaction", + "type": "Object", + "tags": [], + "label": "transaction", + "description": [], + "signature": [ + "InnerTransactionWithName" + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/transaction.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw", + "type": "Interface", + "tags": [], + "label": "TransactionRaw", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.TransactionRaw", + "text": "TransactionRaw" + }, + " extends ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.APMBaseDoc", + "text": "APMBaseDoc" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.processor", + "type": "Object", + "tags": [], + "label": "processor", + "description": [], + "signature": [ + "Processor" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.timestamp", + "type": "Object", + "tags": [], + "label": "timestamp", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.TimestampUs", + "text": "TimestampUs" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.trace", + "type": "Object", + "tags": [], + "label": "trace", + "description": [], + "signature": [ + "{ id: string; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.event", + "type": "Object", + "tags": [], + "label": "event", + "description": [], + "signature": [ + "{ outcome?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.EventOutcome", + "text": "EventOutcome" + }, + " | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.transaction", + "type": "Object", + "tags": [], + "label": "transaction", + "description": [], + "signature": [ + "{ duration: { us: number; }; id: string; marks?: { agent?: { [name: string]: number; } | undefined; } | undefined; name?: string | undefined; page?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Page", + "text": "Page" + }, + " | undefined; result?: string | undefined; sampled: boolean; span_count?: { started?: number | undefined; dropped?: number | undefined; } | undefined; type: string; custom?: Record | undefined; message?: { queue?: { name: string; } | undefined; age?: { ms: number; } | undefined; body?: string | undefined; headers?: Record | undefined; } | undefined; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.container", + "type": "Object", + "tags": [], + "label": "container", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Container", + "text": "Container" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.ecs", + "type": "Object", + "tags": [], + "label": "ecs", + "description": [], + "signature": [ + "{ version?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.host", + "type": "Object", + "tags": [], + "label": "host", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Host", + "text": "Host" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Http", + "text": "Http" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.kubernetes", + "type": "Object", + "tags": [], + "label": "kubernetes", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Kubernetes", + "text": "Kubernetes" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.process", + "type": "Object", + "tags": [], + "label": "process", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Process", + "text": "Process" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.service", + "type": "Object", + "tags": [], + "label": "service", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Service", + "text": "Service" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.url", + "type": "Object", + "tags": [], + "label": "url", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Url", + "text": "Url" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.User", + "text": "User" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.user_agent", + "type": "Object", + "tags": [], + "label": "user_agent", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.UserAgent", + "text": "UserAgent" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.cloud", + "type": "Object", + "tags": [], + "label": "cloud", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Cloud", + "text": "Cloud" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.faas", + "type": "Object", + "tags": [], + "label": "faas", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Faas", + "text": "Faas" + }, + " | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TransactionRaw.span", + "type": "Object", + "tags": [], + "label": "span", + "description": [], + "signature": [ + "{ links?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.SpanLink", + "text": "SpanLink" + }, + "[] | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Url", + "type": "Interface", + "tags": [], + "label": "Url", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Url.domain", + "type": "string", + "tags": [], + "label": "domain", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Url.full", + "type": "string", + "tags": [], + "label": "full", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Url.original", + "type": "string", + "tags": [], + "label": "original", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.User", + "type": "Interface", + "tags": [], + "label": "User", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.User.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.UserAgent", + "type": "Interface", + "tags": [], + "label": "UserAgent", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.UserAgent.device", + "type": "Object", + "tags": [], + "label": "device", + "description": [], + "signature": [ + "{ name: string; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.UserAgent.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.UserAgent.original", + "type": "string", + "tags": [], + "label": "original", + "description": [], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.UserAgent.os", + "type": "Object", + "tags": [], + "label": "os", + "description": [], + "signature": [ + "{ name: string; version?: string | undefined; full?: string | undefined; } | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.UserAgent.version", + "type": "string", + "tags": [], + "label": "version", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.AGENT", + "type": "string", + "tags": [], + "label": "AGENT", + "description": [], + "signature": [ + "\"agent\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.AGENT_ACTIVATION_METHOD", + "type": "string", + "tags": [], + "label": "AGENT_ACTIVATION_METHOD", + "description": [], + "signature": [ + "\"agent.activation_method\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.AGENT_NAME", + "type": "string", + "tags": [], + "label": "AGENT_NAME", + "description": [], + "signature": [ + "\"agent.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.AGENT_VERSION", + "type": "string", + "tags": [], + "label": "AGENT_VERSION", + "description": [], + "signature": [ + "\"agent.version\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.AgentName", + "type": "Type", + "tags": [], + "label": "AgentName", + "description": [], + "signature": [ + "\"java\" | \"ruby\" | \"go\" | \"dotnet\" | \"php\" | \"otlp\" | \"android/java\" | \"iOS/swift\" | \"rum-js\" | \"js-base\" | \"nodejs\" | \"python\" | \"opentelemetry/cpp\" | \"opentelemetry/dotnet\" | \"opentelemetry/erlang\" | \"opentelemetry/go\" | \"opentelemetry/java\" | \"opentelemetry/nodejs\" | \"opentelemetry/php\" | \"opentelemetry/python\" | \"opentelemetry/ruby\" | \"opentelemetry/rust\" | \"opentelemetry/swift\" | \"opentelemetry/android\" | \"opentelemetry/webjs\"" + ], + "path": "packages/kbn-elastic-agent-utils/src/agent_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.APP_LAUNCH_TIME", + "type": "string", + "tags": [], + "label": "APP_LAUNCH_TIME", + "description": [], + "signature": [ + "\"application.launch.time\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CHILD_ID", + "type": "string", + "tags": [], + "label": "CHILD_ID", + "description": [], + "signature": [ + "\"child.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLIENT_GEO_CITY_NAME", + "type": "string", + "tags": [], + "label": "CLIENT_GEO_CITY_NAME", + "description": [], + "signature": [ + "\"client.geo.city_name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLIENT_GEO_COUNTRY_ISO_CODE", + "type": "string", + "tags": [], + "label": "CLIENT_GEO_COUNTRY_ISO_CODE", + "description": [], + "signature": [ + "\"client.geo.country_iso_code\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLIENT_GEO_COUNTRY_NAME", + "type": "string", + "tags": [], + "label": "CLIENT_GEO_COUNTRY_NAME", + "description": [], + "signature": [ + "\"client.geo.country_name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLIENT_GEO_REGION_ISO_CODE", + "type": "string", + "tags": [], + "label": "CLIENT_GEO_REGION_ISO_CODE", + "description": [], + "signature": [ + "\"client.geo.region_iso_code\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLIENT_GEO_REGION_NAME", + "type": "string", + "tags": [], + "label": "CLIENT_GEO_REGION_NAME", + "description": [], + "signature": [ + "\"client.geo.region_name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD", + "type": "string", + "tags": [], + "label": "CLOUD", + "description": [], + "signature": [ + "\"cloud\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_ACCOUNT_ID", + "type": "string", + "tags": [], + "label": "CLOUD_ACCOUNT_ID", + "description": [], + "signature": [ + "\"cloud.account.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_AVAILABILITY_ZONE", + "type": "string", + "tags": [], + "label": "CLOUD_AVAILABILITY_ZONE", + "description": [], + "signature": [ + "\"cloud.availability_zone\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_INSTANCE_ID", + "type": "string", + "tags": [], + "label": "CLOUD_INSTANCE_ID", + "description": [], + "signature": [ + "\"cloud.instance.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_INSTANCE_NAME", + "type": "string", + "tags": [], + "label": "CLOUD_INSTANCE_NAME", + "description": [], + "signature": [ + "\"cloud.instance.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_MACHINE_TYPE", + "type": "string", + "tags": [], + "label": "CLOUD_MACHINE_TYPE", + "description": [], + "signature": [ + "\"cloud.machine.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_PROVIDER", + "type": "string", + "tags": [], + "label": "CLOUD_PROVIDER", + "description": [], + "signature": [ + "\"cloud.provider\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_REGION", + "type": "string", + "tags": [], + "label": "CLOUD_REGION", + "description": [], + "signature": [ + "\"cloud.region\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CLOUD_SERVICE_NAME", + "type": "string", + "tags": [], + "label": "CLOUD_SERVICE_NAME", + "description": [], + "signature": [ + "\"cloud.service.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CONTAINER", + "type": "string", + "tags": [], + "label": "CONTAINER", + "description": [], + "signature": [ + "\"container\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CONTAINER_ID", + "type": "string", + "tags": [], + "label": "CONTAINER_ID", + "description": [], + "signature": [ + "\"container.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.CONTAINER_IMAGE", + "type": "string", + "tags": [], + "label": "CONTAINER_IMAGE", + "description": [], + "signature": [ + "\"container.image.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.DATA_STEAM_TYPE", + "type": "string", + "tags": [], + "label": "DATA_STEAM_TYPE", + "description": [], + "signature": [ + "\"data_stream.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.DESTINATION_ADDRESS", + "type": "string", + "tags": [], + "label": "DESTINATION_ADDRESS", + "description": [], + "signature": [ + "\"destination.address\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.DEVICE_MODEL_IDENTIFIER", + "type": "string", + "tags": [], + "label": "DEVICE_MODEL_IDENTIFIER", + "description": [], + "signature": [ + "\"device.model.identifier\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ElasticAgentName", + "type": "Type", + "tags": [], + "label": "ElasticAgentName", + "description": [ + "\nWe cannot mark these arrays as const and derive their type\nbecause we need to be able to assign them as mutable entities for ES queries." + ], + "signature": [ + "\"java\" | \"ruby\" | \"go\" | \"dotnet\" | \"php\" | \"android/java\" | \"iOS/swift\" | \"rum-js\" | \"js-base\" | \"nodejs\" | \"python\"" + ], + "path": "packages/kbn-elastic-agent-utils/src/agent_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_CULPRIT", + "type": "string", + "tags": [], + "label": "ERROR_CULPRIT", + "description": [], + "signature": [ + "\"error.culprit\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_EXC_HANDLED", + "type": "string", + "tags": [], + "label": "ERROR_EXC_HANDLED", + "description": [], + "signature": [ + "\"error.exception.handled\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_EXC_MESSAGE", + "type": "string", + "tags": [], + "label": "ERROR_EXC_MESSAGE", + "description": [], + "signature": [ + "\"error.exception.message\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_EXC_TYPE", + "type": "string", + "tags": [], + "label": "ERROR_EXC_TYPE", + "description": [], + "signature": [ + "\"error.exception.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_EXCEPTION", + "type": "string", + "tags": [], + "label": "ERROR_EXCEPTION", + "description": [], + "signature": [ + "\"error.exception\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_GROUP_ID", + "type": "string", + "tags": [], + "label": "ERROR_GROUP_ID", + "description": [], + "signature": [ + "\"error.grouping_key\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_GROUP_NAME", + "type": "string", + "tags": [], + "label": "ERROR_GROUP_NAME", + "description": [], + "signature": [ + "\"error.grouping_name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_ID", + "type": "string", + "tags": [], + "label": "ERROR_ID", + "description": [], + "signature": [ + "\"error.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_LOG_LEVEL", + "type": "string", + "tags": [], + "label": "ERROR_LOG_LEVEL", + "description": [], + "signature": [ + "\"error.log.level\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_LOG_MESSAGE", + "type": "string", + "tags": [], + "label": "ERROR_LOG_MESSAGE", + "description": [], + "signature": [ + "\"error.log.message\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_PAGE_URL", + "type": "string", + "tags": [], + "label": "ERROR_PAGE_URL", + "description": [], + "signature": [ + "\"error.page.url\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.ERROR_TYPE", + "type": "string", + "tags": [], + "label": "ERROR_TYPE", + "description": [], + "signature": [ + "\"error.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EVENT_NAME", + "type": "string", + "tags": [], + "label": "EVENT_NAME", + "description": [], + "signature": [ + "\"event.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EVENT_OUTCOME", + "type": "string", + "tags": [], + "label": "EVENT_OUTCOME", + "description": [], + "signature": [ + "\"event.outcome\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EVENT_SUCCESS_COUNT", + "type": "string", + "tags": [], + "label": "EVENT_SUCCESS_COUNT", + "description": [], + "signature": [ + "\"event.success_count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.EventOutcome", + "type": "Type", + "tags": [], + "label": "EventOutcome", + "description": [], + "signature": [ + "\"unknown\" | \"success\" | \"failure\"" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/event_outcome.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.FAAS_BILLED_DURATION", + "type": "string", + "tags": [], + "label": "FAAS_BILLED_DURATION", + "description": [], + "signature": [ + "\"faas.billed_duration\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.FAAS_COLDSTART", + "type": "string", + "tags": [], + "label": "FAAS_COLDSTART", + "description": [], + "signature": [ + "\"faas.coldstart\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.FAAS_COLDSTART_DURATION", + "type": "string", + "tags": [], + "label": "FAAS_COLDSTART_DURATION", + "description": [], + "signature": [ + "\"faas.coldstart_duration\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.FAAS_DURATION", + "type": "string", + "tags": [], + "label": "FAAS_DURATION", + "description": [], + "signature": [ + "\"faas.duration\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.FAAS_ID", + "type": "string", + "tags": [], + "label": "FAAS_ID", + "description": [], + "signature": [ + "\"faas.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.FAAS_NAME", + "type": "string", + "tags": [], + "label": "FAAS_NAME", + "description": [], + "signature": [ + "\"faas.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.FAAS_TRIGGER_TYPE", + "type": "string", + "tags": [], + "label": "FAAS_TRIGGER_TYPE", + "description": [], + "signature": [ + "\"faas.trigger.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HOST", + "type": "string", + "tags": [], + "label": "HOST", + "description": [], + "signature": [ + "\"host\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HOST_ARCHITECTURE", + "type": "string", + "tags": [], + "label": "HOST_ARCHITECTURE", + "description": [], + "signature": [ + "\"host.architecture\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HOST_HOSTNAME", + "type": "string", + "tags": [], + "label": "HOST_HOSTNAME", + "description": [], + "signature": [ + "\"host.hostname\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HOST_NAME", + "type": "string", + "tags": [], + "label": "HOST_NAME", + "description": [], + "signature": [ + "\"host.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HOST_OS_PLATFORM", + "type": "string", + "tags": [], + "label": "HOST_OS_PLATFORM", + "description": [], + "signature": [ + "\"host.os.platform\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HOST_OS_VERSION", + "type": "string", + "tags": [], + "label": "HOST_OS_VERSION", + "description": [], + "signature": [ + "\"host.os.version\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HTTP_REQUEST_METHOD", + "type": "string", + "tags": [], + "label": "HTTP_REQUEST_METHOD", + "description": [], + "signature": [ + "\"http.request.method\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.HTTP_RESPONSE_STATUS_CODE", + "type": "string", + "tags": [], + "label": "HTTP_RESPONSE_STATUS_CODE", + "description": [], + "signature": [ + "\"http.response.status_code\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.INDEX", + "type": "string", + "tags": [], + "label": "INDEX", + "description": [], + "signature": [ + "\"_index\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.KUBERNETES", + "type": "string", + "tags": [], + "label": "KUBERNETES", + "description": [], + "signature": [ + "\"kubernetes\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.KUBERNETES_POD_NAME", + "type": "string", + "tags": [], + "label": "KUBERNETES_POD_NAME", + "description": [], + "signature": [ + "\"kubernetes.pod.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.KUBERNETES_POD_UID", + "type": "string", + "tags": [], + "label": "KUBERNETES_POD_UID", + "description": [], + "signature": [ + "\"kubernetes.pod.uid\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.LABEL_GC", + "type": "string", + "tags": [], + "label": "LABEL_GC", + "description": [], + "signature": [ + "\"labels.gc\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.LABEL_LIFECYCLE_STATE", + "type": "string", + "tags": [], + "label": "LABEL_LIFECYCLE_STATE", + "description": [], + "signature": [ + "\"labels.lifecycle_state\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.LABEL_NAME", + "type": "string", + "tags": [], + "label": "LABEL_NAME", + "description": [], + "signature": [ + "\"labels.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.LABEL_TELEMETRY_AUTO_VERSION", + "type": "string", + "tags": [], + "label": "LABEL_TELEMETRY_AUTO_VERSION", + "description": [], + "signature": [ + "\"labels.telemetry_auto_version\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.LABEL_TYPE", + "type": "string", + "tags": [], + "label": "LABEL_TYPE", + "description": [], + "signature": [ + "\"labels.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Metric", + "type": "Type", + "tags": [], + "label": "Metric", + "description": [], + "signature": [ + "BaseMetric | TransactionBreakdownMetric | SpanBreakdownMetric | TransactionDurationMetric | ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.SpanDestinationMetric", + "text": "SpanDestinationMetric" + }, + " | SystemMetric | JVMMetric" + ], + "path": "packages/kbn-apm-types/src/es_schemas/ui/metric.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_CGROUP_MEMORY_LIMIT_BYTES", + "type": "string", + "tags": [], + "label": "METRIC_CGROUP_MEMORY_LIMIT_BYTES", + "description": [], + "signature": [ + "\"system.process.cgroup.memory.mem.limit.bytes\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_CGROUP_MEMORY_USAGE_BYTES", + "type": "string", + "tags": [], + "label": "METRIC_CGROUP_MEMORY_USAGE_BYTES", + "description": [], + "signature": [ + "\"system.process.cgroup.memory.mem.usage.bytes\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_GC_COUNT", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_GC_COUNT", + "description": [], + "signature": [ + "\"jvm.gc.count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_GC_TIME", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_GC_TIME", + "description": [], + "signature": [ + "\"jvm.gc.time\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_HEAP_MEMORY_COMMITTED", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_HEAP_MEMORY_COMMITTED", + "description": [], + "signature": [ + "\"jvm.memory.heap.committed\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_HEAP_MEMORY_MAX", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_HEAP_MEMORY_MAX", + "description": [], + "signature": [ + "\"jvm.memory.heap.max\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_HEAP_MEMORY_USED", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_HEAP_MEMORY_USED", + "description": [], + "signature": [ + "\"jvm.memory.heap.used\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED", + "description": [], + "signature": [ + "\"jvm.memory.non_heap.committed\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_NON_HEAP_MEMORY_MAX", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_NON_HEAP_MEMORY_MAX", + "description": [], + "signature": [ + "\"jvm.memory.non_heap.max\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_NON_HEAP_MEMORY_USED", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_NON_HEAP_MEMORY_USED", + "description": [], + "signature": [ + "\"jvm.memory.non_heap.used\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_JAVA_THREAD_COUNT", + "type": "string", + "tags": [], + "label": "METRIC_JAVA_THREAD_COUNT", + "description": [], + "signature": [ + "\"jvm.thread.count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_JVM_GC_DURATION", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_JVM_GC_DURATION", + "description": [], + "signature": [ + "\"process.runtime.jvm.gc.duration\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_JVM_PROCESS_CPU_PERCENT", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_JVM_PROCESS_CPU_PERCENT", + "description": [], + "signature": [ + "\"process.runtime.jvm.cpu.utilization\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_JVM_PROCESS_MEMORY_COMMITTED", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_JVM_PROCESS_MEMORY_COMMITTED", + "description": [], + "signature": [ + "\"process.runtime.jvm.memory.committed\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_JVM_PROCESS_MEMORY_LIMIT", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_JVM_PROCESS_MEMORY_LIMIT", + "description": [], + "signature": [ + "\"process.runtime.jvm.memory.limit\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_JVM_PROCESS_MEMORY_USAGE", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_JVM_PROCESS_MEMORY_USAGE", + "description": [], + "signature": [ + "\"process.runtime.jvm.memory.usage\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_JVM_PROCESS_THREADS_COUNT", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_JVM_PROCESS_THREADS_COUNT", + "description": [], + "signature": [ + "\"process.runtime.jvm.threads.count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_JVM_SYSTEM_CPU_PERCENT", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_JVM_SYSTEM_CPU_PERCENT", + "description": [], + "signature": [ + "\"process.runtime.jvm.system.cpu.utilization\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_SYSTEM_CPU_UTILIZATION", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_SYSTEM_CPU_UTILIZATION", + "description": [], + "signature": [ + "\"system.cpu.utilization\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_OTEL_SYSTEM_MEMORY_UTILIZATION", + "type": "string", + "tags": [], + "label": "METRIC_OTEL_SYSTEM_MEMORY_UTILIZATION", + "description": [], + "signature": [ + "\"system.memory.utilization\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_PROCESS_CPU_PERCENT", + "type": "string", + "tags": [], + "label": "METRIC_PROCESS_CPU_PERCENT", + "description": [], + "signature": [ + "\"system.process.cpu.total.norm.pct\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_SYSTEM_CPU_PERCENT", + "type": "string", + "tags": [], + "label": "METRIC_SYSTEM_CPU_PERCENT", + "description": [], + "signature": [ + "\"system.cpu.total.norm.pct\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_SYSTEM_FREE_MEMORY", + "type": "string", + "tags": [], + "label": "METRIC_SYSTEM_FREE_MEMORY", + "description": [], + "signature": [ + "\"system.memory.actual.free\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRIC_SYSTEM_TOTAL_MEMORY", + "type": "string", + "tags": [], + "label": "METRIC_SYSTEM_TOTAL_MEMORY", + "description": [], + "signature": [ + "\"system.memory.total\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.MetricRaw", + "type": "Type", + "tags": [], + "label": "MetricRaw", + "description": [], + "signature": [ + "BaseMetric | TransactionBreakdownMetric | SpanBreakdownMetric | TransactionDurationMetric | ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.SpanDestinationMetric", + "text": "SpanDestinationMetric" + }, + " | SystemMetric | JVMMetric" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/metric_raw.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRICSET_INTERVAL", + "type": "string", + "tags": [], + "label": "METRICSET_INTERVAL", + "description": [], + "signature": [ + "\"metricset.interval\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.METRICSET_NAME", + "type": "string", + "tags": [], + "label": "METRICSET_NAME", + "description": [], + "signature": [ + "\"metricset.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.NETWORK_CONNECTION_TYPE", + "type": "string", + "tags": [], + "label": "NETWORK_CONNECTION_TYPE", + "description": [], + "signature": [ + "\"network.connection.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.OBSERVER_HOSTNAME", + "type": "string", + "tags": [], + "label": "OBSERVER_HOSTNAME", + "description": [], + "signature": [ + "\"observer.hostname\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.OBSERVER_LISTENING", + "type": "string", + "tags": [], + "label": "OBSERVER_LISTENING", + "description": [], + "signature": [ + "\"observer.listening\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.OpenTelemetryAgentName", + "type": "Type", + "tags": [], + "label": "OpenTelemetryAgentName", + "description": [], + "signature": [ + "\"otlp\" | \"opentelemetry/cpp\" | \"opentelemetry/dotnet\" | \"opentelemetry/erlang\" | \"opentelemetry/go\" | \"opentelemetry/java\" | \"opentelemetry/nodejs\" | \"opentelemetry/php\" | \"opentelemetry/python\" | \"opentelemetry/ruby\" | \"opentelemetry/rust\" | \"opentelemetry/swift\" | \"opentelemetry/android\" | \"opentelemetry/webjs\"" + ], + "path": "packages/kbn-elastic-agent-utils/src/agent_names.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.PARENT_ID", + "type": "string", + "tags": [], + "label": "PARENT_ID", + "description": [], + "signature": [ + "\"parent.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.PROCESSOR_EVENT", + "type": "string", + "tags": [], + "label": "PROCESSOR_EVENT", + "description": [], + "signature": [ + "\"processor.event\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE", + "type": "string", + "tags": [], + "label": "SERVICE", + "description": [], + "signature": [ + "\"service\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_ENVIRONMENT", + "type": "string", + "tags": [], + "label": "SERVICE_ENVIRONMENT", + "description": [], + "signature": [ + "\"service.environment\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_FRAMEWORK_NAME", + "type": "string", + "tags": [], + "label": "SERVICE_FRAMEWORK_NAME", + "description": [], + "signature": [ + "\"service.framework.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_FRAMEWORK_VERSION", + "type": "string", + "tags": [], + "label": "SERVICE_FRAMEWORK_VERSION", + "description": [], + "signature": [ + "\"service.framework.version\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_LANGUAGE_NAME", + "type": "string", + "tags": [], + "label": "SERVICE_LANGUAGE_NAME", + "description": [], + "signature": [ + "\"service.language.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_LANGUAGE_VERSION", + "type": "string", + "tags": [], + "label": "SERVICE_LANGUAGE_VERSION", + "description": [], + "signature": [ + "\"service.language.version\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_NAME", + "type": "string", + "tags": [], + "label": "SERVICE_NAME", + "description": [], + "signature": [ + "\"service.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_NODE_NAME", + "type": "string", + "tags": [], + "label": "SERVICE_NODE_NAME", + "description": [], + "signature": [ + "\"service.node.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_OVERFLOW_COUNT", + "type": "string", + "tags": [], + "label": "SERVICE_OVERFLOW_COUNT", + "description": [], + "signature": [ + "\"service_transaction.aggregation.overflow_count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_RUNTIME_NAME", + "type": "string", + "tags": [], + "label": "SERVICE_RUNTIME_NAME", + "description": [], + "signature": [ + "\"service.runtime.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_RUNTIME_VERSION", + "type": "string", + "tags": [], + "label": "SERVICE_RUNTIME_VERSION", + "description": [], + "signature": [ + "\"service.runtime.version\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_TARGET_TYPE", + "type": "string", + "tags": [], + "label": "SERVICE_TARGET_TYPE", + "description": [], + "signature": [ + "\"service.target.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SERVICE_VERSION", + "type": "string", + "tags": [], + "label": "SERVICE_VERSION", + "description": [], + "signature": [ + "\"service.version\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SESSION_ID", + "type": "string", + "tags": [], + "label": "SESSION_ID", + "description": [], + "signature": [ + "\"session.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_ACTION", + "type": "string", + "tags": [], + "label": "SPAN_ACTION", + "description": [], + "signature": [ + "\"span.action\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_COMPOSITE_COMPRESSION_STRATEGY", + "type": "string", + "tags": [], + "label": "SPAN_COMPOSITE_COMPRESSION_STRATEGY", + "description": [], + "signature": [ + "\"span.composite.compression_strategy\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_COMPOSITE_COUNT", + "type": "string", + "tags": [], + "label": "SPAN_COMPOSITE_COUNT", + "description": [], + "signature": [ + "\"span.composite.count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_COMPOSITE_SUM", + "type": "string", + "tags": [], + "label": "SPAN_COMPOSITE_SUM", + "description": [], + "signature": [ + "\"span.composite.sum.us\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_DESTINATION_SERVICE_RESOURCE", + "type": "string", + "tags": [], + "label": "SPAN_DESTINATION_SERVICE_RESOURCE", + "description": [], + "signature": [ + "\"span.destination.service.resource\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT", + "type": "string", + "tags": [], + "label": "SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT", + "description": [], + "signature": [ + "\"span.destination.service.response_time.count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM", + "type": "string", + "tags": [], + "label": "SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM", + "description": [], + "signature": [ + "\"span.destination.service.response_time.sum.us\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_DURATION", + "type": "string", + "tags": [], + "label": "SPAN_DURATION", + "description": [], + "signature": [ + "\"span.duration.us\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_ID", + "type": "string", + "tags": [], + "label": "SPAN_ID", + "description": [], + "signature": [ + "\"span.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_LINKS", + "type": "string", + "tags": [], + "label": "SPAN_LINKS", + "description": [], + "signature": [ + "\"span.links\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_LINKS_SPAN_ID", + "type": "string", + "tags": [], + "label": "SPAN_LINKS_SPAN_ID", + "description": [], + "signature": [ + "\"span.links.span.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_LINKS_TRACE_ID", + "type": "string", + "tags": [], + "label": "SPAN_LINKS_TRACE_ID", + "description": [], + "signature": [ + "\"span.links.trace.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_NAME", + "type": "string", + "tags": [], + "label": "SPAN_NAME", + "description": [], + "signature": [ + "\"span.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_SELF_TIME_SUM", + "type": "string", + "tags": [], + "label": "SPAN_SELF_TIME_SUM", + "description": [], + "signature": [ + "\"span.self_time.sum.us\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_SUBTYPE", + "type": "string", + "tags": [], + "label": "SPAN_SUBTYPE", + "description": [], + "signature": [ + "\"span.subtype\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_SYNC", + "type": "string", + "tags": [], + "label": "SPAN_SYNC", + "description": [], + "signature": [ + "\"span.sync\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SPAN_TYPE", + "type": "string", + "tags": [], + "label": "SPAN_TYPE", + "description": [], + "signature": [ + "\"span.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.SpanDestinationMetric", + "type": "Type", + "tags": [], + "label": "SpanDestinationMetric", + "description": [], + "signature": [ + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.APMBaseDoc", + "text": "APMBaseDoc" + }, + " & { processor: { name: \"metric\"; event: \"metric\"; }; cloud?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Cloud", + "text": "Cloud" + }, + " | undefined; container?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Container", + "text": "Container" + }, + " | undefined; kubernetes?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Kubernetes", + "text": "Kubernetes" + }, + " | undefined; service?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Service", + "text": "Service" + }, + " | undefined; host?: ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.Host", + "text": "Host" + }, + " | undefined; } & { span: { destination: { service: { resource: string; response_time: { count: number; sum: { us: number; }; }; }; }; }; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/metric_raw.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.Stackframe", + "type": "Type", + "tags": [], + "label": "Stackframe", + "description": [], + "signature": [ + "StackframeBase | ", + { + "pluginId": "@kbn/apm-types", + "scope": "common", + "docId": "kibKbnApmTypesPluginApi", + "section": "def-common.StackframeWithLineContext", + "text": "StackframeWithLineContext" + } + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/stackframe.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.StackframeWithLineContext", + "type": "Type", + "tags": [], + "label": "StackframeWithLineContext", + "description": [], + "signature": [ + "StackframeBase & { line: Line & { context: string; }; }" + ], + "path": "packages/kbn-apm-types/src/es_schemas/raw/fields/stackframe.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TIER", + "type": "string", + "tags": [], + "label": "TIER", + "description": [], + "signature": [ + "\"_tier\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TIMESTAMP", + "type": "string", + "tags": [], + "label": "TIMESTAMP", + "description": [], + "signature": [ + "\"timestamp.us\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRACE_ID", + "type": "string", + "tags": [], + "label": "TRACE_ID", + "description": [], + "signature": [ + "\"trace.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_DURATION", + "type": "string", + "tags": [], + "label": "TRANSACTION_DURATION", + "description": [], + "signature": [ + "\"transaction.duration.us\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_DURATION_HISTOGRAM", + "type": "string", + "tags": [], + "label": "TRANSACTION_DURATION_HISTOGRAM", + "description": [], + "signature": [ + "\"transaction.duration.histogram\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_DURATION_SUMMARY", + "type": "string", + "tags": [], + "label": "TRANSACTION_DURATION_SUMMARY", + "description": [], + "signature": [ + "\"transaction.duration.summary\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_FAILURE_COUNT", + "type": "string", + "tags": [], + "label": "TRANSACTION_FAILURE_COUNT", + "description": [], + "signature": [ + "\"transaction.failure_count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_ID", + "type": "string", + "tags": [], + "label": "TRANSACTION_ID", + "description": [], + "signature": [ + "\"transaction.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_NAME", + "type": "string", + "tags": [], + "label": "TRANSACTION_NAME", + "description": [], + "signature": [ + "\"transaction.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_OVERFLOW_COUNT", + "type": "string", + "tags": [], + "label": "TRANSACTION_OVERFLOW_COUNT", + "description": [], + "signature": [ + "\"transaction.aggregation.overflow_count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_PAGE_URL", + "type": "string", + "tags": [], + "label": "TRANSACTION_PAGE_URL", + "description": [], + "signature": [ + "\"transaction.page.url\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_PROFILER_STACK_TRACE_IDS", + "type": "string", + "tags": [], + "label": "TRANSACTION_PROFILER_STACK_TRACE_IDS", + "description": [], + "signature": [ + "\"transaction.profiler_stack_trace_ids\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_RESULT", + "type": "string", + "tags": [], + "label": "TRANSACTION_RESULT", + "description": [], + "signature": [ + "\"transaction.result\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_ROOT", + "type": "string", + "tags": [], + "label": "TRANSACTION_ROOT", + "description": [], + "signature": [ + "\"transaction.root\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_SAMPLED", + "type": "string", + "tags": [], + "label": "TRANSACTION_SAMPLED", + "description": [], + "signature": [ + "\"transaction.sampled\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_SUCCESS_COUNT", + "type": "string", + "tags": [], + "label": "TRANSACTION_SUCCESS_COUNT", + "description": [], + "signature": [ + "\"transaction.success_count\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.TRANSACTION_TYPE", + "type": "string", + "tags": [], + "label": "TRANSACTION_TYPE", + "description": [], + "signature": [ + "\"transaction.type\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.URL_FULL", + "type": "string", + "tags": [], + "label": "URL_FULL", + "description": [], + "signature": [ + "\"url.full\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.USER_AGENT_NAME", + "type": "string", + "tags": [], + "label": "USER_AGENT_NAME", + "description": [], + "signature": [ + "\"user_agent.name\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.USER_AGENT_ORIGINAL", + "type": "string", + "tags": [], + "label": "USER_AGENT_ORIGINAL", + "description": [], + "signature": [ + "\"user_agent.original\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.USER_ID", + "type": "string", + "tags": [], + "label": "USER_ID", + "description": [], + "signature": [ + "\"user.id\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.VALUE_OTEL_JVM_PROCESS_MEMORY_HEAP", + "type": "string", + "tags": [], + "label": "VALUE_OTEL_JVM_PROCESS_MEMORY_HEAP", + "description": [], + "signature": [ + "\"heap\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/apm-types", + "id": "def-common.VALUE_OTEL_JVM_PROCESS_MEMORY_NON_HEAP", + "type": "string", + "tags": [], + "label": "VALUE_OTEL_JVM_PROCESS_MEMORY_NON_HEAP", + "description": [], + "signature": [ + "\"non_heap\"" + ], + "path": "packages/kbn-apm-types/src/es_fields/apm.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_apm_types.mdx b/api_docs/kbn_apm_types.mdx new file mode 100644 index 0000000000000..ae6b2b8e752b7 --- /dev/null +++ b/api_docs/kbn_apm_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: kibKbnApmTypesPluginApi +slug: /kibana-dev-docs/api/kbn-apm-types +title: "@kbn/apm-types" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/apm-types plugin +date: 2024-08-09 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-types'] +--- +import kbnApmTypesObj from './kbn_apm_types.devdocs.json'; + + + +Contact [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 316 | 0 | 315 | 0 | + +## Common + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index edc744fe6fdd7..d89f300c22cae 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_avc_banner.devdocs.json b/api_docs/kbn_avc_banner.devdocs.json index e86dac6904afb..84622adb07ab7 100644 --- a/api_docs/kbn_avc_banner.devdocs.json +++ b/api_docs/kbn_avc_banner.devdocs.json @@ -35,6 +35,23 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/avc-banner", + "id": "def-public.useIsStillYear2024", + "type": "Function", + "tags": [], + "label": "useIsStillYear2024", + "description": [], + "signature": [ + "() => boolean" + ], + "path": "packages/kbn-avc-banner/src/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [], diff --git a/api_docs/kbn_avc_banner.mdx b/api_docs/kbn_avc_banner.mdx index 8d2071d53695f..c5162e3b2766b 100644 --- a/api_docs/kbn_avc_banner.mdx +++ b/api_docs/kbn_avc_banner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-avc-banner title: "@kbn/avc-banner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/avc-banner plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/avc-banner'] --- import kbnAvcBannerObj from './kbn_avc_banner.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-defend-workflows](https://github.com/orgs/elastic/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2 | 0 | 2 | 0 | +| 3 | 0 | 3 | 0 | ## Client diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 429bbe9006178..1dc2f49f4c87d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index 7aedfb0634050..86b7dd66f9f3a 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index 34ec918e0239d..e85729810262a 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index 382023cd58319..9e1a7a1cb093e 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 0d2c30f73981f..7e82a0e7fee71 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: 2024-07-31 +date: 2024-08-09 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 ef43b568d06cd..9379e60e8b414 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: 2024-07-31 +date: 2024-08-09 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 8d714e9cf959c..e242e6aae319d 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: 2024-07-31 +date: 2024-08-09 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 538fb0470de79..8d3a5251e12ed 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: 2024-07-31 +date: 2024-08-09 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 3425182024f09..e8c4ee961a525 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: 2024-07-31 +date: 2024-08-09 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 bcf3dcfd9745e..265b03ec36d10 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: 2024-07-31 +date: 2024-08-09 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 8fb138d3e21e9..48f5eb050e190 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: 2024-07-31 +date: 2024-08-09 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 8da7b2e174f9d..d3501c233f8f2 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_code_editor.mdx index e60af91339918..0bcf2cae47ec7 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 804ff5407989e..07ff53da835cc 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 448e719003a23..693e8e420adc5 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index a3a10a69261b2..34a2479160975 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: 2024-07-31 +date: 2024-08-09 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 36fcaa786f888..9bece4544b547 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: 2024-07-31 +date: 2024-08-09 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 922f5422a35c3..bebb572bec47a 100644 --- a/api_docs/kbn_config_mocks.devdocs.json +++ b/api_docs/kbn_config_mocks.devdocs.json @@ -352,7 +352,7 @@ "section": "def-server.ConfigPath", "text": "ConfigPath" }, - ", dynamicConfigPaths: string[]], unknown>; setDynamicConfigOverrides: jest.MockInstance], unknown>; } & ", + ", dynamicConfigPaths: string[]], unknown>; setDynamicConfigOverrides: jest.MockInstance, [newOverrides: Record], 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 a2609f7dfa2c1..5d02a6662e46b 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 9a5cba0e5a3a7..ba1bc5a16484f 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: 2024-07-31 +date: 2024-08-09 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 ccf0c2dd807b2..f9125cd04200f 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: 2024-07-31 +date: 2024-08-09 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 6a21a5fd776b7..e1c3bf569facc 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: 2024-07-31 +date: 2024-08-09 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 8471a3f7dd97e..ca81004357a49 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: 2024-07-31 +date: 2024-08-09 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_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index 50c8d83754979..8b9e12ab76b71 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.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 b25e860fa3bf5..1af17d5f3154c 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: 2024-07-31 +date: 2024-08-09 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_user_profiles.mdx b/api_docs/kbn_content_management_user_profiles.mdx index 7c4172c98b453..740064b6d3a3c 100644 --- a/api_docs/kbn_content_management_user_profiles.mdx +++ b/api_docs/kbn_content_management_user_profiles.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-user-profiles title: "@kbn/content-management-user-profiles" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-user-profiles plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-user-profiles'] --- import kbnContentManagementUserProfilesObj from './kbn_content_management_user_profiles.devdocs.json'; diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index e45103c8204b9..cb9a33bc508c2 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_core_analytics_browser.devdocs.json index 7947b4922115d..502a1f60df079 100644 --- a/api_docs/kbn_core_analytics_browser.devdocs.json +++ b/api_docs/kbn_core_analytics_browser.devdocs.json @@ -13,7 +13,10 @@ "description": [ "\nGeneral settings of the analytics client" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "AnalyticsClientInitContext" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -26,23 +29,7 @@ "description": [ "\nBoolean indicating if it's running in developer mode." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-analytics-browser", - "id": "def-public.AnalyticsClientInitContext.sendTo", - "type": "CompoundType", - "tags": [], - "label": "sendTo", - "description": [ - "\nSpecify if the shippers should send their data to the production or staging environments." - ], - "signature": [ - "\"production\" | \"staging\"" - ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -56,15 +43,9 @@ "\nApplication-provided logger." ], "signature": [ - { - "pluginId": "@kbn/logging", - "scope": "common", - "docId": "kibKbnLoggingPluginApi", - "section": "def-common.Logger", - "text": "Logger" - } + "Logger" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -81,16 +62,10 @@ "\nDefinition of a context provider" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -103,7 +78,7 @@ "description": [ "\nThe name of the provider." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -120,7 +95,7 @@ "Observable", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -135,16 +110,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -161,16 +130,10 @@ "\nDefinition of the full event structure" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.Event", - "text": "Event" - }, + "Event", "" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -183,7 +146,7 @@ "description": [ "\nThe time the event was generated in ISO format." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -196,7 +159,7 @@ "description": [ "\nThe event type." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -212,7 +175,7 @@ "signature": [ "Properties" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -226,15 +189,9 @@ "\nThe {@link EventContext} enriched during the processing pipeline." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventContext", - "text": "EventContext" - } + "EventContext" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -250,7 +207,10 @@ "description": [ "\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "signature": [ + "EventContext" + ], + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -266,7 +226,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -282,7 +242,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -298,7 +258,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -314,7 +274,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -330,7 +290,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -346,7 +306,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -362,7 +322,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -378,7 +338,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -394,7 +354,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -410,7 +370,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -426,7 +386,7 @@ "signature": [ "[key: string]: unknown" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -443,16 +403,10 @@ "\nDefinition of an Event Type." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -465,7 +419,7 @@ "description": [ "\nThe event type's unique name." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -480,16 +434,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -505,7 +453,10 @@ "description": [ "\nAnalytics client's public APIs" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "IAnalyticsClient" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -523,7 +474,7 @@ "signature": [ "(eventType: string, eventData: EventTypeData) => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": true, "references": [ @@ -675,6 +626,18 @@ "plugin": "@kbn/cloud", "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" }, + { + "plugin": "@kbn/shared-ux-chrome-navigation", + "path": "packages/shared-ux/chrome/navigation/src/analytics/event_tracker.ts" + }, + { + "plugin": "@kbn/shared-ux-chrome-navigation", + "path": "packages/shared-ux/chrome/navigation/src/services.tsx" + }, + { + "plugin": "spaces", + "path": "x-pack/plugins/spaces/public/analytics/event_tracker.ts" + }, { "plugin": "observabilityAIAssistant", "path": "x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts" @@ -971,6 +934,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" @@ -1024,172 +991,60 @@ "path": "x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx" }, { - "plugin": "observabilityAIAssistant", - "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts" + "plugin": "spaces", + "path": "x-pack/plugins/spaces/public/management/management_service.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "@kbn/shared-ux-chrome-navigation", + "path": "packages/shared-ux/chrome/navigation/mocks/storybook.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { - "plugin": "@kbn/core-analytics-browser-mocks", - "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "@kbn/core-analytics-browser-mocks", @@ -1439,10 +1294,6 @@ "plugin": "actions", "path": "x-pack/plugins/actions/server/lib/action_executor.test.ts" }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" - }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" @@ -1471,6 +1322,30 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" @@ -1497,7 +1372,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1514,7 +1389,7 @@ "signature": [ "EventTypeData" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1533,16 +1408,10 @@ ], "signature": [ "(eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1556,16 +1425,10 @@ "The definition of the event type {@link EventTypeOpts }." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1584,32 +1447,14 @@ ], "signature": [ "(Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1623,16 +1468,10 @@ "The {@link IShipper } class to instantiate the shipper." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1649,7 +1488,7 @@ "signature": [ "ShipperConfig" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1664,16 +1503,10 @@ "Additional options to register the shipper {@link RegisterShipperOpts }." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -1692,16 +1525,10 @@ ], "signature": [ "(optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1715,15 +1542,9 @@ "{@link OptInConfig }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - } + "OptInConfig" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1744,16 +1565,10 @@ ], "signature": [ "(contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": true, "references": [ @@ -1853,6 +1668,10 @@ "plugin": "licensing", "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" }, + { + "plugin": "spaces", + "path": "x-pack/plugins/spaces/public/analytics/register_analytics_context.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" @@ -1873,18 +1692,6 @@ "plugin": "apm", "path": "x-pack/plugins/observability_solution/apm/public/analytics/register_service_inventory_view_type_context.ts" }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, { "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" @@ -1989,10 +1796,6 @@ "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" - }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" @@ -2045,16 +1848,10 @@ "{@link ContextProviderOpts }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2074,7 +1871,7 @@ "signature": [ "(contextProviderName: string) => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2090,7 +1887,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2110,16 +1907,10 @@ "signature": [ "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2135,7 +1926,7 @@ "signature": [ "() => Promise" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2153,7 +1944,7 @@ "signature": [ "() => Promise" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2171,7 +1962,10 @@ "description": [ "\nBasic structure of a Shipper" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "signature": [ + "IShipper" + ], + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2186,16 +1980,10 @@ ], "signature": [ "(events: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.Event", - "text": "Event" - }, + "Event", ">[]) => void" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2209,16 +1997,10 @@ "batched events {@link Event }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.Event", - "text": "Event" - }, + "Event", ">[]" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2238,7 +2020,7 @@ "signature": [ "(isOptedIn: boolean) => void" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2254,7 +2036,7 @@ "signature": [ "boolean" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2273,16 +2055,10 @@ ], "signature": [ "((newContext: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventContext", - "text": "EventContext" - }, + "EventContext", ") => void) | undefined" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2296,15 +2072,9 @@ "The full new context to set {@link EventContext }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventContext", - "text": "EventContext" - } + "EventContext" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2324,16 +2094,10 @@ "signature": [ "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", "> | undefined" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2349,7 +2113,7 @@ "signature": [ "() => Promise" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2367,7 +2131,7 @@ "signature": [ "() => void" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2419,7 +2183,10 @@ "description": [ "\nOptions for the optIn API" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "OptInConfig" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2433,15 +2200,9 @@ "\nControls the global enabled/disabled behaviour of the client and shippers." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfigPerType", - "text": "OptInConfigPerType" - } + "OptInConfigPerType" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2456,16 +2217,10 @@ ], "signature": [ "Record | undefined" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2481,7 +2236,10 @@ "description": [ "\nSets whether a type of event is enabled/disabled globally or per shipper." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "OptInConfigPerType" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2494,7 +2252,7 @@ "description": [ "\nThe event type is globally enabled." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2510,7 +2268,7 @@ "signature": [ "Record | undefined" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2526,7 +2284,10 @@ "description": [ "\nOptional options to register a shipper" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "RegisterShipperOpts" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2542,24 +2303,12 @@ "\nSchema to represent an array" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " extends ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMeta", - "text": "SchemaMeta" - }, + "SchemaMeta", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2575,7 +2324,7 @@ "signature": [ "\"array\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2590,72 +2339,24 @@ ], "signature": [ "{ type: \"pass_through\"; _meta: { description: string; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", "; } | (unknown extends Value ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " : NonNullable extends Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends object ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", ")" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2672,16 +2373,10 @@ "\nSchema to define a primitive value" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2696,31 +2391,13 @@ ], "signature": [ "NonNullable extends string | Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaStringTypes", - "text": "AllowedSchemaStringTypes" - }, + "AllowedSchemaStringTypes", " : NonNullable extends number ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaNumberTypes", - "text": "AllowedSchemaNumberTypes" - }, + "AllowedSchemaNumberTypes", " : NonNullable extends boolean ? \"boolean\" : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaTypes", - "text": "AllowedSchemaTypes" - } + "AllowedSchemaTypes" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2735,16 +2412,10 @@ ], "signature": [ "{ description: string; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2761,16 +2432,10 @@ "\nSchema meta with optional description" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMeta", - "text": "SchemaMeta" - }, + "SchemaMeta", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2785,16 +2450,10 @@ ], "signature": [ "({ description?: string | undefined; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", ") | undefined" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2811,24 +2470,12 @@ "\nSchema to represent an object" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " extends ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMeta", - "text": "SchemaMeta" - }, + "SchemaMeta", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2843,16 +2490,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2869,16 +2510,10 @@ "\nConstructor of a {@link IShipper}" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2891,7 +2526,7 @@ "description": [ "\nThe shipper's unique name" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2907,7 +2542,7 @@ "signature": [ "any" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2923,7 +2558,7 @@ "signature": [ "Config" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2938,15 +2573,9 @@ "Common context {@link AnalyticsClientInitContext }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AnalyticsClientInitContext", - "text": "AnalyticsClientInitContext" - } + "AnalyticsClientInitContext" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2966,7 +2595,10 @@ "description": [ "\nShape of the events emitted by the telemetryCounter$ observable" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "signature": [ + "TelemetryCounter" + ], + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2982,7 +2614,7 @@ "signature": [ "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2995,7 +2627,7 @@ "description": [ "\nWho emitted the event? It can be \"client\" or the name of the shipper." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -3008,7 +2640,7 @@ "description": [ "\nThe event type the success/failure/drop event refers to." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -3021,7 +2653,7 @@ "description": [ "\nCode to provide additional information about the success or failure. Examples are 200/400/504/ValidationError/UnknownError" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -3034,7 +2666,7 @@ "description": [ "\nThe number of events that this counter refers to." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -3056,7 +2688,7 @@ "signature": [ "\"boolean\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3073,7 +2705,7 @@ "signature": [ "\"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3090,7 +2722,7 @@ "signature": [ "\"keyword\" | \"text\" | \"date\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3107,7 +2739,7 @@ "signature": [ "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3123,63 +2755,21 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; registerEventType: (eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void; registerShipper: (Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void; registerContextProvider: (contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], "path": "packages/core/analytics/core-analytics-browser/src/types.ts", @@ -3198,23 +2788,11 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; }" ], "path": "packages/core/analytics/core-analytics-browser/src/types.ts", @@ -3234,7 +2812,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3251,7 +2829,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3267,31 +2845,13 @@ ], "signature": [ "Value extends string | Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaStringTypes", - "text": "AllowedSchemaStringTypes" - }, + "AllowedSchemaStringTypes", " : Value extends number ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaNumberTypes", - "text": "AllowedSchemaNumberTypes" - }, + "AllowedSchemaNumberTypes", " : Value extends boolean ? \"boolean\" : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaTypes", - "text": "AllowedSchemaTypes" - } + "AllowedSchemaTypes" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3307,16 +2867,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3333,7 +2887,7 @@ "signature": [ "unknown extends Value ? { optional?: boolean | undefined; } : undefined extends Value ? { optional: true; } : { optional?: false | undefined; }" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3349,72 +2903,24 @@ ], "signature": [ "{ type: \"pass_through\"; _meta: { description: string; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", "; } | (unknown extends Value ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " : NonNullable extends Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends object ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", ")" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3431,7 +2937,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3448,7 +2954,7 @@ "signature": [ "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index b6241ca8cb289..be77f4a5dc67e 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.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 | |-------------------|-----------|------------------------|-----------------| -| 101 | 0 | 0 | 0 | +| 100 | 0 | 0 | 0 | ## Client diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 91376aa7d2855..417924b10ef52 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: 2024-07-31 +date: 2024-08-09 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 fe5210175cf62..edf5580284f34 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_core_analytics_server.devdocs.json index 3d8547c721ef1..1194fae25d085 100644 --- a/api_docs/kbn_core_analytics_server.devdocs.json +++ b/api_docs/kbn_core_analytics_server.devdocs.json @@ -21,7 +21,10 @@ "description": [ "\nGeneral settings of the analytics client" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "AnalyticsClientInitContext" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -34,23 +37,7 @@ "description": [ "\nBoolean indicating if it's running in developer mode." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/core-analytics-server", - "id": "def-server.AnalyticsClientInitContext.sendTo", - "type": "CompoundType", - "tags": [], - "label": "sendTo", - "description": [ - "\nSpecify if the shippers should send their data to the production or staging environments." - ], - "signature": [ - "\"production\" | \"staging\"" - ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -64,15 +51,9 @@ "\nApplication-provided logger." ], "signature": [ - { - "pluginId": "@kbn/logging", - "scope": "common", - "docId": "kibKbnLoggingPluginApi", - "section": "def-common.Logger", - "text": "Logger" - } + "Logger" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -89,16 +70,10 @@ "\nDefinition of a context provider" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -111,7 +86,7 @@ "description": [ "\nThe name of the provider." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -128,7 +103,7 @@ "Observable", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -143,16 +118,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -169,16 +138,10 @@ "\nDefinition of the full event structure" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.Event", - "text": "Event" - }, + "Event", "" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -191,7 +154,7 @@ "description": [ "\nThe time the event was generated in ISO format." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -204,7 +167,7 @@ "description": [ "\nThe event type." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -220,7 +183,7 @@ "signature": [ "Properties" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -234,15 +197,9 @@ "\nThe {@link EventContext} enriched during the processing pipeline." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventContext", - "text": "EventContext" - } + "EventContext" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -258,7 +215,10 @@ "description": [ "\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "signature": [ + "EventContext" + ], + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -274,7 +234,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -290,7 +250,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -306,7 +266,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -322,7 +282,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -338,7 +298,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -354,7 +314,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -370,7 +330,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -386,7 +346,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -402,7 +362,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -418,7 +378,7 @@ "signature": [ "string | undefined" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -434,7 +394,7 @@ "signature": [ "[key: string]: unknown" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -451,16 +411,10 @@ "\nDefinition of an Event Type." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -473,7 +427,7 @@ "description": [ "\nThe event type's unique name." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -488,16 +442,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -513,7 +461,10 @@ "description": [ "\nAnalytics client's public APIs" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "IAnalyticsClient" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -531,7 +482,7 @@ "signature": [ "(eventType: string, eventData: EventTypeData) => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": true, "references": [ @@ -683,6 +634,18 @@ "plugin": "@kbn/cloud", "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" }, + { + "plugin": "@kbn/shared-ux-chrome-navigation", + "path": "packages/shared-ux/chrome/navigation/src/analytics/event_tracker.ts" + }, + { + "plugin": "@kbn/shared-ux-chrome-navigation", + "path": "packages/shared-ux/chrome/navigation/src/services.tsx" + }, + { + "plugin": "spaces", + "path": "x-pack/plugins/spaces/public/analytics/event_tracker.ts" + }, { "plugin": "observabilityAIAssistant", "path": "x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts" @@ -979,6 +942,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" @@ -1032,172 +999,60 @@ "path": "x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx" }, { - "plugin": "observabilityAIAssistant", - "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "spaces", + "path": "x-pack/plugins/spaces/public/management/management_service.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "@kbn/shared-ux-chrome-navigation", + "path": "packages/shared-ux/chrome/navigation/mocks/storybook.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" }, { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { - "plugin": "@kbn/core-analytics-browser-mocks", - "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts" }, { "plugin": "@kbn/core-analytics-browser-mocks", @@ -1447,10 +1302,6 @@ "plugin": "actions", "path": "x-pack/plugins/actions/server/lib/action_executor.test.ts" }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" - }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" @@ -1479,6 +1330,30 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" @@ -1505,7 +1380,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1522,7 +1397,7 @@ "signature": [ "EventTypeData" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1541,16 +1416,10 @@ ], "signature": [ "(eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1564,16 +1433,10 @@ "The definition of the event type {@link EventTypeOpts }." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1592,32 +1455,14 @@ ], "signature": [ "(Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1631,16 +1476,10 @@ "The {@link IShipper } class to instantiate the shipper." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1657,7 +1496,7 @@ "signature": [ "ShipperConfig" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1672,16 +1511,10 @@ "Additional options to register the shipper {@link RegisterShipperOpts }." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -1700,16 +1533,10 @@ ], "signature": [ "(optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1723,15 +1550,9 @@ "{@link OptInConfig }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - } + "OptInConfig" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1752,16 +1573,10 @@ ], "signature": [ "(contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": true, "references": [ @@ -1861,6 +1676,10 @@ "plugin": "licensing", "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" }, + { + "plugin": "spaces", + "path": "x-pack/plugins/spaces/public/analytics/register_analytics_context.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" @@ -1881,18 +1700,6 @@ "plugin": "apm", "path": "x-pack/plugins/observability_solution/apm/public/analytics/register_service_inventory_view_type_context.ts" }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" - }, { "plugin": "@kbn/core-analytics-browser-mocks", "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" @@ -1997,10 +1804,6 @@ "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" }, - { - "plugin": "@kbn/ebt", - "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" - }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" @@ -2053,16 +1856,10 @@ "{@link ContextProviderOpts }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2082,7 +1879,7 @@ "signature": [ "(contextProviderName: string) => void" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2098,7 +1895,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2118,16 +1915,10 @@ "signature": [ "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2143,7 +1934,7 @@ "signature": [ "() => Promise" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2161,7 +1952,7 @@ "signature": [ "() => Promise" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2179,7 +1970,10 @@ "description": [ "\nBasic structure of a Shipper" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "signature": [ + "IShipper" + ], + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2194,16 +1988,10 @@ ], "signature": [ "(events: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.Event", - "text": "Event" - }, + "Event", ">[]) => void" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2217,16 +2005,10 @@ "batched events {@link Event }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.Event", - "text": "Event" - }, + "Event", ">[]" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2246,7 +2028,7 @@ "signature": [ "(isOptedIn: boolean) => void" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2262,7 +2044,7 @@ "signature": [ "boolean" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2281,16 +2063,10 @@ ], "signature": [ "((newContext: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventContext", - "text": "EventContext" - }, + "EventContext", ") => void) | undefined" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2304,15 +2080,9 @@ "The full new context to set {@link EventContext }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventContext", - "text": "EventContext" - } + "EventContext" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2332,16 +2102,10 @@ "signature": [ "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", "> | undefined" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2357,7 +2121,7 @@ "signature": [ "() => Promise" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2375,7 +2139,7 @@ "signature": [ "() => void" ], - "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "path": "node_modules/@elastic/ebt/client/src/shippers/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2393,7 +2157,10 @@ "description": [ "\nOptions for the optIn API" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "OptInConfig" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2407,15 +2174,9 @@ "\nControls the global enabled/disabled behaviour of the client and shippers." ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfigPerType", - "text": "OptInConfigPerType" - } + "OptInConfigPerType" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2430,16 +2191,10 @@ ], "signature": [ "Record | undefined" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2455,7 +2210,10 @@ "description": [ "\nSets whether a type of event is enabled/disabled globally or per shipper." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "OptInConfigPerType" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2468,7 +2226,7 @@ "description": [ "\nThe event type is globally enabled." ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2484,7 +2242,7 @@ "signature": [ "Record | undefined" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2500,7 +2258,10 @@ "description": [ "\nOptional options to register a shipper" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "signature": [ + "RegisterShipperOpts" + ], + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -2516,24 +2277,12 @@ "\nSchema to represent an array" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " extends ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMeta", - "text": "SchemaMeta" - }, + "SchemaMeta", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2549,7 +2298,7 @@ "signature": [ "\"array\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2564,72 +2313,24 @@ ], "signature": [ "{ type: \"pass_through\"; _meta: { description: string; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", "; } | (unknown extends Value ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " : NonNullable extends Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends object ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", ")" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2646,16 +2347,10 @@ "\nSchema to define a primitive value" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2670,31 +2365,13 @@ ], "signature": [ "NonNullable extends string | Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaStringTypes", - "text": "AllowedSchemaStringTypes" - }, + "AllowedSchemaStringTypes", " : NonNullable extends number ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaNumberTypes", - "text": "AllowedSchemaNumberTypes" - }, + "AllowedSchemaNumberTypes", " : NonNullable extends boolean ? \"boolean\" : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaTypes", - "text": "AllowedSchemaTypes" - } + "AllowedSchemaTypes" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2709,16 +2386,10 @@ ], "signature": [ "{ description: string; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2735,16 +2406,10 @@ "\nSchema meta with optional description" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMeta", - "text": "SchemaMeta" - }, + "SchemaMeta", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2759,16 +2424,10 @@ ], "signature": [ "({ description?: string | undefined; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", ") | undefined" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2785,24 +2444,12 @@ "\nSchema to represent an object" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " extends ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMeta", - "text": "SchemaMeta" - }, + "SchemaMeta", "" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2817,16 +2464,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -2843,16 +2484,10 @@ "\nConstructor of a {@link IShipper}" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", "" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2865,7 +2500,7 @@ "description": [ "\nThe shipper's unique name" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2881,7 +2516,7 @@ "signature": [ "any" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2897,7 +2532,7 @@ "signature": [ "Config" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2912,15 +2547,9 @@ "Common context {@link AnalyticsClientInitContext }" ], "signature": [ - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AnalyticsClientInitContext", - "text": "AnalyticsClientInitContext" - } + "AnalyticsClientInitContext" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2940,7 +2569,10 @@ "description": [ "\nShape of the events emitted by the telemetryCounter$ observable" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "signature": [ + "TelemetryCounter" + ], + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2956,7 +2588,7 @@ "signature": [ "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2969,7 +2601,7 @@ "description": [ "\nWho emitted the event? It can be \"client\" or the name of the shipper." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2982,7 +2614,7 @@ "description": [ "\nThe event type the success/failure/drop event refers to." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -2995,7 +2627,7 @@ "description": [ "\nCode to provide additional information about the success or failure. Examples are 200/400/504/ValidationError/UnknownError" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false }, @@ -3008,7 +2640,7 @@ "description": [ "\nThe number of events that this counter refers to." ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false } @@ -3030,7 +2662,7 @@ "signature": [ "\"boolean\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3047,7 +2679,7 @@ "signature": [ "\"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3064,7 +2696,7 @@ "signature": [ "\"keyword\" | \"text\" | \"date\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3081,7 +2713,7 @@ "signature": [ "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3097,63 +2729,21 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; registerEventType: (eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void; registerShipper: (Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void; registerContextProvider: (contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], "path": "packages/core/analytics/core-analytics-server/src/contracts.ts", @@ -3172,63 +2762,21 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; registerEventType: (eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void; registerShipper: (Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void; registerContextProvider: (contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], "path": "packages/core/analytics/core-analytics-server/src/contracts.ts", @@ -3247,23 +2795,11 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; }" ], "path": "packages/core/analytics/core-analytics-server/src/contracts.ts", @@ -3283,7 +2819,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3300,7 +2836,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3316,31 +2852,13 @@ ], "signature": [ "Value extends string | Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaStringTypes", - "text": "AllowedSchemaStringTypes" - }, + "AllowedSchemaStringTypes", " : Value extends number ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaNumberTypes", - "text": "AllowedSchemaNumberTypes" - }, + "AllowedSchemaNumberTypes", " : Value extends boolean ? \"boolean\" : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.AllowedSchemaTypes", - "text": "AllowedSchemaTypes" - } + "AllowedSchemaTypes" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3356,16 +2874,10 @@ ], "signature": [ "{ [Key in keyof Required]: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaValue", - "text": "SchemaValue" - }, + "SchemaValue", "; }" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3382,7 +2894,7 @@ "signature": [ "unknown extends Value ? { optional?: boolean | undefined; } : undefined extends Value ? { optional: true; } : { optional?: false | undefined; }" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3398,72 +2910,24 @@ ], "signature": [ "{ type: \"pass_through\"; _meta: { description: string; } & ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaMetaOptional", - "text": "SchemaMetaOptional" - }, + "SchemaMetaOptional", "; } | (unknown extends Value ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " | ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaArray", - "text": "SchemaArray" - }, + "SchemaArray", " : NonNullable extends Date ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", " : NonNullable extends object ? ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaObject", - "text": "SchemaObject" - }, + "SchemaObject", " : ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.SchemaChildValue", - "text": "SchemaChildValue" - }, + "SchemaChildValue", ")" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3480,7 +2944,7 @@ "signature": [ "string" ], - "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "path": "node_modules/@elastic/ebt/client/src/analytics_client/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3497,7 +2961,7 @@ "signature": [ "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" ], - "path": "packages/analytics/ebt/client/src/events/types.ts", + "path": "node_modules/@elastic/ebt/client/src/events/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 1a03f20dfc99d..5b21a04dfed79 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_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 | |-------------------|-----------|------------------------|-----------------| -| 100 | 0 | 0 | 0 | +| 99 | 0 | 0 | 0 | ## Server diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 511ecc7be6d3c..694d474e59d20 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: 2024-07-31 +date: 2024-08-09 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 a5a1631a958b5..44eca42f3cec1 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: 2024-07-31 +date: 2024-08-09 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 b12ed025c58e2..1806c49376abe 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: 2024-07-31 +date: 2024-08-09 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 8d7373f033145..f4d7dcda7a43b 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: 2024-07-31 +date: 2024-08-09 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 e98d295208ca7..deedcc6c0d1d0 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: 2024-07-31 +date: 2024-08-09 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 14ad05dc499fb..ebf6b330ac5e8 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_core_apps_browser_internal.devdocs.json index 55c6bb04db033..18fb1dea37623 100644 --- a/api_docs/kbn_core_apps_browser_internal.devdocs.json +++ b/api_docs/kbn_core_apps_browser_internal.devdocs.json @@ -363,23 +363,11 @@ "description": [], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; }" ], "path": "packages/core/apps/core-apps-browser-internal/src/core_app.ts", diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index eda5c7fbbf4bf..f6cb98d24ce2f 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: 2024-07-31 +date: 2024-08-09 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 31adc7add0377..2cb1339ec4852 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 5801e6c4c51ca..44906fd3b5426 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index f5cea3950f34e..8fa197bdb21d6 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: 2024-07-31 +date: 2024-08-09 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 081488ab4b896..91fe59f472191 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: 2024-07-31 +date: 2024-08-09 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 50a36b006ece5..2f3ca370b7d34 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: 2024-07-31 +date: 2024-08-09 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 e01206b59bbd3..1e402b4f7ca35 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: 2024-07-31 +date: 2024-08-09 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 6759ac841d615..73c417a283f68 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: 2024-07-31 +date: 2024-08-09 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 d23c6e92f38bc..a7cce8af91e49 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: 2024-07-31 +date: 2024-08-09 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 5a486731b17ad..35c000de8c6bb 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: 2024-07-31 +date: 2024-08-09 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 3ed8988623a57..5a6cd6201af6e 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_core_chrome_browser.devdocs.json index 40259ec3d235b..f8fadfa7b7a26 100644 --- a/api_docs/kbn_core_chrome_browser.devdocs.json +++ b/api_docs/kbn_core_chrome_browser.devdocs.json @@ -3700,7 +3700,7 @@ "label": "AppDeepLinkId", "description": [], "signature": [ - "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"metrics\" | \"management\" | \"synthetics\" | \"ux\" | \"apm\" | \"logs\" | \"profiling\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"canvas\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchRelevance\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"enterpriseSearchRelevance:inferenceEndpoints\" | \"observability-logs-explorer\" | \"observabilityOnboarding\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:services\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"profiling:functions\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"securitySolutionUI:notes-management\" | \"fleet:settings\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\" | \"fleet:agents\"" + "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"metrics\" | \"management\" | \"apm\" | \"synthetics\" | \"ux\" | \"logs\" | \"profiling\" | \"dashboards\" | \"slo\" | \"observabilityAIAssistant\" | \"home\" | \"canvas\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:logPatternAnalysis\" | \"ml:logRateAnalysis\" | \"ml:singleMetricViewer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:cross_cluster_replication\" | \"management:dataViews\" | \"management:spaces\" | \"management:settings\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchRelevance\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"searchHomepage\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"enterpriseSearchRelevance:inferenceEndpoints\" | \"observability-logs-explorer\" | \"observabilityOnboarding\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:services\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"profiling:functions\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"securitySolutionUI:notes-management\" | \"fleet:settings\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\" | \"fleet:agents\"" ], "path": "packages/core/chrome/core-chrome-browser/src/project_navigation.ts", "deprecated": false, diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 7190d2a20f0d5..0d1542869897b 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: 2024-07-31 +date: 2024-08-09 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 01a3252c8a762..9ed530dd87d31 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: 2024-07-31 +date: 2024-08-09 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 6b0654b0cc29b..02fb5a6f0fd76 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: 2024-07-31 +date: 2024-08-09 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 f3851859bc80f..eb937c07f7d1f 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: 2024-07-31 +date: 2024-08-09 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 ccf61dcfd8cb5..67cbbaee132b0 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: 2024-07-31 +date: 2024-08-09 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 f9ce5e189764e..cde594d8473b3 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: 2024-07-31 +date: 2024-08-09 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 761cfad051b1d..8e874bd2a7d2c 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: 2024-07-31 +date: 2024-08-09 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 1ef494137a049..19242bdb3056f 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: 2024-07-31 +date: 2024-08-09 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 2e955f2ab66a4..6a9e89d5f2ef4 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: 2024-07-31 +date: 2024-08-09 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 e2f65f0746144..6685dbcf23743 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: 2024-07-31 +date: 2024-08-09 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 07d289e9d0c60..4a62a2199b5f3 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: 2024-07-31 +date: 2024-08-09 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 327e94bc0a23a..9a08814a9b235 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: 2024-07-31 +date: 2024-08-09 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 70421a30b8dca..70091ec03bcd9 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: 2024-07-31 +date: 2024-08-09 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 6567c4bb9dc31..42b2c34a1f8b2 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: 2024-07-31 +date: 2024-08-09 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 18f12bf471715..d84cb2989609e 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: 2024-07-31 +date: 2024-08-09 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 8b79d7beda3ca..56442283192c1 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: 2024-07-31 +date: 2024-08-09 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 7cad7c4115700..c0867ea7b8610 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: 2024-07-31 +date: 2024-08-09 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 931d550ff5faf..e9cd41d0d5c40 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: 2024-07-31 +date: 2024-08-09 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 65f4b362e4c53..358275e28d45d 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: 2024-07-31 +date: 2024-08-09 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 854a1a6a1538f..9dc87fad52004 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: 2024-07-31 +date: 2024-08-09 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 360a64ca5d767..174e63c9b804d 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: 2024-07-31 +date: 2024-08-09 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 19632ff6dba2b..fb7388f5262bb 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: 2024-07-31 +date: 2024-08-09 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 6ecc522559d7a..c02cbbf8bc750 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: 2024-07-31 +date: 2024-08-09 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 f979d572ec62d..8056eb99e729b 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: 2024-07-31 +date: 2024-08-09 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 49151ecc4f18d..38e91b749b11d 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: 2024-07-31 +date: 2024-08-09 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 c8ea31162da51..55d01d5c2b356 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: 2024-07-31 +date: 2024-08-09 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 95e16d5e7b40d..b16ebc474e4d4 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: 2024-07-31 +date: 2024-08-09 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 da7299c93558d..624aa6ff68159 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: 2024-07-31 +date: 2024-08-09 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 c80d4fd932288..ad785a27e9ceb 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: 2024-07-31 +date: 2024-08-09 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 64db3639ff354..3a4906e37b662 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: 2024-07-31 +date: 2024-08-09 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 7fdcb6435c9a9..d5e1f39451dc9 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: 2024-07-31 +date: 2024-08-09 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 610f65e8db71e..de2f5b89041ca 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: 2024-07-31 +date: 2024-08-09 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 1882a64591529..087190766d81a 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: 2024-07-31 +date: 2024-08-09 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 3e176be3eddbf..ec026df38c6c6 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: 2024-07-31 +date: 2024-08-09 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 029cfa86fc6cc..d08c66510fff3 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: 2024-07-31 +date: 2024-08-09 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 2ca16f2d8cec9..7380da51b0c24 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: 2024-07-31 +date: 2024-08-09 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 813bf0cb3b674..bf3c4c90ac97d 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: 2024-07-31 +date: 2024-08-09 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 c8eeef99cd18c..0cbdb9fb0b75c 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: 2024-07-31 +date: 2024-08-09 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 99d3c64bcd234..6cfd0aec342e7 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: 2024-07-31 +date: 2024-08-09 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 7cd9de70db514..6d994379497b4 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: 2024-07-31 +date: 2024-08-09 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 f6fa63616a341..f906eb3896b19 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: 2024-07-31 +date: 2024-08-09 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 3db3fbab28446..56080c5e812bb 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: 2024-07-31 +date: 2024-08-09 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 5fb212a11d89e..d0d08b34cc5a5 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: 2024-07-31 +date: 2024-08-09 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 cef994502e444..b3c361e10c315 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: 2024-07-31 +date: 2024-08-09 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 3e373606d15a4..2e352e7395919 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: 2024-07-31 +date: 2024-08-09 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 7a57728a3b396..a0bbfc4eea42a 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: 2024-07-31 +date: 2024-08-09 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 8e08360e695a4..7dae0f3e92ae3 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: 2024-07-31 +date: 2024-08-09 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 431d7030eaef8..93485ff3b0444 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: 2024-07-31 +date: 2024-08-09 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 8751c6033f8fe..cc5185b0ceb81 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -4434,6 +4434,10 @@ "plugin": "indexLifecycleManagement", "path": "x-pack/plugins/index_lifecycle_management/server/routes/api/snapshot_repositories/register_fetch_route.ts" }, + { + "plugin": "inference", + "path": "x-pack/plugins/inference/server/routes/connectors.ts" + }, { "plugin": "ingestPipelines", "path": "x-pack/plugins/ingest_pipelines/server/routes/api/get.ts" @@ -4831,8 +4835,8 @@ "path": "x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts" }, { - "plugin": "apm", - "path": "x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts" + "plugin": "apmDataAccess", + "path": "x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts" }, { "plugin": "console", @@ -6268,6 +6272,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/routes/authentication/saml.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/routes/authorization/roles/post.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/routes/deprecations/kibana_user_role.ts" @@ -7028,6 +7036,10 @@ "plugin": "indexLifecycleManagement", "path": "x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts" }, + { + "plugin": "inference", + "path": "x-pack/plugins/inference/server/routes/chat_complete.ts" + }, { "plugin": "ingestPipelines", "path": "x-pack/plugins/ingest_pipelines/server/routes/api/create.ts" @@ -8260,6 +8272,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/routes/role_mapping/post.test.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/routes/authorization/roles/post.test.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/routes/authentication/common.test.ts" @@ -14938,10 +14954,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts" @@ -16252,6 +16264,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts" @@ -16360,14 +16376,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" @@ -16958,10 +16966,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts" - }, { "plugin": "synthetics", "path": "x-pack/plugins/observability_solution/synthetics/server/server.ts" diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 4a72bb1b0944a..5100b7f6afe7e 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: 2024-07-31 +date: 2024-08-09 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 3d5fb301f8f1b..e9f72b71cd4e9 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: 2024-07-31 +date: 2024-08-09 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 c2e129b450bb1..aec8780d5f567 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: 2024-07-31 +date: 2024-08-09 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 47ded96f6288e..3090ef4347a9c 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: 2024-07-31 +date: 2024-08-09 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 2a204db8ffeaa..9f54f3c78437d 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: 2024-07-31 +date: 2024-08-09 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 f70f85c071b6b..f4011152fb41a 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: 2024-07-31 +date: 2024-08-09 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 285b53ea71e0e..bd59601807d38 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: 2024-07-31 +date: 2024-08-09 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 3a9217dbe9732..1c0f0e4a11757 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: 2024-07-31 +date: 2024-08-09 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 861fa65377f30..beec46f6d9db7 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: 2024-07-31 +date: 2024-08-09 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 d70746e4df9bd..34c750f82bac9 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: 2024-07-31 +date: 2024-08-09 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 804d859c0a4f5..93b96e2a645ae 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_core_lifecycle_browser.devdocs.json index de0a3acb01514..295994fc63a54 100644 --- a/api_docs/kbn_core_lifecycle_browser.devdocs.json +++ b/api_docs/kbn_core_lifecycle_browser.devdocs.json @@ -38,63 +38,21 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; registerEventType: (eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void; registerShipper: (Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void; registerContextProvider: (contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], "path": "packages/core/lifecycle/core-lifecycle-browser/src/core_setup.ts", @@ -420,23 +378,11 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; }" ], "path": "packages/core/lifecycle/core-lifecycle-browser/src/core_start.ts", diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 6dd50692711e4..12efcd4466520 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: 2024-07-31 +date: 2024-08-09 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 b6d5d7da93e67..a9d42cd47ce23 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_core_lifecycle_server.devdocs.json index 06d8f6a9bbfbc..bc9633a4c0006 100644 --- a/api_docs/kbn_core_lifecycle_server.devdocs.json +++ b/api_docs/kbn_core_lifecycle_server.devdocs.json @@ -36,63 +36,21 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; registerEventType: (eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void; registerShipper: (Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void; registerContextProvider: (contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], "path": "packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts", @@ -211,63 +169,21 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; registerEventType: (eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void; registerShipper: (Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void; registerContextProvider: (contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", @@ -718,23 +634,11 @@ ], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; }" ], "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index 2128422256f0c..68d6ac1ca7830 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: 2024-07-31 +date: 2024-08-09 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 abc667c047000..143580bc6bb5e 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: 2024-07-31 +date: 2024-08-09 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 97575cdaf8c04..d67322bf9169f 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: 2024-07-31 +date: 2024-08-09 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 cf17cbd49cfff..bd258ff9948d1 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: 2024-07-31 +date: 2024-08-09 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 782f8d6542561..d06dbb23e85f5 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: 2024-07-31 +date: 2024-08-09 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 d65f211942c45..170e63282cd8d 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: 2024-07-31 +date: 2024-08-09 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 5f6ec51418fa0..d15fd56fbe2ff 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: 2024-07-31 +date: 2024-08-09 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 9fbe1b4fa3738..117e13dca012c 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: 2024-07-31 +date: 2024-08-09 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 414689dfd3402..a077383f13d9e 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: 2024-07-31 +date: 2024-08-09 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 8b5177351f11c..a75af415bf139 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: 2024-07-31 +date: 2024-08-09 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 a3fd35284ae13..ac0676cd0b463 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: 2024-07-31 +date: 2024-08-09 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 aeca2bcde1d8b..9533acd8b0fe3 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: 2024-07-31 +date: 2024-08-09 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 54ad2bb55f3b2..f2b414389fd7e 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: 2024-07-31 +date: 2024-08-09 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 1f5b50c809af3..f675db73a70f5 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: 2024-07-31 +date: 2024-08-09 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 57b5ec43dea23..676313414850f 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: 2024-07-31 +date: 2024-08-09 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 dad22974c3106..f9437b5fd84ac 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: 2024-07-31 +date: 2024-08-09 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 f4974322986ea..bf7092c865e84 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: 2024-07-31 +date: 2024-08-09 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 5665e226f51c2..ca453d071079f 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: 2024-07-31 +date: 2024-08-09 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 3d4b7bc5e2ac3..6e19b4f0d4b4f 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: 2024-07-31 +date: 2024-08-09 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 da1e72b64af81..8480719363f8d 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: 2024-07-31 +date: 2024-08-09 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 0bb269573d411..885f74784d328 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: 2024-07-31 +date: 2024-08-09 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 1b024ad77bd36..566d02babb61f 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: 2024-07-31 +date: 2024-08-09 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 6ea47f6456f8c..84d93e3eaed5a 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: 2024-07-31 +date: 2024-08-09 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 95e27629a1223..ca5a33cc85a46 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: 2024-07-31 +date: 2024-08-09 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_contracts_browser.mdx b/api_docs/kbn_core_plugins_contracts_browser.mdx index 0e0ab116f5343..14996c99c56c9 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-browser'] --- import kbnCorePluginsContractsBrowserObj from './kbn_core_plugins_contracts_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_contracts_server.mdx b/api_docs/kbn_core_plugins_contracts_server.mdx index 628bd4b04b8cd..42e84d2b593cc 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index a478dcbf9465a..a29446c980250 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index 3543f9ba58209..f353b4316af76 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: 2024-07-31 +date: 2024-08-09 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 e7bee47ccd548..2a491bc483b76 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: 2024-07-31 +date: 2024-08-09 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 1be698bbc5721..8e0809bf9d4f8 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: 2024-07-31 +date: 2024-08-09 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 8be561c1b75ad..fa30028828579 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: 2024-07-31 +date: 2024-08-09 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 3051e30695647..c9ec6e24cfe05 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: 2024-07-31 +date: 2024-08-09 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 7b5933882fae8..91eaa30902f3e 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: 2024-07-31 +date: 2024-08-09 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 7cea7b9bdecb7..788888e4705a8 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 559f79042b838..057e32e796580 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 7eb95c62b312f..1b9f8f6ede201 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; 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 d8b0364a63dcc..13eb6e4585eb3 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: 2024-07-31 +date: 2024-08-09 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 a330f3fc0db4a..b40d89c5805e9 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: 2024-07-31 +date: 2024-08-09 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 fcc8595bbb859..0f423bd78c27e 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: 2024-07-31 +date: 2024-08-09 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 ad7cdc2315c30..d9ab12711b469 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: 2024-07-31 +date: 2024-08-09 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 ca48f329f0b01..9d7d773764ecf 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: 2024-07-31 +date: 2024-08-09 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 d8c558adc9487..7534fc5e42ff7 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: 2024-07-31 +date: 2024-08-09 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 75ebd577f1cf7..b105b0ed59f7f 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: 2024-07-31 +date: 2024-08-09 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 c991313f038c5..f483ffc5bc952 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: 2024-07-31 +date: 2024-08-09 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 113b5d3c5f479..4796a221ce2ca 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index cc0aee6ac01f1..a2c400a196027 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; 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 d8d4e255be39a..7e9ac13a350e8 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 6ea999727dbda..909861f3f3d53 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 5de7b4a4c3b75..2de5ab39e507d 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: 2024-07-31 +date: 2024-08-09 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 78671c2123532..03135f16191d5 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: 2024-07-31 +date: 2024-08-09 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 d860bd85f9cf3..a8a41e7c727e7 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: 2024-07-31 +date: 2024-08-09 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_security_browser.mdx b/api_docs/kbn_core_security_browser.mdx index 0e4b476a178de..cd5448cd6c19b 100644 --- a/api_docs/kbn_core_security_browser.mdx +++ b/api_docs/kbn_core_security_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser title: "@kbn/core-security-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser'] --- import kbnCoreSecurityBrowserObj from './kbn_core_security_browser.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_internal.mdx b/api_docs/kbn_core_security_browser_internal.mdx index 81340f13d8486..9624a4545c8c3 100644 --- a/api_docs/kbn_core_security_browser_internal.mdx +++ b/api_docs/kbn_core_security_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-internal title: "@kbn/core-security-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-internal plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-internal'] --- import kbnCoreSecurityBrowserInternalObj from './kbn_core_security_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_mocks.mdx b/api_docs/kbn_core_security_browser_mocks.mdx index 12537a361d603..0402917897e9d 100644 --- a/api_docs/kbn_core_security_browser_mocks.mdx +++ b/api_docs/kbn_core_security_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-mocks title: "@kbn/core-security-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-mocks plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-mocks'] --- import kbnCoreSecurityBrowserMocksObj from './kbn_core_security_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_security_common.mdx b/api_docs/kbn_core_security_common.mdx index d70ed4cf4c1d2..f989c3653cf96 100644 --- a/api_docs/kbn_core_security_common.mdx +++ b/api_docs/kbn_core_security_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-common title: "@kbn/core-security-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-common'] --- import kbnCoreSecurityCommonObj from './kbn_core_security_common.devdocs.json'; diff --git a/api_docs/kbn_core_security_server.mdx b/api_docs/kbn_core_security_server.mdx index 6ea17270993bb..1593dcc57cfef 100644 --- a/api_docs/kbn_core_security_server.mdx +++ b/api_docs/kbn_core_security_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server title: "@kbn/core-security-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server'] --- import kbnCoreSecurityServerObj from './kbn_core_security_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_internal.mdx b/api_docs/kbn_core_security_server_internal.mdx index f8ef237f37c84..15aff9bf7e908 100644 --- a/api_docs/kbn_core_security_server_internal.mdx +++ b/api_docs/kbn_core_security_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-internal title: "@kbn/core-security-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-internal plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-internal'] --- import kbnCoreSecurityServerInternalObj from './kbn_core_security_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_mocks.mdx b/api_docs/kbn_core_security_server_mocks.mdx index 3a2b394039903..4720e05d9fb5b 100644 --- a/api_docs/kbn_core_security_server_mocks.mdx +++ b/api_docs/kbn_core_security_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-mocks title: "@kbn/core-security-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-mocks plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-mocks'] --- import kbnCoreSecurityServerMocksObj from './kbn_core_security_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index f9adabb4acacf..7568bb887dfeb 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: 2024-07-31 +date: 2024-08-09 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 91dd4db504cc1..da2502ed4f79d 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: 2024-07-31 +date: 2024-08-09 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 0992bb0e6c218..8d14377960f06 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_core_status_server_internal.devdocs.json index dddd1a97097d8..63c08a5cd2861 100644 --- a/api_docs/kbn_core_status_server_internal.devdocs.json +++ b/api_docs/kbn_core_status_server_internal.devdocs.json @@ -287,63 +287,21 @@ "description": [], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; registerEventType: (eventTypeOps: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.EventTypeOpts", - "text": "EventTypeOpts" - }, + "EventTypeOpts", ") => void; registerShipper: (Shipper: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ShipperClassConstructor", - "text": "ShipperClassConstructor" - }, + "ShipperClassConstructor", ", shipperConfig: ShipperConfig, opts?: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.RegisterShipperOpts", - "text": "RegisterShipperOpts" - }, + "RegisterShipperOpts", " | undefined) => void; registerContextProvider: (contextProviderOpts: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.ContextProviderOpts", - "text": "ContextProviderOpts" - }, + "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], "path": "packages/core/status/core-status-server-internal/src/status_service.ts", diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 2b8f03057d7d6..0257be469caf1 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: 2024-07-31 +date: 2024-08-09 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 59ca40efcd57d..b499a1e42024d 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: 2024-07-31 +date: 2024-08-09 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 ec0f8b225d7b3..2774961a3c812 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: 2024-07-31 +date: 2024-08-09 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 95c3b396f80a4..f02dc28bfce2f 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: 2024-07-31 +date: 2024-08-09 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 e32f4ae455f44..1a958e1b88f6b 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: 2024-07-31 +date: 2024-08-09 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_model_versions.mdx b/api_docs/kbn_core_test_helpers_model_versions.mdx index 87453fd21b01b..cc3456ffeb069 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-model-versions'] --- import kbnCoreTestHelpersModelVersionsObj from './kbn_core_test_helpers_model_versions.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 d5177d2436814..c6e0fdd0a122d 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: 2024-07-31 +date: 2024-08-09 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 2b6f52ca52eb7..3a493a3bd91ba 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: 2024-07-31 +date: 2024-08-09 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 159801e5100cc..dc42e82de6fcf 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: 2024-07-31 +date: 2024-08-09 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 fdfab56df8a35..cdf5d6aa49334 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index ef3ac3938bb76..2496f91642325 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: 2024-07-31 +date: 2024-08-09 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 55797d5ac7776..00846f0ffbf0d 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: 2024-07-31 +date: 2024-08-09 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 c92c310a40b3d..2f7d8e487e01b 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_core_ui_settings_common.mdx index bfebfb7f3e814..f665161d18a7c 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 00389a16eb260..fdca230ef07d7 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 9532d204c0fa1..351f975848df3 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: 2024-07-31 +date: 2024-08-09 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 9de8decbd2a62..f8917ecfe6ba7 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: 2024-07-31 +date: 2024-08-09 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 818b91a232963..32480f65b4c63 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: 2024-07-31 +date: 2024-08-09 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 67b118638d65e..4bcdc0a453e8e 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: 2024-07-31 +date: 2024-08-09 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 d201a37c9db39..fca33731984ea 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: 2024-07-31 +date: 2024-08-09 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_profile_browser.mdx b/api_docs/kbn_core_user_profile_browser.mdx index b79c6ed7b2ad8..bd3d0381e7df4 100644 --- a/api_docs/kbn_core_user_profile_browser.mdx +++ b/api_docs/kbn_core_user_profile_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser title: "@kbn/core-user-profile-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser'] --- import kbnCoreUserProfileBrowserObj from './kbn_core_user_profile_browser.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_internal.mdx b/api_docs/kbn_core_user_profile_browser_internal.mdx index 68e0443eabaca..aedf107337bc2 100644 --- a/api_docs/kbn_core_user_profile_browser_internal.mdx +++ b/api_docs/kbn_core_user_profile_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-internal title: "@kbn/core-user-profile-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-internal plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-internal'] --- import kbnCoreUserProfileBrowserInternalObj from './kbn_core_user_profile_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_mocks.mdx b/api_docs/kbn_core_user_profile_browser_mocks.mdx index 2fd7844d8b667..53a7365df6238 100644 --- a/api_docs/kbn_core_user_profile_browser_mocks.mdx +++ b/api_docs/kbn_core_user_profile_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-mocks title: "@kbn/core-user-profile-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-mocks plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-mocks'] --- import kbnCoreUserProfileBrowserMocksObj from './kbn_core_user_profile_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_common.mdx b/api_docs/kbn_core_user_profile_common.mdx index 445046bcfb406..b7806d7b6ec4c 100644 --- a/api_docs/kbn_core_user_profile_common.mdx +++ b/api_docs/kbn_core_user_profile_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-common title: "@kbn/core-user-profile-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-common'] --- import kbnCoreUserProfileCommonObj from './kbn_core_user_profile_common.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server.mdx b/api_docs/kbn_core_user_profile_server.mdx index bb2b4d1d98b85..8094fe623e0cf 100644 --- a/api_docs/kbn_core_user_profile_server.mdx +++ b/api_docs/kbn_core_user_profile_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server title: "@kbn/core-user-profile-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server'] --- import kbnCoreUserProfileServerObj from './kbn_core_user_profile_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_internal.mdx b/api_docs/kbn_core_user_profile_server_internal.mdx index 648145a300e39..7300993993998 100644 --- a/api_docs/kbn_core_user_profile_server_internal.mdx +++ b/api_docs/kbn_core_user_profile_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-internal title: "@kbn/core-user-profile-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-internal plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-internal'] --- import kbnCoreUserProfileServerInternalObj from './kbn_core_user_profile_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_mocks.mdx b/api_docs/kbn_core_user_profile_server_mocks.mdx index b17e8b714cc1b..ba4ecb5a02dbc 100644 --- a/api_docs/kbn_core_user_profile_server_mocks.mdx +++ b/api_docs/kbn_core_user_profile_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-mocks title: "@kbn/core-user-profile-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-mocks plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-mocks'] --- import kbnCoreUserProfileServerMocksObj from './kbn_core_user_profile_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 e552a3d3eaeaa..54d3eb0d54009 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: 2024-07-31 +date: 2024-08-09 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_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 39eefe243f208..d93fd4fd7b53c 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: 2024-07-31 +date: 2024-08-09 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 b8a0cc2d5f766..d1d732932864e 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: 2024-07-31 +date: 2024-08-09 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 a6434ae5fc75b..a9a7cae320917 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index 7b08c4ac57e1d..b176dc921daeb 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index f7542d8729623..093955fc39a25 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 33cca2139dac4..af970848e6950 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index 4f39850c75424..06b4a832bf2b3 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 44981977919b5..9270660671311 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index 1acfdf6490d1d..43426e12a901b 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index 902777dcf3e38..34a836301ac67 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 904c026e73bc0..de5e5ad065480 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: 2024-07-31 +date: 2024-08-09 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 523faf00e20a8..bdd39e12deff4 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: 2024-07-31 +date: 2024-08-09 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 1df804579b87f..cecde54dd1efd 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_fleet.mdx b/api_docs/kbn_deeplinks_fleet.mdx index 5ef95ed7ed41f..f1117eaa22603 100644 --- a/api_docs/kbn_deeplinks_fleet.mdx +++ b/api_docs/kbn_deeplinks_fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-fleet title: "@kbn/deeplinks-fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-fleet plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-fleet'] --- import kbnDeeplinksFleetObj from './kbn_deeplinks_fleet.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 2db9a33551eca..4218c00f11fd0 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: 2024-07-31 +date: 2024-08-09 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 50690bc0236ec..ee4019966ca86 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: 2024-07-31 +date: 2024-08-09 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 be655a5e24262..8a0fac7dab160 100644 --- a/api_docs/kbn_deeplinks_observability.devdocs.json +++ b/api_docs/kbn_deeplinks_observability.devdocs.json @@ -759,7 +759,7 @@ "label": "AppId", "description": [], "signature": [ - "\"metrics\" | \"synthetics\" | \"ux\" | \"apm\" | \"logs\" | \"profiling\" | \"slo\" | \"observabilityAIAssistant\" | \"observability-overview\" | \"observability-logs-explorer\" | \"observabilityOnboarding\"" + "\"metrics\" | \"apm\" | \"synthetics\" | \"ux\" | \"logs\" | \"profiling\" | \"slo\" | \"observabilityAIAssistant\" | \"observability-overview\" | \"observability-logs-explorer\" | \"observabilityOnboarding\"" ], "path": "packages/deeplinks/observability/deep_links.ts", "deprecated": false, diff --git a/api_docs/kbn_deeplinks_observability.mdx b/api_docs/kbn_deeplinks_observability.mdx index b269a81c0d3d0..5653d7c4d8895 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 34d9608a369b2..0fa2bef3adca7 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_security.mdx b/api_docs/kbn_deeplinks_security.mdx index 3fcd5ad29e59e..c8a8d2f21feee 100644 --- a/api_docs/kbn_deeplinks_security.mdx +++ b/api_docs/kbn_deeplinks_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-security title: "@kbn/deeplinks-security" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-security plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-security'] --- import kbnDeeplinksSecurityObj from './kbn_deeplinks_security.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_shared.mdx b/api_docs/kbn_deeplinks_shared.mdx index fc6dcf2fd9210..b8f82f64f24c4 100644 --- a/api_docs/kbn_deeplinks_shared.mdx +++ b/api_docs/kbn_deeplinks_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-shared title: "@kbn/deeplinks-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-shared plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-shared'] --- import kbnDeeplinksSharedObj from './kbn_deeplinks_shared.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index d01c598379102..fff4d2a8a794f 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: 2024-07-31 +date: 2024-08-09 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 74e469f21d33f..6158a308c9724 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: 2024-07-31 +date: 2024-08-09 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 486aacb1058bd..05a338844517e 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_default_nav_ml.mdx index 8e278606ef5c5..813469ddf6a55 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: 2024-07-31 +date: 2024-08-09 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 5cb51db937593..7c5deb366ed80 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: 2024-07-31 +date: 2024-08-09 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 93fca28e724e1..b44c90bdc7519 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: 2024-07-31 +date: 2024-08-09 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 97cb166861df6..81427e7c085ae 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: 2024-07-31 +date: 2024-08-09 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 bac986a33549d..07b97ab693338 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 4383e3ca9e60a..7b5e2b7481274 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 67928bd9b750f..98086a1171dda 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -546,7 +546,7 @@ "label": "securitySolution", "description": [], "signature": [ - "{ readonly artifactControl: string; readonly avcResults: string; readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; readonly configureEndpointIntegrationPolicy: string; readonly exceptions: { value_lists: string; }; readonly privileges: string; readonly manageDetectionRules: string; readonly createDetectionRules: string; readonly createEsqlRuleType: string; readonly ruleUiAdvancedParams: string; readonly entityAnalytics: { readonly riskScorePrerequisites: string; readonly entityRiskScoring: string; readonly assetCriticality: string; }; readonly detectionEngineOverview: string; }" + "{ readonly aiAssistant: string; readonly artifactControl: string; readonly avcResults: string; readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; readonly configureEndpointIntegrationPolicy: string; readonly exceptions: { value_lists: string; }; readonly privileges: string; readonly manageDetectionRules: string; readonly createDetectionRules: string; readonly createEsqlRuleType: string; readonly ruleUiAdvancedParams: string; readonly entityAnalytics: { readonly riskScorePrerequisites: string; readonly entityRiskScoring: string; readonly assetCriticality: string; }; readonly detectionEngineOverview: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, @@ -658,7 +658,7 @@ "label": "observability", "description": [], "signature": [ - "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly customThreshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; readonly syntheticsAlerting: string; readonly syntheticsCommandReference: string; readonly syntheticsProjectMonitors: string; readonly syntheticsMigrateFromIntegration: string; readonly sloBurnRateRule: string; }" + "{ readonly guide: string; readonly infrastructureThreshold: string; readonly logsThreshold: string; readonly metricsThreshold: string; readonly customThreshold: string; readonly monitorStatus: string; readonly monitorUptime: string; readonly tlsCertificate: string; readonly uptimeDurationAnomaly: string; readonly monitorLogs: string; readonly analyzeMetrics: string; readonly monitorUptimeSynthetics: string; readonly userExperience: string; readonly createAlerts: string; readonly syntheticsAlerting: string; readonly syntheticsCommandReference: string; readonly syntheticsProjectMonitors: string; readonly syntheticsMigrateFromIntegration: string; readonly sloBurnRateRule: string; readonly aiAssistant: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 9398b9da2b34b..115ea4a3be083 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.devdocs.json b/api_docs/kbn_docs_utils.devdocs.json index da6dd5935a733..1afbc1016d3d3 100644 --- a/api_docs/kbn_docs_utils.devdocs.json +++ b/api_docs/kbn_docs_utils.devdocs.json @@ -9,19 +9,11 @@ "objects": [] }, "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { "classes": [], "functions": [ { "parentPluginId": "@kbn/docs-utils", - "id": "def-common.findPlugins", + "id": "def-server.findPlugins", "type": "Function", "tags": [], "label": "findPlugins", @@ -37,7 +29,7 @@ "children": [ { "parentPluginId": "@kbn/docs-utils", - "id": "def-common.findPlugins.$1", + "id": "def-server.findPlugins.$1", "type": "Array", "tags": [], "label": "pluginOrPackageFilter", @@ -56,7 +48,7 @@ }, { "parentPluginId": "@kbn/docs-utils", - "id": "def-common.findTeamPlugins", + "id": "def-server.findTeamPlugins", "type": "Function", "tags": [], "label": "findTeamPlugins", @@ -72,7 +64,7 @@ "children": [ { "parentPluginId": "@kbn/docs-utils", - "id": "def-common.findTeamPlugins.$1", + "id": "def-server.findTeamPlugins.$1", "type": "string", "tags": [], "label": "team", @@ -91,7 +83,7 @@ }, { "parentPluginId": "@kbn/docs-utils", - "id": "def-common.runBuildApiDocsCli", + "id": "def-server.runBuildApiDocsCli", "type": "Function", "tags": [], "label": "runBuildApiDocsCli", @@ -111,5 +103,13 @@ "enums": [], "misc": [], "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 8b19ce5140ade..0d0dca3d62f85 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; @@ -23,8 +23,8 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban |-------------------|-----------|------------------------|-----------------| | 5 | 0 | 5 | 1 | -## Common +## Server ### Functions - + diff --git a/api_docs/kbn_dom_drag_drop.mdx b/api_docs/kbn_dom_drag_drop.mdx index ec06d911e71dd..3d525619ff8a7 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_ebt_tools.devdocs.json index 44363fefececf..4accaa3356967 100644 --- a/api_docs/kbn_ebt_tools.devdocs.json +++ b/api_docs/kbn_ebt_tools.devdocs.json @@ -64,13 +64,7 @@ ], "signature": [ "(analytics: Pick<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.IAnalyticsClient", - "text": "IAnalyticsClient" - }, + "IAnalyticsClient", ", \"registerEventType\">) => void" ], "path": "packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts", @@ -88,13 +82,7 @@ ], "signature": [ "Pick<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.IAnalyticsClient", - "text": "IAnalyticsClient" - }, + "IAnalyticsClient", ", \"registerEventType\">" ], "path": "packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts", @@ -117,13 +105,7 @@ ], "signature": [ "(analytics: Pick<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.IAnalyticsClient", - "text": "IAnalyticsClient" - }, + "IAnalyticsClient", ", \"reportEvent\">, eventData: ", { "pluginId": "@kbn/ebt-tools", @@ -149,13 +131,7 @@ ], "signature": [ "Pick<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.IAnalyticsClient", - "text": "IAnalyticsClient" - }, + "IAnalyticsClient", ", \"reportEvent\">" ], "path": "packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts", diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 20c2d479d7132..709e84ec7865d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index 57eca41526b8e..f92317cf0f791 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: 2024-07-31 +date: 2024-08-09 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_agent_utils.devdocs.json b/api_docs/kbn_elastic_agent_utils.devdocs.json index 9814a2afd0fa3..3212c7b28aacf 100644 --- a/api_docs/kbn_elastic_agent_utils.devdocs.json +++ b/api_docs/kbn_elastic_agent_utils.devdocs.json @@ -431,7 +431,7 @@ "label": "AgentName", "description": [], "signature": [ - "\"java\" | \"ruby\" | \"go\" | \"dotnet\" | \"php\" | \"otlp\" | \"android/java\" | \"iOS/swift\" | \"rum-js\" | \"js-base\" | \"opentelemetry/webjs\" | \"opentelemetry/java\" | \"nodejs\" | \"python\" | \"opentelemetry/cpp\" | \"opentelemetry/dotnet\" | \"opentelemetry/erlang\" | \"opentelemetry/go\" | \"opentelemetry/nodejs\" | \"opentelemetry/php\" | \"opentelemetry/python\" | \"opentelemetry/ruby\" | \"opentelemetry/rust\" | \"opentelemetry/swift\" | \"opentelemetry/android\"" + "\"java\" | \"ruby\" | \"go\" | \"dotnet\" | \"php\" | \"otlp\" | \"android/java\" | \"iOS/swift\" | \"rum-js\" | \"js-base\" | \"nodejs\" | \"python\" | \"opentelemetry/cpp\" | \"opentelemetry/dotnet\" | \"opentelemetry/erlang\" | \"opentelemetry/go\" | \"opentelemetry/java\" | \"opentelemetry/nodejs\" | \"opentelemetry/php\" | \"opentelemetry/python\" | \"opentelemetry/ruby\" | \"opentelemetry/rust\" | \"opentelemetry/swift\" | \"opentelemetry/android\" | \"opentelemetry/webjs\"" ], "path": "packages/kbn-elastic-agent-utils/src/agent_names.ts", "deprecated": false, @@ -544,7 +544,7 @@ "label": "OpenTelemetryAgentName", "description": [], "signature": [ - "\"otlp\" | \"opentelemetry/webjs\" | \"opentelemetry/java\" | \"opentelemetry/cpp\" | \"opentelemetry/dotnet\" | \"opentelemetry/erlang\" | \"opentelemetry/go\" | \"opentelemetry/nodejs\" | \"opentelemetry/php\" | \"opentelemetry/python\" | \"opentelemetry/ruby\" | \"opentelemetry/rust\" | \"opentelemetry/swift\" | \"opentelemetry/android\"" + "\"otlp\" | \"opentelemetry/cpp\" | \"opentelemetry/dotnet\" | \"opentelemetry/erlang\" | \"opentelemetry/go\" | \"opentelemetry/java\" | \"opentelemetry/nodejs\" | \"opentelemetry/php\" | \"opentelemetry/python\" | \"opentelemetry/ruby\" | \"opentelemetry/rust\" | \"opentelemetry/swift\" | \"opentelemetry/android\" | \"opentelemetry/webjs\"" ], "path": "packages/kbn-elastic-agent-utils/src/agent_names.ts", "deprecated": false, diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 278dcd540da9d..d8c6b738cf088 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index 9f2d48fb85bdf..b4d11b8b1d160 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.devdocs.json b/api_docs/kbn_elastic_assistant_common.devdocs.json index 7ba7ee84ca6e9..703a1f2b44cd4 100644 --- a/api_docs/kbn_elastic_assistant_common.devdocs.json +++ b/api_docs/kbn_elastic_assistant_common.devdocs.json @@ -4205,7 +4205,7 @@ "\nDefault features available to the elastic assistant" ], "signature": [ - "{ readonly assistantKnowledgeBaseByDefault: false; readonly assistantModelEvaluation: false; readonly assistantBedrockChat: false; }" + "{ readonly assistantKnowledgeBaseByDefault: false; readonly assistantModelEvaluation: false; readonly assistantBedrockChat: true; }" ], "path": "x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts", "deprecated": false, diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index d09ea433dfe84..7c1e63fb179d5 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; diff --git a/api_docs/kbn_entities_schema.devdocs.json b/api_docs/kbn_entities_schema.devdocs.json index f8a943faf01b5..5a095101476e4 100644 --- a/api_docs/kbn_entities_schema.devdocs.json +++ b/api_docs/kbn_entities_schema.devdocs.json @@ -18,7 +18,41 @@ }, "common": { "classes": [], - "functions": [], + "functions": [ + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.durationSchemaWithMinimum", + "type": "Function", + "tags": [], + "label": "durationSchemaWithMinimum", + "description": [], + "signature": [ + "(minimumMinutes: number) => Zod.ZodEffects" + ], + "path": "x-pack/packages/kbn-entities-schema/src/schema/common.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.durationSchemaWithMinimum.$1", + "type": "number", + "tags": [], + "label": "minimumMinutes", + "description": [], + "signature": [ + "number" + ], + "path": "x-pack/packages/kbn-entities-schema/src/schema/common.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [], "enums": [ { @@ -43,7 +77,7 @@ "label": "EntityDefinition", "description": [], "signature": [ - "{ id: string; type: string; version: string; name: string; history: { interval: moment.Duration; timestampField: string; settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: moment.Duration | undefined; backfillFrequency?: string | undefined; } | undefined; }; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: boolean; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + "{ id: string; type: string; version: string; name: string; history: { interval: string; settings: { lookbackPeriod: string; frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }; timestampField: string; }; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: boolean; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", { "pluginId": "@kbn/entities-schema", "scope": "common", @@ -236,7 +270,7 @@ "label": "durationSchema", "description": [], "signature": [ - "Zod.ZodEffects" + "Zod.ZodString" ], "path": "x-pack/packages/kbn-entities-schema/src/schema/common.ts", "deprecated": false, @@ -291,7 +325,7 @@ "section": "def-common.BasicAggregations", "text": "BasicAggregations" }, - "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }>, \"many\">>; staticFields: Zod.ZodOptional>; managed: Zod.ZodDefault>; history: Zod.ZodObject<{ timestampField: Zod.ZodString; interval: Zod.ZodEffects, moment.Duration, string>; settings: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; backfillSyncDelay: Zod.ZodOptional; backfillLookbackPeriod: Zod.ZodOptional>; backfillFrequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: moment.Duration | undefined; backfillFrequency?: string | undefined; }, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { interval: moment.Duration; timestampField: string; settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: moment.Duration | undefined; backfillFrequency?: string | undefined; } | undefined; }, { interval: string; timestampField: string; settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; } | undefined; }>; latest: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; }, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; } | undefined; }, { settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; } | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { id: string; type: string; version: string; name: string; history: { interval: moment.Duration; timestampField: string; settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: moment.Duration | undefined; backfillFrequency?: string | undefined; } | undefined; }; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: boolean; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }>, \"many\">>; staticFields: Zod.ZodOptional>; managed: Zod.ZodDefault>; history: Zod.ZodObject<{ timestampField: Zod.ZodString; interval: Zod.ZodEffects; settings: Zod.ZodEffects; syncDelay: Zod.ZodOptional; lookbackPeriod: Zod.ZodDefault>; frequency: Zod.ZodOptional; backfillSyncDelay: Zod.ZodOptional; backfillLookbackPeriod: Zod.ZodOptional; backfillFrequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { lookbackPeriod: string; frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; lookbackPeriod?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }>>, { lookbackPeriod: string; frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; lookbackPeriod?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; } | undefined>; }, \"strip\", Zod.ZodTypeAny, { interval: string; settings: { lookbackPeriod: string; frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }; timestampField: string; }, { interval: string; timestampField: string; settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; lookbackPeriod?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; } | undefined; }>; latest: Zod.ZodOptional; syncDelay: Zod.ZodOptional; frequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; }, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; } | undefined; }, { settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; } | undefined; }>>; }, \"strip\", Zod.ZodTypeAny, { id: string; type: string; version: string; name: string; history: { interval: string; settings: { lookbackPeriod: string; frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }; timestampField: string; }; managed: boolean; indexPatterns: string[]; identityFields: ({ field: string; optional: boolean; } | { field: string; optional: boolean; })[]; displayNameTemplate: string; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", { "pluginId": "@kbn/entities-schema", "scope": "common", @@ -299,7 +333,7 @@ "section": "def-common.BasicAggregations", "text": "BasicAggregations" }, - "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; metadata?: ({ destination: string; limit: number; source: string; } | { source: string; destination: string; limit: number; })[] | undefined; latest?: { settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; } | undefined; } | undefined; staticFields?: Record | undefined; }, { id: string; type: string; version: string; name: string; history: { interval: string; timestampField: string; settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; } | undefined; }; indexPatterns: string[]; identityFields: (string | { field: string; optional: boolean; })[]; displayNameTemplate: string; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", + "; filter?: string | undefined; } | { name: string; aggregation: \"doc_count\"; filter?: string | undefined; } | { name: string; field: string; percentile: number; aggregation: \"percentile\"; filter?: string | undefined; })[]; equation: string; }[] | undefined; metadata?: ({ destination: string; limit: number; source: string; } | { source: string; destination: string; limit: number; })[] | undefined; latest?: { settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; } | undefined; } | undefined; staticFields?: Record | undefined; }, { id: string; type: string; version: string; name: string; history: { interval: string; timestampField: string; settings?: { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; lookbackPeriod?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; } | undefined; }; indexPatterns: string[]; identityFields: (string | { field: string; optional: boolean; })[]; displayNameTemplate: string; filter?: string | undefined; description?: string | undefined; metrics?: { name: string; metrics: ({ name: string; field: string; aggregation: ", { "pluginId": "@kbn/entities-schema", "scope": "common", @@ -374,6 +408,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/entities-schema", + "id": "def-common.historySettingsSchema", + "type": "Object", + "tags": [], + "label": "historySettingsSchema", + "description": [], + "signature": [ + "Zod.ZodEffects; syncDelay: Zod.ZodOptional; lookbackPeriod: Zod.ZodDefault>; frequency: Zod.ZodOptional; backfillSyncDelay: Zod.ZodOptional; backfillLookbackPeriod: Zod.ZodOptional; backfillFrequency: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { lookbackPeriod: string; frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; lookbackPeriod?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }>>, { lookbackPeriod: string; frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; }, { frequency?: string | undefined; syncDelay?: string | undefined; syncField?: string | undefined; lookbackPeriod?: string | undefined; backfillSyncDelay?: string | undefined; backfillLookbackPeriod?: string | undefined; backfillFrequency?: string | undefined; } | undefined>" + ], + "path": "x-pack/packages/kbn-entities-schema/src/schema/common.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/entities-schema", "id": "def-common.identityFieldsSchema", diff --git a/api_docs/kbn_entities_schema.mdx b/api_docs/kbn_entities_schema.mdx index 7c817b8381226..c956702fbde14 100644 --- a/api_docs/kbn_entities_schema.mdx +++ b/api_docs/kbn_entities_schema.mdx @@ -8,26 +8,29 @@ slug: /kibana-dev-docs/api/kbn-entities-schema title: "@kbn/entities-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/entities-schema plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/entities-schema'] --- import kbnEntitiesSchemaObj from './kbn_entities_schema.devdocs.json'; -Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin. +Contact [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) for questions regarding this plugin. **Code health stats** | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 23 | 0 | 23 | 0 | +| 26 | 0 | 26 | 0 | ## Common ### Objects +### Functions + + ### Enums diff --git a/api_docs/kbn_es.devdocs.json b/api_docs/kbn_es.devdocs.json index 6fad581f61b92..96bcb65c45764 100644 --- a/api_docs/kbn_es.devdocs.json +++ b/api_docs/kbn_es.devdocs.json @@ -942,6 +942,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/es", + "id": "def-common.STATEFUL_ROLES_ROOT_PATH", + "type": "string", + "tags": [], + "label": "STATEFUL_ROLES_ROOT_PATH", + "description": [], + "path": "packages/kbn-es/src/paths.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/es", "id": "def-common.SYSTEM_INDICES_SUPERUSER", diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index a271f46def043..f82ac454c0687 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.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 | |-------------------|-----------|------------------------|-----------------| -| 54 | 0 | 39 | 7 | +| 55 | 0 | 40 | 7 | ## Common diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index fe3b02e1c0dc8..bea5acd36cb5b 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 39c3f2ec4badd..dbddfce2bc2c8 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 0c289846574ee..2598a5aa8ea82 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index d311d2550b77d..84215f9aa5dc6 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 21e23b12943a4..c91cffff4aa89 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_ast.devdocs.json b/api_docs/kbn_esql_ast.devdocs.json index 10c827a75121e..b4dd177a110e4 100644 --- a/api_docs/kbn_esql_ast.devdocs.json +++ b/api_docs/kbn_esql_ast.devdocs.json @@ -377,6 +377,254 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.find", + "type": "Function", + "tags": [], + "label": "find", + "description": [ + "\nFinds and returns the first node that matches the search criteria.\n" + ], + "signature": [ + "(node: ", + "WalkerAstNode", + ", predicate: (node: ", + "ESQLProperNode", + ") => boolean) => ", + "ESQLProperNode", + " | undefined" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.find.$1", + "type": "CompoundType", + "tags": [], + "label": "node", + "description": [ + "AST node to start the search from." + ], + "signature": [ + "WalkerAstNode" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.find.$2", + "type": "Function", + "tags": [], + "label": "predicate", + "description": [ + "A function that returns true if the node matches the search criteria." + ], + "signature": [ + "(node: ", + "ESQLProperNode", + ") => boolean" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "The first node that matches the search criteria." + ] + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.findAll", + "type": "Function", + "tags": [], + "label": "findAll", + "description": [ + "\nFinds and returns all nodes that match the search criteria.\n" + ], + "signature": [ + "(node: ", + "WalkerAstNode", + ", predicate: (node: ", + "ESQLProperNode", + ") => boolean) => ", + "ESQLProperNode", + "[]" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.findAll.$1", + "type": "CompoundType", + "tags": [], + "label": "node", + "description": [ + "AST node to start the search from." + ], + "signature": [ + "WalkerAstNode" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.findAll.$2", + "type": "Function", + "tags": [], + "label": "predicate", + "description": [ + "A function that returns true if the node matches the search criteria." + ], + "signature": [ + "(node: ", + "ESQLProperNode", + ") => boolean" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "All nodes that match the search criteria." + ] + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.match", + "type": "Function", + "tags": [], + "label": "match", + "description": [ + "\nMatches a single node against a template object. Returns the first node\nthat matches the template.\n" + ], + "signature": [ + "(node: ", + "WalkerAstNode", + ", template: ", + "NodeMatchTemplate", + ") => ", + "ESQLProperNode", + " | undefined" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.match.$1", + "type": "CompoundType", + "tags": [], + "label": "node", + "description": [ + "AST node to match against the template." + ], + "signature": [ + "WalkerAstNode" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.match.$2", + "type": "Object", + "tags": [], + "label": "template", + "description": [ + "Template object to match against the node." + ], + "signature": [ + "NodeMatchTemplate" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "The first node that matches the template" + ] + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.matchAll", + "type": "Function", + "tags": [], + "label": "matchAll", + "description": [ + "\nMatches all nodes against a template object. Returns all nodes that match\nthe template.\n" + ], + "signature": [ + "(node: ", + "WalkerAstNode", + ", template: ", + "NodeMatchTemplate", + ") => ", + "ESQLProperNode", + "[]" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.matchAll.$1", + "type": "CompoundType", + "tags": [], + "label": "node", + "description": [ + "AST node to match against the template." + ], + "signature": [ + "WalkerAstNode" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.Walker.matchAll.$2", + "type": "Object", + "tags": [], + "label": "template", + "description": [ + "Template object to match against the node." + ], + "signature": [ + "NodeMatchTemplate" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "All nodes that match the template" + ] + }, { "parentPluginId": "@kbn/esql-ast", "id": "def-common.Walker.findFunction", @@ -2555,6 +2803,44 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.WalkerOptions.visitAny", + "type": "Function", + "tags": [], + "label": "visitAny", + "description": [ + "\nCalled for any node type that does not have a specific visitor.\n" + ], + "signature": [ + "((node: ", + "ESQLProperNode", + ") => void) | undefined" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-ast", + "id": "def-common.WalkerOptions.visitAny.$1", + "type": "CompoundType", + "tags": [], + "label": "node", + "description": [ + "Any valid AST node." + ], + "signature": [ + "ESQLProperNode" + ], + "path": "packages/kbn-esql-ast/src/walker/walker.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false @@ -2746,7 +3032,9 @@ "label": "ESQLLiteral", "description": [], "signature": [ - "ESQLNumberLiteral", + "ESQLDecimalLiteral", + " | ", + "ESQLIntegerLiteral", " | ", "ESQLBooleanLiteral", " | ", diff --git a/api_docs/kbn_esql_ast.mdx b/api_docs/kbn_esql_ast.mdx index e502b160b9ff5..201fa9f458ed0 100644 --- a/api_docs/kbn_esql_ast.mdx +++ b/api_docs/kbn_esql_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-ast title: "@kbn/esql-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-ast plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-ast'] --- import kbnEsqlAstObj from './kbn_esql_ast.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 135 | 1 | 120 | 12 | +| 149 | 1 | 120 | 15 | ## Common diff --git a/api_docs/kbn_esql_utils.devdocs.json b/api_docs/kbn_esql_utils.devdocs.json index 6423998036e33..d8d67ceb112ba 100644 --- a/api_docs/kbn_esql_utils.devdocs.json +++ b/api_docs/kbn_esql_utils.devdocs.json @@ -1082,10 +1082,18 @@ "tags": [], "label": "getInitialESQLQuery", "description": [ - "\nBuilds an ES|QL query for the provided index or index pattern" + "\nBuilds an ES|QL query for the provided dataView\nIf there is @timestamp field in the index, we don't add the WHERE clause\nIf there is no @timestamp and there is a dataView timeFieldName, we add the WHERE clause with the timeFieldName" ], "signature": [ - "(indexOrIndexPattern: string) => string" + "(dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ") => string" ], "path": "packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts", "deprecated": false, @@ -1094,12 +1102,18 @@ { "parentPluginId": "@kbn/esql-utils", "id": "def-common.getInitialESQLQuery.$1", - "type": "string", + "type": "Object", "tags": [], - "label": "indexOrIndexPattern", + "label": "dataView", "description": [], "signature": [ - "string" + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + } ], "path": "packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts", "deprecated": false, @@ -1363,6 +1377,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/esql-utils", + "id": "def-common.FEEDBACK_LINK", + "type": "string", + "tags": [], + "label": "FEEDBACK_LINK", + "description": [], + "signature": [ + "\"https://ela.st/esql-feedback\"" + ], + "path": "packages/kbn-esql-utils/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/esql-utils", "id": "def-common.TextBasedLanguages", diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index bf4b7665b7797..80bfa62b3bad8 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 65 | 0 | 61 | 0 | +| 66 | 0 | 62 | 0 | ## Common diff --git a/api_docs/kbn_esql_validation_autocomplete.devdocs.json b/api_docs/kbn_esql_validation_autocomplete.devdocs.json index 7bf05bdd643e7..9c4af05a18969 100644 --- a/api_docs/kbn_esql_validation_autocomplete.devdocs.json +++ b/api_docs/kbn_esql_validation_autocomplete.devdocs.json @@ -660,7 +660,9 @@ " | ", "ESQLList", " | ", - "ESQLNumberLiteral", + "ESQLDecimalLiteral", + " | ", + "ESQLIntegerLiteral", " | ", "ESQLBooleanLiteral", " | ", @@ -778,7 +780,9 @@ " | ", "ESQLList", " | ", - "ESQLNumberLiteral", + "ESQLDecimalLiteral", + " | ", + "ESQLIntegerLiteral", " | ", "ESQLBooleanLiteral", " | ", @@ -880,7 +884,9 @@ " | ", "ESQLList", " | ", - "ESQLNumberLiteral", + "ESQLDecimalLiteral", + " | ", + "ESQLIntegerLiteral", " | ", "ESQLBooleanLiteral", " | ", @@ -990,7 +996,9 @@ " | ", "ESQLList", " | ", - "ESQLNumberLiteral", + "ESQLDecimalLiteral", + " | ", + "ESQLIntegerLiteral", " | ", "ESQLBooleanLiteral", " | ", @@ -3180,10 +3188,13 @@ { "parentPluginId": "@kbn/esql-validation-autocomplete", "id": "def-common.ESQLRealField.type", - "type": "string", + "type": "CompoundType", "tags": [], "label": "type", "description": [], + "signature": [ + "\"boolean\" | \"geo_point\" | \"geo_shape\" | \"ip\" | \"keyword\" | \"text\" | \"date\" | \"version\" | \"integer\" | \"long\" | \"double\" | \"unsigned_long\" | \"cartesian_point\" | \"cartesian_shape\" | \"counter_integer\" | \"counter_long\" | \"counter_double\" | \"unsupported\"" + ], "path": "packages/kbn-esql-validation-autocomplete/src/validation/types.ts", "deprecated": false, "trackAdoption": false @@ -3196,7 +3207,7 @@ "label": "metadata", "description": [], "signature": [ - "{ description?: string | undefined; type?: string | undefined; } | undefined" + "{ description?: string | undefined; } | undefined" ], "path": "packages/kbn-esql-validation-autocomplete/src/validation/types.ts", "deprecated": false, diff --git a/api_docs/kbn_esql_validation_autocomplete.mdx b/api_docs/kbn_esql_validation_autocomplete.mdx index 6aaab51bcdff2..4f283efcc130b 100644 --- a/api_docs/kbn_esql_validation_autocomplete.mdx +++ b/api_docs/kbn_esql_validation_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-validation-autocomplete title: "@kbn/esql-validation-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-validation-autocomplete plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-validation-autocomplete'] --- import kbnEsqlValidationAutocompleteObj from './kbn_esql_validation_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 0a48132690e74..1b5f813e453db 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_event_annotation_components.mdx index 48af2667571b7..c0765b814e931 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/event-annotation-components'] --- import kbnEventAnnotationComponentsObj from './kbn_event_annotation_components.devdocs.json'; diff --git a/api_docs/kbn_expandable_flyout.mdx b/api_docs/kbn_expandable_flyout.mdx index bfc01bbba333b..ed44a5ff7102e 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: 2024-07-31 +date: 2024-08-09 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 c7473b6186af5..d59599b42e91e 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_field_utils.mdx b/api_docs/kbn_field_utils.mdx index ef16b59aafc46..d157494cb0126 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-utils'] --- import kbnFieldUtilsObj from './kbn_field_utils.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index f1a04c25c8d04..b1054e0196c11 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: 2024-07-31 +date: 2024-08-09 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_formatters.mdx b/api_docs/kbn_formatters.mdx index 100c126e5fa13..b75017126c961 100644 --- a/api_docs/kbn_formatters.mdx +++ b/api_docs/kbn_formatters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-formatters title: "@kbn/formatters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/formatters plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/formatters'] --- import kbnFormattersObj from './kbn_formatters.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.devdocs.json b/api_docs/kbn_ftr_common_functional_services.devdocs.json index e9799fac98892..9c00accd18000 100644 --- a/api_docs/kbn_ftr_common_functional_services.devdocs.json +++ b/api_docs/kbn_ftr_common_functional_services.devdocs.json @@ -434,7 +434,64 @@ } ], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials", + "type": "Interface", + "tags": [], + "label": "RoleCredentials", + "description": [], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials.apiKey", + "type": "Object", + "tags": [], + "label": "apiKey", + "description": [], + "signature": [ + "{ id: string; name: string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials.apiKeyHeader", + "type": "Object", + "tags": [], + "label": "apiKeyHeader", + "description": [], + "signature": [ + "{ Authorization: string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.RoleCredentials.cookieHeader", + "type": "Object", + "tags": [], + "label": "cookieHeader", + "description": [], + "signature": [ + "{ Cookie: string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ { @@ -529,7 +586,61 @@ "section": "def-common.RetryService", "text": "RetryService" }, - "; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -579,13 +690,82 @@ "section": "def-common.RetryService", "text": "RetryService" }, - "; }>, ProvidedTypeMap<{}>>" + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" ], "path": "packages/kbn-ftr-common-functional-services/services/ftr_provider_context.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.InternalRequestHeader", + "type": "Type", + "tags": [], + "label": "InternalRequestHeader", + "description": [], + "signature": [ + "{ 'kbn-xsrf': string; } | { 'x-elastic-internal-origin': string; 'kbn-xsrf': string; }" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/ftr-common-functional-services", "id": "def-common.KibanaServer", @@ -606,6 +786,24 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.SupertestWithoutAuthProviderType", + "type": "Type", + "tags": [], + "label": "SupertestWithoutAuthProviderType", + "description": [], + "signature": [ + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>" + ], + "path": "packages/kbn-ftr-common-functional-services/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [ @@ -709,7 +907,7 @@ "section": "def-common.RetryService", "text": "RetryService" }, - "; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + "; supertestWithoutAuth: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -718,8 +916,10 @@ "text": "FtrProviderContext" }, ") => ", - "default", - "; kibanaServer: ({ getService }: ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -727,92 +927,41 @@ "section": "def-common.FtrProviderContext", "text": "FtrProviderContext" }, - ") => ", - { - "pluginId": "@kbn/test", - "scope": "common", - "docId": "kibKbnTestPluginApi", - "section": "def-common.KbnClient", - "text": "KbnClient" - }, - "; esArchiver: ({ getService }: ", + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", - "section": "def-common.FtrProviderContext", - "text": "FtrProviderContext" + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" }, - ") => ", + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", { - "pluginId": "@kbn/es-archiver", + "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", - "docId": "kibKbnEsArchiverPluginApi", - "section": "def-common.EsArchiver", - "text": "EsArchiver" + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" }, - "; retry: typeof ", + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", - "section": "def-common.RetryService", - "text": "RetryService" + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" }, - "; }>, ProvidedTypeMap<{}>>" - ], - "path": "packages/kbn-ftr-common-functional-services/services/es.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/ftr-common-functional-services", - "id": "def-common.services.kibanaServer", - "type": "Function", - "tags": [], - "label": "kibanaServer", - "description": [], - "signature": [ - "({ getService }: ", - { - "pluginId": "@kbn/ftr-common-functional-services", - "scope": "common", - "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", - "section": "def-common.FtrProviderContext", - "text": "FtrProviderContext" - }, - ") => ", - { - "pluginId": "@kbn/test", - "scope": "common", - "docId": "kibKbnTestPluginApi", - "section": "def-common.KbnClient", - "text": "KbnClient" - } - ], - "path": "packages/kbn-ftr-common-functional-services/services/all.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "@kbn/ftr-common-functional-services", - "id": "def-common.services.kibanaServer.$1", - "type": "Object", - "tags": [], - "label": "__0", - "description": [], - "signature": [ + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", { - "pluginId": "@kbn/test", + "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", - "docId": "kibKbnTestPluginApi", - "section": "def-common.GenericFtrProviderContext", - "text": "GenericFtrProviderContext" + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" }, - "<{ es: ({ getService }: ", + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -862,7 +1011,7 @@ "section": "def-common.RetryService", "text": "RetryService" }, - "; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + "; supertestWithoutAuth: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -871,8 +1020,10 @@ "text": "FtrProviderContext" }, ") => ", - "default", - "; kibanaServer: ({ getService }: ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -880,41 +1031,43 @@ "section": "def-common.FtrProviderContext", "text": "FtrProviderContext" }, - ") => ", + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", { - "pluginId": "@kbn/test", + "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", - "docId": "kibKbnTestPluginApi", - "section": "def-common.KbnClient", - "text": "KbnClient" + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" }, - "; esArchiver: ({ getService }: ", + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", - "section": "def-common.FtrProviderContext", - "text": "FtrProviderContext" + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" }, - ") => ", + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", { - "pluginId": "@kbn/es-archiver", + "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", - "docId": "kibKbnEsArchiverPluginApi", - "section": "def-common.EsArchiver", - "text": "EsArchiver" + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" }, - "; retry: typeof ", + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", - "section": "def-common.RetryService", - "text": "RetryService" + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" }, - "; }>, ProvidedTypeMap<{}>>" + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" ], - "path": "packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts", + "path": "packages/kbn-ftr-common-functional-services/services/es.ts", "deprecated": false, "trackAdoption": false } @@ -922,10 +1075,10 @@ }, { "parentPluginId": "@kbn/ftr-common-functional-services", - "id": "def-common.services.esArchiver", + "id": "def-common.services.kibanaServer", "type": "Function", "tags": [], - "label": "esArchiver", + "label": "kibanaServer", "description": [], "signature": [ "({ getService }: ", @@ -938,11 +1091,11 @@ }, ") => ", { - "pluginId": "@kbn/es-archiver", + "pluginId": "@kbn/test", "scope": "common", - "docId": "kibKbnEsArchiverPluginApi", - "section": "def-common.EsArchiver", - "text": "EsArchiver" + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" } ], "path": "packages/kbn-ftr-common-functional-services/services/all.ts", @@ -952,7 +1105,7 @@ "children": [ { "parentPluginId": "@kbn/ftr-common-functional-services", - "id": "def-common.services.esArchiver.$1", + "id": "def-common.services.kibanaServer.$1", "type": "Object", "tags": [], "label": "__0", @@ -1015,7 +1168,7 @@ "section": "def-common.RetryService", "text": "RetryService" }, - "; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + "; supertestWithoutAuth: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -1024,8 +1177,10 @@ "text": "FtrProviderContext" }, ") => ", - "default", - "; kibanaServer: ({ getService }: ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", @@ -1033,13 +1188,65 @@ "section": "def-common.FtrProviderContext", "text": "FtrProviderContext" }, - ") => ", + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", { - "pluginId": "@kbn/test", + "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", - "docId": "kibKbnTestPluginApi", - "section": "def-common.KbnClient", - "text": "KbnClient" + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" }, "; esArchiver: ({ getService }: ", { @@ -1065,9 +1272,63 @@ "section": "def-common.RetryService", "text": "RetryService" }, - "; }>, ProvidedTypeMap<{}>>" + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" ], - "path": "packages/kbn-ftr-common-functional-services/services/es_archiver.ts", + "path": "packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts", "deprecated": false, "trackAdoption": false } @@ -1075,24 +1336,831 @@ }, { "parentPluginId": "@kbn/ftr-common-functional-services", - "id": "def-common.services.retry", - "type": "Object", + "id": "def-common.services.esArchiver", + "type": "Function", "tags": [], - "label": "retry", + "label": "esArchiver", "description": [], "signature": [ - "typeof ", + "({ getService }: ", { "pluginId": "@kbn/ftr-common-functional-services", "scope": "common", "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", - "section": "def-common.RetryService", - "text": "RetryService" + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" } ], "path": "packages/kbn-ftr-common-functional-services/services/all.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.esArchiver.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.GenericFtrProviderContext", + "text": "GenericFtrProviderContext" + }, + "<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/es_archiver.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.retry", + "type": "Object", + "tags": [], + "label": "retry", + "description": [], + "signature": [ + "typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + } + ], + "path": "packages/kbn-ftr-common-functional-services/services/all.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.supertestWithoutAuth", + "type": "Function", + "tags": [], + "label": "supertestWithoutAuth", + "description": [], + "signature": [ + "({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/all.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.supertestWithoutAuth.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.GenericFtrProviderContext", + "text": "GenericFtrProviderContext" + }, + "<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/supertest_without_auth.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.samlAuth", + "type": "Function", + "tags": [], + "label": "samlAuth", + "description": [], + "signature": [ + "({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/all.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/ftr-common-functional-services", + "id": "def-common.services.samlAuth.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.GenericFtrProviderContext", + "text": "GenericFtrProviderContext" + }, + "<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }, {}, ProvidedTypeMap<{ es: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "default", + "; kibanaServer: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/test", + "scope": "common", + "docId": "kibKbnTestPluginApi", + "section": "def-common.KbnClient", + "text": "KbnClient" + }, + "; esArchiver: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + { + "pluginId": "@kbn/es-archiver", + "scope": "common", + "docId": "kibKbnEsArchiverPluginApi", + "section": "def-common.EsArchiver", + "text": "EsArchiver" + }, + "; retry: typeof ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RetryService", + "text": "RetryService" + }, + "; supertestWithoutAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => ", + "node_modules/@types/supertest/lib/agent", + "<", + "SuperTestStatic", + ".Test>; samlAuth: ({ getService }: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.FtrProviderContext", + "text": "FtrProviderContext" + }, + ") => Promise<{ getInteractiveUserSessionCookieWithRoleScope(role: string): Promise; getM2MApiCredentialsWithRoleScope(role: string): Promise<{ Cookie: string; }>; getEmail(role: string): Promise; getUserData(role: string): Promise<", + "UserProfile", + ">; createM2mApiKeyWithDefaultRoleScope(): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; createM2mApiKeyWithRoleScope(role: string): Promise<", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + ">; invalidateM2mApiKeyWithRoleScope(roleCredentials: ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.RoleCredentials", + "text": "RoleCredentials" + }, + "): Promise; getCommonRequestHeader(): { 'kbn-xsrf': string; }; getInternalRequestHeader(): ", + { + "pluginId": "@kbn/ftr-common-functional-services", + "scope": "common", + "docId": "kibKbnFtrCommonFunctionalServicesPluginApi", + "section": "def-common.InternalRequestHeader", + "text": "InternalRequestHeader" + }, + "; DEFAULT_ROLE: string; }>; }>, ProvidedTypeMap<{}>>" + ], + "path": "packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index de9c05b48031a..f359cb56062f5 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.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 | |-------------------|-----------|------------------------|-----------------| -| 36 | 0 | 21 | 1 | +| 46 | 0 | 31 | 1 | ## Common @@ -31,6 +31,9 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban ### Classes +### Interfaces + + ### Consts, variables and types diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index 3093134dc68ea..287394ae5d9f7 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index e65436c9b7199..0d2a6676daa23 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: 2024-07-31 +date: 2024-08-09 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 5faf8b64345da..f6affb2cc3901 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: 2024-07-31 +date: 2024-08-09 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 b70c342917440..e62ae05eb88e3 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_grouping.mdx b/api_docs/kbn_grouping.mdx index a5eb7dd72f378..f5e67e9e0056c 100644 --- a/api_docs/kbn_grouping.mdx +++ b/api_docs/kbn_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grouping title: "@kbn/grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grouping plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grouping'] --- import kbnGroupingObj from './kbn_grouping.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 6f7cbe11af0a2..56214b9a0a957 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: 2024-07-31 +date: 2024-08-09 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 b9c50d7e674cd..f4278b57dd23c 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: 2024-07-31 +date: 2024-08-09 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 29443897a35fd..3f325c3286ebf 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: 2024-07-31 +date: 2024-08-09 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 eb8ec77be9b0c..4f31cadb45514 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: 2024-07-31 +date: 2024-08-09 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 6b5d84df88f2a..0210717eca361 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: 2024-07-31 +date: 2024-08-09 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 fa228667a6e10..5537e3d5d8d35 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: 2024-07-31 +date: 2024-08-09 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 a836477cbf880..9fa7d2e5a5615 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: 2024-07-31 +date: 2024-08-09 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 7e83473ded804..954c8e0027282 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: 2024-07-31 +date: 2024-08-09 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 e10e6308767a6..af6d88c50c3eb 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_index_management.mdx b/api_docs/kbn_index_management.mdx index 78f667ca3b5aa..fd627fe773e53 100644 --- a/api_docs/kbn_index_management.mdx +++ b/api_docs/kbn_index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-management title: "@kbn/index-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-management plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-management'] --- import kbnIndexManagementObj from './kbn_index_management.devdocs.json'; diff --git a/api_docs/kbn_inference_integration_flyout.mdx b/api_docs/kbn_inference_integration_flyout.mdx index 0e5b996614bcb..4e8ce719ee1fa 100644 --- a/api_docs/kbn_inference_integration_flyout.mdx +++ b/api_docs/kbn_inference_integration_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference_integration_flyout title: "@kbn/inference_integration_flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference_integration_flyout plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference_integration_flyout'] --- import kbnInferenceIntegrationFlyoutObj from './kbn_inference_integration_flyout.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index 2f5713a560cb7..e4cbca9d3634b 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: 2024-07-31 +date: 2024-08-09 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 375b18041dee7..6534f38d91464 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: 2024-07-31 +date: 2024-08-09 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 19bad669a4907..5627d4ada06d0 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_ipynb.mdx b/api_docs/kbn_ipynb.mdx index c687476587f57..0f09a71e32498 100644 --- a/api_docs/kbn_ipynb.mdx +++ b/api_docs/kbn_ipynb.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ipynb title: "@kbn/ipynb" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ipynb plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ipynb'] --- import kbnIpynbObj from './kbn_ipynb.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index bc7ba4c9059d8..813674a4abc18 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: 2024-07-31 +date: 2024-08-09 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 fbb57a16110f7..bb6bb9e13ae39 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: 2024-07-31 +date: 2024-08-09 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 3fc4877551468..6cc8901f07644 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_json_schemas.mdx b/api_docs/kbn_json_schemas.mdx index b340ed96bc5f3..56f0c0274e5ea 100644 --- a/api_docs/kbn_json_schemas.mdx +++ b/api_docs/kbn_json_schemas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-schemas title: "@kbn/json-schemas" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-schemas plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-schemas'] --- import kbnJsonSchemasObj from './kbn_json_schemas.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 63b30528b6c16..f856ccd79cf0d 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_language_documentation_popover.devdocs.json index 19da7e82a44df..63df68fdd2a7c 100644 --- a/api_docs/kbn_language_documentation_popover.devdocs.json +++ b/api_docs/kbn_language_documentation_popover.devdocs.json @@ -27,7 +27,7 @@ "label": "LanguageDocumentationPopover", "description": [], "signature": [ - "React.NamedExoticComponent & { readonly type: ({ language, sections, buttonProps, searchInDescription, linkToDocumentation, }: DocumentationPopoverProps) => JSX.Element; }" + "React.NamedExoticComponent & { readonly type: ({ language, sections, buttonProps, searchInDescription, linkToDocumentation, isHelpMenuOpen, onHelpMenuVisibilityChange, }: DocumentationPopoverProps) => JSX.Element; }" ], "path": "packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx", "deprecated": false, diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 95737e90d1f4a..79fd39cb3b444 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_lens_embeddable_utils.mdx index 0947e84df8a5d..2eabd18413b80 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index c55d2157d3856..f20b453af5f0f 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 517e89359c988..d0b28f46bb67f 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: 2024-07-31 +date: 2024-08-09 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 be848204da64d..2bae0c016ebfb 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index 7e1b6808281f3..a0ef2c4d6316a 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 07e0b131490ad..3bfea5fc1db1e 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_management_cards_navigation.devdocs.json index 328c9ff782cc3..1b27bb9518cfd 100644 --- a/api_docs/kbn_management_cards_navigation.devdocs.json +++ b/api_docs/kbn_management_cards_navigation.devdocs.json @@ -145,7 +145,7 @@ "label": "hideLinksTo", "description": [], "signature": [ - "(\"transform\" | \"tags\" | \"maintenanceWindows\" | \"dataViews\" | \"settings\" | \"data_quality\" | \"filesManagement\" | \"roles\" | \"reporting\" | \"api_keys\" | \"index_management\" | \"ingest_pipelines\" | \"jobsListLink\" | \"objects\" | \"pipelines\" | \"triggersActions\" | \"triggersActionsConnectors\")[] | undefined" + "(\"transform\" | \"tags\" | \"maintenanceWindows\" | \"dataViews\" | \"spaces\" | \"settings\" | \"data_quality\" | \"filesManagement\" | \"roles\" | \"reporting\" | \"api_keys\" | \"index_management\" | \"ingest_pipelines\" | \"jobsListLink\" | \"objects\" | \"pipelines\" | \"triggersActions\" | \"triggersActionsConnectors\")[] | undefined" ], "path": "packages/kbn-management/cards_navigation/src/types.ts", "deprecated": false, @@ -202,7 +202,7 @@ "label": "AppId", "description": [], "signature": [ - "\"transform\" | \"tags\" | \"maintenanceWindows\" | \"dataViews\" | \"settings\" | \"data_quality\" | \"filesManagement\" | \"roles\" | \"reporting\" | \"api_keys\" | \"index_management\" | \"ingest_pipelines\" | \"jobsListLink\" | \"objects\" | \"pipelines\" | \"triggersActions\" | \"triggersActionsConnectors\"" + "\"transform\" | \"tags\" | \"maintenanceWindows\" | \"dataViews\" | \"spaces\" | \"settings\" | \"data_quality\" | \"filesManagement\" | \"roles\" | \"reporting\" | \"api_keys\" | \"index_management\" | \"ingest_pipelines\" | \"jobsListLink\" | \"objects\" | \"pipelines\" | \"triggersActions\" | \"triggersActionsConnectors\"" ], "path": "packages/kbn-management/cards_navigation/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_management_cards_navigation.mdx b/api_docs/kbn_management_cards_navigation.mdx index 4c7fdde6e16e8..a6a934fbec596 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: 2024-07-31 +date: 2024-08-09 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_application.mdx b/api_docs/kbn_management_settings_application.mdx index d329595d4105d..035dbfe2654df 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-application'] --- import kbnManagementSettingsApplicationObj from './kbn_management_settings_application.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_category.mdx b/api_docs/kbn_management_settings_components_field_category.mdx index aeced829bc8ef..1a1389ce7b568 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-category'] --- import kbnManagementSettingsComponentsFieldCategoryObj from './kbn_management_settings_components_field_category.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_input.mdx b/api_docs/kbn_management_settings_components_field_input.mdx index cf057f90d2e7f..4961ffd1147d7 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-input'] --- import kbnManagementSettingsComponentsFieldInputObj from './kbn_management_settings_components_field_input.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_field_row.mdx b/api_docs/kbn_management_settings_components_field_row.mdx index 324ca6a3720ce..0acaa705ce0e4 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-field-row'] --- import kbnManagementSettingsComponentsFieldRowObj from './kbn_management_settings_components_field_row.devdocs.json'; diff --git a/api_docs/kbn_management_settings_components_form.mdx b/api_docs/kbn_management_settings_components_form.mdx index 5545c7ed0a820..fb62c685dabdc 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-components-form'] --- import kbnManagementSettingsComponentsFormObj from './kbn_management_settings_components_form.devdocs.json'; diff --git a/api_docs/kbn_management_settings_field_definition.mdx b/api_docs/kbn_management_settings_field_definition.mdx index 88900122ca9bd..672a325bccfbb 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-field-definition'] --- import kbnManagementSettingsFieldDefinitionObj from './kbn_management_settings_field_definition.devdocs.json'; diff --git a/api_docs/kbn_management_settings_ids.devdocs.json b/api_docs/kbn_management_settings_ids.devdocs.json index 516e6a7f6bade..f5662d441bdc0 100644 --- a/api_docs/kbn_management_settings_ids.devdocs.json +++ b/api_docs/kbn_management_settings_ids.devdocs.json @@ -1072,6 +1072,66 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_AI_ASSISTANT_LOGS_INDEX_PATTERN_ID", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_AI_ASSISTANT_LOGS_INDEX_PATTERN_ID", + "description": [], + "signature": [ + "\"observability:aiAssistantLogsIndexPattern\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_AI_ASSISTANT_RESPONSE_LANGUAGE", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_AI_ASSISTANT_RESPONSE_LANGUAGE", + "description": [], + "signature": [ + "\"observability:aiAssistantResponseLanguage\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_AI_ASSISTANT_SEARCH_CONNECTOR_INDEX_PATTERN", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_AI_ASSISTANT_SEARCH_CONNECTOR_INDEX_PATTERN", + "description": [], + "signature": [ + "\"observability:aiAssistantSearchConnectorIndexPattern\"" + ], + "path": "packages/kbn-management/settings/setting_ids/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/management-settings-ids", + "id": "def-common.OBSERVABILITY_AI_ASSISTANT_SIMULATED_FUNCTION_CALLING", + "type": "string", + "tags": [], + "label": "OBSERVABILITY_AI_ASSISTANT_SIMULATED_FUNCTION_CALLING", + "description": [], + "signature": [ + "\"observability:aiAssistantSimulatedFunctionCalling\"" + ], + "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", diff --git a/api_docs/kbn_management_settings_ids.mdx b/api_docs/kbn_management_settings_ids.mdx index 0f93901a5854a..d0115078e8869 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-ids'] --- import kbnManagementSettingsIdsObj from './kbn_management_settings_ids.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 139 | 0 | 137 | 0 | +| 143 | 0 | 141 | 0 | ## Common diff --git a/api_docs/kbn_management_settings_section_registry.mdx b/api_docs/kbn_management_settings_section_registry.mdx index 159bac4f7118d..23b153a08a3bc 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_management_settings_types.mdx index ec11d18de12f7..c7f57749ad627 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-types'] --- import kbnManagementSettingsTypesObj from './kbn_management_settings_types.devdocs.json'; diff --git a/api_docs/kbn_management_settings_utilities.mdx b/api_docs/kbn_management_settings_utilities.mdx index 0f0e12cc8fbb0..4d309d29a5eca 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/management-settings-utilities'] --- import kbnManagementSettingsUtilitiesObj from './kbn_management_settings_utilities.devdocs.json'; diff --git a/api_docs/kbn_management_storybook_config.mdx b/api_docs/kbn_management_storybook_config.mdx index 07ecec37d32e0..b77b2d65316a3 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: 2024-07-31 +date: 2024-08-09 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 36bdad273646b..d9956bfcba7c4 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: 2024-07-31 +date: 2024-08-09 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 1aed3d15aa103..73b006dbaaf0a 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_ml_agg_utils.devdocs.json index f36963ad8bd0c..3e2c306db4d73 100644 --- a/api_docs/kbn_ml_agg_utils.devdocs.json +++ b/api_docs/kbn_ml_agg_utils.devdocs.json @@ -408,7 +408,46 @@ } ], "returnComment": [ - "whether arg is of type SignificantItem" + "Return whether arg is of type SignificantItem" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.isSignificantItemGroup", + "type": "Function", + "tags": [], + "label": "isSignificantItemGroup", + "description": [ + "\nType guard to check if the given argument is a SignificantItemGroup." + ], + "signature": [ + "(arg: unknown) => boolean" + ], + "path": "x-pack/packages/ml/agg_utils/src/type_guards.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.isSignificantItemGroup.$1", + "type": "Unknown", + "tags": [], + "label": "arg", + "description": [ + "The unknown type to be evaluated" + ], + "signature": [ + "unknown" + ], + "path": "x-pack/packages/ml/agg_utils/src/type_guards.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "Return whether arg is of type SignificantItemGroup" ], "initialIsOpen": false }, @@ -2080,7 +2119,14 @@ "\nAn array of data points, each represented by a NumericDataItem." ], "signature": [ - "NumericDataItem[]" + { + "pluginId": "@kbn/ml-agg-utils", + "scope": "common", + "docId": "kibKbnMlAggUtilsPluginApi", + "section": "def-common.NumericDataItem", + "text": "NumericDataItem" + }, + "[]" ], "path": "x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts", "deprecated": false, @@ -2204,6 +2250,66 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.NumericDataItem", + "type": "Interface", + "tags": [ + "interface" + ], + "label": "NumericDataItem", + "description": [ + "\nRepresents an item in numeric data." + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.NumericDataItem.key", + "type": "number", + "tags": [], + "label": "key", + "description": [ + "\nThe numeric key." + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.NumericDataItem.key_as_string", + "type": "string", + "tags": [], + "label": "key_as_string", + "description": [ + "\nAn optional string representation of the key." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.NumericDataItem.doc_count", + "type": "number", + "tags": [], + "label": "doc_count", + "description": [ + "\nThe document count associated with the key." + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/ml-agg-utils", "id": "def-common.NumericHistogramField", @@ -2792,6 +2898,45 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.SignificantItemHistogramItem", + "type": "Interface", + "tags": [], + "label": "SignificantItemHistogramItem", + "description": [ + "\nRepresents a data item in a significant term histogram." + ], + "signature": [ + { + "pluginId": "@kbn/ml-agg-utils", + "scope": "common", + "docId": "kibKbnMlAggUtilsPluginApi", + "section": "def-common.SignificantItemHistogramItem", + "text": "SignificantItemHistogramItem" + }, + " extends SignificantItemHistogramItemBase" + ], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-common.SignificantItemHistogramItem.doc_count_significant_item", + "type": "number", + "tags": [], + "label": "doc_count_significant_item", + "description": [ + "The document count for this histogram item in the significant item context." + ], + "path": "x-pack/packages/ml/agg_utils/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "enums": [ @@ -2878,23 +3023,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/ml-agg-utils", - "id": "def-common.SignificantItemHistogramItem", - "type": "Type", - "tags": [], - "label": "SignificantItemHistogramItem", - "description": [ - "\nRepresents a data item in a significant term histogram." - ], - "signature": [ - "SignificantItemHistogramItemV1 | SignificantItemHistogramItemV2" - ], - "path": "x-pack/packages/ml/agg_utils/src/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/ml-agg-utils", "id": "def-common.SignificantItemType", diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index f111626328740..9d5d73a033509 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.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 | |-------------------|-----------|------------------------|-----------------| -| 90 | 1 | 0 | 0 | +| 97 | 1 | 0 | 0 | ## Common diff --git a/api_docs/kbn_ml_anomaly_utils.mdx b/api_docs/kbn_ml_anomaly_utils.mdx index 07ca3a4cd8bc0..dd84ae65b9b61 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: 2024-07-31 +date: 2024-08-09 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_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index 63f8011ddd6df..0a07c49755b54 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index 7bef7dcbd56cd..2197d32529433 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: 2024-07-31 +date: 2024-08-09 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_chi2test.mdx b/api_docs/kbn_ml_chi2test.mdx index 5a3c2b9acb8f9..87db95be070b7 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-chi2test'] --- import kbnMlChi2testObj from './kbn_ml_chi2test.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 c1308977a1a5f..b8cd7147e4989 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: 2024-07-31 +date: 2024-08-09 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 d4294b7a56337..596a091e86cfd 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_ml_date_picker.mdx index ad669b61d48c9..880f4c21d3b43 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_date_utils.mdx b/api_docs/kbn_ml_date_utils.mdx index 8c44aceb446f9..208cc3e39e4bc 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: 2024-07-31 +date: 2024-08-09 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 1440e21ad6309..a7305f8a4c592 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: 2024-07-31 +date: 2024-08-09 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 4b81f36a8ae27..1a12e22154970 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: 2024-07-31 +date: 2024-08-09 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 f1e03f2258d67..fc6ed63a6822a 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: 2024-07-31 +date: 2024-08-09 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 044db1fca16c7..d02563e7e27d8 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: 2024-07-31 +date: 2024-08-09 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 91bfac7e5e409..704057c951f3c 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: 2024-07-31 +date: 2024-08-09 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 4e45555b68565..b87112e26c0d3 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: 2024-07-31 +date: 2024-08-09 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 8eb0bb825352a..a301295da765c 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: 2024-07-31 +date: 2024-08-09 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 d4484218862ae..235dfb0d31f79 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: 2024-07-31 +date: 2024-08-09 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 fc667de3cb96e..551ea1dc6caed 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: 2024-07-31 +date: 2024-08-09 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 9a64aa0240097..e5565078c25a4 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: 2024-07-31 +date: 2024-08-09 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 7495d7f88b6a5..6d77cf685cdcf 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: 2024-07-31 +date: 2024-08-09 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 c1aa39132a3c5..a9960cfc51f99 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: 2024-07-31 +date: 2024-08-09 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 eb591caba86f0..3f469db713a08 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: 2024-07-31 +date: 2024-08-09 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_time_buckets.mdx b/api_docs/kbn_ml_time_buckets.mdx index c8877f11557ec..dfec863c7b045 100644 --- a/api_docs/kbn_ml_time_buckets.mdx +++ b/api_docs/kbn_ml_time_buckets.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-time-buckets title: "@kbn/ml-time-buckets" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-time-buckets plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-time-buckets'] --- import kbnMlTimeBucketsObj from './kbn_ml_time_buckets.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.devdocs.json b/api_docs/kbn_ml_trained_models_utils.devdocs.json index ae77700bbd77e..29e317f3d5c28 100644 --- a/api_docs/kbn_ml_trained_models_utils.devdocs.json +++ b/api_docs/kbn_ml_trained_models_utils.devdocs.json @@ -178,6 +178,19 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/ml-trained-models-utils", + "id": "def-common.ModelDefinition.supported", + "type": "boolean", + "tags": [], + "label": "supported", + "description": [ + "Indicates if model version is supported by the cluster" + ], + "path": "x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/ml-trained-models-utils", "id": "def-common.ModelDefinition.hidden", @@ -619,7 +632,7 @@ "label": "ELASTIC_MODEL_DEFINITIONS", "description": [], "signature": [ - "{ [x: string]: ", + "{ [x: string]: Omit<", { "pluginId": "@kbn/ml-trained-models-utils", "scope": "common", @@ -627,7 +640,7 @@ "section": "def-common.ModelDefinition", "text": "ModelDefinition" }, - "; }" + ", \"supported\">; }" ], "path": "x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts", "deprecated": false, diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 9e319294ce668..8ca410ad45524 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.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 | |-------------------|-----------|------------------------|-----------------| -| 43 | 0 | 38 | 1 | +| 44 | 0 | 38 | 1 | ## Common diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 6b4b3598b9042..e3a889ecac84f 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index d6285e2d73708..18e180b9a4d9a 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index cd97aa589cf1c..f642a52cec5f7 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 22cf7b21798a8..3bf1a19614a44 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_object_versioning.mdx b/api_docs/kbn_object_versioning.mdx index 35540943db54e..b30a3e4e8277e 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: 2024-07-31 +date: 2024-08-09 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 7e78fae26a19e..f2723603a38cd 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alert-details'] --- import kbnObservabilityAlertDetailsObj from './kbn_observability_alert_details.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_rule_utils.mdx b/api_docs/kbn_observability_alerting_rule_utils.mdx index cdb6f37acbde2..5b40d0a795928 100644 --- a/api_docs/kbn_observability_alerting_rule_utils.mdx +++ b/api_docs/kbn_observability_alerting_rule_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-rule-utils title: "@kbn/observability-alerting-rule-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-rule-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-rule-utils'] --- import kbnObservabilityAlertingRuleUtilsObj from './kbn_observability_alerting_rule_utils.devdocs.json'; diff --git a/api_docs/kbn_observability_alerting_test_data.mdx b/api_docs/kbn_observability_alerting_test_data.mdx index 62715488ea384..5b24aba396313 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index fad3b2e3c122a..494ee2cd786d3 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.devdocs.json b/api_docs/kbn_openapi_bundler.devdocs.json index dd50a60833f72..9d1ff6d58ae1a 100644 --- a/api_docs/kbn_openapi_bundler.devdocs.json +++ b/api_docs/kbn_openapi_bundler.devdocs.json @@ -46,7 +46,7 @@ "id": "def-common.bundle.$1", "type": "Object", "tags": [], - "label": "{\n sourceGlob,\n outputFilePath = 'bundled-{version}.schema.yaml',\n options,\n}", + "label": "{\n sourceGlob,\n outputFilePath = 'bundled_{version}.schema.yaml',\n options,\n}", "description": [], "signature": [ { @@ -209,9 +209,7 @@ "label": "options", "description": [], "signature": [ - "{ mergedSpecInfo?: Partial<", - "OpenAPIV3", - ".InfoObject> | undefined; } | undefined" + "MergerOptions | undefined" ], "path": "packages/kbn-openapi-bundler/src/openapi_merger.ts", "deprecated": false, diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index 8a121034c3140..adfbc760f9616 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index ce0bd2c505dac..062b0f833553d 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-generator'] --- import kbnOpenapiGeneratorObj from './kbn_openapi_generator.devdocs.json'; diff --git a/api_docs/kbn_optimizer.devdocs.json b/api_docs/kbn_optimizer.devdocs.json index c24d3b2e064e2..82de9992c06d6 100644 --- a/api_docs/kbn_optimizer.devdocs.json +++ b/api_docs/kbn_optimizer.devdocs.json @@ -9,18 +9,10 @@ "objects": [] }, "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { "classes": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig", + "id": "def-server.OptimizerConfig", "type": "Class", "tags": [], "label": "OptimizerConfig", @@ -31,7 +23,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.parseOptions", + "id": "def-server.OptimizerConfig.parseOptions", "type": "Function", "tags": [], "label": "parseOptions", @@ -46,7 +38,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.parseOptions.$1", + "id": "def-server.OptimizerConfig.parseOptions.$1", "type": "Object", "tags": [], "label": "options", @@ -64,7 +56,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.create", + "id": "def-server.OptimizerConfig.create", "type": "Function", "tags": [], "label": "create", @@ -73,9 +65,9 @@ "(inputOptions: Options) => ", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" } ], @@ -85,7 +77,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.create.$1", + "id": "def-server.OptimizerConfig.create.$1", "type": "Object", "tags": [], "label": "inputOptions", @@ -103,7 +95,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed", + "id": "def-server.OptimizerConfig.Unnamed", "type": "Function", "tags": [], "label": "Constructor", @@ -117,7 +109,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$1", + "id": "def-server.OptimizerConfig.Unnamed.$1", "type": "Array", "tags": [], "label": "bundles", @@ -133,7 +125,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$2", + "id": "def-server.OptimizerConfig.Unnamed.$2", "type": "Array", "tags": [], "label": "filteredBundles", @@ -149,7 +141,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$3", + "id": "def-server.OptimizerConfig.Unnamed.$3", "type": "boolean", "tags": [], "label": "cache", @@ -164,7 +156,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$4", + "id": "def-server.OptimizerConfig.Unnamed.$4", "type": "boolean", "tags": [], "label": "watch", @@ -179,7 +171,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$5", + "id": "def-server.OptimizerConfig.Unnamed.$5", "type": "boolean", "tags": [], "label": "inspectWorkers", @@ -194,7 +186,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$6", + "id": "def-server.OptimizerConfig.Unnamed.$6", "type": "Array", "tags": [], "label": "plugins", @@ -210,7 +202,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$7", + "id": "def-server.OptimizerConfig.Unnamed.$7", "type": "string", "tags": [], "label": "repoRoot", @@ -225,7 +217,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$8", + "id": "def-server.OptimizerConfig.Unnamed.$8", "type": "number", "tags": [], "label": "maxWorkerCount", @@ -240,7 +232,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$9", + "id": "def-server.OptimizerConfig.Unnamed.$9", "type": "boolean", "tags": [], "label": "dist", @@ -255,7 +247,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$10", + "id": "def-server.OptimizerConfig.Unnamed.$10", "type": "boolean", "tags": [], "label": "profileWebpack", @@ -270,7 +262,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.Unnamed.$11", + "id": "def-server.OptimizerConfig.Unnamed.$11", "type": "Object", "tags": [], "label": "themeTags", @@ -288,7 +280,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.getWorkerConfig", + "id": "def-server.OptimizerConfig.getWorkerConfig", "type": "Function", "tags": [], "label": "getWorkerConfig", @@ -303,7 +295,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.getWorkerConfig.$1", + "id": "def-server.OptimizerConfig.getWorkerConfig.$1", "type": "Unknown", "tags": [], "label": "optimizerCacheKey", @@ -321,7 +313,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerConfig.getCacheableWorkerConfig", + "id": "def-server.OptimizerConfig.getCacheableWorkerConfig", "type": "Function", "tags": [], "label": "getCacheableWorkerConfig", @@ -343,7 +335,7 @@ "functions": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.logOptimizerProgress", + "id": "def-server.logOptimizerProgress", "type": "Function", "tags": [], "label": "logOptimizerProgress", @@ -362,9 +354,9 @@ "<", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerUpdate", + "section": "def-server.OptimizerUpdate", "text": "OptimizerUpdate" }, ">" @@ -375,7 +367,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.logOptimizerProgress.$1", + "id": "def-server.logOptimizerProgress.$1", "type": "Object", "tags": [], "label": "log", @@ -400,7 +392,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.logOptimizerState", + "id": "def-server.logOptimizerState", "type": "Function", "tags": [], "label": "logOptimizerState", @@ -417,25 +409,25 @@ ", config: ", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" }, ") => Operator<", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerUpdate", + "section": "def-server.OptimizerUpdate", "text": "OptimizerUpdate" }, ", ", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerUpdate", + "section": "def-server.OptimizerUpdate", "text": "OptimizerUpdate" }, ">" @@ -446,7 +438,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.logOptimizerState.$1", + "id": "def-server.logOptimizerState.$1", "type": "Object", "tags": [], "label": "log", @@ -467,7 +459,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.logOptimizerState.$2", + "id": "def-server.logOptimizerState.$2", "type": "Object", "tags": [], "label": "config", @@ -475,9 +467,9 @@ "signature": [ { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" } ], @@ -492,7 +484,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.readLimits", + "id": "def-server.readLimits", "type": "Function", "tags": [], "label": "readLimits", @@ -507,7 +499,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.readLimits.$1", + "id": "def-server.readLimits.$1", "type": "string", "tags": [], "label": "path", @@ -526,7 +518,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.reportOptimizerTimings", + "id": "def-server.reportOptimizerTimings", "type": "Function", "tags": [], "label": "reportOptimizerTimings", @@ -543,25 +535,25 @@ ", config: ", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" }, ") => Operator<", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerUpdate", + "section": "def-server.OptimizerUpdate", "text": "OptimizerUpdate" }, ", ", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerUpdate", + "section": "def-server.OptimizerUpdate", "text": "OptimizerUpdate" }, ">" @@ -572,7 +564,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.reportOptimizerTimings.$1", + "id": "def-server.reportOptimizerTimings.$1", "type": "Object", "tags": [], "label": "log", @@ -593,7 +585,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.reportOptimizerTimings.$2", + "id": "def-server.reportOptimizerTimings.$2", "type": "Object", "tags": [], "label": "config", @@ -601,9 +593,9 @@ "signature": [ { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" } ], @@ -618,7 +610,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.runFindBabelHelpersInEntryBundlesCli", + "id": "def-server.runFindBabelHelpersInEntryBundlesCli", "type": "Function", "tags": [], "label": "runFindBabelHelpersInEntryBundlesCli", @@ -635,7 +627,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.runFindNodeLibsBrowserPolyfillsInEntryBundlesCli", + "id": "def-server.runFindNodeLibsBrowserPolyfillsInEntryBundlesCli", "type": "Function", "tags": [], "label": "runFindNodeLibsBrowserPolyfillsInEntryBundlesCli", @@ -652,7 +644,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.runKbnOptimizerCli", + "id": "def-server.runKbnOptimizerCli", "type": "Function", "tags": [], "label": "runKbnOptimizerCli", @@ -666,7 +658,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.runKbnOptimizerCli.$1", + "id": "def-server.runKbnOptimizerCli.$1", "type": "Object", "tags": [], "label": "options", @@ -677,7 +669,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.runKbnOptimizerCli.$1.defaultLimitsPath", + "id": "def-server.runKbnOptimizerCli.$1.defaultLimitsPath", "type": "string", "tags": [], "label": "defaultLimitsPath", @@ -694,7 +686,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.runOptimizer", + "id": "def-server.runOptimizer", "type": "Function", "tags": [], "label": "runOptimizer", @@ -703,9 +695,9 @@ "(config: ", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" }, ") => ", @@ -713,9 +705,9 @@ "<", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerUpdate", + "section": "def-server.OptimizerUpdate", "text": "OptimizerUpdate" }, ">" @@ -726,7 +718,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.runOptimizer.$1", + "id": "def-server.runOptimizer.$1", "type": "Object", "tags": [], "label": "config", @@ -734,9 +726,9 @@ "signature": [ { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" } ], @@ -751,7 +743,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.updateBundleLimits", + "id": "def-server.updateBundleLimits", "type": "Function", "tags": [], "label": "updateBundleLimits", @@ -765,7 +757,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.updateBundleLimits.$1", + "id": "def-server.updateBundleLimits.$1", "type": "Object", "tags": [], "label": "{\n log,\n config,\n dropMissing,\n limitsPath,\n}", @@ -784,7 +776,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.validateLimitsForAllBundles", + "id": "def-server.validateLimitsForAllBundles", "type": "Function", "tags": [], "label": "validateLimitsForAllBundles", @@ -801,9 +793,9 @@ ", config: ", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" }, ", limitsPath: string) => void" @@ -814,7 +806,7 @@ "children": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.validateLimitsForAllBundles.$1", + "id": "def-server.validateLimitsForAllBundles.$1", "type": "Object", "tags": [], "label": "log", @@ -835,7 +827,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.validateLimitsForAllBundles.$2", + "id": "def-server.validateLimitsForAllBundles.$2", "type": "Object", "tags": [], "label": "config", @@ -843,9 +835,9 @@ "signature": [ { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerConfig", + "section": "def-server.OptimizerConfig", "text": "OptimizerConfig" } ], @@ -856,7 +848,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.validateLimitsForAllBundles.$3", + "id": "def-server.validateLimitsForAllBundles.$3", "type": "string", "tags": [], "label": "limitsPath", @@ -879,7 +871,7 @@ "misc": [ { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerUpdate", + "id": "def-server.OptimizerUpdate", "type": "Type", "tags": [], "label": "OptimizerUpdate", @@ -899,7 +891,7 @@ }, { "parentPluginId": "@kbn/optimizer", - "id": "def-common.OptimizerUpdate$", + "id": "def-server.OptimizerUpdate$", "type": "Type", "tags": [], "label": "OptimizerUpdate$", @@ -909,9 +901,9 @@ "<", { "pluginId": "@kbn/optimizer", - "scope": "common", + "scope": "server", "docId": "kibKbnOptimizerPluginApi", - "section": "def-common.OptimizerUpdate", + "section": "def-server.OptimizerUpdate", "text": "OptimizerUpdate" }, ">" @@ -923,5 +915,13 @@ } ], "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 88891905f3227..1d98c03234012 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; @@ -23,14 +23,14 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban |-------------------|-----------|------------------------|-----------------| | 45 | 0 | 45 | 10 | -## Common +## Server ### Functions - + ### Classes - + ### Consts, variables and types - + diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 13dc97e915ffb..637e067a45759 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: 2024-07-31 +date: 2024-08-09 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 afa97bbb88cd7..6a1c68996bf35 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: 2024-07-31 +date: 2024-08-09 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_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index f27e8c87612c8..bf1ee7759d94d 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 972c14dbf3bcf..889a9523193d0 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: 2024-07-31 +date: 2024-08-09 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_check.devdocs.json b/api_docs/kbn_plugin_check.devdocs.json index 28ee612a740a9..794be230733e3 100644 --- a/api_docs/kbn_plugin_check.devdocs.json +++ b/api_docs/kbn_plugin_check.devdocs.json @@ -9,19 +9,11 @@ "objects": [] }, "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { "classes": [], "functions": [ { "parentPluginId": "@kbn/plugin-check", - "id": "def-common.runPluginCheckCli", + "id": "def-server.runPluginCheckCli", "type": "Function", "tags": [], "label": "runPluginCheckCli", @@ -43,5 +35,13 @@ "enums": [], "misc": [], "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_plugin_check.mdx b/api_docs/kbn_plugin_check.mdx index d38e52315ac85..35306a7857640 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; @@ -23,8 +23,8 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh |-------------------|-----------|------------------------|-----------------| | 1 | 0 | 0 | 0 | -## Common +## Server ### Functions - + diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 44029ed794bd4..db1c6f856552e 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.devdocs.json b/api_docs/kbn_plugin_helpers.devdocs.json index 29443ce01ee1c..dd3add5884bf5 100644 --- a/api_docs/kbn_plugin_helpers.devdocs.json +++ b/api_docs/kbn_plugin_helpers.devdocs.json @@ -9,19 +9,11 @@ "objects": [] }, "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [] - }, - "common": { "classes": [], "functions": [ { "parentPluginId": "@kbn/plugin-helpers", - "id": "def-common.runCli", + "id": "def-server.runCli", "type": "Function", "tags": [], "label": "runCli", @@ -41,5 +33,13 @@ "enums": [], "misc": [], "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index cb8e508211b59..a33156ba9da50 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; @@ -23,8 +23,8 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban |-------------------|-----------|------------------------|-----------------| | 1 | 0 | 1 | 0 | -## Common +## Server ### Functions - + diff --git a/api_docs/kbn_presentation_containers.devdocs.json b/api_docs/kbn_presentation_containers.devdocs.json index 0ab83e67e79ef..d3adf3e03080d 100644 --- a/api_docs/kbn_presentation_containers.devdocs.json +++ b/api_docs/kbn_presentation_containers.devdocs.json @@ -368,6 +368,58 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/presentation-containers", + "id": "def-public.childrenUnsavedChanges$", + "type": "Function", + "tags": [], + "label": "childrenUnsavedChanges$", + "description": [ + "\n Create an observable stream of unsaved changes from all react embeddable children" + ], + "signature": [ + "(children$: ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "public", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-public.PublishingSubject", + "text": "PublishingSubject" + }, + "<{ [key: string]: unknown; }>) => ", + "Observable", + "<{ [key: string]: object; } | undefined>" + ], + "path": "packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/presentation-containers", + "id": "def-public.childrenUnsavedChanges$.$1", + "type": "Object", + "tags": [], + "label": "children$", + "description": [], + "signature": [ + { + "pluginId": "@kbn/presentation-publishing", + "scope": "public", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-public.PublishingSubject", + "text": "PublishingSubject" + }, + "<{ [key: string]: unknown; }>" + ], + "path": "packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/presentation-containers", "id": "def-public.combineCompatibleChildrenApis", @@ -504,6 +556,100 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/presentation-containers", + "id": "def-public.initializeUnsavedChanges", + "type": "Function", + "tags": [], + "label": "initializeUnsavedChanges", + "description": [], + "signature": [ + "(initialLastSavedState: RuntimeState, parentApi: unknown, comparators: ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "public", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-public.StateComparators", + "text": "StateComparators" + }, + ") => { api: ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "public", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-public.PublishesUnsavedChanges", + "text": "PublishesUnsavedChanges" + }, + " & ", + { + "pluginId": "@kbn/presentation-containers", + "scope": "public", + "docId": "kibKbnPresentationContainersPluginApi", + "section": "def-public.HasSnapshottableState", + "text": "HasSnapshottableState" + }, + "; cleanup: () => void; }" + ], + "path": "packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/presentation-containers", + "id": "def-public.initializeUnsavedChanges.$1", + "type": "Uncategorized", + "tags": [], + "label": "initialLastSavedState", + "description": [], + "signature": [ + "RuntimeState" + ], + "path": "packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/presentation-containers", + "id": "def-public.initializeUnsavedChanges.$2", + "type": "Unknown", + "tags": [], + "label": "parentApi", + "description": [], + "signature": [ + "unknown" + ], + "path": "packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/presentation-containers", + "id": "def-public.initializeUnsavedChanges.$3", + "type": "Object", + "tags": [], + "label": "comparators", + "description": [], + "signature": [ + { + "pluginId": "@kbn/presentation-publishing", + "scope": "public", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-public.StateComparators", + "text": "StateComparators" + }, + "" + ], + "path": "packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/presentation-containers", "id": "def-public.listenForCompatibleApi", diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index 6217bb699b25c..2b340ad57b80a 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.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 | |-------------------|-----------|------------------------|-----------------| -| 82 | 0 | 71 | 0 | +| 88 | 0 | 76 | 0 | ## Client diff --git a/api_docs/kbn_presentation_publishing.devdocs.json b/api_docs/kbn_presentation_publishing.devdocs.json index 9fe667edc9f38..14d724b3408b0 100644 --- a/api_docs/kbn_presentation_publishing.devdocs.json +++ b/api_docs/kbn_presentation_publishing.devdocs.json @@ -1067,7 +1067,8 @@ "docId": "kibKbnPresentationPublishingPluginApi", "section": "def-public.PublishesUnsavedChanges", "text": "PublishesUnsavedChanges" - } + }, + "" ], "path": "packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts", "deprecated": false, @@ -6565,6 +6566,16 @@ "tags": [], "label": "PublishesUnsavedChanges", "description": [], + "signature": [ + { + "pluginId": "@kbn/presentation-publishing", + "scope": "public", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-public.PublishesUnsavedChanges", + "text": "PublishesUnsavedChanges" + }, + "" + ], "path": "packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts", "deprecated": false, "trackAdoption": false, @@ -6579,21 +6590,21 @@ "signature": [ "{ source: ", "Observable", - " | undefined; readonly value: object | undefined; error: (err: any) => void; forEach: { (next: (value: object | undefined) => void): Promise; (next: (value: object | undefined) => void, promiseCtor: PromiseConstructorLike): Promise; }; complete: () => void; getValue: () => object | undefined; closed: boolean; pipe: { (): ", + " | undefined; readonly value: Partial | undefined; error: (err: any) => void; forEach: { (next: (value: Partial | undefined) => void): Promise; (next: (value: Partial | undefined) => void, promiseCtor: PromiseConstructorLike): Promise; }; complete: () => void; getValue: () => Partial | undefined; closed: boolean; pipe: { (): ", "Observable", - "; (op1: ", + " | undefined>; (op1: ", "OperatorFunction", - "): ", + " | undefined, A>): ", "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", "): ", "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6601,7 +6612,7 @@ "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6611,7 +6622,7 @@ "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6623,7 +6634,7 @@ "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6637,7 +6648,7 @@ "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6653,7 +6664,7 @@ "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6671,7 +6682,7 @@ "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6691,7 +6702,7 @@ "Observable", "; (op1: ", "OperatorFunction", - ", op2: ", + " | undefined, A>, op2: ", "OperatorFunction", ", op3: ", "OperatorFunction", @@ -6713,21 +6724,21 @@ "Observable", "; }; observers: ", "Observer", - "[]; isStopped: boolean; hasError: boolean; thrownError: any; lift: (operator: ", + " | undefined>[]; isStopped: boolean; hasError: boolean; thrownError: any; lift: (operator: ", "Operator", - ") => ", + " | undefined, R>) => ", "Observable", "; unsubscribe: () => void; readonly observed: boolean; asObservable: () => ", "Observable", - "; operator: ", + " | undefined>; operator: ", "Operator", - " | undefined; subscribe: { (observerOrNext?: Partial<", + " | undefined> | undefined; subscribe: { (observerOrNext?: Partial<", "Observer", - "> | ((value: object | undefined) => void) | undefined): ", + " | undefined>> | ((value: Partial | undefined) => void) | undefined): ", "Subscription", - "; (next?: ((value: object | undefined) => void) | null | undefined, error?: ((error: any) => void) | null | undefined, complete?: (() => void) | null | undefined): ", + "; (next?: ((value: Partial | undefined) => void) | null | undefined, error?: ((error: any) => void) | null | undefined, complete?: (() => void) | null | undefined): ", "Subscription", - "; }; toPromise: { (): Promise; (PromiseCtor: PromiseConstructor): Promise; (PromiseCtor: PromiseConstructorLike): Promise; }; }" + "; }; toPromise: { (): Promise | undefined>; (PromiseCtor: PromiseConstructor): Promise | undefined>; (PromiseCtor: PromiseConstructorLike): Promise | undefined>; }; }" ], "path": "packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts", "deprecated": false, diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index bc7fe94ff1495..5be8284b5e1b1 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index f44bfa36edafa..a0640d0df6ddc 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index d0317fb886b8d..8a53692389400 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: 2024-07-31 +date: 2024-08-09 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 c67cee09b34f0..e310e28e31994 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_hooks.mdx b/api_docs/kbn_react_hooks.mdx index 6c09de91e2e03..0fcfd3f18eb11 100644 --- a/api_docs/kbn_react_hooks.mdx +++ b/api_docs/kbn_react_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-hooks title: "@kbn/react-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-hooks plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-hooks'] --- import kbnReactHooksObj from './kbn_react_hooks.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index eacf24e2ff5a4..5819ec07359f1 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: 2024-07-31 +date: 2024-08-09 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 f84e3a0ac4ff9..6a59a646850ca 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: 2024-07-31 +date: 2024-08-09 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 33a83393e1057..9d956d3f8b7cb 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: 2024-07-31 +date: 2024-08-09 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 f842545a09b73..eb7f6ad6b7a0e 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: 2024-07-31 +date: 2024-08-09 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 38a9d4bc4945a..cc5d88ceaeabc 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: 2024-07-31 +date: 2024-08-09 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 006305524b986..ab68c418d0127 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-kibana-mount'] --- import kbnReactKibanaMountObj from './kbn_react_kibana_mount.devdocs.json'; diff --git a/api_docs/kbn_recently_accessed.mdx b/api_docs/kbn_recently_accessed.mdx index adf8fe5077fa0..31b028cf26375 100644 --- a/api_docs/kbn_recently_accessed.mdx +++ b/api_docs/kbn_recently_accessed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-recently-accessed title: "@kbn/recently-accessed" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/recently-accessed plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/recently-accessed'] --- import kbnRecentlyAccessedObj from './kbn_recently_accessed.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index d93d2db17f124..50709a04232fe 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: 2024-07-31 +date: 2024-08-09 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 812e529b58efb..c5ff895887cb1 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: 2024-07-31 +date: 2024-08-09 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 9ce6f4ea5a7a1..762f0c2346e0b 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: 2024-07-31 +date: 2024-08-09 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 7b94869efb09a..38e84e9a5b618 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: 2024-07-31 +date: 2024-08-09 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 9ba7494da69ef..1a1457b4e9810 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-common'] --- import kbnReportingCommonObj from './kbn_reporting_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_csv_share_panel.mdx b/api_docs/kbn_reporting_csv_share_panel.mdx index a79dff7d86168..172e86fdd7e21 100644 --- a/api_docs/kbn_reporting_csv_share_panel.mdx +++ b/api_docs/kbn_reporting_csv_share_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-csv-share-panel title: "@kbn/reporting-csv-share-panel" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-csv-share-panel plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-csv-share-panel'] --- import kbnReportingCsvSharePanelObj from './kbn_reporting_csv_share_panel.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv.mdx b/api_docs/kbn_reporting_export_types_csv.mdx index 028eb8ebfb66f..c913fba84c84b 100644 --- a/api_docs/kbn_reporting_export_types_csv.mdx +++ b/api_docs/kbn_reporting_export_types_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv title: "@kbn/reporting-export-types-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv'] --- import kbnReportingExportTypesCsvObj from './kbn_reporting_export_types_csv.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_csv_common.mdx b/api_docs/kbn_reporting_export_types_csv_common.mdx index 61ec22de4468d..56b80eef5264b 100644 --- a/api_docs/kbn_reporting_export_types_csv_common.mdx +++ b/api_docs/kbn_reporting_export_types_csv_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-csv-common title: "@kbn/reporting-export-types-csv-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-csv-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-csv-common'] --- import kbnReportingExportTypesCsvCommonObj from './kbn_reporting_export_types_csv_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf.mdx b/api_docs/kbn_reporting_export_types_pdf.mdx index 645cdbb215dae..0e35216eed852 100644 --- a/api_docs/kbn_reporting_export_types_pdf.mdx +++ b/api_docs/kbn_reporting_export_types_pdf.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf title: "@kbn/reporting-export-types-pdf" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf'] --- import kbnReportingExportTypesPdfObj from './kbn_reporting_export_types_pdf.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_pdf_common.mdx b/api_docs/kbn_reporting_export_types_pdf_common.mdx index 632048c6cc368..cdf4cbb333701 100644 --- a/api_docs/kbn_reporting_export_types_pdf_common.mdx +++ b/api_docs/kbn_reporting_export_types_pdf_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-pdf-common title: "@kbn/reporting-export-types-pdf-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-pdf-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-pdf-common'] --- import kbnReportingExportTypesPdfCommonObj from './kbn_reporting_export_types_pdf_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png.mdx b/api_docs/kbn_reporting_export_types_png.mdx index d82740a0f278d..e8b47eeea570d 100644 --- a/api_docs/kbn_reporting_export_types_png.mdx +++ b/api_docs/kbn_reporting_export_types_png.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png title: "@kbn/reporting-export-types-png" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png'] --- import kbnReportingExportTypesPngObj from './kbn_reporting_export_types_png.devdocs.json'; diff --git a/api_docs/kbn_reporting_export_types_png_common.mdx b/api_docs/kbn_reporting_export_types_png_common.mdx index ba95ffa1cfc0d..74e18a82a6360 100644 --- a/api_docs/kbn_reporting_export_types_png_common.mdx +++ b/api_docs/kbn_reporting_export_types_png_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-export-types-png-common title: "@kbn/reporting-export-types-png-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-export-types-png-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-export-types-png-common'] --- import kbnReportingExportTypesPngCommonObj from './kbn_reporting_export_types_png_common.devdocs.json'; diff --git a/api_docs/kbn_reporting_mocks_server.mdx b/api_docs/kbn_reporting_mocks_server.mdx index 026322d91d840..9c7941d91bb30 100644 --- a/api_docs/kbn_reporting_mocks_server.mdx +++ b/api_docs/kbn_reporting_mocks_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-mocks-server title: "@kbn/reporting-mocks-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-mocks-server plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-mocks-server'] --- import kbnReportingMocksServerObj from './kbn_reporting_mocks_server.devdocs.json'; diff --git a/api_docs/kbn_reporting_public.mdx b/api_docs/kbn_reporting_public.mdx index f3591af2062cb..f4f8d5d69ffc9 100644 --- a/api_docs/kbn_reporting_public.mdx +++ b/api_docs/kbn_reporting_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-public title: "@kbn/reporting-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-public plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-public'] --- import kbnReportingPublicObj from './kbn_reporting_public.devdocs.json'; diff --git a/api_docs/kbn_reporting_server.mdx b/api_docs/kbn_reporting_server.mdx index fa3d83f3456be..142b5872b89a9 100644 --- a/api_docs/kbn_reporting_server.mdx +++ b/api_docs/kbn_reporting_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-reporting-server title: "@kbn/reporting-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/reporting-server plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/reporting-server'] --- import kbnReportingServerObj from './kbn_reporting_server.devdocs.json'; diff --git a/api_docs/kbn_resizable_layout.mdx b/api_docs/kbn_resizable_layout.mdx index b37c848c7bf7e..6f89adb019a4e 100644 --- a/api_docs/kbn_resizable_layout.mdx +++ b/api_docs/kbn_resizable_layout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-resizable-layout title: "@kbn/resizable-layout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/resizable-layout plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/resizable-layout'] --- import kbnResizableLayoutObj from './kbn_resizable_layout.devdocs.json'; diff --git a/api_docs/kbn_response_ops_feature_flag_service.mdx b/api_docs/kbn_response_ops_feature_flag_service.mdx index b08ec20208150..3eb25f6920210 100644 --- a/api_docs/kbn_response_ops_feature_flag_service.mdx +++ b/api_docs/kbn_response_ops_feature_flag_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-response-ops-feature-flag-service title: "@kbn/response-ops-feature-flag-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/response-ops-feature-flag-service plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-feature-flag-service'] --- import kbnResponseOpsFeatureFlagServiceObj from './kbn_response_ops_feature_flag_service.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 974db7be301fc..8fa39beb05e1d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rollup.mdx b/api_docs/kbn_rollup.mdx index 8da8bb52414c1..b0dff4db3696d 100644 --- a/api_docs/kbn_rollup.mdx +++ b/api_docs/kbn_rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rollup title: "@kbn/rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rollup plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rollup'] --- import kbnRollupObj from './kbn_rollup.devdocs.json'; diff --git a/api_docs/kbn_router_to_openapispec.mdx b/api_docs/kbn_router_to_openapispec.mdx index 867fb75bc08bb..3e9240b8580ef 100644 --- a/api_docs/kbn_router_to_openapispec.mdx +++ b/api_docs/kbn_router_to_openapispec.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-to-openapispec title: "@kbn/router-to-openapispec" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-to-openapispec plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-to-openapispec'] --- import kbnRouterToOpenapispecObj from './kbn_router_to_openapispec.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index e5c4d33708fa6..230a004a69bd3 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index a6cc8683ff9ae..08be4922ad817 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: 2024-07-31 +date: 2024-08-09 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 95fd393e44a4f..a05072af3ca45 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: 2024-07-31 +date: 2024-08-09 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 a679e730118ea..29ec4ff50e200 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_search_api_panels.mdx index 66282e92d3e78..8c25bb9ec32fc 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.devdocs.json b/api_docs/kbn_search_connectors.devdocs.json index 51f17627b7d52..9d609f75fbad9 100644 --- a/api_docs/kbn_search_connectors.devdocs.json +++ b/api_docs/kbn_search_connectors.devdocs.json @@ -25093,7 +25093,7 @@ "label": "depends_on", "description": [], "signature": [ - "{ field: string; value: string; }[]" + "never[]" ], "path": "packages/kbn-search-connectors/types/native_connectors.ts", "deprecated": false, @@ -51905,6 +51905,206 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.sharepoint_server.configuration.authentication", + "type": "Object", + "tags": [], + "label": "authentication", + "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_server.configuration.authentication.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_server.configuration.authentication.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_server.configuration.authentication.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.sharepoint_server.configuration.authentication.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_server.configuration.authentication.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.sharepoint_server.configuration.authentication.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_server.configuration.authentication.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_server.configuration.authentication.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_server.configuration.authentication.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.sharepoint_server.configuration.authentication.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_server.configuration.authentication.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_server.configuration.authentication.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_server.configuration.authentication.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_server.configuration.username", diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 87a453cb6f887..ec6ee3bed6a2e 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-ki | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3915 | 0 | 3915 | 0 | +| 3929 | 0 | 3929 | 0 | ## Common diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index 5737a07c0c185..0952e04990691 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index 3ea0bb629272f..ba4f70ac49695 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index 5aba9be63c6dd..63325c095d147 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_search_types.mdx b/api_docs/kbn_search_types.mdx index 12032fac42b29..b1d7183b31cb8 100644 --- a/api_docs/kbn_search_types.mdx +++ b/api_docs/kbn_search_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-types title: "@kbn/search-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-types plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-types'] --- import kbnSearchTypesObj from './kbn_search_types.devdocs.json'; diff --git a/api_docs/kbn_security_api_key_management.mdx b/api_docs/kbn_security_api_key_management.mdx index c86e68d54c29d..3fd70e6f78d70 100644 --- a/api_docs/kbn_security_api_key_management.mdx +++ b/api_docs/kbn_security_api_key_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-api-key-management title: "@kbn/security-api-key-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-api-key-management plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-api-key-management'] --- import kbnSecurityApiKeyManagementObj from './kbn_security_api_key_management.devdocs.json'; diff --git a/api_docs/kbn_security_form_components.mdx b/api_docs/kbn_security_form_components.mdx index 7b198b475a80e..b37f088ed2295 100644 --- a/api_docs/kbn_security_form_components.mdx +++ b/api_docs/kbn_security_form_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-form-components title: "@kbn/security-form-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-form-components plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-form-components'] --- import kbnSecurityFormComponentsObj from './kbn_security_form_components.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index 2f3f370a8ab7e..577bd0e92d33d 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index 3780a1ccc3385..31223a90a9992 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index 1d33d2dac7e4e..462a7906a761c 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index 4757c78e20b3f..88b9f59719293 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_solution_distribution_bar.mdx b/api_docs/kbn_security_solution_distribution_bar.mdx index b77937713a907..9f1343fe17f2a 100644 --- a/api_docs/kbn_security_solution_distribution_bar.mdx +++ b/api_docs/kbn_security_solution_distribution_bar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-distribution-bar title: "@kbn/security-solution-distribution-bar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-distribution-bar plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-distribution-bar'] --- import kbnSecuritySolutionDistributionBarObj from './kbn_security_solution_distribution_bar.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index 9bbd43a24c5f9..264eec3c04698 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-solution-features'] --- import kbnSecuritySolutionFeaturesObj from './kbn_security_solution_features.devdocs.json'; diff --git a/api_docs/kbn_security_solution_navigation.mdx b/api_docs/kbn_security_solution_navigation.mdx index 92584d4409be9..b0b80a300cafd 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: 2024-07-31 +date: 2024-08-09 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 3cd265269307f..cc03821702323 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: 2024-07-31 +date: 2024-08-09 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 de2793747d980..14972b29c38fb 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: 2024-07-31 +date: 2024-08-09 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 19b4a0b3e6400..dd1feccd52ab7 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: 2024-07-31 +date: 2024-08-09 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 3485fc785d757..f9e9d3315803b 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: 2024-07-31 +date: 2024-08-09 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 75b5ccd664a22..7aac9d824e159 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: 2024-07-31 +date: 2024-08-09 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 f60e142b97e7c..cd0d6ead49d09 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json index 48cd40e6e1260..fa61ae7f0558f 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.devdocs.json +++ b/api_docs/kbn_securitysolution_exception_list_components.devdocs.json @@ -851,7 +851,7 @@ "label": "formattedDateComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"body\" | \"html\" | \"stop\" | \"q\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/index.tsx", "deprecated": false, @@ -865,7 +865,7 @@ "label": "securityLinkAnchorComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"body\" | \"html\" | \"stop\" | \"q\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/meta/index.tsx", "deprecated": false, @@ -1004,7 +1004,7 @@ "label": "securityLinkAnchorComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"body\" | \"html\" | \"stop\" | \"q\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, @@ -1018,7 +1018,7 @@ "label": "formattedDateComponent", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"body\" | \"html\" | \"stop\" | \"q\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, @@ -1144,7 +1144,7 @@ "label": "showValueListModal", "description": [], "signature": [ - "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"body\" | \"html\" | \"stop\" | \"q\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" + "\"symbol\" | \"object\" | \"source\" | \"meta\" | \"desc\" | \"filter\" | \"big\" | \"link\" | \"small\" | \"sub\" | \"sup\" | \"text\" | \"map\" | \"head\" | \"slot\" | \"style\" | \"title\" | \"data\" | \"path\" | \"code\" | \"pattern\" | \"summary\" | \"template\" | \"span\" | \"q\" | \"body\" | \"html\" | \"stop\" | \"main\" | \"form\" | \"line\" | \"rect\" | \"label\" | \"progress\" | \"article\" | \"image\" | \"menu\" | \"base\" | React.ComponentType | \"s\" | \"legend\" | \"canvas\" | \"svg\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"p\" | \"select\" | \"output\" | \"script\" | \"time\" | \"mask\" | \"input\" | \"table\" | \"a\" | \"abbr\" | \"address\" | \"area\" | \"aside\" | \"audio\" | \"b\" | \"bdi\" | \"bdo\" | \"blockquote\" | \"br\" | \"button\" | \"caption\" | \"cite\" | \"col\" | \"colgroup\" | \"datalist\" | \"dd\" | \"del\" | \"details\" | \"dfn\" | \"dialog\" | \"div\" | \"dl\" | \"dt\" | \"em\" | \"embed\" | \"fieldset\" | \"figcaption\" | \"figure\" | \"footer\" | \"header\" | \"hgroup\" | \"hr\" | \"i\" | \"iframe\" | \"img\" | \"ins\" | \"kbd\" | \"keygen\" | \"li\" | \"mark\" | \"menuitem\" | \"meter\" | \"nav\" | \"noindex\" | \"noscript\" | \"ol\" | \"optgroup\" | \"option\" | \"param\" | \"picture\" | \"pre\" | \"rp\" | \"rt\" | \"ruby\" | \"samp\" | \"section\" | \"strong\" | \"tbody\" | \"td\" | \"textarea\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"track\" | \"u\" | \"ul\" | \"var\" | \"video\" | \"wbr\" | \"webview\" | \"animate\" | \"animateMotion\" | \"animateTransform\" | \"circle\" | \"clipPath\" | \"defs\" | \"ellipse\" | \"feBlend\" | \"feColorMatrix\" | \"feComponentTransfer\" | \"feComposite\" | \"feConvolveMatrix\" | \"feDiffuseLighting\" | \"feDisplacementMap\" | \"feDistantLight\" | \"feDropShadow\" | \"feFlood\" | \"feFuncA\" | \"feFuncB\" | \"feFuncG\" | \"feFuncR\" | \"feGaussianBlur\" | \"feImage\" | \"feMerge\" | \"feMergeNode\" | \"feMorphology\" | \"feOffset\" | \"fePointLight\" | \"feSpecularLighting\" | \"feSpotLight\" | \"feTile\" | \"feTurbulence\" | \"foreignObject\" | \"g\" | \"linearGradient\" | \"marker\" | \"metadata\" | \"mpath\" | \"polygon\" | \"polyline\" | \"radialGradient\" | \"switch\" | \"textPath\" | \"tspan\" | \"use\" | \"view\"" ], "path": "packages/kbn-securitysolution-exception-list-components/src/exception_item_card/exception_item_card.tsx", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 209c3b94362b1..661a2252abca6 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: 2024-07-31 +date: 2024-08-09 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_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 9a9009639d6b2..6b65540844883 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json index 38fe460b060e7..d705674f3eb19 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json @@ -987,7 +987,7 @@ "label": "Type", "description": [], "signature": [ - "\"eql\" | \"esql\" | \"query\" | \"threshold\" | \"saved_query\" | \"threat_match\" | \"machine_learning\" | \"new_terms\"" + "\"eql\" | \"esql\" | \"query\" | \"threshold\" | \"threat_match\" | \"saved_query\" | \"machine_learning\" | \"new_terms\"" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/type/index.ts", "deprecated": false, @@ -1002,7 +1002,7 @@ "label": "TypeOrUndefined", "description": [], "signature": [ - "\"eql\" | \"esql\" | \"query\" | \"threshold\" | \"saved_query\" | \"threat_match\" | \"machine_learning\" | \"new_terms\" | undefined" + "\"eql\" | \"esql\" | \"query\" | \"threshold\" | \"threat_match\" | \"saved_query\" | \"machine_learning\" | \"new_terms\" | undefined" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/type/index.ts", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index ab5a7ad4b3891..1c0d6a6d7bc8a 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: 2024-07-31 +date: 2024-08-09 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 283f95e6b8938..28bbb1cb50c70 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: 2024-07-31 +date: 2024-08-09 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 8cb9ba4f73ee6..78871781a9ee2 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: 2024-07-31 +date: 2024-08-09 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 6ac434bf9620d..2759da152787e 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: 2024-07-31 +date: 2024-08-09 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 53590adc56bb2..9b564765be866 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: 2024-07-31 +date: 2024-08-09 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 16e3ce5f61dfe..8a36f10caa10c 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: 2024-07-31 +date: 2024-08-09 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 d8445ab2f6b3d..b96366d3d0209 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: 2024-07-31 +date: 2024-08-09 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 8d69beb152f0c..8bfdb68f00eba 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_securitysolution_rules.devdocs.json index 3b513ef6f5670..15c7914511c23 100644 --- a/api_docs/kbn_securitysolution_rules.devdocs.json +++ b/api_docs/kbn_securitysolution_rules.devdocs.json @@ -75,7 +75,7 @@ "label": "isRuleType", "description": [], "signature": [ - "(ruleType: unknown) => ruleType is \"eql\" | \"esql\" | \"query\" | \"threshold\" | \"saved_query\" | \"threat_match\" | \"machine_learning\" | \"new_terms\"" + "(ruleType: unknown) => ruleType is \"eql\" | \"esql\" | \"query\" | \"threshold\" | \"threat_match\" | \"saved_query\" | \"machine_learning\" | \"new_terms\"" ], "path": "packages/kbn-securitysolution-rules/src/utils.ts", "deprecated": false, @@ -259,7 +259,7 @@ "label": "RuleType", "description": [], "signature": [ - "\"eql\" | \"esql\" | \"query\" | \"threshold\" | \"saved_query\" | \"threat_match\" | \"machine_learning\" | \"new_terms\"" + "\"eql\" | \"esql\" | \"query\" | \"threshold\" | \"threat_match\" | \"saved_query\" | \"machine_learning\" | \"new_terms\"" ], "path": "packages/kbn-securitysolution-rules/src/rule_type_mappings.ts", "deprecated": false, diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 61c5a898e4f83..e45dce53c9560 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: 2024-07-31 +date: 2024-08-09 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 cced6ca3a4a8b..3754426f39521 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_securitysolution_utils.devdocs.json index d36fe39b6481f..7f9eeb7c9d58c 100644 --- a/api_docs/kbn_securitysolution_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_utils.devdocs.json @@ -251,6 +251,53 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/securitysolution-utils", + "id": "def-common.isAggregatingQuery", + "type": "Function", + "tags": [], + "label": "isAggregatingQuery", + "description": [], + "signature": [ + "(ast: ", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAst", + "text": "ESQLAst" + }, + ") => boolean" + ], + "path": "packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-utils", + "id": "def-common.isAggregatingQuery.$1", + "type": "Array", + "tags": [], + "label": "ast", + "description": [], + "signature": [ + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAst", + "text": "ESQLAst" + } + ], + "path": "packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/securitysolution-utils", "id": "def-common.isPathValid", diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index a1ecb4fdbdca6..5a0a585cc9326 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detection-engine](https://github.com/orgs/elastic/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 49 | 0 | 44 | 0 | +| 51 | 0 | 46 | 0 | ## Common diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 20039ef8e7788..80394e07bce98 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: 2024-07-31 +date: 2024-08-09 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 3736aba4bc108..dcbc0bbe2c3dd 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository_utils.mdx b/api_docs/kbn_server_route_repository_utils.mdx index a941fd4c47aed..54e0938623d67 100644 --- a/api_docs/kbn_server_route_repository_utils.mdx +++ b/api_docs/kbn_server_route_repository_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository-utils title: "@kbn/server-route-repository-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository-utils'] --- import kbnServerRouteRepositoryUtilsObj from './kbn_server_route_repository_utils.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index 1b837eb871f5a..ce383973e02de 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-common-settings'] --- import kbnServerlessCommonSettingsObj from './kbn_serverless_common_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_observability_settings.mdx b/api_docs/kbn_serverless_observability_settings.mdx index 7ca21f443fcdf..51976aeada24d 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index 4dbe840bc7698..03346f1128546 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_serverless_search_settings.mdx index 573885b39cc53..d8e1b331ff958 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-search-settings'] --- import kbnServerlessSearchSettingsObj from './kbn_serverless_search_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_security_settings.mdx b/api_docs/kbn_serverless_security_settings.mdx index 025595e572c15..f63210dcce3c5 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-security-settings'] --- import kbnServerlessSecuritySettingsObj from './kbn_serverless_security_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_storybook_config.mdx b/api_docs/kbn_serverless_storybook_config.mdx index 698fa0856fdce..b77df2cc86320 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: 2024-07-31 +date: 2024-08-09 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 d64bf1aee4050..35796b57b0599 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: 2024-07-31 +date: 2024-08-09 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 f5a5349d4638a..0f51bd3433a54 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: 2024-07-31 +date: 2024-08-09 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_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index a239fefd7c0dd..3cb82b7af71ed 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: 2024-07-31 +date: 2024-08-09 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_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index a5f0a4ef5332d..68cd659c14da0 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: 2024-07-31 +date: 2024-08-09 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 30f5e477058eb..ddb10f980cc7d 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: 2024-07-31 +date: 2024-08-09 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 d604b6cd95066..0c032e48c014a 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: 2024-07-31 +date: 2024-08-09 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.devdocs.json b/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json index cc5d573381a01..3b673465c3055 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json +++ b/api_docs/kbn_shared_ux_chrome_navigation.devdocs.json @@ -212,9 +212,11 @@ }, "[]>; }; getIsSideNavCollapsed$: () => ", "Observable", - "; }; http: { basePath: BasePathService; getLoadingCount$(): ", + "; }; http: { basePath: ", + "BasePathService", + "; getLoadingCount$(): ", "Observable", - "; }; }" + "; }; analytics: { reportEvent: (eventType: string, eventData: object) => void; }; }" ], "path": "packages/shared-ux/chrome/navigation/src/types.ts", "deprecated": false, @@ -267,7 +269,7 @@ "label": "basePath", "description": [], "signature": [ - "{ prepend: (url: string) => string; }" + "{ prepend: (url: string) => string; remove: (url: string) => string; }" ], "path": "packages/shared-ux/chrome/navigation/src/types.ts", "deprecated": false, @@ -397,6 +399,20 @@ "path": "packages/shared-ux/chrome/navigation/src/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-chrome-navigation", + "id": "def-public.NavigationServices.eventTracker", + "type": "Object", + "tags": [], + "label": "eventTracker", + "description": [], + "signature": [ + "EventTracker" + ], + "path": "packages/shared-ux/chrome/navigation/src/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -603,7 +619,20 @@ "initialIsOpen": false } ], - "enums": [], + "enums": [ + { + "parentPluginId": "@kbn/shared-ux-chrome-navigation", + "id": "def-public.FieldType", + "type": "Enum", + "tags": [], + "label": "FieldType", + "description": [], + "path": "packages/shared-ux/chrome/navigation/src/analytics/event_tracker.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], "misc": [ { "parentPluginId": "@kbn/shared-ux-chrome-navigation", @@ -640,6 +669,18 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-chrome-navigation", + "id": "def-public.EventType", + "type": "string", + "tags": [], + "label": "EventType", + "description": [], + "path": "packages/shared-ux/chrome/navigation/src/analytics/event_tracker.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index daed35ea7a120..56f093c4688c6 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.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 | 0 | 23 | 0 | +| 35 | 0 | 26 | 2 | ## Client @@ -31,6 +31,9 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ### Interfaces +### Enums + + ### Consts, variables and types diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 1c506b14f754d..301470d5c9882 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-error-boundary'] --- import kbnSharedUxErrorBoundaryObj from './kbn_shared_ux_error_boundary.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 1d66ca2e75ebd..52acc4d1b2639 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: 2024-07-31 +date: 2024-08-09 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 fa6fb520e28a8..aa7d166898a2d 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: 2024-07-31 +date: 2024-08-09 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 f0b649fa9031b..7b524e1a65ea7 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: 2024-07-31 +date: 2024-08-09 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 36ecaa0275b50..f8c7d9c4668ab 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: 2024-07-31 +date: 2024-08-09 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 85ed33fc09dc4..ca75b3b573c4f 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: 2024-07-31 +date: 2024-08-09 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 8a89c96505336..ad18cbc4b95f2 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: 2024-07-31 +date: 2024-08-09 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 fdc2e20bbde02..68ff7db54045d 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: 2024-07-31 +date: 2024-08-09 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 ea81428223d7c..416222fe0f420 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: 2024-07-31 +date: 2024-08-09 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 8e42f780ddda8..ff0df39065e16 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: 2024-07-31 +date: 2024-08-09 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 b235f6470d42f..d8991488c0dd6 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: 2024-07-31 +date: 2024-08-09 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 4beb17231d7f2..75f556420d56c 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: 2024-07-31 +date: 2024-08-09 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 0921d8191775e..4da52f40a26ca 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: 2024-07-31 +date: 2024-08-09 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 a64436a339a06..00630609117a2 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: 2024-07-31 +date: 2024-08-09 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 da79f5ff595d0..e0fde1a8cafff 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: 2024-07-31 +date: 2024-08-09 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 b917beea797db..009f561f63c03 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: 2024-07-31 +date: 2024-08-09 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 f319c3039d9c5..ed13cb061ea95 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: 2024-07-31 +date: 2024-08-09 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 9c46fd9872c5a..84617395fe9a3 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: 2024-07-31 +date: 2024-08-09 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 0b3eaf5f757f2..9fd250b69e3df 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: 2024-07-31 +date: 2024-08-09 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 6318decfa8881..ea30e7e0f16ca 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: 2024-07-31 +date: 2024-08-09 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 e4e0854ca28fa..155c09856b70b 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: 2024-07-31 +date: 2024-08-09 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 17790a97e02a6..4bf5b914cb22e 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: 2024-07-31 +date: 2024-08-09 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 ff836719c97a9..8c84a656ab65d 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: 2024-07-31 +date: 2024-08-09 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 96e45e580ee53..deefd7e2a7845 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: 2024-07-31 +date: 2024-08-09 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 fd976f45b320c..09c30f5ab939b 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: 2024-07-31 +date: 2024-08-09 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 f01e1c43068ff..fd32b528733c2 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: 2024-07-31 +date: 2024-08-09 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 a67ff92ec019a..b053e0e22afba 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: 2024-07-31 +date: 2024-08-09 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 5dd276f15d771..f3d5a5b3c1db6 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: 2024-07-31 +date: 2024-08-09 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 3ff8a5ab02963..550d9812a14eb 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: 2024-07-31 +date: 2024-08-09 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 279f00e7c449c..f620b34a00479 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: 2024-07-31 +date: 2024-08-09 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 ba40e9463f47a..911369ac4943e 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: 2024-07-31 +date: 2024-08-09 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_tabbed_modal.mdx b/api_docs/kbn_shared_ux_tabbed_modal.mdx index c8b7d347c88c1..fdf87392677a3 100644 --- a/api_docs/kbn_shared_ux_tabbed_modal.mdx +++ b/api_docs/kbn_shared_ux_tabbed_modal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-tabbed-modal title: "@kbn/shared-ux-tabbed-modal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-tabbed-modal plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-tabbed-modal'] --- import kbnSharedUxTabbedModalObj from './kbn_shared_ux_tabbed_modal.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index cc36e5fdb791f..509fe1e863de9 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: 2024-07-31 +date: 2024-08-09 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 77f3590eb32c0..dad426dd249e5 100644 --- a/api_docs/kbn_slo_schema.devdocs.json +++ b/api_docs/kbn_slo_schema.devdocs.json @@ -606,7 +606,7 @@ "label": "CreateSLOInput", "description": [], "signature": [ - "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; } & { id?: string | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; preventInitialBackfill?: boolean | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | string[] | undefined; revision?: number | undefined; }" + "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; } & { id?: string | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; preventInitialBackfill?: boolean | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | string[] | undefined; revision?: number | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/create.ts", "deprecated": false, @@ -621,7 +621,7 @@ "label": "CreateSLOParams", "description": [], "signature": [ - "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: ", + "{ name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -835,7 +835,7 @@ "label": "FindSLODefinitionsResponse", "description": [], "signature": [ - "{ page: number; perPage: number; total: number; results: { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }[]; }" + "{ page: number; perPage: number; total: number; results: { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }[]; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find_definition.ts", "deprecated": false, @@ -895,7 +895,7 @@ "label": "FindSLOResponse", "description": [], "signature": [ - "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; })[]; }" + "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; })[]; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts", "deprecated": false, @@ -910,7 +910,7 @@ "label": "GetPreviewDataParams", "description": [], "signature": [ - "{ indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; range: { from: Date; to: Date; }; } & { objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: ", + "{ indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; range: { from: Date; to: Date; }; } & { objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -993,7 +993,7 @@ "label": "GetSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get.ts", "deprecated": false, @@ -1098,7 +1098,7 @@ "label": "Indicator", "description": [], "signature": [ - "{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }" + "{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/indicators.ts", "deprecated": false, @@ -1263,7 +1263,7 @@ "label": "ResetSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/reset.ts", "deprecated": false, @@ -1278,7 +1278,7 @@ "label": "SLODefinitionResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -1293,7 +1293,7 @@ "label": "SLOWithSummaryResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -1338,7 +1338,7 @@ "label": "TimesliceMetricBasicMetricWithField", "description": [], "signature": [ - "{ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }" + "{ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/indicators.ts", "deprecated": false, @@ -1368,7 +1368,7 @@ "label": "TimesliceMetricIndicator", "description": [], "signature": [ - "{ type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }" + "{ type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/indicators.ts", "deprecated": false, @@ -1398,7 +1398,7 @@ "label": "UpdateSLOInput", "description": [], "signature": [ - "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | undefined; timeWindow?: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; } | undefined; budgetingMethod?: \"occurrences\" | \"timeslices\" | undefined; objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }) | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; preventInitialBackfill?: boolean | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | string[] | undefined; }" + "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | undefined; timeWindow?: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; } | undefined; budgetingMethod?: \"occurrences\" | \"timeslices\" | undefined; objective?: ({ target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }) | undefined; settings?: { syncDelay?: string | undefined; frequency?: string | undefined; preventInitialBackfill?: boolean | undefined; } | undefined; tags?: string[] | undefined; groupBy?: string | string[] | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/update.ts", "deprecated": false, @@ -1413,7 +1413,7 @@ "label": "UpdateSLOParams", "description": [], "signature": [ - "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | undefined; timeWindow?: { duration: ", + "{ name?: string | undefined; description?: string | undefined; indicator?: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | undefined; timeWindow?: { duration: ", { "pluginId": "@kbn/slo-schema", "scope": "common", @@ -1468,7 +1468,7 @@ "label": "UpdateSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/update.ts", "deprecated": false, diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index f413f73a2bab7..ffaecd43fb361 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 5564a362e5211..b7ac8544c18d5 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 19fbfc97e3ef4..bf30651905a68 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index b72c562ca5948..b19632c57f329 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: 2024-07-31 +date: 2024-08-09 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 7cf8007a52e87..9f0093dbdb0e2 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: 2024-07-31 +date: 2024-08-09 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 47159bb8644d0..e1ffd1f71bdd4 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_synthetics_e2e.mdx b/api_docs/kbn_synthetics_e2e.mdx index c2f6a4a0123d5..12f115f2dbe7e 100644 --- a/api_docs/kbn_synthetics_e2e.mdx +++ b/api_docs/kbn_synthetics_e2e.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-synthetics-e2e title: "@kbn/synthetics-e2e" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/synthetics-e2e plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/synthetics-e2e'] --- import kbnSyntheticsE2eObj from './kbn_synthetics_e2e.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 71fa7cf6939b0..e0a07799c07f6 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: 2024-07-31 +date: 2024-08-09 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 50380d3b663e5..a8df40b09e765 100644 --- a/api_docs/kbn_test.devdocs.json +++ b/api_docs/kbn_test.devdocs.json @@ -1699,21 +1699,6 @@ "deprecated": false, "trackAdoption": false, "isRequired": true - }, - { - "parentPluginId": "@kbn/test", - "id": "def-common.SamlSessionManager.Unnamed.$2", - "type": "string", - "tags": [], - "label": "rolesFilename", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-test/src/auth/session_manager.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false } ], "returnComment": [] @@ -5077,17 +5062,29 @@ { "parentPluginId": "@kbn/test", "id": "def-common.SamlSessionManagerOptions.supportedRoles", - "type": "Array", + "type": "Object", "tags": [], "label": "supportedRoles", "description": [], "signature": [ - "string[] | undefined" + "SupportedRoles", + " | undefined" ], "path": "packages/kbn-test/src/auth/session_manager.ts", "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/test", + "id": "def-common.SamlSessionManagerOptions.cloudUsersFilePath", + "type": "string", + "tags": [], + "label": "cloudUsersFilePath", + "description": [], + "path": "packages/kbn-test/src/auth/session_manager.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/test", "id": "def-common.SamlSessionManagerOptions.log", diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index be84022c6db56..10570bb82a120 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.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 | |-------------------|-----------|------------------------|-----------------| -| 315 | 4 | 267 | 13 | +| 315 | 4 | 267 | 14 | ## Common diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index c49ad8ff85630..ce326a644d56d 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 2a17d9c56167e..d4791f44e8c64 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: 2024-07-31 +date: 2024-08-09 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 60fe07d885d2d..ed5fab2876211 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: 2024-07-31 +date: 2024-08-09 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 c55d5a6d0a970..70a51ba17fe67 100644 --- a/api_docs/kbn_text_based_editor.devdocs.json +++ b/api_docs/kbn_text_based_editor.devdocs.json @@ -227,7 +227,7 @@ "tags": [], "label": "TextBasedLanguagesEditorProps", "description": [], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -243,7 +243,7 @@ "signature": [ "{ esql: string; }" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -267,7 +267,7 @@ }, ") => void" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -287,7 +287,7 @@ "text": "AggregateQuery" } ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -315,7 +315,7 @@ }, " | undefined, abortController?: AbortController | undefined) => Promise" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -336,7 +336,7 @@ }, " | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -351,7 +351,7 @@ "signature": [ "AbortController | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -359,53 +359,6 @@ ], "returnComment": [] }, - { - "parentPluginId": "@kbn/text-based-editor", - "id": "def-public.TextBasedLanguagesEditorProps.expandCodeEditor", - "type": "Function", - "tags": [], - "label": "expandCodeEditor", - "description": [ - "Can be used to expand/minimize the editor" - ], - "signature": [ - "(status: boolean) => void" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/text-based-editor", - "id": "def-public.TextBasedLanguagesEditorProps.expandCodeEditor.$1", - "type": "boolean", - "tags": [], - "label": "status", - "description": [], - "signature": [ - "boolean" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/text-based-editor", - "id": "def-public.TextBasedLanguagesEditorProps.isCodeEditorExpanded", - "type": "boolean", - "tags": [], - "label": "isCodeEditorExpanded", - "description": [ - "If it is true, the editor initializes with height EDITOR_INITIAL_HEIGHT_EXPANDED" - ], - "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.detectedTimestamp", @@ -418,7 +371,7 @@ "signature": [ "string | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -434,7 +387,7 @@ "signature": [ "Error[] | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -450,7 +403,7 @@ "signature": [ "string | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -466,7 +419,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -482,23 +435,7 @@ "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.isDarkMode", - "type": "CompoundType", - "tags": [], - "label": "isDarkMode", - "description": [ - "Indicator if the editor is on dark mode" - ], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -512,23 +449,7 @@ "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.hideMinimizeButton", - "type": "CompoundType", - "tags": [], - "label": "hideMinimizeButton", - "description": [ - "If true it hides the minimize button and the user can't return to the minimized version\nUseful when the application doesn't want to give this capability" - ], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -544,7 +465,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -560,7 +481,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -576,7 +497,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -592,7 +513,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -608,7 +529,7 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, @@ -624,23 +545,23 @@ "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/text-based-editor", - "id": "def-public.TextBasedLanguagesEditorProps.hideHeaderWhenExpanded", + "id": "def-public.TextBasedLanguagesEditorProps.hasOutline", "type": "CompoundType", "tags": [], - "label": "hideHeaderWhenExpanded", + "label": "hasOutline", "description": [ - "hide header buttons when editor is expanded" + "adds border in the editor" ], "signature": [ "boolean | undefined" ], - "path": "packages/kbn-text-based-editor/src/text_based_languages_editor.tsx", + "path": "packages/kbn-text-based-editor/src/types.ts", "deprecated": false, "trackAdoption": false } diff --git a/api_docs/kbn_text_based_editor.mdx b/api_docs/kbn_text_based_editor.mdx index 22bc53369724f..bd2353284b1df 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: 2024-07-31 +date: 2024-08-09 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-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 33 | 0 | 13 | 0 | +| 28 | 0 | 12 | 0 | ## Client diff --git a/api_docs/kbn_timerange.mdx b/api_docs/kbn_timerange.mdx index 47d52748f0702..4846098065625 100644 --- a/api_docs/kbn_timerange.mdx +++ b/api_docs/kbn_timerange.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-timerange title: "@kbn/timerange" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/timerange plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/timerange'] --- import kbnTimerangeObj from './kbn_timerange.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index f8e4b1091a979..6a1ba249ead24 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index 179834d98308e..20bdb0582855e 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_try_in_console.devdocs.json b/api_docs/kbn_try_in_console.devdocs.json index c7b351e280f0e..272e1b8129dce 100644 --- a/api_docs/kbn_try_in_console.devdocs.json +++ b/api_docs/kbn_try_in_console.devdocs.json @@ -27,7 +27,7 @@ "label": "TryInConsoleButton", "description": [], "signature": [ - "({ request, application, consolePlugin, sharePlugin, content, showIcon, link, }: ", + "({ request, application, consolePlugin, sharePlugin, content, showIcon, type, }: ", "TryInConsoleButtonProps", ") => JSX.Element | null" ], @@ -40,7 +40,7 @@ "id": "def-common.TryInConsoleButton.$1", "type": "Object", "tags": [], - "label": "{\n request,\n application,\n consolePlugin,\n sharePlugin,\n content,\n showIcon = true,\n link = false,\n}", + "label": "{\n request,\n application,\n consolePlugin,\n sharePlugin,\n content = TRY_IN_CONSOLE,\n showIcon = true,\n type = 'emptyButton',\n}", "description": [], "signature": [ "TryInConsoleButtonProps" diff --git a/api_docs/kbn_try_in_console.mdx b/api_docs/kbn_try_in_console.mdx index 8647728297d20..49bdc67c547f5 100644 --- a/api_docs/kbn_try_in_console.mdx +++ b/api_docs/kbn_try_in_console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-try-in-console title: "@kbn/try-in-console" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/try-in-console plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/try-in-console'] --- import kbnTryInConsoleObj from './kbn_try_in_console.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 69eda1e8d167c..0d7f43d8a4aee 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: 2024-07-31 +date: 2024-08-09 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 5b7dd288a863c..c34f69600296c 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: 2024-07-31 +date: 2024-08-09 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 c6086babb0e21..9616543f1d14c 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index c151bbe1aad8a..976e97f043fd2 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 2a3bda01cc90b..3137a1ee30333 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: 2024-07-31 +date: 2024-08-09 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 index c778b833f799d..00dea271a943c 100644 --- a/api_docs/kbn_unified_data_table.devdocs.json +++ b/api_docs/kbn_unified_data_table.devdocs.json @@ -11,7 +11,9 @@ "label": "DataTableRowControl", "description": [], "signature": [ - "({ children }: { children: React.ReactNode; }) => JSX.Element" + "({ size, children }: React.PropsWithChildren<{ size?: ", + "Size", + " | undefined; }>) => JSX.Element" ], "path": "packages/kbn-unified-data-table/src/components/data_table_row_control.tsx", "deprecated": false, @@ -20,29 +22,19 @@ { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.DataTableRowControl.$1", - "type": "Object", + "type": "CompoundType", "tags": [], - "label": "{ children }", + "label": "{ size, children }", "description": [], + "signature": [ + "React.PropsWithChildren<{ size?: ", + "Size", + " | undefined; }>" + ], "path": "packages/kbn-unified-data-table/src/components/data_table_row_control.tsx", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.DataTableRowControl.$1.children", - "type": "CompoundType", - "tags": [], - "label": "children", - "description": [], - "signature": [ - "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" - ], - "path": "packages/kbn-unified-data-table/src/components/data_table_row_control.tsx", - "deprecated": false, - "trackAdoption": false - } - ] + "isRequired": true } ], "returnComment": [], @@ -516,7 +508,7 @@ "label": "UnifiedDataTable", "description": [], "signature": [ - "({ ariaLabelledBy, columns, columnsMeta, showColumnTokens, configHeaderRowHeight, headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds, dataView, loadingState, onFilter, onResize, onSetColumns, onSort, rows, searchDescription, searchTitle, settings, showTimeCol, showFullScreenButton, sort, useNewFieldsApi, isSortEnabled, isPaginationEnabled, cellActionsTriggerId, className, rowHeightState, onUpdateRowHeight, maxAllowedSampleSize, sampleSizeState, onUpdateSampleSize, isPlainRecord, rowsPerPageState, onUpdateRowsPerPage, onFieldEdited, services, renderCustomGridBody, renderCustomToolbar, trailingControlColumns, totalHits, onFetchMoreRecords, renderDocumentView, setExpandedDoc, expandedDoc, configRowHeight, showMultiFields, maxDocFieldsDisplayed, externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, externalCustomRenderers, additionalFieldGroups, consumer, componentsTourSteps, gridStyleOverride, rowLineHeightOverride, cellActionsMetadata, customGridColumnsConfiguration, customControlColumnsConfiguration, enableComparisonMode, cellContext, renderCellPopover, getRowIndicator, }: ", + "({ ariaLabelledBy, columns, columnsMeta, showColumnTokens, configHeaderRowHeight, headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds, rowAdditionalLeadingControls, dataView, loadingState, onFilter, onResize, onSetColumns, onSort, rows, searchDescription, searchTitle, settings, showTimeCol, showFullScreenButton, sort, useNewFieldsApi, isSortEnabled, isPaginationEnabled, cellActionsTriggerId, className, rowHeightState, onUpdateRowHeight, maxAllowedSampleSize, sampleSizeState, onUpdateSampleSize, isPlainRecord, rowsPerPageState, onUpdateRowsPerPage, onFieldEdited, services, renderCustomGridBody, renderCustomToolbar, externalControlColumns, trailingControlColumns, totalHits, onFetchMoreRecords, renderDocumentView, setExpandedDoc, expandedDoc, configRowHeight, showMultiFields, maxDocFieldsDisplayed, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, externalCustomRenderers, additionalFieldGroups, consumer, componentsTourSteps, gridStyleOverride, rowLineHeightOverride, cellActionsMetadata, customGridColumnsConfiguration, enableComparisonMode, cellContext, renderCellPopover, getRowIndicator, }: ", { "pluginId": "@kbn/unified-data-table", "scope": "public", @@ -535,7 +527,7 @@ "id": "def-public.UnifiedDataTable.$1", "type": "Object", "tags": [], - "label": "{\n ariaLabelledBy,\n columns,\n columnsMeta,\n showColumnTokens,\n configHeaderRowHeight,\n headerRowHeightState,\n onUpdateHeaderRowHeight,\n controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,\n dataView,\n loadingState,\n onFilter,\n onResize,\n onSetColumns,\n onSort,\n rows,\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 maxAllowedSampleSize,\n sampleSizeState,\n onUpdateSampleSize,\n isPlainRecord = false,\n rowsPerPageState,\n onUpdateRowsPerPage,\n onFieldEdited,\n services,\n renderCustomGridBody,\n renderCustomToolbar,\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 additionalFieldGroups,\n consumer = 'discover',\n componentsTourSteps,\n gridStyleOverride,\n rowLineHeightOverride,\n cellActionsMetadata,\n customGridColumnsConfiguration,\n customControlColumnsConfiguration,\n enableComparisonMode,\n cellContext,\n renderCellPopover,\n getRowIndicator,\n}", + "label": "{\n ariaLabelledBy,\n columns,\n columnsMeta,\n showColumnTokens,\n configHeaderRowHeight,\n headerRowHeightState,\n onUpdateHeaderRowHeight,\n controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT,\n rowAdditionalLeadingControls,\n dataView,\n loadingState,\n onFilter,\n onResize,\n onSetColumns,\n onSort,\n rows,\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 maxAllowedSampleSize,\n sampleSizeState,\n onUpdateSampleSize,\n isPlainRecord = false,\n rowsPerPageState,\n onUpdateRowsPerPage,\n onFieldEdited,\n services,\n renderCustomGridBody,\n renderCustomToolbar,\n externalControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls\n trailingControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls\n totalHits,\n onFetchMoreRecords,\n renderDocumentView,\n setExpandedDoc,\n expandedDoc,\n configRowHeight,\n showMultiFields = true,\n maxDocFieldsDisplayed = 50,\n externalAdditionalControls,\n rowsPerPageOptions,\n visibleCellActions,\n externalCustomRenderers,\n additionalFieldGroups,\n consumer = 'discover',\n componentsTourSteps,\n gridStyleOverride,\n rowLineHeightOverride,\n cellActionsMetadata,\n customGridColumnsConfiguration,\n enableComparisonMode,\n cellContext,\n renderCellPopover,\n getRowIndicator,\n}", "description": [], "signature": [ { @@ -592,10 +584,10 @@ "interfaces": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumns", + "id": "def-public.CustomGridColumnProps", "type": "Interface", "tags": [], - "label": "ControlColumns", + "label": "CustomGridColumnProps", "description": [], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -603,13 +595,13 @@ "children": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumns.select", + "id": "def-public.CustomGridColumnProps.column", "type": "Object", "tags": [], - "label": "select", + "label": "column", "description": [], "signature": [ - "EuiDataGridControlColumn" + "EuiDataGridColumn" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -617,13 +609,13 @@ }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumns.openDetails", - "type": "Object", + "id": "def-public.CustomGridColumnProps.headerRowHeight", + "type": "number", "tags": [], - "label": "openDetails", + "label": "headerRowHeight", "description": [], "signature": [ - "EuiDataGridControlColumn" + "number | undefined" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -634,10 +626,10 @@ }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumnsProps", + "id": "def-public.RowControlColumn", "type": "Interface", "tags": [], - "label": "ControlColumnsProps", + "label": "RowControlColumn", "description": [], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -645,33 +637,124 @@ "children": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.ControlColumnsProps.controlColumns", - "type": "Object", + "id": "def-public.RowControlColumn.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.headerAriaLabel", + "type": "string", "tags": [], - "label": "controlColumns", + "label": "headerAriaLabel", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.headerCellRender", + "type": "CompoundType", + "tags": [], + "label": "headerCellRender", + "description": [], + "signature": [ + "React.ComponentType<{}> | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.renderControl", + "type": "Function", + "tags": [], + "label": "renderControl", "description": [], "signature": [ + "(Control: ", { "pluginId": "@kbn/unified-data-table", "scope": "public", "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.ControlColumns", - "text": "ControlColumns" - } + "section": "def-public.RowControlComponent", + "text": "RowControlComponent" + }, + ", props: ", + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlRowProps", + "text": "RowControlRowProps" + }, + ") => React.ReactElement>" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.renderControl.$1", + "type": "Function", + "tags": [], + "label": "Control", + "description": [], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlComponent", + "text": "RowControlComponent" + } + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlColumn.renderControl.$2", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlRowProps", + "text": "RowControlRowProps" + } + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomGridColumnProps", + "id": "def-public.RowControlProps", "type": "Interface", "tags": [], - "label": "CustomGridColumnProps", + "label": "RowControlProps", "description": [], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -679,13 +762,13 @@ "children": [ { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomGridColumnProps.column", - "type": "Object", + "id": "def-public.RowControlProps.datatestsubj", + "type": "string", "tags": [], - "label": "column", + "label": "'data-test-subj'", "description": [], "signature": [ - "EuiDataGridColumn" + "string | undefined" ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -693,13 +776,119 @@ }, { "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomGridColumnProps.headerRowHeight", + "id": "def-public.RowControlProps.color", + "type": "CompoundType", + "tags": [], + "label": "color", + "description": [], + "signature": [ + "\"text\" | \"warning\" | \"success\" | \"primary\" | \"accent\" | \"danger\" | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.disabled", + "type": "CompoundType", + "tags": [], + "label": "disabled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.iconType", + "type": "CompoundType", + "tags": [], + "label": "iconType", + "description": [], + "signature": [ + "string | React.ComponentType<{}>" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlProps.onClick", + "type": "Function", + "tags": [], + "label": "onClick", + "description": [], + "signature": [ + "((props: ", + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlRowProps", + "text": "RowControlRowProps" + }, + ") => void) | undefined" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlRowProps", + "type": "Interface", + "tags": [], + "label": "RowControlRowProps", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlRowProps.rowIndex", "type": "number", "tags": [], - "label": "headerRowHeight", + "label": "rowIndex", + "description": [], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlRowProps.record", + "type": "Object", + "tags": [], + "label": "record", "description": [], "signature": [ - "number | undefined" + { + "pluginId": "@kbn/discover-utils", + "scope": "common", + "docId": "kibKbnDiscoverUtilsPluginApi", + "section": "def-common.DataTableRecord", + "text": "DataTableRecord" + } ], "path": "packages/kbn-unified-data-table/src/types.ts", "deprecated": false, @@ -1955,10 +2144,37 @@ "parentPluginId": "@kbn/unified-data-table", "id": "def-public.UnifiedDataTableProps.externalControlColumns", "type": "Array", - "tags": [], + "tags": [ + "deprecated" + ], "label": "externalControlColumns", + "description": [], + "signature": [ + "EuiDataGridControlColumn", + "[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": true, + "trackAdoption": false, + "references": [ + { + "plugin": "cloudSecurityPosture", + "path": "x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.tsx" + } + ] + }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.UnifiedDataTableProps.trailingControlColumns", + "type": "Array", + "tags": [], + "label": "trailingControlColumns", "description": [ - "\nOptional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select." + "\nAn optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid.\nWe recommend to rather position all controls in the beginning of rows and use `rowAdditionalLeadingControls` for that\nas number of columns can be dynamically changed and we don't want the controls to become hidden due to horizontal scroll." ], "signature": [ "EuiDataGridControlColumn", @@ -1968,6 +2184,29 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.UnifiedDataTableProps.rowAdditionalLeadingControls", + "type": "Array", + "tags": [], + "label": "rowAdditionalLeadingControls", + "description": [ + "\nOptional value to extend the list of default row actions" + ], + "signature": [ + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlColumn", + "text": "RowControlColumn" + }, + "[] | undefined" + ], + "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.UnifiedDataTableProps.totalHits", @@ -2093,23 +2332,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.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-public.UnifiedDataTableProps.visibleCellActions", @@ -2203,29 +2425,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.UnifiedDataTableProps.customControlColumnsConfiguration", - "type": "Function", - "tags": [], - "label": "customControlColumnsConfiguration", - "description": [ - "\nAn optional settings to control which columns to render as trailing and leading control columns" - ], - "signature": [ - { - "pluginId": "@kbn/unified-data-table", - "scope": "public", - "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.CustomControlColumnConfiguration", - "text": "CustomControlColumnConfiguration" - }, - " | undefined" - ], - "path": "packages/kbn-unified-data-table/src/components/data_table.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.UnifiedDataTableProps.consumer", @@ -2553,56 +2752,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomControlColumnConfiguration", - "type": "Type", - "tags": [], - "label": "CustomControlColumnConfiguration", - "description": [], - "signature": [ - "(props: ", - { - "pluginId": "@kbn/unified-data-table", - "scope": "public", - "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.ControlColumnsProps", - "text": "ControlColumnsProps" - }, - ") => { leadingControlColumns: ", - "EuiDataGridControlColumn", - "[]; trailingControlColumns?: ", - "EuiDataGridControlColumn", - "[] | undefined; }" - ], - "path": "packages/kbn-unified-data-table/src/types.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "@kbn/unified-data-table", - "id": "def-public.CustomControlColumnConfiguration.$1", - "type": "Object", - "tags": [], - "label": "props", - "description": [], - "signature": [ - { - "pluginId": "@kbn/unified-data-table", - "scope": "public", - "docId": "kibKbnUnifiedDataTablePluginApi", - "section": "def-public.ControlColumnsProps", - "text": "ControlColumnsProps" - } - ], - "path": "packages/kbn-unified-data-table/src/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/unified-data-table", "id": "def-public.CustomGridColumnsConfiguration", @@ -2708,6 +2857,60 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlComponent", + "type": "Type", + "tags": [], + "label": "RowControlComponent", + "description": [], + "signature": [ + "React.FunctionComponent<", + { + "pluginId": "@kbn/unified-data-table", + "scope": "public", + "docId": "kibKbnUnifiedDataTablePluginApi", + "section": "def-public.RowControlProps", + "text": "RowControlProps" + }, + ">" + ], + "path": "packages/kbn-unified-data-table/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/unified-data-table", + "id": "def-public.RowControlComponent.$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/unified-data-table", + "id": "def-public.RowControlComponent.$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/unified-data-table", "id": "def-public.SELECT_ROW", diff --git a/api_docs/kbn_unified_data_table.mdx b/api_docs/kbn_unified_data_table.mdx index 02a286e80f249..0d678dee656b2 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-data-table'] --- import kbnUnifiedDataTableObj from './kbn_unified_data_table.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 | |-------------------|-----------|------------------------|-----------------| -| 154 | 0 | 81 | 1 | +| 166 | 0 | 92 | 2 | ## Client diff --git a/api_docs/kbn_unified_doc_viewer.mdx b/api_docs/kbn_unified_doc_viewer.mdx index 7c57764f5f633..dd40ab84f750a 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-doc-viewer'] --- import kbnUnifiedDocViewerObj from './kbn_unified_doc_viewer.devdocs.json'; diff --git a/api_docs/kbn_unified_field_list.devdocs.json b/api_docs/kbn_unified_field_list.devdocs.json index 408aed745f51c..ccbed1bcf4edb 100644 --- a/api_docs/kbn_unified_field_list.devdocs.json +++ b/api_docs/kbn_unified_field_list.devdocs.json @@ -2203,7 +2203,7 @@ "section": "def-public.CoreStart", "text": "CoreStart" }, - ", \"uiSettings\" | \"analytics\">; data: ", + ", \"analytics\" | \"uiSettings\">; data: ", { "pluginId": "data", "scope": "public", diff --git a/api_docs/kbn_unified_field_list.mdx b/api_docs/kbn_unified_field_list.mdx index ef43dc8a26d00..59912836c80d2 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index fd8203e8416cc..8093cd5714992 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_prompt.mdx b/api_docs/kbn_unsaved_changes_prompt.mdx index b7e0c373a71c4..f5b13b98e6dd1 100644 --- a/api_docs/kbn_unsaved_changes_prompt.mdx +++ b/api_docs/kbn_unsaved_changes_prompt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-prompt title: "@kbn/unsaved-changes-prompt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-prompt plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-prompt'] --- import kbnUnsavedChangesPromptObj from './kbn_unsaved_changes_prompt.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index eecba58bee1e9..aa9725cbfdc65 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: 2024-07-31 +date: 2024-08-09 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 ddc43f4a20ddb..5f38a00aac58b 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: 2024-07-31 +date: 2024-08-09 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.mdx b/api_docs/kbn_utility_types.mdx index 1bd54fa05b410..cf4dd152185e4 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 5b3282ae53511..6911a2336aa89 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: 2024-07-31 +date: 2024-08-09 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 af072dcd84be0..69395d6553f8f 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: 2024-07-31 +date: 2024-08-09 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 af876c9fb9162..654a50048ed8c 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index c7d6a35813cca..168f64dc4d557 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index 27530bc3b18b9..f5de7e0e3da0b 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index f4d2ffb84418f..4ce37322d2d25 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kbn_zod.mdx b/api_docs/kbn_zod.mdx index 8d5019013a074..e5f422bc3fbcc 100644 --- a/api_docs/kbn_zod.mdx +++ b/api_docs/kbn_zod.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod title: "@kbn/zod" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod'] --- import kbnZodObj from './kbn_zod.devdocs.json'; diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index cce4d5f49b71c..e7f46a8e5d907 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 749249956e629..57ae1ecf77fdb 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index b72630cf865ec..5482a932c3c9c 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: 2024-07-31 +date: 2024-08-09 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 8a83a2c7f7e08..b575b25792a44 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: 2024-07-31 +date: 2024-08-09 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 84cd8defd9c1d..368833dcf20b7 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 5ddbda38d0597..8c9e398a47960 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 973d4071627bd..9f92581905720 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: 2024-07-31 +date: 2024-08-09 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 7a6912604a729..e4831463c9857 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: 2024-07-31 +date: 2024-08-09 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 cfbe979c479d9..f666e8ff207fc 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/links.mdx b/api_docs/links.mdx index 3c2d62943f21b..d9c77df8d9daa 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'links'] --- import linksObj from './links.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 6316f62bf017e..59b9402b2b316 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_data_access.devdocs.json b/api_docs/logs_data_access.devdocs.json index c695eedcacdbf..4098b0950c6f1 100644 --- a/api_docs/logs_data_access.devdocs.json +++ b/api_docs/logs_data_access.devdocs.json @@ -6,7 +6,41 @@ "interfaces": [], "enums": [], "misc": [], - "objects": [] + "objects": [], + "setup": { + "parentPluginId": "logsDataAccess", + "id": "def-public.LogsDataAccessPluginSetup", + "type": "Type", + "tags": [], + "label": "LogsDataAccessPluginSetup", + "description": [], + "signature": [ + "void" + ], + "path": "x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "logsDataAccess", + "id": "def-public.LogsDataAccessPluginStart", + "type": "Type", + "tags": [], + "label": "LogsDataAccessPluginStart", + "description": [], + "signature": [ + "{ services: { logSourcesService: ", + "LogSourcesService", + "; }; }" + ], + "path": "x-pack/plugins/observability_solution/logs_data_access/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "lifecycle": "start", + "initialIsOpen": true + } }, "server": { "classes": [], @@ -132,7 +166,17 @@ "LogsErrorRateTimeseries", ") => Promise<", "LogsErrorRateTimeseriesReturnType", - ">; getLogSourcesService: (request: ", + ">; logSourcesServiceFactory: { getLogSourcesService(savedObjectsClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "server", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-server.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + "): Promise<", + "LogSourcesService", + ">; getScopedLogSourcesService(request: ", { "pluginId": "@kbn/core-http-server", "scope": "server", @@ -140,11 +184,9 @@ "section": "def-server.KibanaRequest", "text": "KibanaRequest" }, - ") => Promise<{ getLogSources: () => Promise<", - "LogSource", - "[]>; setLogSources: (sources: ", - "LogSource", - "[]) => Promise; }>; }; }" + "): Promise<", + "LogSourcesService", + ">; }; }; }" ], "path": "x-pack/plugins/observability_solution/logs_data_access/server/plugin.ts", "deprecated": false, diff --git a/api_docs/logs_data_access.mdx b/api_docs/logs_data_access.mdx index c3960b87f142f..3c859a51fc723 100644 --- a/api_docs/logs_data_access.mdx +++ b/api_docs/logs_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsDataAccess title: "logsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the logsDataAccess plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsDataAccess'] --- import logsDataAccessObj from './logs_data_access.devdocs.json'; @@ -21,7 +21,15 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 7 | 0 | 7 | 6 | +| 9 | 0 | 9 | 6 | + +## Client + +### Setup + + +### Start + ## Server diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index 85e1147bb218b..df1ba1c191a82 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.devdocs.json b/api_docs/logs_shared.devdocs.json index d0c2a280e1eab..32758ecf4558d 100644 --- a/api_docs/logs_shared.devdocs.json +++ b/api_docs/logs_shared.devdocs.json @@ -11,7 +11,7 @@ "label": "getLogViewReferenceFromUrl", "description": [], "signature": [ - "({ logViewKey, sourceIdKey, toastsService, urlStateStorage, }: LogViewUrlStateDependencies) => { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | null" + "({ logViewKey, sourceIdKey, toastsService, urlStateStorage, }: LogViewUrlStateDependencies) => { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | null" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/observability_logs/log_view_state/src/url_state_storage_service.ts", "deprecated": false, @@ -840,7 +840,7 @@ "label": "LogViewProvider", "description": [], "signature": [ - "React.FunctionComponent; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", + ">; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", { "pluginId": "logsShared", "scope": "common", @@ -1918,7 +1918,7 @@ "BaseActionObject", ", ", "ServiceMap", - ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" + ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/hooks/use_log_view.ts", "deprecated": false, @@ -1943,7 +1943,7 @@ "label": "initialLogViewReference", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/hooks/use_log_view.ts", "deprecated": false, @@ -2343,7 +2343,7 @@ "section": "def-public.LogViewNotificationEvent", "text": "LogViewNotificationEvent" }, - ">; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", + ">; hasFailedLoading: boolean; hasFailedLoadingLogView: boolean; hasFailedLoadingLogViewStatus: boolean; hasFailedResolvingLogView: boolean; latestLoadLogViewFailures: Error[]; isUninitialized: boolean; isLoading: boolean; isLoadingLogView: boolean; isLoadingLogViewStatus: boolean; isResolvingLogView: boolean; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; logView: ({ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }) | undefined; resolvedLogView: ", { "pluginId": "logsShared", "scope": "common", @@ -2499,7 +2499,7 @@ "BaseActionObject", ", ", "ServiceMap", - ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" + ">>; update: (logViewAttributes: Partial<{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }>) => Promise; changeLogViewReference: (logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }) => void; revertToDefaultLogView: () => void; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/public/hooks/use_log_view.ts", "deprecated": false, @@ -2905,6 +2905,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "logsShared", + "id": "def-public.LogsSharedClientStartDeps.logsDataAccess", + "type": "Object", + "tags": [], + "label": "logsDataAccess", + "description": [], + "signature": [ + "{ services: { logSourcesService: ", + "LogSourcesService", + "; }; }" + ], + "path": "x-pack/plugins/observability_solution/logs_shared/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "logsShared", "id": "def-public.LogsSharedClientStartDeps.observabilityAIAssistant", @@ -3345,7 +3361,7 @@ "label": "LogViewNotificationEvent", "description": [], "signature": [ - "{ type: \"LOADING_LOG_VIEW_STARTED\"; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; } | { type: \"LOADING_LOG_VIEW_SUCCEEDED\"; resolvedLogView: ", + "{ type: \"LOADING_LOG_VIEW_STARTED\"; logViewReference: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }; } | { type: \"LOADING_LOG_VIEW_SUCCEEDED\"; resolvedLogView: ", { "pluginId": "logsShared", "scope": "common", @@ -3578,7 +3594,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesAroundParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -3631,7 +3647,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -3687,7 +3703,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -3740,7 +3756,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -3796,7 +3812,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -3839,7 +3855,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -3932,7 +3948,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -3975,7 +3991,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4223,7 +4239,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesAroundParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -4276,7 +4292,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4332,7 +4348,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, params: ", "LogEntriesParams", ", columnOverrides?: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[] | undefined) => Promise<{ entries: { id: string; index: string; cursor: { time: string; tiebreaker: number; }; columns: ({ columnId: string; time: string; } | { columnId: string; field: string; value: ", { @@ -4385,7 +4401,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4441,7 +4457,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, start: number, end: number, bucketSize: number, filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -4484,7 +4500,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -4577,7 +4593,7 @@ "section": "def-server.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", + ", logView: { logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }, startTimestamp: number, endTimestamp: number, bucketSize: number, highlightQueries: string[], filterQuery?: ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -4620,7 +4636,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts", "deprecated": false, @@ -5476,7 +5492,7 @@ "label": "logView", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } | undefined" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/locators/types.ts", "deprecated": false, @@ -6093,7 +6109,7 @@ "label": "LogIndexReference", "description": [], "signature": [ - "{ type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }" + "{ type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6199,6 +6215,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "logsShared", + "id": "def-common.LogSourcesKibanaAdvancedSettingReference", + "type": "Type", + "tags": [], + "label": "LogSourcesKibanaAdvancedSettingReference", + "description": [], + "signature": [ + "{ type: \"kibana_advanced_setting\"; }" + ], + "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "logsShared", "id": "def-common.LogTimestampColumn", @@ -6222,7 +6253,7 @@ "label": "LogView", "description": [], "signature": [ - "{ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }" + "{ id: string; origin: \"internal\" | \"inline\" | \"stored\" | \"infra-source-stored\" | \"infra-source-internal\" | \"infra-source-fallback\"; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; } & { updatedAt?: number | undefined; version?: string | undefined; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6237,7 +6268,7 @@ "label": "LogViewAttributes", "description": [], "signature": [ - "{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }" + "{ name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6267,7 +6298,7 @@ "label": "LogViewReference", "description": [], "signature": [ - "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" + "{ logViewId: string; type: \"log-view-reference\"; } | { type: \"log-view-inline\"; id: string; attributes: { name: string; description: string; logIndices: { type: \"data_view\"; dataViewId: string; } | { type: \"index_name\"; indexName: string; } | { type: \"kibana_advanced_setting\"; }; logColumns: ({ timestampColumn: { id: string; }; } | { messageColumn: { id: string; }; } | { fieldColumn: { id: string; } & { field: string; }; })[]; }; }" ], "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", "deprecated": false, @@ -6704,7 +6735,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -6848,7 +6883,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -7004,7 +7043,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -7160,7 +7203,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -7570,7 +7617,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", @@ -8163,6 +8214,24 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "logsShared", + "id": "def-common.logSourcesKibanaAdvancedSettingRT", + "type": "Object", + "tags": [], + "label": "logSourcesKibanaAdvancedSettingRT", + "description": [], + "signature": [ + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>" + ], + "path": "x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "logsShared", "id": "def-common.logTimestampColumnRT", @@ -8282,7 +8351,11 @@ "LiteralC", "<\"index_name\">; indexName: ", "StringC", - "; }>]>; logColumns: ", + "; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"kibana_advanced_setting\">; }>]>; logColumns: ", "ArrayC", "<", "UnionC", diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index ab5bc47310224..21900bf317223 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 297 | 0 | 269 | 32 | +| 300 | 0 | 272 | 32 | ## Client diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 5cd7585d22877..0c26d51706f47 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 12ac5e462cb7e..3521c63bfd0c5 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 7f6101dcf82bb..debc3f409b82d 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: 2024-07-31 +date: 2024-08-09 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 index 6f8f384c313d1..786c41c318074 100644 --- a/api_docs/metrics_data_access.devdocs.json +++ b/api_docs/metrics_data_access.devdocs.json @@ -745,7 +745,7 @@ "label": "hostSnapshotMetricTypes", "description": [], "signature": [ - "(\"memory\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"logRate\" | \"cpu\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\")[]" + "(\"memory\" | \"logRate\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"cpuTotal\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"rxV2\" | \"txV2\")[]" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts", "deprecated": false, @@ -790,7 +790,7 @@ "label": "InventoryMetric", "description": [], "signature": [ - "\"custom\" | \"hostSystemOverview\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"" + "\"custom\" | \"hostSystemOverview\" | \"hostCpuUsageTotal\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1522,7 +1522,7 @@ "label": "SnapshotMetricType", "description": [], "signature": [ - "\"count\" | \"memory\" | \"custom\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"logRate\" | \"cpu\" | \"s3BucketSize\" | \"s3NumberOfObjects\" | \"s3TotalRequests\" | \"s3UploadBytes\" | \"s3DownloadBytes\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\" | \"rdsLatency\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\" | \"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesEmpty\" | \"sqsMessagesSent\" | \"sqsOldestMessage\"" + "\"count\" | \"memory\" | \"custom\" | \"logRate\" | \"rx\" | \"normalizedLoad1m\" | \"memoryFree\" | \"tx\" | \"cpu\" | \"s3BucketSize\" | \"s3NumberOfObjects\" | \"s3TotalRequests\" | \"s3UploadBytes\" | \"s3DownloadBytes\" | \"cpuTotal\" | \"diskLatency\" | \"diskSpaceUsage\" | \"load\" | \"memoryTotal\" | \"rxV2\" | \"txV2\" | \"diskIOReadBytes\" | \"diskIOWriteBytes\" | \"rdsLatency\" | \"rdsConnections\" | \"rdsQueriesExecuted\" | \"rdsActiveTransactions\" | \"sqsMessagesVisible\" | \"sqsMessagesDelayed\" | \"sqsMessagesEmpty\" | \"sqsMessagesSent\" | \"sqsOldestMessage\"" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1537,7 +1537,7 @@ "label": "TSVBMetricModel", "description": [], "signature": [ - "{ id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cumulative_sum\" | \"derivative\" | \"cardinality\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" + "{ id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsageTotal\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cardinality\" | \"cumulative_sum\" | \"derivative\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1552,7 +1552,7 @@ "label": "TSVBMetricModelCreator", "description": [], "signature": [ - "(timeField: string, indexPattern: string | string[], interval: string) => { id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cumulative_sum\" | \"derivative\" | \"cardinality\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" + "(timeField: string, indexPattern: string | string[], interval: string) => { id: \"custom\" | \"hostSystemOverview\" | \"hostCpuUsageTotal\" | \"hostCpuUsage\" | \"hostFilesystem\" | \"hostK8sOverview\" | \"hostK8sCpuCap\" | \"hostK8sDiskCap\" | \"hostK8sMemoryCap\" | \"hostK8sPodCap\" | \"hostLoad\" | \"hostMemoryUsage\" | \"hostNetworkTraffic\" | \"hostDockerOverview\" | \"hostDockerInfo\" | \"hostDockerTop5ByCpu\" | \"hostDockerTop5ByMemory\" | \"podOverview\" | \"podCpuUsage\" | \"podMemoryUsage\" | \"podLogUsage\" | \"podNetworkTraffic\" | \"containerOverview\" | \"containerCpuKernel\" | \"containerCpuUsage\" | \"containerDiskIOOps\" | \"containerDiskIOBytes\" | \"containerMemory\" | \"containerNetworkTraffic\" | \"containerK8sOverview\" | \"containerK8sCpuUsage\" | \"containerK8sMemoryUsage\" | \"nginxHits\" | \"nginxRequestRate\" | \"nginxActiveConnections\" | \"nginxRequestsPerConnection\" | \"awsOverview\" | \"awsCpuUtilization\" | \"awsNetworkBytes\" | \"awsNetworkPackets\" | \"awsDiskioBytes\" | \"awsDiskioOps\" | \"awsEC2CpuUtilization\" | \"awsEC2NetworkTraffic\" | \"awsEC2DiskIOBytes\" | \"awsS3TotalRequests\" | \"awsS3NumberOfObjects\" | \"awsS3BucketSize\" | \"awsS3DownloadBytes\" | \"awsS3UploadBytes\" | \"awsRDSCpuTotal\" | \"awsRDSConnections\" | \"awsRDSQueriesExecuted\" | \"awsRDSActiveTransactions\" | \"awsRDSLatency\" | \"awsSQSMessagesVisible\" | \"awsSQSMessagesDelayed\" | \"awsSQSMessagesSent\" | \"awsSQSMessagesEmpty\" | \"awsSQSOldestMessage\"; requires: string[]; index_pattern: string | string[]; interval: string; time_field: string; type: string; series: ({ id: string; metrics: ({ id: string; type: \"count\"; } | ({ id: string; type: \"min\" | \"max\" | \"sum\" | \"avg\" | \"count\" | \"cardinality\" | \"cumulative_sum\" | \"derivative\" | \"calculation\" | \"series_agg\" | \"positive_only\"; } & { field?: string | undefined; }) | { id: string; script: string; type: \"calculation\"; variables: { field: string; id: string; name: string; }[]; } | { id: string; field: string; unit: string; type: \"derivative\"; } | ({ id: string; type: \"percentile\"; percentiles: { id: string; value: number; }[]; } & { field?: string | undefined; }) | { id: string; function: string; type: \"series_agg\"; })[]; split_mode: string; } & { terms_field?: string | undefined; terms_size?: number | undefined; terms_order_by?: string | undefined; filter?: { query: string; language: \"kuery\" | \"lucene\"; } | undefined; })[]; } & { filter?: string | undefined; map_field_to?: string | undefined; id_type?: \"cloud\" | \"node\" | undefined; drop_last_bucket?: boolean | undefined; }" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -1715,7 +1715,7 @@ "description": [], "signature": [ "KeyofC", - "<{ hostSystemOverview: null; hostCpuUsage: null; hostFilesystem: null; hostK8sOverview: null; hostK8sCpuCap: null; hostK8sDiskCap: null; hostK8sMemoryCap: null; hostK8sPodCap: null; hostLoad: null; hostMemoryUsage: null; hostNetworkTraffic: null; hostDockerOverview: null; hostDockerInfo: null; hostDockerTop5ByCpu: null; hostDockerTop5ByMemory: null; podOverview: null; podCpuUsage: null; podMemoryUsage: null; podLogUsage: null; podNetworkTraffic: null; containerOverview: null; containerCpuKernel: null; containerCpuUsage: null; containerDiskIOOps: null; containerDiskIOBytes: null; containerMemory: null; containerNetworkTraffic: null; containerK8sOverview: null; containerK8sCpuUsage: null; containerK8sMemoryUsage: null; nginxHits: null; nginxRequestRate: null; nginxActiveConnections: null; nginxRequestsPerConnection: null; awsOverview: null; awsCpuUtilization: null; awsNetworkBytes: null; awsNetworkPackets: null; awsDiskioBytes: null; awsDiskioOps: null; awsEC2CpuUtilization: null; awsEC2NetworkTraffic: null; awsEC2DiskIOBytes: null; awsS3TotalRequests: null; awsS3NumberOfObjects: null; awsS3BucketSize: null; awsS3DownloadBytes: null; awsS3UploadBytes: null; awsRDSCpuTotal: null; awsRDSConnections: null; awsRDSQueriesExecuted: null; awsRDSActiveTransactions: null; awsRDSLatency: null; awsSQSMessagesVisible: null; awsSQSMessagesDelayed: null; awsSQSMessagesSent: null; awsSQSMessagesEmpty: null; awsSQSOldestMessage: null; custom: null; }>" + "<{ hostSystemOverview: null; hostCpuUsageTotal: null; hostCpuUsage: null; hostFilesystem: null; hostK8sOverview: null; hostK8sCpuCap: null; hostK8sDiskCap: null; hostK8sMemoryCap: null; hostK8sPodCap: null; hostLoad: null; hostMemoryUsage: null; hostNetworkTraffic: null; hostDockerOverview: null; hostDockerInfo: null; hostDockerTop5ByCpu: null; hostDockerTop5ByMemory: null; podOverview: null; podCpuUsage: null; podMemoryUsage: null; podLogUsage: null; podNetworkTraffic: null; containerOverview: null; containerCpuKernel: null; containerCpuUsage: null; containerDiskIOOps: null; containerDiskIOBytes: null; containerMemory: null; containerNetworkTraffic: null; containerK8sOverview: null; containerK8sCpuUsage: null; containerK8sMemoryUsage: null; nginxHits: null; nginxRequestRate: null; nginxActiveConnections: null; nginxRequestsPerConnection: null; awsOverview: null; awsCpuUtilization: null; awsNetworkBytes: null; awsNetworkPackets: null; awsDiskioBytes: null; awsDiskioOps: null; awsEC2CpuUtilization: null; awsEC2NetworkTraffic: null; awsEC2DiskIOBytes: null; awsS3TotalRequests: null; awsS3NumberOfObjects: null; awsS3BucketSize: null; awsS3DownloadBytes: null; awsS3UploadBytes: null; awsRDSCpuTotal: null; awsRDSConnections: null; awsRDSQueriesExecuted: null; awsRDSActiveTransactions: null; awsRDSLatency: null; awsSQSMessagesVisible: null; awsSQSMessagesDelayed: null; awsSQSMessagesSent: null; awsSQSMessagesEmpty: null; awsSQSOldestMessage: null; custom: null; }>" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, @@ -2064,6 +2064,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-common.SnapshotMetricTypeKeys.cpuTotal", + "type": "Uncategorized", + "tags": [], + "label": "cpuTotal", + "description": [], + "signature": [ + "null" + ], + "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "metricsDataAccess", "id": "def-common.SnapshotMetricTypeKeys.cpu", @@ -2204,6 +2218,34 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-common.SnapshotMetricTypeKeys.txV2", + "type": "Uncategorized", + "tags": [], + "label": "txV2", + "description": [], + "signature": [ + "null" + ], + "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "metricsDataAccess", + "id": "def-common.SnapshotMetricTypeKeys.rxV2", + "type": "Uncategorized", + "tags": [], + "label": "rxV2", + "description": [], + "signature": [ + "null" + ], + "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "metricsDataAccess", "id": "def-common.SnapshotMetricTypeKeys.logRate", @@ -2468,7 +2510,7 @@ "description": [], "signature": [ "KeyofC", - "<{ count: null; cpu: null; diskLatency: null; diskSpaceUsage: null; load: null; memory: null; memoryFree: null; memoryTotal: null; normalizedLoad1m: 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; }>" + "<{ count: null; cpuTotal: null; cpu: null; diskLatency: null; diskSpaceUsage: null; load: null; memory: null; memoryFree: null; memoryTotal: null; normalizedLoad1m: null; tx: null; rx: null; txV2: null; rxV2: 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; }>" ], "path": "x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts", "deprecated": false, diff --git a/api_docs/metrics_data_access.mdx b/api_docs/metrics_data_access.mdx index 0e3285a0ece95..6e3d77f031903 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs- | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 105 | 8 | 105 | 6 | +| 108 | 8 | 108 | 6 | ## Client diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 3e6cc39d1e3fc..a5fd03d22f8be 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index 85db8c0d75c82..5e7b080d1c64c 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index bf0a0e7711bbc..8b241b5c6f388 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: 2024-07-31 +date: 2024-08-09 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 2e4e3940de656..f8af3c0b1736e 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.devdocs.json b/api_docs/navigation.devdocs.json index 9e1321c642012..847c0acfbe791 100644 --- a/api_docs/navigation.devdocs.json +++ b/api_docs/navigation.devdocs.json @@ -98,7 +98,7 @@ "label": "setup", "description": [], "signature": [ - "(_core: ", + "(core: ", { "pluginId": "@kbn/core-lifecycle-browser", "scope": "public", @@ -124,7 +124,7 @@ "id": "def-public.NavigationPublicPlugin.setup.$1", "type": "Object", "tags": [], - "label": "_core", + "label": "core", "description": [], "signature": [ { @@ -734,6 +734,34 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "navigation", + "id": "def-public.TopNavMenuData.fill", + "type": "CompoundType", + "tags": [], + "label": "fill", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "navigation", + "id": "def-public.TopNavMenuData.color", + "type": "string", + "tags": [], + "label": "color", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "navigation", "id": "def-public.TopNavMenuData.isLoading", @@ -1173,13 +1201,13 @@ "misc": [ { "parentPluginId": "navigation", - "id": "def-common.SOLUTION_NAV_FEATURE_FLAG_NAME", + "id": "def-common.DEFAULT_ROUTE_UI_SETTING_ID", "type": "string", "tags": [], - "label": "SOLUTION_NAV_FEATURE_FLAG_NAME", + "label": "DEFAULT_ROUTE_UI_SETTING_ID", "description": [], "signature": [ - "\"solutionNavEnabled\"" + "\"defaultRoute\"" ], "path": "src/plugins/navigation/common/constants.ts", "deprecated": false, @@ -1187,6 +1215,65 @@ "initialIsOpen": false } ], - "objects": [] + "objects": [ + { + "parentPluginId": "navigation", + "id": "def-common.DEFAULT_ROUTES", + "type": "Object", + "tags": [], + "label": "DEFAULT_ROUTES", + "description": [], + "path": "src/plugins/navigation/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "navigation", + "id": "def-common.DEFAULT_ROUTES.classic", + "type": "string", + "tags": [], + "label": "classic", + "description": [], + "path": "src/plugins/navigation/common/constants.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "navigation", + "id": "def-common.DEFAULT_ROUTES.es", + "type": "string", + "tags": [], + "label": "es", + "description": [], + "path": "src/plugins/navigation/common/constants.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "navigation", + "id": "def-common.DEFAULT_ROUTES.oblt", + "type": "string", + "tags": [], + "label": "oblt", + "description": [], + "path": "src/plugins/navigation/common/constants.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "navigation", + "id": "def-common.DEFAULT_ROUTES.security", + "type": "string", + "tags": [], + "label": "security", + "description": [], + "path": "src/plugins/navigation/common/constants.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ] } } \ No newline at end of file diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 44cd383ea44d2..0ac7656423f6c 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.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 | |-------------------|-----------|------------------------|-----------------| -| 49 | 0 | 47 | 4 | +| 56 | 0 | 54 | 4 | ## Client @@ -45,6 +45,9 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh ## Common +### Objects + + ### Consts, variables and types diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index a6abb78971590..8ad140c1d9143 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: 2024-07-31 +date: 2024-08-09 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 f770c3ceb6639..34e4ced8c545f 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: 2024-07-31 +date: 2024-08-09 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 bb8e3ea372653..cd598758d56be 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: 2024-07-31 +date: 2024-08-09 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 a7a5d85326671..472391e133e2d 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -787,7 +787,7 @@ }, " | undefined; list: () => string[]; }; selectedAlertId?: string | undefined; } & ", "CommonProps", - " & { as?: \"div\" | undefined; } & _EuiFlyoutProps & Omit, HTMLDivElement>, keyof _EuiFlyoutProps> & Omit, HTMLDivElement>, \"key\" | keyof React.HTMLAttributes | \"css\"> & { ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject | null | undefined; }, \"id\" | \"key\" | \"prefix\" | \"type\" | \"onError\" | \"defaultValue\" | \"security\" | \"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\" | \"children\" | \"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\" | \"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\" | \"data-test-subj\" | \"css\" | \"paddingSize\" | \"size\" | \"onClose\" | \"ref\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"pushAnimation\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\" | \"as\">, \"id\" | \"key\" | \"prefix\" | \"type\" | \"onError\" | \"defaultValue\" | \"security\" | \"alert\" | \"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\" | \"children\" | \"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\" | \"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\" | \"data-test-subj\" | \"css\" | \"alerts\" | \"paddingSize\" | \"size\" | \"onClose\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"pushAnimation\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\" | \"as\" | \"rawAlert\" | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject | null | undefined; }> & { readonly _result: ({ alert, rawAlert, 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; }, \"id\" | \"key\" | \"prefix\" | \"type\" | \"onError\" | \"defaultValue\" | \"security\" | \"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\" | \"children\" | \"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\" | \"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\" | \"data-test-subj\" | \"css\" | \"size\" | \"paddingSize\" | \"onClose\" | \"ref\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"pushAnimation\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\" | \"as\">, \"id\" | \"key\" | \"prefix\" | \"type\" | \"onError\" | \"defaultValue\" | \"security\" | \"alert\" | \"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\" | \"children\" | \"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\" | \"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\" | \"data-test-subj\" | \"css\" | \"alerts\" | \"size\" | \"paddingSize\" | \"onClose\" | \"maxWidth\" | \"ownFocus\" | \"hideCloseButton\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"pushAnimation\" | \"focusTrapProps\" | \"includeFixedHeadersInFocusTrap\" | \"as\" | \"rawAlert\" | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: ((instance: HTMLDivElement | null) => void) | React.RefObject | null | undefined; }> & { readonly _result: ({ alert, rawAlert, alerts, isInApp, observabilityRuleTypeRegistry, onClose, selectedAlertId, }: AlertsFlyoutProps) => JSX.Element | null; }" ], "path": "x-pack/plugins/observability_solution/observability/public/index.ts", "deprecated": false, @@ -1042,7 +1042,7 @@ "label": "useAnnotations", "description": [], "signature": [ - "({ domain, editAnnotation, slo, setEditAnnotation, }?: { slo?: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }) | undefined; editAnnotation?: ({ id: string; } & { annotation: { title?: string | undefined; type?: string | undefined; style?: { icon?: string | undefined; color?: string | undefined; line?: { width?: number | undefined; style?: \"dashed\" | \"solid\" | \"dotted\" | undefined; iconPosition?: \"top\" | \"bottom\" | undefined; textDecoration?: \"none\" | \"name\" | undefined; } | undefined; rect?: { fill?: \"inside\" | \"outside\" | undefined; } | undefined; } | undefined; }; '@timestamp': string; message: string; } & { event?: ({ start: string; } & { end?: string | undefined; }) | undefined; tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; monitor?: { id?: string | undefined; } | undefined; slo?: ({ id: string; } & { instanceId?: string | undefined; }) | undefined; host?: { name?: string | undefined; } | undefined; }) | null | undefined; setEditAnnotation?: ((annotation: ({ id: string; } & { annotation: { title?: string | undefined; type?: string | undefined; style?: { icon?: string | undefined; color?: string | undefined; line?: { width?: number | undefined; style?: \"dashed\" | \"solid\" | \"dotted\" | undefined; iconPosition?: \"top\" | \"bottom\" | undefined; textDecoration?: \"none\" | \"name\" | undefined; } | undefined; rect?: { fill?: \"inside\" | \"outside\" | undefined; } | undefined; } | undefined; }; '@timestamp': string; message: string; } & { event?: ({ start: string; } & { end?: string | undefined; }) | undefined; tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; monitor?: { id?: string | undefined; } | undefined; slo?: ({ id: string; } & { instanceId?: string | undefined; }) | undefined; host?: { name?: string | undefined; } | undefined; }) | null) => void) | undefined; domain?: { min: string | number; max: string | number; } | undefined; }) => { annotations: ({ id: string; } & { annotation: { title?: string | undefined; type?: string | undefined; style?: { icon?: string | undefined; color?: string | undefined; line?: { width?: number | undefined; style?: \"dashed\" | \"solid\" | \"dotted\" | undefined; iconPosition?: \"top\" | \"bottom\" | undefined; textDecoration?: \"none\" | \"name\" | undefined; } | undefined; rect?: { fill?: \"inside\" | \"outside\" | undefined; } | undefined; } | undefined; }; '@timestamp': string; message: string; } & { event?: ({ start: string; } & { end?: string | undefined; }) | undefined; tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; monitor?: { id?: string | undefined; } | undefined; slo?: ({ id: string; } & { instanceId?: string | undefined; }) | undefined; host?: { name?: string | undefined; } | undefined; })[]; onAnnotationClick: (annotations: { rects: ", + "({ domain, editAnnotation, slo, setEditAnnotation, }?: { slo?: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }) | undefined; editAnnotation?: ({ id: string; } & { annotation: { title?: string | undefined; type?: string | undefined; style?: { icon?: string | undefined; color?: string | undefined; line?: { width?: number | undefined; style?: \"dashed\" | \"solid\" | \"dotted\" | undefined; iconPosition?: \"top\" | \"bottom\" | undefined; textDecoration?: \"none\" | \"name\" | undefined; } | undefined; rect?: { fill?: \"inside\" | \"outside\" | undefined; } | undefined; } | undefined; }; '@timestamp': string; message: string; } & { event?: ({ start: string; } & { end?: string | undefined; }) | undefined; tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; monitor?: { id?: string | undefined; } | undefined; slo?: ({ id: string; } & { instanceId?: string | undefined; }) | undefined; host?: { name?: string | undefined; } | undefined; }) | null | undefined; setEditAnnotation?: ((annotation: ({ id: string; } & { annotation: { title?: string | undefined; type?: string | undefined; style?: { icon?: string | undefined; color?: string | undefined; line?: { width?: number | undefined; style?: \"dashed\" | \"solid\" | \"dotted\" | undefined; iconPosition?: \"top\" | \"bottom\" | undefined; textDecoration?: \"none\" | \"name\" | undefined; } | undefined; rect?: { fill?: \"inside\" | \"outside\" | undefined; } | undefined; } | undefined; }; '@timestamp': string; message: string; } & { event?: ({ start: string; } & { end?: string | undefined; }) | undefined; tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; monitor?: { id?: string | undefined; } | undefined; slo?: ({ id: string; } & { instanceId?: string | undefined; }) | undefined; host?: { name?: string | undefined; } | undefined; }) | null) => void) | undefined; domain?: { min: string | number; max: string | number; } | undefined; }) => { annotations: ({ id: string; } & { annotation: { title?: string | undefined; type?: string | undefined; style?: { icon?: string | undefined; color?: string | undefined; line?: { width?: number | undefined; style?: \"dashed\" | \"solid\" | \"dotted\" | undefined; iconPosition?: \"top\" | \"bottom\" | undefined; textDecoration?: \"none\" | \"name\" | undefined; } | undefined; rect?: { fill?: \"inside\" | \"outside\" | undefined; } | undefined; } | undefined; }; '@timestamp': string; message: string; } & { event?: ({ start: string; } & { end?: string | undefined; }) | undefined; tags?: string[] | undefined; service?: { name?: string | undefined; environment?: string | undefined; version?: string | undefined; } | undefined; monitor?: { id?: string | undefined; } | undefined; slo?: ({ id: string; } & { instanceId?: string | undefined; }) | undefined; host?: { name?: string | undefined; } | undefined; })[]; onAnnotationClick: (annotations: { rects: ", "RectAnnotationEvent", "[]; lines: ", "LineAnnotationEvent", @@ -1079,7 +1079,7 @@ "label": "slo", "description": [], "signature": [ - "({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }) | undefined" + "({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }) | undefined" ], "path": "x-pack/plugins/observability_solution/observability/public/components/annotations/use_annotations.tsx", "deprecated": false, @@ -4785,7 +4785,7 @@ "label": "ObservabilityFetchDataPlugins", "description": [], "signature": [ - "\"uptime\" | \"ux\" | \"infra_logs\" | \"infra_metrics\" | \"apm\" | \"universal_profiling\"" + "\"uptime\" | \"apm\" | \"ux\" | \"infra_logs\" | \"infra_metrics\" | \"universal_profiling\"" ], "path": "x-pack/plugins/observability_solution/observability/public/typings/fetch_overview_data/index.ts", "deprecated": false, diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 95163a8cdc78e..dfeb8dd25f1c9 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 701 | 2 | 694 | 21 | +| 701 | 2 | 694 | 22 | ## Client diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index 53272cae2b032..542a4ff27ca5d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant_app.mdx b/api_docs/observability_a_i_assistant_app.mdx index 4734c8ba1ef93..e2e99cac8af92 100644 --- a/api_docs/observability_a_i_assistant_app.mdx +++ b/api_docs/observability_a_i_assistant_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistantApp title: "observabilityAIAssistantApp" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistantApp plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistantApp'] --- import observabilityAIAssistantAppObj from './observability_a_i_assistant_app.devdocs.json'; diff --git a/api_docs/observability_ai_assistant_management.mdx b/api_docs/observability_ai_assistant_management.mdx index 58ab4f18ce903..845a6410839a9 100644 --- a/api_docs/observability_ai_assistant_management.mdx +++ b/api_docs/observability_ai_assistant_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAiAssistantManagement title: "observabilityAiAssistantManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAiAssistantManagement plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAiAssistantManagement'] --- import observabilityAiAssistantManagementObj from './observability_ai_assistant_management.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index afe593fc1bdf7..a80d8fe2af855 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 166c98a8d0414..450b385bb3cdb 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index 9f9952b34daae..096ec480d34a1 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityShared'] --- import observabilitySharedObj from './observability_shared.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 46a4d920372d9..191871f79f468 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/painless_lab.mdx b/api_docs/painless_lab.mdx index 2260fa50a9f6b..8b5cd8287264b 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'painlessLab'] --- import painlessLabObj from './painless_lab.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 16b8a6f43a2e7..a01de278ae337 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 827 | 703 | 43 | +| 828 | 704 | 45 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 51628 | 241 | 38443 | 1894 | +| 51995 | 241 | 38936 | 1911 | ## Plugin Directory @@ -32,9 +32,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 4 | 0 | 4 | 1 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 74 | 0 | 9 | 2 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 871 | 1 | 839 | 52 | -| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | The user interface for Elastic APM | 29 | 0 | 29 | 124 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 9 | 0 | 9 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 2 | 0 | 2 | 0 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | The user interface for Elastic APM | 29 | 0 | 29 | 119 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 74 | 0 | 74 | 1 | | | [@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. | 83 | 1 | 73 | 2 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | @@ -48,7 +47,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | cloudFullStory | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | When Kibana runs on Elastic Cloud, this plugin registers FullStory as a shipper for telemetry. | 0 | 0 | 0 | 0 | | cloudLinks | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | Adds the links to the Elastic Cloud console | 0 | 0 | 0 | 0 | | | [@elastic/kibana-cloud-security-posture](https://github.com/orgs/elastic/teams/kibana-cloud-security-posture) | The cloud security posture plugin | 14 | 0 | 2 | 2 | -| | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 38 | 0 | 30 | 0 | +| | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 39 | 0 | 30 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Content management app | 149 | 0 | 125 | 6 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 351 | 0 | 343 | 19 | | crossClusterReplication | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | @@ -56,12 +55,12 @@ 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 | 271 | 0 | 252 | 1 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 129 | 0 | 123 | 12 | | | [@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. | 3205 | 31 | 2590 | 24 | +| | [@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. | 3209 | 31 | 2594 | 24 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 5 | 0 | 5 | 0 | | | [@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 | 35 | 0 | 25 | 5 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Reusable data view field editor across Kibana | 72 | 0 | 33 | 1 | | | [@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. | 1170 | 0 | 401 | 3 | +| | [@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. | 1224 | 0 | 443 | 3 | | | [@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 | 4 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin introduces the concept of data set quality, where users can easily get an overview on the data sets they have. | 10 | 0 | 10 | 5 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 15 | 0 | 9 | 2 | @@ -70,13 +69,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | A stateful layer to register shared features and provide an access point to discover without a direct dependency | 16 | 0 | 15 | 2 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | Server APIs for the Elastic AI Assistant | 49 | 0 | 35 | 2 | -| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 570 | 1 | 460 | 9 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 571 | 1 | 461 | 9 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 19 | 0 | 19 | 2 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 53 | 0 | 46 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Adds dashboards for discovering and managing Enterprise Search products. | 5 | 0 | 5 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Entity manager plugin for entity assets (inventory, topology, etc) | 14 | 0 | 14 | 1 | +| | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | - | 2 | 0 | 2 | 0 | +| | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | Entity manager plugin for entity assets (inventory, topology, etc) | 16 | 0 | 16 | 1 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 99 | 3 | 97 | 3 | -| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 29 | 0 | 10 | 0 | +| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 24 | 0 | 9 | 0 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The Event Annotation service contains expressions for event annotations | 201 | 0 | 201 | 6 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The listing page for event annotations. | 15 | 0 | 15 | 0 | @@ -102,7 +102,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@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. | 84 | 0 | 84 | 8 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 3 | 0 | 3 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1351 | 5 | 1229 | 73 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1351 | 5 | 1229 | 74 | | 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) | - | 72 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -114,14 +114,15 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Image embeddable | 1 | 0 | 1 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 227 | 0 | 222 | 1 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 16 | 0 | 14 | 11 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin visualizes data from Filebeat and Metricbeat, and integrates with other Observability solutions | 37 | 0 | 34 | 6 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 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) | - | 127 | 2 | 100 | 4 | | | [@elastic/security-scalability](https://github.com/orgs/elastic/teams/security-scalability) | Plugin implementing the Integration Assistant API and UI | 47 | 0 | 40 | 3 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides UI and APIs for the interactive setup mode. | 28 | 0 | 18 | 0 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 133 | 0 | 133 | 6 | -| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 5 | 0 | 5 | 0 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 105 | 0 | 105 | 6 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 5 | 0 | 5 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 6 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 153 | 0 | 121 | 3 | | kibanaUsageCollection | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -133,23 +134,23 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | A dashboard panel for creating links to dashboards or external links. | 5 | 0 | 5 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 226 | 0 | 97 | 52 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 7 | 0 | 7 | 6 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 9 | 0 | 9 | 6 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin provides a LogsExplorer component using the Discover customization framework, offering several affordances specifically designed for log consumption. | 117 | 4 | 117 | 22 | -| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | Exposes the shared components and APIs to access and visualize logs. | 297 | 0 | 269 | 32 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | Exposes the shared components and APIs to access and visualize logs. | 300 | 0 | 272 | 32 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 44 | 0 | 44 | 7 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 209 | 0 | 205 | 28 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 60 | 0 | 60 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 105 | 8 | 105 | 6 | +| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 108 | 8 | 108 | 6 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 154 | 3 | 67 | 102 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 2 | 0 | 2 | 0 | | | [@elastic/stack-monitoring](https://github.com/orgs/elastic/teams/stack-monitoring) | - | 15 | 3 | 13 | 1 | | | [@elastic/stack-monitoring](https://github.com/orgs/elastic/teams/stack-monitoring) | - | 9 | 0 | 9 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 49 | 0 | 47 | 4 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 56 | 0 | 54 | 4 | | | [@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/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 701 | 2 | 694 | 21 | +| | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 701 | 2 | 694 | 22 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 290 | 1 | 288 | 26 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 4 | 0 | 4 | 0 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 2 | 0 | 2 | 0 | @@ -178,11 +179,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin hosting shared features for connectors | 19 | 0 | 19 | 3 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 18 | 0 | 10 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 10 | 0 | 6 | 1 | -| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 8 | 0 | 8 | 1 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 10 | 0 | 10 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 15 | 0 | 9 | 1 | | searchprofiler | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-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. | 438 | 0 | 222 | 1 | -| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 190 | 0 | 121 | 36 | +| | [@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. | 447 | 0 | 231 | 1 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 192 | 0 | 123 | 33 | | | [@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. | 7 | 0 | 7 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | The core Serverless plugin, providing APIs to Serverless Project plugins. | 25 | 0 | 24 | 0 | @@ -192,17 +193,17 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds URL Service and sharing capabilities to Kibana | 121 | 0 | 60 | 12 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 59 | 0 | 59 | 1 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-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. | 260 | 0 | 66 | 0 | +| | [@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. | 261 | 0 | 67 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 25 | 0 | 25 | 3 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 10 | 0 | 10 | 0 | | synthetics | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 1 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 105 | 0 | 62 | 5 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 107 | 0 | 63 | 7 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 45 | 0 | 1 | 0 | | | [@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/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 30 | 0 | 14 | 4 | -| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 228 | 1 | 184 | 18 | +| | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 226 | 1 | 182 | 17 | | | [@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) | - | 590 | 1 | 564 | 51 | @@ -210,12 +211,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Extends UI Actions plugin with more functionality | 212 | 0 | 145 | 11 | | | [@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). | 15 | 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. | 71 | 0 | 36 | 6 | -| | [@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. | 152 | 2 | 113 | 23 | +| | [@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. | 149 | 2 | 112 | 22 | | upgradeAssistant | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | This plugin visualizes data from Heartbeat, and integrates with other Observability solutions. | 1 | 0 | 1 | 0 | | urlDrilldown | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds drilldown implementations to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 12 | 0 | 12 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 56 | 0 | 16 | 2 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 51 | 0 | 14 | 4 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | The default editor used in most aggregation-based visualizations. | 56 | 0 | 49 | 4 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | Contains the gauge chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting. | 7 | 0 | 7 | 2 | @@ -255,8 +256,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 4 | 0 | 4 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 49 | 0 | 49 | 8 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 200 | 0 | 200 | 31 | +| | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 316 | 0 | 315 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 11 | 0 | 11 | 0 | -| | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 2 | 0 | 2 | 0 | +| | [@elastic/security-defend-workflows](https://github.com/orgs/elastic/teams/security-defend-workflows) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-qa](https://github.com/orgs/elastic/teams/kibana-qa) | - | 12 | 0 | 12 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 4 | 0 | 1 | 0 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 10 | 0 | 10 | 0 | @@ -283,10 +285,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 49 | 0 | 33 | 3 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 30 | 0 | 30 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 195 | 1 | 128 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 101 | 0 | 0 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 100 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 1 | | | [@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) | - | 100 | 0 | 0 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 99 | 0 | 0 | 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) | - | 5 | 0 | 5 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 103 | 0 | 27 | 0 | @@ -486,21 +488,20 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/docs](https://github.com/orgs/elastic/teams/docs) | - | 78 | 0 | 78 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 5 | 0 | 5 | 1 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 41 | 0 | 27 | 6 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 152 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 33 | 0 | 24 | 1 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 13 | 0 | 5 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 35 | 0 | 34 | 0 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 156 | 0 | 130 | 9 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 354 | 0 | 328 | 0 | -| | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 23 | 0 | 23 | 0 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 54 | 0 | 39 | 7 | +| | [@elastic/obs-entities](https://github.com/orgs/elastic/teams/obs-entities) | - | 26 | 0 | 26 | 0 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 55 | 0 | 40 | 7 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 32 | 0 | 19 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 11 | 0 | 6 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 269 | 1 | 209 | 15 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 27 | 0 | 27 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | -| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 135 | 1 | 120 | 12 | -| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 65 | 0 | 61 | 0 | +| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 149 | 1 | 120 | 15 | +| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 66 | 0 | 62 | 0 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 194 | 0 | 183 | 10 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 40 | 0 | 40 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 0 | 52 | 1 | @@ -509,7 +510,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 49 | 0 | 40 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 0 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 3 | 0 | 3 | 0 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 36 | 0 | 21 | 1 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 46 | 0 | 31 | 1 | | | [@elastic/appex-qa](https://github.com/orgs/elastic/teams/appex-qa) | - | 551 | 6 | 511 | 3 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 1 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 1 | 0 | 1 | 0 | @@ -549,14 +550,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 23 | 0 | 7 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 8 | 0 | 2 | 3 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 45 | 0 | 0 | 0 | -| | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 139 | 0 | 137 | 0 | +| | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 143 | 0 | 141 | 0 | | | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 20 | 0 | 11 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 88 | 0 | 10 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 56 | 0 | 6 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 2 | 0 | 0 | 0 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 592 | 1 | 1 | 0 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 2 | 0 | 2 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 90 | 1 | 0 | 0 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 97 | 1 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 209 | 3 | 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) | - | 37 | 0 | 0 | 0 | @@ -579,7 +580,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 8 | 0 | 0 | 0 | | | [@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) | - | 34 | 0 | 0 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 43 | 0 | 38 | 1 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 44 | 0 | 38 | 1 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 18 | 0 | 18 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 31 | 1 | 24 | 1 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 22 | 0 | 16 | 0 | @@ -599,7 +600,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 1 | 0 | 0 | 0 | | | [@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/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 82 | 0 | 71 | 0 | +| | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 88 | 0 | 76 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 216 | 0 | 181 | 5 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 168 | 0 | 55 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 13 | 0 | 7 | 0 | @@ -637,7 +638,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detections-response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 128 | 0 | 125 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 76 | 0 | 76 | 0 | -| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 3915 | 0 | 3915 | 0 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 3929 | 0 | 3929 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 18 | 1 | 17 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 25 | 0 | 25 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 20 | 0 | 18 | 1 | @@ -669,7 +670,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 209 | 0 | 161 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 28 | 0 | 25 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 120 | 0 | 116 | 0 | -| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 49 | 0 | 44 | 0 | +| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 51 | 0 | 46 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 69 | 0 | 64 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 31 | 0 | 30 | 1 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 5 | 0 | 5 | 0 | @@ -685,7 +686,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 30 | 0 | 8 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 0 | 28 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 0 | 23 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 35 | 0 | 26 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 2 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 5 | 0 | 4 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 2 | 0 | @@ -727,11 +728,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 41 | 2 | 21 | 0 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 32 | 2 | 32 | 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) | - | 315 | 4 | 267 | 13 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 315 | 4 | 267 | 14 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 36 | 0 | 18 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 131 | 3 | 98 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | -| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 33 | 0 | 13 | 0 | +| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 28 | 0 | 12 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 8 | 0 | 8 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 72 | 0 | 55 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 15 | 0 | 15 | 0 | @@ -741,7 +742,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 42 | 0 | 28 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 57 | 0 | 48 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 9 | 0 | 8 | 0 | -| | [@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 | 154 | 0 | 81 | 1 | +| | [@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 | 166 | 0 | 92 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 18 | 0 | 17 | 5 | | | [@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 | 314 | 0 | 285 | 8 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 13 | 0 | 9 | 0 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index 55c668514a9da..201888af2ba9c 100644 --- a/api_docs/presentation_panel.mdx +++ b/api_docs/presentation_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationPanel title: "presentationPanel" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationPanel plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationPanel'] --- import presentationPanelObj from './presentation_panel.devdocs.json'; diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 7e331bd9d9133..4b03c2ea0f413 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 30c86563ce5ea..fd6064d37fbbe 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index f4949a4add1c8..5f7bced63aa1e 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 34e1d922745f8..212774f690ccd 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: 2024-07-31 +date: 2024-08-09 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 ec57e374979a2..07500f7076982 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: 2024-07-31 +date: 2024-08-09 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 27bf229ad7626..83f9dfb5d0207 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: 2024-07-31 +date: 2024-08-09 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 0ad9bd6b51ae9..32539cc037213 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: 2024-07-31 +date: 2024-08-09 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 494213fd4d0d3..fe3b0b92b01cc 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: 2024-07-31 +date: 2024-08-09 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 b642c08004f8c..87e916666621d 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: 2024-07-31 +date: 2024-08-09 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 d21ffc70c9127..ded22b051fbab 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: 2024-07-31 +date: 2024-08-09 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 57334b10b1138..3409f9faff25f 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: 2024-07-31 +date: 2024-08-09 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 bb0f244de624f..24253bbbc421b 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: 2024-07-31 +date: 2024-08-09 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 c87afb8d72e35..e19caa8827522 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: 2024-07-31 +date: 2024-08-09 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 909039407ae1b..6f51293e8df31 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: 2024-07-31 +date: 2024-08-09 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 0f50c3d008c2b..9a1653fb4e6d5 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: 2024-07-31 +date: 2024-08-09 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 5cc2dc069127e..cbf7ad95d1d5a 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/search_connectors.mdx b/api_docs/search_connectors.mdx index 4856f7fae9040..2771db829e225 100644 --- a/api_docs/search_connectors.mdx +++ b/api_docs/search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchConnectors title: "searchConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the searchConnectors plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchConnectors'] --- import searchConnectorsObj from './search_connectors.devdocs.json'; diff --git a/api_docs/search_homepage.mdx b/api_docs/search_homepage.mdx index ee829fbe14bfd..9c30bc2e258f0 100644 --- a/api_docs/search_homepage.mdx +++ b/api_docs/search_homepage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchHomepage title: "searchHomepage" image: https://source.unsplash.com/400x175/?github description: API docs for the searchHomepage plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchHomepage'] --- import searchHomepageObj from './search_homepage.devdocs.json'; diff --git a/api_docs/search_inference_endpoints.mdx b/api_docs/search_inference_endpoints.mdx index 155fcbd0938ed..4546d7de15e23 100644 --- a/api_docs/search_inference_endpoints.mdx +++ b/api_docs/search_inference_endpoints.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchInferenceEndpoints title: "searchInferenceEndpoints" image: https://source.unsplash.com/400x175/?github description: API docs for the searchInferenceEndpoints plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchInferenceEndpoints'] --- import searchInferenceEndpointsObj from './search_inference_endpoints.devdocs.json'; diff --git a/api_docs/search_notebooks.devdocs.json b/api_docs/search_notebooks.devdocs.json index ac5a01c9ceab0..94c6f910d6e77 100644 --- a/api_docs/search_notebooks.devdocs.json +++ b/api_docs/search_notebooks.devdocs.json @@ -65,6 +65,38 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "searchNotebooks", + "id": "def-public.SearchNotebooksPluginStart.setSelectedNotebook", + "type": "Function", + "tags": [], + "label": "setSelectedNotebook", + "description": [], + "signature": [ + "(notebookId: string) => void" + ], + "path": "x-pack/plugins/search_notebooks/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchNotebooks", + "id": "def-public.SearchNotebooksPluginStart.setSelectedNotebook.$1", + "type": "string", + "tags": [], + "label": "notebookId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/search_notebooks/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "lifecycle": "start", diff --git a/api_docs/search_notebooks.mdx b/api_docs/search_notebooks.mdx index 8974f0b57aa83..8cffd670a4e06 100644 --- a/api_docs/search_notebooks.mdx +++ b/api_docs/search_notebooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchNotebooks title: "searchNotebooks" image: https://source.unsplash.com/400x175/?github description: API docs for the searchNotebooks plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNotebooks'] --- import searchNotebooksObj from './search_notebooks.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-ki | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 8 | 0 | 8 | 1 | +| 10 | 0 | 10 | 1 | ## Client diff --git a/api_docs/search_playground.mdx b/api_docs/search_playground.mdx index 9e82cc3351afd..fea42a0515304 100644 --- a/api_docs/search_playground.mdx +++ b/api_docs/search_playground.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchPlayground title: "searchPlayground" image: https://source.unsplash.com/400x175/?github description: API docs for the searchPlayground plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchPlayground'] --- import searchPlaygroundObj from './search_playground.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index 7e1c6760e405a..e7af8f38e9928 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -7218,7 +7218,14 @@ "label": "rules", "description": [], "signature": [ - "{} | RoleMappingRule" + "{} | ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingRule", + "text": "RoleMappingRule" + } ], "path": "x-pack/plugins/security/common/model/role_mapping.ts", "deprecated": false, @@ -7241,6 +7248,162 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "security", + "id": "def-common.RoleMappingAllRule", + "type": "Interface", + "tags": [], + "label": "RoleMappingAllRule", + "description": [], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "security", + "id": "def-common.RoleMappingAllRule.all", + "type": "Array", + "tags": [], + "label": "all", + "description": [], + "signature": [ + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingRule", + "text": "RoleMappingRule" + }, + "[]" + ], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "security", + "id": "def-common.RoleMappingAnyRule", + "type": "Interface", + "tags": [], + "label": "RoleMappingAnyRule", + "description": [], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "security", + "id": "def-common.RoleMappingAnyRule.any", + "type": "Array", + "tags": [], + "label": "any", + "description": [], + "signature": [ + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingRule", + "text": "RoleMappingRule" + }, + "[]" + ], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "security", + "id": "def-common.RoleMappingExceptRule", + "type": "Interface", + "tags": [], + "label": "RoleMappingExceptRule", + "description": [], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "security", + "id": "def-common.RoleMappingExceptRule.except", + "type": "CompoundType", + "tags": [], + "label": "except", + "description": [], + "signature": [ + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingAnyRule", + "text": "RoleMappingAnyRule" + }, + " | ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingAllRule", + "text": "RoleMappingAllRule" + }, + " | ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingFieldRule", + "text": "RoleMappingFieldRule" + }, + " | ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingExceptRule", + "text": "RoleMappingExceptRule" + } + ], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "security", + "id": "def-common.RoleMappingFieldRule", + "type": "Interface", + "tags": [], + "label": "RoleMappingFieldRule", + "description": [], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "security", + "id": "def-common.RoleMappingFieldRule.field", + "type": "Object", + "tags": [], + "label": "field", + "description": [], + "signature": [ + "{ [x: string]: any; }" + ], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "security", "id": "def-common.RoleRemoteClusterPrivilege", @@ -8204,6 +8367,51 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "security", + "id": "def-common.RoleMappingRule", + "type": "Type", + "tags": [], + "label": "RoleMappingRule", + "description": [], + "signature": [ + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingAnyRule", + "text": "RoleMappingAnyRule" + }, + " | ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingAllRule", + "text": "RoleMappingAllRule" + }, + " | ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingFieldRule", + "text": "RoleMappingFieldRule" + }, + " | ", + { + "pluginId": "security", + "scope": "common", + "docId": "kibSecurityPluginApi", + "section": "def-common.RoleMappingExceptRule", + "text": "RoleMappingExceptRule" + } + ], + "path": "x-pack/plugins/security/common/model/role_mapping.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "security", "id": "def-common.RoleTemplate", diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 486e8e38763cb..f99cd963ebcbb 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.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 | |-------------------|-----------|------------------------|-----------------| -| 438 | 0 | 222 | 1 | +| 447 | 0 | 231 | 1 | ## Client diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index 0ea62472d498e..fef443a955c1e 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -390,7 +390,7 @@ "label": "data", "description": [], "signature": [ - "({ id: string; type: \"eql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"eql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; event_category_override?: string | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"saved_query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; query?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"threshold\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threshold: { value: number; field: string | string[]; cardinality?: { value: number; field: string; }[] | undefined; }; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; saved_id?: string | undefined; } | { id: string; type: \"threat_match\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { value: string; type: \"mapping\"; field: string; }[]; }[]; threat_index: string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"kuery\" | \"lucene\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { id: string; type: \"machine_learning\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: string | string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"new_terms\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"kuery\" | \"lucene\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"esql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; language: \"esql\"; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; })[]" + "({ id: string; type: \"eql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"eql\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; tiebreaker_field?: string | undefined; timestamp_field?: string | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; event_category_override?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"saved_query\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; saved_id: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; query?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; response_actions?: ({ params: { query?: string | undefined; timeout?: number | undefined; queries?: { id: string; query: string; version?: string | undefined; snapshot?: boolean | undefined; platform?: string | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; removed?: boolean | undefined; }[] | undefined; ecs_mapping?: Zod.objectOutputType<{}, Zod.ZodObject<{ field: Zod.ZodOptional; value: Zod.ZodOptional]>>; }, \"strip\", Zod.ZodTypeAny, { value?: string | string[] | undefined; field?: string | undefined; }, { value?: string | string[] | undefined; field?: string | undefined; }>, \"strip\"> | undefined; saved_query_id?: string | undefined; pack_id?: string | undefined; }; action_type_id: \".osquery\"; } | { params: { command: \"isolate\"; comment?: string | undefined; } | { config: { field: string; overwrite: boolean; }; command: \"kill-process\" | \"suspend-process\"; comment?: string | undefined; }; action_type_id: \".endpoint\"; })[] | undefined; } | { id: string; type: \"threshold\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threshold: { value: number; field: string | string[]; cardinality?: { value: number; field: string; }[] | undefined; }; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { duration: { value: number; unit: \"m\" | \"h\" | \"s\"; }; } | undefined; saved_id?: string | undefined; } | { id: string; type: \"threat_match\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; threat_query: string; threat_mapping: { entries: { value: string; type: \"mapping\"; field: string; }[]; }[]; threat_index: string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; saved_id?: string | undefined; threat_filters?: unknown[] | undefined; threat_indicator_path?: string | undefined; threat_language?: \"kuery\" | \"lucene\" | undefined; concurrent_searches?: number | undefined; items_per_search?: number | undefined; } | { id: string; type: \"machine_learning\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; anomaly_threshold: number; machine_learning_job_id: string | string[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"new_terms\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"kuery\" | \"lucene\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; new_terms_fields: string[]; history_window_start: string; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; index?: string[] | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; filters?: unknown[] | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; data_view_id?: string | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; } | { id: string; type: \"esql\"; version: number; name: string; actions: { params: {} & { [k: string]: unknown; }; id: string; action_type_id: string; frequency?: { throttle: string | null; notifyWhen: \"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"; summary: boolean; } | undefined; uuid?: string | undefined; group?: string | undefined; alerts_filter?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; }[]; tags: string[]; setup: string; description: string; enabled: boolean; revision: number; query: string; interval: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; risk_score: number; language: \"esql\"; from: string; to: string; created_at: string; created_by: string; updated_at: string; updated_by: string; references: string[]; author: string[]; immutable: boolean; rule_id: string; threat: { framework: string; tactic: { id: string; name: string; reference: string; }; technique?: { id: string; name: string; reference: string; subtechnique?: { id: string; name: string; reference: string; }[] | undefined; }[] | undefined; }[]; risk_score_mapping: { value: string; field: string; operator: \"equals\"; risk_score?: number | undefined; }[]; severity_mapping: { value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; field: string; operator: \"equals\"; }[]; exceptions_list: { id: string; type: \"endpoint\" | \"detection\" | \"rule_default\" | \"endpoint_trusted_apps\" | \"endpoint_events\" | \"endpoint_host_isolation_exceptions\" | \"endpoint_blocklists\"; list_id: string; namespace_type: \"single\" | \"agnostic\"; }[]; false_positives: string[]; max_signals: number; related_integrations: { version: string; package: string; integration?: string | undefined; }[]; required_fields: { type: string; name: string; ecs: boolean; }[]; meta?: Zod.objectOutputType<{}, Zod.ZodUnknown, \"strip\"> | undefined; namespace?: string | undefined; license?: string | undefined; throttle?: string | undefined; outcome?: \"exactMatch\" | \"aliasMatch\" | \"conflict\" | undefined; alias_target_id?: string | undefined; alias_purpose?: \"savedObjectConversion\" | \"savedObjectImport\" | undefined; note?: string | undefined; rule_name_override?: string | undefined; timestamp_override?: string | undefined; timestamp_override_fallback_disabled?: boolean | undefined; timeline_id?: string | undefined; timeline_title?: string | undefined; building_block_type?: string | undefined; output_index?: string | undefined; investigation_fields?: { field_names: string[]; } | undefined; rule_source?: { type: \"external\"; is_customized: boolean; } | { type: \"internal\"; } | undefined; execution_summary?: { last_execution: { message: string; date: string; status: \"running\" | \"succeeded\" | \"failed\" | \"going to run\" | \"partial failure\"; metrics: { total_search_duration_ms?: number | undefined; total_indexing_duration_ms?: number | undefined; execution_gap_duration_s?: number | undefined; total_enrichment_duration_ms?: number | undefined; }; status_order: number; }; } | undefined; alert_suppression?: { group_by: string[]; duration?: { value: number; unit: \"m\" | \"h\" | \"s\"; } | undefined; missing_fields_strategy?: \"doNotSuppress\" | \"suppress\" | undefined; } | undefined; })[]" ], "path": "x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts", "deprecated": false, @@ -485,7 +485,7 @@ "\nExperimental flag needed to enable the link" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"assistantBedrockChat\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsSentinelOneKillProcessEnabled\" | \"responseActionsSentinelOneProcessesEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewDisabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineDisabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"valueListItemsModalEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"assistantBedrockChat\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsSentinelOneKillProcessEnabled\" | \"responseActionsSentinelOneProcessesEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewDisabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineDisabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"valueListItemsModalEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | \"dataIngestionHubEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -565,7 +565,7 @@ "\nExperimental flag needed to disable the link. Opposite of experimentalKey" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"assistantBedrockChat\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsSentinelOneKillProcessEnabled\" | \"responseActionsSentinelOneProcessesEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewDisabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineDisabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"valueListItemsModalEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"assistantBedrockChat\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"responseActionsSentinelOneKillProcessEnabled\" | \"responseActionsSentinelOneProcessesEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"securitySolutionNotesEnabled\" | \"entityAlertPreviewDisabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineDisabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"prebuiltRulesCustomizationEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"valueListItemsModalEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | \"dataIngestionHubEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -1073,14 +1073,14 @@ { "parentPluginId": "securitySolution", "id": "def-public.TimelineModel.timelineType", - "type": "Enum", + "type": "CompoundType", "tags": [], "label": "timelineType", "description": [ "timelineType: default | template" ], "signature": [ - "TimelineType" + "\"default\" | \"template\"" ], "path": "x-pack/plugins/security_solution/public/timelines/store/model.ts", "deprecated": false, @@ -1240,14 +1240,14 @@ { "parentPluginId": "securitySolution", "id": "def-public.TimelineModel.status", - "type": "Enum", + "type": "CompoundType", "tags": [], "label": "status", "description": [ "status: active | draft" ], "signature": [ - "TimelineStatus" + "\"active\" | \"draft\" | \"immutable\"" ], "path": "x-pack/plugins/security_solution/public/timelines/store/model.ts", "deprecated": false, @@ -1463,8 +1463,7 @@ "label": "excludedRowRendererIds", "description": [], "signature": [ - "RowRendererId", - "[]" + "(\"alert\" | \"alerts\" | \"plain\" | \"system\" | \"registry\" | \"auditd\" | \"auditd_file\" | \"library\" | \"netflow\" | \"suricata\" | \"system_dns\" | \"system_endgame_process\" | \"system_file\" | \"system_fim\" | \"system_security_event\" | \"system_socket\" | \"threat_match\" | \"zeek\")[]" ], "path": "x-pack/plugins/security_solution/public/timelines/store/model.ts", "deprecated": false, @@ -1932,7 +1931,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsSentinelOneKillProcessEnabled: boolean; readonly responseActionsSentinelOneProcessesEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly assistantBedrockChat: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineDisabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly valueListItemsModalEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsSentinelOneKillProcessEnabled: boolean; readonly responseActionsSentinelOneProcessesEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly assistantBedrockChat: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineDisabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly valueListItemsModalEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; readonly dataIngestionHubEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/types.ts", "deprecated": false, @@ -2202,6 +2201,27 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "securitySolution", + "id": "def-server.AppClient.Unnamed.$5", + "type": "CompoundType", + "tags": [], + "label": "buildFlavor", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.BuildFlavor", + "text": "BuildFlavor" + } + ], + "path": "x-pack/plugins/security_solution/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -2317,6 +2337,29 @@ "trackAdoption": false, "children": [], "returnComment": [] + }, + { + "parentPluginId": "securitySolution", + "id": "def-server.AppClient.getBuildFlavor", + "type": "Function", + "tags": [], + "label": "getBuildFlavor", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "@kbn/config", + "scope": "server", + "docId": "kibKbnConfigPluginApi", + "section": "def-server.BuildFlavor", + "text": "BuildFlavor" + } + ], + "path": "x-pack/plugins/security_solution/server/client/client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "initialIsOpen": false @@ -3039,7 +3082,7 @@ "\nThe security solution generic experimental features" ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsSentinelOneKillProcessEnabled: boolean; readonly responseActionsSentinelOneProcessesEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly assistantBedrockChat: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineDisabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly valueListItemsModalEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsSentinelOneKillProcessEnabled: boolean; readonly responseActionsSentinelOneProcessesEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly assistantBedrockChat: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineDisabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly valueListItemsModalEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; readonly dataIngestionHubEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", "deprecated": false, @@ -3215,7 +3258,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsSentinelOneKillProcessEnabled: boolean; readonly responseActionsSentinelOneProcessesEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly assistantBedrockChat: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineDisabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly valueListItemsModalEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly responseActionsSentinelOneKillProcessEnabled: boolean; readonly responseActionsSentinelOneProcessesEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly securitySolutionNotesEnabled: boolean; readonly entityAlertPreviewDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly assistantBedrockChat: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineDisabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly prebuiltRulesCustomizationEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly valueListItemsModalEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; readonly dataIngestionHubEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, @@ -3281,7 +3324,7 @@ "\nA list of allowed values that can be used in `xpack.securitySolution.enableExperimental`.\nThis object is then used to validate and parse the value entered." ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsSentinelOneKillProcessEnabled: false; readonly responseActionsSentinelOneProcessesEnabled: false; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly responseActionScanEnabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly securitySolutionNotesEnabled: false; readonly entityAlertPreviewDisabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly assistantBedrockChat: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly jamfDataInAnalyzerEnabled: false; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineDisabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: true; readonly valueListItemsModalEnabled: true; readonly manualRuleRunEnabled: false; readonly filterProcessDescendantsForEventFiltersEnabled: false; }" + "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: true; readonly responseActionsSentinelOneKillProcessEnabled: false; readonly responseActionsSentinelOneProcessesEnabled: false; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: true; readonly responseActionScanEnabled: false; readonly securitySolutionNotesEnabled: false; readonly entityAlertPreviewDisabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly assistantBedrockChat: true; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: true; readonly jamfDataInAnalyzerEnabled: false; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineDisabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly prebuiltRulesCustomizationEnabled: false; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: true; readonly valueListItemsModalEnabled: true; readonly manualRuleRunEnabled: false; readonly filterProcessDescendantsForEventFiltersEnabled: false; readonly dataIngestionHubEnabled: false; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 2ff1d891e1531..4e9f907ac2dec 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: 2024-07-31 +date: 2024-08-09 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 | |-------------------|-----------|------------------------|-----------------| -| 190 | 0 | 121 | 36 | +| 192 | 0 | 123 | 33 | ## Client diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 6e7875dc114b4..e49f4f80e0e96 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: 2024-07-31 +date: 2024-08-09 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 fb0d9f4aba9b2..067e227e2739f 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolutionServerless'] --- import securitySolutionServerlessObj from './security_solution_serverless.devdocs.json'; diff --git a/api_docs/serverless.mdx b/api_docs/serverless.mdx index 9cc17bcb892f6..db39d71671e48 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'serverless'] --- import serverlessObj from './serverless.devdocs.json'; diff --git a/api_docs/serverless_observability.mdx b/api_docs/serverless_observability.mdx index 8303f92a8a9a5..8dc2406690cc9 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: 2024-07-31 +date: 2024-08-09 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 d27e3005894e9..7e0d68bb81986 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: 2024-07-31 +date: 2024-08-09 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 aff1d8c19d971..94ce04d88d052 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: 2024-07-31 +date: 2024-08-09 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 c8a246cbd5837..c1fe088ff1776 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/slo.devdocs.json b/api_docs/slo.devdocs.json index 9f983ae9c2a44..b8669843b1816 100644 --- a/api_docs/slo.devdocs.json +++ b/api_docs/slo.devdocs.json @@ -1082,7 +1082,7 @@ }, "<", "CreateSLOForm", - "<{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }>> | undefined; }) => JSX.Element; }" + "<{ type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"cardinality\" | \"last_value\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }>> | undefined; }) => JSX.Element; }" ], "path": "x-pack/plugins/observability_solution/slo/public/types.ts", "deprecated": false, @@ -1251,7 +1251,7 @@ { "parentPluginId": "slo", "id": "def-server.PluginSetup.usageCollection", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "usageCollection", "description": [], @@ -1260,9 +1260,11 @@ "pluginId": "usageCollection", "scope": "server", "docId": "kibUsageCollectionPluginApi", - "section": "def-server.UsageCollectionSetup", - "text": "UsageCollectionSetup" - } + "section": "def-server.ICollectorSet", + "text": "ICollectorSet" + }, + " & ", + "UsageCountersServiceSetup" ], "path": "x-pack/plugins/observability_solution/slo/server/plugin.ts", "deprecated": false, diff --git a/api_docs/slo.mdx b/api_docs/slo.mdx index 53700201ee04f..bd15f6ad56b19 100644 --- a/api_docs/slo.mdx +++ b/api_docs/slo.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/slo title: "slo" image: https://source.unsplash.com/400x175/?github description: API docs for the slo plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'slo'] --- import sloObj from './slo.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index ee0b493ce76e3..ae4b974ca9611 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: 2024-07-31 +date: 2024-08-09 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 ec973ef3686f5..320429beae408 100644 --- a/api_docs/spaces.devdocs.json +++ b/api_docs/spaces.devdocs.json @@ -1794,13 +1794,13 @@ ], "signature": [ { - "pluginId": "cloud", + "pluginId": "spaces", "scope": "common", - "docId": "kibCloudPluginApi", - "section": "def-common.OnBoardingDefaultSolution", - "text": "OnBoardingDefaultSolution" + "docId": "kibSpacesPluginApi", + "section": "def-common.SolutionView", + "text": "SolutionView" }, - " | \"classic\" | undefined" + " | undefined" ], "path": "x-pack/plugins/spaces/common/types/space/v1.ts", "deprecated": false, @@ -1834,13 +1834,13 @@ "signature": [ "{ id?: string | undefined; name?: string | undefined; description?: string | undefined; color?: string | undefined; initials?: string | undefined; imageUrl?: string | undefined; disabledFeatures?: string[] | undefined; _reserved?: boolean | undefined; solution?: ", { - "pluginId": "cloud", + "pluginId": "spaces", "scope": "common", - "docId": "kibCloudPluginApi", - "section": "def-common.OnBoardingDefaultSolution", - "text": "OnBoardingDefaultSolution" + "docId": "kibSpacesPluginApi", + "section": "def-common.SolutionView", + "text": "SolutionView" }, - " | \"classic\" | undefined; }" + " | undefined; }" ], "path": "x-pack/plugins/spaces/public/space_avatar/types.ts", "deprecated": false, @@ -3717,13 +3717,13 @@ ], "signature": [ { - "pluginId": "cloud", + "pluginId": "spaces", "scope": "common", - "docId": "kibCloudPluginApi", - "section": "def-common.OnBoardingDefaultSolution", - "text": "OnBoardingDefaultSolution" + "docId": "kibSpacesPluginApi", + "section": "def-common.SolutionView", + "text": "SolutionView" }, - " | \"classic\" | undefined" + " | undefined" ], "path": "x-pack/plugins/spaces/common/types/space/v1.ts", "deprecated": false, @@ -4974,13 +4974,13 @@ ], "signature": [ { - "pluginId": "cloud", + "pluginId": "spaces", "scope": "common", - "docId": "kibCloudPluginApi", - "section": "def-common.OnBoardingDefaultSolution", - "text": "OnBoardingDefaultSolution" + "docId": "kibSpacesPluginApi", + "section": "def-common.SolutionView", + "text": "SolutionView" }, - " | \"classic\" | undefined" + " | undefined" ], "path": "x-pack/plugins/spaces/common/types/space/v1.ts", "deprecated": false, @@ -5058,6 +5058,28 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "spaces", + "id": "def-common.SolutionView", + "type": "Type", + "tags": [], + "label": "SolutionView", + "description": [], + "signature": [ + { + "pluginId": "cloud", + "scope": "common", + "docId": "kibCloudPluginApi", + "section": "def-common.OnBoardingDefaultSolution", + "text": "OnBoardingDefaultSolution" + }, + " | \"classic\"" + ], + "path": "x-pack/plugins/spaces/common/types/space/v1.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "spaces", "id": "def-common.SPACE_SEARCH_COUNT_THRESHOLD", diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 52c8505216554..5b974623c8d2c 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: 2024-07-31 +date: 2024-08-09 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 | |-------------------|-----------|------------------------|-----------------| -| 260 | 0 | 66 | 0 | +| 261 | 0 | 67 | 0 | ## Client diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index feabc736b6631..234a53cdde1b8 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.devdocs.json b/api_docs/stack_connectors.devdocs.json index 546dd6308eb8d..5c0fdb71ca39b 100644 --- a/api_docs/stack_connectors.devdocs.json +++ b/api_docs/stack_connectors.devdocs.json @@ -30,7 +30,7 @@ "section": "def-common.Type", "text": "Type" }, - "; }> | Readonly<{} & { subAction: \"getIncident\"; subActionParams: Readonly<{} & { externalId: string; }>; }> | Readonly<{} & { subAction: \"handshake\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"pushToService\"; subActionParams: Readonly<{} & { comments: Readonly<{} & { comment: string; commentId: string; }>[] | null; incident: Readonly<{} & { description: string | null; summary: string; labels: string[] | null; issueType: string | null; priority: string | null; parent: string | null; externalId: string | null; otherFields: Record | null; }>; }>; }> | Readonly<{} & { subAction: \"issueTypes\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"fieldsByIssueType\"; subActionParams: Readonly<{} & { id: string; }>; }> | Readonly<{} & { subAction: \"issues\"; subActionParams: Readonly<{} & { title: string; }>; }> | Readonly<{} & { subAction: \"issue\"; subActionParams: Readonly<{} & { id: string; }>; }>>" + "; }> | Readonly<{} & { subAction: \"getIncident\"; subActionParams: Readonly<{} & { externalId: string; }>; }> | Readonly<{} & { subAction: \"handshake\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"pushToService\"; subActionParams: Readonly<{} & { comments: Readonly<{} & { comment: string; commentId: string; }>[] | null; incident: Readonly<{} & { description: string | null; summary: string; labels: string[] | null; parent: string | null; issueType: string | null; priority: string | null; externalId: string | null; otherFields: Record | null; }>; }>; }> | Readonly<{} & { subAction: \"issueTypes\"; subActionParams: Readonly<{} & {}>; }> | Readonly<{} & { subAction: \"fieldsByIssueType\"; subActionParams: Readonly<{} & { id: string; }>; }> | Readonly<{} & { subAction: \"issues\"; subActionParams: Readonly<{} & { title: string; }>; }> | Readonly<{} & { subAction: \"issue\"; subActionParams: Readonly<{} & { id: string; }>; }>>" ], "path": "x-pack/plugins/stack_connectors/server/connector_types/jira/schema.ts", "deprecated": false, diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 5476d109c1431..d053eac7afb78 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.devdocs.json b/api_docs/task_manager.devdocs.json index f14b739a6a8c0..6ed73468daeea 100644 --- a/api_docs/task_manager.devdocs.json +++ b/api_docs/task_manager.devdocs.json @@ -199,7 +199,7 @@ { "parentPluginId": "taskManager", "id": "def-server.TaskManagerPlugin.setup.$2.usageCollection", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "usageCollection", "description": [], @@ -230,7 +230,7 @@ "label": "start", "description": [], "signature": [ - "({ savedObjects, elasticsearch, executionContext, docLinks, }: ", + "({ savedObjects, elasticsearch, executionContext, docLinks }: ", { "pluginId": "@kbn/core-lifecycle-server", "scope": "server", @@ -238,6 +238,8 @@ "section": "def-server.CoreStart", "text": "CoreStart" }, + ", { cloud }: ", + "TaskManagerPluginStart", ") => ", { "pluginId": "taskManager", @@ -256,7 +258,7 @@ "id": "def-server.TaskManagerPlugin.start.$1", "type": "Object", "tags": [], - "label": "{\n savedObjects,\n elasticsearch,\n executionContext,\n docLinks,\n }", + "label": "{ savedObjects, elasticsearch, executionContext, docLinks }", "description": [], "signature": [ { @@ -271,6 +273,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "taskManager", + "id": "def-server.TaskManagerPlugin.start.$2", + "type": "Object", + "tags": [], + "label": "{ cloud }", + "description": [], + "signature": [ + "TaskManagerPluginStart" + ], + "path": "x-pack/plugins/task_manager/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -1437,6 +1454,23 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "taskManager", + "id": "def-server.TaskRegisterDefinition.cost", + "type": "CompoundType", + "tags": [], + "label": "cost", + "description": [ + "\nAn optional definition of the cost associated with running the task." + ], + "signature": [ + "TaskCost", + " | undefined" + ], + "path": "x-pack/plugins/task_manager/server/task_type_dictionary.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "taskManager", "id": "def-server.TaskRegisterDefinition.description", diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index de5ecb3c0e1ad..9adba47978355 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.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 | |-------------------|-----------|------------------------|-----------------| -| 105 | 0 | 62 | 5 | +| 107 | 0 | 63 | 7 | ## Server diff --git a/api_docs/telemetry.devdocs.json b/api_docs/telemetry.devdocs.json index 20738f47f4682..ae2e19bf1a858 100644 --- a/api_docs/telemetry.devdocs.json +++ b/api_docs/telemetry.devdocs.json @@ -630,7 +630,7 @@ "When the data comes from a matching index-pattern, the name of the pattern" ], "signature": [ - "\"search\" | \"logstash\" | \"alerts\" | \"apm\" | \"metricbeat\" | \"enterprise-search\" | \"app-search\" | \"magento2\" | \"magento\" | \"shopify\" | \"wordpress\" | \"drupal\" | \"joomla\" | \"sharepoint\" | \"squarespace\" | \"sitecore\" | \"weebly\" | \"acquia\" | \"filebeat\" | \"generic-filebeat\" | \"generic-metricbeat\" | \"functionbeat\" | \"generic-functionbeat\" | \"heartbeat\" | \"generic-heartbeat\" | \"generic-logstash\" | \"fluentd\" | \"telegraf\" | \"prometheusbeat\" | \"fluentbit\" | \"nginx\" | \"apache\" | \"generic-logs\" | \"endgame\" | \"logs-endpoint\" | \"metrics-endpoint\" | \"siem-signals\" | \"auditbeat\" | \"winlogbeat\" | \"packetbeat\" | \"tomcat\" | \"artifactory\" | \"aruba\" | \"barracuda\" | \"bluecoat\" | \"arcsight\" | \"checkpoint\" | \"cisco\" | \"citrix\" | \"cyberark\" | \"cylance\" | \"fireeye\" | \"fortinet\" | \"infoblox\" | \"kaspersky\" | \"mcafee\" | \"paloaltonetworks\" | \"rsa\" | \"snort\" | \"sonicwall\" | \"sophos\" | \"squid\" | \"symantec\" | \"tippingpoint\" | \"trendmicro\" | \"tripwire\" | \"zscaler\" | \"zeek\" | \"sigma_doc\" | \"ecs-corelight\" | \"suricata\" | \"wazuh\" | \"meow\" | \"host_risk_score\" | \"user_risk_score\" | undefined" + "\"search\" | \"logstash\" | \"alerts\" | \"apm\" | \"metricbeat\" | \"suricata\" | \"zeek\" | \"enterprise-search\" | \"app-search\" | \"magento2\" | \"magento\" | \"shopify\" | \"wordpress\" | \"drupal\" | \"joomla\" | \"sharepoint\" | \"squarespace\" | \"sitecore\" | \"weebly\" | \"acquia\" | \"filebeat\" | \"generic-filebeat\" | \"generic-metricbeat\" | \"functionbeat\" | \"generic-functionbeat\" | \"heartbeat\" | \"generic-heartbeat\" | \"generic-logstash\" | \"fluentd\" | \"telegraf\" | \"prometheusbeat\" | \"fluentbit\" | \"nginx\" | \"apache\" | \"generic-logs\" | \"endgame\" | \"logs-endpoint\" | \"metrics-endpoint\" | \"siem-signals\" | \"auditbeat\" | \"winlogbeat\" | \"packetbeat\" | \"tomcat\" | \"artifactory\" | \"aruba\" | \"barracuda\" | \"bluecoat\" | \"arcsight\" | \"checkpoint\" | \"cisco\" | \"citrix\" | \"cyberark\" | \"cylance\" | \"fireeye\" | \"fortinet\" | \"infoblox\" | \"kaspersky\" | \"mcafee\" | \"paloaltonetworks\" | \"rsa\" | \"snort\" | \"sonicwall\" | \"sophos\" | \"squid\" | \"symantec\" | \"tippingpoint\" | \"trendmicro\" | \"tripwire\" | \"zscaler\" | \"sigma_doc\" | \"ecs-corelight\" | \"wazuh\" | \"meow\" | \"host_risk_score\" | \"user_risk_score\" | undefined" ], "path": "src/plugins/telemetry/server/telemetry_collection/get_data_telemetry/get_data_telemetry.ts", "deprecated": false, diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 8f6f39ff22ff0..32a23ceb691eb 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.devdocs.json b/api_docs/telemetry_collection_manager.devdocs.json index cdb24a34bd491..89928b629a3db 100644 --- a/api_docs/telemetry_collection_manager.devdocs.json +++ b/api_docs/telemetry_collection_manager.devdocs.json @@ -51,7 +51,7 @@ { "parentPluginId": "telemetryCollectionManager", "id": "def-server.StatsCollectionConfig.usageCollection", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "usageCollection", "description": [], @@ -60,9 +60,11 @@ "pluginId": "usageCollection", "scope": "server", "docId": "kibUsageCollectionPluginApi", - "section": "def-server.UsageCollectionSetup", - "text": "UsageCollectionSetup" - } + "section": "def-server.ICollectorSet", + "text": "ICollectorSet" + }, + " & ", + "UsageCountersServiceSetup" ], "path": "src/plugins/telemetry_collection_manager/server/types.ts", "deprecated": false, diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 7e7429f228cae..ea5e016796087 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: 2024-07-31 +date: 2024-08-09 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 2039d2bd3f325..f6a0d757e96e1 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: 2024-07-31 +date: 2024-08-09 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 e531c635e9d91..4a0163d07d058 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 6f88699be8732..c8c7531cc6444 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: 2024-07-31 +date: 2024-08-09 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 94d368b31abaa..0d8299cd0d6d7 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -1642,8 +1642,7 @@ "\nReturns a DataProviderType" ], "signature": [ - "DataProviderType", - " | undefined" + "\"default\" | \"template\" | undefined" ], "path": "x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts", "deprecated": false, @@ -3543,238 +3542,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "timelines", - "id": "def-common.BrowserField", - "type": "Type", - "tags": [ - "deprecated" - ], - "label": "BrowserField", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.DataViewFieldBase", - "text": "DataViewFieldBase" - }, - " & { count?: number | undefined; conflictDescriptions?: Record | undefined; format?: ", - { - "pluginId": "fieldFormats", - "scope": "common", - "docId": "kibFieldFormatsPluginApi", - "section": "def-common.SerializedFieldFormat", - "text": "SerializedFieldFormat" - }, - "<{}, ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.SerializableRecord", - "text": "SerializableRecord" - }, - "> | undefined; esTypes?: string[] | undefined; searchable: boolean; aggregatable: boolean; isNull?: boolean | undefined; readFromDocValues?: boolean | undefined; indexed?: boolean | undefined; customLabel?: string | undefined; customDescription?: string | undefined; runtimeField?: ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.RuntimeFieldSpec", - "text": "RuntimeFieldSpec" - }, - " | undefined; fixedInterval?: string[] | undefined; timeZone?: string[] | undefined; timeSeriesDimension?: boolean | undefined; timeSeriesMetric?: ", - "MappingTimeSeriesMetricType", - " | undefined; shortDotsEnable?: boolean | undefined; isMapped?: boolean | undefined; parentName?: string | undefined; defaultFormatter?: string | undefined; }" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": true, - "trackAdoption": false, - "references": [ - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "@kbn/securitysolution-data-table", - "path": "x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/containers/source/index.tsx" - }, - { - "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/event_details/types.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/types.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/types.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts" - } - ], - "initialIsOpen": false - }, { "parentPluginId": "timelines", "id": "def-common.BrowserFields", @@ -3953,6 +3720,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/sourcerer/store/model.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/sourcerer/store/model.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx" @@ -4055,11 +3826,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts" + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts" + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx" }, { "plugin": "securitySolution", @@ -4067,31 +3838,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx" + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx" + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.ts" }, { "plugin": "securitySolution", @@ -4161,6 +3912,18 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/flyout/document_details/shared/context.tsx" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.tsx" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/utils.ts" @@ -4243,9 +4006,7 @@ "label": "DataProvidersAnd", "description": [], "signature": [ - "{ id: string; type?: ", - "DataProviderType", - " | undefined; name: string; enabled: boolean; excluded: boolean; kqlQuery: string; queryMatch: ", + "{ id: string; type?: \"default\" | \"template\" | undefined; name: string; enabled: boolean; excluded: boolean; kqlQuery: string; queryMatch: ", { "pluginId": "timelines", "scope": "common", @@ -4361,28 +4122,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "timelines", - "id": "def-common.EMPTY_INDEX_FIELDS", - "type": "Array", - "tags": [], - "label": "EMPTY_INDEX_FIELDS", - "description": [], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.FieldSpec", - "text": "FieldSpec" - }, - "[]" - ], - "path": "x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "timelines", "id": "def-common.EntityType", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index bff6be0c92a2e..62283ec54596b 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-threat-hunting-investigations](https://github.com/org | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 228 | 1 | 184 | 18 | +| 226 | 1 | 182 | 17 | ## Client diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 7d961eec5568c..c3e4ab112251d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 357698033c16a..6ad099fb50dcd 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 8ce65408cac17..614152b1c03c5 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: 2024-07-31 +date: 2024-08-09 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 06a761e387c10..077155e7aa49d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_doc_viewer.mdx b/api_docs/unified_doc_viewer.mdx index 6873230f999ff..69bd2664ffcf1 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedDocViewer'] --- import unifiedDocViewerObj from './unified_doc_viewer.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index f827f569bd53f..c35c0e0e23c5e 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: 2024-07-31 +date: 2024-08-09 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 90db05c3662ca..0b46582c1421c 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -620,7 +620,7 @@ }, "[] | undefined; iconType?: ", "IconType", - " | undefined; dataTestSubj?: string | undefined; showSaveQuery?: boolean | 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; additionalQueryBarMenuItems?: ", + " | undefined; showQueryInput?: boolean | undefined; dataTestSubj?: string | undefined; showSaveQuery?: boolean | undefined; customSubmitButton?: React.ReactNode; dataViewPickerOverride?: React.ReactNode; screenTitle?: string | undefined; showQueryMenu?: boolean | undefined; showFilterBar?: boolean | undefined; showDatePicker?: boolean | undefined; showAutoRefreshOnly?: boolean | undefined; additionalQueryBarMenuItems?: ", "AdditionalQueryBarMenuItems", " | undefined; filtersForSuggestions?: ", { @@ -704,7 +704,7 @@ "section": "def-common.TimeRange", "text": "TimeRange" }, - "; }) => void) | undefined; onCancel?: (() => void) | undefined; onRefreshChange?: (((options: { isPaused: boolean; refreshInterval: number; }) => void) & ((options: { isPaused: boolean; refreshInterval: number; }) => void)) | undefined; indicateNoData?: boolean | undefined; isAutoRefreshDisabled?: boolean | undefined; nonKqlMode?: \"text\" | \"lucene\" | undefined; disableQueryLanguageSwitcher?: boolean | undefined; displayStyle?: \"inPage\" | \"detached\" | undefined; fillSubmitButton?: boolean | undefined; dataViewPickerComponentProps?: ", + "; }) => void) | undefined; onCancel?: (() => void) | undefined; onRefreshChange?: (((options: { isPaused: boolean; refreshInterval: number; }) => void) & ((options: { isPaused: boolean; refreshInterval: number; }) => void)) | undefined; indicateNoData?: boolean | undefined; isAutoRefreshDisabled?: boolean | undefined; nonKqlMode?: \"text\" | \"lucene\" | undefined; disableQueryLanguageSwitcher?: boolean | undefined; displayStyle?: \"inPage\" | \"detached\" | \"withBorders\" | undefined; fillSubmitButton?: boolean | undefined; dataViewPickerComponentProps?: ", { "pluginId": "unifiedSearch", "scope": "public", @@ -712,9 +712,7 @@ "section": "def-public.DataViewPickerProps", "text": "DataViewPickerProps" }, - " | undefined; textBasedLanguageModeErrors?: Error[] | undefined; textBasedLanguageModeWarning?: string | undefined; onTextBasedSavedAndExit?: (({ onSave }: ", - "OnSaveTextLanguageQueryProps", - ") => void) | undefined; showSubmitButton?: boolean | undefined; submitButtonStyle?: \"auto\" | \"full\" | \"iconOnly\" | undefined; suggestionsSize?: ", + " | undefined; textBasedLanguageModeErrors?: Error[] | undefined; textBasedLanguageModeWarning?: string | undefined; showSubmitButton?: boolean | undefined; submitButtonStyle?: \"auto\" | \"full\" | \"iconOnly\" | undefined; suggestionsSize?: ", "SuggestionsListSize", " | undefined; suggestionsAbstraction?: ", "SuggestionsAbstraction", @@ -1132,58 +1130,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "unifiedSearch", - "id": "def-public.DataViewPickerProps.onSaveTextLanguageQuery", - "type": "Function", - "tags": [], - "label": "onSaveTextLanguageQuery", - "description": [ - "\nCallback that is called when the user clicks the Save and switch transition modal button" - ], - "signature": [ - "(({ onSave, onCancel }: ", - "OnSaveTextLanguageQueryProps", - ") => void) | undefined" - ], - "path": "src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "unifiedSearch", - "id": "def-public.DataViewPickerProps.onSaveTextLanguageQuery.$1", - "type": "Object", - "tags": [], - "label": "{ onSave, onCancel }", - "description": [], - "signature": [ - "OnSaveTextLanguageQueryProps" - ], - "path": "src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "unifiedSearch", - "id": "def-public.DataViewPickerProps.shouldShowTextBasedLanguageTransitionModal", - "type": "CompoundType", - "tags": [], - "label": "shouldShowTextBasedLanguageTransitionModal", - "description": [ - "\nDetermines if the text based language transition\nmodal should be shown when switching data views" - ], - "signature": [ - "boolean | undefined" - ], - "path": "src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "unifiedSearch", "id": "def-public.DataViewPickerProps.isDisabled", @@ -1592,23 +1538,11 @@ "description": [], "signature": [ "{ optIn: (optInConfig: ", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.OptInConfig", - "text": "OptInConfig" - }, + "OptInConfig", ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", "Observable", "<", - { - "pluginId": "@kbn/ebt", - "scope": "common", - "docId": "kibKbnEbtPluginApi", - "section": "def-common.TelemetryCounter", - "text": "TelemetryCounter" - }, + "TelemetryCounter", ">; }" ], "path": "src/plugins/unified_search/public/types.ts", diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index e8ba15514e456..6762fdce473d3 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.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 | |-------------------|-----------|------------------------|-----------------| -| 152 | 2 | 113 | 23 | +| 149 | 2 | 112 | 22 | ## Client diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 3b5eb21b2a594..9d34c07a0e8bc 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.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 | |-------------------|-----------|------------------------|-----------------| -| 152 | 2 | 113 | 23 | +| 149 | 2 | 112 | 22 | ## Client diff --git a/api_docs/uptime.mdx b/api_docs/uptime.mdx index f629fe28c3f15..4c4db200e1ddd 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: 2024-07-31 +date: 2024-08-09 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 66a7300fb1bbb..37aad36ae4d0d 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.devdocs.json b/api_docs/usage_collection.devdocs.json index af090a71c02da..965b393222e37 100644 --- a/api_docs/usage_collection.devdocs.json +++ b/api_docs/usage_collection.devdocs.json @@ -1734,48 +1734,158 @@ }, { "parentPluginId": "usageCollection", - "id": "def-server.IUsageCounter", + "id": "def-server.ICollectorSet", "type": "Interface", "tags": [], - "label": "IUsageCounter", + "label": "ICollectorSet", "description": [ - "\nUsage Counter allows to keep track of any events that occur.\nBy calling {@link IUsageCounter.incrementCounter} devs can notify this\nAPI whenever the event happens." + "\nInterface to register and manage Usage Collectors through a CollectorSet" ], - "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", + "path": "src/plugins/usage_collection/server/collector/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "usageCollection", - "id": "def-server.IUsageCounter.incrementCounter", + "id": "def-server.ICollectorSet.makeUsageCollector", "type": "Function", "tags": [], - "label": "incrementCounter", + "label": "makeUsageCollector", "description": [ - "\nNotifies the counter about a new event happening so it can increase the count internally." + "\nCreates a usage collector to collect plugin telemetry data.\nregisterCollector must be called to connect the created collector with the service." ], "signature": [ - "(params: ", - "IncrementCounterParams", - ") => void" + "(options: ", + { + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.UsageCollectorOptions", + "text": "UsageCollectorOptions" + }, + ") => ", + { + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.ICollector", + "text": "ICollector" + }, + "" ], - "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", + "path": "src/plugins/usage_collection/server/collector/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "usageCollection", - "id": "def-server.IUsageCounter.incrementCounter.$1", + "id": "def-server.ICollectorSet.makeUsageCollector.$1", + "type": "CompoundType", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.UsageCollectorOptions", + "text": "UsageCollectorOptions" + }, + "" + ], + "path": "src/plugins/usage_collection/server/collector/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "usageCollection", + "id": "def-server.ICollectorSet.registerCollector", + "type": "Function", + "tags": [], + "label": "registerCollector", + "description": [ + "\nRegister a usage collector or a stats collector.\nUsed to connect the created collector to telemetry." + ], + "signature": [ + "(collector: ", + { + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.ICollector", + "text": "ICollector" + }, + ") => void" + ], + "path": "src/plugins/usage_collection/server/collector/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "usageCollection", + "id": "def-server.ICollectorSet.registerCollector.$1", "type": "Object", "tags": [], - "label": "params", - "description": [ - "{@link IncrementCounterParams }" + "label": "collector", + "description": [], + "signature": [ + { + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.ICollector", + "text": "ICollector" + }, + "" ], + "path": "src/plugins/usage_collection/server/collector/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "usageCollection", + "id": "def-server.ICollectorSet.getCollectorByType", + "type": "Function", + "tags": [], + "label": "getCollectorByType", + "description": [ + "\nReturns a usage collector by type" + ], + "signature": [ + "(type: string) => ", + { + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.ICollector", + "text": "ICollector" + }, + " | undefined" + ], + "path": "src/plugins/usage_collection/server/collector/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "usageCollection", + "id": "def-server.ICollectorSet.getCollectorByType.$1", + "type": "string", + "tags": [], + "label": "type", + "description": [], "signature": [ - "IncrementCounterParams" + "string" ], - "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", + "path": "src/plugins/usage_collection/server/collector/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1788,69 +1898,111 @@ }, { "parentPluginId": "usageCollection", - "id": "def-server.UsageCountersSavedObjectAttributes", + "id": "def-server.IUsageCounter", "type": "Interface", "tags": [], - "label": "UsageCountersSavedObjectAttributes", + "label": "IUsageCounter", "description": [ - "\nThe attributes stored in the UsageCounters' SavedObjects" + "\nUsage Counter allows to keep track of any events that occur.\nBy calling {@link IUsageCounter.incrementCounter} devs can notify this\nAPI whenever the event happens." ], - "path": "src/plugins/usage_collection/server/usage_counters/saved_objects.ts", + "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "usageCollection", - "id": "def-server.UsageCountersSavedObjectAttributes.domainId", + "id": "def-server.IUsageCounter.domainId", "type": "string", "tags": [], "label": "domainId", "description": [ - "The domain ID registered in the Usage Counter" + "\nDefines a domainId (aka a namespace) under which multiple counters can be stored" ], - "path": "src/plugins/usage_collection/server/usage_counters/saved_objects.ts", + "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "usageCollection", - "id": "def-server.UsageCountersSavedObjectAttributes.counterName", - "type": "string", + "id": "def-server.IUsageCounter.retentionPeriodDays", + "type": "number", "tags": [], - "label": "counterName", + "label": "retentionPeriodDays", "description": [ - "The counter name" + "\nDefines custom retention period for the counters under this domain.\nThis is the number of days worth of counters that must be kept in the system indices.\nSee USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS for default value" ], - "path": "src/plugins/usage_collection/server/usage_counters/saved_objects.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCountersSavedObjectAttributes.counterType", - "type": "string", - "tags": [], - "label": "counterType", - "description": [ - "The counter type" + "signature": [ + "number | undefined" ], - "path": "src/plugins/usage_collection/server/usage_counters/saved_objects.ts", + "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "usageCollection", - "id": "def-server.UsageCountersSavedObjectAttributes.source", - "type": "string", + "id": "def-server.IUsageCounter.incrementCounter", + "type": "Function", "tags": [], - "label": "source", + "label": "incrementCounter", "description": [ - "The source of the event that is being counted: 'server' | 'ui'" + "\nNotifies the counter about a new event happening so it can increase the count internally." ], - "path": "src/plugins/usage_collection/server/usage_counters/saved_objects.ts", + "signature": [ + "(params: ", + "IncrementCounterParams", + ") => void" + ], + "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "usageCollection", + "id": "def-server.IUsageCounter.incrementCounter.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [ + "{@link IncrementCounterParams }" + ], + "signature": [ + "IncrementCounterParams" + ], + "path": "src/plugins/usage_collection/server/usage_counters/usage_counter.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "usageCollection", + "id": "def-server.UsageCountersSavedObjectAttributes", + "type": "Interface", + "tags": [], + "label": "UsageCountersSavedObjectAttributes", + "description": [ + "\nThe attributes stored in the UsageCounters' SavedObjects" + ], + "signature": [ + { + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.UsageCountersSavedObjectAttributes", + "text": "UsageCountersSavedObjectAttributes" }, + " extends ", + "AbstractCounter" + ], + "path": "src/plugins/usage_collection/server/usage_counters/saved_objects.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "usageCollection", "id": "def-server.UsageCountersSavedObjectAttributes.count", @@ -1882,7 +2034,7 @@ "signature": [ "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" ], - "path": "packages/analytics/ebt/client/src/schema/types.ts", + "path": "node_modules/@elastic/ebt/client/src/schema/types.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2112,248 +2264,26 @@ "setup": { "parentPluginId": "usageCollection", "id": "def-server.UsageCollectionSetup", - "type": "Interface", + "type": "Type", "tags": [], "label": "UsageCollectionSetup", "description": [ - "Server's setup APIs exposed by the UsageCollection Service" + "Plugin's setup API" ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.createUsageCounter", - "type": "Function", - "tags": [], - "label": "createUsageCounter", - "description": [ - "\nCreates and registers a usage counter to collect daily aggregated plugin counter events" - ], - "signature": [ - "(type: string) => ", - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.IUsageCounter", - "text": "IUsageCounter" - } - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.createUsageCounter.$1", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.getUsageCounterByDomainId", - "type": "Function", - "tags": [], - "label": "getUsageCounterByDomainId", - "description": [ - "\nReturns a usage counter by type" - ], - "signature": [ - "(type: string) => ", - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.IUsageCounter", - "text": "IUsageCounter" - }, - " | undefined" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.getUsageCounterByDomainId.$1", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.makeUsageCollector", - "type": "Function", - "tags": [], - "label": "makeUsageCollector", - "description": [ - "\nCreates a usage collector to collect plugin telemetry data.\nregisterCollector must be called to connect the created collector with the service." - ], - "signature": [ - "(options: ", - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.UsageCollectorOptions", - "text": "UsageCollectorOptions" - }, - ") => ", - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.ICollector", - "text": "ICollector" - }, - "" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.makeUsageCollector.$1", - "type": "CompoundType", - "tags": [], - "label": "options", - "description": [], - "signature": [ - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.UsageCollectorOptions", - "text": "UsageCollectorOptions" - }, - "" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, + "signature": [ { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.registerCollector", - "type": "Function", - "tags": [], - "label": "registerCollector", - "description": [ - "\nRegister a usage collector or a stats collector.\nUsed to connect the created collector to telemetry." - ], - "signature": [ - "(collector: ", - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.ICollector", - "text": "ICollector" - }, - ") => void" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.registerCollector.$1", - "type": "Object", - "tags": [], - "label": "collector", - "description": [], - "signature": [ - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.ICollector", - "text": "ICollector" - }, - "" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] + "pluginId": "usageCollection", + "scope": "server", + "docId": "kibUsageCollectionPluginApi", + "section": "def-server.ICollectorSet", + "text": "ICollectorSet" }, - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.getCollectorByType", - "type": "Function", - "tags": [], - "label": "getCollectorByType", - "description": [ - "\nReturns a usage collector by type" - ], - "signature": [ - "(type: string) => ", - { - "pluginId": "usageCollection", - "scope": "server", - "docId": "kibUsageCollectionPluginApi", - "section": "def-server.ICollector", - "text": "ICollector" - }, - " | undefined" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "usageCollection", - "id": "def-server.UsageCollectionSetup.getCollectorByType.$1", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "signature": [ - "string" - ], - "path": "src/plugins/usage_collection/server/plugin.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - } + " & ", + "UsageCountersServiceSetup" ], + "path": "src/plugins/usage_collection/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, "lifecycle": "setup", "initialIsOpen": true } diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 6a4287a5b89ce..aec2f872d8bba 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.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 | |-------------------|-----------|------------------------|-----------------| -| 56 | 0 | 16 | 2 | +| 51 | 0 | 14 | 4 | ## Client diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 49ee2b2a2d8cb..9fd3659c61e8b 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: 2024-07-31 +date: 2024-08-09 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 64f4590c4e8db..f4554b6b833f0 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: 2024-07-31 +date: 2024-08-09 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 d4158d4d9d695..b9ebb861681a6 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: 2024-07-31 +date: 2024-08-09 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 c9f718e456b4a..011a5448fcb72 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: 2024-07-31 +date: 2024-08-09 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 a73a6bf09359d..4977547589cdd 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: 2024-07-31 +date: 2024-08-09 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 a00ef9f73d6b1..70158bdcf688b 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: 2024-07-31 +date: 2024-08-09 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 d80ca5455c75a..7626113758d05 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: 2024-07-31 +date: 2024-08-09 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 dea362715607c..0e487f02401b0 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: 2024-07-31 +date: 2024-08-09 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 4854c14bb51c7..9bbc0c2d082e4 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: 2024-07-31 +date: 2024-08-09 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 82c7940c8c6cd..134c6dc84e2d5 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: 2024-07-31 +date: 2024-08-09 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 8ae3570a2fe59..51126cca22af6 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: 2024-07-31 +date: 2024-08-09 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 6a568c7aaaaca..0a4684c1fb556 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -15659,7 +15659,7 @@ "label": "GaugeCentralMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, @@ -15689,7 +15689,7 @@ "label": "GaugeLabelMajorMode", "description": [], "signature": [ - "\"none\" | \"auto\" | \"custom\"" + "\"none\" | \"custom\" | \"auto\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, @@ -16105,7 +16105,7 @@ "label": "Operation", "description": [], "signature": [ - "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"range\" | \"average\" | \"date_histogram\" | \"percentile\" | \"terms\" | \"cumulative_sum\" | \"moving_average\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\" | \"counter_rate\" | \"differences\" | \"formula\" | \"static_value\" | \"normalize_by_unit\"" + "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"terms\" | \"range\" | \"cumulative_sum\" | \"date_histogram\" | \"average\" | \"percentile\" | \"moving_average\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\" | \"counter_rate\" | \"differences\" | \"formula\" | \"static_value\" | \"normalize_by_unit\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", "deprecated": false, @@ -16135,7 +16135,7 @@ "label": "OperationWithSourceField", "description": [], "signature": [ - "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"range\" | \"average\" | \"date_histogram\" | \"percentile\" | \"terms\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\"" + "\"min\" | \"max\" | \"sum\" | \"median\" | \"count\" | \"filters\" | \"terms\" | \"range\" | \"date_histogram\" | \"average\" | \"percentile\" | \"unique_count\" | \"standard_deviation\" | \"percentile_rank\" | \"last_value\"" ], "path": "src/plugins/visualizations/common/convert_to_lens/types/operations.ts", "deprecated": false, diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 18d7faaade251..04dd0eba0cf18 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: 2024-07-31 +date: 2024-08-09 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/catalog-info.yaml b/catalog-info.yaml index 4af2698ca6cca..47fd1049e2fed 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -113,6 +113,9 @@ spec: metadata: name: kibana / sonarqube spec: + env: + SLACK_NOTIFICATIONS_CHANNEL: '#kibana-operations-alerts' + ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' repository: elastic/kibana provider_settings: trigger_mode: none diff --git a/config/serverless.yml b/config/serverless.yml index 1eab9f962b54b..6006b85323d32 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -108,8 +108,6 @@ xpack.index_management.editableIndexSettings: limited xpack.index_management.enableMappingsSourceFieldSection: false # Disable toggle for enabling data retention in DSL form from Index Management UI xpack.index_management.enableTogglingDataRetention: false -# Disable the Monaco migration in Console -console.dev.enableMonaco: false # Keep deeplinks visible so that they are shown in the sidenav dev_tools.deeplinks.navLinkStatus: visible @@ -152,6 +150,7 @@ server.versioned.strictClientVersionCheck: false # Enforce single "default" space and disable feature visibility controls xpack.spaces.maxSpaces: 1 xpack.spaces.allowFeatureVisibility: false +xpack.spaces.allowSolutionVisibility: false # Only display console autocomplete suggestions for ES endpoints that are available in serverless console.autocompleteDefinitions.endpointsAvailability: serverless diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 6b515415fe731..d58ba5b97832f 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -74,7 +74,8 @@ Review important information about the {kib} 8.x releases. [[release-notes-8.15.0]] == {kib} 8.15.0 -coming::[8.15.0] + +For information about the {kib} 8.15.0 release, review the following information. [float] [[deprecations-8.15.0]] @@ -93,6 +94,278 @@ you make the necessary updates after you upgrade to 8.15.0. The Uptime app is already hidden from Kibana when there is no recent Heartbeat data, but will be completely removed in 9.0.0. You should migrate to Synthetics as an alternative. For more details, refer to the {observability-guide}/uptime-intro.html[Uptime documentation]. ==== +[float] +[[breaking-changes-8.15.0]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.15.0, review the breaking changes, then mitigate the impact to your application. + +[discrete] +[[breaking-184036]] +.Adds rate limiting to install by upload endpoint. +[%collapsible] +==== +*Details* + +Rate limiting was added to the upload `api/fleet/epm/packages` endpoint. For more information, refer to {kibana-pull}184036[#184036]. + +*Impact* + +If you do two or more requests in less than 10 seconds, the subsequent requests fail with `429 Too Many Requests`. +Wait 10 seconds before uploading again. +This change could potentially break automations for users that rely on frequent package uploads. +==== + +[float] +[[features-8.15.0]] +=== Features +{kib} 8.15.0 adds the following new and notable features. + +Alerting:: +* Allow decimals in the threshold filed for the Failed transaction rate threshold rule ({kibana-pull}184647[#184647]). +Cases:: +* Cases custom fields and the cases webhook are now GA ({kibana-pull}187880[#187880]). +* Allow users to create case using templates ({kibana-pull}187138[#187138]). +Dashboards:: +* Adding a panel to a dashboard now opens a flyout and the list of panels available is now organized more logically ({kibana-pull}183764[#183764]). +Discover:: +* In ES|QL mode, you can now create WHERE clause filters more intuitively by interacting with the table, sidebar and table row viewer, including for ordinal charts ({kibana-pull}181399[#181399]) & ({kibana-pull}184420[#184420]). +* You can now filter an ES|QL chart by brushing a date histogram ({kibana-pull}184012[#184012]). +Elastic Security:: +* For the Elastic Security 8.15.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Fleet:: +* UI for the custom integration creation with AI ({kibana-pull}186304[#186304]). +//* Change agent policies in edit package policy page ({kibana-pull}186084[#186084]). +//* Create shared package policy ({kibana-pull}185916[#185916]). +//* Introduce policy_ids in package policy SO ({kibana-pull}184636[#184636]). +* Surface option to delete diagnostics files ({kibana-pull}183690[#183690]). +* Allow to reset log level for agents >= 8.15.0 ({kibana-pull}183434[#183434]). +* Adds warning if need root integrations trying to be used with unprivileged agents ({kibana-pull}183283[#183283]). +* Adds unprivileged vs privileged agent count to Fleet UI ({kibana-pull}183077[#183077]). +Lens & Visualizations:: +* You can now show additional statistics in the legend of your time series charts created with *Lens* ({kibana-pull}182357[#182357]). +Machine Learning:: +* Adds Field statistics to the list of available panels in Dashboards ({kibana-pull}184030[#184030]). +* Adds ES|QL support for field statistics table in Discover ({kibana-pull}180849[#180849]). +* AIOps: Moves Pattern analysis to a tab instead of a flyout in Discover ({kibana-pull}178916[#178916]). +* AIOps: Adds AI Assistant contextual insights to the Log Rate Analysis page in the Machine Learning plugin for Observability serverless projects ({kibana-pull}186509[#186509]). +Management:: +* Adds an advanced `search:timeout` setting and changes the timeout behavior to display partial results instead of just an error ({kibana-pull}179679[#179679]). +Observability:: +* Updates links for integration buttons in Observability solution ({kibana-pull}184477[#184477]). +* Adds SLO status, SLI value, error budget remaining and consumed to the burn rate alert context ({kibana-pull}184471[#184471]). +* Adds an option to prevent initial backfill for SLOs ({kibana-pull}184312[#184312]). +* Updates `Add data` links to use improved deep linking ({kibana-pull}184164[#184164]). +* Changes the navigation behavior for the Observability guide cards to pre-select the correct solution ({kibana-pull}184065[#184065]). +* Updates the Alerts details to now show an history chart for all types of alerts ({kibana-pull}181824[#181824]). +* Adds Docs count, Size, Services, Hosts, and Degraded docs KPIs to the dataset quality flyout ({kibana-pull}179479[#179479]). +Platform:: +* Improves the **Share** menu to let you navigate through a tabbed modal to copy links for Discover, Dashboards, and Lens. ({kibana-pull}180406[#180406]). +* Changes the behavior of the "Endpoints and API keys" button in the header to open the Connection details flyout ({kibana-pull}183236[#183236]). +* Adds a quick way to create an API keys with a 90-days expiration to the Connection details flyout, and clarifies the Elasticsearch endpoint and Cloud ID information ({kibana-pull}180912[#180912]). +* Adds a feedback button to the header in Serverless projects({kibana-pull}180942[#180942]). + +For more information about the features introduced in 8.15.0, refer to <>. + +[[enhancements-and-bug-fixes-v8.15.0]] +=== Enhancements and bug fixes + +For detailed information about the 8.15.0 release, review the enhancements and bug fixes. + + +[float] +[[enhancement-v8.15.0]] +=== Enhancements +Alerting:: +* Adds support of additional fields for ServiceNow ITSM and SecOps ({kibana-pull}184023[#184023]). +* Adds support for the additional info field in the ServiceNow ITOM connector ({kibana-pull}183380[#183380]). +Cases:: +* The Cases webhook connector now supports SSL certificate authentication ({kibana-pull}185925[#185925]). +Dashboards:: +* Adds a "Creator" column to the Dashboards list ({kibana-pull}182256[#182256]). +* Adds the ability to filter dashboards by creator for dashboards created on or after version 8.14 ({kibana-pull}180147[#180147]). +* When switching back from maximized to minimized view on a panel, you return to your original position in the dashboard ({kibana-pull}184696[#184696]). +* Improves the dashboard background color to match the current color mode wen margins are turned off ({kibana-pull}181450[#181450]). +* Simplifies the workflow for creating a copy of the dashboard currently open in both view and edit modes ({kibana-pull}180938[#180938]). +Discover:: +* Adds a button to expand the time range on demand when no results are found for a search ({kibana-pull}181723[#181723]). +* Changes the Discover document viewer flyout to push the rest of the UI instead of hiding it with an overlay ({kibana-pull}166406[#166406]). +* CSV reports now include custom field labels when they exist ({kibana-pull}181565[#181565]). +Elastic Security:: +* For the Elastic Security 8.15.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +ES|QL:: +* Improves metadata autocomplete suggestions with comma and pipe ({kibana-pull}188338[#188338]). +* Automatically encapsulate index names with special characters with quotes ({kibana-pull}187899[#187899]). +* Adds support for integrations ({kibana-pull}184716[#184716]). +* Adds the ability to comment out the current line with the keyboard using `CMD + /` ({kibana-pull}184637[#184637]). + +Fleet:: +* Use API key for standalone agent onboarding ({kibana-pull}187133[#187133]). +//* Adds action for upgrading all agents on policy ({kibana-pull}186827[#186827]). +* Makes Fleet & Integrations layouts full width ({kibana-pull}186056[#186056]). +* Adds support for setting `add_fields` processors on all agents under an agent policy ({kibana-pull}184693[#184693]). +* Adds force flag to delete agent_policies API ({kibana-pull}184419[#184419]). +* Adds data tags to agent policy APIs ({kibana-pull}183563[#183563]). +* Adds support for mappings with store: true ({kibana-pull}183390[#183390]). +* Shows all integration assets on detail page ({kibana-pull}182180[#182180]). +* Adds overrides to package policies update endpoint ({kibana-pull}181453[#181453]). +* Enables `agent.monitoring.http` settings on agent policy UI ({kibana-pull}180922[#180922]). +* Removes unnecessary field definitions for custom integrations and adds `logs@mappings` to log streams ({kibana-pull}178083[#178083]). +Lens & Visualizations:: +* Adds wildcard matching to field pickers across {kib} in *Lens* ({kibana-pull}182631[#182631]). +Machine Learning:: +* AIOps: Adds cardinality check to Log Rate Analysis ({kibana-pull}181129[#181129]). +* AIOps: Reduces rerenders when streaming analysis results ({kibana-pull}182793[#182793]). +* AIOps Log Rate Analysis: Improves explanation of log rate spike/dip ({kibana-pull}186342[#186342]). +* AIOps Log Rate Analysis: Merges fetch queue for keyword and text field candidates ({kibana-pull}183649[#183649]). +* AIOps Log Rate Analysis: Adds controls for controlling which columns will be visible ({kibana-pull}184262[#184262]). +* Anomaly Detection: Single Metric Viewer - Adds cases action ({kibana-pull}183423[#183423]). +* Anomaly Detection: Adds 'Add to dashboard' action for Single Metric Viewer ({kibana-pull}182538[#182538]). +* Anomaly swim lane: UX improvements ({kibana-pull}182586[#182586]). +* Single Metric Viewer embeddable: Ensures chart height is responsive on resize ({kibana-pull}185907[#185907]). +* Single Metric Viewer embeddable in dashboards: Moves all config to flyout ({kibana-pull}182756[#182756]). +* Adds progress bar for trained models download ({kibana-pull}184906[#184906]). +* Updates code editors for Transform, Data Frame and Anomaly Detection wizards ({kibana-pull}184518[#184518]). +Management:: +* The SIEM Query rule loads fewer fields on query execution ({kibana-pull}184890[#184890]). +* The ES Query rule loads fewer fields on query execution ({kibana-pull}183694[#183694]). +* Adds the ability to horizontally resize the autocomplete popup in the Dev Tools Console ({kibana-pull}180243[#180243]). +* The Kibana configuration file now supports assigning a default value for environment variables, using the `${VAR_ENV:defaultValue}` syntax. ({kibana-pull}182139[#182139]). +Observability:: +* Adds the ability to clone a monitor in Synthetics ({kibana-pull}184393[#184393]). +* Adds 10 and 30 seconds frequency options to lightweight monitors in Synthetics ({kibana-pull}184380[#184380]). +* Improves the destination of `Add data` links in Observability to make data ingestion more efficient ({kibana-pull}184164[#184164]). +* Adds an option to mark AI Assistant Knowledge Base entries as public ({kibana-pull}184094[#184094]). +* Updates the AI assistant to query all search connectors by default and adds a setting to override this new default ({kibana-pull}183712[#183712]). +* Adds downstream dependency service name to logs and errors to improve alert insights ({kibana-pull}183215[#183215]). +Operations:: +* Adds password support to the Kibana keystore ({kibana-pull}180414[#180414]). +Platform:: +* Adds http2 support to the Kibana server, that can be enabled using the `server.protocol: http2` Kibana setting ({kibana-pull}183465[#183465]). +* Kibana's `rolling-file` appender now supports more advanced retention policies. Refer to the <> for more details ({kibana-pull}182346[#182346]). +Security:: +* Adds an optional role description field to roles ({kibana-pull}183145[#183145]). +* Adds the ability to filter audit logs by username using the `xpack.security.audit.ignore_filters.users` configuration setting ({kibana-pull}183137[#183137]). +* Adds support for `remote_cluster` privileges in ES role definition ({kibana-pull}182377[#182377]). +* Improves the experience for managing a larger number of API keys by adding server side filtering, pagination and querying. ({kibana-pull}168970[#168970]). + +[float] +[[fixes-v8.15.0]] +=== Bug Fixes +Alerting:: +* Fixes kibana.alert.rule.execution.timestamp timezone and format ({kibana-pull}183905[#183905]). +* Fixes undefined error source in alerting log tags ({kibana-pull}182352[#182352]). +* Allow the rule types to throw user errors ({kibana-pull}184213[#184213]). +* Sets validation errors in subaction framework as user errors ({kibana-pull}184317[#184317]). +* Fixes x-axis time zone on alertSummaryWidget full size ({kibana-pull}187468[#187468]). +Dashboards:: +* Prevent jumping control drag handle between view modes ({kibana-pull}184533[#184533]). +* Fixes error on navigation when invalid selections tour step is open ({kibana-pull}189449[#189449]). +* Fixes positioning of dragged link in Links editor ({kibana-pull}189122[#189122]). +* Don't close the flyout when canceling the Save to library action ({kibana-pull}188995[#188995]). +* Fixes error thrown on numeric options list ({kibana-pull}188789[#188789]). +* Adds tooltip support to `PresentationPanel` header badges ({kibana-pull}186102[#186102]). +* Fixes unsaved changes on new dashboards bug ({kibana-pull}184955[#184955]). +* Reset `maximizedPanelId` on Dashboard navigation ({kibana-pull}183060[#183060]). +Discover:: +* Fixes time range filter ({kibana-pull}187010[#187010]). +* Reset selected fields when modifying the ES|QL query ({kibana-pull}185997[#185997]). +* Fixes document comparison mode and the field statistics tab when using Smart Fields ({kibana-pull}184172[#184172]). +* Disables sorting for Document view ({kibana-pull}187553[#187553]). +* Correctly adds the limit to the field statistics queries ({kibana-pull}186967[#186967]). +* Fixes overlapping on error messages ({kibana-pull}181416[#181416]). +Elastic Search:: +* Allow to save mappings with errors ({kibana-pull}188326[#188326]). + +Elastic Security:: +* For the Elastic Security 8.15.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +ES|QL:: +* Suppress empty syntax error ({kibana-pull}184246[#184246]). +* Accept null values for function arguments ({kibana-pull}184254[#184254]). +* Recognize transformational commands with ast parsing ({kibana-pull}184291[#184291]). +* Accept negated index patterns ({kibana-pull}184528[#184528]). +* Fixes the overflow problem of the editor ({kibana-pull}186166[#186166]). +* Removes inaccurate value suggestions ({kibana-pull}189228[#189228]). +* Improves support for `Invoke` completion trigger type ({kibana-pull}188877[#188877]). +Fleet:: +* Fixes navigating back to Agent policy integration list ({kibana-pull}189165[#189165]). +* Fixes copy agent policy, missed bump revision ({kibana-pull}188935[#188935]). +* Force field enabled=false on inputs that have all their streams disabled ({kibana-pull}188919[#188919]). +* Fill in empty values for `constant_keyword` fields from existing mappings ({kibana-pull}188145[#188145]). +* Enrollment token table may show an empty last page ({kibana-pull}188049[#188049]). +* Separated `showInactive` from unenrolled status filter ({kibana-pull}187960[#187960]). +* Fixes missing policy filter in Fleet Server check to enable secrets ({kibana-pull}187935[#187935]). +* Allow preconfigured agent policy only with name and id ({kibana-pull}187542[#187542]). +* Show warning callout in configs tab when an error occurs ({kibana-pull}187487[#187487]). +* Enable rollover in custom integrations install when getting mapper_exception error ({kibana-pull}186991[#186991]). +* Adds concurrency limit to EPM bulk install API + fix duplicate installations ({kibana-pull}185900[#185900]). +* Include inactive agents in agent policy agent count ({kibana-pull}184517[#184517]). +* Fixes KQL filtering ({kibana-pull}183757[#183757]). +* Prevent concurrent runs of Fleet setup ({kibana-pull}183636[#183636]). +Lens & Visualizations:: +* Do not pass incorrect filters to the state in *Lens* ({kibana-pull}189292[#189292]). +* Adds error reason in workspace panel when error happens in *Lens* ({kibana-pull}189161[#189161]). +* Fixes "Unable to load" page error on edit/add ES|QL panel ({kibana-pull}188664[#188664]). +* Improves the performance of the table ES|QL visualization ({kibana-pull}187142[#187142]). +* Fixes sort field error message for last value ({kibana-pull}184883[#184883]). +* Fixes reference line width stale update in *Lens* ({kibana-pull}184414[#184414]). +* Fixes prepend sizing on duration formatter in *Lens* ({kibana-pull}184403[#184403]). +* Fixes data table actions when the first row is empty in *Lens* ({kibana-pull}181344[#181344]). +* Fixes y-axis scale/custom domain issues and help/error text in *Lens* ({kibana-pull}180532[#180532]). +Logs:: +* Fixes log stream flyout when embedded in APM or the Infrastructure UI ({kibana-pull}189763[#189763]). +* Fixes log entry flyout when response is slow ({kibana-pull}187303[#187303]). +* Fixes flyout link to the legacy Uptime app ({kibana-pull}186328[#186328]). +Machine Learning:: +* Fixes display of model state in trained models list with starting and stopping deployments ({kibana-pull}188847[#188847]). +* AIOps: Fixes runtime mappings in pattern analysis ({kibana-pull}188530[#188530]). +* Fixes Field statistics panel displaying multiple errors if associated index is deleted and race condition when refreshes too fast ({kibana-pull}188327[#188327]). +* Hides ML embeddables from the "Add panel" flyout when ML isn't available ({kibana-pull}187639[#187639]). +* Removes info callout mentioning ML nodes for serverless environment ({kibana-pull}187583[#187583]). +* Fixes upgrade warning ({kibana-pull}187387[#187387]). +* Fixes change point menu which can get stuck open ({kibana-pull}186063[#186063]). +* Do not retry model deployment ({kibana-pull}185012[#185012]). +* Refreshes jobs list after import ({kibana-pull}184757[#184757]). +* AIOps Log Rate Analysis: Fixes date picker refresh button ({kibana-pull}183768[#183768]). +* Single Metric Viewer embeddable: Ensures creating job rule from anomaly click actions is successful ({kibana-pull}183554[#183554]). +* Adds bucket span validation to job creation flyouts ({kibana-pull}183510[#183510]). +* Fixes Choropleth map disappears when time range is changed ({kibana-pull}181933[#181933]). +Management:: +* Allows selection of timestamp when some index pattern segments are unmet ({kibana-pull}189336[#189336]). +* Transforms and Anomaly detection: Updates width for icon in messages tab to prevent overlap ({kibana-pull}188374[#188374]). +* Transform: Fixes transform stats API call in the transform health alerting rule ({kibana-pull}187586[#187586]). +* Transforms: Improves data view checks ({kibana-pull}181892[#181892]). +* Fixes human-readable (precise) formatting ({kibana-pull}181391[#181391]). +Observability:: +* Decreases bucket size top_dependencies sends to get_connection_stats query ({kibana-pull}182884[#182884]). +* Fixes SLO history details data ({kibana-pull}183097[#183097]). +* Improves permission check on SLO pages ({kibana-pull}182609[#182609]). +* Fixes contextual insights for APM errors ({kibana-pull}184642[#184642]). +* Fixes Alerts page history navigation ({kibana-pull}186068[#186068]). +* Accept project monitors with `monitor.url` of type `string` that contains commas ({kibana-pull}186112[#186112]). +* Fixes TLS certificate view for > 3 monitors per certificate ({kibana-pull}186204[#186204]). +* Fixes Synthetics/Uptime fields alerts autocomplete for query bar ({kibana-pull}186588[#186588]). +* Hides AI Assistant menu item when in a disabled space ({kibana-pull}188017[#188017]). +* Fixes AI Assistant settings when plugin is disabled ({kibana-pull}188160[#188160]). +* Fixes bug “Cannot set initialMessages if initialConversationId is set" ({kibana-pull}189885[#189885]). +* Respect query:allowLeadingWildcards in optional query filter ({kibana-pull}189488[#189488]). +* Fixes showing the correct log view in the rule creation flyout ({kibana-pull}189205[#189205]). +* Fixes a bug where "Retest on failure" couldn't be turned off when creating a monitor in the Synthetics app. ({kibana-pull}189013[#189013]). +* Fixes incorrect Redis and AWS CPU percentage metrics displayed on the Infrastructure Inventory page ({kibana-pull}188768[#188768]). +* Improves information communicated in case of insufficient privileges in the Dataset Quality UI ({kibana-pull}183947[#183947]). +Platform:: +* Accessibility fixes for user profile input labels ({kibana-pull}186471[#186471]). +* Fixes several internationalization and localization inconsistencies ({kibana-pull}181735[#181735]). +* Fixes case sensitivity in tag search ({kibana-pull}183092[#183092]). +Querying & Filtering:: +* Fixes performance issues with nested sub-queries ({kibana-pull}181208[#181208]). +Security:: +* Fixes `ComboBox` overflow with large chips ({kibana-pull}184722[#184722]). +* Adds `disabledFeatures` back to mappings, so it can be aggregated on ({kibana-pull}184195[#184195]). +* Supports read-only remote index and cluster sections with read_security access ({kibana-pull}183126[#183126]). +* Adds transformation of application wildcard `*` privilege to `all` to correctly filter and display roles as `superuser`. ({kibana-pull}181400[#181400]). +Sharing:: +* Improves error handling ({kibana-pull}185903[#185903]). + [[release-notes-8.14.3]] == {kib} 8.14.3 @@ -105,6 +378,8 @@ The 8.14.3 release includes the following bug fixes and known issues. include::CHANGELOG.asciidoc[tag=known-issue-186969] +include::CHANGELOG.asciidoc[tag=known-issue-189394] + [discrete] [[known-185691]] .When using the Observability AI Assistant with the OpenAI connector, function calling will result in an error @@ -146,6 +421,8 @@ The 8.14.2 release includes the following bug fixes and known issues. include::CHANGELOG.asciidoc[tag=known-issue-186969] +include::CHANGELOG.asciidoc[tag=known-issue-189394] + [float] [[fixes-v8.14.2]] === Bug Fixes @@ -170,6 +447,8 @@ The 8.14.1 release includes the following bug fixes and known issues. include::CHANGELOG.asciidoc[tag=known-issue-186969] +include::CHANGELOG.asciidoc[tag=known-issue-189394] + [float] [[fixes-v8.14.1]] === Bug Fixes @@ -194,7 +473,6 @@ For information about the {kib} 8.14.0 release, review the following information === Known issues // tag::known-issue-186969[] -[discrete] .Creating or editing APM, {observability} and {stack-monitor-app} rules fails [%collapsible] ==== @@ -208,9 +486,33 @@ This known issue impacts only {observability}, {stack-monitor-app}, and APM and *Workaround* + To work around this issue for {observability} and APM and {user-experience} rules, create them from the {observability} *Alerts* page. Refer to <> and {observability-guide}/create-alerts-rules.html[Create and manage {observability} rules]. + +*Resolved* + +This issue is resolved in 8.15.0. ==== // end::known-issue-186969[] +// tag::known-issue-189394[] +.{webhook-cm} connector fails to send HTTP headers +[%collapsible] +==== +*Details* + +If you configured the {webhook-cm} connector to send key-value pairs as headers, that information is not sent unles you have also enabled the basic authentication option for the connector. +Refer to https://github.com/elastic/kibana/issues/189394[#189394]. + +*Impact* + +The impact of this issue will vary depending on the purpose of your headers. +For example, if you added an `ApiKey` authorization header, you might receive a `401` authorization error since it's no longer sent by the connector. + +*Workaround* + +To work around this issue, enable the *Require authentication for this webhook* option, which is the `hasAuth` property in the API. +You must then provide a username and password for authentication. + +*Resolved* + +This issue is resolved in 8.15.0. +==== +// end::known-issue-189394[] + [float] [[breaking-changes-8.14.0]] === Breaking changes @@ -3824,7 +4126,7 @@ Review the following information about the {kib} 8.6.0 release. When you attempt to create an APM latency threshold rule in **Observability** > **Alerts** > **Rules** for all services or all transaction types, the request will fail with a `params invalid` error. *Impact* + -This known issue only impacts the Observability Rules page. To work around this issue, create APM latency threshold rules in the APM Alerts and Rules dialog. See {kibana-ref}/apm-alerts.html[Alerts and rules] for detailed instructions. +This known issue only impacts the Observability Rules page. To work around this issue, create APM latency threshold rules in the APM Alerts and Rules dialog. See {observability-guide}/apm-alerts.html[Alerts and rules] for detailed instructions. ==== [float] @@ -5414,7 +5716,7 @@ you make the necessary updates after you upgrade to 8.3.0. Removes the `apm_user` role. For more information, check {kibana-pull}132790[#132790]. *Impact* + -In the link:https://www.elastic.co/guide/en/kibana/8.3/xpack-apm.html[APM documentation], the `apm_user`role is replaced with the `viewer` and `editor` built-in roles. +The `apm_user`role is replaced with the `viewer` and `editor` built-in roles. ==== [discrete] @@ -7600,7 +7902,7 @@ Use spaces, cross-cluster replication, or cross-cluster search. To migrate to << The logging configuration and log output format has changed. For more information, refer to {kibana-pull}112305[#112305]. *Impact* + -Use the new <>. +Use the new <>. ==== [float] diff --git a/docs/api/role-management.asciidoc b/docs/api/role-management.asciidoc index 4c4620a23943a..7fbded3e57dd3 100644 --- a/docs/api/role-management.asciidoc +++ b/docs/api/role-management.asciidoc @@ -9,6 +9,7 @@ WARNING: Do not use the {ref}/security-api.html#security-role-apis[{es} role man The following {kib} role management APIs are available: * <> to create a new {kib} role, or update the attributes of an existing role +* <> to create a new {kib} roles, or update the attributes of existing roles * <> to retrieve all {kib} roles @@ -20,3 +21,4 @@ include::role-management/put.asciidoc[] include::role-management/get.asciidoc[] include::role-management/get-all.asciidoc[] include::role-management/delete.asciidoc[] +include::role-management/put-bulk.asciidoc[] diff --git a/docs/api/role-management/put-bulk.asciidoc b/docs/api/role-management/put-bulk.asciidoc new file mode 100644 index 0000000000000..e41b836e26357 --- /dev/null +++ b/docs/api/role-management/put-bulk.asciidoc @@ -0,0 +1,377 @@ +[[role-management-api-put-bulk]] +=== Bulk create or update roles API +++++ +Bulk create or update roles API +++++ + +preview::["This functionality is in technical preview, and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."] + +experimental[] Create new {kib} roles, or update the attributes of an existing roles. {kib} roles are stored in the +{es} native realm. + +[[role-management-api-put-bulk-request]] +==== Request + +`POST :/api/security/roles` + +[[role-management-api-put-bulk-prereqs]] +==== Prerequisite + +To use the bulk create or update roles API, you must have the `manage_security` cluster privilege. + +[role="child_attributes"] +[[role-management-api-bulk-response-body]] +==== Request body + +`roles`:: + (object) Object that specifies the roles to add as a role name to role map. +`` (required):: (string) The role name. +`description`:: + (Optional, string) Description for the role. + +`metadata`:: + (Optional, object) In the `metadata` object, keys that begin with `_` are reserved for system usage. + +`elasticsearch`:: + (Optional, object) {es} cluster and index privileges. Valid keys include + `cluster`, `indices`, `remote_indices`, `remote_cluster`, and `run_as`. For more information, see + {ref}/defining-roles.html[Defining roles]. + +`kibana`:: + (list) Objects that specify the <> for the role. ++ +.Properties of `kibana` +[%collapsible%open] +===== +`base` ::: + (Optional, list) A base privilege. When specified, the base must be `["all"]` or `["read"]`. + When the `base` privilege is specified, you are unable to use the `feature` section. + "all" grants read/write access to all {kib} features for the specified spaces. + "read" grants read-only access to all {kib} features for the specified spaces. + +`feature` ::: + (object) Contains privileges for specific features. + When the `feature` privileges are specified, you are unable to use the `base` section. + To retrieve a list of available features, use the <>. + +`spaces` ::: + (list) The spaces to apply the privileges to. + To grant access to all spaces, set to `["*"]`, or omit the value. +===== + +[[role-management-api-bulk-put-response-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +==== Examples + +Grant access to various features in all spaces: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/roles +{ + "roles": { + "my_kibana_role_1": { + "description": "my_kibana_role_1_description", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": [], + "feature": { + "discover": ["all"], + "visualize": ["all"], + "dashboard": ["all"], + "dev_tools": ["read"], + "advancedSettings": ["read"], + "indexPatterns": ["read"], + "graph": ["all"], + "apm": ["read"], + "maps": ["read"], + "canvas": ["read"], + "infrastructure": ["all"], + "logs": ["all"], + "uptime": ["all"] + }, + "spaces": ["*"] + } + ] + }, + "my_kibana_role_2": { + "description": "my_kibana_role_2_description", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": [], + "feature": { + "discover": ["all"], + "visualize": ["all"], + "dashboard": ["all"], + "dev_tools": ["read"], + "logs": ["all"], + "uptime": ["all"] + }, + "spaces": ["*"] + } + ] + } + } +} +-------------------------------------------------- +// KIBANA + +Grant dashboard-only access to only the Marketing space for `my_kibana_role_1` and dashboard-only access to only Sales space for `my_kibana_role_2`: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/roles +{ + "roles": { + "my_kibana_role_1": { + "description": "Grants dashboard-only access to only the Marketing space.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": [], + "feature": { + "dashboard": ["read"] + }, + "spaces": ["marketing"] + } + ] + }, + "my_kibana_role_2": { + "description": "Grants dashboard-only access to only the Sales space.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": [], + "feature": { + "dashboard": ["read"] + }, + "spaces": ["sales"] + } + ] + } + } +} + +-------------------------------------------------- +// KIBANA + +Grant full access to all features in the Default space for `my_kibana_role_1` and `my_kibana_role_2`: + +[source,sh] +-------------------------------------------------- +$ curl -X PUT api/security/role +{ + "roles": { + "my_kibana_role_1": { + "description": "Grants full access to all features in the Default space.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": ["all"], + "feature": {}, + "spaces": ["default"] + } + ] + }, + "my_kibana_role_2": { + "description": "Grants full access to all features in the Default space.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": ["all"], + "feature": {}, + "spaces": ["default"] + } + ] + } + } +} + +-------------------------------------------------- +// KIBANA + +Grant different access to different spaces: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/roles +{ + "roles": { + "my_kibana_role_1": { + "description": "Grants full access to discover and dashboard features in the default space. Grants read access in the marketing, and sales spaces.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": [], + "feature": { + "discover": ["all"], + "dashboard": ["all"] + }, + "spaces": ["default"] + }, + { + "base": ["read"], + "spaces": ["marketing", "sales"] + } + ] + }, + "my_kibana_role_2": { + "description": "Grants full access to discover and dashboard features in the default space. Grants read access in the marketing space.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": [], + "indices": [] + }, + "kibana": [ + { + "base": [], + "feature": { + "discover": ["all"], + "dashboard": ["all"] + }, + "spaces": ["default"] + }, + { + "base": ["read"], + "spaces": ["marketing"] + } + ] + } + } +} + +-------------------------------------------------- +// KIBANA + +Grant access to {kib} and {es}: + +[source,sh] +-------------------------------------------------- +$ curl -X POST api/security/roles +{ + "roles": { + "my_kibana_role_1": { + "description": "Grants all cluster privileges and full access to index1 and index2. Grants full access to remote_index1 and remote_index2, and the monitor_enrich cluster privilege on remote_cluster1. Grants all Kibana privileges in the default space.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": ["all"], + "indices": [ + { + "names": ["index1", "index2"], + "privileges": ["all"] + } + ], + "remote_indices": [ + { + "clusters": ["remote_cluster1"], + "names": ["remote_index1", "remote_index2"], + "privileges": ["all"] + } + ], + "remote_cluster": [ + { + "clusters": ["remote_cluster1"], + "privileges": ["monitor_enrich"] + } + ] + }, + "kibana": [ + { + "base": ["all"], + "feature": {}, + "spaces": ["default"] + } + ] + }, + "my_kibana_role_2": { + "description": "Grants all cluster privileges and full access to index1. Grants full access to remote_index1, and the monitor_enrich cluster privilege on remote_cluster1. Grants all Kibana privileges in the default space.", + "metadata": { + "version": 1 + }, + "elasticsearch": { + "cluster": ["all"], + "indices": [ + { + "names": ["index1"], + "privileges": ["all"] + } + ], + "remote_indices": [ + { + "clusters": ["remote_cluster1"], + "names": ["remote_index1"], + "privileges": ["all"] + } + ], + "remote_cluster": [ + { + "clusters": ["remote_cluster1"], + "privileges": ["monitor_enrich"] + } + ] + }, + "kibana": [ + { + "base": ["all"], + "feature": {}, + "spaces": ["default"] + } + ] + } + } +} + +-------------------------------------------------- +// KIBANA diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc deleted file mode 100644 index c74615f40a647..0000000000000 --- a/docs/apm/advanced-queries.asciidoc +++ /dev/null @@ -1,85 +0,0 @@ -[role="xpack"] -[[advanced-queries]] -=== Query your data - -Querying your APM data is an essential tool that can make finding bottlenecks in your code even more straightforward. - -Using the query bar, a powerful data query feature, you can pass advanced queries on your data -to filter on specific pieces of information you’re interested in. - -The query bar comes with a handy autocomplete that helps find the fields and even provides suggestions to the data they include. -You can select the query bar and hit the down arrow on your keyboard to begin scanning recommendations. - -[float] -[[apm-app-advanced-queries]] -=== Querying in the APM app - -When querying in the APM app, you’re merely searching and selecting data from fields in {es} documents. Queries entered -into the query bar are also added as parameters to the URL, so it’s easy to share a specific query or view with others. - -When you type, you can begin to see some of the transaction fields available for filtering: - -[role="screenshot"] -image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] - -[TIP] -===== -To learn more about the {kib} query language capabilities, see the {kibana-ref}/kuery-query.html[Kibana Query Language Enhancements] documentation. -===== - -[float] -[[apm-app-queries]] -==== APM app queries - -APM queries can be handy for removing noise from your data in the <>, <>, -<>, <>, and <> views. - -For example, in the *Services* view, you can quickly view a list of all the instrumented services running on your production -environment: `service.environment : production`. Or filter the list by including the APM agent's name and the host it’s running on: -`service.environment : "production" and agent.name : "java" and host.name : "prod-server1"`. - -On the *Traces* view, you might want to view failed transaction results from any of your running containers: -`transaction.result :"FAILURE" and container.id : *`. - -On the *Transactions* view, you may want to list only the slower transactions than a specified time threshold: `transaction.duration.us > 2000000`. -Or filter the list by including the service version and the Kubernetes pod it's running on: -`transaction.duration.us > 2000000 and service.version : "7.12.0" and kubernetes.pod.name : "pod-5468b47f57-pqk2m"`. - -[float] -[[discover-advanced-queries]] -=== Querying in Discover - -Alternatively, you can query your APM documents in {kibana-ref}/discover.html[*Discover*]. -Querying documents in *Discover* works the same way as queries in the APM app, -and *Discover* supports all of the example APM app queries shown on this page. - -[float] -[[discover-queries]] -==== Discover queries - -One example where you may want to make use of *Discover* -is to view _all_ transactions for an endpoint instead of just a sample. - -TIP: Starting in v7.6, you can view ten samples per bucket in the APM app, instead of just one. - -Use the APM app to find a transaction name and time bucket that you're interested in learning more about. -Then, switch to *Discover* and make a search: - -["source","sh"] ------ -processor.event: "transaction" AND transaction.name: "" and transaction.duration.us > 13000 and transaction.duration.us < 14000` ------ - -In this example, we're interested in viewing all of the `APIRestController#customers` transactions -that took between 13 and 14 milliseconds. Here's what Discover returns: - -[role="screenshot"] -image::apm/images/advanced-discover.png[View all transactions in bucket] - -You can now explore the data until you find a specific transaction that you're interested in. -Copy that transaction's `transaction.id` and paste it into the APM app to view the data in the context of the APM app: - -[role="screenshot"] -image::apm/images/specific-transaction-search.png[View specific transaction in apm app] -[role="screenshot"] -image::apm/images/specific-transaction.png[View specific transaction in apm app] diff --git a/docs/apm/agent-configuration.asciidoc b/docs/apm/agent-configuration.asciidoc deleted file mode 100644 index 62d4f3d7e2fec..0000000000000 --- a/docs/apm/agent-configuration.asciidoc +++ /dev/null @@ -1,56 +0,0 @@ -[role="xpack"] -[[agent-configuration]] -=== APM Agent central configuration - -++++ -Configure APM agents with central config -++++ - -APM Agent configuration allows you to fine-tune your APM agent configuration from within the APM app. -Changes are automatically propagated to your APM agents, so there's no need to redeploy. - -To get started, choose the services and environments you wish to configure. -The APM app will let you know when your APM agents have applied your configurations. - -[role="screenshot"] -image::apm/images/apm-agent-configuration.png[APM Agent configuration in Kibana] - -[float] -==== Precedence - -Configurations set from the APM app take precedence over configurations set locally in each APM agent. -However, if APM Server is slow to respond, is offline, reports an error, etc., -APM agents will use local defaults until they're able to update the configuration. -For this reason, it is still essential to set custom default configurations locally in each of your APM agents. - -[float] -==== Supported configurations - -Each APM agent has a list of supported configurations. -After selecting a Service name and environment in the APM app, -a list of all supported configuration options, -including descriptions and default values, will be displayed. - -Supported configurations are also tagged with the image:./images/dynamic-config.svg[] badge in each APM agent's configuration reference: - -[horizontal] -Android agent:: {apm-android-ref}/configuration.html[Configuration reference] -Go agent:: {apm-go-ref}/configuration.html[Configuration reference] -iOS agent:: {apm-ios-ref}/configuration.html[Configuration reference] -Java agent:: {apm-java-ref}/configuration.html[Configuration reference] -.NET agent:: {apm-dotnet-ref}/configuration.html[Configuration reference] -Node.js agent:: {apm-node-ref}/configuration.html[Configuration reference] -PHP agent:: {apm-php-ref}/configuration.html[Configuration reference] -Python agent:: {apm-py-ref}/configuration.html[Configuration reference] -Ruby agent:: {apm-ruby-ref}/configuration.html[Configuration reference] -Real User Monitoring (RUM) agent:: {apm-rum-ref}/configuration.html[Configuration reference] - -[float] -==== APM Server configuration - -For most users, APM agent configuration should work out-of-the-box. -If you run into trouble, it may be because you're not using the {es} output, -or because your {es} credentials don't have sufficient privileges. - -See {apm-guide-ref}/configure-agent-config.html[configure APM agent configuration] -to learn how to configure APM Server to avoid these problems. diff --git a/docs/apm/agent-explorer.asciidoc b/docs/apm/agent-explorer.asciidoc deleted file mode 100644 index 2129853c9ee8c..0000000000000 --- a/docs/apm/agent-explorer.asciidoc +++ /dev/null @@ -1,18 +0,0 @@ -[[agent-explorer]] -=== APM Agent explorer - -++++ -Identify deployment details for APM agents -++++ - -beta::[] - -APM agent explorer provides a centralized panel to identify APM agent deployment details, like service name, environment, instances, and agent name, version, and documentation. - -[role="screenshot"] -image::apm/images/apm-agent-explorer.png[APM agent explorer] - -Select an APM agent to expand it and view the details of each agent instance. - -[role="screenshot"] -image::apm/images/apm-agent-explorer-flyout.png[APM agent explorer flyout] diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc deleted file mode 100644 index 113a03437cbef..0000000000000 --- a/docs/apm/api.asciidoc +++ /dev/null @@ -1,826 +0,0 @@ -[role="xpack"] -[[apm-api]] -== APM app API - -++++ -REST API -++++ - -Some APM app features are provided via a REST API: - -* <> -* <> -* <> -* <> - -[float] -[[apm-api-example]] -=== Using the APIs - -// The following content is reused throughout the API docs -// tag::using-the-APIs[] -Interact with APM APIs using cURL or another API tool. -All APM APIs are Kibana APIs, not Elasticsearch APIs; -because of this, the Kibana dev tools console cannot be used to interact with APM APIs. - -For all APM APIs, you must use a request header. -Supported headers are `Authorization`, `kbn-xsrf`, and `Content-Type`. - -`Authorization: ApiKey {credentials}`:: -Kibana supports token-based authentication with the Elasticsearch API key service. -The API key returned by the {ref}/security-api-create-api-key.html[Elasticsearch create API key API] -can be used by sending a request with an `Authorization` header that has a value of `ApiKey` followed by the `{credentials}`, -where `{credentials}` is the base64 encoding of `id` and `api_key` joined by a colon. -+ -Alternatively, you can create a user and use their username and password to authenticate API access: `-u $USER:$PASSWORD`. -+ -Whether using `Authorization: ApiKey {credentials}`, or `-u $USER:$PASSWORD`, -users interacting with APM APIs must have <>. - -`kbn-xsrf: true`:: - By default, you must use `kbn-xsrf` for all API calls, except in the following scenarios: - -* The API endpoint uses the `GET` or `HEAD` operations -* The path is allowed using the <> setting -* XSRF protections are disabled using the <> setting - -`Content-Type: application/json`:: - Applicable only when you send a payload in the API request. - {kib} API requests and responses use JSON. - Typically, if you include the `kbn-xsrf` header, you must also include the `Content-Type` header. -// end::using-the-APIs[] - -Here's an example CURL request that adds an annotation to the APM app: - -[source,curl] ----- -curl -X POST \ - http://localhost:5601/api/apm/services/opbeans-java/annotation \ --H 'Content-Type: application/json' \ --H 'kbn-xsrf: true' \ --H 'Authorization: Basic YhUlubWZhM0FDbnlQeE6WRtaW49FQmSGZ4RUWXdX' \ --d '{ - "@timestamp": "2020-05-11T10:31:30.452Z", - "service": { - "version": "1.2" - }, - "message": "Revert upgrade", - "tags": [ - "elastic.co", "customer" - ] - }' ----- - -[float] -[[kibana-api]] -=== Kibana API - -In addition to the APM specific API endpoints, Kibana provides its own <> -which you can use to automate certain aspects of configuring and deploying Kibana. - -//// -******************************************************* -******************************************************* -//// - -[role="xpack"] -[[agent-config-api]] -=== Agent Configuration API - -The APM agent configuration API allows you to fine-tune your APM agent configuration, -without needing to redeploy your application. - -The following APM agent configuration APIs are available: - -* <> to create or update an APM agent configuration -* <> to delete an APM agent configuration. -* <> to list all APM agent configurations. -* <> to search for an APM agent configuration. - -[float] -[[use-agent-config-api]] -==== How to use APM APIs - -.Expand for required headers, privileges, and usage details -[%collapsible%closed] -====== -include::api.asciidoc[tag=using-the-APIs] -====== - -//// -******************************************************* -//// - -[[apm-update-config]] -==== Create or update configuration - -[[apm-update-config-req]] -===== Request - -`PUT /api/apm/settings/agent-configuration` - -[role="child_attributes"] -[[apm-update-config-req-body]] -===== Request body - -`service`:: -(required, object) Service identifying the configuration to create or update. -+ -.Properties of `service` -[%collapsible%open] -====== -`name` ::: - (required, string) Name of service - -`environment` ::: - (optional, string) Environment of service -====== - -`settings`:: -(required) Key/value object with option name and option value. - -`agent_name`:: -(optional) The agent name is used by the UI to determine which settings to display. - - -[[apm-update-config-example]] -===== Example - -[source,curl] --------------------------------------------------- -PUT /api/apm/settings/agent-configuration -{ - "service": { - "name": "frontend", - "environment": "production" - }, - "settings": { - "transaction_sample_rate": "0.4", - "capture_body": "off", - "transaction_max_spans": "500" - }, - "agent_name": "nodejs" -} --------------------------------------------------- - -//// -******************************************************* -//// - - -[[apm-delete-config]] -==== Delete configuration - -[[apm-delete-config-req]] -===== Request - -`DELETE /api/apm/settings/agent-configuration` - -[role="child_attributes"] -[[apm-delete-config-req-body]] -===== Request body -`service`:: -(required, object) Service identifying the configuration to delete -+ -.Properties of `service` -[%collapsible%open] -====== -`name` ::: - (required, string) Name of service - -`environment` ::: - (optional, string) Environment of service -====== - - -[[apm-delete-config-example]] -===== Example - -[source,curl] --------------------------------------------------- -DELETE /api/apm/settings/agent-configuration -{ - "service" : { - "name": "frontend", - "environment": "production" - } -} --------------------------------------------------- - -//// -******************************************************* -//// - -[[apm-list-config]] -==== List configuration - -[[apm-list-config-req]] -===== Request - -`GET /api/apm/settings/agent-configuration` - -[[apm-list-config-body]] -===== Response body - -[source,js] --------------------------------------------------- -[ - { - "agent_name": "go", - "service": { - "name": "opbeans-go", - "environment": "production" - }, - "settings": { - "transaction_sample_rate": "1", - "capture_body": "off", - "transaction_max_spans": "200" - }, - "@timestamp": 1581934104843, - "applied_by_agent": false, - "etag": "1e58c178efeebae15c25c539da740d21dee422fc" - }, - { - "agent_name": "go", - "service": { - "name": "opbeans-go" - }, - "settings": { - "transaction_sample_rate": "1", - "capture_body": "off", - "transaction_max_spans": "300" - }, - "@timestamp": 1581934111727, - "applied_by_agent": false, - "etag": "3eed916d3db434d9fb7f039daa681c7a04539a64" - }, - { - "agent_name": "nodejs", - "service": { - "name": "frontend" - }, - "settings": { - "transaction_sample_rate": "1", - }, - "@timestamp": 1582031336265, - "applied_by_agent": false, - "etag": "5080ed25785b7b19f32713681e79f46996801a5b" - } -] --------------------------------------------------- - -[[apm-list-config-example]] -===== Example - -[source,curl] --------------------------------------------------- -GET /api/apm/settings/agent-configuration --------------------------------------------------- - -//// -******************************************************* -//// - -[[apm-search-config]] -==== Search configuration - -[[apm-search-config-req]] -===== Request - -`POST /api/apm/settings/agent-configuration/search` - -[role="child_attributes"] -[[apm-search-config-req-body]] -===== Request body - -`service`:: -(required, object) Service identifying the configuration. -+ -.Properties of `service` -[%collapsible%open] -====== -`name` ::: - (required, string) Name of service - -`environment` ::: - (optional, string) Environment of service -====== - -`etag`:: -(required) etag is sent by the APM agent to indicate the etag of the last successfully applied configuration. If the etag matches an existing configuration its `applied_by_agent` property will be set to `true`. Every time a configuration is edited `applied_by_agent` is reset to `false`. - -[[apm-search-config-body]] -===== Response body - -[source,js] --------------------------------------------------- -{ - "_index": ".apm-agent-configuration", - "_id": "CIaqXXABmQCdPphWj8EJ", - "_score": 2, - "_source": { - "agent_name": "nodejs", - "service": { - "name": "frontend" - }, - "settings": { - "transaction_sample_rate": "1", - }, - "@timestamp": 1582031336265, - "applied_by_agent": false, - "etag": "5080ed25785b7b19f32713681e79f46996801a5b" - } -} --------------------------------------------------- - -[[apm-search-config-example]] -===== Example - -[source,curl] --------------------------------------------------- -POST /api/apm/settings/agent-configuration/search -{ - "etag": "1e58c178efeebae15c25c539da740d21dee422fc", - "service" : { - "name": "frontend", - "environment": "production" - } -} --------------------------------------------------- - -//// -******************************************************* -******************************************************* -//// - -[role="xpack"] -[[apm-annotation-api]] -=== Annotation API - -The Annotation API allows you to annotate visualizations in the APM app with significant events, like deployments, -allowing you to easily see how these events are impacting the performance of your existing applications. - -By default, annotations are stored in a newly created `observability-annotations` index. -The name of this index can be changed in your `config.yml` by editing `xpack.observability.annotations.index`. -If you change the default index name, you'll also need to <> accordingly. - -The following APIs are available: - -* <> to create an annotation for APM. -// * <> POST /api/observability/annotation -// * <> GET /api/observability/annotation/:id -// * <> DELETE /api/observability/annotation/:id - -[float] -[[use-annotation-api]] -==== How to use APM APIs - -.Expand for required headers, privileges, and usage details -[%collapsible%closed] -====== -include::api.asciidoc[tag=using-the-APIs] -====== - -//// -******************************************************* -//// - -[[apm-annotation-create]] -==== Create or update annotation - -[[apm-annotation-config-req]] -===== Request - -`POST /api/apm/services/:serviceName/annotation` - -[role="child_attributes"] -[[apm-annotation-config-req-body]] -===== Request body - -`service`:: -(required, object) Service identifying the configuration to create or update. -+ -.Properties of `service` -[%collapsible%open] -====== -`version` ::: - (required, string) Version of service. - -`environment` ::: - (optional, string) Environment of service. -====== - -`@timestamp`:: -(required, string) The date and time of the annotation. Must be in https://www.w3.org/TR/NOTE-datetime[ISO 8601] format. - -`message`:: -(optional, string) The message displayed in the annotation. Defaults to `service.version`. - -`tags`:: -(optional, array) Tags are used by the APM app to distinguish APM annotations from other annotations. -Tags may have additional functionality in future releases. Defaults to `[apm]`. -While you can add additional tags, you cannot remove the `apm` tag. - -[[apm-annotation-config-example]] -===== Example - -The following example creates an annotation for a service named `opbeans-java`. - -[source,curl] --------------------------------------------------- -curl -X POST \ - http://localhost:5601/api/apm/services/opbeans-java/annotation \ --H 'Content-Type: application/json' \ --H 'kbn-xsrf: true' \ --H 'Authorization: Basic YhUlubWZhM0FDbnlQeE6WRtaW49FQmSGZ4RUWXdX' \ --d '{ - "@timestamp": "2020-05-08T10:31:30.452Z", - "service": { - "version": "1.2" - }, - "message": "Deployment 1.2" - }' --------------------------------------------------- - -[[apm-annotation-config-body]] -===== Response body - -[source,js] --------------------------------------------------- -{ - "_index": "observability-annotations", - "_id": "Lc9I93EBh6DbmkeV7nFX", - "_version": 1, - "_seq_no": 12, - "_primary_term": 1, - "found": true, - "_source": { - "message": "Deployment 1.2", - "@timestamp": "2020-05-08T10:31:30.452Z", - "service": { - "version": "1.2", - "name": "opbeans-java" - }, - "tags": [ - "apm", - "elastic.co", - "customer" - ], - "annotation": { - "type": "deployment" - }, - "event": { - "created": "2020-05-09T02:34:43.937Z" - } - } -} --------------------------------------------------- - -//// -******************************************************* -******************************************************* -//// - -[role="xpack"] -[[rum-sourcemap-api]] -=== RUM source map API - -IMPORTANT: This endpoint is only compatible with the -{apm-guide-ref}/index.html[APM integration for Elastic Agent]. - -A source map allows minified files to be mapped back to original source code -- -allowing you to maintain the speed advantage of minified code, -without losing the ability to quickly and easily 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] -[[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 - -.Expand for required headers, privileges, and usage details -[%collapsible%closed] -====== -include::api.asciidoc[tag=using-the-APIs] -====== - -//// -******************************************************* -//// - -[[rum-sourcemap-post]] -==== Create or update source map - -Create or update a source map for a specific service and version. - -[[rum-sourcemap-post-privs]] -===== Privileges - -The user accessing this endpoint requires `All` Kibana privileges for the {beat_kib_app} feature. -For more information, see <>. - -[[apm-sourcemap-post-req]] -===== Request - -`POST /api/apm/sourcemaps` - -[role="child_attributes"] -[[apm-sourcemap-post-req-body]] -===== Request body - -`service_name`:: -(required, string) The name of the service that the service map should apply to. - -`service_version`:: -(required, string) The version of the service that the service map should apply to. - -`bundle_filepath`:: -(required, string) The absolute path of the final bundle as used in the web application. - -`sourcemap`:: -(required, string or file upload) The source map. It must follow the -https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k[source map revision 3 proposal]. - -[[apm-sourcemap-post-example]] -===== Examples - -The following example uploads a source map for a service named `foo` and a service version of `1.0.0`: - -[source,curl] --------------------------------------------------- -curl -X POST "http://localhost:5601/api/apm/sourcemaps" \ --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 'bundle_filepath="/test/e2e/general-usecase/bundle.js"' \ --F 'sourcemap="{\"version\":3,\"file\":\"static/js/main.chunk.js\",\"sources\":[\"fleet-source-map-client/src/index.css\",\"fleet-source-map-client/src/App.js\",\"webpack:///./src/index.css?bb0a\",\"fleet-source-map-client/src/index.js\",\"fleet-source-map-client/src/reportWebVitals.js\"],\"sourcesContent\":[\"content\"],\"mappings\":\"mapping\",\"sourceRoot\":\"\"}"' <1> --------------------------------------------------- -<1> Alternatively, upload the source map as a file with `-F 'sourcemap=@path/to/source_map/bundle.js.map'` - -[[apm-sourcemap-post-body]] -===== Response body - -[source,js] --------------------------------------------------- -{ - "type": "sourcemap", - "identifier": "foo-1.0.0", - "relative_url": "/api/fleet/artifacts/foo-1.0.0/644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "body": "eJyFkL1OwzAUhd/Fc+MbYMuCEBIbHRjKgBgc96R16tiWr1OQqr47NwqJxEK3q/PzWccXxchnZ7E1A1SjuhjVZtF2yOxiEPlO17oWox3D3uPFeSRTjmJQARfCPeiAgGx8NTKsYdAc1T3rwaSJGcds8Sp3c1HnhfywUZ3QhMTFFGepZxqMC9oex3CS9tpk1XyozgOlmoVKuJX1DqEQZ0su7PGtLU+V/3JPKc3cL7TJ2FNDRPov4bFta3MDM4f7W69lpJjLO9qdK8bzVPhcJz3HUCQ4LbO/p5hCSC4cZPByrp/wFqOklbpefwAhzpqI", - "created": "2021-07-09T20:47:44.812Z", - "id": "apm:foo-1.0.0-644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "compressionAlgorithm": "zlib", - "decodedSha256": "644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "decodedSize": 441, - "encodedSha256": "024c72749c3e3dd411b103f7040ae62633558608f480bce4b108cf5b2275bd24", - "encodedSize": 237, - "encryptionAlgorithm": "none", - "packageName": "apm" -} --------------------------------------------------- - -//// -******************************************************* -//// - -[[rum-sourcemap-get]] -==== Get source maps - -Returns an array of Fleet artifacts, including source map uploads. - -[[rum-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 <>. - -[[apm-sourcemap-get-req]] -===== Request - -`GET /api/apm/sourcemaps` - -[[apm-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}' --------------------------------------------------- - -[[apm-sourcemap-get-body]] -===== Response body - -[source,js] --------------------------------------------------- -{ - "artifacts": [ - { - "type": "sourcemap", - "identifier": "foo-1.0.0", - "relative_url": "/api/fleet/artifacts/foo-1.0.0/644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "body": { - "serviceName": "foo", - "serviceVersion": "1.0.0", - "bundleFilepath": "/test/e2e/general-usecase/bundle.js", - "sourceMap": { - "version": 3, - "file": "static/js/main.chunk.js", - "sources": [ - "fleet-source-map-client/src/index.css", - "fleet-source-map-client/src/App.js", - "webpack:///./src/index.css?bb0a", - "fleet-source-map-client/src/index.js", - "fleet-source-map-client/src/reportWebVitals.js" - ], - "sourcesContent": [ - "content" - ], - "mappings": "mapping", - "sourceRoot": "" - } - }, - "created": "2021-07-09T20:47:44.812Z", - "id": "apm:foo-1.0.0-644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "compressionAlgorithm": "zlib", - "decodedSha256": "644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", - "decodedSize": 441, - "encodedSha256": "024c72749c3e3dd411b103f7040ae62633558608f480bce4b108cf5b2275bd24", - "encodedSize": 237, - "encryptionAlgorithm": "none", - "packageName": "apm" - } - ] -} --------------------------------------------------- - -//// -******************************************************* -//// - -[[rum-sourcemap-delete]] -==== Delete source map - -Delete a previously uploaded source map. - -[[rum-sourcemap-delete-privs]] -===== Privileges - -The user accessing this endpoint requires `All` Kibana privileges for the {beat_kib_app} feature. -For more information, see <>. - -[[apm-sourcemap-delete-req]] -===== Request - -`DELETE /api/apm/sourcemaps/:id` - -[[apm-sourcemap-delete-example]] -===== Example - -The following example deletes a source map with an id of `apm:foo-1.0.0-644fd5a9`: - -[source,curl] --------------------------------------------------- -curl -X DELETE "http://localhost:5601/api/apm/sourcemaps/apm:foo-1.0.0-644fd5a9" \ --H 'Content-Type: application/json' \ --H 'kbn-xsrf: true' \ --H 'Authorization: ApiKey ${YOUR_API_KEY}' --------------------------------------------------- - -[[apm-sourcemap-delete-body]] -===== Response body - -[source,js] --------------------------------------------------- -{} --------------------------------------------------- - -//// -******************************************************* -******************************************************* -//// - -[role="xpack"] -[[agent-key-api]] -=== APM agent Key API - -The APM agent Key API allows you to configure APM agent keys to authorize requests from APM agents to the APM Server. - -The following APM agent key APIs are available: - -* <> to create an APM agent key - -[float] -[[use-agent-key-api]] -==== How to use APM APIs - -.Expand for required headers, privileges, and usage details -[%collapsible%closed] -====== -include::api.asciidoc[tag=using-the-APIs] -====== - -//// -******************************************************* -//// - -[[apm-create-agent-key]] -==== Create agent key - -Create an APM agent API key. Specify API key privileges in the request body at creation time. - -[[apm-create-agent-key-privileges]] -===== Privileges - -The user creating an APM agent API key must have at least the `manage_own_api_key` cluster privilege -and the APM application-level privileges that it wishes to grant. - -====== Example role - -The example below uses the Kibana <> to create a role named `apm_agent_key_user`. -Create and assign this role to a user that wishes to create APM agent API keys. - -[source,js] --------------------------------------------------- -POST /_security/role/apm_agent_key_user -{ - "cluster": ["manage_own_api_key"], - "applications": [{ - "application": "apm", - "privileges": ["event:write", "config_agent:read"], - "resources": ["*"] - }] -} --------------------------------------------------- - -[[apm-create-agent-key-req]] -===== Request - -`POST /api/apm/agent_keys` - -[role="child_attributes"] -[[apm-create-agent-key-req-body]] -===== Request body - -`name`:: -(required, string) Name of the APM agent key. - -`privileges`:: -(required, array) APM agent key privileges. It can take one or more of the following values: - - - `event:write`. Required for ingesting APM agent events. - - `config_agent:read`. Required for APM agents to read agent configuration remotely. - -[[apm-agent-key-create-example]] -===== Example - -[source,curl] --------------------------------------------------- -POST /api/apm/agent_keys -{ - "name": "apm-key", - "privileges": ["event:write", "config_agent:read"] -} --------------------------------------------------- - -[[apm-agent-key-create-body]] -===== Response body - -[source,js] --------------------------------------------------- -{ - "agentKey": { - "id": "3DCLmn0B3ZMhLUa7WBG9", - "name": "apm-key", - "api_key": "PjGloCGOTzaZr8ilUPvkjA", - "encoded": "M0RDTG1uMEIzWk1oTFVhN1dCRzk6UGpHbG9DR09UemFacjhpbFVQdmtqQQ==" - } -} --------------------------------------------------- - -Once created, you can copy the API key (Base64 encoded) and use it to to authorize requests from APM agents to the APM Server. \ No newline at end of file diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc deleted file mode 100644 index 59cfbc50f38dc..0000000000000 --- a/docs/apm/apm-alerts.asciidoc +++ /dev/null @@ -1,175 +0,0 @@ -[role="xpack"] -[[apm-alerts]] -=== Alerts and rules - -++++ -Create an alert -++++ - -The APM app allows you to define **rules** to detect complex conditions within your APM data -and trigger built-in **actions** when those conditions are met. - -The following **rules** are supported: - -* **Threshold rule**: -Alert when the latency or failed transaction rate is abnormal. -Threshold rules can be as broad or as granular as you'd like, enabling you to define exactly when you want to be alerted--whether that's at the environment level, service name level, transaction type level, and/or transaction name level. -* **Anomaly rule**: -Alert when either the latency of a service is anomalous. Anomaly rules can be set at the environment level, service level, and/or transaction type level. -* **Error count rule**: -Alert when the number of errors in a service exceeds a defined threshold. Error count rules can be set at the environment level, service level, and error group level. - -[role="screenshot"] -image::apm/images/apm-alert.png[Create an alert in the APM app] - -Below, we'll walk through the creation of two APM rules. - -For a complete walkthrough of the **Create rule** flyout panel, including detailed information on each configurable property, -see Kibana's <>. - -[float] -[[apm-create-transaction-alert]] -=== Example: create a latency anomaly rule - -Latency anomaly rules trigger when the latency of a service is abnormal. -Because some parts of an application are more important than others, and have a different -tolerance for latency, we'll target a specific transaction within a service. - -Before continuing, identify the service name, transaction type, and environment that you'd like to create a latency anomaly rule for. -This guide will create an alert for all services based on the following criteria: - -* Service: `{your_service.name}` -* Transaction: `{your_transaction.name}` -* Environment: `{your_service.environment}` -* Severity level: critical -* Check every five minutes -* Send an alert to a Slack channel when the rule status changes - -From any page in the APM app, select **Alerts and rules** > **Create anomaly rule**. -Change the name of the rule, but do not edit the tags. - -Based on the criteria above, define the following rule details: - -* **Service** - `{your_service.name}` -* **Type** - `{your_transaction.name}` -* **Environment** - `{your_service.environment}` -* **Has anomaly with severity** - `critical` -* **Check every** - `5 minutes` - -Next, add a connector type. Multiple connectors can be selected, but in this example we're interested in Slack. -Select **Slack** > **Create a connector**. -Enter a name for the connector, -and paste your Slack webhook URL. -See Slack's webhook documentation if you need to create one. - -A default message is provided as a starting point for your alert. -You can use the https://mustache.github.io/[Mustache] template syntax, i.e., `{{variable}}` -to pass additional alert values at the time a condition is detected to an action. -A list of available variables can be accessed by selecting the -**add variable** button image:apm/images/add-variable.png[add variable button]. - -Click **Save**. Your rule has been created and is now active! - -[float] -[[apm-create-error-alert]] -=== Example: create an error count threshold alert - -The error count threshold alert triggers when the number of errors in a service exceeds a defined threshold. -Because some errors are more important than others, this guide will focus a specific error group ID. - -Before continuing, identify the service name, environment name, and error group ID that you'd like to create a latency anomaly rule for. -The easiest way to find an error group ID is to select the service that you're interested in and navigating to the **Errors** tab. - -This guide will create an alert for an error group ID based on the following criteria: - -* Service: `{your_service.name}` -* Environment: `{your_service.environment}` -* Error Grouping Key: `{your_error.ID}` -* Error rate is above 25 errors for the last five minutes -* Group alerts by `service.name` and `service.environment` -* Check every 1 minute -* Send the alert via email to the site reliability team - -From any page in the APM app, select **Alerts and rules** > **Create error count rule**. -Change the name of the alert, but do not edit the tags. - -Based on the criteria above, define the following rule details: - -* **Service**: `{your_service.name}` -* **Environment**: `{your_service.environment}` -* **Error Grouping Key**: `{your_error.ID}` -* **Is above** - `25 errors` -* **For the last** - `5 minutes` -* **Group alerts by** - `service.name` `service.environment` -* **Check every** - `1 minute` - -[NOTE] -==== -Alternatively, you can use a KQL filter to limit the scope of the alert: - -. Toggle on *Use KQL Filter*. -. Add a filter, for example to achieve the same effect as the example above: -+ -[source,txt] ------- -service.name:"{your_service.name}" and service.environment:"{your_service.environment}" and error.grouping_key:"{your_error.ID}" ------- - -Using a KQL Filter to limit the scope is available for _Latency threshold_, _Failed transaction rate threshold_, and -_Error count threshold_ rules. -==== - -Select the **Email** connector and click **Create a connector**. -Fill out the required details: sender, host, port, etc., and click **save**. - -A default message is provided as a starting point for your alert. -You can use the https://mustache.github.io/[Mustache] template syntax, i.e., `{{variable}}` -to pass additional alert values at the time a condition is detected to an action. -A list of available variables can be accessed by selecting the -**add variable** button image:apm/images/add-variable.png[add variable button]. - -Click **Save**. The alert has been created and is now active! - -[float] -[[apm-alert-view-active]] -=== View active alerts - -Active alerts are displayed and grouped in multiple ways in the APM app. - -[float] -[[apm-alert-view-group]] -==== View alerts by service group - -If you're using the <> feature, you can view alerts by service group. -From the service group overview page, click the red alert indicator to open the **Alerts** tab with a predefined filter that matches the filter used when creating the service group. - -[role="screenshot"] -image::apm/images/apm-service-group.png[Example view of service group in the APM app in Kibana] - -[float] -[[apm-alert-view-service]] -==== View alerts by service - -Alerts can be viewed within the context of any service. -After selecting a service, go to the **Alerts** tab to view any alerts that are active for the selected service. - -[role="screenshot"] -image::apm/images/active-alert-service.png[View active alerts by service] - -[float] -[[apm-alert-manage]] -=== Manage alerts and rules - -From the APM app, select **Alerts and rules** > **Manage rules** to be taken to -the {kib} *{rules-ui}* page. -From this page, you can disable, mute, and delete APM alerts. - -[float] -[[apm-alert-more-info]] -=== More information - -See {kibana-ref}/alerting-getting-started.html[Alerting] for more information. - -NOTE: If you are using an **on-premise** Elastic Stack deployment with security, -communication between Elasticsearch and Kibana must have TLS configured. -More information is in the alerting {kibana-ref}/alerting-setup.html#alerting-prerequisites[prerequisites]. diff --git a/docs/apm/apm-app-users.asciidoc b/docs/apm/apm-app-users.asciidoc deleted file mode 100644 index 94ba6ff51bb90..0000000000000 --- a/docs/apm/apm-app-users.asciidoc +++ /dev/null @@ -1,346 +0,0 @@ -[role="xpack"] -[[apm-app-users]] -== APM app users and privileges - -:beat_default_index_prefix: apm -:annotation_index: observability-annotations - -++++ -Users and privileges -++++ - -Use role-based access control to grant users access to secured -resources. The roles that you set up depend on your organization's security -requirements and the minimum privileges required to use specific features. - -{es-security-features} provides {ref}/built-in-roles.html[built-in roles] that grant a -subset of the privileges needed by APM users. -When possible, assign users the built-in roles to minimize the affect of future changes on your security strategy. -If no built-in role is available, you can assign users the privileges needed to accomplish a specific task. -In general, there are three types of privileges you'll work with: - -* **Elasticsearch cluster privileges**: Manage the actions a user can perform against your cluster. -* **Elasticsearch index privileges**: Control access to the data in specific indices your cluster. -* **Kibana feature privileges**: Grant users write or read access to features and apps within Kibana. - -Select your use-case to get started: - -* <> -* <> -* <> -* <> -* <> - -//// -*********************************** *********************************** -//// - -[role="xpack"] -[[apm-app-reader]] -=== APM reader user - -++++ -Create an APM reader user -++++ - -APM reader users typically need to view the APM app and dashboards and visualizations that use APM data. -These users might also need to create and edit dashboards, visualizations, and machine learning jobs. - -[[apm-app-reader-full]] -==== APM reader - -To create an APM reader user: - -. Create a new role, named something like `read-apm`, and assign the following privileges: -+ --- -:apm-read-view: -:apm-monitor: -include::./tab-widgets/apm-app-reader/widget.asciidoc[] -:!apm-read-view: -:!apm-monitor: --- - -. Assign the `read-apm` role created in the previous step, and the following built-in roles to -any APM reader users: -+ -[options="header"] -|==== -|Role | Purpose - -|`kibana_admin` -|Grants access to all features in Kibana. - -|`machine_learning_admin` -|Grants the privileges required to create, update, and view machine learning jobs -|==== - -[[apm-app-reader-partial]] -==== Partial APM reader - -In some instances, you may wish to restrict certain Kibana apps that a user has access to. - -. Create a new role, named something like `read-apm-partial`, and assign the following privileges: -+ --- -include::./tab-widgets/apm-app-reader/widget.asciidoc[] --- - -. Assign feature privileges to any Kibana feature that the user needs access to. -Here are two examples: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -| Kibana -| `Read` or `All` on the {beat_kib_app} feature -| Allow the use of the the {beat_kib_app} apps - -| Kibana -| `Read` or `All` on Dashboards and Discover -| Allow the user to view, edit, and create dashboards, as well as browse data. -|==== - -. Finally, assign the following role if a user needs to enable and edit machine learning features: -+ -[options="header"] -|==== -|Role | Purpose - -|`machine_learning_admin` -|Grants the privileges required to create, update, and view machine learning jobs -|==== - -//// -*********************************** *********************************** -//// - -[role="xpack"] -[[apm-app-annotation-user-create]] -=== APM app annotation user - -++++ -Create an annotation user -++++ - -NOTE: By default, the `viewer` and `editor` built-in roles provide read access to Observability annotations. -You only need to create an annotation user to write to the annotations index -(<>). - -[[apm-app-annotation-user]] -==== Annotation user - -View deployment annotations in the APM app. - -. Create a new role, named something like `annotation_user`, -and assign the following privileges: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -|Index -|`read` on +\{ANNOTATION_INDEX\}+^1^ -|Read-only access to the observability annotation index - -|Index -|`view_index_metadata` on +\{ANNOTATION_INDEX\}+^1^ -|Read-only access to observability annotation index metadata -|==== -+ -^1^ +\{ANNOTATION_INDEX\}+ should be the index name you've defined in -<>. - -. Assign the `annotation_user` created previously, and the roles and privileges necessary to create -a <> or <> APM reader to any users that need to view annotations in the APM app - -[[apm-app-annotation-api]] -==== Annotation API - -See <>. - -//// -*********************************** *********************************** -//// - -[role="xpack"] -[[apm-app-central-config-user]] -=== APM app central config user - -++++ -Create a central config user -++++ - -[[apm-app-central-config-manager]] -==== Central configuration manager - -Central configuration users need to be able to view, create, update, and delete APM agent configurations. - -. Create a new role, named something like `central-config-manager`, and assign the following privileges: -+ --- -include::./tab-widgets/central-config-users/widget.asciidoc[] --- -+ -TIP: Using the deprecated APM Server binaries? -Add the privileges under the **Classic APM indices** tab above. - -. Assign the `central-config-manager` role created in the previous step, -and the following Kibana feature privileges to anyone who needs to manage central configurations: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -| Kibana -|`All` on the {beat_kib_app} feature -|Allow full use of the {beat_kib_app} apps -|==== - -[[apm-app-central-config-reader]] -==== Central configuration reader - -In some instances, you may wish to create a user that can only read central configurations, -but not create, update, or delete them. - -. Create a new role, named something like `central-config-reader`, and assign the following privileges: -+ --- -include::./tab-widgets/central-config-users/widget.asciidoc[] --- -+ -TIP: Using the deprecated APM Server binaries? -Add the privileges under the **Classic APM indices** tab above. - -. Assign the `central-config-reader` role created in the previous step, -and the following Kibana feature privileges to anyone who needs to read central configurations: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -| Kibana -|`read` on the {beat_kib_app} feature -|Allow read access to the {beat_kib_app} apps -|==== - -[[apm-app-central-config-api]] -==== Central configuration API - -See <>. - -//// -*********************************** *********************************** -//// - -[role="xpack"] -[[apm-app-storage-explorer-user-create]] -=== APM app storage explorer user - -++++ -Create a storage explorer user -++++ - -[[apm-app-storage-explorer-user]] -==== Storage Explorer user - -View the **Storage Explorer** in the APM app. - -. Create a new role, named something like `storage-explorer_user`, -and assign the following privileges: -+ --- -include::./tab-widgets/storage-explorer-user/widget.asciidoc[] --- - -. Assign the `storage-explorer_user` created previously, and the roles and privileges necessary to create -a <> or <> APM reader to any users that need to view **Storage Explorer** in the APM app. - -//// -*********************************** *********************************** -//// - -[role="xpack"] -[[apm-app-api-user]] -=== APM app API user - -++++ -Create an API user -++++ - -[[apm-app-api-config-manager]] -==== Central configuration API - -Users can list, search, create, update, and delete central configurations via the APM app API. - -. Assign the following Kibana feature privileges: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -| Kibana -|`all` on the {beat_kib_app} feature -|Allow all access to the {beat_kib_app} apps -|==== - -[[apm-app-api-config-reader]] -==== Central configuration API reader - -Sometimes a user only needs to list and search central configurations via the APM app API. - -. Assign the following Kibana feature privileges: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -| Kibana -|`read` on the {beat_kib_app} feature -|Allow read access to the {beat_kib_app} apps -|==== - -[[apm-app-api-annotation-manager]] -==== Annotation API - -Users can use the annotation API to create annotations on their APM data. - -. Create a new role, named something like `annotation_role`, -and assign the following privileges: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -|Index -|`manage` on +{annotation_index}+ index -|Check if the +{annotation_index}+ index exists - -|Index -|`read` on +{annotation_index}+ index -|Read the +{annotation_index}+ index - -|Index -|`create_index` on +{annotation_index}+ index -|Create the +{annotation_index}+ index - -|Index -|`create_doc` on +{annotation_index}+ index -|Create new annotations in the +{annotation_index}+ index -|==== - -. Assign the `annotation_role` created previously, -and the following Kibana feature privileges to any annotation API users: -+ -[options="header"] -|==== -|Type | Privilege | Purpose - -| Kibana -|`all` on the {beat_kib_app} feature -|Allow all access to the {beat_kib_app} apps -|==== - -//LEARN MORE -//Learn more about <>. diff --git a/docs/apm/apm-spaces.asciidoc b/docs/apm/apm-spaces.asciidoc deleted file mode 100644 index 093b7560cd5aa..0000000000000 --- a/docs/apm/apm-spaces.asciidoc +++ /dev/null @@ -1,415 +0,0 @@ -[role="xpack"] -[[apm-spaces]] -=== Control access to APM data - -Starting in version 8.2.0, the APM app is <> aware. -This allows you to separate your data--and access to that data--by team, use case, service environment, -or any other filter that you choose. - -To take advantage of this feature, your APM data needs to be written to different data steams. -One way to accomplish this is with different namespaces. -For example, you can send production data to an APM integration with a namespace of `production`, -while sending staging data to a different APM integration with a namespace of `staging`. - -Multiple APM integration instances is not required though. The simplest way to take advantage of this feature -is by creating filtered aliases. See the guide below for more information. - -[float] -[[apm-spaces-example]] -=== Guide: Separate staging and production data - -This guide will explain how to separate your staging and production data. -This can be helpful to either remove noise when troubleshooting a production issue, -or to create more granular access control for certain data. - -This guide assumes that you: - -* Are sending both staging and production APM data to an {es} cluster. -* Have configured the `environment` variable in your APM agent configurations. -This variable sets the `service.environment` field in APM documents. -You should have documents where `service.environment: production` and `service.environment: staging`. -If this field is empty, see <> to learn how to set this value. - -[float] -==== Step 1: Create filtered aliases - -The APM app uses index patterns to query your APM data. An index pattern can match data streams, indices, and/or aliases. -The default values are: - -[options="header"] -|==== -| Index setting | Default index pattern -| Error | `logs-apm*` -| Span/Transaction | `traces-apm*` -| Metrics | `metrics-apm*` -|==== - -NOTE: The default index settings also query the `apm-*` data view. -This data view matches APM data shipped in earlier versions of APM (prior to v8.0). - -Instead of querying the default APM data views, we can create filtered aliases for the APM app to query. -A filtered alias is a secondary name for a group of data streams that has a user-defined -filter to limit the documents that the alias can access. - -To separate `staging` and `production` APM data, we'd need to create six filtered aliases--three -aliases for each service environment: - -[options="header"] -|==== -| Index setting | `production` env | `staging` env -| Error | `production-logs-apm` | `staging-logs-apm` -| Span/Transaction | `production-traces-apm` | `staging-traces-apm` -| Metrics | `production-metrics-apm` | `staging-metrics-apm` -|==== - -The `production--apm` aliases will contain a filter that only provides access to documents -where the `service.environment` is `production`. -Similarly, the `staging--apm` aliases will contain a filter that only provides access to documents -where the `service.environment` is `staging`. - -To create these six filtered aliases, use the {es} {ref}/indices-aliases.html[Aliases API]. -In {kib}, open **Dev Tools** and run the following POST requests. - -[%collapsible%open] -.`traces-apm*` production alias example -==== -[source, console] ----- -POST /_aliases?pretty -{ - "actions": [ - { - "add": { - "index": "traces-apm*", <1> - "alias": "production-traces-apm", <2> - "filter": { - "term": { - "service.environment": { - "value": "production" <3> - } - } - } - } - } - ] -} ----- -<1> This example matches the APM traces data stream -<2> The alias must not match the default APM index (`traces-apm*,apm-*`) -<3> Only match documents where `service.environment: production` -==== - -[%collapsible] -.`logs-apm*` production alias example -==== -[source, console] ----- -POST /_aliases?pretty -{ - "actions": [ - { - "add": { - "index": "logs-apm*", <1> - "alias": "production-logs-apm", <2> - "filter": { - "term": { - "service.environment": { - "value": "production" <3> - } - } - } - } - } - ] -} ----- -<1> This example matches the APM logs data stream -<2> The alias must not match the default APM index (`logs-apm*,apm-*`) -<3> Only match documents where `service.environment: production` -==== - -[%collapsible] -.`metrics-apm*` production alias example -==== -[source, console] ----- -POST /_aliases?pretty -{ - "actions": [ - { - "add": { - "index": "metrics-apm*", <1> - "alias": "production-metrics-apm", <2> - "filter": { - "term": { - "service.environment": { - "value": "production" <3> - } - } - } - } - } - ] -} ----- -<1> This example matches the APM metrics data stream -<2> The alias must not match the default APM index (`metrics-apm*,apm-*`) -<3> Only match documents where `service.environment: production` -==== - -[%collapsible] -.`traces-apm*` staging alias example -==== -[source, console] ----- -POST /_aliases?pretty -{ - "actions": [ - { - "add": { - "index": "traces-apm*", <1> - "alias": "staging-traces-apm", <2> - "filter": { - "term": { - "service.environment": { - "value": "staging" <3> - } - } - } - } - } - ] -} ----- -<1> This example matches the APM traces data stream -<2> The alias must not match the default APM index (`traces-apm*,apm-*`) -<3> Only match documents where `service.environment: staging` -==== - -[%collapsible] -.`logs-apm*` staging alias example -==== -[source, console] ----- -POST /_aliases?pretty -{ - "actions": [ - { - "add": { - "index": "logs-apm*", <1> - "alias": "staging-logs-apm", <2> - "filter": { - "term": { - "service.environment": { - "value": "staging" <3> - } - } - } - } - } - ] -} ----- -<1> This example matches the APM logs data stream -<2> The alias must not match the default APM index (`logs-apm*,apm-*`) -<3> Only match documents where `service.environment: staging` -==== - -[%collapsible] -.`metrics-apm*` staging alias example -==== -[source, console] ----- -POST /_aliases?pretty -{ - "actions": [ - { - "add": { - "index": "metrics-apm*", <1> - "alias": "staging-metrics-apm", <2> - "filter": { - "term": { - "service.environment": { - "value": "staging" <3> - } - } - } - } - } - ] -} ----- -<1> This example matches the APM metrics data stream -<2> The alias must not match the default APM index (`metrics-apm*,apm-*`) -<3> Only match documents where `service.environment: staging` -==== - -[float] -==== Step 2: Create {kib} spaces - -Next, you'll need to create a {Kib} space for each service environment. -To create these spaces, navigate to **Stack Management** > **Spaces** > **Create a space**. -For this guide, we've created two Kibana spaces, one named `production` and one named `staging`. - -See <> for more information on creating a space. - -[float] -==== Step 3: Update APM index settings in each space - -Now we can change the default data views that the APM app queries in each space. - -Open the APM app and navigate to **Settings** > **Indices**. -Use the table below to update your settings for each space. -The values in each column match the names of the filtered aliases we created in step one. - -[options="header"] -|==== -| Index setting | `production` space | `staging` space -| Error indices | `production-logs-apm` | `staging-logs-apm` -| Span indices | `production-traces-apm` | `staging-traces-apm` -| Transaction indices | `production-traces-apm` | `staging-traces-apm` -| Metrics indices | `production-metrics-apm` | `staging-metrics-apm` -|==== - -[role="screenshot"] -image::settings/images/apm-settings.png[APM app settings in Kibana] - -[float] -==== Step 4: Create {kib} access roles - -In {kib}, navigate to **Stack Management** > **Roles** and click **Create role**. - -You'll need to create two roles: one for `staging` users (we'll call this role `staging_apm_viewer`) -and one for `production` users (we'll call this role `production_apm_viewer`). - -Using the table below, assign each role the following privileges: - -[options="header"] -|==== -| Privileges | `production_apm_viewer` | `staging_apm_viewer` -| Index privileges | index: `production-*-apm`, privilege: `read` | index: `staging-*-apm`, privilege: `read` -| Kibana privileges | space: `production`, feature privileges: `APM and User Experience: read` | space: `staging`, feature privileges: `APM and User Experience: read` -|==== - -[role="screenshot"] -image::./images/apm-roles-config.png[APM role config example] - -Alternatively, you can use the -{es} {ref}/security-api-put-role.html[Create or update roles API]: - -[%collapsible%open] -.Create a `production_apm_viewer` role -==== -This request creates a `production_apm_viewer` role: - -[source, console] ----- -POST /_security/role/production_apm_viewer -{ - "cluster": [ ], - "indices": [ - { - "names": ["production-*-apm"], <1> - "privileges": ["read"] - } - ], - "applications": [ - { - "application" : "kibana-.kibana", - "privileges" : [ - "feature_apm.read" <2> - ], - "resources" : [ - "space:production" <3> - ] - } - ] -} ----- -<1> This data view matches all of the production aliases created in step one. -<2> Assigns `read` privileges for the APM and User Experience apps. -<3> Provides access to the space named `production`. -==== - -[%collapsible] -.Create a `staging_apm_viewer` role -==== -This request creates a `staging_apm_viewer` role: - -[source, console] ----- -POST /_security/role/staging_apm_viewer -{ - "cluster": [ ], - "indices": [ - { - "names": ["staging-*-apm"], <1> - "privileges": ["read"] - } - ], - "applications": [ - { - "application" : "kibana-.kibana", - "privileges" : [ - "feature_apm.read" <2> - ], - "resources" : [ - "space:staging" <3> - ] - } - ] -} ----- -<1> This data view matches all of the staging aliases created in step one. -<2> Assigns `read` privileges for the APM and User Experience apps. -<3> Provides access to the space named `staging`. -==== - -[float] -==== Step 5: Assign users to roles - -The last thing to do is assign users to the newly created roles above. -Users will only have access to the data within the spaces that they are granted. - -For information on how to create users and assign them roles with the {kib} UI, -see <>. - -Alternatively, you can use the -{es} {ref}/security-api-put-user.html[Create or update users API]. - -This example creates a new user and assigns them the `production_apm_viewer` role created in the previous step. -This user will only have access to the production space and data with a `service.environment` of `production`. -Remember to change the `password`, `full_name`, and `email` fields. - -[source, console] ----- -POST /_security/user/production-apm-user -{ - "password" : "l0ng-r4nd0m-p@ssw0rd", - "roles" : [ "production_apm_viewer" ], <1> - "full_name" : "Jane Production Smith", - "email" : "janesmith@example.com" -} ----- -<1> Assigns the previously created `production_apm_viewer` role. - -This example creates a new user and assigns them the `staging_apm_viewer` role created in the previous step. -This user will only have access to the staging space and data with a `service.environment` of `staging`. -Remember to change the `password`, `full_name`, and `email` fields. - -[source, console] ----- -POST /_security/user/staging-apm-user -{ - "password" : "l0ng-r4nd0m-p@ssw0rd", - "roles" : [ "staging_apm_viewer" ], <1> - "full_name" : "John Staging Doe", - "email" : "johndoe@example.com" -} ----- -<1> Assigns the previously created `staging_apm_viewer` role. - -[float] -==== Step 6: Marvel - -That's it! Head back to the APM app and marvel at your space-specific data. diff --git a/docs/apm/correlations.asciidoc b/docs/apm/correlations.asciidoc deleted file mode 100644 index ca77c6c8c6afb..0000000000000 --- a/docs/apm/correlations.asciidoc +++ /dev/null @@ -1,89 +0,0 @@ -[role="xpack"] -[[correlations]] -=== Find transaction latency and failure correlations - -Correlations surface attributes of your data that are potentially correlated -with high-latency or erroneous transactions. For example, if you are a site -reliability engineer who is responsible for keeping production systems up and -running, you want to understand what is causing slow transactions. Identifying -attributes that are responsible for higher latency transactions can potentially -point you toward the root cause. You may find a correlation with a particular -piece of hardware, like a host or pod. Or, perhaps a set of users, based on IP -address or region, is facing increased latency due to local data center issues. - -To find correlations, select a service on the *Services* page in the {apm-app} -then select a transaction group from the *Transactions* tab. - -NOTE: Queries within the {apm-app} are also applied to the correlations. - -[discrete] -[[correlations-latency]] -==== Find high transaction latency correlations - -The correlations on the *Latency correlations* tab help you discover which -attributes are contributing to increased transaction latency. - -[role="screenshot"] -image::apm/images/correlations-hover.png[Latency correlations] - -The progress bar indicates the status of the asynchronous analysis, which -performs statistical searches across a large number of attributes. For large -time ranges and services with high transaction throughput, this might take some -time. To improve performance, reduce the time range. - -The latency distribution chart visualizes the overall latency of the -transactions in the transaction group. If there are attributes that have a -statistically significant correlation with slow response times, they are listed -in a table below the chart. The table is sorted by correlation coefficients that -range from 0 to 1. Attributes with higher correlation values are more likely to -contribute to high latency transactions. By default, the attribute with the -highest correlation value is added to the chart. To see the latency distribution -for other attributes, select their row in the table. - -If a correlated attribute seems noteworthy, use the **Filter** quick links: - -* `+` creates a new query in the {apm-app} for filtering transactions containing -the selected value. -* `-` creates a new query in the {apm-app} to filter out transactions containing -the selected value. - -You can also click the icon beside the field name to view and filter its most -popular values. - -In this example screenshot, there are transactions that are skewed to the right -with slower response times than the overall latency distribution. If you select -the `+` filter in the appropriate row of the table, it creates a new query in -the {apm-app} for transactions with this attribute. With the "noise" now -filtered out, you can begin viewing sample traces to continue your investigation. - -[discrete] -[[correlations-error-rate]] -==== Find failed transaction correlations - -The correlations on the *Failed transaction correlations* tab help you discover -which attributes are most influential in distinguishing between transaction -failures and successes. In this context, the success or failure of a transaction -is determined by its {ecs-ref}/ecs-event.html#field-event-outcome[event.outcome] -value. For example, APM agents set the `event.outcome` to `failure` when an HTTP -transaction returns a `5xx` status code. - -The chart highlights the failed transactions in the overall latency distribution -for the transaction group. If there are attributes that have a statistically -significant correlation with failed transactions, they are listed in a table. -The table is sorted by scores, which are mapped to high, medium, or low impact -levels. Attributes with high impact levels are more likely to contribute to -failed transactions. By default, the attribute with the highest score is added -to the chart. To see a different attribute in the chart, select its row in the -table. - -For example, in the screenshot below, there are attributes such as a specific -node and pod name that have medium impact on the failed transactions. - -[role="screenshot"] -image::apm/images/correlations-failed-transactions.png[Failed transaction correlations] - -Select the `+` filter to create a new query in the {apm-app} for transactions -with one or more of these attributes. If you are unfamiliar with a field, click -the icon beside its name to view its most popular values and optionally filter -on those values too. Each time that you add another attribute, it is filtering -out more and more noise and bringing you closer to a diagnosis. diff --git a/docs/apm/custom-links.asciidoc b/docs/apm/custom-links.asciidoc deleted file mode 100644 index 4fdf39b643f94..0000000000000 --- a/docs/apm/custom-links.asciidoc +++ /dev/null @@ -1,222 +0,0 @@ -[role="xpack"] -[[custom-links]] -=== Custom links - -++++ -Create custom links -++++ - -Elastic's custom link feature allows you to easily create up to 500 dynamic links -based on your specific APM data. -Custom links can be filtered to only appear in the APM app for relevant services, -environments, transaction types, or transaction names. - -Ready to dive in? Jump straight to the <>. - -[float] -[[custom-links-create]] -=== Create a link - -Each custom link consists of a label, URL, and optional filter. -The easiest way to create a custom link is from within the actions dropdown in the transaction detail page. -This method will automatically apply filters, scoping the link to that specific service, -environment, transaction type, and transaction name. - -Alternatively, you can create a custom link in the APM app by navigating to **Settings** > **Customize UI**, -and selecting **Create custom link**. - -[float] -[[custom-links-label]] -==== Label - -The name of your custom link. -The actions context menu displays this text, so keep it as short as possible. - -TIP: Custom links are displayed alphabetically in the actions menu. - -[float] -[[custom-links-url]] -==== URL - -The URL your link points to. -URLs support dynamic field name variables, encapsulated in double curly brackets: `{{field.name}}`. -These variables will be replaced with transaction metadata when the link is clicked. - -Because everyone's data is different, -you'll need to examine your traces to see what metadata is available for use. -To do this, select a trace in the APM app, and click **Metadata** in the **Trace Sample** table. - -[role="screenshot"] -image::apm/images/example-metadata.png[Example metadata] - -[float] -[[custom-links-filters]] -==== Filters - -Filter each link to only appear for specific services or transactions. -You can filter on the following fields: - -* `service.name` -* `service.env` -* `transaction.type` -* `transaction.name` - -Multiple values are allowed when comma-separated. - -[float] -[[custom-links-examples]] -=== Custom link examples - -// Relevant documentation links -:jira-query-params: https://confluence.atlassian.com/jirakb/how-to-create-issues-using-direct-html-links-in-jira-server-159474.html -:github-query-params: https://help.github.com/en/github/managing-your-work-on-github/about-automation-for-issues-and-pull-requests-with-query-parameters - -Not sure where to start with custom links? -Take a look at the examples below and customize them to your liking! - -[float] -[[custom-links-examples-email]] -==== Email - -Email the owner of a service. - -|==== -|Label |`Email engineer` -|Link |`mailto:@.com` -|Filters |`service.name:` -|==== - -**Example** - -This link opens an email addressed to the team or owner of `python-backend`. -It will only appear on services with the name `python-backend`. - -|==== -|Label |`Email python-backend engineers` -|Link |`mailto:python_team@elastic.co` -|Filters |`service.name:python-backend` -|==== - -[float] -[[custom-links-examples-gh]] -==== GitHub issue - -Open a GitHub issue with pre-populated metadata from the selected trace sample. - -|==== -|Label |`Open an issue in ` -|Link |`https://github.com///issues/new?title=&body=<BODY>` -|Filters |`service.name:client` -|==== - -**Example** - -This link opens a new GitHub issue in the apm-agent-rum repository. -It populates the issue body with relevant metadata from the currently active trace. -Clicking this link results in the following issue being created: - -[role="screenshot"] -image::apm/images/create-github-issue.png[Example github issue] - -|==== -|Label |`Open an issue in apm-rum-js` -|Link |`https://github.com/elastic/apm-agent-rum-js/issues/new?title=Investigate+APM+trace&body=Investigate+the+following+APM+trace%3A%0D%0A%0D%0Aservice.name%3A+{{service.name}}%0D%0Atransaction.id%3A+{{transaction.id}}%0D%0Acontainer.id%3A+{{container.id}}%0D%0Aurl.full%3A+{{url.full}}` -|Filters |`service.name:client` -|==== - -See the {github-query-params}[GitHub automation documentation] for a full list of supported query parameters. - -[float] -[[custom-links-examples-jira]] -==== Jira task - -Create a Jira task with pre-populated metadata from the selected trace sample. - -|==== -|Label |`Open an issue in Jira` -|Link |`https://<JIRA_BASE_URL>/secure/CreateIssueDetails!init.jspa?<ARGUMENTS>` -|==== - -**Example** - -This link creates a new task on the Engineering board in Jira. -It populates the issue body with relevant metadata from the currently active trace. -Clicking this link results in the following task being created in Jira: - -[role="screenshot"] -image::apm/images/create-jira-issue.png[Example jira issue] - -|==== -|Label |`Open a task in Jira` -|Link |`https://test-site-33.atlassian.net/secure/CreateIssueDetails!init.jspa?pid=10000&issuetype=10001&summary=Created+via+APM&description=Investigate+the+following+APM+trace%3A%0D%0A%0D%0Aservice.name%3A+{{service.name}}%0D%0Atransaction.id%3A+{{transaction.id}}%0D%0Acontainer.id%3A+{{container.id}}%0D%0Aurl.full%3A+{{url.full}}` -|==== - -See the {jira-query-params}[Jira application administration knowledge base] -for a full list of supported query parameters. - -[float] -[[custom-links-examples-kib]] -==== Kibana dashboards - -Link to a custom dashboard in Kibana. - -|==== -|Label |`Open transaction in custom visualization` -|Link |`https://kibana-instance/app/kibana#/dashboard?_g=query:(language:kuery,query:'transaction.id:{{transaction.id}}'...` -|==== - -**Example** - -This link opens the current `transaction.id` in a custom kibana dashboard. -There are no filters set. - -|==== -|Label |`Open transaction in Python drilldown viz` -|URL |`https://kibana-instance/app/kibana#/dashboard?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-24h,to:now))&_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:cb79c1c0-1af8-472c-aaf7-d158a76946fb,w:24,x:0,y:0),id:c8c74b20-6a30-11ea-92ab-b5d3feff11df,panelIndex:cb79c1c0-1af8-472c-aaf7-d158a76946fb,type:visualization,version:'7.7')),query:(language:kuery,query:'transaction.id:{{transaction.id}}'),timeRestore:!f,title:'',viewMode:edit)` -|==== - -[float] -[[custom-links-examples-slack]] -==== Slack channel - -Open a specified slack channel. - -|==== -|Label |`Open SLACK_CHANNEL` -|Link |`https://COMPANY_SLACK.slack.com/archives/SLACK_CHANNEL` -|Filters |`service.name` : `SERVICE_NAME` -|==== - -**Example** - -This link opens a company slack channel, #apm-support. -It only appears when `transaction.name` is `GET user/login`. - -|==== -|Label |`Open #apm-user-support` -|Link |`https://microsoft.slack.com/archives/efk52kt23k` -|Filters |`transaction.name:GET user/login` -|==== - -[float] -[[custom-links-examples-web]] -==== Website - -Open an internal or external website. - -|==== -|Label |`Open <WEBSITE>` -|Link |`https://<COMPANY_SLACK>.slack.com/archives/<SLACK_CHANNEL>` -|Filters |`service.name:<SERVICE_NAME>` -|==== - -**Example** - -This link opens more data on a specific `user.email`. -It only appears on front-end transactions. - -|==== -|Label |`View user internally` -|Link |`https://internal-site.company.com/user/{{user.email}}` -|Filters |`service.name:client` -|==== diff --git a/docs/apm/dependencies.asciidoc b/docs/apm/dependencies.asciidoc deleted file mode 100644 index 4ac5cdef3f71c..0000000000000 --- a/docs/apm/dependencies.asciidoc +++ /dev/null @@ -1,48 +0,0 @@ -[role="xpack"] -[[dependencies]] -=== Dependencies - -APM agents collect details about external calls made from instrumented services. -Sometimes, these external calls resolve into a downstream service that's instrumented -- in these cases, -you can utilize <<distributed-tracing,distributed tracing>> to drill down into problematic downstream services. -Other times, though, it's not possible to instrument a downstream dependency -- -like with a database or third-party service. -**Dependencies** gives you a window into these uninstrumented, downstream dependencies. - -[role="screenshot"] -image::apm/images/dependencies.png[Dependencies view in the APM app in Kibana] - -Many application issues are caused by slow or unresponsive downstream dependencies. -And because a single, slow dependency can significantly impact the end-user experience, -it's important to be able to quickly identify these problems and determine the root cause. - -Select a dependency to see detailed latency, throughput, and failed transaction rate metrics. - -[role="screenshot"] -image::apm/images/dependencies-drilldown.png[Dependencies drilldown view in the APM app in Kibana] - -When viewing a dependency, consider your pattern of usage with that dependency. -If your usage pattern _hasn't_ increased or decreased, -but the experience has been negatively effected -- either with an increase in latency or errors, -there's likely a problem with the dependency that needs to be addressed. - -If your usage pattern _has_ changed, the dependency view can quickly show you whether -that pattern change exists in all upstream services, or just a subset of your services. -You might then start digging into traces coming from -impacted services to determine why that pattern change has occurred. - -[float] -[[dependencies-operations]] -==== Operations - -beta::[] - -**Dependency operations** provides a granular breakdown of the operations/queries a dependency is executing. - -[role="screenshot"] -image::apm/images/operations.png[operations view in the APM app in Kibana] - -Selecting an operation displays the operation's impact and performance trends over time, via key metrics like latency, throughput, and failed transaction rate. In addition, the <<spans,**Trace sample timeline**>> provides a visual drill-down into an end-to-end trace sample. - -[role="screenshot"] -image::apm/images/operations-detail.png[operations detail view in the APM app in Kibana] diff --git a/docs/apm/deployment-annotations.asciidoc b/docs/apm/deployment-annotations.asciidoc deleted file mode 100644 index 8add9c58a4cab..0000000000000 --- a/docs/apm/deployment-annotations.asciidoc +++ /dev/null @@ -1,48 +0,0 @@ -[role="xpack"] -[[transactions-annotations]] -=== Track deployments with annotations - -++++ -<titleabbrev>Track deployments with annotations</titleabbrev> -++++ - -[role="screenshot"] -image::apm/images/apm-transaction-annotation.png[Example view of transactions annotation in the APM app in Kibana] - -For enhanced visibility into your deployments, we offer deployment annotations on all transaction charts. -This feature enables you to easily determine if your deployment has increased response times for an end-user, -or if the memory/CPU footprint of your application has changed. -Being able to quickly identify bad deployments enables you to rollback and fix issues without causing costly outages. - -By default, automatic deployment annotations are enabled. -This means the APM app will create an annotation on your data when the `service.version` of your application changes. - -Alternatively, you can explicitly create deployment annotations with our annotation API. -The API can integrate into your CI/CD pipeline, -so that each time you deploy, a POST request is sent to the annotation API endpoint: - -[source,curl] ----- -curl -X POST \ - http://localhost:5601/api/apm/services/${SERVICE_NAME}/annotation \ <1> --H 'Content-Type: application/json' \ --H 'kbn-xsrf: true' \ --H 'Authorization: Basic ${API_KEY}' \ <2> --d '{ - "@timestamp": "${DEPLOY_TIME}", <3> - "service": { - "version": "${SERVICE_VERSION}" <4> - }, - "message": "${MESSAGE}" <5> - }' ----- -<1> The `service.name` of your application -<2> An APM app API key with sufficient privileges -<3> The time of the deployment -<4> The `service.version` to be displayed in the annotation -<5> A custom message to be displayed in the annotation - -See the <<apm-annotation-api,annotation API>> reference for more information. - - -NOTE: If custom annotations have been created for the selected time period, any derived annotations, i.e., those created automatically when `service.version` changes, will not be shown. diff --git a/docs/apm/errors.asciidoc b/docs/apm/errors.asciidoc deleted file mode 100644 index 5cc9aebc2e220..0000000000000 --- a/docs/apm/errors.asciidoc +++ /dev/null @@ -1,36 +0,0 @@ -[role="xpack"] -[[errors]] -=== Errors - -TIP: {apm-guide-ref}/data-model-errors.html[Errors] are groups of exceptions with a similar exception or log message. - -The *Errors* overview provides a high-level view of the exceptions that APM agents catch, -or that users manually report with APM agent APIs. -Like errors are grouped together to make it easy to quickly see which errors are affecting your services, -and to take actions to rectify them. - -A service returning a 5xx code from a request handler, controller, etc., will not create -an exception that an APM agent can catch, and will therefore not show up in this view. - -[role="screenshot"] -image::apm/images/apm-errors-overview.png[APM Errors overview] - -Selecting an error group ID or error message brings you to the *Error group*. - -[role="screenshot"] -image::apm/images/apm-error-group.png[APM Error group] - -The error group details page visualizes the number of error occurrences over time and compared to a recent time range. -This allows you to quickly determine if the error rate is changing or remaining constant. -You'll also see the top 5 affected transactions--enabling you to quickly narrow down which transactions are most impacted -by the selected error. - -Further down, you'll see an Error sample. -The error shown is always the most recent to occur. -The sample includes the exception message, culprit, stack trace where the error occurred, -and additional contextual information to help debug the issue--all of which can be copied with the click of a button. - -In some cases, you might also see a Transaction sample ID. -This feature allows you to make a connection between the errors and transactions, -by linking you to the specific transaction where the error occurred. -This allows you to see the whole trace, including which services the request went through. diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc deleted file mode 100644 index e3d085b771a85..0000000000000 --- a/docs/apm/filters.asciidoc +++ /dev/null @@ -1,46 +0,0 @@ -[role="xpack"] -[[filters]] -=== Filters - -++++ -<titleabbrev>Filter data</titleabbrev> -++++ - -Global filters are ways you can filter data across the APM app based on a specific -time range or environment. When viewing a specific service, the filter persists -as you move between tabs. - -[role="screenshot"] -image::apm/images/global-filters.png[Global filters available in the APM app in Kibana] - -[NOTE] -===== -If you prefer to use advanced queries on your data to filter on specific pieces -of information, see <<advanced-queries,Query your data>>. -===== - -[[global-time-range]] -==== Global time range - -The <<set-time-filter,global time range filter>> in {kib} restricts APM data to a specific time period. - -[[environment-selector]] -==== Service environment filter - -The environment selector is a global filter for `service.environment`. -It allows you to view only relevant data and is especially useful for separating development from production environments. -By default, all environments are displayed. If there are no environment options, you'll see "not defined". - -Service environments are defined when configuring your APM agents. -It's vital to be consistent when naming environments in your APM agents. -To learn how to configure service environments, see the specific APM agent documentation: - -* *Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONMENT`] -* *iOS agent:* _Not yet supported_ -* *Java:* {apm-java-ref}/config-core.html#config-environment[`environment`] -* *.NET:* {apm-dotnet-ref}/config-core.html#config-environment[`Environment`] -* *Node.js:* {apm-node-ref}/configuration.html#environment[`environment`] -* *PHP:* {apm-php-ref}/configuration-reference.html#config-environment[`environment`] -* *Python:* {apm-py-ref}/configuration.html#config-environment[`environment`] -* *Ruby:* {apm-ruby-ref}/configuration.html#config-environment[`environment`] -* *Real User Monitoring:* {apm-rum-ref}/configuration.html#environment[`environment`] diff --git a/docs/apm/getting-started.asciidoc b/docs/apm/getting-started.asciidoc deleted file mode 100644 index 1a369202788e1..0000000000000 --- a/docs/apm/getting-started.asciidoc +++ /dev/null @@ -1,71 +0,0 @@ -[role="xpack"] -[[apm-getting-started]] -== Get started with the APM app - -++++ -<titleabbrev>Get started</titleabbrev> -++++ - -// Conditionally display a screenshot or video depending on what the -// current documentation version is. - -ifeval::["{is-current-version}"=="true"] -++++ -<script type="text/javascript" async src="https://play.vidyard.com/embed/v4.js"></script> -<img - style="width: 100%; margin: auto; display: block;" - class="vidyard-player-embed" - src="https://play.vidyard.com/Y4nE2XLYEk75odbRQmUA3g.jpg" - data-uuid="Y4nE2XLYEk75odbRQmUA3g" - data-v="4" - data-type="inline" -/> -</br> -++++ -endif::[] - -For a quick, high-level overview of the health and performance of your application, -start with: - -* <<services>> -* <<traces>> -* <<dependencies>> -* <<service-maps>> - -Notice something awry? Select a service or trace and dive deeper with: - -* <<service-overview>> -* <<mobile-service-overview>> -* <<transactions>> -* <<spans>> -* <<errors>> -* <<metrics>> -* <<infrastructure>> -* <<logs>> - -TIP: Want to learn more about the Elastic APM ecosystem? -See the {apm-guide-ref}/apm-overview.html[APM Overview]. - -include::services.asciidoc[] - -include::traces.asciidoc[] - -include::dependencies.asciidoc[] - -include::service-maps.asciidoc[] - -include::service-overview.asciidoc[] - -include::mobile-service.asciidoc[] - -include::transactions.asciidoc[] - -include::spans.asciidoc[] - -include::errors.asciidoc[] - -include::metrics.asciidoc[] - -include::infrastructure.asciidoc[] - -include::logs.asciidoc[] \ No newline at end of file diff --git a/docs/apm/how-to-guides.asciidoc b/docs/apm/how-to-guides.asciidoc deleted file mode 100644 index fe7d6626a077c..0000000000000 --- a/docs/apm/how-to-guides.asciidoc +++ /dev/null @@ -1,47 +0,0 @@ -[role="xpack"] -[[apm-how-to]] -== How-to guides - -Learn how to perform common APM app tasks. - - -* <<agent-configuration>> -* <<apm-spaces>> -* <<apm-alerts>> -* <<custom-links>> -* <<filters>> -* <<correlations>> -* <<agent-explorer>> -* <<machine-learning-integration>> -* <<mobile-session-explorer>> -* <<apm-lambda>> -* <<advanced-queries>> -* <<storage-explorer>> -* <<transactions-annotations>> - - -include::agent-configuration.asciidoc[] - -include::apm-spaces.asciidoc[] - -include::apm-alerts.asciidoc[] - -include::custom-links.asciidoc[] - -include::filters.asciidoc[] - -include::correlations.asciidoc[] - -include::agent-explorer.asciidoc[] - -include::machine-learning.asciidoc[] - -include::mobile-session-explorer.asciidoc[] - -include::lambda.asciidoc[] - -include::advanced-queries.asciidoc[] - -include::storage-explorer.asciidoc[] - -include::deployment-annotations.asciidoc[] \ No newline at end of file diff --git a/docs/apm/images/active-alert-service.png b/docs/apm/images/active-alert-service.png deleted file mode 100644 index 7d7deaf078ffb..0000000000000 Binary files a/docs/apm/images/active-alert-service.png and /dev/null differ diff --git a/docs/apm/images/add-variable.png b/docs/apm/images/add-variable.png deleted file mode 100644 index 860ab66f22f4e..0000000000000 Binary files a/docs/apm/images/add-variable.png and /dev/null differ diff --git a/docs/apm/images/advanced-discover.png b/docs/apm/images/advanced-discover.png deleted file mode 100644 index 5291526783a6b..0000000000000 Binary files a/docs/apm/images/advanced-discover.png and /dev/null differ diff --git a/docs/apm/images/all-instances.png b/docs/apm/images/all-instances.png deleted file mode 100644 index 70028b5a9b58b..0000000000000 Binary files a/docs/apm/images/all-instances.png and /dev/null differ diff --git a/docs/apm/images/apm-agent-configuration.png b/docs/apm/images/apm-agent-configuration.png deleted file mode 100644 index 22fd9d75c3d73..0000000000000 Binary files a/docs/apm/images/apm-agent-configuration.png and /dev/null differ diff --git a/docs/apm/images/apm-agent-explorer-flyout.png b/docs/apm/images/apm-agent-explorer-flyout.png deleted file mode 100644 index 9792a93a71234..0000000000000 Binary files a/docs/apm/images/apm-agent-explorer-flyout.png and /dev/null differ diff --git a/docs/apm/images/apm-agent-explorer.png b/docs/apm/images/apm-agent-explorer.png deleted file mode 100644 index c6fb7d031ed92..0000000000000 Binary files a/docs/apm/images/apm-agent-explorer.png and /dev/null differ diff --git a/docs/apm/images/apm-alert.png b/docs/apm/images/apm-alert.png deleted file mode 100644 index ccaf2de64ec08..0000000000000 Binary files a/docs/apm/images/apm-alert.png and /dev/null differ diff --git a/docs/apm/images/apm-anomaly-alert.png b/docs/apm/images/apm-anomaly-alert.png deleted file mode 100644 index 35ce9a2296c9c..0000000000000 Binary files a/docs/apm/images/apm-anomaly-alert.png and /dev/null differ diff --git a/docs/apm/images/apm-distributed-tracing.png b/docs/apm/images/apm-distributed-tracing.png deleted file mode 100644 index 4d1b8cde20e95..0000000000000 Binary files a/docs/apm/images/apm-distributed-tracing.png and /dev/null differ diff --git a/docs/apm/images/apm-error-group.png b/docs/apm/images/apm-error-group.png deleted file mode 100644 index 22bceb9d8111b..0000000000000 Binary files a/docs/apm/images/apm-error-group.png and /dev/null differ diff --git a/docs/apm/images/apm-errors-overview.png b/docs/apm/images/apm-errors-overview.png deleted file mode 100644 index c390b7ddc009d..0000000000000 Binary files a/docs/apm/images/apm-errors-overview.png and /dev/null differ diff --git a/docs/apm/images/apm-errors-watcher-assistant.png b/docs/apm/images/apm-errors-watcher-assistant.png deleted file mode 100644 index 1a4d6b5b4c0ea..0000000000000 Binary files a/docs/apm/images/apm-errors-watcher-assistant.png and /dev/null differ diff --git a/docs/apm/images/apm-geo-ui.png b/docs/apm/images/apm-geo-ui.png deleted file mode 100644 index 69c1390a27989..0000000000000 Binary files a/docs/apm/images/apm-geo-ui.png and /dev/null differ diff --git a/docs/apm/images/apm-index-pattern.png b/docs/apm/images/apm-index-pattern.png deleted file mode 100644 index b389110fc422f..0000000000000 Binary files a/docs/apm/images/apm-index-pattern.png and /dev/null differ diff --git a/docs/apm/images/apm-integration-config.png b/docs/apm/images/apm-integration-config.png deleted file mode 100644 index 7ff5cb5e9d0ba..0000000000000 Binary files a/docs/apm/images/apm-integration-config.png and /dev/null differ diff --git a/docs/apm/images/apm-logs-tab.png b/docs/apm/images/apm-logs-tab.png deleted file mode 100644 index c79be8b5eb0b7..0000000000000 Binary files a/docs/apm/images/apm-logs-tab.png and /dev/null differ diff --git a/docs/apm/images/apm-metrics.png b/docs/apm/images/apm-metrics.png deleted file mode 100644 index c2d609c7c4cd5..0000000000000 Binary files a/docs/apm/images/apm-metrics.png and /dev/null differ diff --git a/docs/apm/images/apm-ml-integration.png b/docs/apm/images/apm-ml-integration.png deleted file mode 100644 index e95dae5a41f35..0000000000000 Binary files a/docs/apm/images/apm-ml-integration.png and /dev/null differ diff --git a/docs/apm/images/apm-query-bar.png b/docs/apm/images/apm-query-bar.png deleted file mode 100644 index 457573f485f5a..0000000000000 Binary files a/docs/apm/images/apm-query-bar.png and /dev/null differ diff --git a/docs/apm/images/apm-roles-config.png b/docs/apm/images/apm-roles-config.png deleted file mode 100644 index ebd992abe9303..0000000000000 Binary files a/docs/apm/images/apm-roles-config.png and /dev/null differ diff --git a/docs/apm/images/apm-service-group.png b/docs/apm/images/apm-service-group.png deleted file mode 100644 index 44a0191411e3e..0000000000000 Binary files a/docs/apm/images/apm-service-group.png and /dev/null differ diff --git a/docs/apm/images/apm-service-map-anomaly.png b/docs/apm/images/apm-service-map-anomaly.png deleted file mode 100644 index cd59f86690666..0000000000000 Binary files a/docs/apm/images/apm-service-map-anomaly.png and /dev/null differ diff --git a/docs/apm/images/apm-services-overview.png b/docs/apm/images/apm-services-overview.png deleted file mode 100644 index 0badeea3be4b3..0000000000000 Binary files a/docs/apm/images/apm-services-overview.png and /dev/null differ diff --git a/docs/apm/images/apm-services-trace.png b/docs/apm/images/apm-services-trace.png deleted file mode 100644 index 083c69318e8ed..0000000000000 Binary files a/docs/apm/images/apm-services-trace.png and /dev/null differ diff --git a/docs/apm/images/apm-settings.png b/docs/apm/images/apm-settings.png deleted file mode 100644 index 2c8ebace287b8..0000000000000 Binary files a/docs/apm/images/apm-settings.png and /dev/null differ diff --git a/docs/apm/images/apm-setup.png b/docs/apm/images/apm-setup.png deleted file mode 100644 index 8aadd8911c6e8..0000000000000 Binary files a/docs/apm/images/apm-setup.png and /dev/null differ diff --git a/docs/apm/images/apm-span-detail.png b/docs/apm/images/apm-span-detail.png deleted file mode 100644 index d0b6a4de3d3df..0000000000000 Binary files a/docs/apm/images/apm-span-detail.png and /dev/null differ diff --git a/docs/apm/images/apm-traces.png b/docs/apm/images/apm-traces.png deleted file mode 100644 index c8b8d40b01335..0000000000000 Binary files a/docs/apm/images/apm-traces.png and /dev/null differ diff --git a/docs/apm/images/apm-transaction-annotation.png b/docs/apm/images/apm-transaction-annotation.png deleted file mode 100644 index b9360db2ff474..0000000000000 Binary files a/docs/apm/images/apm-transaction-annotation.png and /dev/null differ diff --git a/docs/apm/images/apm-transaction-duration-dist.png b/docs/apm/images/apm-transaction-duration-dist.png deleted file mode 100644 index 9c7ab5dd67dc0..0000000000000 Binary files a/docs/apm/images/apm-transaction-duration-dist.png and /dev/null differ diff --git a/docs/apm/images/apm-transaction-response-dist.png b/docs/apm/images/apm-transaction-response-dist.png deleted file mode 100644 index 70e5ad7041287..0000000000000 Binary files a/docs/apm/images/apm-transaction-response-dist.png and /dev/null differ diff --git a/docs/apm/images/apm-transaction-sample.png b/docs/apm/images/apm-transaction-sample.png deleted file mode 100644 index a9490fc20d853..0000000000000 Binary files a/docs/apm/images/apm-transaction-sample.png and /dev/null differ diff --git a/docs/apm/images/apm-transactions-overview.png b/docs/apm/images/apm-transactions-overview.png deleted file mode 100644 index 34cd0219b895d..0000000000000 Binary files a/docs/apm/images/apm-transactions-overview.png and /dev/null differ diff --git a/docs/apm/images/apm-transactions-table.png b/docs/apm/images/apm-transactions-table.png deleted file mode 100644 index 8a3415bc9a9f1..0000000000000 Binary files a/docs/apm/images/apm-transactions-table.png and /dev/null differ diff --git a/docs/apm/images/correlations-failed-transactions.png b/docs/apm/images/correlations-failed-transactions.png deleted file mode 100644 index 19221e751ef69..0000000000000 Binary files a/docs/apm/images/correlations-failed-transactions.png and /dev/null differ diff --git a/docs/apm/images/correlations-hover.png b/docs/apm/images/correlations-hover.png deleted file mode 100644 index 9731517b32c43..0000000000000 Binary files a/docs/apm/images/correlations-hover.png and /dev/null differ diff --git a/docs/apm/images/create-github-issue.png b/docs/apm/images/create-github-issue.png deleted file mode 100644 index 81ea4e5e78c27..0000000000000 Binary files a/docs/apm/images/create-github-issue.png and /dev/null differ diff --git a/docs/apm/images/create-jira-issue.png b/docs/apm/images/create-jira-issue.png deleted file mode 100644 index 962c98df3f6c6..0000000000000 Binary files a/docs/apm/images/create-jira-issue.png and /dev/null differ diff --git a/docs/apm/images/dependencies-drilldown.png b/docs/apm/images/dependencies-drilldown.png deleted file mode 100644 index af82ee3d9305f..0000000000000 Binary files a/docs/apm/images/dependencies-drilldown.png and /dev/null differ diff --git a/docs/apm/images/dependencies.png b/docs/apm/images/dependencies.png deleted file mode 100644 index 260025d31654b..0000000000000 Binary files a/docs/apm/images/dependencies.png and /dev/null differ diff --git a/docs/apm/images/dynamic-config.svg b/docs/apm/images/dynamic-config.svg deleted file mode 100644 index df62a3c84f4b4..0000000000000 --- a/docs/apm/images/dynamic-config.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="59" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="59" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#9f9f9f" d="M0 0h0v20H0z"/><path fill="#9f9f9f" d="M0 0h59v20H0z"/><path fill="url(#b)" d="M0 0h59v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="295" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">Dynamic</text><text x="295" y="140" transform="scale(.1)" textLength="490">Dynamic</text></g> </svg> \ No newline at end of file diff --git a/docs/apm/images/error-rate.png b/docs/apm/images/error-rate.png deleted file mode 100644 index 845fa2af07de1..0000000000000 Binary files a/docs/apm/images/error-rate.png and /dev/null differ diff --git a/docs/apm/images/example-metadata.png b/docs/apm/images/example-metadata.png deleted file mode 100644 index 2a5bda7f088f6..0000000000000 Binary files a/docs/apm/images/example-metadata.png and /dev/null differ diff --git a/docs/apm/images/global-filters.png b/docs/apm/images/global-filters.png deleted file mode 100644 index f93a5214c316b..0000000000000 Binary files a/docs/apm/images/global-filters.png and /dev/null differ diff --git a/docs/apm/images/green-service.png b/docs/apm/images/green-service.png deleted file mode 100644 index bbc00a3543b08..0000000000000 Binary files a/docs/apm/images/green-service.png and /dev/null differ diff --git a/docs/apm/images/infra.png b/docs/apm/images/infra.png deleted file mode 100644 index e139012270d5f..0000000000000 Binary files a/docs/apm/images/infra.png and /dev/null differ diff --git a/docs/apm/images/jvm-metrics-overview.png b/docs/apm/images/jvm-metrics-overview.png deleted file mode 100644 index c6f28f7bdf48f..0000000000000 Binary files a/docs/apm/images/jvm-metrics-overview.png and /dev/null differ diff --git a/docs/apm/images/jvm-metrics.png b/docs/apm/images/jvm-metrics.png deleted file mode 100644 index 70f7965b72df6..0000000000000 Binary files a/docs/apm/images/jvm-metrics.png and /dev/null differ diff --git a/docs/apm/images/lambda-cold-start-trace.png b/docs/apm/images/lambda-cold-start-trace.png deleted file mode 100644 index c6f6efd0557ce..0000000000000 Binary files a/docs/apm/images/lambda-cold-start-trace.png and /dev/null differ diff --git a/docs/apm/images/lambda-correlations.png b/docs/apm/images/lambda-correlations.png deleted file mode 100644 index c1a72ccb2d930..0000000000000 Binary files a/docs/apm/images/lambda-correlations.png and /dev/null differ diff --git a/docs/apm/images/lambda-overview.png b/docs/apm/images/lambda-overview.png deleted file mode 100644 index 9d0558949f0ce..0000000000000 Binary files a/docs/apm/images/lambda-overview.png and /dev/null differ diff --git a/docs/apm/images/latency.png b/docs/apm/images/latency.png deleted file mode 100644 index 1c220c1a4bfdd..0000000000000 Binary files a/docs/apm/images/latency.png and /dev/null differ diff --git a/docs/apm/images/local-filter.png b/docs/apm/images/local-filter.png deleted file mode 100644 index edcaf8b6a609c..0000000000000 Binary files a/docs/apm/images/local-filter.png and /dev/null differ diff --git a/docs/apm/images/logs.png b/docs/apm/images/logs.png deleted file mode 100644 index 94d77b47495f1..0000000000000 Binary files a/docs/apm/images/logs.png and /dev/null differ diff --git a/docs/apm/images/metadata-icons.png b/docs/apm/images/metadata-icons.png deleted file mode 100644 index 402c0ed07c70d..0000000000000 Binary files a/docs/apm/images/metadata-icons.png and /dev/null differ diff --git a/docs/apm/images/mobile-location.png b/docs/apm/images/mobile-location.png deleted file mode 100644 index 35b0d91a2d2bf..0000000000000 Binary files a/docs/apm/images/mobile-location.png and /dev/null differ diff --git a/docs/apm/images/mobile-most-used.png b/docs/apm/images/mobile-most-used.png deleted file mode 100644 index 24b29f95d4742..0000000000000 Binary files a/docs/apm/images/mobile-most-used.png and /dev/null differ diff --git a/docs/apm/images/mobile-session-error-details.png b/docs/apm/images/mobile-session-error-details.png deleted file mode 100644 index 41c0bf509514d..0000000000000 Binary files a/docs/apm/images/mobile-session-error-details.png and /dev/null differ diff --git a/docs/apm/images/mobile-session-explorer-apm.png b/docs/apm/images/mobile-session-explorer-apm.png deleted file mode 100644 index 55fbc857901f0..0000000000000 Binary files a/docs/apm/images/mobile-session-explorer-apm.png and /dev/null differ diff --git a/docs/apm/images/mobile-session-explorer-nav.png b/docs/apm/images/mobile-session-explorer-nav.png deleted file mode 100644 index d208f4091201a..0000000000000 Binary files a/docs/apm/images/mobile-session-explorer-nav.png and /dev/null differ diff --git a/docs/apm/images/mobile-session-filter-discover.png b/docs/apm/images/mobile-session-filter-discover.png deleted file mode 100644 index 989284ba2aeac..0000000000000 Binary files a/docs/apm/images/mobile-session-filter-discover.png and /dev/null differ diff --git a/docs/apm/images/mobile-tp.png b/docs/apm/images/mobile-tp.png deleted file mode 100644 index 81ce267fb858e..0000000000000 Binary files a/docs/apm/images/mobile-tp.png and /dev/null differ diff --git a/docs/apm/images/operations-detail.png b/docs/apm/images/operations-detail.png deleted file mode 100644 index 64a1c6550859d..0000000000000 Binary files a/docs/apm/images/operations-detail.png and /dev/null differ diff --git a/docs/apm/images/operations.png b/docs/apm/images/operations.png deleted file mode 100644 index 119f8bdf99ff7..0000000000000 Binary files a/docs/apm/images/operations.png and /dev/null differ diff --git a/docs/apm/images/red-service.png b/docs/apm/images/red-service.png deleted file mode 100644 index be7a62b1774ab..0000000000000 Binary files a/docs/apm/images/red-service.png and /dev/null differ diff --git a/docs/apm/images/service-maps-java.png b/docs/apm/images/service-maps-java.png deleted file mode 100644 index aa8e5dc5056cd..0000000000000 Binary files a/docs/apm/images/service-maps-java.png and /dev/null differ diff --git a/docs/apm/images/service-maps.png b/docs/apm/images/service-maps.png deleted file mode 100644 index 3d8d48bd957ae..0000000000000 Binary files a/docs/apm/images/service-maps.png and /dev/null differ diff --git a/docs/apm/images/service-quick-health.png b/docs/apm/images/service-quick-health.png deleted file mode 100644 index aab1332513079..0000000000000 Binary files a/docs/apm/images/service-quick-health.png and /dev/null differ diff --git a/docs/apm/images/spans-dependencies.png b/docs/apm/images/spans-dependencies.png deleted file mode 100644 index 558099dd450c1..0000000000000 Binary files a/docs/apm/images/spans-dependencies.png and /dev/null differ diff --git a/docs/apm/images/specific-transaction-search.png b/docs/apm/images/specific-transaction-search.png deleted file mode 100644 index 4ed548f015713..0000000000000 Binary files a/docs/apm/images/specific-transaction-search.png and /dev/null differ diff --git a/docs/apm/images/specific-transaction.png b/docs/apm/images/specific-transaction.png deleted file mode 100644 index 52073bf76520a..0000000000000 Binary files a/docs/apm/images/specific-transaction.png and /dev/null differ diff --git a/docs/apm/images/storage-explorer-expanded.png b/docs/apm/images/storage-explorer-expanded.png deleted file mode 100644 index 844f07b6f8b7e..0000000000000 Binary files a/docs/apm/images/storage-explorer-expanded.png and /dev/null differ diff --git a/docs/apm/images/storage-explorer-overview.png b/docs/apm/images/storage-explorer-overview.png deleted file mode 100644 index d3fcccca5f6ee..0000000000000 Binary files a/docs/apm/images/storage-explorer-overview.png and /dev/null differ diff --git a/docs/apm/images/time-series-expected-bounds-comparison.png b/docs/apm/images/time-series-expected-bounds-comparison.png deleted file mode 100644 index 6e705064e65b3..0000000000000 Binary files a/docs/apm/images/time-series-expected-bounds-comparison.png and /dev/null differ diff --git a/docs/apm/images/trace-explorer.png b/docs/apm/images/trace-explorer.png deleted file mode 100644 index 70c13f650e2fb..0000000000000 Binary files a/docs/apm/images/trace-explorer.png and /dev/null differ diff --git a/docs/apm/images/traffic-transactions.png b/docs/apm/images/traffic-transactions.png deleted file mode 100644 index 05e66dfaa4ece..0000000000000 Binary files a/docs/apm/images/traffic-transactions.png and /dev/null differ diff --git a/docs/apm/images/transaction-icon.png b/docs/apm/images/transaction-icon.png deleted file mode 100644 index 3534e6915d6cb..0000000000000 Binary files a/docs/apm/images/transaction-icon.png and /dev/null differ diff --git a/docs/apm/images/yellow-service.png b/docs/apm/images/yellow-service.png deleted file mode 100644 index 43afd6250be72..0000000000000 Binary files a/docs/apm/images/yellow-service.png and /dev/null differ diff --git a/docs/apm/index.asciidoc b/docs/apm/index.asciidoc deleted file mode 100644 index 2674ecf2a13b8..0000000000000 --- a/docs/apm/index.asciidoc +++ /dev/null @@ -1,41 +0,0 @@ -[role="xpack"] -[[xpack-apm]] -= APM - -[partintro] --- -The APM app in {kib} allows you to monitor your software services and applications in real-time; -visualize detailed performance information on your services, -identify and analyze errors, -and monitor host-level and APM agent-specific metrics like JVM and Go runtime metrics. - -[float] -[[apm-bottlenecks]] -== Visualizing application bottlenecks - -Having access to application-level insights with just a few clicks can drastically decrease the time you spend -debugging errors, slow response times, and crashes. - -For example, you can see information about response times, requests per minute, and status codes per endpoint. -You can even dive into a specific request sample and get a complete waterfall view of what your application is spending its time on. -You might see that your bottlenecks are in database queries, cache calls, or external requests. -For each incoming request and each application error, -you can also see contextual information such as the request header, user information, -system values, or custom data that you manually attached to the request. --- - -:beat_kib_app: APM and User Experience - -include::set-up.asciidoc[] - -include::getting-started.asciidoc[] - -include::how-to-guides.asciidoc[] - -include::apm-app-users.asciidoc[] - -include::settings.asciidoc[] - -include::api.asciidoc[] - -include::troubleshooting.asciidoc[] diff --git a/docs/apm/infrastructure.asciidoc b/docs/apm/infrastructure.asciidoc deleted file mode 100644 index ff6343061ca24..0000000000000 --- a/docs/apm/infrastructure.asciidoc +++ /dev/null @@ -1,15 +0,0 @@ -[role="xpack"] -[[infrastructure]] -=== Infrastructure - -beta::[] - -The *Infrastructure* tab provides information about the containers, pods, and hosts -that the selected service is linked to. - -[role="screenshot"] -image::apm/images/infra.png[Example view of the Infrastructure tab in APM app in Kibana] - -IT ops and software reliability engineers (SREs) can use this tab -to quickly find a service's underlying infrastructure resources when debugging a problem. -Knowing what infrastructure is related to a service allows you to remediate issues by restarting, killing hanging instances, changing configuration, rolling back deployments, scaling up, scaling out, and so on. \ No newline at end of file diff --git a/docs/apm/lambda.asciidoc b/docs/apm/lambda.asciidoc deleted file mode 100644 index af3012dd19a5e..0000000000000 --- a/docs/apm/lambda.asciidoc +++ /dev/null @@ -1,52 +0,0 @@ -[role="xpack"] -[[apm-lambda]] -=== Observe Lambda functions - -Elastic APM provides performance and error monitoring for AWS Lambda functions. -See how your Lambda functions relate to and depend on other services, and -get insight into function execution and runtime behavior, like lambda duration, cold start rate, cold start duration, compute usage, memory usage, and more. - -To set up Lambda monitoring, see the relevant -{apm-guide-ref}/monitoring-aws-lambda.html[quick start guide]. - -[role="screenshot"] -image::apm/images/lambda-overview.png[lambda overview] - -[float] -[[apm-lambda-cold-start-info]] -==== Cold starts - -A cold start occurs when a Lambda function has not been used for a certain period of time. A lambda worker receives a request to run the function and prepares an execution environment. - -Cold starts are an unavoidable byproduct of the serverless world, but visibility into how they impact your services can help you make better decisions about factors like how much memory to allocate to a function, whether to enable provisioned concurrency, or if it's time to consider removing a large dependency. - -[float] -[[apm-lambda-cold-start-rate]] -===== Cold start rate - -The cold start rate (i.e. proportion of requests that experience a cold start) is displayed per service and per transaction. - -Cold start is also displayed in the trace waterfall, where you can drill-down into individual traces and see trace metadata like AWS request ID, trigger type, and trigger request ID. - -[role="screenshot"] -image::apm/images/lambda-cold-start-trace.png[lambda cold start trace] - -[float] -[[apm-lambda-cold-start-latency]] -===== Latency distribution correlation - -The <<correlations-latency,latency correlations>> feature can be used to visualize the impact of Lambda cold starts on latency--just select the `faas.coldstart` field. - -[role="screenshot"] -image::apm/images/lambda-correlations.png[lambda correlations example] - -[float] -[[apm-lambda-service-config]] -==== AWS Lambda function grouping - -The default APM agent configuration results in one APM service per AWS Lambda function, -where the Lambda function name is the service name. - -In some use cases, it makes more sense to logically group multiple lambda functions under a single -APM service. You can achieve this by setting the `ELASTIC_APM_SERVICE_NAME` environment variable -on related Lambda functions to the same value. diff --git a/docs/apm/logs.asciidoc b/docs/apm/logs.asciidoc deleted file mode 100644 index 04f5eff185692..0000000000000 --- a/docs/apm/logs.asciidoc +++ /dev/null @@ -1,19 +0,0 @@ -[role="xpack"] -[[logs]] -=== Logs - -The *Logs* tab shows contextual logs for the selected service. - -// tag::log-overview[] -Logs provide detailed information about specific events, and are crucial to successfully debugging slow or erroneous transactions. - -If you've correlated your application's logs and traces, you never have to search for relevant data; it's already available to you. Viewing log and trace data together allows you to quickly diagnose and solve problems. - -To learn how to correlate your logs with your instrumented services, -see {observability-guide}/application-logs.html[log correlation] -// end::log-overview[] - -[role="screenshot"] -image::apm/images/logs.png[Example view of the Logs tab in APM app in Kibana] - -TIP: Logs displayed on this page are filtered on `service.name` diff --git a/docs/apm/machine-learning.asciidoc b/docs/apm/machine-learning.asciidoc deleted file mode 100644 index f01cdf70b6e05..0000000000000 --- a/docs/apm/machine-learning.asciidoc +++ /dev/null @@ -1,73 +0,0 @@ -[role="xpack"] -[[machine-learning-integration]] -=== Machine learning integration - -++++ -<titleabbrev>Integrate with machine learning</titleabbrev> -++++ - -The Machine learning integration initiates a new job predefined to calculate anomaly scores on APM transaction durations. -With this integration, you can quickly pinpoint anomalous transactions and see the health of -any upstream and downstream services. - -Machine learning jobs are created per environment and are based on a service's average response time. -Because jobs are created at the environment level, -you can add new services to your existing environments without the need for additional machine learning jobs. - -Results from machine learning jobs are shown in multiple places throughout the APM app: - -* The **Services overview** provides a quick-glance view of the general health of all of your services. -+ -[role="screenshot"] -image::apm/images/service-quick-health.png[Example view of anomaly scores on response times in the APM app] - -* The transaction duration chart will show the expected bounds and add an annotation when the anomaly score is 75 or above. -+ -[role="screenshot"] -image::apm/images/apm-ml-integration.png[Example view of anomaly scores on response times in the APM app] - -* Service Maps will display a color-coded anomaly indicator based on the detected anomaly score. -+ -[role="screenshot"] -image::apm/images/apm-service-map-anomaly.png[Example view of anomaly scores on service maps in the APM app] - -[float] -[[create-ml-integration]] -=== Enable anomaly detection - -To enable machine learning anomaly detection: - -. From the Services overview, Traces overview, or Service Map tab, -select **Anomaly detection**. - -. Click **Create Job**. - -. Machine learning jobs are created at the environment level. -Select all of the service environments that you want to enable anomaly detection in. -Anomalies will surface for all services and transaction types within the selected environments. - -. Click **Create Jobs**. - -That's it! After a few minutes, the job will begin calculating results; -it might take additional time for results to appear on your service maps. -To manage existing jobs, click **Manage jobs**. - -[float] -[[warning-ml-integration]] -=== Anomaly detection warning - -To make machine learning as easy as possible to set up, -the APM app will warn you when filtered to an environment without a machine learning job. - -[role="screenshot"] -image::apm/images/apm-anomaly-alert.png[Example view of anomaly alert in the APM app] - -[float] -[[unkown-ml-integration]] -=== Unknown service health - -After enabling anomaly detection, service health may display as "Unknown". Here are some reasons why this can occur: - -1. No machine learning job exists. See <<create-ml-integration>> to enable anomaly detection and create a machine learning job. -2. There is no machine learning data for the job. If you just created the machine learning job you'll need to wait a few minutes for data to be available. Alternatively, if the service or its enviroment are new, you'll need to wait for more trace data. -3. No "request" or "page-load" transaction type exists for this service; service health is only available for these transaction types. diff --git a/docs/apm/metrics.asciidoc b/docs/apm/metrics.asciidoc deleted file mode 100644 index c8f2a9c552720..0000000000000 --- a/docs/apm/metrics.asciidoc +++ /dev/null @@ -1,24 +0,0 @@ -[role="xpack"] -[[metrics]] -=== Metrics - -The *Metrics* overview provides APM agent-specific metrics, -which lets you perform more in-depth root cause analysis investigations within the APM app. - -If you're experiencing a problem with your service, you can use this page to attempt to find the underlying cause. -For example, you might be able to correlate a high number of errors with a long transaction duration, high CPU usage, or a memory leak. - -[role="screenshot"] -image::apm/images/apm-metrics.png[Example view of the Metrics overview in APM app in Kibana] - -If you're using the Java APM agent, you can view metrics for each JVM. - -[role="screenshot"] -image::apm/images/jvm-metrics-overview.png[Example view of the Metrics overview for the Java Agent] - -Breaking down metrics by JVM makes it much easier to analyze the provided metrics: -CPU usage, memory usage, heap or non-heap memory, -thread count, garbage collection rate, and garbage collection time spent per minute. - -[role="screenshot"] -image::apm/images/jvm-metrics.png[Example view of the Metrics overview for the Java Agent] diff --git a/docs/apm/mobile-errors.asciidoc b/docs/apm/mobile-errors.asciidoc deleted file mode 100644 index df4e4f380b0c7..0000000000000 --- a/docs/apm/mobile-errors.asciidoc +++ /dev/null @@ -1,36 +0,0 @@ -[role="xpack"] -[[mobile-errors-crashes]] -=== Mobile errors and crashes - -TIP: {apm-guide-ref}/data-model-errors.html[Errors] are groups of exceptions with a similar exception or log message. - -The *Errors & Crashes* overview provides a high-level view of errors and crashes that APM mobile agents catch, -or that users manually report with APM agent APIs. Errors and crashes are separated into two tabs for easy differentiation. -Like errors are grouped together to make it easy to quickly see which errors are affecting your services, -and to take actions to rectify them. - - - - - -[role="screenshot"] -image::apm/images/mobile-errors-overview.png[Mobile Errors overview] - -Selecting an error group ID or error message brings you to the *Error group*. - -[role="screenshot"] -image::apm/images/mobile-error-group.png[Mobile Error group] - -The error group details page visualizes the number of error occurrences over time and compared to a recent time range. -This allows you to quickly determine if the error rate is changing or remaining constant. -You'll also see the "most affected" chart which can be oriented to 'by device' or 'by app version'. - -Further down, you'll see an Error sample. -The error shown is always the most recent to occur. -The sample includes the exception message, culprit, stack trace where the error occurred (when available), -and additional contextual information to help debug the issue--all of which can be copied with the click of a button. - -In some cases, you might also see a Transaction sample ID. -This feature allows you to make a connection between the errors and transactions, -by linking you to the specific transaction where the error occurred. -This allows you to see the whole trace, including which services the request went through. diff --git a/docs/apm/mobile-service.asciidoc b/docs/apm/mobile-service.asciidoc deleted file mode 100644 index 774d4592dd67a..0000000000000 --- a/docs/apm/mobile-service.asciidoc +++ /dev/null @@ -1,61 +0,0 @@ -[role="xpack"] -[[mobile-service-overview]] -=== Mobile service overview - -Selecting a mobile service brings you to the *Mobile service overview*. -The *Mobile service overview* contains a wide variety of charts and tables that provide -high-level visibility into how a mobile service is performing for your users--enabling you -to make data-driven decisions about how to improve your user experience. - -For example, see: - -* Crash Rate (Crashes per session) -* Slowest App load time -- coming soon -* Number of sessions -* Number of HTTP requests -* Map showing the total number of HTTP requests based on country and region -* Most used devices, network connection type, OS version, and app version -* Latency, throughput, and errors over time -* Service dependencies - -All of these metrics & insights can help SREs and developers better understand the health -of their mobile application environment and the impact of backend errors and bottlenecks on end-user experience. - -[discrete] -[[mobile-service-stats]] -=== Quick stats - -Understand the impact of slow application load times and variations in application crash rate on user traffic (coming soon). -Visualize session and HTTP trends, and see where your users are located--enabling you to optimize your infrastructure deployment and routing topology. - -Note: due to the way crash rate is calculated (crashes per session) it is possible to have greater than 100% rate, due to fact that a session may contain multiple crashes. - -[role="screenshot"] -image::apm/images/mobile-location.png[mobile service overview centered on location map] - -[discrete] -[[mobile-service-most-used]] -=== Most used - -Optimize your end-user experience and your application QA strategy based on your most used device models and operating system versions. - -[role="screenshot"] -image::apm/images/mobile-most-used.png[mobile service overview showing most used devices, network, OS, and app version] - - -[discrete] -[[mobile-throughput-transactions]] -=== Throughput and transactions - -include::./service-overview.asciidoc[tag=throughput-transactions] - -[discrete] -[[mobile-error-and-dependencies]] -=== Failed transaction rate and dependencies - -include::./service-overview.asciidoc[tag=ftr] - -include::./service-overview.asciidoc[tag=dependencies] - -[role="screenshot"] -image::apm/images/mobile-tp.png[mobile service overview showing latency, throughput, and errors] \ No newline at end of file diff --git a/docs/apm/mobile-session-explorer.asciidoc b/docs/apm/mobile-session-explorer.asciidoc deleted file mode 100644 index 92d4e4dc1fe8d..0000000000000 --- a/docs/apm/mobile-session-explorer.asciidoc +++ /dev/null @@ -1,43 +0,0 @@ -[role="xpack] -[[mobile-session-explorer]] -=== Exploring mobile sessions with Discover -Elastic Mobile APM provides session tracking by attaching a `session.id`, a guid, to every span and event. -This allows for the recall of the activities of a specific user during a specific period of time. The best way recall -these data points is using the xref:document-explorer[Discover document explorer]. This guide will explain how to do that. - -=== Viewing sessions with Discover - -The first step is to find the relevant `session.id`. In this example, we'll walk through investigating a crash. -Since all events and spans have `session.id` attributes, a crash is no different. - -The steps to follow are: - -* copy the `session.id` from the relevant document. -* Open the Discover page. -* Select the appropriate data view (use `APM` to search all datastreams) -* set filter to the copied `session.id` - -Here we can see the `session.id` guid in the metadata viewer in the error detail view: -[role="screenshot"] -image::images/mobile-session-error-details.png[Example of session.id in error details] - -Copy this value and open the Discover page: - -[role="screenshot"] -image::images/mobile-session-explorer-nav.png[Example view of navigation to Discover] - - -set the data view. `APM` selected in the example: - -[role="screenshot"] -image::images/mobile-session-explorer-apm.png[Example view of Explorer selecting APM data view] - -filter using the `session.id`: `session.id: "<copied session id guid>"`: - -[role="screenshot"] -image::images/mobile-session-filter-discover.png[Filter Explor using session.id] - -explore all the documents associated with that session id including crashes, lifecycle events, network requests, errors, and other custom events! - - - diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc deleted file mode 100644 index 85c8efa4adb5d..0000000000000 --- a/docs/apm/service-maps.asciidoc +++ /dev/null @@ -1,118 +0,0 @@ -[role="xpack"] -[[service-maps]] -=== Service Map - -A service map is a real-time visual representation of the instrumented services in your application's architecture. -It shows you how these services are connected, along with high-level metrics like average transaction duration, -requests per minute, and errors per minute. -If enabled, service maps also integrate with machine learning--for real time health indicators based on anomaly detection scores. -All of these features can help you quickly and visually assess your services' status and health. - -// Conditionally display a screenshot or video depending on what the -// current documentation version is. - -ifeval::["{is-current-version}"=="true"] -++++ -<script type="text/javascript" async src="https://play.vidyard.com/embed/v4.js"></script> -<img - style="width: 100%; margin: auto; display: block;" - class="vidyard-player-embed" - src="https://play.vidyard.com/VH8gKnPE3Z2csACZTCeQrw.jpg" - data-uuid="VH8gKnPE3Z2csACZTCeQrw" - data-v="4" - data-type="inline" -/> -</br> -++++ -endif::[] - -ifeval::["{is-current-version}"=="false"] -[role="screenshot"] -image::apm/images/service-maps.png[Example view of service maps in the APM app in Kibana] -endif::[] - -We currently surface two types of service maps: - -* Global: All services instrumented with APM agents and the connections between them are shown. -* Service-specific: Highlight connections for a selected service. - -[float] -[[service-maps-how]] -=== How do service maps work? - -Service Maps rely on distributed traces to draw connections between services. -As {apm-guide-ref}/apm-distributed-tracing.html[distributed tracing] is enabled out-of-the-box for supported technologies, so are service maps. -However, if a service isn't instrumented, -or a `traceparent` header isn't being propagated to it, -distributed tracing will not work, and the connection will not be drawn on the map. - -[float] -[[visualize-your-architecture]] -=== Visualize your architecture - -Select the **Service Map** tab to get started. -By default, all instrumented services and connections are shown. -Whether you're onboarding a new engineer, or just trying to grasp the big picture, -drag things around, zoom in and out, and begin to visualize how your services are connected. - -Customize what the service map displays using either the query bar or the environment selector. -The query bar enables you to use <<advanced-queries,advanced queries>> to customize the service map based on your needs. -The environment selector allows you to narrow displayed results to a specific environment. -This can be useful if you have two or more services, in separate environments, but with the same name. -Use the environment drop-down to only see the data you're interested in, like `dev` or `production`. - -If there's a specific service that interests you, select that service to highlight its connections. -Click **Focus map** to refocus the map on the selected service and lock the connection highlighting. -From here, select **Service Details**, or click the **Transactions** tab to jump to the Transaction overview for the selected service. -You can also use the tabs at the top of the page to easily jump to the **Errors** or **Metrics** overview. - -[role="screenshot"] -image::apm/images/service-maps-java.png[Example view of service maps in the APM app in Kibana] - -[float] -[[service-map-anomaly-detection]] -=== Anomaly detection with machine learning - -You can create machine learning jobs to calculate anomaly scores on APM transaction durations within the selected service. -When these jobs are active, service maps will display a color-coded anomaly indicator based on the detected anomaly score: - -[horizontal] -image:apm/images/green-service.png[APM green service]:: Max anomaly score **≤25**. Service is healthy. -image:apm/images/yellow-service.png[APM yellow service]:: Max anomaly score **26-74**. Anomalous activity detected. Service may be degraded. -image:apm/images/red-service.png[APM red service]:: Max anomaly score **≥75**. Anomalous activity detected. Service is unhealthy. - -[role="screenshot"] -image::apm/images/apm-service-map-anomaly.png[Example view of anomaly scores on service maps in the APM app] - -If an anomaly has been detected, click *View anomalies* to view the anomaly detection metric viewer in the Machine learning app. -This time series analysis will display additional details on the severity and time of the detected anomalies. - -To learn how to create a machine learning job, see <<machine-learning-integration,machine learning integration>>. - -[float] -[[service-maps-legend]] -=== Legend - -Nodes appear on the map in one of two shapes: - -* **Circle**: Instrumented services. Interior icons are based on the language of the APM agent used. -* **Diamond**: Databases, external, and messaging. Interior icons represent the generic type, -with specific icons for known entities, like Elasticsearch. -Type and subtype are based on `span.type`, and `span.subtype`. - -[float] -[[service-maps-supported]] -=== Supported APM agents - -Service Maps are supported for the following APM agent versions: - -[horizontal] -Go agent:: ≥ v1.7.0 -iOS agent:: _Not yet supported_ -Java agent:: ≥ v1.13.0 -.NET agent:: ≥ v1.3.0 -Node.js agent:: ≥ v3.6.0 -PHP agent:: ≥ v1.2.0 -Python agent:: ≥ v5.5.0 -Ruby agent:: ≥ v3.6.0 -Real User Monitoring (RUM) agent:: ≥ v4.7.0 diff --git a/docs/apm/service-overview.asciidoc b/docs/apm/service-overview.asciidoc deleted file mode 100644 index a47ba33a7486f..0000000000000 --- a/docs/apm/service-overview.asciidoc +++ /dev/null @@ -1,193 +0,0 @@ -[role="xpack"] -[[service-overview]] -=== Service overview - -Selecting a non-mobile <<services,*service*>> brings you to the *Service overview*. -The *Service overview* contains a wide variety of charts and tables that provide -high-level visibility into how a service is performing across your infrastructure: - -* Service details like service version, runtime version, framework, and APM agent name and version -* Container and orchestration information -* Cloud provider, machine type, service name, region, and availability zone -* Serverless function names and event trigger type -* Latency, throughput, and errors over time -* Service dependencies - -[discrete] -[[service-time-comparison]] -=== Time series and expected bounds comparison - -For insight into the health of your services, you can compare how a service -performs relative to a previous time frame or to the expected bounds from the -corresponding {anomaly-job}. For example, has latency been slowly increasing -over time, did the service experience a sudden spike, is the throughput similar -to what the {ml} job expects – enabling a comparison can provide the answer. - -[role="screenshot"] -image::apm/images/time-series-expected-bounds-comparison.png[Time series and expected bounds comparison] - -Select the *Comparison* box to apply a time-based or expected bounds comparison. -The time-based comparison options are based on the selected time filter range: - -[options="header"] -|==== -|Time filter | Time comparison options - -|≤ 24 hours -|One day or one week - -|> 24 hours and ≤ 7 days -|One week - -|> 7 days -|An identical amount of time immediately before the selected time range -|==== - -You can use the expected bounds comparison if {ml-jobs} exist in your selected -environment and you have -{ml-docs}/setup.html#kib-visibility-spaces[access to the {ml-features}]. - -[discrete] -[[service-latency]] -=== Latency - -Response times for the service. You can filter the *Latency* chart to display the average, -95th, or 99th percentile latency times for the service. - -[role="screenshot"] -image::apm/images/latency.png[Service latency] - -[discrete] -[[service-throughput-transactions]] -=== Throughput and transactions - -// tag::throughput-transactions[] -The *Throughput* chart visualizes the average number of transactions per minute for the selected service. - -The *Transactions* table displays a list of _transaction groups_ for the -selected service and includes the latency, traffic, error rate, and the impact for each transaction. -Transactions that share the same name are grouped, and only one entry is displayed for each group. - -By default, transaction groups are sorted by _Impact_ to show the most used and slowest endpoints in your -service. If there is a particular endpoint you are interested in, click *View transactions* to view a -list of similar transactions on the <<transactions, transactions overview>> page. - -[role="screenshot"] -image::apm/images/traffic-transactions.png[Traffic and transactions] -// end::throughput-transactions[] - -[discrete] -[[service-error-rates]] -=== Failed transaction rate and errors - -// tag::ftr[] -The failed transaction rate represents the percentage of failed transactions from the perspective of the selected service. -It's useful for visualizing unexpected increases, decreases, or irregular patterns in a service's transactions. - -[TIP] -==== -HTTP **transactions** from the HTTP server perspective do not consider a `4xx` status code (client error) as a failure -because the failure was caused by the caller, not the HTTP server. Thus, `event.outcome=success` and there will be no increase in failed transaction rate. - -HTTP **spans** from the client perspective however, are considered failures if the HTTP status code is ≥ 400. -These spans will set `event.outcome=failure` and increase the failed transaction rate. - -If there is no HTTP status, both transactions and spans are considered successful unless an error is reported. -==== -// end::ftr[] - -The *Errors* table provides a high-level view of each error message when it first and last occurred, -along with the total number of occurrences. This makes it very easy to quickly see which errors affect -your services and take actions to rectify them. To do so, click *View errors*. - -[role="screenshot"] -image::apm/images/error-rate.png[failed transaction rate and errors] - -[discrete] -[[service-span-duration]] -=== Span types average duration and dependencies - -The *Time spent by span type* chart visualizes each span type's average duration and helps you determine -which spans could be slowing down transactions. The "app" label displayed under the -chart indicates that something was happening within the application. This could signal that the APM -agent does not have auto-instrumentation for whatever was happening during that time or that the time was spent in the -application code and not in database or external requests. - -// tag::dependencies[] -The *Dependencies* table displays a list of downstream services or external connections relevant -to the service at the selected time range. The table displays latency, throughput, failed transaction rate, and the impact of -each dependency. By default, dependencies are sorted by _Impact_ to show the most used and the slowest dependency. -If there is a particular dependency you are interested in, click *<<dependencies,View dependencies>>* to learn more about it. - -NOTE: Displaying dependencies for services instrumented with the Real User Monitoring (RUM) agent -requires an agent version ≥ v5.6.3. - -[role="screenshot"] -image::apm/images/spans-dependencies.png[Span type duration and dependencies] -// end::dependencies[] - -[discrete] -[[service-cold-start]] -=== Cold start rate - -The cold start rate chart is specific to serverless services, and displays the -percentage of requests that trigger a cold start of a serverless function. -A cold start occurs when a serverless function has not been used for a certain period of time. -Analyzing the cold start rate can be useful for deciding how much memory to allocate to a function, -or when to remove a large dependency. - -The cold start rate chart is currently supported for <<apm-lambda-cold-start-info,AWS Lambda>> -functions and Azure functions. - -[discrete] -[[service-instances]] -=== Instances - -The *Instances* table displays a list of all the available service instances within the selected time range. -Depending on how the service runs, the instance could be a host or a container. The table displays latency, throughput, -failed transaction, CPU usage, and memory usage for each instance. By default, instances are sorted by _Throughput_. - -[role="screenshot"] -image::apm/images/all-instances.png[All instances] - -[discrete] -[[service-metadata]] -=== Service metadata - -To view metadata relating to the service agent, and if relevant, the container and cloud provider, -click on each icon located at the top of the page beside the service name. - -[role="screenshot"] -image::apm/images/metadata-icons.png[Service metadata] - -*Service information* - -* Service version -* Runtime name and version -* Framework name -* APM agent name and version - -*Container information* - -* Operating system -* Containerized - Yes or no. -* Total number of instances -* Orchestration - -*Cloud provider information* - -* Cloud provider -* Cloud service name -* Availability zones -* Machine types -* Project ID -* Region - -*Serverless information* - -* Function name(s) -* Event trigger type - -*Alerts* - -* Recently fired alerts diff --git a/docs/apm/services.asciidoc b/docs/apm/services.asciidoc deleted file mode 100644 index c4deeb7e40322..0000000000000 --- a/docs/apm/services.asciidoc +++ /dev/null @@ -1,61 +0,0 @@ -[role="xpack"] -[[services]] -=== Services - -*Service* inventory provides a quick, high-level overview of the health and general -performance of all instrumented services. - -To help surface potential issues, services are sorted by their health status: -**critical** > **warning** > **healthy** > **unknown**. -Health status is powered by <<machine-learning-integration,machine learning>> -and requires anomaly detection to be enabled. - -In addition to health status, active alerts for each service are prominently displayed in the service inventory table. Selecting an active alert badge brings you to the <<apm-alerts,Alerts>> tab where you can learn more about the active alert and take action. - -[role="screenshot"] -image::apm/images/apm-services-overview.png[Example view of services table the APM app in Kibana] - -[float] -[[service-groups]] -==== Service groups - -beta::[] - -Group services together to build meaningful views that remove noise, simplify investigations across services, -and <<apm-alert-view-group,combine related alerts>>. -Service groups are {kib} space-specific and available for any users with appropriate access. - -// This screenshot is reused in the alerts docs -// Ensure it has an active alert showing -[role="screenshot"] -image::apm/images/apm-service-group.png[Example view of service group in the APM app in Kibana] - -To enable Service groups, open {kib} and navigate to **Stack Management** > **Advanced Settings** > **Observability**, -and enable the **Service groups feature**. - -To create a service group: - -. Navigate to **Observability** > **APM** > **Services**. -. Switch to **Service groups**. -. Click **Create group**. -. Specify a name, color, and description. -. Click **Select services**. -. Specify a <<kuery-query, Kibana Query Language (KQL)>> query to select services for the group. -Services that match the query within the last 24 hours will be assigned to the group. - -[NOTE] -==== -Once a service group has been saved, this list of services within it is static. -If a newly added service matches the KQL query, it will not be automatically added to the service group. -Similarly, if a service stops matching the KQL query, it will not be removed from the group. - -To update the list of services within a group, -edit the service group, click **Refresh** next to the KQL query, and click **Save group**. -==== - -**Examples** - -Not sure where to get started? Here are some sample queries you can build from: - -* Group services by environment--in this example, "production": `service.environment : "production"` -* Group services by name--this example groups those that end in "beat": `service.name : *beat` (matches services named "Auditbeat", "Heartbeat", "Filebeat", etc.) diff --git a/docs/apm/set-up.asciidoc b/docs/apm/set-up.asciidoc deleted file mode 100644 index c049fdd49ba6c..0000000000000 --- a/docs/apm/set-up.asciidoc +++ /dev/null @@ -1,16 +0,0 @@ -[role="xpack"] -[[apm-ui]] -== Set up the APM app - -++++ -<titleabbrev>Set up</titleabbrev> -++++ - -APM is available via the navigation sidebar in {Kib}. -If you have not already installed and configured Elastic APM, -follow the steps on the *Add data* page to get started. - -[role="screenshot"] -image::apm/images/apm-setup.png[Installation instructions on the APM page in Kibana] - -That's it! You're now ready to explore your data. diff --git a/docs/apm/settings.asciidoc b/docs/apm/settings.asciidoc deleted file mode 100644 index c11ab5f70f6dd..0000000000000 --- a/docs/apm/settings.asciidoc +++ /dev/null @@ -1,34 +0,0 @@ -// Do not link directly to this page. -// Link to the anchor in `/docs/settings/apm-settings.asciidoc` instead. -[role="xpack"] -[[apm-settings-in-kibana]] -== APM app settings - -++++ -<titleabbrev>Settings</titleabbrev> -++++ - -You do not need to configure any settings to use the APM app. It is enabled by default. - -[float] -[[apm-indices-settings]] -=== APM Indices - -include::./../settings/apm-settings.asciidoc[tag=apm-indices-settings] - -[float] -[[general-apm-settings]] -=== General APM settings - -include::./../settings/apm-settings.asciidoc[tag=general-apm-settings] - -[float] -[[apm-labs]] -=== APM Labs - -**APM Labs** allows you to easily try out new features that are technical preview. - -To enable APM labs, navigate to **APM** > **Settings** > **General settings** and toggle **Enable labs button in APM**. -Select **Save changes** and refresh the page. - -After enabling **APM Labs** select **Labs** in the toolbar to see the technical preview features available to try out. diff --git a/docs/apm/spans.asciidoc b/docs/apm/spans.asciidoc deleted file mode 100644 index 40af80cb864a9..0000000000000 --- a/docs/apm/spans.asciidoc +++ /dev/null @@ -1,73 +0,0 @@ -[role="xpack"] -[[spans]] -=== Trace sample timeline - -The trace sample timeline visualization is a bird's-eye view of what your application was doing while it was trying to respond to a request. -This makes it useful for visualizing where a selected transaction spent most of its time. - -[role="screenshot"] -image::apm/images/apm-transaction-sample.png[Example of distributed trace colors in the APM app in Kibana] - -View a span in detail by clicking on it in the timeline waterfall. -For example, when you click on an SQL Select database query, -the information displayed includes the actual SQL that was executed, how long it took, -and the percentage of the trace's total time. -You also get a stack trace, which shows the SQL query in your code. -Finally, APM knows which files are your code and which are just modules or libraries that you've installed. -These library frames will be minimized by default in order to show you the most relevant stack trace. - -TIP: A {apm-guide-ref}/data-model-spans.html[span] is the duration of a single event. -Spans are automatically captured by APM agents, and you can also define custom spans. -Each span has a type and is defined by a different color in the timeline/waterfall visualization. - -[role="screenshot"] -image::apm/images/apm-span-detail.png[Example view of a span detail in the APM app in Kibana] - -[float] -[[trace-sample-investigate]] -==== Investigate - -The trace sample timeline features an **Investigate** button which provides a quick way to jump -to other areas of the Elastic Observability UI while maintaining the context of the currently selected trace sample. -For example, quickly view: - -* logs and metrics for the selected pod -* logs and metrics for the selected host -* trace logs for the selected `trace.id` -* uptime status of the selected domain -* the <<service-maps,service map>> filtered by the selected trace -* the selected transaction in <<discover,Discover>> -* your <<custom-links,custom links>> - -[float] -[[distributed-tracing]] -==== Distributed tracing - -When a trace travels through multiple services it is known as a _distributed trace_. -In APM, the colors in a distributed trace represent different services and -are listed in the order they occur. - -[role="screenshot"] -image::apm/images/apm-services-trace.png[Example of distributed trace colors in the APM app in Kibana] - -As application architectures are shifting from monolithic to more distributed, service-based architectures, -distributed tracing has become a crucial feature of modern application performance monitoring. -It allows you to trace requests through your service architecture automatically, and visualize those traces in one single view in the APM app. -From initial web requests to your front-end service, to queries made to your back-end services, -this makes finding possible bottlenecks throughout your application much easier and faster. - -[role="screenshot"] -image::apm/images/apm-distributed-tracing.png[Example view of the distributed tracing in APM app in Kibana] - -Don't forget; by definition, a distributed trace includes more than one transaction. -When viewing distributed traces in the timeline waterfall, -you'll see this icon: image:apm/images/transaction-icon.png[APM icon], -which indicates the next transaction in the trace. -For easier problem isolation, transactions can be collapsed in the waterfall by clicking -the icon to the left of the transactions. -Transactions can also be expanded and viewed in detail by clicking on them. - -After exploring these traces, -you can return to the full trace by clicking *View full trace*. - -TIP: Distributed tracing is supported by all APM agents, and there's no additional configuration needed. diff --git a/docs/apm/storage-explorer.asciidoc b/docs/apm/storage-explorer.asciidoc deleted file mode 100644 index 7ab7b0ec70c45..0000000000000 --- a/docs/apm/storage-explorer.asciidoc +++ /dev/null @@ -1,91 +0,0 @@ -[[storage-explorer]] -=== Storage Explorer - -Analyze your APM data and manage costs with **storage explorer**. -For example, analyze the storage footprint of each of your services to see which are producing -large amounts of data--then change the sample rate of a service to lower the amount of data ingested. -Or, expand the time filter to visualize data trends over time so that you can better forecast -and prepare for future storage needs. - -[role="screenshot"] -image::apm/images/storage-explorer-overview.png[APM Storage Explorer] - -[float] -==== Index lifecycle phases - -A default {apm-guide-ref}/ilm-how-to.html[index lifecycle policy] is applied to each APM data stream, -but can be customized depending on your business needs. -Use the **Index lifecycle phase** dropdown to visualize and analyze your storage by phase. - -Customizing the default APM index lifecycle policies can save money by specifying things like: - -* The point at which an index can be moved to less performant hardware. -* The point at which availability is not as critical and the number of replicas can be reduced. -* When the index can be safely deleted. - -See {apm-guide-ref}/ilm-how-to.html[Index lifecycle management] to learn more about customizing -the default APM index lifecycle policies. - -[float] -==== Service size chart - -The service size chart displays the estimated size of each service over time. -Expand the time filter to visualize data trends and estimate daily data generation. - -[float] -==== Service statistics table - -The service statistics table provides detailed information on each service: - -* A list of **service environments**. -* The **sampling rate**. This value is calculated by dividing the number of sampled transactions by total throughput. -It might differ from the configured sampling rate for two reasons: with head-based sampling, -the initial service makes the sampling decision, and with tail-based sampling, -granular policies allow you to set multiple sample rates. -* The estimated **size on disk**. This storage size includes both primary and replica shards and is -calculated by prorating the total size of your indices by the service's document count divided by -the total number of documents. -* Number of **transactions**, **spans**, **errors**, and **metrics** — doc count and size on disk. - -[role="screenshot"] -image::apm/images/storage-explorer-expanded.png[APM Storage Explorer service breakdown] - -As you explore your service statistics, you might want to take action to reduce the number of -documents and therefore storage size of a particular service. - -[float] -===== Reduce the number of transactions -To reduce the number of transactions a service generates, configure a more aggressive -{apm-guide-ref}/sampling.html[transaction sampling policy]. Transaction sampling lowers -the amount of data ingested without negatively impacting the usefulness of your data. - -[float] -===== Reduce the number of spans -To reduce the number of spans a service generates, enable -{apm-guide-ref}/span-compression.html[span compression]. Span compression saves on data -and transfer costs by compressing multiple, similar spans into a single span. - -[float] -===== Reduce the number of metrics -To reduce the number of system, runtime, and application metrics, -tune the APM agent or agents that are collecting the data. -You can disable the collection of specific metrics with the **disable metrics** configuration. -Or, you can set the **metrics interval** to zero seconds to deactivate metrics entirely. -Most APM agents support both options. -See the relevant {apm-agents-ref}[APM agent configuration options] for more details. - -[float] -===== Reduce the number of errors -To reduce the number of errors a service generate, -work with your developers to change how exceptions are handled in your code. - -[float] -==== Privileges - -Storage Explorer requires expanded privileges to view. -See <<apm-app-storage-explorer-user-create>> for more information. - -[float] -==== Limitations - -Multi-cluster deployments are not supported. diff --git a/docs/apm/tab-widgets/apm-app-reader/content.asciidoc b/docs/apm/tab-widgets/apm-app-reader/content.asciidoc deleted file mode 100644 index 6b9c996035f6c..0000000000000 --- a/docs/apm/tab-widgets/apm-app-reader/content.asciidoc +++ /dev/null @@ -1,45 +0,0 @@ -// tag::classic-indices[] -[options="header"] -|==== -|Type |Privilege |Purpose - -|Index -|`read` on `apm-*` -|Read-only access to `apm-*` data - -|Index -|`view_index_metadata` on `apm-*` -|Read-only access to `apm-*` index metadata -|==== -// end::classic-indices[] - -// tag::data-streams[] -[options="header"] -|==== -|Type |Privilege |Purpose - -|Index -|`read` on `logs-apm*` -|Read-only access to `logs-apm*` data - -|Index -|`view_index_metadata` on `logs-apm*` -|Read-only access to `logs-apm*` index metadata - -|Index -|`read` on `metrics-apm*` -|Read-only access to `metrics-apm*` data - -|Index -|`view_index_metadata` on `metrics-apm*` -|Read-only access to `metrics-apm*` index metadata - -|Index -|`read` on `traces-apm*` -|Read-only access to `traces-apm*` data - -|Index -|`view_index_metadata` on `traces-apm*` -|Read-only access to `traces-apm*` index metadata -|==== -// end::data-streams[] diff --git a/docs/apm/tab-widgets/apm-app-reader/widget.asciidoc b/docs/apm/tab-widgets/apm-app-reader/widget.asciidoc deleted file mode 100644 index a325b04f6cd86..0000000000000 --- a/docs/apm/tab-widgets/apm-app-reader/widget.asciidoc +++ /dev/null @@ -1,40 +0,0 @@ -++++ -<div class="tabs" data-tab-group="apm-app-reader"> - <div role="tablist" aria-label="APM app reader"> - <button role="tab" - aria-selected="true" - aria-controls="data-streams-tab" - id="data-streams"> - Data streams - </button> - <button role="tab" - aria-selected="false" - aria-controls="classic-indices-tab" - id="classic-indices" - tabindex="-1"> - Classic APM indices - </button> - </div> - <div tabindex="0" - role="tabpanel" - id="data-streams-tab" - aria-labelledby="data-streams"> -++++ - -include::content.asciidoc[tag=data-streams] - -++++ - </div> - <div tabindex="0" - role="tabpanel" - id="classic-indices-tab" - aria-labelledby="classic-indices" - hidden=""> -++++ - -include::content.asciidoc[tag=classic-indices] - -++++ - </div> -</div> -++++ \ No newline at end of file diff --git a/docs/apm/tab-widgets/central-config-users/content.asciidoc b/docs/apm/tab-widgets/central-config-users/content.asciidoc deleted file mode 100644 index 0945050d9a861..0000000000000 --- a/docs/apm/tab-widgets/central-config-users/content.asciidoc +++ /dev/null @@ -1,53 +0,0 @@ -// tag::classic-indices[] -[options="header"] -|==== -|Type |Privilege |Purpose - -|Index -|`read` on `apm-*` -|Read-only access to `apm-*` data - -|Index -|`view_index_metadata` on `apm-*` -|Read-only access to `apm-*` index metadata -|==== -// end::classic-indices[] - -// tag::data-streams[] -[options="header"] -|==== -|Type |Privilege |Purpose - -|Index -|`read` on `apm-agent-configuration` -|Read-only access to `apm-agent-configuration` data - -|Index -|`view_index_metadata` on `apm-agent-configuration` -|Read-only access to `apm-agent-configuration` index metadata - -|Index -|`read` on `logs-apm*` -|Read-only access to `logs-apm*` data - -|Index -|`view_index_metadata` on `logs-apm*` -|Read-only access to `logs-apm*` index metadata - -|Index -|`read` on `metrics-apm*` -|Read-only access to `metrics-apm*` data - -|Index -|`view_index_metadata` on `metrics-apm*` -|Read-only access to `metrics-apm*` index metadata - -|Index -|`read` on `traces-apm*` -|Read-only access to `traces-apm*` data - -|Index -|`view_index_metadata` on `traces-apm*` -|Read-only access to `traces-apm*` index metadata -|==== -// end::data-streams[] diff --git a/docs/apm/tab-widgets/central-config-users/widget.asciidoc b/docs/apm/tab-widgets/central-config-users/widget.asciidoc deleted file mode 100644 index 586a3515a718d..0000000000000 --- a/docs/apm/tab-widgets/central-config-users/widget.asciidoc +++ /dev/null @@ -1,40 +0,0 @@ -++++ -<div class="tabs" data-tab-group="central-config-manager"> - <div role="tablist" aria-label="Central config manager"> - <button role="tab" - aria-selected="true" - aria-controls="data-streams-tab" - id="data-streams"> - Data streams - </button> - <button role="tab" - aria-selected="false" - aria-controls="classic-indices-tab" - id="classic-indices" - tabindex="-1"> - Classic APM indices - </button> - </div> - <div tabindex="0" - role="tabpanel" - id="data-streams-tab" - aria-labelledby="data-streams"> -++++ - -include::content.asciidoc[tag=data-streams] - -++++ - </div> - <div tabindex="0" - role="tabpanel" - id="classic-indices-tab" - aria-labelledby="classic-indices" - hidden=""> -++++ - -include::content.asciidoc[tag=classic-indices] - -++++ - </div> -</div> -++++ \ No newline at end of file diff --git a/docs/apm/tab-widgets/storage-explorer-user/content.asciidoc b/docs/apm/tab-widgets/storage-explorer-user/content.asciidoc deleted file mode 100644 index 8661be8d3b6e4..0000000000000 --- a/docs/apm/tab-widgets/storage-explorer-user/content.asciidoc +++ /dev/null @@ -1,37 +0,0 @@ -// tag::classic-indices[] -[options="header"] -|==== -|Type |Privilege |Purpose - -|Cluster -|`monitor` -|Monitor the disk space used by APM indices - -|Index -|`monitor` on `apm-*` -|Monitor access to `apm-*` for storage explorer -|==== -// end::classic-indices[] - -// tag::data-streams[] -[options="header"] -|==== -|Type |Privilege |Purpose - -|Cluster -|`monitor` -|Monitor the disk space used by APM data streams - -|Index -|`monitor` on `logs-apm*` -|Monitor access to `logs-apm*` for storage explorer - -|Index -|`monitor` on `metrics-apm*` -|Monitor access to `metrics-apm*` for storage explorer - -|Index -|`monitor` on `traces-apm*` -|Monitor access to `traces-apm*` for storage explorer -|==== -// end::data-streams[] diff --git a/docs/apm/tab-widgets/storage-explorer-user/widget.asciidoc b/docs/apm/tab-widgets/storage-explorer-user/widget.asciidoc deleted file mode 100644 index a577f2e8b94ae..0000000000000 --- a/docs/apm/tab-widgets/storage-explorer-user/widget.asciidoc +++ /dev/null @@ -1,40 +0,0 @@ -++++ -<div class="tabs" data-tab-group="apm-app-storage-explorer-reader"> - <div role="tablist" aria-label="APM app storage explorer-reader"> - <button role="tab" - aria-selected="true" - aria-controls="data-streams-tab" - id="data-streams"> - Data streams - </button> - <button role="tab" - aria-selected="false" - aria-controls="classic-indices-tab" - id="classic-indices" - tabindex="-1"> - Classic APM indices - </button> - </div> - <div tabindex="0" - role="tabpanel" - id="data-streams-tab" - aria-labelledby="data-streams"> -++++ - -include::content.asciidoc[tag=data-streams] - -++++ - </div> - <div tabindex="0" - role="tabpanel" - id="classic-indices-tab" - aria-labelledby="classic-indices" - hidden=""> -++++ - -include::content.asciidoc[tag=classic-indices] - -++++ - </div> -</div> -++++ \ No newline at end of file diff --git a/docs/apm/traces.asciidoc b/docs/apm/traces.asciidoc deleted file mode 100644 index 4c912a03dcf64..0000000000000 --- a/docs/apm/traces.asciidoc +++ /dev/null @@ -1,40 +0,0 @@ -[role="xpack"] -[[traces]] -=== Traces - -TIP: Traces link together related transactions to show an end-to-end performance of how a request was served -and which services were part of it. -In addition to the Traces overview, you can view your application traces in the <<spans,trace sample timeline waterfall>>. - -*Traces* displays your application's entry (root) transactions. -Transactions with the same name are grouped together and only shown once in this table. -If you're using <<distributed-tracing,distributed tracing>>, -this view is key to finding the critical paths within your application. - -By default, transactions are sorted by _Impact_. -Impact helps show the most used and slowest endpoints in your service -- in other words, -it's the collective amount of pain a specific endpoint is causing your users. -If there's a particular endpoint you're worried about, select it to view its -<<transaction-details,transaction details>>. - -You can also use queries to filter and search the transactions shown on this page. -Note that only properties available on root transactions are searchable. -For example, you can't search for `label.tier: 'high'`, as that field is only available on non-root transactions. - -[role="screenshot"] -image::apm/images/apm-traces.png[Example view of the Traces overview in APM app in Kibana] - -[float] -[[trace-explorer]] -==== Trace explorer - -preview::[] - -**Trace explorer** is an experimental top-level search tool that allows you to query your traces using <<kuery-query,Kibana Query Language (KQL)>> or {ref}/eql.html[Event Query Language (EQL)]. - -Curate your own custom queries, or use the <<service-maps,**Service Map**>> to find and select edges to automatically generate queries based on your selection: - -[role="screenshot"] -image::apm/images/trace-explorer.png[Trace explorer] - -Enable **Trace explorer** in <<apm-labs,APM Labs>> or in <<observability-apm-trace-explorer-tab,{kib} advanced settings>>. diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc deleted file mode 100644 index 284d28c7b60c0..0000000000000 --- a/docs/apm/transactions.asciidoc +++ /dev/null @@ -1,177 +0,0 @@ -[role="xpack"] -[[transactions]] -=== Transactions - -TIP: A {apm-guide-ref}/data-model-transactions.html[transaction] describes an event captured by an Elastic APM agent instrumenting a service. -APM agents automatically collect performance metrics on HTTP requests, database queries, and much more. - -[role="screenshot"] -image::apm/images/apm-transactions-overview.png[Example view of transactions table in the APM app in Kibana] - -The *Latency*, *Throughput*, *Failed transaction rate*, *Time spent by span type*, and *Cold start rate* -charts display information on all transactions associated with the selected service: - -*Latency*:: -Response times for the service. Options include average, 95th, and 99th percentile. -If there's a weird spike that you'd like to investigate, -you can simply zoom in on the graph - this will adjust the specific time range, -and all of the data on the page will update accordingly. - -*Throughput*:: -Visualize response codes: `2xx`, `3xx`, `4xx`, etc. -Useful for determining if more responses than usual are being served with a particular response code. -Like in the latency graph, you can zoom in on anomalies to further investigate them. - -[[transaction-error-rate]] -*Failed transaction rate*:: -The failed transaction rate represents the percentage of failed transactions from the perspective of the selected service. -It's useful for visualizing unexpected increases, decreases, or irregular patterns in a service's transactions. -+ -[TIP] -==== -HTTP **transactions** from the HTTP server perspective do not consider a `4xx` status code (client error) as a failure -because the failure was caused by the caller, not the HTTP server. Thus, `event.outcome=success` and there will be no increase in failed transaction rate. - -HTTP **spans** from the client perspective however, are considered failures if the HTTP status code is ≥ 400. -These spans will set `event.outcome=failure` and increase the failed transaction rate. - -If there is no HTTP status, both transactions and spans are considered successful unless an error is reported. -==== - -*Time spent by span type*:: -Visualize where your application is spending most of its time. -For example, is your app spending time in external calls, database processing, or application code execution? -+ -The time a transaction took to complete is also recorded and displayed on the chart under the "app" label. -"app" indicates that something was happening within the application, but we're not sure exactly what. -This could be a sign that the APM agent does not have auto-instrumentation for whatever was happening during that time. -+ -It's important to note that if you have asynchronous spans, the sum of all span times may exceed the duration of the transaction. - -*Cold start rate*:: -Only applicable to serverless transactions, this chart displays the percentage of requests that trigger a cold start of a serverless function. -See <<apm-lambda-cold-start-info>> for more information. - -[discrete] -[[transactions-table]] -=== Transactions table - -The *Transactions* table displays a list of _transaction groups_ for the selected service. -In other words, this view groups all transactions of the same name together, -and only displays one entry for each group. - -[role="screenshot"] -image::apm/images/apm-transactions-table.png[Example view of the transactions table in the APM app in Kibana] - -By default, transaction groups are sorted by _Impact_. -Impact helps show the most used and slowest endpoints in your service - in other words, -it's the collective amount of pain a specific endpoint is causing your users. -If there's a particular endpoint you're worried about, you can click on it to view the <<transaction-details, transaction details>>. - -[IMPORTANT] -==== -If you only see one route in the Transactions table, or if you have transactions named "unknown route", -it could be a symptom that the APM agent either wasn't installed correctly or doesn't support your framework. - -For further details, including troubleshooting and custom implementation instructions, -refer to the documentation for each {apm-agents-ref}[APM Agent] you've implemented. -==== - -[discrete] -[[rum-transaction-overview]] -=== RUM Transaction overview - -The transaction overview page is customized for the JavaScript RUM agent. -Specifically, the page highlights *page load times* for your service: - -[role="screenshot"] -image::apm/images/apm-geo-ui.png[average page load duration distribution] - -Additional RUM goodies, like core vitals, and visitor breakdown by browser, location, and device, -are available in the Observability User Experience tab. -// To do -// Add link to the Observability UE docs when complete - -[discrete] -[[transaction-details]] -=== Transaction details - -Selecting a transaction group will bring you to the *transaction* details. -This page is visually similar to the transaction overview, but it shows data from all transactions within -the selected transaction group. - -[role="screenshot"] -image::apm/images/apm-transactions-overview.png[Example view of response time distribution] - -[[transaction-duration-distribution]] -==== Latency distribution - -The latency distribution shows a plot of all transaction durations for the given time period. -The following screenshot shows a typical distribution -and indicates most of our requests were served quickly -- awesome! -The requests on the right are taking longer than average; we probably need to focus on them. - -[role="screenshot"] -image::apm/images/apm-transaction-duration-dist.png[Example view of latency distribution graph] - -Click and drag to select a latency duration _bucket_ to display up to 500 trace samples. - -[[transaction-trace-sample]] -==== Trace samples - -Trace samples are based on the _bucket_ selection in the *Latency distribution* chart; -update the samples by selecting a new _bucket_. -The number of requests per bucket is displayed when hovering over the graph, -and the selected bucket is highlighted to stand out. - -Each bucket presents up to ten trace samples in a *timeline*, trace sample *metadata*, -and any related *logs*. - -*Trace sample timeline* - -Each sample has a trace timeline waterfall that shows how a typical request in that bucket executed. -This waterfall is useful for understanding the parent/child hierarchy of transactions and spans, -and ultimately determining _why_ a request was slow. -For large waterfalls, expand problematic transactions and collapse well-performing ones -for easier problem isolation and troubleshooting. - -[role="screenshot"] -image::apm/images/apm-transaction-sample.png[Example view of transactions sample] - -NOTE: More information on timeline waterfalls is available in <<spans, spans>>. - -*Trace sample metadata* - -Learn more about a trace sample in the *Metadata* tab: - -* Labels - Custom labels added by APM agents -* HTTP request/response information -* Host information -* Container information -* Service - The service/application runtime, APM agent, name, etc.. -* Process - The process id that served up the request. -* APM agent information -* URL -* User - Requires additional configuration, but allows you to see which user experienced the current transaction. -* FaaS information, like cold start, AWS request ID, trigger type, and trigger request ID - -TIP: All of this data is stored in documents in Elasticsearch. -This means you can select "Actions - View transaction in Discover" to see the actual Elasticsearch document under the discover tab. - -*Trace sample logs* - -The *Logs* tab displays logs related to the sampled trace. - -include::./logs.asciidoc[tag=log-overview] - -[role="screenshot"] -image::apm/images/apm-logs-tab.png[APM logs tab] - -[[transaction-latency-correlations]] -==== Correlations - -Correlations surface attributes of your data that are potentially correlated with high-latency or erroneous transactions. -To learn more, see <<correlations>>. - -[role="screenshot"] -image::apm/images/correlations-hover.png[APM lattency correlations] diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc deleted file mode 100644 index 93464d23518b6..0000000000000 --- a/docs/apm/troubleshooting.asciidoc +++ /dev/null @@ -1,191 +0,0 @@ -[[troubleshooting]] -== Troubleshooting - -++++ -<titleabbrev>Troubleshooting</titleabbrev> -++++ - -This section describes common problems you might encounter with the APM app. -To add to this page, please consider opening a -https://github.com/elastic/kibana/pulls[pull request] with your proposed changes. - -If your issue is potentially related to other components of the APM ecosystem, -don't forget to check our other troubleshooting guides or discussion forum: - -* {apm-guide-ref}/troubleshoot-apm.html[APM Server troubleshooting] -* {apm-dotnet-ref}/troubleshooting.html[.NET agent troubleshooting] -* {apm-go-ref}/troubleshooting.html[Go agent troubleshooting] -* {apm-ios-ref}/troubleshooting.html[iOS agent troubleshooting] -* {apm-java-ref}/trouble-shooting.html[Java agent troubleshooting] -* {apm-node-ref}/troubleshooting.html[Node.js agent troubleshooting] -* {apm-php-ref}/troubleshooting.html[PHP agent troubleshooting] -* {apm-py-ref}/troubleshooting.html[Python agent troubleshooting] -* {apm-ruby-ref}/debugging.html[Ruby agent troubleshooting] -* {apm-rum-ref}/troubleshooting.html[RUM troubleshooting] -* https://discuss.elastic.co/c/apm[APM discussion forum]. - -[discrete] -[[troubleshooting-apm-app]] -== Troubleshoot common APM app problems - -* <<no-apm-data-found>> -* <<troubleshooting-too-many-transactions>> -* <<troubleshooting-unknown-route>> -* <<troubleshooting-fields-unsearchable>> -* <<service-map-rum-connections>> - -[float] -[[no-apm-data-found]] -=== No APM data found - -This section can help with any of the following: - -* Data isn't displaying in the APM app -* Data isn't displaying in the APM app after an upgrade -* You see a message like "No Services Found", -* You see errors like "Fielddata is disabled on text fields by default..." - -These problems are likely to be caused by a missing index template or ingest pipeline. -By default, {fleet} sets up these and other APM assets when the APM integration is installed. -Try reinstalling the APM integration by navigating to -**Integrations** > **Elastic APM** > **Manage in Fleet** > **Settings** > **Reinstall Elastic APM**. - -Because assets cannot be applied to indices retroactively, -after reinstalling the APM integration you must either wait for the index to rollover or force a rollover. -To force a rollover, use the {ref}/indices-rollover-index.html[rollover API] to target the relevant {apm-guide-ref}/apm-data-streams.html[APM data streams]. - -[float] -[[troubleshooting-too-many-transactions]] -=== Too many unique transaction names - -Transaction names are defined in each APM agent; when an APM agent supports a framework, -it includes logic for naming the transactions that the framework creates. -In some cases though, like when using an APM agent's API to create custom transactions, -it is up to the user to define a pattern for transaction naming. -When transactions are named incorrectly, each unique URL can be associated with a unique transaction group—causing -an explosion in the number of transaction groups per service, and leading to inaccuracies in the APM app. - -To fix a large number of unique transaction names, -you need to change how you are using the APM agent API to name your transactions. -To do this, ensure you are **not** naming based on parameters that can change. -For example, user ids, product ids, order numbers, query parameters, etc., -should be stripped away, and commonality should be found between your unique URLs. - -Let's look at an example from the RUM agent documentation. Here are a few URLs you might find on Elastic.co: - -[source,yml] ----- -// Blog Posts -https://www.elastic.co/blog/reflections-on-three-years-in-the-elastic-public-sector -https://www.elastic.co/blog/say-heya-to-the-elastic-search-awards -https://www.elastic.co/blog/and-the-winner-of-the-elasticon-2018-training-subscription-drawing-is - -// Documentation -https://www.elastic.co/guide/en/elastic-stack/current/index.html -https://www.elastic.co/guide/en/apm/get-started/current/index.html -https://www.elastic.co/guide/en/infrastructure/guide/current/index.html ----- - -These URLs, like most, include unique names. -If we named transactions based on each unique URL, we'd end up with the problem described above—a -very large number of different transaction names. -Instead, we should strip away the unique information and group our transactions based on common information. -In this case, that means naming all blog transactions, `/blog`, and all documentation transactions, `/guide`. - -If you feel like you'd be losing valuable information by following this naming convention, don't fret! -You can always add additional metadata to your transactions using {apm-guide-ref}/data-model-metadata.html#data-model-labels[labels] (indexed) or -{apm-guide-ref}/data-model-metadata.html#data-model-custom[custom context] (non-indexed). - -After ensuring you've correctly named your transactions, -you might still see errors in the APM app related to transaction group limit reached: - -`The number of transaction groups has been reached. Current APM server capacity for handling unique transaction groups has been reached. There are at least X transactions missing in this list. Please decrease the number of transaction groups in your service or increase the memory allocated to APM server.` - -You will see this warning if an agent is creating too many transaction groups. This could indicate incorrect instrumentation which will have to be fixed in your application. Alternatively you can increase the memory of the APM server. - -`Number of transaction groups exceed the allowed maximum(1,000) that are displayed. The maximum number of transaction groups displayed in Kibana has been reached. Try narrowing down results by using the query bar..` - -You will see this warning if your results have more than `1000` unique transaction groups. Alternatively you can use the query bar to reduce the number of unique transaction groups in your results. - -**More information** - -While this can happen with any APM agent, it typically occurs with the RUM agent. -For more information on how to correctly set `transaction.name` in the RUM agent, -see {apm-rum-ref}/custom-transaction-name.html[custom initial page load transaction names]. - -The RUM agent can also set the `transaction.name` when observing for transaction events. -See {apm-rum-ref}/agent-api.html#observe[`apm.observe()`] for more information. - -If your problem is occurring in a different APM agent, the tips above still apply. -See the relevant {apm-agents-ref}[Agent API documentation] to adjust how you're naming your transactions. - -[float] -[[troubleshooting-unknown-route]] -=== Unknown route - -The {apm-app-ref}/transactions.html[transaction overview] will only display helpful information -when the transactions in your services are named correctly. -If you're seeing "GET unknown route" or "unknown route" in the APM app, -it could be a sign that something isn't working as it should. - -Elastic APM agents come with built-in support for popular frameworks out-of-the-box. -This means, among other things, that the APM agent will try to automatically name HTTP requests. -As an example, the Node.js agent uses the route that handled the request, while the Java agent uses the Servlet name. - -"Unknown route" indicates that the APM agent can't determine what to name the request, -perhaps because the technology you're using isn't supported, the agent has been installed incorrectly, -or because something is happening to the request that the agent doesn't understand. - -To resolve this, you'll need to head over to the relevant {apm-agents-ref}[APM agent documentation]. -Specifically, view the agent's supported technologies page. -You can also use the agent's public API to manually set a name for the transaction. - -[float] -[[troubleshooting-fields-unsearchable]] -=== Fields are not searchable - -In Elasticsearch, index templates are used to define settings and mappings that determine how fields should be analyzed. -The recommended index templates for APM are installed by {fleet} when the Elastic APM integration is installed. -These templates, by default, enable and disable indexing on certain fields. - -As an example, some APM agents store cookie values in `http.request.cookies`. -Since `http.request` has disabled dynamic indexing, and `http.request.cookies` is not declared in a custom mapping, -the values in `http.request.cookies` are not indexed and thus not searchable. - -*Ensure an APM data view exists* -As a first step, you should ensure the correct data view exists. -In {kib}, go to *Stack Management* > *Data views*. -You should see the APM data view--the default is -`traces-apm*,apm-*,logs-apm*,apm-*,metrics-apm*,apm-*`. -If you don't, the data view doesn't exist. -To fix this, navigate to the APM app in {kib} and select *Add data*. -In the APM tutorial, click *Load Kibana objects* to create the APM data view. - -If creating an APM data view doesn't solve the problem, -see <<no-apm-data-found>> for further troubleshooting. - -*Ensure a field is searchable* -There are two things you can do to if you'd like to ensure a field is searchable: - -1. Index your additional data as {apm-guide-ref}/data-model-metadata.html[labels] instead. -These are dynamic by default, which means they will be indexed and become searchable and aggregatable. - -2. Create a custom mapping for the field. -// link will be added in a later PR. -// docs will be added in https://github.com/elastic/apm-server/pull/6940 - -[float] -[[service-map-rum-connections]] -=== Service Maps: no connection between client and server - -If the service map is not showing an expected connection between the client and server, -it's likely because you haven't configured -{apm-rum-ref}/distributed-tracing-guide.html[`distributedTracingOrigins`]. - - -This setting is necessary, for example, for cross-origin requests. -If you have a basic web application that provides data via an API on `localhost:4000`, -and serves HTML from `localhost:4001`, you'd need to set `distributedTracingOrigins: ['https://localhost:4000']` -to ensure the origin is monitored as a part of distributed tracing. -In other words, `distributedTracingOrigins` is consulted prior to the APM agent adding the -distributed tracing `traceparent` header to each request. diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 00b2bd8b6a624..e51f77d096adf 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -470,10 +470,6 @@ The plugin exposes the static DefaultEditorController class to consume. |WARNING: Missing README. -|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/assets_data_access[assetsDataAccess] -|WARNING: Missing README. - - |{kib-repo}blob/{branch}/x-pack/plugins/banners/README.md[banners] |Allow to add a header banner that will be displayed on every page of the Kibana application @@ -570,6 +566,10 @@ security and spaces filtering. |This plugin provides Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search. +|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/entities_data_access/README.md[entitiesDataAccess] +|Exposes services to access entities data. + + |{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/entity_manager/README.md[entityManager] |This plugin provides access to observed asset data, such as information about hosts, pods, containers, services, and more. @@ -630,6 +630,11 @@ Index Management by running this series of requests in Console: |This service is exposed from the Index Management setup contract and can be used to add content to the indices list and the index details page. +|{kib-repo}blob/{branch}/x-pack/plugins/inference/README.md[inference] +|The inference plugin is a central place to handle all interactions with the Elasticsearch Inference API and +external LLM APIs. Its goals are: + + |{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/infra/README.md[infra] |This is the home of the infra plugin, which aims to provide a solution for the infrastructure monitoring use-case within Kibana. diff --git a/docs/discover/images/log-pattern-analysis-available-fields.png b/docs/discover/images/log-pattern-analysis-available-fields.png deleted file mode 100644 index 9ac267c58bbd3..0000000000000 Binary files a/docs/discover/images/log-pattern-analysis-available-fields.png and /dev/null differ diff --git a/docs/discover/images/log-pattern-analysis-results.png b/docs/discover/images/log-pattern-analysis-results.png index 8071c9bb9d05b..65d864ed18653 100644 Binary files a/docs/discover/images/log-pattern-analysis-results.png and b/docs/discover/images/log-pattern-analysis-results.png differ diff --git a/docs/discover/log-pattern-analysis.asciidoc b/docs/discover/log-pattern-analysis.asciidoc index 398add21cc38c..b4bd9fec29ec9 100644 --- a/docs/discover/log-pattern-analysis.asciidoc +++ b/docs/discover/log-pattern-analysis.asciidoc @@ -1,8 +1,6 @@ [[run-pattern-analysis-discover]] == Run a pattern analysis on your log data -preview::["This functionality is in technical preview, requires a link:https://www.elastic.co/subscriptions[Platinum subscription], and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."] - include::../user/ml/index.asciidoc[tag=log-pattern-analysis-intro] Log pattern analysis works on every text field. @@ -16,23 +14,23 @@ can use your own data. . If you don't see any results, expand the time range, for example, to *Last 15 days*. -. Click the `message` field in the **Available fields** list sidebar and click -**Run pattern analysis**. -+ --- -[role="screenshot"] -image::images/log-pattern-analysis-available-fields.png["Available fields view in Discover showing the message field selected."] - -The pattern analysis starts. The results are displayed in a flyout when the -analysis is complete. +. Click the *Patterns* tab next to *Documents* and *Field statistics*. The +pattern analysis starts. The results are displayed under the chart. You can +change the analyzed field by using the field selector. In the +*Pattern analysis menu*, you can change the *Minimum time range*. This option +enables you to widen the time range for calculating patterns which improves +accuracy. The patterns, however, are still displayed by the time range you +selected in step 3. [role="screenshot"] image::images/log-pattern-analysis-results.png["Log pattern analysis results in Discover."] --- + . (optional) Apply filters to one or more patterns. *Discover* only displays documents that match the selected patterns. Additionally, you can remove selected patterns from *Discover*, resulting in the display of only those documents that don't match the selected pattern. These options enable you to remove unimportant messages and focus on the more important, actionable data -during troubleshooting. \ No newline at end of file +during troubleshooting. You can also create a categorization {anomaly-job} +directly from the *Patterns* tab to find anomalous behavior in the selected +pattern. \ No newline at end of file diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 73789c750e015..4a88d33c83e5d 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -187,7 +187,7 @@ this setting stores part of the URL in your browser session to keep the URL short. [[theme-darkmode]]`theme:darkMode`:: -The UI theme that the {kib} UI should use. +The UI theme that the {kib} UI should use. Set to `enabled` or `disabled` to enable or disable the dark theme. Set to `system` to have the {kib} UI theme follow the system theme. You must refresh the page to apply the setting. @@ -418,7 +418,7 @@ beta:[] Enables the usage of service transaction metrics, which are low cardinal [[observability-apm-labs]]`observability:apmLabsButton`:: Enable or disable the APM Labs button -- a quick way to enable and disable technical preview features in APM. -See <<apm-labs>> to learn more. +// See <<apm-labs>> to learn more. [[observability-apm-critical-path]]`observability:apmEnableCriticalPath`:: When enabled, displays the critical path of a trace. @@ -453,7 +453,7 @@ preview:[] Display Amazon Lambda metrics in the service metrics tab. Shows the Uptime app even if there is no recent Heartbeat data. [[observability-apm-enable-comparison]]`observability:enableComparisonByDefault`:: -Determines whether the <<service-time-comparison, comparison feature>> is enabled or disabled by default in the APM app. +Determines whether the comparison feature is enabled or disabled by default in the APM app. [[observability-apm-enable-infra-view]]`observability:enableInfrastructureView`:: Enables the Infrastructure view in the APM app. diff --git a/docs/observability/index.asciidoc b/docs/observability/index.asciidoc index c924cea3712dd..78ca03296df8f 100644 --- a/docs/observability/index.asciidoc +++ b/docs/observability/index.asciidoc @@ -4,7 +4,7 @@ = Observability Observability enables you to add and monitor your logs, system -metrics, uptime data, and application traces, as a single stack. +metrics, uptime data, and application traces, as a single stack. With *Observability*, you have: @@ -44,8 +44,8 @@ image::observability/images/logs-app.png[Logs app in {kib}] The {metrics-app} in {kib} enables you to visualize infrastructure metrics to help diagnose problematic spikes, identify high resource utilization, -automatically discover and track pods, and unify your metrics -with logs and APM data in {es}. +automatically discover and track pods, and unify your metrics +with logs and APM data in {es}. To get started with the {metrics-app}, see {observability-guide}/ingest-metrics.html[Ingest metrics]. @@ -75,7 +75,7 @@ The APM app in {kib} enables you to monitors software services and applications collect unhandled errors and exceptions, and automatically pick up basic host-level metrics and agent specific metrics. -To get started with the APM app, see <<apm-ui,Set up the APM app>>. +// To get started with the APM app, see <<apm-ui,Set up the APM app>>. [role="screenshot"] image::observability/images/apm-app.png[APM app in {kib}] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 767037d39536d..06a2fc7886b58 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -111,10 +111,10 @@ This page was deleted. See <<development-visualize-index>>. deprecated::[7.9.0] -Watcher error reports have been removed and replaced with Kibana's <<apm-alerts,alerting and actions>> feature. -To create error alerts with new tool, select **Alerts** - **Create threshold alert** - **Error rate**. +// Watcher error reports have been removed and replaced with Kibana's <<apm-alerts,alerting and actions>> feature. +// To create error alerts with new tool, select **Alerts** - **Create threshold alert** - **Error rate**. -More information on this new feature is available in <<apm-alerts>>. +// More information on this new feature is available in <<apm-alerts>>. [role="exclude",id="development-security-rbac"] == Role-based access control @@ -437,4 +437,331 @@ For the most up-to-date API details, refer to the [role="exclude",id="add-case-connectors"] == Add connectors to cases -This content has moved. Refer to <<manage-cases-settings>>. \ No newline at end of file +This content has moved. Refer to <<manage-cases-settings>>. + +//// +APM redirects +//// + +:apm-docs-move-notice: This documentation has moved to the {observability-guide}/apm.html[Observability guide] as of version 8.14. + +[role="exclude",id="xpack-apm"] +== APM + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-ui.html[the APM UI documentation]. + +[role="exclude",id="apm-ui"] +== Set up + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-ui.html[the APM UI documentation]. + +[role="exclude",id="apm-getting-started"] +== Get started + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm.html[the APM UI documentation]. + +[role="exclude",id="services"] +== Services + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-services.html[Services]. + +[role="exclude",id="traces"] +== Traces + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-traces.html[Traces]. + +[role="exclude",id="dependencies"] +== Dependencies + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-dependencies.html[Dependencies]. + +[role="exclude",id="service-maps"] +== Service Map + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-service-maps.html[Service Map]. + +[float] +[[service-maps-supported]] +=== Supported APM agents + +Refer to {observability-guide}/apm-service-maps.html#service-maps-supported[Supported APM agents]. + +[role="exclude",id="service-overview"] +== Service overview + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-service-overview.html[Service overview]. + +[float] +[[service-span-duration]] +=== Span types average duration and dependencies + +Refer to {observability-guide}/apm-service-overview.html#service-span-duration[Service overview]. + +[role="exclude",id="mobile-service-overview"] +== Mobile service overview + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-mobile-service-overview.html[Mobile service overview]. + +[role="exclude",id="transactions"] +== Transactions + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-transactions.html[Transactions]. + +[role="exclude",id="spans"] +== Trace sample timeline + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-spans.html[Trace sample timeline]. + +[role="exclude",id="errors"] +== Errors + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-errors.html[Errors]. + +[role="exclude",id="metrics"] +== Metrics + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-metrics.html[Metrics]. + +[role="exclude",id="infrastructure"] +== Infrastructure + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-infrastructure.html[Infrastructure]. + +[role="exclude",id="logs"] +== Logs + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-logs.html[Logs]. + +[role="exclude",id="apm-how-to"] +== How-to guides + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-how-to-guides.html[How-to guides]. + +[role="exclude",id="agent-configuration"] +== Configure APM agents with central config + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-agent-configuration.html[Configure APM agents with central config]. + +[role="exclude",id="apm-spaces"] +== Control access to APM data + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-spaces.html[Control access to APM data]. + +[role="exclude",id="apm-alerts"] +== Create an alert + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-alerts.html[Create an alert]. + +[role="exclude",id="custom-links"] +== Create custom links + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-custom-links.html[Create custom links]. + +[role="exclude",id="filters"] +== Filter data + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-filters.html[Filter data]. + +[float] +[[environment-selector]] +=== Service environment filter + +Refer to {observability-guide}/apm-filters.html#environment-selector[Filter data]. + +[role="exclude",id="correlations"] +== Find transaction latency and failure correlations + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-correlations.html[Find transaction latency and failure correlations]. + +[role="exclude",id="agent-explorer"] +== Identify deployment details for APM agents + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-agent-explorer.html[Identify deployment details for APM agents]. + +[role="exclude",id="machine-learning-integration"] +== Integrate with machine learning + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-machine-learning-integration.html[Integrate with machine learning]. + +[role="exclude",id="mobile-session-explorer"] +== Exploring mobile sessions with Discover + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-mobile-session-explorer.html[Exploring mobile sessions with Discover]. + +[role="exclude",id="_viewing_sessions_with_discover"] +== Viewing sessions with Discover + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-mobile-session-explorer.html#viewing-sessions-with-discover[Viewing sessions with Discover]. + +[role="exclude",id="apm-lambda"] +== Observe Lambda functions + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-lambda.html[Observe Lambda functions]. + +[role="exclude",id="advanced-queries"] +== Query your data + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-advanced-queries.html[Query your data]. + +[role="exclude",id="storage-explorer"] +== Storage Explorer + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-storage-explorer.html[Storage Explorer]. + +[role="exclude",id="transactions-annotations"] +== Track deployments with annotations + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-transactions-annotations.html[Track deployments with annotations]. + +[role="exclude",id="apm-app-users"] +== Users and privileges + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-app-users.html[Users and privileges]. + +[role="exclude",id="apm-app-reader"] +== Create an APM reader user + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-app-reader.html[Create an APM reader user]. + +[role="exclude",id="apm-app-annotation-user-create"] +== Create an annotation user + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-app-annotation-user-create.html[Create an annotation user]. + +[role="exclude",id="apm-app-central-config-user"] +== Create a central config user + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-app-central-config-user.html[Create a central config user]. + +[role="exclude",id="apm-app-storage-explorer-user-create"] +== Create a storage explorer user + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-app-storage-explorer-user-create.html[Create a storage explorer user]. + +[role="exclude",id="apm-app-api-user"] +== Create an API user + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-app-api-user.html[Create an API user]. + +[role="exclude",id="apm-settings-in-kibana"] +== Settings + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-settings-in-kibana.html[Settings]. + +[role="exclude",id="apm-api"] +== REST API + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-api.html[REST API]. + +[role="exclude",id="agent-config-api"] +== Agent Configuration API + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-agent-config-api.html[Agent Configuration API]. + +[role="exclude",id="apm-annotation-api"] +== Annotation API + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-annotation-api.html[Annotation API]. + +[role="exclude",id="rum-sourcemap-api"] +== RUM source map API + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-rum-sourcemap-api.html[RUM source map API]. + +[role="exclude",id="agent-key-api"] +== APM agent Key API + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-agent-key-api.html[APM agent Key API]. + +[role="exclude",id="troubleshooting"] +== Troubleshooting + +{apm-docs-move-notice} + +Refer to {observability-guide}/apm-app-troubleshooting.html[Troubleshooting]. + +:!apm-docs-move-notice: diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 05d8cc35f1cde..3cd04a4ff3733 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -24,7 +24,7 @@ Index settings in the APM app take precedence over those set in `kibana.yml`. Starting in version 8.2.0, APM indices are {kib} Spaces-aware; Changes to APM index settings will only apply to the currently enabled space. -See <<apm-spaces>> for more information. +// See <<apm-spaces>> for more information. [role="screenshot"] image::settings/images/apm-settings.png[APM app settings in Kibana] diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index 87ee0b9bac099..49aa22de9fd35 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -12,17 +12,16 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: -* <<general-reporting-settings,Enable the {report-features}>> -* <<encryption-keys,Configure the encryption key>> -* <<reporting-job-queue-settings,Manage background jobs>> -* <<reporting-capture-settings,Capture screenshots>> -* <<reporting-network-policy,Restrict requests with a Reporting network policy>> -* <<reporting-csv-settings,Increase the byte limit for CSV exports>> -* <<reporting-kibana-server-settings,Control how the {report-features} communicate with the {kib} server>> +* <<general-reporting-settings,Enable or disable the {report-features}>> +* <<encryption-keys,Configure an encryption key to protect sensitive authentication data>> +* <<reporting-advanced-settings,Choose an access control model of how users will be granted privileges to {report-features}>> +* <<reporting-job-queue-settings,Manage the way reporting tasks run in the {kib} server background>> +* <<reporting-capture-settings,Control how screenshots are captured for PNG/PDF reports>> +* <<reporting-csv-settings,Control the limits and capabilities of CSV reports>> [float] [[general-reporting-settings]] -==== Enable reporting +=== Enable reporting [[xpack-enable-reporting]]`xpack.reporting.enabled` {ess-icon}:: When `true`, enables the {report-features}. Set this to `false` to disable {report-features} entirely. The default is `true`. @@ -38,7 +37,7 @@ If needed, you can also prevent a {kib} instance from claiming reporting work by [float] [[encryption-keys]] -==== Encryption key setting +=== Encryption key setting By default, an encryption key is generated for the {report-features} each time you start {kib}. If a static encryption key is not persisted in @@ -58,13 +57,41 @@ The static encryption key for reporting. Use an alphanumeric text string that is xpack.reporting.encryptionKey: "something_secret" -------------------------------------------------------------------------------- +[float] +[[reporting-advanced-settings]] +=== Security settings + +Reporting has two forms of access control: each user can only access their own reports, +and custom roles determine who has the privilege to generate reports. When Reporting is configured with +<<kibana-privileges, {kib} application privileges>>, you can control the spaces and applications where users +are allowed to generate reports. + +[NOTE] +============================================================================ +The `xpack.reporting.roles` settings are for a deprecated system of access control in Reporting. Turning off +this feature allows API keys to generate reports, and allows reporting access through {kib} application +privileges. We recommend that you explicitly turn off reporting's deprecated access control feature by adding +`xpack.reporting.roles.enabled: false` to kibana.yml. This will enable you to create custom roles that provide +application privileges for reporting, as described in <<grant-user-access, granting users access to +reporting>>. +============================================================================ + +[[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled`:: +deprecated:[7.14.0,The default for this setting will be `false` in an upcoming version of {kib}.] Sets access +control to a set of assigned reporting roles, specified by `xpack.reporting.roles.allow`. Defaults to `true`. + +`xpack.reporting.roles.allow`:: +deprecated:[7.14.0] In addition to superusers, specifies the roles that can generate reports using the +{ref}/security-api.html#security-role-apis[{es} role management APIs]. Requires `xpack.reporting.roles.enabled` +to be `true`. Defaults to `[ "reporting_user" ]`. + [float] [[reporting-job-queue-settings]] -==== Background job settings +=== Background job settings -Reporting generates reports in the background and jobs are coordinated using documents -in {es}. Depending on how often you generate reports and the overall number of -reports, you might need to change the following settings. +Reporting generates reports on the {kib} server as background tasks, and jobs are coordinated using documents +in {es}. Depending on how often you generate reports and the overall number of reports, you might need to +change the following settings. `xpack.reporting.capture.maxAttempts` {ess-icon}:: If capturing a report fails for any reason, {kib} will re-queue the report job for retry, as many times as this setting. Defaults to `3`. @@ -85,18 +112,25 @@ reporting requires identical values for <<xpack-reporting-encryptionKey, `xpack. security is enabled, <<xpack-security-encryptionKey, `xpack.security.encryptionKey`>>. `xpack.reporting.queue.pollInterval`:: -Specifies the {time-units}[time] that the reporting poller waits between polling the index for any pending Reporting jobs. Can be specified as number of milliseconds. Defaults to `3s`. +Specifies the {time-units}[time] that the reporting poller waits between polling the index for any pending Reporting jobs. Can be specified as a number of milliseconds. Defaults to `3s`. [[xpack-reporting-q-timeout]] `xpack.reporting.queue.timeout` {ess-icon}:: {time-units}[How long] each worker has to produce a report. If your machine is slow or under heavy load, you might need to increase this timeout. If a Reporting job execution goes over this time limit, the job is marked -as a failure and no download will be available. Can be specified as number of milliseconds. Defaults to `4m`. +as a failure and no download will be available. Can be specified as a number of milliseconds. Defaults to `4m`. [float] [[reporting-capture-settings]] -==== Capture settings +=== PNG/PDF settings -Reporting uses an internal "screenshotting" plugin to capture screenshots from {kib}. The following settings control the capturing process. +[NOTE] +============ +include::../user/reporting/reporting-pdf-limitations.asciidoc[] +============ + +To generate PDF and PNG files, Reporting uses an internal "screenshotting" plugin which manages a headless browser that captures screenshots from {kib}. + +The following settings control the capturing process. `xpack.screenshotting.capture.timeouts.openUrl` {ess-icon}:: Specify the {time-units}[time] to allow the Reporting browser to wait for the "Loading..." screen to dismiss @@ -123,7 +157,7 @@ deprecated:[8.0.0,This setting has no effect.] Specify the {time-units}[amount o [float] [[reporting-chromium-settings]] -==== Chromium settings +==== Chromium headless browser settings For PDF and PNG reports, Reporting spawns a headless Chromium browser process on the server to load and capture a screenshot of the {kib} app. When installing {kib} on Linux and Windows platforms, the Chromium binary comes bundled with the {kib} download. For Mac platforms, the Chromium binary is downloaded the first time {kib} is started. @@ -139,13 +173,36 @@ The uri for the proxy server. Providing the username and password for the proxy `xpack.screenshotting.browser.chromium.proxy.bypass`:: An array of hosts that should not go through the proxy server and should use a direct connection instead. Examples of valid entries are "elastic.co", "*.elastic.co", ".elastic.co", ".elastic.co:5601". +[float] +[[reporting-kibana-server-settings]] +==== {kib} server settings for headless browser connection + +To generate screenshots for PNG and PDF reports, Reporting opens the {kib} web interface using a local +connection to the server. In most cases, using a local connection to the {kib} server presents no issue. If +you prefer the headless browser to connect to {kib} using a specific hostname, there are a number of +settings that allow the headless browser to connect to {kib} through a proxy, rather than directly. + +[NOTE] +============ +The `xpack.reporting.kibanaServer` settings are optional. Take caution when editing these settings. Adding +these settings can cause the PDF/PNG {report-features} to fail. If reports fail, inspect the server logs and +pay attention to errors regarding the headless browser being unable to connect to the server. The full {kib} +URL that Reporting is attempting to open is logged during report execution. +============ + +`xpack.reporting.kibanaServer.port`:: The port for accessing {kib}. + +`xpack.reporting.kibanaServer.protocol`:: The protocol for accessing {kib}, typically `http` or `https`. + +[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname`:: The hostname for accessing {kib}. + [float] [[reporting-network-policy]] -=== Network policy settings +==== Network policy settings for headless Chromium restrictions -To generate PDF reports, *Reporting* uses the Chromium browser to fully load the {kib} page on the server. This potentially involves sending requests to external hosts. For example, a request might go to an external image server to show a field formatted as an image, or to show an image in a Markdown visualization. +To generate PDF reports, Reporting uses a headless Chromium browser to fully load the {kib} page on the server. This potentially involves sending requests to external hosts. For example, a request might go to an external image server to show a field formatted as an image, or to show an image in a Markdown visualization. -If the Chromium browser is asked to send a request that violates the network policy, *Reporting* stops processing the page before the request goes out, and the report is marked as a failure. Additional information about the event is in the {kib} server logs. +If the headless Chromium browser is asked to send a request that violates the network policy, it will stop processing the page before the request goes out, and the report is marked as a failure. Additional information about the event is in the {kib} server logs. NOTE: {kib} installations are not designed to be publicly accessible over the internet. The Reporting network policy and other capabilities of the Elastic Stack security features do not change this condition. @@ -153,7 +210,7 @@ NOTE: {kib} installations are not designed to be publicly accessible over the in Capturing a screenshot from a {kib} page involves sending out requests for all the linked web assets. For example, a Markdown visualization can show an image from a remote server. `xpack.screenshotting.networkPolicy.enabled`:: -When `false`, disables the *Reporting* network policy. Defaults to `true`. +When `false`, disables the headless browser network policy. Defaults to `true`. `xpack.screenshotting.networkPolicy.rules`:: A policy is specified as an array of objects that describe what to allow or deny based on a host or protocol. If a host or protocol is not specified, the rule matches any host or protocol. @@ -202,30 +259,17 @@ The `file:` protocol is always denied, even if no network policy is configured. [float] [[reporting-csv-settings]] -==== CSV settings - -[[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` {ess-icon}:: -The maximum {byte-units}[byte size] of a CSV file before being truncated. This setting exists to prevent large -exports from causing performance and storage issues. Can be specified as number of bytes. Defaults to `250mb`. +=== CSV settings [NOTE] ============ -We recommend using CSV reports to export moderate amounts of data only. The feature enables analysis of data in -external tools, but it's not intended for bulk export or to backup {es} data. If you need to export more than -250 MB of CSV, rather than increasing `xpack.reporting.csv.maxSizeBytes`, please use filters to create multiple -smaller reports, or extract the data you need directly from {es}. - -The following deployment configurations may lead to failed report jobs or incomplete reports: - -* Any shard needed for search is unavailable. -* Data is stored on slow storage tiers. -* Network latency between nodes is high. -* {ccs-cap} is used. - -To export large amounts of data we recommend using {es} APIs directly. See {ref}/point-in-time-api.html[Point -in time API], or {ref}/sql-rest-format.html#_csv[SQL with CSV response data format]. +include::../user/reporting/reporting-csv-limitations.asciidoc[] ============ +[[xpack-reporting-csv]] `xpack.reporting.csv.maxSizeBytes` {ess-icon}:: +The maximum {byte-units}[byte size] of a CSV file before being truncated. This setting exists to prevent large +exports from causing performance and storage issues. Can be specified as a number of bytes. Defaults to `250mb`. + `xpack.reporting.csv.scroll.size`:: Number of documents retrieved from {es} for each scroll iteration during a CSV export. Defaults to `500`. [NOTE] @@ -235,13 +279,15 @@ You may need to lower this setting if the default number of documents creates a `xpack.reporting.csv.scroll.duration`:: Amount of {time-units}[time] allowed before {kib} cleans the scroll context during a CSV export. Valid option is either `auto` or {time-units}[time], Defaults to `30s`. + [NOTE] ============ -If search latency in {es} is sufficiently high, such as if you are using {ccs}, you may either need to increase the time setting or set this config value to `auto`. When the config value is set to `auto` the scroll context will be preserved for as long as is possible, before the report task is terminated due to the limits of `xpack.reporting.queue.timeout`. +If search latency in {es} is sufficiently high, such as if you are using {ccs}, you may either need to increase the time setting or set this config value to `auto`. When the config value is set to `auto` the scroll context will be preserved for as long as possible, before the report task is terminated due to the limits of `xpack.reporting.queue.timeout`. ============ `xpack.reporting.csv.scroll.strategy`:: Choose the API method used to page through data during CSV export. Valid options are `scroll` and `pit`. Defaults to `pit`. + [NOTE] ============ Each method has its own unique limitations which are important to understand. @@ -264,43 +310,3 @@ dashboard and later download them in *Stack Management > Reporting*. Defaults to `xpack.reporting.csv.useByteOrderMarkEncoding`:: Adds a byte order mark (`\ufeff`) at the beginning of the CSV file. Defaults to `false`. - -[float] -[[reporting-advanced-settings]] -==== Security settings - -With Security enabled, Reporting has two forms of access control: each user can only access their own reports, and custom roles determine who has privilege to generate reports. When Reporting is configured with <<kibana-privileges, {kib} application privileges>>, you can control the spaces and applications where users are allowed to generate reports. - -[NOTE] -============================================================================ -The `xpack.reporting.roles` settings are for a deprecated system of access control in Reporting. Turning off this feature allows API Keys to generate reports, and allows reporting access through {kib} application privileges. We recommend you explicitly turn off reporting's deprecated access control feature by adding `xpack.reporting.roles.enabled: false` in kibana.yml. This will enable you to create custom roles that provide application privileges for reporting, as described in <<grant-user-access, granting users access to reporting>>. -============================================================================ - -[[xpack-reporting-roles-enabled]] `xpack.reporting.roles.enabled`:: -deprecated:[7.14.0,The default for this setting will be `false` in an upcoming version of {kib}.] Sets access control to a set of assigned reporting roles, specified by `xpack.reporting.roles.allow`. Defaults to `true`. - -`xpack.reporting.roles.allow`:: -deprecated:[7.14.0] In addition to superusers, specifies the roles that can generate reports using the {ref}/security-api.html#security-role-apis[{es} role management APIs]. Requires `xpack.reporting.roles.enabled` to be `true`. Defaults to `[ "reporting_user" ]`. - -[float] -[[reporting-kibana-server-settings]] -==== {kib} server settings - -To generate screenshots for PNG and PDF reports, Reporting opens the {kib} web interface using a local -connection on the server. In most cases, using a local connection to the {kib} server presents no issue. If -you prefer the headless browser to connect to {kib} using a specific hostname, there are a number of -settings that allow the headless browser to connect to {kib} through a proxy, rather than directly. - -[NOTE] -============ -The `xpack.reporting.kibanaServer` settings are optional. Take caution when editing these settings. Adding -these settings can cause the {report-features} to fail. If report fail, -inspect the server logs. The full {kib} URL that Reporting is attempting to - open is logged during report execution. -============ - -`xpack.reporting.kibanaServer.port`:: The port for accessing {kib}.port`>> value. - -`xpack.reporting.kibanaServer.protocol`:: The protocol for accessing {kib}, typically `http` or `https`. - -[[xpack-kibanaServer-hostname]] `xpack.reporting.kibanaServer.hostname`:: The hostname for accessing {kib}. diff --git a/docs/siem/index.asciidoc b/docs/siem/index.asciidoc index 6afddb1dd5fbe..06d9097dcc5dc 100644 --- a/docs/siem/index.asciidoc +++ b/docs/siem/index.asciidoc @@ -6,159 +6,5 @@ <titleabbrev>Security</titleabbrev> ++++ -https://www.elastic.co/security[Elastic Security] combines SIEM threat detection features with endpoint -prevention and response capabilities in one solution. These analytical and -protection capabilities, leveraged by the speed and extensibility of -Elasticsearch, enable analysts to defend their organization from threats before -damage and loss occur. +Elastic Security combines threat detection analytics, cloud native security, and endpoint protection capabilities in a single solution, so you can quickly detect, investigate, and respond to threats and vulnerabilities across your environment. To learn more about how {elastic-sec} works and what it offers, refer to the {security-guide}/es-overview.html[documentation]. -Elastic Security provides the following security benefits and capabilities: - -* A detection engine to identify attacks and system misconfigurations -* A workspace for event triage and investigations -* Interactive visualizations to investigate process relationships -* Inbuilt case management with automated actions -* Detection of signatureless attacks with prebuilt machine learning anomaly jobs -and detection rules - -[discrete] -== Elastic Security components and workflow - -The following diagram provides a comprehensive illustration of the Elastic Security workflow. - -[role="screenshot"] -image::images/workflow.png[] - -Here's an overview of the flow and its components: - -* Data is shipped from your hosts to {es} via beat modules and the Elastic https://www.elastic.co/endpoint-security/[Endpoint Security agent integration]. This integration provides capabilities such as collecting events, detecting and preventing {security-guide}/detection-engine-overview.html#malware-prevention[malicious activity], and artifact delivery. The {fleet-guide}/fleet-overview.html[{fleet}] app is used to -install and manage agents and integrations on your hosts. -+ -The Endpoint Security integration ships the following data sets: -+ -*** *Windows*: Process, network, file, DNS, registry, DLL and driver loads, -malware security detections -*** *Linux/macOS*: Process, network, file -+ -* https://www.elastic.co/integrations?solution=security[Beat modules]: {beats} -are lightweight data shippers. Beat modules provide a way of collecting and -parsing specific data sets from common sources, such as cloud and OS events, -logs, and metrics. Common security-related modules are listed {security-guide}/ingest-data.html#enable-beat-modules[here]. -* The {security-app} in {kib} is used to manage the *Detection engine*, -*Cases*, and *Timeline*, as well as administer hosts running Endpoint Security: -** Detection engine: Automatically searches for suspicious host and network -activity via the following: -*** {security-guide}/detection-engine-overview.html#detection-engine-overview[Detection rules]: Periodically search the data -({es} indices) sent from your hosts for suspicious events. When a suspicious -event is discovered, a detection alert is generated. External systems, such as -Slack and email, can be used to send notifications when alerts are generated. -You can create your own rules and make use of our {security-guide}/prebuilt-rules.html[prebuilt ones]. -*** {security-guide}/detections-ui-exceptions.html[Exceptions]: Reduce noise and the number of -false positives. Exceptions are associated with rules and prevent alerts when -an exception's conditions are met. *Value lists* contain source event -values that can be used as part of an exception's conditions. When -Elastic {endpoint-sec} is installed on your hosts, you can add malware exceptions -directly to the endpoint from the Security app. -*** {security-guide}/machine-learning.html#included-jobs[{ml-cap} jobs]: Automatic anomaly detection of host and -network events. Anomaly scores are provided per host and can be used with -detection rules. -** {security-guide}/timelines-ui.html[Timeline]: Workspace for investigating alerts and events. -Timelines use queries and filters to drill down into events related to -a specific incident. Timeline templates are attached to rules and use predefined -queries when alerts are investigated. Timelines can be saved and shared with -others, as well as attached to Cases. -** {security-guide}/cases-overview.html[Cases]: An internal system for opening, tracking, and sharing -security issues directly in the Security app. Cases can be integrated with -external ticketing systems. -** {security-guide}/admin-page-ov.html[Administration]: View and manage hosts running {endpoint-sec}. - -{security-guide}/ingest-data.html[Ingest data to Elastic Security] and {security-guide}/install-endpoint.html[Configure and install the Elastic Endpoint integration] describe how to ship security-related -data to {es}. - - -For more background information, see: - -* https://www.elastic.co/products/elasticsearch[{es}]: A real-time, -distributed storage, search, and analytics engine. {es} excels at indexing -streams of semi-structured data, such as logs or metrics. -* https://www.elastic.co/products/kibana[{kib}]: An open-source analytics and -visualization platform designed to work with {es}. You use {kib} to search, -view, and interact with data stored in {es} indices. You can easily compile -advanced data analysis and visualize your data in a variety of charts, tables, -and maps. - -[discrete] -=== Compatibility with cold tier nodes - -Cold tier is a {ref}/data-tiers.html[data tier] that holds time-series data that is accessed only occasionally. In {stack} version >=7.11.0, {elastic-sec} supports cold tier data for the following {es} indices: - -* Index patterns specified in `securitySolution:defaultIndex` -* Index patterns specified in the definitions of detection rules, except for indicator match rules -* Index patterns specified in the data sources selector on various {security-app} pages - -{elastic-sec} does NOT support cold tier data for the following {es} indices: - -* Index patterns controlled by {elastic-sec}, including signals and list indices -* Index patterns specified in indicator match rules - -Using cold tier data for unsupported indices may result in detection rule timeouts and overall performance degradation. - -[discrete] -[[self-protection]] -==== Elastic Endpoint self-protection - -Self-protection means that {elastic-endpoint} has guards against users and attackers that may try to interfere with its functionality. This protection feature is consistently enhanced to prevent attackers who may attempt to use newer, more sophisticated tactics to interfere with the {elastic-endpoint}. Self-protection is enabled by default when {elastic-endpoint} installs on supported platforms, listed below. - -Self-protection is enabled on the following 64-bit Windows versions: - -* Windows 8.1 -* Windows 10 -* Windows Server 2012 R2 -* Windows Server 2016 -* Windows Server 2019 - -And on the following macOS versions: - -* macOS 10.15 (Catalina) -* macOS 11 (Big Sur) - -NOTE: Other Windows and macOS variants (and all Linux distributions) do not have self-protection. - -For {stack} version >= 7.11.0, self-protection defines the following permissions: - -* Users -- even Administrator/root -- *cannot* delete {elastic-endpoint} files (located at `c:\Program Files\Elastic\Endpoint` on Windows, and `/Library/Elastic/Endpoint` on macOS). -* Users *cannot* terminate the {elastic-endpoint} program or service. -* Administrator/root users *can* read the endpoint's files. On Windows, the easiest way to read Endpoint files is to start an Administrator `cmd.exe` prompt. On macOS, an Administrator can use the `sudo` command. -* Administrator/root users *can* stop the {elastic-agent}'s service. On Windows, run the `sc stop "Elastic Agent"` command. On macOS, run the `sudo launchctl stop elastic-agent` command. - - -[discrete] -[[siem-integration]] -=== Integration with other Elastic products - -You can use {elastic-sec} with other Elastic products and features to help you -identify and investigate suspicious activity: - -* https://www.elastic.co/products/stack/machine-learning[{ml-cap}] -* https://www.elastic.co/products/stack/alerting[Alerting] -* https://www.elastic.co/products/stack/canvas[Canvas] - -[discrete] -[[data-sources]] -=== APM transaction data sources - -By default, {elastic-sec} monitors {apm-app-ref}/apm-getting-started.html[APM] -`apm-*-transaction*` indices. To add additional APM indices, update the -index patterns in the `securitySolution:defaultIndex` setting ({kib} -> Stack Management -> Advanced Settings -> `securitySolution:defaultIndex`). - -[discrete] -[[ecs-compliant-reqs]] -=== ECS compliance data requirements - -The {ecs-ref}[Elastic Common Schema (ECS)] defines a common set of fields used for -storing event data in Elasticsearch. ECS helps users normalize their event data -to better analyze, visualize, and correlate the data represented in their -events. {elastic-sec} supports events and indicator index data from any ECS-compliant data source. - -IMPORTANT: {elastic-sec} requires {ecs-ref}[ECS-compliant data]. If you use third-party data collectors to ship data to {es}, the data must be mapped to ECS. -{security-guide}/siem-field-reference.html[Elastic Security ECS field reference] lists ECS fields used in {elastic-sec}. diff --git a/docs/spaces/images/space-management.png b/docs/spaces/images/space-management.png index 2668758a98f4c..bbb0164009e53 100644 Binary files a/docs/spaces/images/space-management.png and b/docs/spaces/images/space-management.png differ diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index 545155e656893..83ea01ee3edf5 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -13,7 +13,7 @@ To make sure you can access alerting and actions, see the <<alerting-prerequisit ============================================== Alerting works by running checks on a schedule to detect conditions defined by a rule. When a condition is met, the rule tracks it as an _alert_ and responds by triggering one or more _actions_. -Actions typically involve interaction with {kib} services or third party integrations. _Connectors_ enable actions to talk to these services and integrations. +Actions typically involve interaction with {kib} services or third party integrations. _Connectors_ enable actions to talk to these services and integrations. This section describes all of these elements and how they operate together. [float] @@ -21,7 +21,7 @@ This section describes all of these elements and how they operate together. A rule specifies a background task that runs on the {kib} server to check for specific conditions. {kib} provides two types of rules: stack rules that are built into {kib} and the rules that are registered by {kib} apps. For more information, refer to <<rule-types>>. -A rule consists of three main parts: +A rule consists of three main parts: * _Conditions_: what needs to be detected? * _Schedule_: when/how often should detection checks run? @@ -41,7 +41,7 @@ The following sections describe each part of the rule in more detail. [[alerting-concepts-conditions]] === Conditions -Under the hood, {kib} rules detect conditions by running a JavaScript function on the {kib} server, which gives it the flexibility to support a wide range of conditions, anything from the results of a simple {es} query to heavy computations involving data from multiple sources or external systems. +Under the hood, {kib} rules detect conditions by running a JavaScript function on the {kib} server, which gives it the flexibility to support a wide range of conditions, anything from the results of a simple {es} query to heavy computations involving data from multiple sources or external systems. These conditions are packaged and exposed as _rule types_. A rule type hides the underlying details of the condition, and exposes a set of parameters to control the details of the conditions to detect. @@ -80,7 +80,7 @@ The _action frequency_ defines when the action runs (for example, only when the Some types of rules enable you to further refine the conditions under which actions run. For example, you can specify that actions run only when an alert occurs within a specific time frame or when it matches a KQL query. -Each action definition is therefore a template: all the parameters needed to invoke a service are supplied except for specific values that are only known at the time the rule condition is detected. +Each action definition is therefore a template: all the parameters needed to invoke a service are supplied except for specific values that are only known at the time the rule condition is detected. In the server monitoring example, the `email` connector type is used, and `server` is mapped to the body of the email, using the template string `CPU on {{server}} is high`. @@ -103,7 +103,7 @@ A rule consists of conditions, actions, and a schedule. When conditions are met, image::images/rule-concepts-summary.svg[Rules, connectors, alerts and actions work together to convert detection into action] -. Any time a rule's conditions are met, an alert is created. This example checks for servers with average CPU > 0.9. Three servers meet the condition, so three alerts are created. +. Any time a rule's conditions are met, an alert is created. This example checks for servers with average CPU > 0.9. Three servers meet the condition, so three alerts are created. . Alerts create actions according to the action frequency, as long as they are not muted or throttled. When actions are created, its properties are filled with actual values. In this example, three actions are created when the threshold is met, and the template string {{server}} is replaced with the appropriate server name for each alert. . {kib} runs the actions, sending notifications by using a third party integration like an email service. . If the third party integration has connection parameters or credentials, {kib} fetches these from the appropriate connector. @@ -119,14 +119,14 @@ independent alerting systems. This section will clarify some of the important differences in the function and intent of the two systems. -Functionally, the {alert-features} differ in that: +Functionally, the {alert-features} differ in that: * Scheduled checks are run on {kib} instead of {es} * {kib} <<alerting-concepts-conditions,rules hide the details of detecting conditions>> through rule types, whereas watches provide low-level control over inputs, conditions, and transformations. * {kib} rules track and persist the state of each detected condition through alerts. This makes it possible to mute and throttle individual alerts, and detect changes in state such as resolution. * Actions are linked to alerts. Actions are fired for each occurrence of a detected condition, rather than for the entire rule. -At a higher level, the {alert-features} allow rich integrations across use cases like <<xpack-apm,*APM*>>, <<metrics-app,*Metrics*>>, <<xpack-siem,*Security*>>, and <<uptime-app,*Uptime*>>. +At a higher level, the {alert-features} allow rich integrations across use cases like <<apm-app,*APM*>>, <<metrics-app,*Metrics*>>, <<xpack-siem,*Security*>>, and <<uptime-app,*Uptime*>>. Prepackaged rule types simplify setup and hide the details of complex, domain-specific detections, while providing a consistent interface across {kib}. -- diff --git a/docs/user/alerting/create-and-manage-rules.asciidoc b/docs/user/alerting/create-and-manage-rules.asciidoc index 5a17a583fb387..1caae10ead421 100644 --- a/docs/user/alerting/create-and-manage-rules.asciidoc +++ b/docs/user/alerting/create-and-manage-rules.asciidoc @@ -34,7 +34,7 @@ more information, go to <<alerting-security>>. === Create and edit rules Some rules must be created within the context of a {kib} app like -<<metrics-app,Metrics>>, <<xpack-apm,APM>>, or <<uptime-app,Uptime>>, but others +<<metrics-app,Metrics>>, <<apm-app,*APM*>>, or <<uptime-app,Uptime>>, but others are generic. Generic rule types can be created in *{rules-ui}* by clicking the *Create rule* button. This will launch a flyout that guides you through selecting a rule type and configuring its conditions and actions. diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index b83e8a9dc29aa..36dce0e2586e5 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -78,8 +78,11 @@ When you create a dashboard, you are automatically in edit mode and can make cha * To open an existing dashboard, click the dashboard *Title* you want to open. + +TIP: When looking for a specific dashboard, you can filter them by tag or by creator, or search the list based on their name and description. Note that the creator information is only available for dashboards created on or after version 8.14. ++ When you open an existing dashboard, you are in view mode. To make changes to the dashboard, click *Edit* in the toolbar. + [float] [[create-panels-with-lens]] === Create and add panels @@ -450,4 +453,6 @@ include::create-panels-with-editors.asciidoc[] include::make-dashboards-interactive.asciidoc[] +include::find-dashboards.asciidoc[] + include::dashboard-troubleshooting.asciidoc[] diff --git a/docs/user/dashboard/find-dashboards.asciidoc b/docs/user/dashboard/find-dashboards.asciidoc new file mode 100644 index 0000000000000..f1cb400944eca --- /dev/null +++ b/docs/user/dashboard/find-dashboards.asciidoc @@ -0,0 +1,29 @@ +[[find-dashboards]] +== Search and filter dashboards + +When looking for specific dashboards to open or share, several actions are available to you to help you find them quicker. + +**Search by name, description, or tag** + +On your list of **Dashboards**, use the search field to look for specific terms. These terms will be highlighted in real time in your dashboard list to help you locate what's relevant to you. + +**Filter by tag** + +When creating or editing dashboards, you can assign them tags that allow you to retrieve them faster in the future. + +On your dashboard list, you have an option that lets you filter dashboards in or out based on their tags. + +**Filter by creator** + +The user who created or imported a dashboard is identified as the dashboard's **creator**. This information is visible right from the dashboard list, and you can filter that list by creator. + +Similarly, managed dashboards created by integrations are identified as created by Elastic. + +NOTE: The creator information is only available for dashboards created on or after version 8.14. For dashboards from previous versions, the creator is empty. + +image::images/dashboard-filter-by-creator.png[Option to filter the list of dashboards by creator] + +**Sort by name or last update date** + +You can sort the dashboard list based on their name or their last update date. + diff --git a/docs/user/dashboard/images/dashboard-creator-editor.png b/docs/user/dashboard/images/dashboard-creator-editor.png new file mode 100644 index 0000000000000..ee75fee60c4b0 Binary files /dev/null and b/docs/user/dashboard/images/dashboard-creator-editor.png differ diff --git a/docs/user/dashboard/images/dashboard-filter-by-creator.png b/docs/user/dashboard/images/dashboard-filter-by-creator.png new file mode 100644 index 0000000000000..d68aa6a945511 Binary files /dev/null and b/docs/user/dashboard/images/dashboard-filter-by-creator.png differ diff --git a/docs/user/dashboard/images/legend-icon.svg b/docs/user/dashboard/images/legend-icon.svg new file mode 100644 index 0000000000000..332612054af28 --- /dev/null +++ b/docs/user/dashboard/images/legend-icon.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M2.786.357a.25.25 0 0 1 .428 0l2.559 4.264A.25.25 0 0 1 5.558 5H.442a.25.25 0 0 1-.215-.379L2.786.357ZM3 1.944 4.234 4H1.766L3 1.944Z"/> + <path d="M8.5 2a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7Z"/> + <path fill-rule="evenodd" d="M1.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3ZM2 7v2h2V7H2Z"/> + <path d="M8.5 7.5a.5.5 0 0 0 0 1h7a.5.5 0 0 0 0-1h-7Z"/> + <path fill-rule="evenodd" d="M3 16a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Zm0-1a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/> + <path d="M8.5 13a.5.5 0 0 0 0 1h7a.5.5 0 1 0 0-1h-7Z"/> +</svg> diff --git a/docs/user/dashboard/images/legend-popover.png b/docs/user/dashboard/images/legend-popover.png new file mode 100644 index 0000000000000..4633ab4d8ee52 Binary files /dev/null and b/docs/user/dashboard/images/legend-popover.png differ diff --git a/docs/user/dashboard/images/statistics-in-legends.png b/docs/user/dashboard/images/statistics-in-legends.png new file mode 100644 index 0000000000000..64e8826a49884 Binary files /dev/null and b/docs/user/dashboard/images/statistics-in-legends.png differ diff --git a/docs/user/dashboard/lens.asciidoc b/docs/user/dashboard/lens.asciidoc index b3c5c4980e933..71ca1ccf51233 100644 --- a/docs/user/dashboard/lens.asciidoc +++ b/docs/user/dashboard/lens.asciidoc @@ -361,13 +361,69 @@ The following component menus are available: * *Labels* — Specifies how to display the labels for donut charts, pie charts, and treemaps. -* *Legend* — Specifies how to display the legend. For example, you can display the legend inside the visualization and truncate the legend values. +* *Legend* — Specifies how to display the legend. You can choose to display the legend inside or outside the visualization, truncate the legend values when they're too long, and <<customize-visualization-legend,select additional statistics to show>>. * *Left axis*, *Bottom axis*, and *Right axis* — Specify how you want to display the chart axes. For example, add axis labels and change the orientation and bounds. +[float] +[[customize-visualization-legend]] +===== Customize a visualization legend + +When creating or editing a visualization, you can customize the way the legend gets displayed, and the data it displays. To do that, look for the image:images/legend-icon.svg[Legend icon] icon. + +image::images/legend-popover.png[Menu with options to customize the legend of a visualization, width=40%] + +NOTE: The options available can vary based on the type of chart you're setting up. For example, showing additional statistics is only possible for time series charts. + +**Change the legend's display** + +With the **Visibility**, **Position**, and **Width** options, you can adjust the way the legend appears in or next to the visualization. + +**Truncate long labels** + +With the **Label truncation** option, you can keep your legend minimal in case of long labels that span over multiple lines. + +**Show additional statistics for time series charts** + +To make your legends as informative as possible, you can show some additional **Statistics** for charts with a timestamp on one of the axes, and add a **Series header**. + +**Bar**, **Line** and **Area** charts can show the following values: + +- **Average**: Average value considering all data points in the chart +- **Median**: Median value considering all data points in the chart +- **Minimum**: Minimum value considering all data points in the chart +- **Maximum**: Maximum value considering all data points in the chart +- **Range**: Difference between min and max values +- **Last value**: Last value considering all data points in the chart +- **Last non-null value:** Last non-null value +- **First value**: First value considering all data points in the chart +- **First non-null value**: First non-null value +- **Difference**: Difference between first and last values +- **Difference %**: % difference between first and last values +- **Sum**: Sum of al values plotted in the chart +- **Count**: number of data points plotted in the chart +- **Distinct Count**: number of data points with different values plotted in the chart +- **Variance**: Variance of all data points plotted in the chart +- **Std Deviation**: Standard deviation of all data points plotted in the chart +- **Current or last value**: The exact value of the current or last data point moused over + + +//Not part of release +//**Pie** charts can only show two static options: +//- **Value** +//- **Percentage** + +All statistics are computed based on the selected time range and the aggregated data points shown in the chart, rather than the original data coming from {es}. + +For example, if the metric plotted in the chart is `Median(system.memory)` and the time range is *last 24 hours*, when you show the **Max** statistic in the Legend, the value that shows corresponds to the `Max[Median(system.memory)]` for the last 24 hours. + +image::images/statistics-in-legends.png[Additional statistics shown in the legend of a memory consumption bar chart] + + + [float] [[explore-lens-data-in-discover]] -=== Explore the data in Discover +==== Explore the data in Discover When your visualization includes one data view, you can open and explore the visualization data in *Discover*. diff --git a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc index ecc7b49750eed..49aaabb0a5420 100644 --- a/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc +++ b/docs/user/dashboard/tutorial-create-a-dashboard-of-lens-panels.asciidoc @@ -376,4 +376,6 @@ Now that you have a complete overview of your web server data, save the dashboar . Select *Store time with dashboard*. -. Click *Save*. +. Click *Save*. You will be identified as the **creator** of the dashboard. If you or another user edit the dashboard, you can also view the **last editor** when checking the dashboard information. + +image::images/dashboard-creator-editor.png[Information panel of a dashboard showing its creator and last editor] diff --git a/docs/user/images/array-in-metrics.png b/docs/user/images/array-in-metrics.png new file mode 100644 index 0000000000000..e7bd84a46af2d Binary files /dev/null and b/docs/user/images/array-in-metrics.png differ diff --git a/docs/user/images/create-simple-api-key.png b/docs/user/images/create-simple-api-key.png new file mode 100644 index 0000000000000..d30271401fe7b Binary files /dev/null and b/docs/user/images/create-simple-api-key.png differ diff --git a/docs/user/images/dashboard-creator-filter.png b/docs/user/images/dashboard-creator-filter.png new file mode 100644 index 0000000000000..69534b06b8bd3 Binary files /dev/null and b/docs/user/images/dashboard-creator-filter.png differ diff --git a/docs/user/images/dashboard-creator.png b/docs/user/images/dashboard-creator.png new file mode 100644 index 0000000000000..3b77ac7995b87 Binary files /dev/null and b/docs/user/images/dashboard-creator.png differ diff --git a/docs/user/images/dashboard-last-editor.png b/docs/user/images/dashboard-last-editor.png new file mode 100644 index 0000000000000..1b4062e90c273 Binary files /dev/null and b/docs/user/images/dashboard-last-editor.png differ diff --git a/docs/user/images/esql-field-statistics.png b/docs/user/images/esql-field-statistics.png new file mode 100644 index 0000000000000..0dfbfeeed67cf Binary files /dev/null and b/docs/user/images/esql-field-statistics.png differ diff --git a/docs/user/images/field-statistics-esql.png b/docs/user/images/field-statistics-esql.png new file mode 100644 index 0000000000000..f94a5dfd5b06d Binary files /dev/null and b/docs/user/images/field-statistics-esql.png differ diff --git a/docs/user/images/field-statistics-panel-in-dashboards.png b/docs/user/images/field-statistics-panel-in-dashboards.png new file mode 100644 index 0000000000000..32d6cdcf0051e Binary files /dev/null and b/docs/user/images/field-statistics-panel-in-dashboards.png differ diff --git a/docs/user/images/integrations-in-esql.png b/docs/user/images/integrations-in-esql.png new file mode 100644 index 0000000000000..1a45b223d9888 Binary files /dev/null and b/docs/user/images/integrations-in-esql.png differ diff --git a/docs/user/images/obs-log-rate-analysis-insigths.png b/docs/user/images/obs-log-rate-analysis-insigths.png new file mode 100644 index 0000000000000..d87707d1fcd84 Binary files /dev/null and b/docs/user/images/obs-log-rate-analysis-insigths.png differ diff --git a/docs/user/images/share-modal.png b/docs/user/images/share-modal.png new file mode 100644 index 0000000000000..d8bd1faf1498a Binary files /dev/null and b/docs/user/images/share-modal.png differ diff --git a/docs/user/images/statistics-in-legends.png b/docs/user/images/statistics-in-legends.png new file mode 100644 index 0000000000000..64e8826a49884 Binary files /dev/null and b/docs/user/images/statistics-in-legends.png differ diff --git a/docs/user/images/statistics-in-legends2.png b/docs/user/images/statistics-in-legends2.png new file mode 100644 index 0000000000000..8fecabb9f9425 Binary files /dev/null and b/docs/user/images/statistics-in-legends2.png differ diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index 419574804312c..5ef23d7a3b718 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -30,8 +30,6 @@ include::{kibana-root}/docs/observability/index.asciidoc[] include::{kibana-root}/docs/playground/index.asciidoc[] -include::{kibana-root}/docs/apm/index.asciidoc[] - include::{kibana-root}/docs/siem/index.asciidoc[] include::dev-tools.asciidoc[] @@ -49,4 +47,3 @@ include::api.asciidoc[] include::plugins.asciidoc[] include::troubleshooting/index.asciidoc[] - diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 5075caad71813..03352865e616c 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -73,10 +73,16 @@ to the {es} {ref}/index-lifecycle-management.html[{ilm-init} documentation]. [float] [[csv-limitations]] -=== CSV reports limitations +=== CSV report limitations include::reporting-csv-limitations.asciidoc[] +[float] +[[pdf-limitations]] +=== PNG/PDF report limitations + +include::reporting-pdf-limitations.asciidoc[] + [float] [[share-a-direct-link]] == Share direct links diff --git a/docs/user/reporting/reporting-csv-troubleshooting.asciidoc b/docs/user/reporting/reporting-csv-troubleshooting.asciidoc index a0eb782ce3d3b..f0edec18bd8e5 100644 --- a/docs/user/reporting/reporting-csv-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-csv-troubleshooting.asciidoc @@ -4,33 +4,20 @@ <titleabbrev>CSV</titleabbrev> ++++ -The CSV export feature in Kibana makes queries to Elasticsearch and formats the results into CSV. -This feature offers a solution that attempts to provide the most benefit to the most use cases. -However, things could go wrong during export. -Elasticsearch can stop responding, repeated querying can take so long that authentication tokens can time -out, and the format of exported data can be too complex for spreadsheet applications to handle. -Such situations are outside of the control of Kibana. -If the use case becomes complex enough, it's recommended that you create scripts that query Elasticsearch directly, using a scripting language like Python and the public {es} APIs. - -For more advice about common problems, refer to <<reporting-troubleshooting>>. - [NOTE] ============ -It is recommended that you use CSV reports to export moderate amounts of data only. -The feature enables analysis of data in external tools, but it's not intended for bulk export or to backup {es} data. -If you need to export more than 250 MB of CSV, rather than increasing <<reporting-csv-settings,`xpack.reporting.csv.maxSizeBytes`>>, use -filters to create multiple smaller reports or extract the data you need directly from {es}. - -The following deployment configurations may lead to failed report jobs or incomplete reports: +include::reporting-csv-limitations.asciidoc[] +============ -* Any shard needed for search is unavailable. -* Data is stored on slow storage tiers. -* Network latency between nodes is high. -* {ccs-cap} is used. +The CSV export feature in Kibana makes queries to Elasticsearch and formats the results into CSV. This feature +offers a solution that attempts to provide the most benefit to the most use cases. However, things could go +wrong during export. Elasticsearch can stop responding, repeated querying can take so long that authentication +tokens can time out, and the format of exported data can be too complex for spreadsheet applications to handle. +Such situations are outside of the control of Kibana. If the use case becomes complex enough, it's recommended +that you create scripts that query Elasticsearch directly, using a scripting language like Python and the +public {es} APIs. -To export large amounts of data, use {es} APIs directly. -Check out the {ref}/point-in-time-api.html[Point in time API] or {ref}/sql-rest-format.html#_csv[SQL with CSV response data format]. -============ +For advice about common problems, refer to <<reporting-troubleshooting>>. [float] [[reporting-troubleshooting-csv-configure-scan-api]] diff --git a/docs/user/reporting/reporting-pdf-limitations.asciidoc b/docs/user/reporting/reporting-pdf-limitations.asciidoc new file mode 100644 index 0000000000000..3433b36eaf988 --- /dev/null +++ b/docs/user/reporting/reporting-pdf-limitations.asciidoc @@ -0,0 +1,7 @@ +We recommend using PNG/PDF reports to export moderate amounts of data only. The feature enables a high-level +export capability, but it's not intended for bulk export. If you need to export several pages of image data, +consider using multiple report jobs to export a small number of pages at a time. If the screenshot of exported +dashboard contains a large number of pixels, consider splitting the large dashboard into smaller artifacts to +use less memory and CPU resources. + +For the most reliable configuration of PDF/PNG {report-features}, consider installing {kib} using <<docker,Docker>> or using <<set-up-on-cloud,Elastic Cloud>>. diff --git a/docs/user/reporting/reporting-pdf-troubleshooting.asciidoc b/docs/user/reporting/reporting-pdf-troubleshooting.asciidoc index 9ea3ff6aa3721..771adf9f6c980 100644 --- a/docs/user/reporting/reporting-pdf-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-pdf-troubleshooting.asciidoc @@ -4,7 +4,10 @@ <titleabbrev>PDF/PNG</titleabbrev> ++++ -For the most reliable configuration of PDF/PNG {report-features}, consider installing {kib} using <<docker,Docker>> or using <<set-up-on-cloud,Elastic Cloud>>. +[NOTE] +============ +include::reporting-pdf-limitations.asciidoc[] +============ For more advice about common problems, refer to <<reporting-troubleshooting>>. @@ -99,10 +102,10 @@ Handle the generated output with care. [[reporting-troubleshooting-system-requirements]] === System requirements -In Elastic Cloud, the {kib} instances that most configurations provide by default is for 1GB of RAM for the instance. -That is enough for {kib} {report-features} when the visualization or dashboard is relatively simple, such as a single pie chart or a dashboard with a few visualizations. -However, certain visualization types incur more load than others. -For example, a TSVB panel has a lot of network requests to render. +In Elastic Cloud, the {kib} instances that most configurations provide by default is for 1GB of RAM for the +instance. That is enough for {kib} {report-features} when the visualization or dashboard is relatively simple, +such as a single pie chart or a dashboard with a few visualizations. However, certain visualization types +incur more load than others. For example, a TSVB panel has a lot of network requests to render. If the {kib} instance doesn't have enough memory to run the report, the report fails with an error such as `Error: Page crashed!`. In this case, try increasing the memory for the {kib} instance to 2GB. diff --git a/docs/user/troubleshooting/using-server-logs.asciidoc b/docs/user/troubleshooting/using-server-logs.asciidoc index 894b229d3f34d..b4dfe9e0bcd99 100644 --- a/docs/user/troubleshooting/using-server-logs.asciidoc +++ b/docs/user/troubleshooting/using-server-logs.asciidoc @@ -36,7 +36,7 @@ logging.loggers: ---- WARNING: Kibana's `file` appender is configured to produce logs in {ecs-ref}/ecs-reference.html[ECS JSON] format. It's the only format that includes the meta information necessary for {apm-node-ref}/log-correlation.html[log correlation] out-of-the-box. -The next step is to define what https://www.elastic.co/observability[observability tools] are available. +The next step is to define what https://www.elastic.co/observability[observability tools] are available. For a better experience, set up an https://www.elastic.co/guide/en/apm/get-started/current/observability-integrations.html[Observability integration] provided by Elastic to debug your application with the <<debugging-logs-apm-ui, APM UI.>> To debug something quickly without setting up additional tooling, you can work with <<plain-kibana-logs, the plain {kib} logs.>> @@ -47,7 +47,7 @@ To debug something quickly without setting up additional tooling, you can work w To debug {kib} with the APM UI, you must set up the APM infrastructure. You can find instructions for the setup process https://www.elastic.co/guide/en/apm/get-started/current/observability-integrations.html[on the Observability integrations page]. -Once you set up the APM infrastructure, you can enable the APM agent and put {kib} under load to collect APM events. To analyze the collected metrics and logs, use the APM UI as demonstrated {kibana-ref}/transactions.html#transaction-trace-sample[in the docs]. +Once you set up the APM infrastructure, you can enable the APM agent and put {kib} under load to collect APM events. To analyze the collected metrics and logs, use the APM UI as demonstrated {observability-guide}/apm-transactions.html#transaction-trace-sample[in the docs]. [[plain-kibana-logs]] ==== Plain {kib} logs diff --git a/docs/user/whats-new.asciidoc b/docs/user/whats-new.asciidoc index 112dc7e0b1ed8..3410a889c8f26 100644 --- a/docs/user/whats-new.asciidoc +++ b/docs/user/whats-new.asciidoc @@ -1,156 +1,198 @@ [[whats-new]] -== What's new in 8.14 +== What's new in 8.15 -Here are the highlights of what's new and improved in 8.14. +Here are the highlights of what's new and improved in 8.15. For detailed information about this release, check the <<release-notes, release notes>>. -Previous versions: {kibana-ref-all}/8.13/whats-new.html[8.13] | {kibana-ref-all}/8.12/whats-new.html[8.12] | {kibana-ref-all}/8.11/whats-new.html[8.11] | {kibana-ref-all}/8.10/whats-new.html[8.10] | {kibana-ref-all}/8.9/whats-new.html[8.9] | {kibana-ref-all}/8.8/whats-new.html[8.8] | {kibana-ref-all}/8.7/whats-new.html[8.7] | {kibana-ref-all}/8.6/whats-new.html[8.6] | {kibana-ref-all}/8.5/whats-new.html[8.5] | {kibana-ref-all}/8.4/whats-new.html[8.4] | {kibana-ref-all}/8.3/whats-new.html[8.3] | {kibana-ref-all}/8.2/whats-new.html[8.2] | {kibana-ref-all}/8.1/whats-new.html[8.1] | {kibana-ref-all}/8.0/whats-new.html[8.0] +Previous versions: {kibana-ref-all}/8.14/whats-new.html[8.14] | {kibana-ref-all}/8.13/whats-new.html[8.13] | {kibana-ref-all}/8.12/whats-new.html[8.12] | {kibana-ref-all}/8.11/whats-new.html[8.11] | {kibana-ref-all}/8.10/whats-new.html[8.10] | {kibana-ref-all}/8.9/whats-new.html[8.9] | {kibana-ref-all}/8.8/whats-new.html[8.8] | {kibana-ref-all}/8.7/whats-new.html[8.7] | {kibana-ref-all}/8.6/whats-new.html[8.6] | {kibana-ref-all}/8.5/whats-new.html[8.5] | {kibana-ref-all}/8.4/whats-new.html[8.4] | {kibana-ref-all}/8.3/whats-new.html[8.3] | {kibana-ref-all}/8.2/whats-new.html[8.2] | {kibana-ref-all}/8.1/whats-new.html[8.1] | {kibana-ref-all}/8.0/whats-new.html[8.0] + [discrete] -=== Discover +=== Analyst Experience [discrete] -==== Transitioning {esql} from Tech Preview to General Availability! +==== View dashboard creator and last editor -{esql} offers a streamlined way to filter, transform, and analyze data in {es}. Its intuitive design, utilizing "pipes" (|) for step-by-step data exploration, enables you to easily compose powerful queries for detailed analysis. Whether you're a developer, SRE, or Security Analyst, {esql} empowers you to uncover specific events, perform robust statistical analyses, and create compelling visualizations. As we move from tech preview to general availability, discover the enhanced capabilities of {esql} and elevate your data operations. +You can now see who created and who last updated a dashboard. -[discrete] -==== {esql} Query History +You can find the creator information right from the dashboard list. + +image::images/dashboard-creator.png[Dashboard creator column in dashboard list] + +Quickly find all dashboards created by the same user with a simple filter. -We've enhanced the {esql} editor to improve your workflow. You can now view and re-run your last 20 {esql} queries directly within Discover, {esql} charts, alerts, and maps. This feature makes it easier to manage your queries and streamline your tasks. +image::images/dashboard-creator-filter.png[Filtering dashboards by creator] -[role="screenshot"] -image::images/query-history-in-discover.png[An image of an ES|QL query history in Discover.] +Note that the creator information will be visible only for dashboards created on or after version 8.14. -[role="screenshot"] -image::images/query-history-in-dashboard.png[An image of the auto option.] +You can also see who last updated a dashboard by clicking the dashboard information icon from the dashboard list. The creator is also visible next to it. This information is immutable and cannot be changed. + +image::images/dashboard-last-editor.png[Dashboard details panel with the name of the last editor] [discrete] -==== Document comparison mode in Discover & {esql} +==== Field statistics in Dashboards + +It's now easier than ever to include your field statistics view from **Discover** into **Dashboards**. While running investigations, it is very common that you need to see some field information, such as unique values and their distribution, to make sense of the data. Select the fields that you want with your ES|QL query and get the document count, values, and distribution in your dashboard so you don't have to navigate back and forth to **Discover** to see this information. -You can now select and compare documents or fields. This functionality streamlines analysis and troubleshooting tasks by allowing you to perform detailed comparisons, such as diffing SIP messages of a certain ID across multiple documents in {es}. +image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt9bc52ff7851acc52/669a4f6a490fbc64fa22f279/field-statistics.gif[Showing field statistics panel in Dashboards] [discrete] -==== Storing {esql} visualizations in saved searches +==== Statistics in legends + +Accelerate time to insights by summarizing the values of your charts using average, minimum, maximum, median, and variance, among many others. You can add these statistics for **Lens** and ES|QL visualizations. It is important to note that these statistics are computed using the data points from the chart considering the aggregation used and not the raw data. In the following example, the chart shows the median memory per host, so the Max = 15.3KB for the first series (artifacts.elastic.co) is the maximum value of the median memory per host. + +image::images/statistics-in-legends.png[Statistics in legends] -Changes to {esql} charts in Discover can now be saved along with {esql} query syntax, allowing you to share and manage your Discover views with ease. +You can find the option to select statistics for your legends along with an explanation for each calculation when editing your visualization, as shown in the following image. -[role="screenshot"] -image::images/esql-viz-saved-search.png[An image of {esql} visualization in saved search.] +image::images/statistics-in-legends2.png[Select statistics in legends] [discrete] -==== {esql} field statistics in Unified Field List +==== Array of values for Metrics -Seeing data statistics while crafting queries can be useful to understand the data batter. To enhance this experience, we've added field stats to the sidebar field popover in {esql} mode, similar to what's available in Discover data view mode. +The new **Metrics** now supports fields that show an array of values. -[role="screenshot"] -image::images/esql-field-stats.png[An image of the new field stats sidebar popover.] +image::images/array-in-metrics.png[A metric showing an array of values, width=35%] [discrete] -==== Custom data view field descriptions +==== Push flyout for Discover document viewer -We've implemented a much-requested feature that allows you to add custom descriptions to data view fields. This enhancement is going to make a significant difference in how you manage and understand your data in Kibana. You'll see these descriptions in the Unified Field List popover in Discover and Lens, enhancing your user experience. +You can now seamlessly view document details and the main table simultaneously in **Discover** with the new _push_ flyout. You can adjust the width of the flyout to suit your needs and explore your data much more easily. -[role="screenshot"] -image::images/custom-descriptions.png[An image of a custom data view field description.] +image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/bltb40a408acf4ab688/669a58ea9fecd85219d58ed2/discover-push-flyout.gif[Resizable push flyout in Discover] [discrete] -=== Dashboard +==== Integrations support in the ES|QL editor when using FROM command. + +We're excited to announce enhanced support for integrations in the ES|QL editor with the *FROM* command. Previously, you could only access indices, but now you can also view a list of installed integrations directly within the editor. This improvement streamlines your workflow, making it easier to manage and utilize various integrations while working with your data. + +image::images/integrations-in-esql.png[Accessing an integration from ES|QL] [discrete] -==== Links panel Generally Available +==== Field statistics in ES|QL -You can now easily navigate from one dashboard to another using the links panel. Better organize your dashboards and make them more performant by chunking them in multiple dashboards with fewer visualizations and linking them together. You can carry over your filters, query and time range when navigating to other related dashboards. Display your links horizontally or vertically as it better suits your dashboard layout. You can also use the links panel to include external links in your dashboards. For example, to your wiki page or other applications. Decide whether you want to open the links in the same browser tab or in a new one. +Field statistics are now available in ES|QL. This feature is designed to provide comprehensive insights for each data field. With this enhancement, you can access detailed statistics such as distributions, averages, and other key metrics, helping you quickly understand your data. This makes data exploration and quality assessment more efficient, providing deeper insights and streamlining the analysis of field-level data in ES|QL. -[role="screenshot"] -image::images/links-panel.gif[A gif of the links panel in action.] +image::images/field-statistics-esql.png[Field statistics in ES|QL] [discrete] -==== Controls apply button +==== Filter UX improvements in ES|QL + +We're thrilled to unveil a complete overhaul of filtering in the ES|QL UX. Now, you can seamlessly filter data by browsing a time series chart, allowing for quick and intuitive time-based filtering. Interactive chart filtering lets you refine your data directly by clicking on any chart, while creating WHERE clause filters from the Discover table or sidebar has never been easier. These enhancements streamline data exploration and analysis, making your ES|QL experience more efficient and user-friendly than ever. + +*Filter by clicking a chart:* + +image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt965a5190f246f7c8/669a7d41e5f7c84793b031cb/filter-by-clicking-chart.gif[Filter by clicking a chart] + +*Filter by browsing a time series chart:* + +image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blta20c9a93dded707c/669a7d40843f93a02fe51013/filter-by-brushing-time-series.gif[Filter by browsing a time series chart] + +*Create WHERE clause filters from Discover table or sidebar:* -Controls are a popular way for users to filter their dashboards. Most of the time, users need to filter several of these controls to get the results they are looking for. In order to optimize performance and minimize the number of queries sent to get the data, we added the option to add a button so the controls selection will not be applied until the user clicks on it. That allows users to filter by multiple fields before sending any new requests to fetch the dashboard data. +image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt50ac35ab3af29ff8/669a7d4006a6fafe4c7cb39d/create-where-clause-filters-from-sidebar.gif[Create WHERE clause filters from Discover table or sidebar] -[role="screenshot"] -image::images/controls-apply-button.png[An image of the new controls apply button in the UI.] +[discrete] +=== Alerting, cases, and connectors -This option is off by default and dashboard authors can decide to enable this button by switching off “Apply selections automatically” from the Controls settings. +[discrete] +==== Case templates -[role="screenshot"] -image::images/control-settings.png[An image of the control settings in the UI.] +{kib} cases offer a new powerful capability to enhance the efficiency of your analyst teams with <<case-templates,templates>>. +You can manage multiple templates, each of which can be used to auto-populate values in a case with pre-defined knowledge. +This streamlines the investigative process and significantly reduces time to resolution. [discrete] -==== Gauge chart +==== Case custom fields are GA -Users can now easily build beautiful and fully functional gauge charts from Lens. The revamped gauges will adapt to the field selected suggesting automatic minimum, maximum and goal values. The gauge charts in Lens will allow users to migrate from TSVB more easily. Users can select five different types of gauge charts: semicircular, circular, arc, vertical or horizontal. +In 8.11, <<case-custom-fields,custom fields>> were added to cases and they are now moving from technical preview to general availability. +You can set custom field values in your templates to enhance consistency across cases. + +[discrete] +==== {sn} additional fields -[role="screenshot"] -image::images/gauge-chart.png[An image of the new gauge chats.] +You can now create enriched {sn} tickets based on detected alerts with a more comprehensive structure that matches the {sn} ticket scheme. +A new JSON field is now available as part of the {sn} action, which enables you to send any field from {kib} alerts to {sn} tickets. [discrete] -==== Region map goes GA +==== {webhook-cm} SSL auth support -Users don’t need to navigate the complexity of the Maps app (meant to be used by more advanced geo users) to build a simple map. They can easily do it now from the Lens editor. +It's common for organizations to integrate with third parties using secured authentication. +Currently, most of the available case connectors use basic authentication (user and passwords or tokens), which might not be sufficient to meet organization security policies. +With this release, the <<cases-webhook-action-type,{webhook-cm} connector>> now supports client certification, which enables you to leverage the connector for secured integration with third parties. -[role="screenshot"] -image::images/region-map.png[An image of the region map.] +The {webhook-cm} connector also moves from technical preview to general availability in this release. [discrete] === Machine Learning [discrete] -==== Cohere reranking +==== Improved UX for Log Pattern Analysis in Discover + +Analyze large volumes of logs efficiently, in very short times with Log Pattern Analysis in **Discover**. In 8.15, we redesigned the Log Pattern Analysis user flow in **Discover** to make it easier to use. Discover log patterns with one click for the message field (and other applicable text fields) and easily filter in and out logs to drastically reduce MTTR. + +image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt7e63d7e764ab183e/669a807bd316c7015db35458/ml-log-pattern-analysis.gif[New log pattern analysis interface] + +[discrete] +==== ES|QL support for field statistics in Discover -In 8.14, we have further enhanced Elastic’s inference API by adding support for Cohere’s foundation link:https://cohere.com/blog/rerank-3[Rerank 3 model] rerank-english-v.3.0. This is a natural next step following support for Cohere’s embeddings in 8.13. Elastic is the only vector database that supports Cohere Rerank 3. +The Field statistics functionality now supports ES|QL, Elastic's primary query language. -Reranking adds semantic precision on your search results, bumping the most relevant ones to the top of the list. Reranking is two-stage (or multi-stage) by nature and this is very powerful as it gives you flexibility, depending on your use case: You can easily combine it as an additional step without making changes to your current approach. Whether this is vector search, BM25 or hybrid search, reranking will deliver semantic relevance gains at the top of your search results list. +image::images/esql-field-statistics.png[Field statistics in ES|QL] -This is particularly important for Retrieval Augmented Generation (RAG), given the LLM’s costs and context windows limits. +[discrete] +==== Field statistics embeddable panel in Dashboards + +You can now add field statistics panels with ES|QL support straight within your dashboards, eliminating the need to transition between **Discover** and **Dashboards**. + +image::images/field-statistics-panel-in-dashboards.png[Field statistics embeddable panel in Dashboards] [discrete] -==== Inference API: RBAC +==== Log Rate Analysis contextual insights in serverless Observability + +You can now see insights in natural language, for example for the root cause of a log rate change or threshold alert, in Log Rate Analysis. This feature is currently only available for Observability serverless projects. -Use the new `inference_user` and `inference_admin` built-in roles to easily manage authorization for the inference API and `trained_models` API. The roles include the `manage_inference` and `monitor_inference` privileges which give full access and read access respectively to the inference endpoints. +image::images/obs-log-rate-analysis-insigths.png[Log Rate Analysis contextual insights in serverless Observability] [discrete] -==== AIOps: Log Pattern Analysis is Generally Available +==== Anthropic integration with the Inference API -In 8.14 Log Pattern Analysis becomes GA. Log Pattern Analysis enables faster and smarter investigation across thousands of log messages in order to analyze, troubleshoot and identify the root cause of an incident. Combine it with Anomaly Detection and our other AIOps features to drastically reduce the MTTR. +The inference API provides a seamless, intuitive interface to perform inference and other tasks against proprietary, hosted, and integrated external services. In 8.15, we're extending it to support Anthropic's chat completion API. [discrete] -==== Query history in {esql} data visualizer +==== Support for reranking with the Inference API -We have enhanced the {esql} editor to improve usability and support your workflows. You can now view and re-run your last 20 {esql} queries directly within the {esql} Data Visualizer. +In 8.15, we're also extending the inference API with the ability to host cross encoder models in Elastic and perform the reranking task. -[role="screenshot"] -image::images/esql-data-viz.png[An image of data visualizer for ES|QL.] +[discrete] +=== Global Experience [discrete] -=== Alerting +==== Simplified Sharing + +You can now share a dashboard, search, or lens object in one click. When sharing an object, the most common actions are directly presented to you, and a short link is automatically generated, making it simpler than ever to share your work. + +image::images/share-modal.png[New object share modal, width=50%] [discrete] -==== Kibana case actions +==== “My dashboards” filter -Alerting rules now support a new action that enables you to create cases automatically when alerts are detected. -The case action can aggregate alerts and group them by any alert field and time window. -For example, you can specify that all alerts that are detected by a certain alerting rule in a given time window (for example 7 days) with the same user will be assigned automatically to the same case. For more information, check out <<cases-action-type>>. +The days of manually scrolling through an endless list of dashboards are behind you. You can now filter by creator to go directly to the dashboards created by a specific teammate. -[role="screenshot"] -image::images/case-action.gif[A gif showing the new case action.] +NOTE: Only dashboards created on or after 8.14 will have a creator. [discrete] -==== {stack-manage-app} Alerts page +==== Quick API keys -A new alerts page is now available to manage alerts as part of the *{stack-manage-app}* menu. The new page enables you to filter alerts by rule type and solution and get a unified view of the alerts that you have authority to view within the space. +Many API keys don’t require custom settings, so we made it simple to generate a standard key. From the **Endpoints & API keys** top menu in Search, you can create a key in seconds. -[role="screenshot"] -image::images/alerts.gif[A looping gif of the new alerts page.] +image::images/create-simple-api-key.png[Shortcut to create an API key, width=60%] [discrete] -==== Jira additional fields support +=== Platform Security -With this Jira connector enhancement, alerts can create enriched Jira issues with a more comprehensive structure that matches the Jira ticket scheme. -A new JSON field is now supported as part of the Jira action so you can define any field to be sent from Kibana alerts to Jira tickets. For more information, check out <<jira-action-type>>. +[discrete] +==== Filtering by User in Kibana Audit Logs -[role="screenshot"] -image::images/jira-connector.png[An image of the new jira connector panel, width =60%] \ No newline at end of file +We are pleased to share that ignoring events by user in Kibana audit logs is now possible. This enhancement will give you more flexibility to reduce the overall number of events logged by the Kibana audit logs service and to control the volume of data being generated in audit logs. While we currently offer a number of ways to do this using the `xpack.security.audit.ignore_filters.[]` configuration setting, there wasn't an easy option to filter by user. With this addition, you can configure Kibana audit logs to ignore events based on values from the following fields: users, spaces, outcomes, categories, types and actions. \ No newline at end of file diff --git a/examples/controls_example/public/app/app.tsx b/examples/controls_example/public/app/app.tsx index 59d8271163b00..e940daec0dab3 100644 --- a/examples/controls_example/public/app/app.tsx +++ b/examples/controls_example/public/app/app.tsx @@ -19,11 +19,10 @@ import { import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; - import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { ControlsExampleStartDeps } from '../plugin'; import { ControlGroupRendererExamples } from './control_group_renderer_examples'; -import { ReactControlExample } from './react_control_example'; +import { ReactControlExample } from './react_control_example/react_control_example'; const CONTROLS_AS_A_BUILDING_BLOCK = 'controls_as_a_building_block'; const CONTROLS_REFACTOR_TEST = 'controls_refactor_test'; diff --git a/examples/controls_example/public/app/react_control_example.tsx b/examples/controls_example/public/app/react_control_example.tsx deleted file mode 100644 index 7cd1b3e115b28..0000000000000 --- a/examples/controls_example/public/app/react_control_example.tsx +++ /dev/null @@ -1,415 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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, useMemo, useState } from 'react'; -import { BehaviorSubject, combineLatest } from 'rxjs'; - -import { - EuiButton, - EuiButtonGroup, - EuiCallOut, - EuiCodeBlock, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiSuperDatePicker, - OnTimeChangeProps, -} from '@elastic/eui'; -import { - CONTROL_GROUP_TYPE, - DEFAULT_CONTROL_GROW, - DEFAULT_CONTROL_WIDTH, -} from '@kbn/controls-plugin/common'; -import { CoreStart } from '@kbn/core/public'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public'; -import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; -import { combineCompatibleChildrenApis } from '@kbn/presentation-containers'; -import { - apiPublishesDataLoading, - HasUniqueId, - PublishesDataLoading, - useBatchedPublishingSubjects, - ViewMode as ViewModeType, -} from '@kbn/presentation-publishing'; -import { toMountPoint } from '@kbn/react-kibana-mount'; - -import { ControlGroupApi } from '../react_controls/control_group/types'; -import { RANGE_SLIDER_CONTROL_TYPE } from '../react_controls/data_controls/range_slider/types'; -import { SEARCH_CONTROL_TYPE } from '../react_controls/data_controls/search_control/types'; -import { TIMESLIDER_CONTROL_TYPE } from '../react_controls/timeslider_control/types'; -import { openDataControlEditor } from '../react_controls/data_controls/open_data_control_editor'; - -const toggleViewButtons = [ - { - id: `viewModeToggle_edit`, - value: ViewMode.EDIT, - label: 'Edit mode', - }, - { - id: `viewModeToggle_view`, - value: ViewMode.VIEW, - label: 'View mode', - }, -]; - -const searchControlId = 'searchControl1'; -const rangeSliderControlId = 'rangeSliderControl1'; -const timesliderControlId = 'timesliderControl1'; -const controlGroupPanels = { - [searchControlId]: { - type: SEARCH_CONTROL_TYPE, - order: 2, - grow: true, - width: 'medium', - explicitInput: { - id: searchControlId, - fieldName: 'message', - title: 'Message', - grow: true, - width: 'medium', - enhancements: {}, - }, - }, - [rangeSliderControlId]: { - type: RANGE_SLIDER_CONTROL_TYPE, - order: 1, - grow: true, - width: 'medium', - explicitInput: { - id: rangeSliderControlId, - fieldName: 'bytes', - title: 'Bytes', - grow: true, - width: 'medium', - enhancements: {}, - }, - }, - [timesliderControlId]: { - type: TIMESLIDER_CONTROL_TYPE, - order: 0, - grow: true, - width: 'medium', - explicitInput: { - id: timesliderControlId, - title: 'Time slider', - enhancements: {}, - }, - }, -}; - -const WEB_LOGS_DATA_VIEW_ID = '90943e30-9a47-11e8-b64d-95841ca0b247'; - -export const ReactControlExample = ({ - core, - dataViews: dataViewsService, -}: { - core: CoreStart; - dataViews: DataViewsPublicPluginStart; -}) => { - const dataLoading$ = useMemo(() => { - return new BehaviorSubject<boolean | undefined>(false); - }, []); - const controlGroupFilters$ = useMemo(() => { - return new BehaviorSubject<Filter[] | undefined>(undefined); - }, []); - const filters$ = useMemo(() => { - return new BehaviorSubject<Filter[] | undefined>(undefined); - }, []); - const unifiedSearchFilters$ = useMemo(() => { - return new BehaviorSubject<Filter[] | undefined>(undefined); - }, []); - const timeRange$ = useMemo(() => { - return new BehaviorSubject<TimeRange | undefined>({ - from: 'now-24h', - to: 'now', - }); - }, []); - const timeslice$ = useMemo(() => { - return new BehaviorSubject<[number, number] | undefined>(undefined); - }, []); - const viewMode$ = useMemo(() => { - return new BehaviorSubject<ViewModeType>(ViewMode.EDIT as ViewModeType); - }, []); - const [dataLoading, timeRange, viewMode] = useBatchedPublishingSubjects( - dataLoading$, - timeRange$, - viewMode$ - ); - - const [controlGroupApi, setControlGroupApi] = useState<ControlGroupApi | undefined>(undefined); - const [isControlGroupInitialized, setIsControlGroupInitialized] = useState(false); - const [dataViewNotFound, setDataViewNotFound] = useState(false); - - const dashboardApi = useMemo(() => { - const query$ = new BehaviorSubject<Query | AggregateQuery | undefined>(undefined); - const children$ = new BehaviorSubject<{ [key: string]: unknown }>({}); - - return { - dataLoading: dataLoading$, - unifiedSearchFilters$, - viewMode: viewMode$, - filters$, - query$, - timeRange$, - timeslice$, - children$, - publishFilters: (newFilters: Filter[] | undefined) => filters$.next(newFilters), - setChild: (child: HasUniqueId) => - children$.next({ ...children$.getValue(), [child.uuid]: child }), - removePanel: () => {}, - replacePanel: () => { - return Promise.resolve(''); - }, - getPanelCount: () => { - return 2; - }, - addNewPanel: () => { - return Promise.resolve(undefined); - }, - lastUsedDataViewId: new BehaviorSubject<string>(WEB_LOGS_DATA_VIEW_ID), - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - const subscription = combineCompatibleChildrenApis<PublishesDataLoading, boolean | undefined>( - dashboardApi, - 'dataLoading', - apiPublishesDataLoading, - undefined, - // flatten method - (values) => { - return values.some((isLoading) => isLoading); - } - ).subscribe((isAtLeastOneChildLoading) => { - dataLoading$.next(isAtLeastOneChildLoading); - }); - - return () => { - subscription.unsubscribe(); - }; - }, [dashboardApi, dataLoading$]); - - useEffect(() => { - let ignore = false; - dataViewsService.get(WEB_LOGS_DATA_VIEW_ID).catch(() => { - if (!ignore) { - setDataViewNotFound(true); - } - }); - - return () => { - ignore = true; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (!controlGroupApi) return; - - const subscription = controlGroupApi.filters$.subscribe((controlGroupFilters) => { - controlGroupFilters$.next(controlGroupFilters); - }); - - return () => { - subscription.unsubscribe(); - }; - }, [controlGroupFilters$, controlGroupApi]); - - useEffect(() => { - if (!controlGroupApi) { - return; - } - let ignore = false; - controlGroupApi.untilInitialized().then(() => { - if (!ignore) { - setIsControlGroupInitialized(true); - } - }); - - return () => { - ignore = true; - }; - }, [controlGroupApi]); - - useEffect(() => { - if (!controlGroupApi) return; - - const subscription = controlGroupApi.timeslice$.subscribe((timeslice) => { - timeslice$.next(timeslice); - }); - - return () => { - subscription.unsubscribe(); - }; - }, [controlGroupApi, timeslice$]); - - useEffect(() => { - const subscription = combineLatest([controlGroupFilters$, unifiedSearchFilters$]).subscribe( - ([controlGroupFilters, unifiedSearchFilters]) => { - filters$.next([...(controlGroupFilters ?? []), ...(unifiedSearchFilters ?? [])]); - } - ); - - return () => { - subscription.unsubscribe(); - }; - }, [controlGroupFilters$, filters$, unifiedSearchFilters$]); - - return ( - <> - {dataViewNotFound && ( - <> - <EuiCallOut color="warning" iconType="warning"> - <p>{`Install "Sample web logs" to run example`}</p> - </EuiCallOut> - <EuiSpacer size="m" /> - </> - )} - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiButton - fill - onClick={() => { - controlGroupApi?.onEdit(); - }} - size="s" - > - Control group settings - </EuiButton> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButton - onClick={() => { - core.overlays.openModal( - toMountPoint( - <EuiCodeBlock language="json"> - {JSON.stringify(controlGroupApi?.serializeState(), null, 2)} - </EuiCodeBlock>, - { - theme: core.theme, - i18n: core.i18n, - } - ) - ); - }} - size="s" - > - Serialize control group - </EuiButton> - </EuiFlexItem> - {controlGroupApi && ( - <EuiFlexItem grow={false}> - <EuiButton - onClick={() => { - openDataControlEditor({ - initialState: { - grow: DEFAULT_CONTROL_GROW, - width: DEFAULT_CONTROL_WIDTH, - dataViewId: dashboardApi.lastUsedDataViewId.getValue(), - }, - onSave: ({ type: controlType, state: initialState }) => { - controlGroupApi.addNewPanel({ - panelType: controlType, - initialState, - }); - }, - controlGroupApi, - services: { - core, - dataViews: dataViewsService, - }, - }); - }} - size="s" - > - Add new data control - </EuiButton> - </EuiFlexItem> - )} - <EuiFlexItem grow={false}> - <EuiButtonGroup - legend="Change the view mode" - options={toggleViewButtons} - idSelected={`viewModeToggle_${viewMode}`} - onChange={(_, value) => { - viewMode$.next(value); - }} - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="m" /> - <EuiSuperDatePicker - isLoading={dataLoading} - start={timeRange?.from} - end={timeRange?.to} - onTimeChange={({ start, end }: OnTimeChangeProps) => { - timeRange$.next({ - from: start, - to: end, - }); - }} - /> - <EuiSpacer size="m" /> - <ReactEmbeddableRenderer - onApiAvailable={(api) => { - dashboardApi?.setChild(api); - setControlGroupApi(api as ControlGroupApi); - }} - hidePanelChrome={true} - type={CONTROL_GROUP_TYPE} - getParentApi={() => ({ - ...dashboardApi, - getSerializedStateForChild: () => ({ - rawState: { - controlStyle: 'oneLine', - chainingSystem: 'HIERARCHICAL', - showApplySelections: false, - panelsJSON: JSON.stringify(controlGroupPanels), - ignoreParentSettingsJSON: - '{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}', - } as object, - references: [ - { - name: `controlGroup_${searchControlId}:${SEARCH_CONTROL_TYPE}DataView`, - type: 'index-pattern', - id: WEB_LOGS_DATA_VIEW_ID, - }, - { - name: `controlGroup_${rangeSliderControlId}:${RANGE_SLIDER_CONTROL_TYPE}DataView`, - type: 'index-pattern', - id: WEB_LOGS_DATA_VIEW_ID, - }, - ], - }), - })} - key={`control_group`} - /> - <EuiSpacer size="l" /> - {isControlGroupInitialized && ( - <div style={{ height: '400px' }}> - <ReactEmbeddableRenderer - type={'data_table'} - getParentApi={() => ({ - ...dashboardApi, - getSerializedStateForChild: () => ({ - rawState: {}, - references: [], - }), - })} - hidePanelChrome={false} - onApiAvailable={(api) => { - dashboardApi?.setChild(api); - }} - /> - </div> - )} - </> - ); -}; diff --git a/examples/controls_example/public/app/react_control_example/react_control_example.tsx b/examples/controls_example/public/app/react_control_example/react_control_example.tsx new file mode 100644 index 0000000000000..9436adc208ebd --- /dev/null +++ b/examples/controls_example/public/app/react_control_example/react_control_example.tsx @@ -0,0 +1,421 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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, useMemo, useState } from 'react'; +import { BehaviorSubject, combineLatest, Subject } from 'rxjs'; +import useMountedState from 'react-use/lib/useMountedState'; +import { + EuiBadge, + EuiButton, + EuiButtonEmpty, + EuiButtonGroup, + EuiCallOut, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiSuperDatePicker, + EuiToolTip, + OnTimeChangeProps, +} from '@elastic/eui'; +import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common'; +import { ControlGroupApi } from '@kbn/controls-plugin/public'; +import { CoreStart } from '@kbn/core/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public'; +import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import { combineCompatibleChildrenApis } from '@kbn/presentation-containers'; +import { + apiPublishesDataLoading, + HasUniqueId, + PublishesDataLoading, + useBatchedPublishingSubjects, + ViewMode as ViewModeType, +} from '@kbn/presentation-publishing'; +import { toMountPoint } from '@kbn/react-kibana-mount'; + +import { + clearControlGroupSerializedState, + getControlGroupSerializedState, + setControlGroupSerializedState, + WEB_LOGS_DATA_VIEW_ID, +} from './serialized_control_group_state'; +import { + clearControlGroupRuntimeState, + getControlGroupRuntimeState, + setControlGroupRuntimeState, +} from './runtime_control_group_state'; + +const toggleViewButtons = [ + { + id: `viewModeToggle_edit`, + value: ViewMode.EDIT, + label: 'Edit mode', + }, + { + id: `viewModeToggle_view`, + value: ViewMode.VIEW, + label: 'View mode', + }, +]; + +export const ReactControlExample = ({ + core, + dataViews: dataViewsService, +}: { + core: CoreStart; + dataViews: DataViewsPublicPluginStart; +}) => { + const isMounted = useMountedState(); + const dataLoading$ = useMemo(() => { + return new BehaviorSubject<boolean | undefined>(false); + }, []); + const controlGroupFilters$ = useMemo(() => { + return new BehaviorSubject<Filter[] | undefined>(undefined); + }, []); + const filters$ = useMemo(() => { + return new BehaviorSubject<Filter[] | undefined>(undefined); + }, []); + const unifiedSearchFilters$ = useMemo(() => { + return new BehaviorSubject<Filter[] | undefined>(undefined); + }, []); + const timeRange$ = useMemo(() => { + return new BehaviorSubject<TimeRange | undefined>({ + from: 'now-24h', + to: 'now', + }); + }, []); + const timeslice$ = useMemo(() => { + return new BehaviorSubject<[number, number] | undefined>(undefined); + }, []); + const viewMode$ = useMemo(() => { + return new BehaviorSubject<ViewModeType>(ViewMode.EDIT as ViewModeType); + }, []); + const saveNotification$ = useMemo(() => { + return new Subject<void>(); + }, []); + const [dataLoading, timeRange, viewMode] = useBatchedPublishingSubjects( + dataLoading$, + timeRange$, + viewMode$ + ); + + const [controlGroupApi, setControlGroupApi] = useState<ControlGroupApi | undefined>(undefined); + const [isControlGroupInitialized, setIsControlGroupInitialized] = useState(false); + const [dataViewNotFound, setDataViewNotFound] = useState(false); + const [isResetting, setIsResetting] = useState(false); + + const dashboardApi = useMemo(() => { + const query$ = new BehaviorSubject<Query | AggregateQuery | undefined>(undefined); + const children$ = new BehaviorSubject<{ [key: string]: unknown }>({}); + + return { + dataLoading: dataLoading$, + unifiedSearchFilters$, + viewMode: viewMode$, + filters$, + query$, + timeRange$, + timeslice$, + children$, + publishFilters: (newFilters: Filter[] | undefined) => filters$.next(newFilters), + setChild: (child: HasUniqueId) => + children$.next({ ...children$.getValue(), [child.uuid]: child }), + removePanel: () => {}, + replacePanel: () => { + return Promise.resolve(''); + }, + getPanelCount: () => { + return 2; + }, + addNewPanel: () => { + return Promise.resolve(undefined); + }, + lastUsedDataViewId: new BehaviorSubject<string>(WEB_LOGS_DATA_VIEW_ID), + saveNotification$, + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + const subscription = combineCompatibleChildrenApis<PublishesDataLoading, boolean | undefined>( + dashboardApi, + 'dataLoading', + apiPublishesDataLoading, + undefined, + // flatten method + (values) => { + return values.some((isLoading) => isLoading); + } + ).subscribe((isAtLeastOneChildLoading) => { + dataLoading$.next(isAtLeastOneChildLoading); + }); + + return () => { + subscription.unsubscribe(); + }; + }, [dashboardApi, dataLoading$]); + + useEffect(() => { + let ignore = false; + dataViewsService.get(WEB_LOGS_DATA_VIEW_ID).catch(() => { + if (!ignore) { + setDataViewNotFound(true); + } + }); + + return () => { + ignore = true; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!controlGroupApi) return; + + const subscription = controlGroupApi.filters$.subscribe((controlGroupFilters) => { + controlGroupFilters$.next(controlGroupFilters); + }); + + return () => { + subscription.unsubscribe(); + }; + }, [controlGroupFilters$, controlGroupApi]); + + useEffect(() => { + if (!controlGroupApi) { + return; + } + let ignore = false; + controlGroupApi.untilInitialized().then(() => { + if (!ignore) { + setIsControlGroupInitialized(true); + } + }); + + return () => { + ignore = true; + }; + }, [controlGroupApi]); + + useEffect(() => { + if (!controlGroupApi) return; + + const subscription = controlGroupApi.timeslice$.subscribe((timeslice) => { + timeslice$.next(timeslice); + }); + + return () => { + subscription.unsubscribe(); + }; + }, [controlGroupApi, timeslice$]); + + useEffect(() => { + const subscription = combineLatest([controlGroupFilters$, unifiedSearchFilters$]).subscribe( + ([controlGroupFilters, unifiedSearchFilters]) => { + filters$.next([...(controlGroupFilters ?? []), ...(unifiedSearchFilters ?? [])]); + } + ); + + return () => { + subscription.unsubscribe(); + }; + }, [controlGroupFilters$, filters$, unifiedSearchFilters$]); + + const [unsavedChanges, setUnsavedChanges] = useState<string | undefined>(undefined); + useEffect(() => { + if (!controlGroupApi) { + return; + } + const subscription = controlGroupApi.unsavedChanges.subscribe((nextUnsavedChanges) => { + if (!nextUnsavedChanges) { + clearControlGroupRuntimeState(); + setUnsavedChanges(undefined); + return; + } + + setControlGroupRuntimeState(nextUnsavedChanges); + + // JSON.stringify removes keys where value is `undefined` + // switch `undefined` to `null` to see when value has been cleared + const replacer = (key: unknown, value: unknown) => + typeof value === 'undefined' ? null : value; + setUnsavedChanges(JSON.stringify(nextUnsavedChanges, replacer, ' ')); + }); + + return () => { + subscription.unsubscribe(); + }; + }, [controlGroupApi]); + + return ( + <> + {dataViewNotFound && ( + <EuiCallOut color="warning" iconType="warning"> + <p>{`Install "Sample web logs" to run example`}</p> + </EuiCallOut> + )} + {!dataViewNotFound && ( + <EuiCallOut title="This example uses session storage to persist saved state and unsaved changes"> + <EuiButton + color="accent" + size="s" + onClick={() => { + clearControlGroupSerializedState(); + clearControlGroupRuntimeState(); + window.location.reload(); + }} + > + Reset example + </EuiButton> + </EuiCallOut> + )} + + <EuiSpacer size="m" /> + + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiButton + fill + onClick={() => { + controlGroupApi?.onEdit(); + }} + size="s" + > + Control group settings + </EuiButton> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton + onClick={() => { + core.overlays.openModal( + toMountPoint( + <EuiCodeBlock language="json"> + {JSON.stringify(controlGroupApi?.serializeState(), null, 2)} + </EuiCodeBlock>, + { + theme: core.theme, + i18n: core.i18n, + } + ) + ); + }} + size="s" + > + Serialize control group + </EuiButton> + </EuiFlexItem> + {controlGroupApi && ( + <EuiFlexItem grow={false}> + <EuiButton + onClick={() => { + controlGroupApi?.openAddDataControlFlyout(); + }} + size="s" + > + Add new data control + </EuiButton> + </EuiFlexItem> + )} + <EuiFlexItem grow={false}> + <EuiButtonGroup + legend="Change the view mode" + options={toggleViewButtons} + idSelected={`viewModeToggle_${viewMode}`} + onChange={(_, value) => { + viewMode$.next(value); + }} + /> + </EuiFlexItem> + {unsavedChanges !== undefined && viewMode === 'edit' && ( + <> + <EuiFlexItem grow={false}> + <EuiToolTip content={<pre>{unsavedChanges}</pre>}> + <EuiBadge color="warning">Unsaved changes</EuiBadge> + </EuiToolTip> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonEmpty + isDisabled={!controlGroupApi || isResetting} + isLoading={isResetting} + onClick={async () => { + if (!controlGroupApi) { + return; + } + setIsResetting(true); + await controlGroupApi.asyncResetUnsavedChanges(); + if (isMounted()) setIsResetting(false); + }} + > + Reset + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton + onClick={async () => { + if (controlGroupApi) { + saveNotification$.next(); + setControlGroupSerializedState(await controlGroupApi.serializeState()); + } + }} + > + Save + </EuiButton> + </EuiFlexItem> + </> + )} + </EuiFlexGroup> + <EuiSpacer size="m" /> + <EuiSuperDatePicker + isLoading={dataLoading} + start={timeRange?.from} + end={timeRange?.to} + onTimeChange={({ start, end }: OnTimeChangeProps) => { + timeRange$.next({ + from: start, + to: end, + }); + }} + /> + <EuiSpacer size="m" /> + <ReactEmbeddableRenderer + onApiAvailable={(api) => { + dashboardApi?.setChild(api); + setControlGroupApi(api as ControlGroupApi); + }} + hidePanelChrome={true} + type={CONTROL_GROUP_TYPE} + getParentApi={() => ({ + ...dashboardApi, + getSerializedStateForChild: getControlGroupSerializedState, + getRuntimeStateForChild: getControlGroupRuntimeState, + })} + key={`control_group`} + /> + <EuiSpacer size="l" /> + {isControlGroupInitialized && ( + <div style={{ height: '400px' }}> + <ReactEmbeddableRenderer + type={'data_table'} + getParentApi={() => ({ + ...dashboardApi, + getSerializedStateForChild: () => ({ + rawState: {}, + references: [], + }), + })} + hidePanelChrome={false} + onApiAvailable={(api) => { + dashboardApi?.setChild(api); + }} + /> + </div> + )} + </> + ); +}; diff --git a/examples/controls_example/public/app/react_control_example/runtime_control_group_state.ts b/examples/controls_example/public/app/react_control_example/runtime_control_group_state.ts new file mode 100644 index 0000000000000..50c4ad7ad1d91 --- /dev/null +++ b/examples/controls_example/public/app/react_control_example/runtime_control_group_state.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 { ControlGroupRuntimeState } from '@kbn/controls-plugin/public'; + +const RUNTIME_STATE_SESSION_STORAGE_KEY = + 'kibana.examples.controls.reactControlExample.controlGroupRuntimeState'; + +export function clearControlGroupRuntimeState() { + sessionStorage.removeItem(RUNTIME_STATE_SESSION_STORAGE_KEY); +} + +export function getControlGroupRuntimeState(): Partial<ControlGroupRuntimeState> { + const runtimeStateJSON = sessionStorage.getItem(RUNTIME_STATE_SESSION_STORAGE_KEY); + return runtimeStateJSON ? JSON.parse(runtimeStateJSON) : {}; +} + +export function setControlGroupRuntimeState(runtimeState: Partial<ControlGroupRuntimeState>) { + sessionStorage.setItem(RUNTIME_STATE_SESSION_STORAGE_KEY, JSON.stringify(runtimeState)); +} diff --git a/examples/controls_example/public/app/react_control_example/serialized_control_group_state.ts b/examples/controls_example/public/app/react_control_example/serialized_control_group_state.ts new file mode 100644 index 0000000000000..b02cf450cdd73 --- /dev/null +++ b/examples/controls_example/public/app/react_control_example/serialized_control_group_state.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SerializedPanelState } from '@kbn/presentation-containers'; +import { ControlGroupSerializedState } from '@kbn/controls-plugin/public'; +import { + OPTIONS_LIST_CONTROL, + RANGE_SLIDER_CONTROL, + TIME_SLIDER_CONTROL, +} from '@kbn/controls-plugin/common'; + +const SERIALIZED_STATE_SESSION_STORAGE_KEY = + 'kibana.examples.controls.reactControlExample.controlGroupSerializedState'; +export const WEB_LOGS_DATA_VIEW_ID = '90943e30-9a47-11e8-b64d-95841ca0b247'; + +export function clearControlGroupSerializedState() { + sessionStorage.removeItem(SERIALIZED_STATE_SESSION_STORAGE_KEY); +} + +export function getControlGroupSerializedState(): SerializedPanelState<ControlGroupSerializedState> { + const serializedStateJSON = sessionStorage.getItem(SERIALIZED_STATE_SESSION_STORAGE_KEY); + return serializedStateJSON ? JSON.parse(serializedStateJSON) : initialSerializedControlGroupState; +} + +export function setControlGroupSerializedState( + serializedState: SerializedPanelState<ControlGroupSerializedState> +) { + sessionStorage.setItem(SERIALIZED_STATE_SESSION_STORAGE_KEY, JSON.stringify(serializedState)); +} + +const optionsListId = 'optionsList1'; +const searchControlId = 'searchControl1'; +const rangeSliderControlId = 'rangeSliderControl1'; +const timesliderControlId = 'timesliderControl1'; +const controlGroupPanels = { + [rangeSliderControlId]: { + type: RANGE_SLIDER_CONTROL, + order: 1, + grow: true, + width: 'medium', + explicitInput: { + id: rangeSliderControlId, + fieldName: 'bytes', + title: 'Bytes', + grow: true, + width: 'medium', + enhancements: {}, + }, + }, + [timesliderControlId]: { + type: TIME_SLIDER_CONTROL, + order: 4, + grow: true, + width: 'medium', + explicitInput: { + id: timesliderControlId, + title: 'Time slider', + enhancements: {}, + }, + }, + [optionsListId]: { + type: OPTIONS_LIST_CONTROL, + order: 2, + grow: true, + width: 'medium', + explicitInput: { + id: searchControlId, + fieldName: 'agent.keyword', + title: 'Agent', + grow: true, + width: 'medium', + enhancements: {}, + }, + }, +}; + +const initialSerializedControlGroupState = { + rawState: { + controlStyle: 'oneLine', + chainingSystem: 'HIERARCHICAL', + showApplySelections: false, + panelsJSON: JSON.stringify(controlGroupPanels), + ignoreParentSettingsJSON: + '{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}', + } as object, + references: [ + { + name: `controlGroup_${rangeSliderControlId}:${RANGE_SLIDER_CONTROL}DataView`, + type: 'index-pattern', + id: WEB_LOGS_DATA_VIEW_ID, + }, + { + name: `controlGroup_${optionsListId}:${OPTIONS_LIST_CONTROL}DataView`, + type: 'index-pattern', + id: WEB_LOGS_DATA_VIEW_ID, + }, + ], +}; diff --git a/examples/controls_example/public/plugin.tsx b/examples/controls_example/public/plugin.tsx index 64f6686e92c8c..40ba4b1140fab 100644 --- a/examples/controls_example/public/plugin.tsx +++ b/examples/controls_example/public/plugin.tsx @@ -6,24 +6,16 @@ * Side Public License, v 1. */ -import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common'; import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; -import { EmbeddableSetup, PANEL_HOVER_TRIGGER } from '@kbn/embeddable-plugin/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { PLUGIN_ID } from './constants'; import img from './control_group_image.png'; -import { EditControlAction } from './react_controls/actions/edit_control_action'; -import { registerControlFactory } from './react_controls/control_factory_registry'; -import { RANGE_SLIDER_CONTROL_TYPE } from './react_controls/data_controls/range_slider/types'; -import { SEARCH_CONTROL_TYPE } from './react_controls/data_controls/search_control/types'; -import { TIMESLIDER_CONTROL_TYPE } from './react_controls/timeslider_control/types'; interface SetupDeps { developerExamples: DeveloperExamplesSetup; - embeddable: EmbeddableSetup; } export interface ControlsExampleStartDeps { @@ -35,58 +27,7 @@ export interface ControlsExampleStartDeps { export class ControlsExamplePlugin implements Plugin<void, void, SetupDeps, ControlsExampleStartDeps> { - public setup( - core: CoreSetup<ControlsExampleStartDeps>, - { developerExamples, embeddable }: SetupDeps - ) { - embeddable.registerReactEmbeddableFactory(CONTROL_GROUP_TYPE, async () => { - const [{ getControlGroupEmbeddableFactory }, [coreStart, depsStart]] = await Promise.all([ - import('./react_controls/control_group/get_control_group_factory'), - core.getStartServices(), - ]); - return getControlGroupEmbeddableFactory({ - core: coreStart, - dataViews: depsStart.data.dataViews, - }); - }); - - registerControlFactory(RANGE_SLIDER_CONTROL_TYPE, async () => { - const [{ getRangesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ - import('./react_controls/data_controls/range_slider/get_range_slider_control_factory'), - core.getStartServices(), - ]); - - return getRangesliderControlFactory({ - core: coreStart, - data: depsStart.data, - dataViews: depsStart.data.dataViews, - }); - }); - - registerControlFactory(SEARCH_CONTROL_TYPE, async () => { - const [{ getSearchControlFactory: getSearchEmbeddableFactory }, [coreStart, depsStart]] = - await Promise.all([ - import('./react_controls/data_controls/search_control/get_search_control_factory'), - core.getStartServices(), - ]); - - return getSearchEmbeddableFactory({ - core: coreStart, - dataViewsService: depsStart.data.dataViews, - }); - }); - - registerControlFactory(TIMESLIDER_CONTROL_TYPE, async () => { - const [{ getTimesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ - import('./react_controls/timeslider_control/get_timeslider_control_factory'), - core.getStartServices(), - ]); - return getTimesliderControlFactory({ - core: coreStart, - data: depsStart.data, - }); - }); - + public setup(core: CoreSetup<ControlsExampleStartDeps>, { developerExamples }: SetupDeps) { core.application.register({ id: PLUGIN_ID, title: 'Controls examples', @@ -106,11 +47,7 @@ export class ControlsExamplePlugin }); } - public start(core: CoreStart, deps: ControlsExampleStartDeps) { - const editControlAction = new EditControlAction(); - deps.uiActions.registerAction(editControlAction); - deps.uiActions.attachAction(PANEL_HOVER_TRIGGER, editControlAction.id); - } + public start(core: CoreStart, deps: ControlsExampleStartDeps) {} public stop() {} } diff --git a/examples/controls_example/public/react_controls/actions/edit_control_action.tsx b/examples/controls_example/public/react_controls/actions/edit_control_action.tsx deleted file mode 100644 index c8165c2a3642b..0000000000000 --- a/examples/controls_example/public/react_controls/actions/edit_control_action.tsx +++ /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 React from 'react'; - -import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { apiIsPresentationContainer } from '@kbn/presentation-containers'; -import { - apiCanAccessViewMode, - apiHasParentApi, - apiHasType, - apiHasUniqueId, - apiIsOfType, - EmbeddableApiContext, - getInheritedViewMode, - hasEditCapabilities, -} from '@kbn/presentation-publishing'; -import { Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; - -import { DataControlApi } from '../data_controls/types'; - -const isApiCompatible = (api: unknown | null): api is DataControlApi => - Boolean( - apiHasType(api) && - apiHasUniqueId(api) && - hasEditCapabilities(api) && - apiHasParentApi(api) && - apiCanAccessViewMode(api.parentApi) && - apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) && - apiIsPresentationContainer(api.parentApi) - ); - -const ACTION_EDIT_CONTROL = 'editDataControl'; - -export class EditControlAction implements Action<EmbeddableApiContext> { - public readonly type = ACTION_EDIT_CONTROL; - public readonly id = ACTION_EDIT_CONTROL; - public order = 2; - - constructor() {} - - public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => { - if (!isApiCompatible(context.embeddable)) throw new IncompatibleActionError(); - return ( - <EuiToolTip content={this.getDisplayName(context)}> - <EuiButtonIcon - data-test-subj={`control-action-${context.embeddable.uuid}-edit`} - aria-label={this.getDisplayName(context)} - iconType={this.getIconType(context)} - onClick={() => this.execute(context)} - color="text" - /> - </EuiToolTip> - ); - }; - - public getDisplayName({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); - return i18n.translate('controls.controlGroup.floatingActions.editTitle', { - defaultMessage: 'Edit', - }); - } - - public getIconType({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); - return 'pencil'; - } - - public async isCompatible({ embeddable }: EmbeddableApiContext) { - return ( - isApiCompatible(embeddable) && - getInheritedViewMode(embeddable.parentApi) === ViewMode.EDIT && - embeddable.isEditingEnabled() - ); - } - - public async execute({ embeddable }: EmbeddableApiContext) { - if (!isApiCompatible(embeddable)) throw new IncompatibleActionError(); - await embeddable.onEdit(); - } -} diff --git a/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx b/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx deleted file mode 100644 index 84bf9c949210a..0000000000000 --- a/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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 } from 'react'; -import { BehaviorSubject } from 'rxjs'; -import { - ControlGroupChainingSystem, - ControlWidth, - CONTROL_GROUP_TYPE, - DEFAULT_CONTROL_GROW, - DEFAULT_CONTROL_STYLE, - DEFAULT_CONTROL_WIDTH, -} from '@kbn/controls-plugin/common'; -import { ControlStyle, ParentIgnoreSettings } from '@kbn/controls-plugin/public'; -import { CoreStart } from '@kbn/core/public'; -import { DataView } from '@kbn/data-views-plugin/common'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { combineCompatibleChildrenApis } from '@kbn/presentation-containers'; -import { - apiPublishesDataViews, - PublishesDataViews, - useBatchedPublishingSubjects, -} from '@kbn/presentation-publishing'; -import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch'; -import { initControlsManager } from './init_controls_manager'; -import { openEditControlGroupFlyout } from './open_edit_control_group_flyout'; -import { deserializeControlGroup } from './serialization_utils'; -import { - ControlGroupApi, - ControlGroupRuntimeState, - ControlGroupSerializedState, - ControlGroupUnsavedChanges, -} from './types'; -import { ControlGroup } from './components/control_group'; -import { initSelectionsManager } from './selections_manager'; - -export const getControlGroupEmbeddableFactory = (services: { - core: CoreStart; - dataViews: DataViewsPublicPluginStart; -}) => { - const controlGroupEmbeddableFactory: ReactEmbeddableFactory< - ControlGroupSerializedState, - ControlGroupRuntimeState, - ControlGroupApi - > = { - type: CONTROL_GROUP_TYPE, - deserializeState: (state) => deserializeControlGroup(state), - buildEmbeddable: async (initialState, buildApi, uuid, parentApi, setApi) => { - const { - initialChildControlState, - defaultControlGrow, - defaultControlWidth, - labelPosition: initialLabelPosition, - chainingSystem, - autoApplySelections, - ignoreParentSettings, - } = initialState; - - const autoApplySelections$ = new BehaviorSubject<boolean>(autoApplySelections); - const controlsManager = initControlsManager(initialChildControlState); - const selectionsManager = initSelectionsManager({ - ...controlsManager.api, - autoApplySelections$, - }); - const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined); - const chainingSystem$ = new BehaviorSubject<ControlGroupChainingSystem>(chainingSystem); - const ignoreParentSettings$ = new BehaviorSubject<ParentIgnoreSettings | undefined>( - ignoreParentSettings - ); - const grow = new BehaviorSubject<boolean | undefined>( - defaultControlGrow === undefined ? DEFAULT_CONTROL_GROW : defaultControlGrow - ); - const width = new BehaviorSubject<ControlWidth | undefined>( - defaultControlWidth ?? DEFAULT_CONTROL_WIDTH - ); - const labelPosition$ = new BehaviorSubject<ControlStyle>( // TODO: Rename `ControlStyle` - initialLabelPosition ?? DEFAULT_CONTROL_STYLE // TODO: Rename `DEFAULT_CONTROL_STYLE` - ); - - /** TODO: Handle loading; loading should be true if any child is loading */ - const dataLoading$ = new BehaviorSubject<boolean | undefined>(false); - - /** TODO: Handle unsaved changes - * - Each child has an unsaved changed behaviour subject it pushes to - * - The control group listens to all of them (anyChildHasUnsavedChanges) and publishes its - * own unsaved changes if either one of its children has unsaved changes **or** one of - * the control group settings changed. - * - Children should **not** publish unsaved changes based on their output filters or selections. - * Instead, the control group will handle unsaved changes for filters. - */ - const unsavedChanges = new BehaviorSubject<Partial<ControlGroupUnsavedChanges> | undefined>( - undefined - ); - - const api = setApi({ - ...controlsManager.api, - ...selectionsManager.api, - controlFetch$: (controlUuid: string) => - controlFetch$( - chaining$( - controlUuid, - chainingSystem$, - controlsManager.controlsInOrder$, - controlsManager.api.children$ - ), - controlGroupFetch$(ignoreParentSettings$, parentApi ? parentApi : {}) - ), - ignoreParentSettings$, - autoApplySelections$, - unsavedChanges, - resetUnsavedChanges: () => { - // TODO: Implement this - }, - snapshotRuntimeState: () => { - // TODO: Remove this if it ends up being unnecessary - return {} as unknown as ControlGroupRuntimeState; - }, - dataLoading: dataLoading$, - onEdit: async () => { - openEditControlGroupFlyout( - api, - { - chainingSystem: chainingSystem$, - labelPosition: labelPosition$, - autoApplySelections: autoApplySelections$, - ignoreParentSettings: ignoreParentSettings$, - }, - { core: services.core } - ); - }, - isEditingEnabled: () => true, - getTypeDisplayName: () => - i18n.translate('controls.controlGroup.displayName', { - defaultMessage: 'Controls', - }), - serializeState: () => { - const { panelsJSON, references } = controlsManager.serializeControls(); - return { - rawState: { - chainingSystem: chainingSystem$.getValue(), - controlStyle: labelPosition$.getValue(), // Rename "labelPosition" to "controlStyle" - showApplySelections: !autoApplySelections$.getValue(), - ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings$.getValue()), - panelsJSON, - }, - references, - }; - }, - grow, - width, - dataViews, - labelPosition: labelPosition$, - }); - - /** Subscribe to all children's output data views, combine them, and output them */ - const childrenDataViewsSubscription = combineCompatibleChildrenApis< - PublishesDataViews, - DataView[] - >(api, 'dataViews', apiPublishesDataViews, []).subscribe((newDataViews) => - dataViews.next(newDataViews) - ); - - return { - api, - Component: () => { - const [hasUnappliedSelections, labelPosition] = useBatchedPublishingSubjects( - selectionsManager.hasUnappliedSelections$, - labelPosition$ - ); - - useEffect(() => { - return () => { - selectionsManager.cleanup(); - childrenDataViewsSubscription.unsubscribe(); - }; - }, []); - - return ( - <ControlGroup - applySelections={selectionsManager.applySelections} - controlGroupApi={api} - controlsManager={controlsManager} - hasUnappliedSelections={hasUnappliedSelections} - labelPosition={labelPosition} - /> - ); - }, - }; - }, - }; - - return controlGroupEmbeddableFactory; -}; diff --git a/examples/controls_example/public/react_controls/control_group/init_controls_manager.test.ts b/examples/controls_example/public/react_controls/control_group/init_controls_manager.test.ts deleted file mode 100644 index 98210d3d19eac..0000000000000 --- a/examples/controls_example/public/react_controls/control_group/init_controls_manager.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { DefaultControlApi } from '../types'; -import { initControlsManager } from './init_controls_manager'; - -jest.mock('uuid', () => ({ - v4: jest.fn().mockReturnValue('delta'), -})); - -describe('PresentationContainer api', () => { - test('addNewPanel should add control at end of controls', async () => { - const controlsManager = initControlsManager({ - alpha: { type: 'whatever', order: 0 }, - bravo: { type: 'whatever', order: 1 }, - charlie: { type: 'whatever', order: 2 }, - }); - const addNewPanelPromise = controlsManager.api.addNewPanel({ - panelType: 'whatever', - initialState: {}, - }); - controlsManager.setControlApi('delta', {} as unknown as DefaultControlApi); - await addNewPanelPromise; - expect(controlsManager.controlsInOrder$.value.map((element) => element.id)).toEqual([ - 'alpha', - 'bravo', - 'charlie', - 'delta', - ]); - }); - - test('removePanel should remove control', () => { - const controlsManager = initControlsManager({ - alpha: { type: 'whatever', order: 0 }, - bravo: { type: 'whatever', order: 1 }, - charlie: { type: 'whatever', order: 2 }, - }); - controlsManager.api.removePanel('bravo'); - expect(controlsManager.controlsInOrder$.value.map((element) => element.id)).toEqual([ - 'alpha', - 'charlie', - ]); - }); - - test('replacePanel should replace control', async () => { - const controlsManager = initControlsManager({ - alpha: { type: 'whatever', order: 0 }, - bravo: { type: 'whatever', order: 1 }, - charlie: { type: 'whatever', order: 2 }, - }); - const replacePanelPromise = controlsManager.api.replacePanel('bravo', { - panelType: 'whatever', - initialState: {}, - }); - controlsManager.setControlApi('delta', {} as unknown as DefaultControlApi); - await replacePanelPromise; - expect(controlsManager.controlsInOrder$.value.map((element) => element.id)).toEqual([ - 'alpha', - 'delta', - 'charlie', - ]); - }); - - describe('untilInitialized', () => { - test('should not resolve until all controls are initialized', async () => { - const controlsManager = initControlsManager({ - alpha: { type: 'whatever', order: 0 }, - bravo: { type: 'whatever', order: 1 }, - }); - let isDone = false; - controlsManager.api.untilInitialized().then(() => { - isDone = true; - }); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(isDone).toBe(false); - - controlsManager.setControlApi('alpha', {} as unknown as DefaultControlApi); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(isDone).toBe(false); - - controlsManager.setControlApi('bravo', {} as unknown as DefaultControlApi); - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(isDone).toBe(true); - }); - - test('should resolve when all control already initialized ', async () => { - const controlsManager = initControlsManager({ - alpha: { type: 'whatever', order: 0 }, - bravo: { type: 'whatever', order: 1 }, - }); - controlsManager.setControlApi('alpha', {} as unknown as DefaultControlApi); - controlsManager.setControlApi('bravo', {} as unknown as DefaultControlApi); - - let isDone = false; - controlsManager.api.untilInitialized().then(() => { - isDone = true; - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - expect(isDone).toBe(true); - }); - }); -}); diff --git a/examples/controls_example/public/react_controls/control_group/init_controls_manager.ts b/examples/controls_example/public/react_controls/control_group/init_controls_manager.ts deleted file mode 100644 index 378bfe4d82567..0000000000000 --- a/examples/controls_example/public/react_controls/control_group/init_controls_manager.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { v4 as generateId } from 'uuid'; -import { - HasSerializedChildState, - PanelPackage, - PresentationContainer, -} from '@kbn/presentation-containers'; -import type { Reference } from '@kbn/content-management-utils'; -import { BehaviorSubject, first, merge } from 'rxjs'; -import { PublishingSubject } from '@kbn/presentation-publishing'; -import { omit } from 'lodash'; -import { ControlPanelsState, ControlPanelState } from './types'; -import { DefaultControlApi, DefaultControlState } from '../types'; - -export type ControlsInOrder = Array<{ id: string; type: string }>; - -export function initControlsManager(initialControlPanelsState: ControlPanelsState) { - const initialControlIds = Object.keys(initialControlPanelsState); - const children$ = new BehaviorSubject<{ [key: string]: DefaultControlApi }>({}); - const controlsPanelState: { [panelId: string]: DefaultControlState } = { - ...initialControlPanelsState, - }; - const controlsInOrder$ = new BehaviorSubject<ControlsInOrder>( - Object.keys(initialControlPanelsState) - .map((key) => ({ - id: key, - order: initialControlPanelsState[key].order, - type: initialControlPanelsState[key].type, - })) - .sort((a, b) => (a.order > b.order ? 1 : -1)) - .map(({ id, type }) => ({ id, type })) // filter out `order` - ); - - function untilControlLoaded( - id: string - ): DefaultControlApi | Promise<DefaultControlApi | undefined> { - if (children$.value[id]) { - return children$.value[id]; - } - - return new Promise((resolve) => { - const subscription = merge(children$, controlsInOrder$).subscribe(() => { - if (children$.value[id]) { - subscription.unsubscribe(); - resolve(children$.value[id]); - return; - } - - // control removed before the control finished loading. - const controlState = controlsInOrder$.value.find((element) => element.id === id); - if (!controlState) { - subscription.unsubscribe(); - resolve(undefined); - } - }); - }); - } - - function getControlApi(controlUuid: string) { - return children$.value[controlUuid]; - } - - async function addNewPanel( - { panelType, initialState }: PanelPackage<DefaultControlState>, - index: number - ) { - const id = generateId(); - const nextControlsInOrder = [...controlsInOrder$.value]; - nextControlsInOrder.splice(index, 0, { - id, - type: panelType, - }); - controlsInOrder$.next(nextControlsInOrder); - controlsPanelState[id] = initialState ?? {}; - return await untilControlLoaded(id); - } - - function removePanel(panelId: string) { - delete controlsPanelState[panelId]; - controlsInOrder$.next(controlsInOrder$.value.filter(({ id }) => id !== panelId)); - children$.next(omit(children$.value, panelId)); - } - - return { - controlsInOrder$, - getControlApi, - setControlApi: (uuid: string, controlApi: DefaultControlApi) => { - children$.next({ - ...children$.getValue(), - [uuid]: controlApi, - }); - }, - serializeControls: () => { - const references: Reference[] = []; - const explicitInputPanels: { - [panelId: string]: ControlPanelState & { explicitInput: object }; - } = {}; - - controlsInOrder$.getValue().forEach(({ id }, index) => { - const controlApi = getControlApi(id); - if (!controlApi) { - return; - } - - const { - rawState: { grow, width, ...rest }, - references: controlReferences, - } = controlApi.serializeState(); - - if (controlReferences && controlReferences.length > 0) { - references.push(...controlReferences); - } - - explicitInputPanels[id] = { - grow, - order: index, - type: controlApi.type, - width, - /** Re-add the `explicitInput` layer on serialize so control group saved object retains shape */ - explicitInput: rest, - }; - }); - - return { - panelsJSON: JSON.stringify(explicitInputPanels), - references, - }; - }, - api: { - getSerializedStateForChild: (childId: string) => { - const controlPanelState = controlsPanelState[childId]; - return controlPanelState ? { rawState: controlPanelState } : undefined; - }, - children$: children$ as PublishingSubject<{ - [key: string]: DefaultControlApi; - }>, - getPanelCount: () => { - return controlsInOrder$.value.length; - }, - addNewPanel: async (panel: PanelPackage<DefaultControlState>) => { - return addNewPanel(panel, controlsInOrder$.value.length); - }, - removePanel, - replacePanel: async (panelId, newPanel) => { - const index = controlsInOrder$.value.findIndex(({ id }) => id === panelId); - removePanel(panelId); - const controlApi = await addNewPanel( - newPanel, - index >= 0 ? index : controlsInOrder$.value.length - ); - return controlApi ? controlApi.uuid : ''; - }, - untilInitialized: () => { - return new Promise((resolve) => { - children$ - .pipe( - first((children) => { - const atLeastOneControlNotInitialized = initialControlIds.some( - (controlId) => !children[controlId] - ); - return !atLeastOneControlNotInitialized; - }) - ) - .subscribe(() => { - resolve(); - }); - }); - }, - } as PresentationContainer & - HasSerializedChildState<ControlPanelState> & { untilInitialized: () => Promise<void> }, - }; -} diff --git a/examples/controls_example/public/react_controls/control_group/types.ts b/examples/controls_example/public/react_controls/control_group/types.ts deleted file mode 100644 index cfbd525dab704..0000000000000 --- a/examples/controls_example/public/react_controls/control_group/types.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ControlGroupChainingSystem } from '@kbn/controls-plugin/common/control_group/types'; -import { ParentIgnoreSettings } from '@kbn/controls-plugin/public'; -import { ControlStyle, ControlWidth } from '@kbn/controls-plugin/public/types'; -import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; -import { Filter } from '@kbn/es-query'; -import { HasSerializedChildState, PresentationContainer } from '@kbn/presentation-containers'; -import { - HasEditCapabilities, - HasParentApi, - PublishesDataLoading, - PublishesFilters, - PublishesTimeslice, - PublishesUnifiedSearch, - PublishesUnsavedChanges, - PublishingSubject, -} from '@kbn/presentation-publishing'; -import { PublishesDataViews } from '@kbn/presentation-publishing/interfaces/publishes_data_views'; -import { Observable } from 'rxjs'; -import { DefaultControlState, PublishesControlDisplaySettings } from '../types'; -import { ControlFetchContext } from './control_fetch/control_fetch'; - -/** The control display settings published by the control group are the "default" */ -type PublishesControlGroupDisplaySettings = PublishesControlDisplaySettings & { - labelPosition: PublishingSubject<ControlStyle>; -}; -export interface ControlPanelsState<ControlState extends ControlPanelState = ControlPanelState> { - [panelId: string]: ControlState; -} - -export type ControlGroupUnsavedChanges = Omit< - ControlGroupRuntimeState, - 'initialChildControlState' | 'defaultControlGrow' | 'defaultControlWidth' -> & { - filters: Filter[] | undefined; -}; - -export type ControlPanelState = DefaultControlState & { type: string; order: number }; - -export type ControlGroupApi = PresentationContainer & - DefaultEmbeddableApi<ControlGroupSerializedState, ControlGroupRuntimeState> & - PublishesFilters & - PublishesDataViews & - HasSerializedChildState<ControlPanelState> & - HasEditCapabilities & - PublishesDataLoading & - PublishesUnsavedChanges & - PublishesControlGroupDisplaySettings & - PublishesTimeslice & - Partial<HasParentApi<PublishesUnifiedSearch>> & { - autoApplySelections$: PublishingSubject<boolean>; - controlFetch$: (controlUuid: string) => Observable<ControlFetchContext>; - ignoreParentSettings$: PublishingSubject<ParentIgnoreSettings | undefined>; - untilInitialized: () => Promise<void>; - }; - -export interface ControlGroupRuntimeState { - chainingSystem: ControlGroupChainingSystem; - defaultControlGrow?: boolean; - defaultControlWidth?: ControlWidth; - labelPosition: ControlStyle; // TODO: Rename this type to ControlLabelPosition - autoApplySelections: boolean; - ignoreParentSettings?: ParentIgnoreSettings; - - initialChildControlState: ControlPanelsState<ControlPanelState>; - /** TODO: Handle the editor config, which is used with the control group renderer component */ - editorConfig?: { - hideDataViewSelector?: boolean; - hideWidthSettings?: boolean; - hideAdditionalSettings?: boolean; - }; -} - -export type ControlGroupEditorState = Pick< - ControlGroupRuntimeState, - 'chainingSystem' | 'labelPosition' | 'autoApplySelections' | 'ignoreParentSettings' ->; - -export type ControlGroupSerializedState = Omit< - ControlGroupRuntimeState, - | 'labelPosition' - | 'ignoreParentSettings' - | 'defaultControlGrow' - | 'defaultControlWidth' - | 'anyChildHasUnsavedChanges' - | 'initialChildControlState' - | 'autoApplySelections' -> & { - panelsJSON: string; - ignoreParentSettingsJSON: string; - // In runtime state, we refer to this property as `labelPosition`; - // to avoid migrations, we will continue to refer to this property as `controlStyle` in the serialized state - controlStyle: ControlStyle; - // In runtime state, we refer to the inverse of this property as `autoApplySelections` - // to avoid migrations, we will continue to refer to this property as `showApplySelections` in the serialized state - showApplySelections: boolean | undefined; -}; diff --git a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.ts b/examples/controls_example/public/react_controls/data_controls/initialize_data_control.ts deleted file mode 100644 index 1055047779f71..0000000000000 --- a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.ts +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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'; -import { BehaviorSubject, combineLatest, first, switchMap } from 'rxjs'; - -import { CoreStart } from '@kbn/core-lifecycle-browser'; -import { DataView, DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { Filter } from '@kbn/es-query'; -import { SerializedPanelState } from '@kbn/presentation-containers'; -import { StateComparators } from '@kbn/presentation-publishing'; - -import { i18n } from '@kbn/i18n'; -import { ControlGroupApi } from '../control_group/types'; -import { initializeDefaultControlApi } from '../initialize_default_control_api'; -import { ControlApiInitialization, ControlStateManager, DefaultControlState } from '../types'; -import { openDataControlEditor } from './open_data_control_editor'; -import { DataControlApi, DefaultDataControlState } from './types'; - -export const initializeDataControl = <EditorState extends object = {}>( - controlId: string, - controlType: string, - state: DefaultDataControlState, - editorStateManager: ControlStateManager<EditorState>, - controlGroup: ControlGroupApi, - services: { - core: CoreStart; - dataViews: DataViewsPublicPluginStart; - } -): { - api: ControlApiInitialization<DataControlApi>; - cleanup: () => void; - comparators: StateComparators<DefaultDataControlState>; - stateManager: ControlStateManager<DefaultDataControlState>; - serialize: () => SerializedPanelState<DefaultControlState>; - untilFiltersInitialized: () => Promise<void>; -} => { - const defaultControl = initializeDefaultControlApi(state); - - const panelTitle = new BehaviorSubject<string | undefined>(state.title); - const defaultPanelTitle = new BehaviorSubject<string | undefined>(undefined); - const dataViewId = new BehaviorSubject<string>(state.dataViewId); - const fieldName = new BehaviorSubject<string>(state.fieldName); - const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined); - const filters$ = new BehaviorSubject<Filter[] | undefined>(undefined); - - const stateManager: ControlStateManager<DefaultDataControlState> = { - ...defaultControl.stateManager, - dataViewId, - fieldName, - title: panelTitle, - }; - - function clearBlockingError() { - if (defaultControl.api.blockingError.value) { - defaultControl.api.setBlockingError(undefined); - } - } - - const dataViewIdSubscription = dataViewId - .pipe( - switchMap(async (currentDataViewId) => { - let dataView: DataView | undefined; - try { - dataView = await services.dataViews.get(currentDataViewId); - return { dataView }; - } catch (error) { - return { error }; - } - }) - ) - .subscribe(({ dataView, error }) => { - if (error) { - defaultControl.api.setBlockingError(error); - } else { - clearBlockingError(); - } - dataViews.next(dataView ? [dataView] : undefined); - }); - - const fieldNameSubscription = combineLatest([dataViews, fieldName]).subscribe( - ([nextDataViews, nextFieldName]) => { - const dataView = nextDataViews - ? nextDataViews.find(({ id }) => dataViewId.value === id) - : undefined; - if (!dataView) { - return; - } - - const field = dataView.getFieldByName(nextFieldName); - if (!field) { - defaultControl.api.setBlockingError( - new Error( - i18n.translate('controlsExamples.errors.fieldNotFound', { - defaultMessage: 'Could not locate field: {fieldName}', - values: { fieldName: nextFieldName }, - }) - ) - ); - } else { - clearBlockingError(); - } - defaultPanelTitle.next(field ? field.displayName || field.name : nextFieldName); - } - ); - - const onEdit = async () => { - // get the initial state from the state manager - const mergedStateManager = { - ...stateManager, - ...editorStateManager, - } as ControlStateManager<DefaultDataControlState & EditorState>; - const initialState = ( - Object.keys(mergedStateManager) as Array<keyof DefaultDataControlState & EditorState> - ).reduce((prev, key) => { - return { - ...prev, - [key]: mergedStateManager[key]?.getValue(), - }; - }, {} as DefaultDataControlState & EditorState); - - // open the editor to get the new state - openDataControlEditor<DefaultDataControlState & EditorState>({ - services, - onSave: ({ type: newType, state: newState }) => { - if (newType === controlType) { - // apply the changes from the new state via the state manager - (Object.keys(initialState) as Array<keyof DefaultDataControlState & EditorState>).forEach( - (key) => { - if (!isEqual(mergedStateManager[key].getValue(), newState[key])) { - mergedStateManager[key].next(newState[key]); - } - } - ); - } else { - // replace the control with a new one of the updated type - controlGroup.replacePanel(controlId, { panelType: newType, initialState: newState }); - } - }, - initialState: { - ...initialState, - controlType, - controlId, - defaultPanelTitle: defaultPanelTitle.getValue(), - }, - controlGroupApi: controlGroup, - }); - }; - - const api: ControlApiInitialization<DataControlApi> = { - ...defaultControl.api, - panelTitle, - defaultPanelTitle, - dataViews, - onEdit, - filters$, - setOutputFilter: (newFilter: Filter | undefined) => { - filters$.next(newFilter ? [newFilter] : undefined); - }, - isEditingEnabled: () => true, - }; - - return { - api, - cleanup: () => { - dataViewIdSubscription.unsubscribe(); - fieldNameSubscription.unsubscribe(); - }, - comparators: { - ...defaultControl.comparators, - title: [panelTitle, (value: string | undefined) => panelTitle.next(value)], - dataViewId: [dataViewId, (value: string) => dataViewId.next(value)], - fieldName: [fieldName, (value: string) => fieldName.next(value)], - }, - stateManager, - serialize: () => { - return { - rawState: { - ...defaultControl.serialize().rawState, - dataViewId: dataViewId.getValue(), - fieldName: fieldName.getValue(), - title: panelTitle.getValue(), - }, - references: [ - { - name: `controlGroup_${controlId}:${controlType}DataView`, - type: DATA_VIEW_SAVED_OBJECT_TYPE, - id: dataViewId.getValue(), - }, - ], - }; - }, - untilFiltersInitialized: async () => { - return new Promise((resolve) => { - combineLatest([defaultControl.api.blockingError, filters$]) - .pipe( - first( - ([blockingError, filters]) => - blockingError !== undefined || (filters?.length ?? 0) > 0 - ) - ) - .subscribe(() => { - resolve(); - }); - }); - }, - }; -}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx b/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx deleted file mode 100644 index 4db429ba7cb68..0000000000000 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.tsx +++ /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 and the Server Side Public License, v 1; you may not 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, useMemo } from 'react'; -import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; -import { buildRangeFilter, Filter, RangeFilterParams } from '@kbn/es-query'; -import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; -import { BehaviorSubject, combineLatest, map, skip } from 'rxjs'; -import { initializeDataControl } from '../initialize_data_control'; -import { DataControlFactory } from '../types'; -import { RangeSliderControl } from './components/range_slider_control'; -import { hasNoResults$ } from './has_no_results'; -import { minMax$ } from './min_max'; -import { RangeSliderStrings } from './range_slider_strings'; -import { - RangesliderControlApi, - RangesliderControlState, - RangeValue, - RANGE_SLIDER_CONTROL_TYPE, - Services, -} from './types'; - -export const getRangesliderControlFactory = ( - services: Services -): DataControlFactory<RangesliderControlState, RangesliderControlApi> => { - return { - type: RANGE_SLIDER_CONTROL_TYPE, - getIconType: () => 'controlsHorizontal', - getDisplayName: RangeSliderStrings.control.getDisplayName, - isFieldCompatible: (field) => { - return field.aggregatable && field.type === 'number'; - }, - CustomOptionsComponent: ({ currentState, updateState, setControlEditorValid }) => { - const step = currentState.step ?? 1; - return ( - <> - <EuiFormRow fullWidth label={RangeSliderStrings.editor.getStepTitle()}> - <EuiFieldNumber - value={step} - onChange={(event) => { - const newStep = event.target.valueAsNumber; - updateState({ step: newStep }); - setControlEditorValid(newStep > 0); - }} - min={0} - isInvalid={step === undefined || step <= 0} - data-test-subj="rangeSliderControl__stepAdditionalSetting" - /> - </EuiFormRow> - </> - ); - }, - buildControl: async (initialState, buildApi, uuid, controlGroupApi) => { - const controlFetch$ = controlGroupApi.controlFetch$(uuid); - const loadingMinMax$ = new BehaviorSubject<boolean>(false); - const loadingHasNoResults$ = new BehaviorSubject<boolean>(false); - const dataLoading$ = new BehaviorSubject<boolean | undefined>(undefined); - const step$ = new BehaviorSubject<number | undefined>(initialState.step ?? 1); - const value$ = new BehaviorSubject<RangeValue | undefined>(initialState.value); - function setValue(nextValue: RangeValue | undefined) { - value$.next(nextValue); - } - - const dataControl = initializeDataControl<Pick<RangesliderControlState, 'step' | 'value'>>( - uuid, - RANGE_SLIDER_CONTROL_TYPE, - initialState, - { - step: step$, - value: value$, - }, - controlGroupApi, - services - ); - - const api = buildApi( - { - ...dataControl.api, - dataLoading: dataLoading$, - getTypeDisplayName: RangeSliderStrings.control.getDisplayName, - serializeState: () => { - const { rawState: dataControlState, references } = dataControl.serialize(); - return { - rawState: { - ...dataControlState, - step: step$.getValue(), - value: value$.getValue(), - }, - references, // does not have any references other than those provided by the data control serializer - }; - }, - clearSelections: () => { - value$.next(undefined); - }, - }, - { - ...dataControl.comparators, - step: [step$, (nextStep: number | undefined) => step$.next(nextStep)], - value: [value$, setValue], - } - ); - - const dataLoadingSubscription = combineLatest([loadingMinMax$, loadingHasNoResults$]) - .pipe( - map((values) => { - return values.some((value) => { - return value; - }); - }) - ) - .subscribe((isLoading) => { - dataLoading$.next(isLoading); - }); - - // Clear state when the field changes - const fieldChangedSubscription = combineLatest([ - dataControl.stateManager.fieldName, - dataControl.stateManager.dataViewId, - ]) - .pipe(skip(1)) - .subscribe(() => { - step$.next(1); - value$.next(undefined); - }); - - const max$ = new BehaviorSubject<number | undefined>(undefined); - const min$ = new BehaviorSubject<number | undefined>(undefined); - const minMaxSubscription = minMax$({ - controlFetch$, - data: services.data, - dataViews$: dataControl.api.dataViews, - fieldName$: dataControl.stateManager.fieldName, - setIsLoading: (isLoading: boolean) => { - // clear previous loading error on next loading start - if (isLoading && dataControl.api.blockingError.value) { - dataControl.api.setBlockingError(undefined); - } - loadingMinMax$.next(isLoading); - }, - }).subscribe( - ({ - error, - min, - max, - }: { - error?: Error; - min: number | undefined; - max: number | undefined; - }) => { - if (error) { - dataControl.api.setBlockingError(error); - } - max$.next(max); - min$.next(min); - } - ); - - const outputFilterSubscription = combineLatest([ - dataControl.api.dataViews, - dataControl.stateManager.fieldName, - value$, - ]).subscribe(([dataViews, fieldName, value]) => { - const dataView = dataViews?.[0]; - const dataViewField = - dataView && fieldName ? dataView.getFieldByName(fieldName) : undefined; - const gte = parseFloat(value?.[0] ?? ''); - const lte = parseFloat(value?.[1] ?? ''); - - let rangeFilter: Filter | undefined; - if (value && dataView && dataViewField && !isNaN(gte) && !isNaN(lte)) { - const params = { - gte, - lte, - } as RangeFilterParams; - - rangeFilter = buildRangeFilter(dataViewField, params, dataView); - rangeFilter.meta.key = fieldName; - rangeFilter.meta.type = 'range'; - rangeFilter.meta.params = params; - } - api.setOutputFilter(rangeFilter); - }); - - const selectionHasNoResults$ = new BehaviorSubject(false); - const hasNotResultsSubscription = hasNoResults$({ - controlFetch$, - data: services.data, - dataViews$: dataControl.api.dataViews, - rangeFilters$: dataControl.api.filters$, - ignoreParentSettings$: controlGroupApi.ignoreParentSettings$, - setIsLoading: (isLoading: boolean) => { - loadingHasNoResults$.next(isLoading); - }, - }).subscribe((hasNoResults) => { - selectionHasNoResults$.next(hasNoResults); - }); - - if (initialState.value !== undefined) { - await dataControl.untilFiltersInitialized(); - } - - return { - api, - Component: ({ className: controlPanelClassName }) => { - const [dataLoading, dataViews, fieldName, max, min, selectionHasNotResults, step, value] = - useBatchedPublishingSubjects( - dataLoading$, - dataControl.api.dataViews, - dataControl.stateManager.fieldName, - max$, - min$, - selectionHasNoResults$, - step$, - value$ - ); - - useEffect(() => { - return () => { - dataLoadingSubscription.unsubscribe(); - fieldChangedSubscription.unsubscribe(); - hasNotResultsSubscription.unsubscribe(); - minMaxSubscription.unsubscribe(); - outputFilterSubscription.unsubscribe(); - }; - }, []); - - const fieldFormatter = useMemo(() => { - const dataView = dataViews?.[0]; - if (!dataView) { - return undefined; - } - const fieldSpec = dataView.getFieldByName(fieldName); - return fieldSpec - ? dataView.getFormatterForField(fieldSpec).getConverterFor('text') - : undefined; - }, [dataViews, fieldName]); - - return ( - <RangeSliderControl - controlPanelClassName={controlPanelClassName} - fieldFormatter={fieldFormatter} - isInvalid={selectionHasNotResults} - isLoading={typeof dataLoading === 'boolean' ? dataLoading : false} - max={max} - min={min} - onChange={setValue} - step={step} - value={value} - uuid={uuid} - /> - ); - }, - }; - }, - }; -}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/range_slider_strings.ts b/examples/controls_example/public/react_controls/data_controls/range_slider/range_slider_strings.ts deleted file mode 100644 index cdf64fee21fd5..0000000000000 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/range_slider_strings.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -export const RangeSliderStrings = { - control: { - getDisplayName: () => - i18n.translate('controlsExamples.rangeSliderControl.displayName', { - defaultMessage: 'Range slider', - }), - getInvalidSelectionWarningLabel: () => - i18n.translate('controlsExamples.rangeSlider.control.invalidSelectionWarningLabel', { - defaultMessage: 'Selected range returns no results.', - }), - }, - editor: { - getStepTitle: () => - i18n.translate('controlsExamples.rangeSlider.editor.stepSizeTitle', { - defaultMessage: 'Step size', - }), - }, - popover: { - getNoAvailableDataHelpText: () => - i18n.translate('controlsExamples.rangeSlider.popover.noAvailableDataHelpText', { - defaultMessage: 'There is no data to display. Adjust the time range and filters.', - }), - }, -}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/types.ts b/examples/controls_example/public/react_controls/data_controls/range_slider/types.ts deleted file mode 100644 index 2eb7f70d348e0..0000000000000 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/types.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CoreStart } from '@kbn/core/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DataControlApi, DefaultDataControlState } from '../types'; - -export const RANGE_SLIDER_CONTROL_TYPE = 'rangeSlider'; - -export type RangeValue = [string, string]; - -export interface RangesliderControlState extends DefaultDataControlState { - value?: RangeValue; - step?: number; -} - -export type RangesliderControlApi = DataControlApi; - -export interface Services { - core: CoreStart; - data: DataPublicPluginStart; - dataViews: DataViewsPublicPluginStart; -} diff --git a/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx b/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx deleted file mode 100644 index 232571b3c1256..0000000000000 --- a/examples/controls_example/public/react_controls/data_controls/search_control/get_search_control_factory.tsx +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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 } from 'react'; -import deepEqual from 'react-fast-compare'; -import { BehaviorSubject, combineLatest, debounceTime, distinctUntilChanged, skip } from 'rxjs'; - -import { EuiFieldSearch, EuiFormRow, EuiRadioGroup } from '@elastic/eui'; -import { css } from '@emotion/react'; -import { CoreStart } from '@kbn/core/public'; -import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { i18n } from '@kbn/i18n'; -import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; -import { euiThemeVars } from '@kbn/ui-theme'; - -import { initializeDataControl } from '../initialize_data_control'; -import { DataControlFactory } from '../types'; -import { - SearchControlApi, - SearchControlState, - SearchControlTechniques, - SEARCH_CONTROL_TYPE, -} from './types'; - -const allSearchOptions = [ - { - id: 'match', - label: i18n.translate('controlsExamples.searchControl.searchTechnique.match', { - defaultMessage: 'Fuzzy match', - }), - 'data-test-subj': 'searchControl__matchSearchOptionAdditionalSetting', - }, - { - id: 'simple_query_string', - label: i18n.translate('controlsExamples.searchControl.searchTechnique.simpleQueryString', { - defaultMessage: 'Query string', - }), - 'data-test-subj': 'optionsListControl__queryStringSearchOptionAdditionalSetting', - }, -]; - -const DEFAULT_SEARCH_TECHNIQUE = 'match'; - -export const getSearchControlFactory = ({ - core, - dataViewsService, -}: { - core: CoreStart; - dataViewsService: DataViewsPublicPluginStart; -}): DataControlFactory<SearchControlState, SearchControlApi> => { - return { - type: SEARCH_CONTROL_TYPE, - getIconType: () => 'search', - getDisplayName: () => - i18n.translate('controlsExamples.searchControl.displayName', { defaultMessage: 'Search' }), - isFieldCompatible: (field) => { - return ( - field.searchable && - field.spec.type === 'string' && - (field.spec.esTypes ?? []).includes('text') - ); - }, - CustomOptionsComponent: ({ currentState, updateState }) => { - const searchTechnique = currentState.searchTechnique ?? DEFAULT_SEARCH_TECHNIQUE; - return ( - <EuiFormRow label={'Searching'} data-test-subj="searchControl__searchOptionsRadioGroup"> - <EuiRadioGroup - options={allSearchOptions} - idSelected={searchTechnique} - onChange={(id) => { - const newSearchTechnique = id as SearchControlTechniques; - updateState({ searchTechnique: newSearchTechnique }); - }} - /> - </EuiFormRow> - ); - }, - buildControl: async (initialState, buildApi, uuid, parentApi) => { - const searchString = new BehaviorSubject<string | undefined>(initialState.searchString); - const searchTechnique = new BehaviorSubject<SearchControlTechniques | undefined>( - initialState.searchTechnique ?? DEFAULT_SEARCH_TECHNIQUE - ); - const editorStateManager = { searchTechnique }; - - const dataControl = initializeDataControl<Pick<SearchControlState, 'searchTechnique'>>( - uuid, - SEARCH_CONTROL_TYPE, - initialState, - editorStateManager, - parentApi, - { - core, - dataViews: dataViewsService, - } - ); - - const api = buildApi( - { - ...dataControl.api, - getTypeDisplayName: () => - i18n.translate('controlsExamples.searchControl.displayName', { - defaultMessage: 'Search', - }), - serializeState: () => { - const { rawState: dataControlState, references } = dataControl.serialize(); - return { - rawState: { - ...dataControlState, - searchString: searchString.getValue(), - searchTechnique: searchTechnique.getValue(), - }, - references, // does not have any references other than those provided by the data control serializer - }; - }, - clearSelections: () => { - searchString.next(undefined); - }, - }, - { - ...dataControl.comparators, - searchTechnique: [ - searchTechnique, - (newTechnique: SearchControlTechniques | undefined) => - searchTechnique.next(newTechnique), - ], - searchString: [ - searchString, - (newString: string | undefined) => - searchString.next(newString?.length === 0 ? undefined : newString), - ], - } - ); - - /** - * If either the search string or the search technique changes, recalulate the output filter - */ - const onSearchStringChanged = combineLatest([searchString, searchTechnique]) - .pipe(debounceTime(200), distinctUntilChanged(deepEqual)) - .subscribe(([newSearchString, currentSearchTechnnique]) => { - const currentDataView = dataControl.api.dataViews.getValue()?.[0]; - const currentField = dataControl.stateManager.fieldName.getValue(); - - if (currentDataView && currentField) { - if (newSearchString) { - api.setOutputFilter( - currentSearchTechnnique === 'match' - ? { - query: { match: { [currentField]: { query: newSearchString } } }, - meta: { index: currentDataView.id }, - } - : { - query: { - simple_query_string: { - query: newSearchString, - fields: [currentField], - default_operator: 'and', - }, - }, - meta: { index: currentDataView.id }, - } - ); - } else { - api.setOutputFilter(undefined); - } - } - }); - - /** - * When the field changes (which can happen if either the field name or the dataview id changes), - * clear the previous search string. - */ - const onFieldChanged = combineLatest([ - dataControl.stateManager.fieldName, - dataControl.stateManager.dataViewId, - ]) - .pipe(skip(1)) - .subscribe(() => { - searchString.next(undefined); - }); - - if (initialState.searchString?.length) { - await dataControl.untilFiltersInitialized(); - } - - return { - api, - /** - * The `controlPanelClassNamess` prop is necessary because it contains the class names from the generic - * ControlPanel that are necessary for styling - */ - Component: ({ className: controlPanelClassName }) => { - const currentSearch = useStateFromPublishingSubject(searchString); - - useEffect(() => { - return () => { - // cleanup on unmount - dataControl.cleanup(); - onSearchStringChanged.unsubscribe(); - onFieldChanged.unsubscribe(); - }; - }, []); - - return ( - <EuiFieldSearch - className={controlPanelClassName} - css={css` - height: calc(${euiThemeVars.euiButtonHeight} - 2px) !important; - `} - incremental={true} - isClearable={false} // this will be handled by the clear floating action instead - value={currentSearch ?? ''} - onChange={(event) => { - searchString.next(event.target.value); - }} - placeholder={i18n.translate('controls.searchControl.placeholder', { - defaultMessage: 'Search...', - })} - id={uuid} - fullWidth - /> - ); - }, - }; - }, - }; -}; diff --git a/examples/controls_example/public/react_controls/data_controls/search_control/types.tsx b/examples/controls_example/public/react_controls/data_controls/search_control/types.tsx deleted file mode 100644 index d0b2d43b69f48..0000000000000 --- a/examples/controls_example/public/react_controls/data_controls/search_control/types.tsx +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { DataControlApi, DefaultDataControlState } from '../types'; - -export const SEARCH_CONTROL_TYPE = 'searchControl'; - -export type SearchControlTechniques = 'match' | 'simple_query_string'; - -export interface SearchControlState extends DefaultDataControlState { - searchString?: string; - searchTechnique?: SearchControlTechniques; -} - -export type SearchControlApi = DataControlApi; diff --git a/examples/controls_example/public/react_controls/data_controls/types.ts b/examples/controls_example/public/react_controls/data_controls/types.ts deleted file mode 100644 index b3379889f4223..0000000000000 --- a/examples/controls_example/public/react_controls/data_controls/types.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { DataViewField } from '@kbn/data-views-plugin/common'; -import { Filter } from '@kbn/es-query'; -import { - HasEditCapabilities, - PublishesDataViews, - PublishesFilters, - PublishesPanelTitle, -} from '@kbn/presentation-publishing'; -import { ControlFactory, DefaultControlApi, DefaultControlState } from '../types'; - -export type DataControlApi = DefaultControlApi & - Omit<PublishesPanelTitle, 'hidePanelTitle'> & // control titles cannot be hidden - HasEditCapabilities & - PublishesDataViews & - PublishesFilters & { - setOutputFilter: (filter: Filter | undefined) => void; // a control should only ever output a **single** filter - }; - -export interface DataControlFactory< - State extends DefaultDataControlState = DefaultDataControlState, - Api extends DataControlApi = DataControlApi -> extends ControlFactory<State, Api> { - isFieldCompatible: (field: DataViewField) => boolean; - CustomOptionsComponent?: React.FC<{ - currentState: Partial<State>; - updateState: (newState: Partial<State>) => void; - setControlEditorValid: (valid: boolean) => void; - }>; -} - -export const isDataControlFactory = ( - factory: ControlFactory<object, any> -): factory is DataControlFactory<any, any> => { - return typeof (factory as DataControlFactory).isFieldCompatible === 'function'; -}; - -export interface DefaultDataControlState extends DefaultControlState { - dataViewId: string; - fieldName: string; - title?: string; // custom control label -} diff --git a/examples/controls_example/public/react_controls/timeslider_control/types.ts b/examples/controls_example/public/react_controls/timeslider_control/types.ts deleted file mode 100644 index 38dbf4572bbe2..0000000000000 --- a/examples/controls_example/public/react_controls/timeslider_control/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CoreStart } from '@kbn/core/public'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { PublishesTimeslice } from '@kbn/presentation-publishing'; -import type { DefaultControlApi, DefaultControlState } from '../types'; - -export const TIMESLIDER_CONTROL_TYPE = 'timesliderControl'; - -export type Timeslice = [number, number]; - -export interface TimesliderControlState extends DefaultControlState { - isAnchored?: boolean; - // Encode value as percentage of time range to support relative time ranges. - timesliceStartAsPercentageOfTimeRange?: number; - timesliceEndAsPercentageOfTimeRange?: number; -} - -export type TimesliderControlApi = DefaultControlApi & PublishesTimeslice; - -export interface Services { - core: CoreStart; - data: DataPublicPluginStart; -} diff --git a/examples/controls_example/public/react_controls/types.ts b/examples/controls_example/public/react_controls/types.ts deleted file mode 100644 index a9a7bd2e2c1a7..0000000000000 --- a/examples/controls_example/public/react_controls/types.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { BehaviorSubject } from 'rxjs'; - -import { CanClearSelections, ControlWidth } from '@kbn/controls-plugin/public/types'; -import { SerializedPanelState } from '@kbn/presentation-containers'; -import { PanelCompatibleComponent } from '@kbn/presentation-panel-plugin/public/panel_component/types'; -import { - HasParentApi, - HasType, - HasUniqueId, - PublishesBlockingError, - PublishesDataLoading, - PublishesDisabledActionIds, - PublishesPanelTitle, - PublishesUnsavedChanges, - PublishingSubject, - StateComparators, -} from '@kbn/presentation-publishing'; - -import { ControlGroupApi } from './control_group/types'; - -export interface PublishesControlDisplaySettings { - grow: PublishingSubject<boolean | undefined>; - width: PublishingSubject<ControlWidth | undefined>; -} - -export interface HasCustomPrepend { - CustomPrependComponent: React.FC<{}>; -} - -export type DefaultControlApi = PublishesDataLoading & - PublishesBlockingError & - PublishesUnsavedChanges & - PublishesControlDisplaySettings & - Partial<PublishesPanelTitle & PublishesDisabledActionIds & HasCustomPrepend> & - CanClearSelections & - HasType & - HasUniqueId & - HasParentApi<ControlGroupApi> & { - // Can not use HasSerializableState interface - // HasSerializableState types serializeState as function returning 'MaybePromise' - // Controls serializeState is sync - serializeState: () => SerializedPanelState<DefaultControlState>; - /** TODO: Make these non-public as part of https://github.com/elastic/kibana/issues/174961 */ - setDataLoading: (loading: boolean) => void; - setBlockingError: (error: Error | undefined) => void; - }; - -export interface DefaultControlState { - grow?: boolean; - width?: ControlWidth; -} - -export type ControlApiRegistration<ControlApi extends DefaultControlApi = DefaultControlApi> = Omit< - ControlApi, - 'uuid' | 'parentApi' | 'type' | 'unsavedChanges' | 'resetUnsavedChanges' ->; - -export type ControlApiInitialization<ControlApi extends DefaultControlApi = DefaultControlApi> = - Omit< - ControlApiRegistration<ControlApi>, - 'serializeState' | 'getTypeDisplayName' | 'clearSelections' - >; - -// TODO: Move this to the Control plugin's setup contract -export interface ControlFactory< - State extends DefaultControlState = DefaultControlState, - ControlApi extends DefaultControlApi = DefaultControlApi -> { - type: string; - getIconType: () => string; - getDisplayName: () => string; - buildControl: ( - initialState: State, - buildApi: ( - apiRegistration: ControlApiRegistration<ControlApi>, - comparators: StateComparators<State> - ) => ControlApi, - uuid: string, - parentApi: ControlGroupApi - ) => Promise<{ api: ControlApi; Component: React.FC<{ className: string }> }>; -} - -export type ControlStateManager<State extends object = object> = { - [key in keyof Required<State>]: BehaviorSubject<State[key]>; -}; - -export interface ControlPanelProps< - ApiType extends DefaultControlApi = DefaultControlApi, - PropsType extends {} = { className: string } -> { - uuid: string; - Component: PanelCompatibleComponent<ApiType, PropsType>; -} diff --git a/examples/controls_example/tsconfig.json b/examples/controls_example/tsconfig.json index 5ad45877cf0a7..d81aa3c1168fe 100644 --- a/examples/controls_example/tsconfig.json +++ b/examples/controls_example/tsconfig.json @@ -24,17 +24,7 @@ "@kbn/presentation-containers", "@kbn/presentation-publishing", "@kbn/ui-actions-plugin", - "@kbn/i18n-react", - "@kbn/shared-ux-markdown", - "@kbn/i18n", - "@kbn/core-mount-utils-browser", "@kbn/react-kibana-mount", - "@kbn/content-management-utils", - "@kbn/presentation-util-plugin", - "@kbn/core-lifecycle-browser", - "@kbn/presentation-panel-plugin", - "@kbn/datemath", - "@kbn/ui-theme", "@kbn/react-kibana-context-render", ] } diff --git a/examples/esql_ast_inspector/public/app.tsx b/examples/esql_ast_inspector/public/app.tsx index 4936544345f43..ac2a543bc40e5 100644 --- a/examples/esql_ast_inspector/public/app.tsx +++ b/examples/esql_ast_inspector/public/app.tsx @@ -18,6 +18,7 @@ import { EuiFormRow, EuiButton, } from '@elastic/eui'; +import { EuiProvider } from '@elastic/eui'; import type { CoreStart } from '@kbn/core/public'; @@ -42,48 +43,50 @@ export const App = (props: { core: CoreStart; plugins: StartDependencies }) => { }; return ( - <EuiPage> - <EuiPageBody style={{ maxWidth: 800, margin: '0 auto' }}> - <EuiPageHeader paddingSize="s" bottomBorder={true} pageTitle="ES|QL AST Inspector" /> - <EuiPageSection paddingSize="s"> - <p>This app gives you the AST for a particular ES|QL query.</p> + <EuiProvider> + <EuiPage> + <EuiPageBody style={{ maxWidth: 800, margin: '0 auto' }}> + <EuiPageHeader paddingSize="s" bottomBorder={true} pageTitle="ES|QL AST Inspector" /> + <EuiPageSection paddingSize="s"> + <p>This app gives you the AST for a particular ES|QL query.</p> - <EuiSpacer /> + <EuiSpacer /> - <EuiForm> - <EuiFormRow - fullWidth - label="Query" - isInvalid={Boolean(currentErrors.length)} - error={currentErrors.map((error) => error.message)} - > - <EuiTextArea - inputRef={(node) => { - inputRef.current = node; - }} - isInvalid={Boolean(currentErrors.length)} + <EuiForm> + <EuiFormRow fullWidth - value={currentQuery} - onChange={(e) => setQuery(e.target.value)} - css={{ - height: '5em', - }} - /> - </EuiFormRow> - <EuiFormRow fullWidth> - <EuiButton fullWidth onClick={() => parseQuery(inputRef.current?.value ?? '')}> - Parse - </EuiButton> - </EuiFormRow> - </EuiForm> - <EuiSpacer /> - <CodeEditor - allowFullScreen={true} - languageId={'json'} - value={JSON.stringify(ast, null, 2)} - /> - </EuiPageSection> - </EuiPageBody> - </EuiPage> + label="Query" + isInvalid={Boolean(currentErrors.length)} + error={currentErrors.map((error) => error.message)} + > + <EuiTextArea + inputRef={(node) => { + inputRef.current = node; + }} + isInvalid={Boolean(currentErrors.length)} + fullWidth + value={currentQuery} + onChange={(e) => setQuery(e.target.value)} + css={{ + height: '5em', + }} + /> + </EuiFormRow> + <EuiFormRow fullWidth> + <EuiButton fullWidth onClick={() => parseQuery(inputRef.current?.value ?? '')}> + Parse + </EuiButton> + </EuiFormRow> + </EuiForm> + <EuiSpacer /> + <CodeEditor + allowFullScreen={true} + languageId={'json'} + value={JSON.stringify(ast, null, 2)} + /> + </EuiPageSection> + </EuiPageBody> + </EuiPage> + </EuiProvider> ); }; diff --git a/examples/esql_validation_example/public/app.tsx b/examples/esql_validation_example/public/app.tsx index f785b31de3ba2..62b6405678d15 100644 --- a/examples/esql_validation_example/public/app.tsx +++ b/examples/esql_validation_example/public/app.tsx @@ -23,7 +23,7 @@ import { import type { CoreStart } from '@kbn/core/public'; -import { ESQLCallbacks, validateQuery } from '@kbn/esql-validation-autocomplete'; +import { ESQLCallbacks, ESQLRealField, validateQuery } from '@kbn/esql-validation-autocomplete'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import type { StartDependencies } from './plugin'; import { CodeSnippet } from './code_snippet'; @@ -52,10 +52,11 @@ export const App = (props: { core: CoreStart; plugins: StartDependencies }) => { ['index1', 'anotherIndex', 'dataStream'].map((name) => ({ name, hidden: false })) : undefined, getFieldsFor: callbacksEnabled.fields - ? async () => [ - { name: 'numberField', type: 'number' }, - { name: 'stringField', type: 'string' }, - ] + ? async () => + [ + { name: 'doubleField', type: 'double' }, + { name: 'keywordField', type: 'keyword' }, + ] as ESQLRealField[] : undefined, getPolicies: callbacksEnabled.policies ? async () => [ diff --git a/fleet_packages.json b/fleet_packages.json index d0ee7ad089924..1217c4f2ec861 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -30,7 +30,7 @@ }, { "name": "elastic_agent", - "version": "2.0.1" + "version": "2.0.2" }, { "name": "endpoint", @@ -56,6 +56,6 @@ }, { "name": "security_detection_engine", - "version": "8.15.1" + "version": "8.15.2" } ] \ No newline at end of file diff --git a/oas_docs/makefile b/oas_docs/makefile index aa6bd1af38223..6943921b1c6fe 100644 --- a/oas_docs/makefile +++ b/oas_docs/makefile @@ -15,22 +15,21 @@ .PHONY: api-docs api-docs: ## Generate kibana.serverless.yaml and kibana.yaml - @npx @redocly/cli join "kibana.info.serverless.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml" "../packages/core/saved-objects/docs/openapi/bundled_serverless.yaml" "../x-pack/plugins/observability_solution/slo/docs/openapi/slo/bundled.yaml" -o "output/kibana.serverless.yaml" "bundle.serverless.json" --prefix-components-with-info-prop title - @npx @redocly/cli join "kibana.info.yaml" "../x-pack/plugins/alerting/docs/openapi/bundled.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/cases/docs/openapi/bundled.yaml" "../x-pack/plugins/actions/docs/openapi/bundled.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis.yaml" "../packages/core/saved-objects/docs/openapi/bundled.yaml" "../x-pack/plugins/observability_solution/slo/docs/openapi/slo/bundled.yaml" "bundle.json" -o "output/kibana.yaml" --prefix-components-with-info-prop title + @npx @redocly/cli join "kibana.info.serverless.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml" "../packages/core/saved-objects/docs/openapi/bundled_serverless.yaml" "../x-pack/plugins/observability_solution/slo/docs/openapi/slo/bundled.yaml" "bundle.serverless.json" -o "output/kibana.serverless.yaml" --prefix-components-with-info-prop title + @npx @redocly/cli join "kibana.info.yaml" "../x-pack/plugins/alerting/docs/openapi/bundled.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/cases/docs/openapi/bundled.yaml" "../x-pack/plugins/actions/docs/openapi/bundled.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis.yaml" "../packages/core/saved-objects/docs/openapi/bundled.yaml" "bundle.json" -o "output/kibana.yaml" --prefix-components-with-info-prop title .PHONY: api-docs-stateful api-docs-stateful: ## Generate only kibana.yaml - @npx @redocly/cli join "kibana.info.yaml" "../x-pack/plugins/alerting/docs/openapi/bundled.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/cases/docs/openapi/bundled.yaml" "../x-pack/plugins/actions/docs/openapi/bundled.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis.yaml" "../packages/core/saved-objects/docs/openapi/bundled.yaml" "../x-pack/plugins/observability_solution/slo/docs/openapi/slo/bundled.yaml" "bundle.json" -o "output/kibana.yaml" --prefix-components-with-info-prop title + @npx @redocly/cli join "kibana.info.yaml" "../x-pack/plugins/alerting/docs/openapi/bundled.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/cases/docs/openapi/bundled.yaml" "../x-pack/plugins/actions/docs/openapi/bundled.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis.yaml" "../packages/core/saved-objects/docs/openapi/bundled.yaml" "bundle.json" -o "output/kibana.yaml" --prefix-components-with-info-prop title # Temporarily omit "../x-pack/plugins/fleet/common/openapi/bundled.yaml" due to internals tag and tag sorting .PHONY: api-docs-serverless api-docs-serverless: ## Generate only kibana.serverless.yaml - @npx @redocly/cli join "kibana.info.serverless.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml" "../packages/core/saved-objects/docs/openapi/bundled_serverless.yaml" "../x-pack/plugins/observability_solution/slo/docs/openapi/slo/bundled.yaml" -o "output/kibana.serverless.yaml" "bundle.serverless.json" --prefix-components-with-info-prop title + @npx @redocly/cli join "kibana.info.serverless.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml" "../packages/core/saved-objects/docs/openapi/bundled_serverless.yaml" "../x-pack/plugins/observability_solution/slo/docs/openapi/slo/bundled.yaml" "bundle.serverless.json" -o "output/kibana.serverless.yaml" --prefix-components-with-info-prop title .PHONY: api-docs-lint api-docs-lint: ## Run spectral API docs linter - @npx @stoplight/spectral-cli lint "output/kibana.serverless.yaml" --ruleset ".spectral.yaml" - @npx @stoplight/spectral-cli lint "output/kibana.yaml" --ruleset ".spectral.yaml" + @npx @stoplight/spectral-cli lint "output/*.yaml" --ruleset ".spectral.yaml" .PHONY: api-docs-lint-stateful api-docs-lint-stateful: ## Run spectral API docs linter on kibana.yaml diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 551ab3149dda9..a16a834c63e73 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -57,8 +57,6 @@ servers: variables: kibana_url: default: localhost:5601 - - url: http://localhost:5601 - description: local - url: http://localhost:5622 tags: - name: alerting @@ -94,9 +92,6 @@ tags: Manage Kibana saved objects, including dashboards, visualizations, and more. x-displayName: saved objects - - name: slo - description: SLO APIs enable you to define, manage and track service-level objectives - x-displayName: slo - name: system x-displayName: system paths: @@ -5497,453 +5492,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Saved_objects_400_response' - /s/{spaceId}/api/observability/slos: - post: - summary: Create an SLO - operationId: createSloOp - 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/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_create_slo_request' - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_create_slo_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '409': - description: Conflict - The SLO id already exists - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_409_response' - servers: - - url: https://localhost:5601 - get: - summary: Get a paginated list of SLOs - operationId: findSlosOp - description: > - You must have the `read` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - - name: kqlQuery - in: query - description: A valid kql query to filter the SLO with - schema: - type: string - example: 'slo.name:latency* and slo.tags : "prod"' - - name: page - in: query - description: The page to use for pagination, must be greater or equal than 1 - schema: - type: integer - default: 1 - example: 1 - - name: perPage - in: query - description: Number of SLOs returned by page - schema: - type: integer - default: 25 - maximum: 5000 - example: 25 - - name: sortBy - in: query - description: Sort by field - schema: - type: string - enum: - - sli_value - - status - - error_budget_consumed - - error_budget_remaining - default: status - example: status - - name: sortDirection - in: query - description: Sort order - schema: - type: string - enum: - - asc - - desc - default: asc - example: asc - - name: hideStale - in: query - description: >- - Hide stale SLOs from the list as defined by stale SLO threshold in - SLO settings - schema: - type: boolean - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_find_slo_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_404_response' - /s/{spaceId}/api/observability/slos/{sloId}: - get: - summary: Get an SLO - operationId: getSloOp - description: > - You must have the `read` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - - $ref: '#/components/parameters/SLOs_slo_id' - - name: instanceId - in: query - description: the specific instanceId used by the summary calculation - schema: - type: string - example: host-abcde - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_slo_with_summary_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_404_response' - put: - summary: Update an SLO - operationId: updateSloOp - description: > - You must have the `write` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - - $ref: '#/components/parameters/SLOs_slo_id' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_update_slo_request' - responses: - '200': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_slo_definition_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_404_response' - delete: - summary: Delete an SLO - operationId: deleteSloOp - description: > - You must have the `write` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - - $ref: '#/components/parameters/SLOs_slo_id' - responses: - '204': - description: Successful request - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_404_response' - /s/{spaceId}/api/observability/slos/{sloId}/enable: - post: - summary: Enable an SLO - operationId: enableSloOp - description: > - You must have the `write` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - - $ref: '#/components/parameters/SLOs_slo_id' - responses: - '204': - description: Successful request - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_404_response' - /s/{spaceId}/api/observability/slos/{sloId}/disable: - post: - summary: Disable an SLO - operationId: disableSloOp - description: > - You must have the `write` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - - $ref: '#/components/parameters/SLOs_slo_id' - responses: - '200': - description: Successful request - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_404_response' - /s/{spaceId}/api/observability/slos/{sloId}/_reset: - post: - summary: Reset an SLO - operationId: resetSloOp - description: > - You must have the `write` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - - $ref: '#/components/parameters/SLOs_slo_id' - responses: - '204': - description: Successful request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_slo_definition_response' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - '404': - description: Not found response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_404_response' - /s/{spaceId}/api/observability/slos/_delete_instances: - post: - summary: Batch delete rollup and summary data - operationId: deleteSloInstancesOp - description: > - The deletion occurs for the specified list of `sloId` and `instanceId`. - You must have `all` privileges for the **SLOs** feature in the - **Observability** section of the Kibana feature privileges. - tags: - - slo - parameters: - - $ref: '#/components/parameters/SLOs_kbn_xsrf' - - $ref: '#/components/parameters/SLOs_space_id' - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_delete_slo_instances_request' - responses: - '204': - description: Successful request - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_400_response' - '401': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_401_response' - '403': - description: Unauthorized response - content: - application/json: - schema: - $ref: '#/components/schemas/SLOs_403_response' - servers: - - url: https://localhost:5601 /api/status: get: operationId: /api/status#0 @@ -6382,31 +5930,6 @@ components: required: true schema: type: string - SLOs_kbn_xsrf: - schema: - type: string - in: header - name: kbn-xsrf - description: Cross-site request forgery protection - required: true - SLOs_space_id: - in: path - name: spaceId - description: >- - An identifier for the space. If `/s/` and the identifier are omitted - from the path, the default space is used. - required: true - schema: - type: string - example: default - SLOs_slo_id: - in: path - name: sloId - description: An identifier for the slo. - required: true - schema: - type: string - example: 9c235211-6834-11ea-a78c-6feb38a34414 schemas: Alerting_create_anomaly_detection_alert_rule_request: title: Create anomaly detection rule request @@ -16367,1332 +15890,144 @@ components: data-frame-analytics: type: object description: >- - If saved objects are missing for data frame analytics jobs, they are - created. - additionalProperties: - $ref: >- - #/components/schemas/Machine_learning_APIs_mlSyncResponseDataFrameAnalytics - trained-model: - type: object - description: If saved objects are missing for trained models, they are created. - additionalProperties: - $ref: >- - #/components/schemas/Machine_learning_APIs_mlSyncResponseTrainedModels - Machine_learning_APIs_mlSyncResponseSavedObjectsDeleted: - type: object - title: Sync API response for deleted saved objects - description: >- - If saved objects exist for machine learning jobs or trained models that - no longer exist, they are deleted when you run the sync machine learning - saved objects API. - properties: - anomaly-detector: - type: object - description: >- - If there are saved objects exist for nonexistent anomaly detection - jobs, they are deleted. - additionalProperties: - $ref: >- - #/components/schemas/Machine_learning_APIs_mlSyncResponseAnomalyDetectors - data-frame-analytics: - type: object - description: >- - If there are saved objects exist for nonexistent data frame - analytics jobs, they are deleted. - additionalProperties: - $ref: >- - #/components/schemas/Machine_learning_APIs_mlSyncResponseDataFrameAnalytics - trained-model: - type: object - description: >- - If there are saved objects exist for nonexistent trained models, - they are deleted. - additionalProperties: - $ref: >- - #/components/schemas/Machine_learning_APIs_mlSyncResponseTrainedModels - Machine_learning_APIs_mlSyncResponseTrainedModels: - type: object - title: Sync API response for trained models - description: >- - The sync machine learning saved objects API response contains this - object when there are trained models affected by the synchronization. - There is an object for each relevant trained model, which contains the - synchronization status. - properties: - success: - $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseSuccess' - Machine_learning_APIs_mlSync200Response: - type: object - title: Successful sync API response - properties: - datafeedsAdded: - type: object - description: >- - If a saved object for an anomaly detection job is missing a datafeed - identifier, it is added when you run the sync machine learning saved - objects API. - additionalProperties: - $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseDatafeeds' - datafeedsRemoved: - type: object - description: >- - If a saved object for an anomaly detection job references a datafeed - that no longer exists, it is deleted when you run the sync machine - learning saved objects API. - additionalProperties: - $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseDatafeeds' - savedObjectsCreated: - $ref: >- - #/components/schemas/Machine_learning_APIs_mlSyncResponseSavedObjectsCreated - savedObjectsDeleted: - $ref: >- - #/components/schemas/Machine_learning_APIs_mlSyncResponseSavedObjectsDeleted - Machine_learning_APIs_mlSync4xxResponse: - type: object - title: Unsuccessful sync API response - properties: - error: - type: string - example: Unauthorized - message: - type: string - statusCode: - type: integer - example: 401 - Saved_objects_400_response: - title: Bad request - type: object - required: - - error - - message - - statusCode - properties: - error: - type: string - enum: - - Bad Request - message: - type: string - statusCode: - type: integer - enum: - - 400 - Saved_objects_attributes: - type: object - description: > - The data that you want to create. WARNING: When you create saved - objects, attributes are not validated, which allows you to pass - arbitrary and ill-formed data into the API that can break Kibana. Make - sure any data that you send to the API is properly formed. - Saved_objects_initial_namespaces: - type: array - description: > - Identifiers for the spaces in which this object is created. If this is - provided, the object is created only in the explicitly defined spaces. - If this is not provided, the object is created in the current space - (default behavior). For shareable object types (registered with - `namespaceType: 'multiple'`), this option can be used to specify one or - more spaces, including the "All spaces" identifier ('*'). For isolated - object types (registered with `namespaceType: 'single'` or - `namespaceType: 'multiple-isolated'`), this option can only be used to - specify a single space, and the "All spaces" identifier ('*') is not - allowed. For global object types (`registered with `namespaceType: - agnostic`), this option cannot be used. - Saved_objects_references: - type: array - description: > - Objects with `name`, `id`, and `type` properties that describe the other - saved objects that this object references. Use `name` in attributes to - refer to the other saved object, but never the `id`, which can update - automatically during migrations or import and export. - SLOs_indicator_properties_apm_availability: - title: APM availability - required: - - type - - params - description: Defines properties for the APM availability indicator type - type: object - properties: - params: - description: An object containing the indicator parameters. - type: object - nullable: false - required: - - service - - environment - - transactionType - - transactionName - - index - properties: - service: - description: The APM service name - type: string - example: o11y-app - environment: - description: The APM service environment or "*" - type: string - example: production - transactionType: - description: The APM transaction type or "*" - type: string - example: request - transactionName: - description: The APM transaction name or "*" - type: string - example: GET /my/api - filter: - description: KQL query used for filtering the data - type: string - example: 'service.foo : "bar"' - index: - description: The index used by APM metrics - type: string - example: metrics-apm*,apm* - type: - description: The type of indicator. - type: string - example: sli.apm.transactionDuration - SLOs_filter_meta: - title: FilterMeta - description: Defines properties for a filter - type: object - properties: - alias: - type: string - nullable: true - disabled: - type: boolean - negate: - type: boolean - controlledBy: - type: string - group: - type: string - index: - type: string - isMultiIndex: - type: boolean - type: - type: string - key: - type: string - params: - type: object - value: - type: string - field: - type: string - SLOs_filter: - title: Filter - description: Defines properties for a filter - type: object - properties: - query: - type: object - meta: - $ref: '#/components/schemas/SLOs_filter_meta' - SLOs_kql_with_filters: - title: KQL with filters - description: Defines properties for a filter - oneOf: - - description: the KQL query to filter the documents with. - type: string - example: 'field.environment : "production" and service.name : "my-service"' - - type: object - properties: - kqlQuery: - type: string - filters: - type: array - items: - $ref: '#/components/schemas/SLOs_filter' - SLOs_kql_with_filters_good: - title: KQL query for good events - description: The KQL query used to define the good events. - oneOf: - - description: the KQL query to filter the documents with. - type: string - example: 'request.latency <= 150 and request.status_code : "2xx"' - - type: object - properties: - kqlQuery: - type: string - filters: - type: array - items: - $ref: '#/components/schemas/SLOs_filter' - SLOs_kql_with_filters_total: - title: KQL query for all events - description: The KQL query used to define all events. - oneOf: - - description: the KQL query to filter the documents with. - type: string - example: 'field.environment : "production" and service.name : "my-service"' - - type: object - properties: - kqlQuery: - type: string - filters: - type: array - items: - $ref: '#/components/schemas/SLOs_filter' - SLOs_indicator_properties_custom_kql: - title: Custom Query - required: - - type - - params - description: Defines properties for a custom query indicator type - type: object - properties: - params: - description: An object containing the indicator parameters. - type: object - nullable: false - required: - - index - - timestampField - - good - - total - properties: - index: - description: The index or index pattern to use - type: string - example: my-service-* - dataViewId: - description: >- - The kibana data view id to use, primarily used to include data - view runtime mappings. Make sure to save SLO again if you - add/update run time fields to the data view and if those fields - are being used in slo queries. - type: string - example: 03b80ab3-003d-498b-881c-3beedbaf1162 - filter: - $ref: '#/components/schemas/SLOs_kql_with_filters' - good: - $ref: '#/components/schemas/SLOs_kql_with_filters_good' - total: - $ref: '#/components/schemas/SLOs_kql_with_filters_total' - timestampField: - description: | - The timestamp field used in the source indice. - type: string - example: timestamp - type: - description: The type of indicator. - type: string - example: sli.kql.custom - SLOs_indicator_properties_apm_latency: - title: APM latency - required: - - type - - params - description: Defines properties for the APM latency indicator type - type: object - properties: - params: - description: An object containing the indicator parameters. - type: object - nullable: false - required: - - service - - environment - - transactionType - - transactionName - - index - - threshold - properties: - service: - description: The APM service name - type: string - example: o11y-app - environment: - description: The APM service environment or "*" - type: string - example: production - transactionType: - description: The APM transaction type or "*" - type: string - example: request - transactionName: - description: The APM transaction name or "*" - type: string - example: GET /my/api - filter: - description: KQL query used for filtering the data - type: string - example: 'service.foo : "bar"' - index: - description: The index used by APM metrics - type: string - example: metrics-apm*,apm* - threshold: - description: The latency threshold in milliseconds - type: number - example: 250 - type: - description: The type of indicator. - type: string - example: sli.apm.transactionDuration - SLOs_indicator_properties_custom_metric: - title: Custom metric - required: - - type - - params - description: Defines properties for a custom metric indicator type - type: object - properties: - params: - description: An object containing the indicator parameters. - type: object - nullable: false - required: - - index - - timestampField - - good - - total - properties: - index: - description: The index or index pattern to use - type: string - example: my-service-* - dataViewId: - description: >- - The kibana data view id to use, primarily used to include data - view runtime mappings. Make sure to save SLO again if you - add/update run time fields to the data view and if those fields - are being used in slo queries. - type: string - example: 03b80ab3-003d-498b-881c-3beedbaf1162 - filter: - description: the KQL query to filter the documents with. - type: string - example: 'field.environment : "production" and service.name : "my-service"' - timestampField: - description: | - The timestamp field used in the source indice. - type: string - example: timestamp - good: - description: | - An object defining the "good" metrics and equation - type: object - required: - - metrics - - equation - properties: - metrics: - description: >- - List of metrics with their name, aggregation type, and - field. - type: array - items: - type: object - required: - - name - - aggregation - - field - properties: - name: - description: The name of the metric. Only valid options are A-Z - type: string - example: A - pattern: ^[A-Z]$ - aggregation: - description: >- - The aggregation type of the metric. Only valid option - is "sum" - type: string - example: sum - enum: - - sum - field: - description: The field of the metric. - type: string - example: processor.processed - filter: - description: The filter to apply to the metric. - type: string - example: 'processor.outcome: "success"' - equation: - description: The equation to calculate the "good" metric. - type: string - example: A - total: - description: | - An object defining the "total" metrics and equation - type: object - required: - - metrics - - equation - properties: - metrics: - description: >- - List of metrics with their name, aggregation type, and - field. - type: array - items: - type: object - required: - - name - - aggregation - - field - properties: - name: - description: The name of the metric. Only valid options are A-Z - type: string - example: A - pattern: ^[A-Z]$ - aggregation: - description: >- - The aggregation type of the metric. Only valid option - is "sum" - type: string - example: sum - enum: - - sum - field: - description: The field of the metric. - type: string - example: processor.processed - filter: - description: The filter to apply to the metric. - type: string - example: 'processor.outcome: *' - equation: - description: The equation to calculate the "total" metric. - type: string - example: A - type: - description: The type of indicator. - type: string - example: sli.metric.custom - SLOs_indicator_properties_histogram: - title: Histogram indicator - required: - - type - - params - description: Defines properties for a histogram indicator type - type: object - properties: - params: - description: An object containing the indicator parameters. - type: object - nullable: false - required: - - index - - timestampField - - good - - total - properties: - index: - description: The index or index pattern to use - type: string - example: my-service-* - dataViewId: - description: >- - The kibana data view id to use, primarily used to include data - view runtime mappings. Make sure to save SLO again if you - add/update run time fields to the data view and if those fields - are being used in slo queries. - type: string - example: 03b80ab3-003d-498b-881c-3beedbaf1162 - filter: - description: the KQL query to filter the documents with. - type: string - example: 'field.environment : "production" and service.name : "my-service"' - timestampField: - description: | - The timestamp field used in the source indice. - type: string - example: timestamp - good: - description: | - An object defining the "good" events - type: object - required: - - aggregation - - field - properties: - field: - description: The field use to aggregate the good events. - type: string - example: processor.latency - aggregation: - description: The type of aggregation to use. - type: string - example: value_count - enum: - - value_count - - range - filter: - description: The filter for good events. - type: string - example: 'processor.outcome: "success"' - from: - description: >- - The starting value of the range. Only required for "range" - aggregations. - type: number - example: 0 - to: - description: >- - The ending value of the range. Only required for "range" - aggregations. - type: number - example: 100 - total: - description: | - An object defining the "total" events - type: object - required: - - aggregation - - field - properties: - field: - description: The field use to aggregate the good events. - type: string - example: processor.latency - aggregation: - description: The type of aggregation to use. - type: string - example: value_count - enum: - - value_count - - range - filter: - description: The filter for total events. - type: string - example: 'processor.outcome : *' - from: - description: >- - The starting value of the range. Only required for "range" - aggregations. - type: number - example: 0 - to: - description: >- - The ending value of the range. Only required for "range" - aggregations. - type: number - example: 100 - type: - description: The type of indicator. - type: string - example: sli.histogram.custom - SLOs_timeslice_metric_basic_metric_with_field: - title: Timeslice Metric Basic Metric with Field - required: - - name - - aggregation - - field - type: object - properties: - name: - description: The name of the metric. Only valid options are A-Z - type: string - example: A - pattern: ^[A-Z]$ - aggregation: - description: The aggregation type of the metric. - type: string - example: sum - enum: - - sum - - avg - - min - - max - - std_deviation - - last_value - - cardinality - field: - description: The field of the metric. - type: string - example: processor.processed - filter: - description: The filter to apply to the metric. - type: string - example: 'processor.outcome: "success"' - SLOs_timeslice_metric_percentile_metric: - title: Timeslice Metric Percentile Metric - required: - - name - - aggregation - - field - - percentile - type: object - properties: - name: - description: The name of the metric. Only valid options are A-Z - type: string - example: A - pattern: ^[A-Z]$ - aggregation: - description: >- - The aggregation type of the metric. Only valid option is - "percentile" - type: string - example: percentile - enum: - - percentile - field: - description: The field of the metric. - type: string - example: processor.processed - percentile: - description: The percentile value. - type: number - example: 95 - filter: - description: The filter to apply to the metric. - type: string - example: 'processor.outcome: "success"' - SLOs_timeslice_metric_doc_count_metric: - title: Timeslice Metric Doc Count Metric - required: - - name - - aggregation - type: object - properties: - name: - description: The name of the metric. Only valid options are A-Z - type: string - example: A - pattern: ^[A-Z]$ - aggregation: - description: The aggregation type of the metric. Only valid option is "doc_count" - type: string - example: doc_count - enum: - - doc_count - filter: - description: The filter to apply to the metric. - type: string - example: 'processor.outcome: "success"' - SLOs_indicator_properties_timeslice_metric: - title: Timeslice metric - required: - - type - - params - description: Defines properties for a timeslice metric indicator type - type: object - properties: - params: - description: An object containing the indicator parameters. - type: object - nullable: false - required: - - index - - timestampField - - metric - properties: - index: - description: The index or index pattern to use - type: string - example: my-service-* - dataViewId: - description: >- - The kibana data view id to use, primarily used to include data - view runtime mappings. Make sure to save SLO again if you - add/update run time fields to the data view and if those fields - are being used in slo queries. - type: string - example: 03b80ab3-003d-498b-881c-3beedbaf1162 - filter: - description: the KQL query to filter the documents with. - type: string - example: 'field.environment : "production" and service.name : "my-service"' - timestampField: - description: | - The timestamp field used in the source indice. - type: string - example: timestamp - metric: - description: > - An object defining the metrics, equation, and threshold to - determine if it's a good slice or not - type: object - required: - - metrics - - equation - - comparator - - threshold - properties: - metrics: - description: >- - List of metrics with their name, aggregation type, and - field. - type: array - items: - anyOf: - - $ref: >- - #/components/schemas/SLOs_timeslice_metric_basic_metric_with_field - - $ref: >- - #/components/schemas/SLOs_timeslice_metric_percentile_metric - - $ref: >- - #/components/schemas/SLOs_timeslice_metric_doc_count_metric - equation: - description: The equation to calculate the metric. - type: string - example: A - comparator: - description: >- - The comparator to use to compare the equation to the - threshold. - type: string - example: GT - enum: - - GT - - GTE - - LT - - LTE - threshold: - description: >- - The threshold used to determine if the metric is a good - slice or not. - type: number - example: 100 - type: - description: The type of indicator. - type: string - example: sli.metric.timeslice - SLOs_time_window: - title: Time window - required: - - duration - - type - description: Defines properties for the SLO time window - type: object - properties: - duration: - description: >- - the duration formatted as {duration}{unit}. Accepted values for - rolling: 7d, 30d, 90d. Accepted values for calendar aligned: 1w - (weekly) or 1M (monthly) - type: string - example: 30d - type: - description: >- - Indicates weither the time window is a rolling or a calendar aligned - time window. - type: string - example: rolling - enum: - - rolling - - calendarAligned - SLOs_budgeting_method: - title: Budgeting method - type: string - description: The budgeting method to use when computing the rollup data. - enum: - - occurrences - - timeslices - example: occurrences - SLOs_objective: - title: Objective - required: - - target - description: Defines properties for the SLO objective - type: object - properties: - target: - description: the target objective between 0 and 1 excluded - type: number - minimum: 0 - maximum: 100 - exclusiveMinimum: true - exclusiveMaximum: true - example: 0.99 - timesliceTarget: - description: >- - the target objective for each slice when using a timeslices - budgeting method - type: number - minimum: 0 - maximum: 100 - example: 0.995 - timesliceWindow: - description: >- - the duration of each slice when using a timeslices budgeting method, - as {duraton}{unit} - type: string - example: 5m - SLOs_settings: - title: Settings - description: Defines properties for SLO settings. - type: object - properties: - syncDelay: - description: The synch delay to apply to the transform. Default 1m - type: string - default: 1m - example: 5m - frequency: - description: Configure how often the transform runs, default 1m - type: string - default: 1m - example: 5m - preventInitialBackfill: - description: Prevents the transform from backfilling data when it starts. - type: boolean - default: false - example: true - SLOs_summary_status: - title: summary status - type: string - enum: - - NO_DATA - - HEALTHY - - DEGRADING - - VIOLATED - example: HEALTHY - SLOs_error_budget: - title: Error budget - type: object - required: - - initial - - consumed - - remaining - - isEstimated - properties: - initial: - type: number - description: The initial error budget, as 1 - objective - example: 0.02 - consumed: - type: number - description: The error budget consummed, as a percentage of the initial value. - example: 0.8 - remaining: - type: number - description: The error budget remaining, as a percentage of the initial value. - example: 0.2 - isEstimated: - type: boolean - description: >- - Only for SLO defined with occurrences budgeting method and calendar - aligned time window. - example: true - SLOs_summary: - title: Summary - type: object - description: The SLO computed data - required: - - status - - sliValue - - errorBudget - properties: - status: - $ref: '#/components/schemas/SLOs_summary_status' - sliValue: - type: number - example: 0.9836 - errorBudget: - $ref: '#/components/schemas/SLOs_error_budget' - SLOs_slo_with_summary_response: - title: SLO response - type: object - required: - - id - - name - - description - - indicator - - timeWindow - - budgetingMethod - - objective - - settings - - revision - - summary - - enabled - - groupBy - - instanceId - - tags - - createdAt - - updatedAt - - version - properties: - id: - description: The identifier of the SLO. - type: string - example: 8853df00-ae2e-11ed-90af-09bb6422b258 - name: - description: The name of the SLO. - type: string - example: My Service SLO - description: - description: The description of the SLO. - type: string - example: My SLO description - indicator: - discriminator: - propertyName: type - mapping: - sli.apm.transactionErrorRate: '#/components/schemas/SLOs_indicator_properties_apm_availability' - sli.kql.custom: '#/components/schemas/SLOs_indicator_properties_custom_kql' - sli.apm.transactionDuration: '#/components/schemas/SLOs_indicator_properties_apm_latency' - sli.metric.custom: '#/components/schemas/SLOs_indicator_properties_custom_metric' - sli.histogram.custom: '#/components/schemas/SLOs_indicator_properties_histogram' - sli.metric.timeslice: '#/components/schemas/SLOs_indicator_properties_timeslice_metric' - oneOf: - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_kql' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_availability' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_latency' - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_metric' - - $ref: '#/components/schemas/SLOs_indicator_properties_histogram' - - $ref: '#/components/schemas/SLOs_indicator_properties_timeslice_metric' - timeWindow: - $ref: '#/components/schemas/SLOs_time_window' - budgetingMethod: - $ref: '#/components/schemas/SLOs_budgeting_method' - objective: - $ref: '#/components/schemas/SLOs_objective' - settings: - $ref: '#/components/schemas/SLOs_settings' - revision: - description: The SLO revision - type: number - example: 2 - summary: - $ref: '#/components/schemas/SLOs_summary' - enabled: - description: Indicate if the SLO is enabled - type: boolean - example: true - groupBy: - description: optional group by field to use to generate an SLO per distinct value - type: string - example: some.field - instanceId: - description: the value derived from the groupBy field, if present, otherwise '*' - type: string - example: host-abcde - tags: - description: List of tags - type: array - items: - type: string - createdAt: - description: The creation date - type: string - example: '2023-01-12T10:03:19.000Z' - updatedAt: - description: The last update date - type: string - example: '2023-01-12T10:03:19.000Z' - version: - description: The internal SLO version - type: number - example: 2 - SLOs_find_slo_response: - title: Find SLO response - description: | - A paginated response of SLOs matching the query. + If saved objects are missing for data frame analytics jobs, they are + created. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseDataFrameAnalytics + trained-model: + type: object + description: If saved objects are missing for trained models, they are created. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseTrainedModels + Machine_learning_APIs_mlSyncResponseSavedObjectsDeleted: type: object + title: Sync API response for deleted saved objects + description: >- + If saved objects exist for machine learning jobs or trained models that + no longer exist, they are deleted when you run the sync machine learning + saved objects API. properties: - page: - type: number - example: 1 - perPage: - type: number - example: 25 - total: - type: number - example: 34 - results: - type: array - items: - $ref: '#/components/schemas/SLOs_slo_with_summary_response' - SLOs_400_response: - title: Bad request + anomaly-detector: + type: object + description: >- + If there are saved objects exist for nonexistent anomaly detection + jobs, they are deleted. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseAnomalyDetectors + data-frame-analytics: + type: object + description: >- + If there are saved objects exist for nonexistent data frame + analytics jobs, they are deleted. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseDataFrameAnalytics + trained-model: + type: object + description: >- + If there are saved objects exist for nonexistent trained models, + they are deleted. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseTrainedModels + Machine_learning_APIs_mlSyncResponseTrainedModels: type: object - required: - - statusCode - - error - - message + title: Sync API response for trained models + description: >- + The sync machine learning saved objects API response contains this + object when there are trained models affected by the synchronization. + There is an object for each relevant trained model, which contains the + synchronization status. properties: - statusCode: - type: number - example: 400 - error: - type: string - example: Bad Request - message: - type: string - example: 'Invalid value ''foo'' supplied to: [...]' - SLOs_401_response: - title: Unauthorized + success: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseSuccess' + Machine_learning_APIs_mlSync200Response: type: object - required: - - statusCode - - error - - message + title: Successful sync API response properties: - statusCode: - type: number - example: 401 - error: - type: string - example: Unauthorized - message: - type: string - example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" - SLOs_403_response: - title: Unauthorized + datafeedsAdded: + type: object + description: >- + If a saved object for an anomaly detection job is missing a datafeed + identifier, it is added when you run the sync machine learning saved + objects API. + additionalProperties: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseDatafeeds' + datafeedsRemoved: + type: object + description: >- + If a saved object for an anomaly detection job references a datafeed + that no longer exists, it is deleted when you run the sync machine + learning saved objects API. + additionalProperties: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseDatafeeds' + savedObjectsCreated: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseSavedObjectsCreated + savedObjectsDeleted: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseSavedObjectsDeleted + Machine_learning_APIs_mlSync4xxResponse: type: object - required: - - statusCode - - error - - message + title: Unsuccessful sync API response properties: - statusCode: - type: number - example: 403 error: type: string example: Unauthorized message: type: string - example: "[security_exception\n\tRoot causes:\n\t\tsecurity_exception: unable to authenticate user [elastics] for REST request [/_security/_authenticate]]: unable to authenticate user [elastics] for REST request [/_security/_authenticate]" - SLOs_404_response: - title: Not found - type: object - required: - - statusCode - - error - - message - properties: statusCode: - type: number - example: 404 - error: - type: string - example: Not Found - message: - type: string - example: SLO [3749f390-03a3-11ee-8139-c7ff60a1692d] not found - SLOs_create_slo_request: - title: Create SLO request - description: > - The create SLO API request body varies depending on the type of - indicator, time window and budgeting method. - type: object - required: - - name - - description - - indicator - - timeWindow - - budgetingMethod - - objective - properties: - id: - description: >- - A optional and unique identifier for the SLO. Must be between 8 and - 36 chars - type: string - example: my-super-slo-id - name: - description: A name for the SLO. - type: string - description: - description: A description for the SLO. - type: string - indicator: - oneOf: - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_kql' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_availability' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_latency' - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_metric' - - $ref: '#/components/schemas/SLOs_indicator_properties_histogram' - - $ref: '#/components/schemas/SLOs_indicator_properties_timeslice_metric' - timeWindow: - $ref: '#/components/schemas/SLOs_time_window' - budgetingMethod: - $ref: '#/components/schemas/SLOs_budgeting_method' - objective: - $ref: '#/components/schemas/SLOs_objective' - settings: - $ref: '#/components/schemas/SLOs_settings' - groupBy: - description: optional group by field to use to generate an SLO per distinct value - type: string - example: some.field - tags: - description: List of tags - type: array - items: - type: string - SLOs_create_slo_response: - title: Create SLO response - type: object - required: - - id - properties: - id: - type: string - example: 8853df00-ae2e-11ed-90af-09bb6422b258 - SLOs_409_response: - title: Conflict + type: integer + example: 401 + Saved_objects_400_response: + title: Bad request type: object required: - - statusCode - error - message + - statusCode properties: - statusCode: - type: number - example: 409 error: type: string - example: Conflict + enum: + - Bad Request message: type: string - example: SLO [d077e940-1515-11ee-9c50-9d096392f520] already exists - SLOs_update_slo_request: - title: Update SLO request - description: > - The update SLO API request body varies depending on the type of - indicator, time window and budgeting method. Partial update is handled. - type: object - properties: - name: - description: A name for the SLO. - type: string - description: - description: A description for the SLO. - type: string - indicator: - oneOf: - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_kql' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_availability' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_latency' - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_metric' - - $ref: '#/components/schemas/SLOs_indicator_properties_histogram' - - $ref: '#/components/schemas/SLOs_indicator_properties_timeslice_metric' - timeWindow: - $ref: '#/components/schemas/SLOs_time_window' - budgetingMethod: - $ref: '#/components/schemas/SLOs_budgeting_method' - objective: - $ref: '#/components/schemas/SLOs_objective' - settings: - $ref: '#/components/schemas/SLOs_settings' - tags: - description: List of tags - type: array - items: - type: string - SLOs_slo_definition_response: - title: SLO definition response + statusCode: + type: integer + enum: + - 400 + Saved_objects_attributes: type: object - required: - - id - - name - - description - - indicator - - timeWindow - - budgetingMethod - - objective - - settings - - revision - - enabled - - groupBy - - tags - - createdAt - - updatedAt - - version - properties: - id: - description: The identifier of the SLO. - type: string - example: 8853df00-ae2e-11ed-90af-09bb6422b258 - name: - description: The name of the SLO. - type: string - example: My Service SLO - description: - description: The description of the SLO. - type: string - example: My SLO description - indicator: - discriminator: - propertyName: type - mapping: - sli.apm.transactionErrorRate: '#/components/schemas/SLOs_indicator_properties_apm_availability' - sli.kql.custom: '#/components/schemas/SLOs_indicator_properties_custom_kql' - sli.apm.transactionDuration: '#/components/schemas/SLOs_indicator_properties_apm_latency' - sli.metric.custom: '#/components/schemas/SLOs_indicator_properties_custom_metric' - sli.histogram.custom: '#/components/schemas/SLOs_indicator_properties_histogram' - sli.metric.timeslice: '#/components/schemas/SLOs_indicator_properties_timeslice_metric' - oneOf: - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_kql' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_availability' - - $ref: '#/components/schemas/SLOs_indicator_properties_apm_latency' - - $ref: '#/components/schemas/SLOs_indicator_properties_custom_metric' - - $ref: '#/components/schemas/SLOs_indicator_properties_histogram' - - $ref: '#/components/schemas/SLOs_indicator_properties_timeslice_metric' - timeWindow: - $ref: '#/components/schemas/SLOs_time_window' - budgetingMethod: - $ref: '#/components/schemas/SLOs_budgeting_method' - objective: - $ref: '#/components/schemas/SLOs_objective' - settings: - $ref: '#/components/schemas/SLOs_settings' - revision: - description: The SLO revision - type: number - example: 2 - enabled: - description: Indicate if the SLO is enabled - type: boolean - example: true - groupBy: - description: optional group by field to use to generate an SLO per distinct value - type: string - example: some.field - tags: - description: List of tags - type: array - items: - type: string - createdAt: - description: The creation date - type: string - example: '2023-01-12T10:03:19.000Z' - updatedAt: - description: The last update date - type: string - example: '2023-01-12T10:03:19.000Z' - version: - description: The internal SLO version - type: number - example: 2 - SLOs_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 + The data that you want to create. WARNING: When you create saved + objects, attributes are not validated, which allows you to pass + arbitrary and ill-formed data into the API that can break Kibana. Make + sure any data that you send to the API is properly formed. + Saved_objects_initial_namespaces: + type: array + description: > + Identifiers for the spaces in which this object is created. If this is + provided, the object is created only in the explicitly defined spaces. + If this is not provided, the object is created in the current space + (default behavior). For shareable object types (registered with + `namespaceType: 'multiple'`), this option can be used to specify one or + more spaces, including the "All spaces" identifier ('*'). For isolated + object types (registered with `namespaceType: 'single'` or + `namespaceType: 'multiple-isolated'`), this option can only be used to + specify a single space, and the "All spaces" identifier ('*') is not + allowed. For global object types (`registered with `namespaceType: + agnostic`), this option cannot be used. + Saved_objects_references: + type: array + description: > + Objects with `name`, `id`, and `type` properties that describe the other + saved objects that this object references. Use `name` in attributes to + refer to the other saved object, but never the `id`, which can update + automatically during migrations or import and export. Kibana_HTTP_APIs_core_status_redactedResponse: additionalProperties: false description: A minimal representation of Kibana's operational status. @@ -22131,9 +20466,6 @@ x-tagGroups: - name: Saved objects tags: - saved objects - - name: SLOs - tags: - - slo - name: Kibana HTTP APIs tags: - system diff --git a/oas_docs/overlays/kibana.overlays.serverless.yaml b/oas_docs/overlays/kibana.overlays.serverless.yaml index 359d0d4bddac8..6ed2329c24dca 100644 --- a/oas_docs/overlays/kibana.overlays.serverless.yaml +++ b/oas_docs/overlays/kibana.overlays.serverless.yaml @@ -4,6 +4,7 @@ info: title: Overlays for the Kibana API document version: 0.0.1 actions: + # Clean up server definitions - target: '$.servers.*' description: Remove all servers so we can add our own. remove: true @@ -14,6 +15,11 @@ actions: variables: kibana_url: default: localhost:5601 + # Remove operation-level security definitions + - target: "$.paths['/api/status']['get'].security" + description: Remove system security definitions + remove: true + # Add a document-level security definition - target: '$.components.securitySchemes' description: Add an API key security scheme update: @@ -27,4 +33,41 @@ actions: update: security: - apiKeyAuth: [] - + # Mark all operations as beta + - target: '$.paths[*][*]' + description: Add x-beta + update: + x-beta: true + # Add some tag descriptions and displayNames + - target: '$.tags[?(@.name=="connectors")]' + description: Change tag description and displayName + update: + description: > + Connectors provide a central place to store connection information for services and integrations with Elastic or third party systems. + Alerting rules can use connectors to run actions when rule conditions are met. + externalDocs: + description: Connector documentation + url: https://www.elastic.co/docs/current/serverless/action-connectors + x-displayName: "Connectors" + - target: '$.tags[?(@.name=="data views")]' + description: Change displayName + update: + x-displayName: "Data views" + - target: '$.tags[?(@.name=="ml")]' + description: Change displayName + update: + x-displayName: "Machine learning" + - target: '$.tags[?(@.name=="saved objects")]' + description: Change displayName + update: + x-displayName: "Saved objects" + - target: '$.tags[?(@.name=="slo")]' + description: Change displayName + update: + x-displayName: "Service level objectives" + - target: '$.tags[?(@.name=="system")]' + description: Change displayName + update: + x-displayName: "System" + description: > + Get information about the system status, resource usage, and installed plugins. \ No newline at end of file diff --git a/oas_docs/overlays/kibana.overlays.yaml b/oas_docs/overlays/kibana.overlays.yaml index 7681a7201872a..22162721c6867 100644 --- a/oas_docs/overlays/kibana.overlays.yaml +++ b/oas_docs/overlays/kibana.overlays.yaml @@ -4,6 +4,7 @@ info: title: Overlays for the Kibana API document version: 0.0.1 actions: + # Clean up server definitions - target: '$.servers.*' description: Remove all servers so we can add our own. remove: true @@ -14,6 +15,11 @@ actions: variables: kibana_url: default: localhost:5601 + # Remove operation-level security definitions + - target: "$.paths['/api/status']['get'].security" + description: Remove system security definitions + remove: true + # Add document-level security definitions - target: '$.components.securitySchemes' description: Add an API key security scheme update: @@ -34,6 +40,7 @@ actions: security: - apiKeyAuth: [] - basicAuth: [] + # Add an introduction to spaces - target: '$' description: Add an extra page about spaces update: @@ -53,3 +60,58 @@ actions: If you use the Kibana console to send API requests, it automatically adds the appropriate space identifier. To learn more, check out [Spaces](https://www.elastic.co/guide/en/kibana/current/xpack-spaces.html). + # Add some tag descriptions and displayNames + - target: '$.tags[?(@.name=="alerting")]' + description: Change tag description and displayName + update: + description: > + Alerting enables you to define rules, which detect complex conditions within your data. + When a condition is met, the rule tracks it as an alert and runs the actions that are defined in the rule. + Actions typically involve the use of connectors to interact with Kibana services or third party integrations. + externalDocs: + description: Alerting documentation + url: https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html + x-displayName: "Alerting" + - target: '$.tags[?(@.name=="cases")]' + description: Change tag description and displayName + update: + description: > + Cases are used to open and track issues. + You can add assignees and tags to your cases, set their severity and status, and add alerts, comments, and visualizations. + You can also send cases to external incident management systems by configuring connectors. + externalDocs: + description: Cases documentation + url: https://www.elastic.co/guide/en/kibana/current/cases.html + x-displayName: "Cases" + - target: '$.tags[?(@.name=="connectors")]' + description: Change tag description and displayName + update: + description: > + Connectors provide a central place to store connection information for services and integrations with Elastic or third party systems. + Alerting rules can use connectors to run actions when rule conditions are met. + externalDocs: + description: Connector documentation + url: https://www.elastic.co/guide/en/kibana/current/action-types.html + x-displayName: "Connectors" + - target: '$.tags[?(@.name=="data views")]' + description: Change displayName + update: + x-displayName: "Data views" + - target: '$.tags[?(@.name=="ml")]' + description: Change displayName + update: + x-displayName: "Machine learning" + - target: '$.tags[?(@.name=="saved objects")]' + description: Change displayName + update: + x-displayName: "Saved objects" + # - target: '$.tags[?(@.name=="slo")]' + # description: Change displayName + # update: + # x-displayName: "Service level objectives" + - target: '$.tags[?(@.name=="system")]' + description: Change displayName + update: + x-displayName: "System" + description: > + Get information about the system status, resource usage, and installed plugins. diff --git a/package.json b/package.json index 2c2e3f5a21820..2864dd7970bf6 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "resolutions": { "**/@bazel/typescript/protobufjs": "6.11.4", "**/@hello-pangea/dnd": "16.6.0", - "**/@langchain/core": "^0.2.17", + "**/@langchain/core": "^0.2.18", "**/@types/node": "20.10.5", "**/@typescript-eslint/utils": "5.62.0", "**/chokidar": "^3.5.3", @@ -88,7 +88,7 @@ "**/globule/minimatch": "^3.1.2", "**/hoist-non-react-statics": "^3.3.2", "**/isomorphic-fetch/node-fetch": "^2.6.7", - "**/langchain": "^0.2.10", + "**/langchain": "^0.2.11", "**/react-intl/**/@types/react": "^17.0.45", "**/remark-parse/trim": "1.0.1", "**/sharp": "0.32.6", @@ -110,10 +110,11 @@ "@elastic/apm-rum-react": "^2.0.3", "@elastic/charts": "66.1.0", "@elastic/datemath": "5.0.3", + "@elastic/ebt": "1.0.0", "@elastic/ecs": "^8.11.1", "@elastic/elasticsearch": "^8.14.0", "@elastic/ems-client": "8.5.3", - "@elastic/eui": "95.4.0", + "@elastic/eui": "95.6.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", @@ -180,10 +181,10 @@ "@kbn/apm-data-access-plugin": "link:x-pack/plugins/observability_solution/apm_data_access", "@kbn/apm-data-view": "link:packages/kbn-apm-data-view", "@kbn/apm-plugin": "link:x-pack/plugins/observability_solution/apm", + "@kbn/apm-types": "link:packages/kbn-apm-types", "@kbn/apm-utils": "link:packages/kbn-apm-utils", "@kbn/app-link-test-plugin": "link:test/plugin_functional/plugins/app_link_test", "@kbn/application-usage-test-plugin": "link:x-pack/test/usage_collection/plugins/application_usage_test", - "@kbn/assets-data-access-plugin": "link:x-pack/plugins/observability_solution/assets_data_access", "@kbn/audit-log-plugin": "link:x-pack/test/security_api_integration/plugins/audit_log", "@kbn/avc-banner": "link:packages/kbn-avc-banner", "@kbn/banners-plugin": "link:x-pack/plugins/banners", @@ -436,7 +437,6 @@ "@kbn/discover-utils": "link:packages/kbn-discover-utils", "@kbn/doc-links": "link:packages/kbn-doc-links", "@kbn/dom-drag-drop": "link:packages/kbn-dom-drag-drop", - "@kbn/ebt": "link:packages/analytics/ebt", "@kbn/ebt-tools": "link:packages/kbn-ebt-tools", "@kbn/ecs-data-quality-dashboard": "link:x-pack/packages/security-solution/ecs_data_quality_dashboard", "@kbn/ecs-data-quality-dashboard-plugin": "link:x-pack/plugins/ecs_data_quality_dashboard", @@ -452,6 +452,7 @@ "@kbn/embedded-lens-example-plugin": "link:x-pack/examples/embedded_lens_example", "@kbn/encrypted-saved-objects-plugin": "link:x-pack/plugins/encrypted_saved_objects", "@kbn/enterprise-search-plugin": "link:x-pack/plugins/enterprise_search", + "@kbn/entities-data-access-plugin": "link:x-pack/plugins/observability_solution/entities_data_access", "@kbn/entities-schema": "link:x-pack/packages/kbn-entities-schema", "@kbn/entityManager-plugin": "link:x-pack/plugins/observability_solution/entity_manager", "@kbn/error-boundary-example-plugin": "link:examples/error_boundary", @@ -540,6 +541,7 @@ "@kbn/index-management": "link:x-pack/packages/index-management", "@kbn/index-management-plugin": "link:x-pack/plugins/index_management", "@kbn/index-patterns-test-plugin": "link:test/plugin_functional/plugins/index_patterns", + "@kbn/inference-plugin": "link:x-pack/plugins/inference", "@kbn/inference_integration_flyout": "link:x-pack/packages/ml/inference_integration_flyout", "@kbn/infra-forge": "link:x-pack/packages/kbn-infra-forge", "@kbn/infra-plugin": "link:x-pack/plugins/observability_solution/infra", @@ -789,6 +791,7 @@ "@kbn/securitysolution-utils": "link:packages/kbn-securitysolution-utils", "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", + "@kbn/server-route-repository-client": "link:packages/kbn-server-route-repository-client", "@kbn/server-route-repository-utils": "link:packages/kbn-server-route-repository-utils", "@kbn/serverless": "link:x-pack/plugins/serverless", "@kbn/serverless-common-settings": "link:packages/serverless/settings/common", @@ -868,6 +871,7 @@ "@kbn/status-plugin-b-plugin": "link:test/server_integration/plugins/status_plugin_b", "@kbn/std": "link:packages/kbn-std", "@kbn/synthetics-plugin": "link:x-pack/plugins/observability_solution/synthetics", + "@kbn/synthetics-private-location": "link:x-pack/packages/kbn-synthetics-private-location", "@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", "@kbn/task-manager-plugin": "link:x-pack/plugins/task_manager", @@ -949,12 +953,12 @@ "@kbn/zod": "link:packages/kbn-zod", "@kbn/zod-helpers": "link:packages/kbn-zod-helpers", "@langchain/community": "0.2.18", - "@langchain/core": "^0.2.17", + "@langchain/core": "^0.2.18", "@langchain/google-genai": "^0.0.23", - "@langchain/langgraph": "^0.0.29", + "@langchain/langgraph": "^0.0.31", "@langchain/openai": "^0.1.3", "@langtrase/trace-attributes": "^3.0.8", - "@launchdarkly/node-server-sdk": "^9.4.7", + "@launchdarkly/node-server-sdk": "^9.5.0", "@loaders.gl/core": "^3.4.7", "@loaders.gl/json": "^3.4.7", "@loaders.gl/shapefile": "^3.4.7", @@ -1043,7 +1047,7 @@ "deepmerge": "^4.2.2", "del": "^6.1.0", "diff": "^5.1.0", - "elastic-apm-node": "^4.7.1", + "elastic-apm-node": "^4.7.2", "email-addresses": "^5.0.0", "eventsource-parser": "^1.1.1", "execa": "^5.1.1", @@ -1093,8 +1097,8 @@ "jsonwebtoken": "^9.0.2", "jsts": "^1.6.2", "kea": "^2.6.0", - "langchain": "^0.2.10", - "langsmith": "^0.1.37", + "langchain": "^0.2.11", + "langsmith": "^0.1.39", "launchdarkly-js-client-sdk": "^3.4.0", "launchdarkly-node-server-sdk": "^7.0.3", "load-json-file": "^6.2.0", @@ -1141,14 +1145,14 @@ "pretty-ms": "6.0.0", "prop-types": "^15.8.1", "proxy-from-env": "1.0.0", - "puppeteer": "22.8.1", + "puppeteer": "22.13.1", "query-string": "^6.13.2", "rbush": "^3.0.1", "re-resizable": "^6.9.9", "re2js": "0.4.1", "react": "^17.0.2", "react-ace": "^7.0.5", - "react-diff-view": "^3.2.0", + "react-diff-view": "^3.2.1", "react-dom": "^17.0.2", "react-dropzone": "^4.2.9", "react-fast-compare": "^2.0.4", @@ -1183,14 +1187,14 @@ "remark-gfm": "1.0.0", "remark-parse-no-trim": "^8.0.4", "remark-stringify": "^8.0.3", - "require-in-the-middle": "^7.3.0", + "require-in-the-middle": "^7.4.0", "reselect": "^4.1.8", "resize-observer-polyfill": "1.5.1", "rison-node": "1.0.2", "rxjs": "^7.5.5", "safe-squel": "^5.12.5", "seedrandom": "^3.0.5", - "semver": "^7.5.4", + "semver": "^7.6.3", "set-value": "^4.1.0", "snakecase-keys": "^8.0.0", "source-map-support": "^0.5.19", @@ -1430,7 +1434,7 @@ "@mapbox/vector-tile": "1.3.1", "@octokit/rest": "^17.11.2", "@parcel/watcher": "^2.1.0", - "@redocly/cli": "^1.18.1", + "@redocly/cli": "^1.19.0", "@statoscope/webpack-plugin": "^5.28.2", "@storybook/addon-a11y": "^6.5.16", "@storybook/addon-actions": "^6.5.16", @@ -1570,7 +1574,7 @@ "@types/resolve": "^1.20.1", "@types/seedrandom": ">=2.0.0 <4.0.0", "@types/selenium-webdriver": "^4.1.23", - "@types/semver": "^7", + "@types/semver": "^7.5.8", "@types/set-value": "^2.0.0", "@types/sinon": "^7.0.13", "@types/source-map-support": "^0.5.3", @@ -1647,6 +1651,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-ban": "^1.6.0", "eslint-plugin-cypress": "^2.15.1", + "eslint-plugin-depend": "^0.9.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-formatjs": "^4.12.2", "eslint-plugin-import": "^2.28.0", @@ -1696,7 +1701,7 @@ "json5": "^2.2.3", "jsondiffpatch": "0.4.1", "license-checker": "^25.0.1", - "listr2": "^8.2.3", + "listr2": "^8.2.4", "lmdb": "^2.9.2", "loader-utils": "^2.0.4", "marge": "^1.0.1", @@ -1710,7 +1715,7 @@ "mochawesome-merge": "^4.3.0", "mock-fs": "^5.1.2", "ms-chromium-edge-driver": "^0.5.1", - "msw": "^2.3.4", + "msw": "^2.3.5", "multistream": "^4.1.0", "mutation-observer": "^1.0.3", "native-hdr-histogram": "^1.0.0", @@ -1735,7 +1740,6 @@ "postcss-scss": "^4.0.4", "prettier": "^2.8.8", "proxy": "^2.1.1", - "q": "^1.5.1", "raw-loader": "^3.1.0", "react-test-renderer": "^17.0.2", "recast": "^0.23.9", @@ -1759,7 +1763,7 @@ "svgo": "^2.8.0", "table": "^6.8.1", "tape": "^5.0.1", - "terser": "^5.31.2", + "terser": "^5.31.3", "terser-webpack-plugin": "^4.2.3", "tough-cookie": "^4.1.4", "tree-kill": "^1.2.2", diff --git a/packages/analytics/ebt/README.md b/packages/analytics/ebt/README.md deleted file mode 100644 index 8758a90304cba..0000000000000 --- a/packages/analytics/ebt/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# @kbn/ebt/* - -This module implements the Analytics client used for Event-Based Telemetry. The intention of the client is to be usable on both: the UI and the Server sides. - -## Client - -`@kbn/ebt/client` holds the public APIs to report events, enrich the events' context and set up the transport mechanisms. Refer to the [client's docs](./client/README.md) for more information. - -## Prebuilt shippers - -Elastic-approved shippers are available as `@kbn/ebt/shippers/*` packages. Refer to the [shippers' docs](./shippers/README.md) for more information. diff --git a/packages/analytics/ebt/client/README.md b/packages/analytics/ebt/client/README.md deleted file mode 100644 index 508fc984c1a58..0000000000000 --- a/packages/analytics/ebt/client/README.md +++ /dev/null @@ -1,348 +0,0 @@ -# @kbn/ebt/client - -This module implements the Analytics client used for Event-Based Telemetry. The intention of the client is to be usable on both: the UI and the Server sides. - -## How to use it - -It all starts by creating the client with the `createAnalytics` API: - -```typescript -import { createAnalytics } from '@kbn/ebt/client'; - -const analytics = createAnalytics({ - // Set to `true` when running in developer mode. - // It enables development helpers like schema validation and extra debugging features. - isDev: false, - // Set to `staging` if you don't want your events to be sent to the production cluster. Useful for CI & QA environments. - sendTo: 'production', - // The application's instrumented logger - logger, -}); -``` - -### Reporting events - -Reporting events is as simple as calling the `reportEvent` API every time your application needs to track an event: - -```typescript -analytics.reportEvent('my_unique_event_name', myEventProperties); -``` - -But first, it requires a setup phase where the application must declare the event and the structure of the `eventProperties`: - -```typescript -analytics.registerEventType({ - eventType: 'my_unique_event_name', - schema: { - my_keyword: { - type: 'keyword', - _meta: { - description: 'Represents the key property...' - } - }, - my_number: { - type: 'long', - _meta: { - description: 'Indicates the number of times...', - optional: true - } - }, - my_complex_unknown_meta_object: { - type: 'pass_through', - _meta: { - description: 'Unknown object that contains the key-values...' - } - }, - my_array_of_str: { - type: 'array', - items: { - type: 'text', - _meta: { - description: 'List of tags...' - } - } - }, - my_object: { - properties: { - my_timestamp: { - type: 'date', - _meta: { - description: 'timestamp when the user...' - } - } - } - }, - my_array_of_objects: { - type: 'array', - items: { - properties: { - my_bool_prop: { - type: 'boolean', - _meta: { - description: '`true` when...' - } - } - } - } - } - } -}); -``` - -For more information about how to declare the schemas, refer to the section [Schema definition](#schema-definition). - -### Enriching events - -Context is important! For that reason, the client internally appends the timestamp in which the event was generated and any additional context provided by the Context Providers. To register a context provider use the `registerContextProvider` API: - -```typescript -analytics.registerContextProvider({ - name: 'my_context_provider', - // RxJS Observable that emits every time the context changes. For example: a License changes from `basic` to `trial`. - context$, - // Similar to the `reportEvent` API, schema defining the structure of the expected output of the context$ observable. - schema, -}) -``` - -### Setting the user's opt-in consent - -The client cannot send any data until the user provides consent. At the beginning, the client will internally enqueue any incoming events until the consent is either granted or refused. - -To set the user's selection use the `optIn` API: - -```typescript -analytics.optIn({ - global: { - enabled: true, // The user granted consent - shippers: { - shipperA: false, // Shipper A is explicitly disabled for all events - } - }, - event_types: { - my_unique_event_name: { - enabled: true, // The consent is explictly granted to send this type of event (only if global === true) - shippers: { - shipperB: false, // Shipper B is not allowed to report this event. - } - }, - my_other_event_name: { - enabled: false, // The consent is not granted to send this type of event. - } - } -}) -``` - -### Explicit flush of the events - -If, at any given point (usually testing or during shutdowns) we need to make sure that all the pending events -in the queue are sent. The `flush` API returns a promise that will resolve as soon as all events in the queue are sent. - -```typescript -await analytics.flush() -``` - -### Shipping events - -In order to report the event to an analytics tool, we need to register the shippers our application wants to use. To register a shipper use the API `registerShipper`: - -```typescript -analytics.registerShipper(ShipperClass, shipperOptions); -``` - -There are some prebuilt shippers in this package that can be enabled using the API above. Additionally, each application can register their own custom shippers. - -#### Prebuilt shippers - -Refer to the [shippers' documentation](../shippers/README.md) for more information. - -#### Custom shippers - -To use your own shipper, you just need to implement and register it!: - -```typescript -import type { - AnalyticsClientInitContext, - Event, - EventContext, - IShipper, - TelemetryCounter -} from '@kbn/ebt/client'; - -class MyVeryOwnShipper implements IShipper { - constructor(myOptions: MyOptions, initContext: AnalyticsClientInitContext) { - // ... - } - - public reportEvents(events: Event[]): void { - // Send the events to the analytics platform - } - public optIn(isOptedIn: boolean): void { - // Start/stop any sending mechanisms - } - - public extendContext(newContext: EventContext): void { - // Call any custom APIs to internally set the context - } - - // Emit any success/failed/dropped activity - public telemetryCounter$: Observable<TelemetryCounter>; -} - -// Register the custom shipper -analytics.registerShipper(MyVeryOwnShipper, myOptions); -``` - -### Schema definition - -Schemas are a framework that allows us to document the structure of the events that our application will report. It is useful to understand the meaning of the events that we report. And, at the same time, it serves as an extra validation step from the developer's point of view. - -The syntax of a schema is a _simplified ES mapping on steroids_: it removes some of the ES mapping complexity, and at the same time, it includes features that are specific to the telemetry collection. - -**DISCLAIMER:** **The schema is not a direct mapping to ES indices.** The final structure of how the event is stored will depend on many factors like the context providers, shippers and final analytics solution. - -#### Schema Specification: Primitive data types (`string`, `number`, `boolean`) - -When declaring primitive values like `string` or `number`, the basic schema must contain both: `type` and `_meta`. - -The `type` value depends on the type of the content to report in that field. Refer to the table below for the values allowed in the schema `type`: - -| Typescript `type` | Schema `type` | -|:-----------------:|:-----------------------:| -| `boolean` | `boolean` | -| `string` | `keyword` | -| `string` | `text` | -| `string` | `date` (for ISO format) | -| `number` | `date` (for ms format) | -| `number` | `byte` | -| `number` | `short` | -| `number` | `integer` | -| `number` | `long` | -| `number` | `double` | -| `number` | `float` | - -```typescript -const stringSchema: SchemaValue<string> = { - type: 'text', - _meta: { - description: 'Description of the feature that was broken', - optional: false, - }, -} -``` - -For the `_meta`, refer to [Schema Specification: `_meta`](#schema-specification-_meta). - -#### Schema Specification: Objects - -Declaring the schema of an object contains 2 main attributes: `properties` and an optional `_meta`: - -The `properties` attribute is an object with all the keys that the original object may include: - -```typescript -interface MyObject { - an_id: string; - a_description: string; - a_number?: number; - a_boolean: boolean; -} - -const objectSchema: SchemaObject<MyObject> = { - properties: { - an_id: { - type: 'keyword', - _meta: { - description: 'The ID of the element that generated the event', - optional: false, - }, - }, - a_description: { - type: 'text', - _meta: { - description: 'The human readable description of the element that generated the event', - optional: false, - }, - }, - a_number: { - type: 'long', - _meta: { - description: 'The number of times the element is used', - optional: true, - }, - }, - a_boolean: { - type: 'boolean', - _meta: { - description: 'Is the element still active', - optional: false, - }, - }, - }, - _meta: { - description: 'MyObject represents the events generated by elements in the UI when ...', - optional: false, - } -} -``` - -For the optional `_meta`, refer to [Schema Specification: `_meta`](#schema-specification-_meta). - -#### Schema Specification: Arrays - -Declaring the schema of an array contains 2 main attributes: `items` and an optional `_meta`: - -The `items` attribute is an object declaring the schema of the elements inside the array. At the moment, we only support arrays of one type, so `Array<string | number>` are not allowed. - -```typescript -type MyArray = string[]; - -const arraySchema: SchemaArray<MyArray> = { - items: { - type: 'keyword', - _meta: { - description: 'Tag attached to the element...', - optional: false, - }, - }, - _meta: { - description: 'List of tags attached to the element...', - optional: false, - } -} -``` - -For the optional `_meta`, refer to [Schema Specification: `_meta`](#schema-specification-_meta). - -#### Schema Specification: Special type `pass_through` - -In case a property in the schema is just used to pass through some unknown content that is declared and validated somewhere else, or that it can dynamically grow and shrink, you may use the `type: 'pass_through'` option. It behaves like a [first-order data type](#schema-specification-first-order-data-types-string-number-boolean): - -```typescript -type MyUnknownType = unknown; - -const passThroughSchema: SchemaValue<MyUnknownType> = { - type: 'pass_through', - _meta: { - description: 'Payload context recevied from the HTTP request...', - optional: false, - }, -} -``` - -For the optional `_meta`, refer to [Schema Specification: `_meta`](#schema-specification-_meta). - -#### Schema Specification: `_meta` - -The `_meta` adds the invaluable information of a `description` and whether a field is `optional` in the payload. - -It can be attached to any schema definition as seen in the examples above. For high-order types, like arrays or objects, the `_meta` field is optional. For first-order types, like numbers, strings, booleans or `pass_through`, the `_meta` key is mandatory. - -The field `_meta.optional` is not required unless the schema is describing an optional field. In that case, `_meta.optional: true` is required. However, it's highly encouraged to be explicit about declaring it even when the described field is not optional. - -### Schema Validation - -Apart from documentation, the schema is used to validate the payload during the dev cycle. This adds an extra layer of confidence over the data to be sent. - -The validation, however, is disabled in production because users cannot do anything to fix the bug after it is released. Additionally, receiving _buggy_ events can be considered an additional insight into how our users use our products. For example, the buggy event can be caused by a user following an unexpected path in the UI like clicking an "Upload" button when the file has not been selected [#125013](https://github.com/elastic/kibana/issues/125013). In those cases, receiving the _incomplete_ event tells us the user didn't select a file, but they still hit the "Upload" button. - -The validation is performed with the `io-ts` library. In order to do that, the schema is firstly parsed into the `io-ts` equivalent, and then used to validate the event & context payloads. diff --git a/packages/analytics/ebt/client/index.ts b/packages/analytics/ebt/client/index.ts deleted file mode 100644 index 7127a21a5517e..0000000000000 --- a/packages/analytics/ebt/client/index.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 and the Server Side Public License, v 1; you may not 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 { - AnalyticsClientInitContext, - IAnalyticsClient as AnalyticsClient, -} from './src/analytics_client'; -import { AnalyticsClient as AnalyticsClientClass } from './src/analytics_client'; - -/** - * Creates an {@link AnalyticsClient}. - * @param initContext The initial context to create the client {@link AnalyticsClientInitContext} - */ -export function createAnalytics(initContext: AnalyticsClientInitContext): AnalyticsClient { - return new AnalyticsClientClass(initContext); -} - -export type { - IAnalyticsClient as AnalyticsClient, - // Types for the constructor - AnalyticsClientInitContext, - // Types for the registerShipper API - ShipperClassConstructor, - RegisterShipperOpts, - // Types for the optIn API - OptInConfig, - OptInConfigPerType, - ShipperName, - // Types for the registerContextProvider API - ContextProviderOpts, - ContextProviderName, - // Types for the registerEventType API - EventTypeOpts, -} from './src/analytics_client'; - -export type { - Event, - EventContext, - EventType, - TelemetryCounter, - TelemetryCounterType, -} from './src/events'; - -export type { - RootSchema, - SchemaObject, - SchemaArray, - SchemaChildValue, - SchemaMeta, - SchemaValue, - SchemaMetaOptional, - PossibleSchemaTypes, - AllowedSchemaBooleanTypes, - AllowedSchemaNumberTypes, - AllowedSchemaStringTypes, - AllowedSchemaTypes, -} from './src/schema'; - -export type { IShipper } from './src/shippers'; diff --git a/packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts b/packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts deleted file mode 100644 index 0d72f003e77a6..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts +++ /dev/null @@ -1,1190 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may 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 max-classes-per-file -import { Subject, lastValueFrom, take, toArray } from 'rxjs'; -import type { MockedLogger } from '@kbn/logging-mocks'; -import { loggerMock } from '@kbn/logging-mocks'; -import { AnalyticsClient } from './analytics_client'; -import { shippersMock } from '../shippers/mocks'; -import type { TelemetryCounter } from '../events'; -import { ContextService } from './context_service'; - -describe('AnalyticsClient', () => { - let analyticsClient: AnalyticsClient; - let logger: MockedLogger; - - beforeEach(() => { - jest.useFakeTimers(); - logger = loggerMock.create(); - analyticsClient = new AnalyticsClient({ - logger, - isDev: true, - sendTo: 'staging', - }); - }); - - afterEach(async () => { - await analyticsClient.shutdown(); - jest.useRealTimers(); - }); - - describe('registerEventType', () => { - test('successfully registers a event type', () => { - analyticsClient.registerEventType({ - eventType: 'testEvent', - schema: { - a_field: { - type: 'keyword', - _meta: { - description: 'description of a_field', - }, - }, - }, - }); - }); - - test('cannot register the same event type twice', () => { - analyticsClient.registerEventType({ - eventType: 'testEvent', - schema: { - a_field: { - type: 'keyword', - _meta: { - description: 'description of a_field', - }, - }, - }, - }); - - expect(() => - analyticsClient.registerEventType({ - eventType: 'testEvent', - schema: { - b_field: { - type: 'date', - _meta: { - description: 'description of a_field', - }, - }, - }, - }) - ).toThrowErrorMatchingInlineSnapshot(`"Event Type \\"testEvent\\" is already registered."`); - }); - - test('it can be used after deconstruction of the client', () => { - const { registerEventType } = analyticsClient; - registerEventType({ - eventType: 'testEvent', - schema: { - a_field: { - type: 'keyword', - _meta: { - description: 'description of a_field', - }, - }, - }, - }); - }); - }); - - describe('reportEvent', () => { - test('fails to report an event type because it is not registered yet', () => { - expect(() => - analyticsClient.reportEvent('testEvent', { a_field: 'a' }) - ).toThrowErrorMatchingInlineSnapshot( - `"Attempted to report event type \\"testEvent\\", before registering it. Use the \\"registerEventType\\" API to register it."` - ); - }); - - test('fails to validate the event and throws', () => { - analyticsClient.registerEventType({ - eventType: 'testEvent', - schema: { - a_field: { - type: 'keyword', - _meta: { - description: 'description of a_field', - }, - }, - }, - }); - analyticsClient.optIn({ global: { enabled: true } }); - - expect( - () => analyticsClient.reportEvent('testEvent', { a_field: 100 }) // a_field is expected to be a string - ).toThrowErrorMatchingInlineSnapshot(` - "Failed to validate payload coming from \\"Event Type 'testEvent'\\": - - [a_field]: {\\"expected\\":\\"string\\",\\"actual\\":\\"number\\",\\"value\\":100}" - `); - }); - - test('enqueues multiple events before specifying the optIn consent and registering a shipper', async () => { - analyticsClient.registerEventType({ - eventType: 'testEvent', - schema: { - a_field: { - type: 'keyword', - _meta: { - description: 'description of a_field', - }, - }, - }, - }); - - const internalQueuePromise = lastValueFrom( - // eslint-disable-next-line dot-notation - analyticsClient['internalEventQueue$'].pipe(take(3), toArray()) - ); - - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3), toArray()) - ); - - analyticsClient.reportEvent('testEvent', { a_field: 'a' }); - analyticsClient.reportEvent('testEvent', { a_field: 'b' }); - analyticsClient.reportEvent('testEvent', { a_field: 'c' }); - - // Expect 3 enqueued testEvent, but not shipped - const eventCounters = await telemetryCounterPromise; - expect(eventCounters).toHaveLength(3); - eventCounters.forEach((eventCounter) => { - expect(eventCounter).toEqual({ - type: 'enqueued', - source: 'client', - event_type: 'testEvent', - code: 'enqueued', - count: 1, - }); - }); - - // The events went to the internal queue because there are no optIn nor shippers specified yet - const enqueuedEvents = await internalQueuePromise; - expect(enqueuedEvents).toEqual([ - { - context: {}, - event_type: 'testEvent', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'testEvent', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'testEvent', - properties: { a_field: 'c' }, - timestamp: expect.any(String), - }, - ]); - }); - - test('it can be used after deconstruction of the client', () => { - const { registerEventType, reportEvent } = analyticsClient; - registerEventType({ - eventType: 'testEvent', - schema: { - a_field: { - type: 'keyword', - _meta: { - description: 'description of a_field', - }, - }, - }, - }); - reportEvent('testEvent', { a_field: 'a' }); - }); - - test('Handles errors coming from the shipper.reportEvents API', () => { - const { optIn, registerEventType, registerShipper, reportEvent } = analyticsClient; - const reportEventsMock = jest.fn().mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - class MockedShipper extends shippersMock.MockedShipper { - reportEvents = reportEventsMock; - } - optIn({ global: { enabled: true } }); - registerShipper(MockedShipper, {}); - registerEventType({ eventType: 'testEvent', schema: {} }); - reportEvent('testEvent', {}); - expect(reportEventsMock).toHaveBeenCalledWith([ - { - timestamp: expect.any(String), - event_type: 'testEvent', - properties: {}, - context: {}, - }, - ]); - expect(logger.warn).toHaveBeenCalledWith( - `Failed to report event "testEvent" via shipper "${MockedShipper.shipperName}"`, - expect.any(Error) - ); - }); - }); - - describe('registerShipper', () => { - test('Registers a global shipper', () => { - // eslint-disable-next-line dot-notation - expect(analyticsClient['shippersRegistry'].allShippers.size).toBe(0); - analyticsClient.registerShipper(shippersMock.MockedShipper, {}); - - // eslint-disable-next-line dot-notation - expect(analyticsClient['shippersRegistry'].allShippers.size).toBe(1); - - expect( - // eslint-disable-next-line dot-notation - analyticsClient['shippersRegistry'].allShippers.get(shippersMock.MockedShipper.shipperName) - ).toBeTruthy(); - }); - - test('Fails to register the same global shipper twice', () => { - analyticsClient.registerShipper(shippersMock.MockedShipper, {}); - expect(() => - analyticsClient.registerShipper(shippersMock.MockedShipper, {}) - ).toThrowErrorMatchingInlineSnapshot(`"Shipper \\"mocked-shipper\\" is already registered"`); - }); - - test('Registers an event exclusive shipper', () => { - analyticsClient.registerShipper( - shippersMock.MockedShipper, - {}, - { exclusiveEventTypes: ['eventA', 'eventB'] } - ); - - // eslint-disable-next-line dot-notation - expect(analyticsClient['shippersRegistry'].allShippers.size).toBe(1); - - // eslint-disable-next-line dot-notation - expect(analyticsClient['shippersRegistry'].getShippersForEventType('eventA').size).toBe(1); - // eslint-disable-next-line dot-notation - expect(analyticsClient['shippersRegistry'].getShippersForEventType('eventB').size).toBe(1); - // eslint-disable-next-line dot-notation - expect(analyticsClient['shippersRegistry'].getShippersForEventType('eventC').size).toBe(0); - }); - - test('Forwards the telemetryCounter$ events from the shipper, overwriting the `source` property', async () => { - class MockedShipper extends shippersMock.MockedShipper { - constructor({ telemetryCounter$ }: { telemetryCounter$: Subject<TelemetryCounter> }) { - super(); - this.telemetryCounter$ = telemetryCounter$; - } - } - - const mockTelemetryCounter$ = new Subject<TelemetryCounter>(); - - // Typescript also helps with the config type inference <3 - analyticsClient.registerShipper(MockedShipper, { telemetryCounter$: mockTelemetryCounter$ }); - - const counterEventPromise = lastValueFrom(analyticsClient.telemetryCounter$.pipe(take(1))); - - const counter: TelemetryCounter = { - type: 'succeeded', - source: 'a random value', - event_type: 'eventTypeA', - code: '200', - count: 1000, - }; - - mockTelemetryCounter$.next(counter); - - await expect(counterEventPromise).resolves.toEqual({ - ...counter, - source: MockedShipper.shipperName, - }); - }); - - class MockedShipper extends shippersMock.MockedShipper { - constructor({ - optInMock, - extendContextMock, - shutdownMock, - }: { - optInMock?: jest.Mock; - extendContextMock?: jest.Mock; - shutdownMock?: jest.Mock; - }) { - super(); - if (optInMock) this.optIn = optInMock; - if (extendContextMock) this.extendContext = extendContextMock; - if (shutdownMock) this.shutdown = shutdownMock; - } - } - - test('Registers a shipper and sets the opt-in status if the opt-in status was previously set', () => { - // Call the optIn method before registering the shipper - analyticsClient.optIn({ global: { enabled: true } }); - - const optIn = jest.fn(); - analyticsClient.registerShipper(MockedShipper, { optInMock: optIn }); - expect(optIn).toHaveBeenCalledWith(true); - }); - - test('Registers a shipper and spreads the opt-in status changes', () => { - const optIn = jest.fn(); - analyticsClient.registerShipper(MockedShipper, { optInMock: optIn }); - expect(optIn).not.toHaveBeenCalled(); - - // Call the optIn method after registering the shipper - analyticsClient.optIn({ global: { enabled: true } }); - expect(optIn).toHaveBeenCalledWith(true); - }); - - test('Spreads the context updates to the shipper (only after opt-in)', async () => { - const extendContextMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper, { extendContextMock }); - expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in - analyticsClient.optIn({ global: { enabled: true } }); - await jest.advanceTimersByTimeAsync(10); - expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context - - const context$ = new Subject<{ a_field: boolean }>(); - analyticsClient.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - context$.next({ a_field: true }); - expect(extendContextMock).toHaveBeenCalledWith({ a_field: true }); // After update - }); - - test('Does not spread the context if opt-in === false', async () => { - const extendContextMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper, { extendContextMock }); - expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in - analyticsClient.optIn({ global: { enabled: false } }); - await jest.advanceTimersByTimeAsync(10); - expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in - }); - - test('Handles errors in the shipper', async () => { - const optInMock = jest.fn().mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - const extendContextMock = jest.fn().mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - const shutdownMock = jest.fn().mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - analyticsClient.registerShipper(MockedShipper, { - optInMock, - extendContextMock, - shutdownMock, - }); - expect(() => analyticsClient.optIn({ global: { enabled: true } })).not.toThrow(); - await jest.advanceTimersByTimeAsync(10); - expect(optInMock).toHaveBeenCalledWith(true); - expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context - expect(logger.warn).toHaveBeenCalledWith( - `Shipper "${MockedShipper.shipperName}" failed to extend the context`, - expect.any(Error) - ); - await expect(analyticsClient.shutdown()).resolves.toBeUndefined(); - expect(shutdownMock).toHaveBeenCalled(); - }); - }); - - describe('ContextProvider APIs', () => { - let contextService: ContextService; - - beforeEach(() => { - // eslint-disable-next-line dot-notation - contextService = analyticsClient['contextService']; - }); - - test('Registers a context provider', () => { - const registerContextProviderSpy = jest.spyOn(contextService, 'registerContextProvider'); - const context$ = new Subject<{ a_field: boolean }>(); - analyticsClient.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - expect(registerContextProviderSpy).toHaveBeenCalledTimes(1); - expect(registerContextProviderSpy).toHaveBeenCalledWith({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - }); - - test('Removes a context provider', () => { - const removeContextProviderSpy = jest.spyOn(contextService, 'removeContextProvider'); - analyticsClient.removeContextProvider('contextProviderA'); - expect(removeContextProviderSpy).toHaveBeenCalledTimes(1); - expect(removeContextProviderSpy).toHaveBeenCalledWith('contextProviderA'); - }); - }); - - describe('optIn', () => { - let optInMock1: jest.Mock; - let optInMock2: jest.Mock; - - beforeEach(() => { - optInMock1 = jest.fn(); - - class MockedShipper1 extends shippersMock.MockedShipper { - static shipperName = 'mocked-shipper-1'; - optIn = optInMock1; - } - optInMock2 = jest.fn(); - class MockedShipper2 extends shippersMock.MockedShipper { - static shipperName = 'mocked-shipper-2'; - optIn = optInMock2; - } - - analyticsClient.registerShipper(MockedShipper1, {}); - analyticsClient.registerShipper(MockedShipper2, {}); - }); - - test('Updates global optIn config', () => { - // eslint-disable-next-line dot-notation - expect(analyticsClient['optInConfig$'].value).toBeUndefined(); - - analyticsClient.optIn({ global: { enabled: true } }); - // eslint-disable-next-line dot-notation - expect(analyticsClient['optInConfig$'].value!['optInConfig']).toEqual({ - global: { enabled: true }, - }); - }); - - test('Updates each shipper optIn config for global opt-in: true', () => { - analyticsClient.optIn({ global: { enabled: true } }); - expect(optInMock1).toHaveBeenCalledWith(true); - expect(optInMock2).toHaveBeenCalledWith(true); - }); - - test('Updates each shipper optIn config for global opt-in: false', () => { - analyticsClient.optIn({ global: { enabled: false } }); - expect(optInMock1).toHaveBeenCalledWith(false); - expect(optInMock2).toHaveBeenCalledWith(false); - }); - - test('Updates each shipper optIn config for global opt-in: true && shipper-specific: true', () => { - analyticsClient.optIn({ - global: { enabled: true, shippers: { ['mocked-shipper-1']: true } }, - }); - expect(optInMock1).toHaveBeenCalledWith(true); // Using global and shipper-specific - expect(optInMock2).toHaveBeenCalledWith(true); // Using only global - }); - - test('Updates each shipper optIn config for global opt-in: true && shipper-specific: false', () => { - analyticsClient.optIn({ - global: { enabled: true, shippers: { ['mocked-shipper-1']: false } }, - }); - expect(optInMock1).toHaveBeenCalledWith(false); // Using global and shipper-specific - expect(optInMock2).toHaveBeenCalledWith(true); // Using only global - }); - - test('Updates each shipper optIn config for global opt-in: false && shipper-specific: true', () => { - analyticsClient.optIn({ - global: { enabled: false, shippers: { ['mocked-shipper-1']: true } }, - }); - expect(optInMock1).toHaveBeenCalledWith(false); // Using global and shipper-specific - expect(optInMock2).toHaveBeenCalledWith(false); // Using only global - }); - - test('Updates each shipper optIn config for global opt-in: false && shipper-specific: false', () => { - analyticsClient.optIn({ - global: { enabled: false, shippers: { ['mocked-shipper-1']: false } }, - }); - expect(optInMock1).toHaveBeenCalledWith(false); // Using global and shipper-specific - expect(optInMock2).toHaveBeenCalledWith(false); // Using only global - }); - - test('Catches error in the shipper.optIn method', () => { - optInMock1.mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - analyticsClient.optIn({ global: { enabled: true } }); - expect(optInMock1).toHaveBeenCalledWith(true); // Using global and shipper-specific - expect(optInMock2).toHaveBeenCalledWith(true); // Using only global - expect(logger.warn).toHaveBeenCalledWith( - 'Failed to set isOptedIn:true in shipper mocked-shipper-1', - expect.any(Error) - ); - }); - }); - - describe('E2E', () => { - class MockedShipper1 extends shippersMock.MockedShipper { - static shipperName = 'mocked-shipper-1'; - constructor({ reportEventsMock }: { reportEventsMock: jest.Mock }) { - super(); - this.reportEvents = reportEventsMock; - } - } - - class MockedShipper2 extends MockedShipper1 { - static shipperName = 'mocked-shipper-2'; - } - - beforeEach(() => { - analyticsClient.registerEventType({ - eventType: 'event-type-a', - schema: { - a_field: { - type: 'keyword', - _meta: { - description: 'description of a_field', - }, - }, - }, - }); - analyticsClient.registerEventType({ - eventType: 'event-type-b', - schema: { - b_field: { - type: 'long', - _meta: { - description: 'description of b_field', - }, - }, - }, - }); - }); - - test('Enqueues early events', async () => { - // eslint-disable-next-line dot-notation - const internalEventQueue$ = analyticsClient['internalEventQueue$']; - - const internalQueuePromise = lastValueFrom(internalEventQueue$.pipe(take(2), toArray())); - - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(2), toArray()) - ); - - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - - // Expect 2 enqueued testEvent, but not shipped - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - ]); - - // The events went to the internal queue because there are no optIn nor shippers specified yet - await expect(internalQueuePromise).resolves.toEqual([ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - }); - - test('Sends events from the internal queue when there are shippers and an opt-in response is true', async () => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - // As proven in the previous test, the events are still enqueued. - // Let's register a shipper and opt-in to test the dequeue logic. - const reportEventsMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); - analyticsClient.optIn({ global: { enabled: true } }); - await jest.advanceTimersByTimeAsync(10); - - expect(reportEventsMock).toHaveBeenCalledTimes(2); - expect(reportEventsMock).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock).toHaveBeenNthCalledWith(2, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - - // Expect 3 enqueued events, and 2 sent_to_shipper batched requests - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 2, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }); - - test('Discards events from the internal queue when there are shippers and an opt-in response is false', async () => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(4), toArray()) // Waiting for 4 enqueued - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - const reportEventsMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); - analyticsClient.optIn({ global: { enabled: false } }); - - // Report event after opted-out - analyticsClient.reportEvent('event-type-a', { a_field: 'c' }); - - expect(reportEventsMock).toHaveBeenCalledTimes(0); - - // Expect 4 enqueued, but not shipped - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - ]); - - // eslint-disable-next-line dot-notation - expect(analyticsClient['internalEventQueue$'].observed).toBe(false); - }); - - test('Discards events from the internal queue when there are no shippers and an opt-in response is false', async () => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(4), toArray()) // Waiting for 4 enqueued - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - analyticsClient.optIn({ global: { enabled: false } }); - - // Report event after opted-out - analyticsClient.reportEvent('event-type-a', { a_field: 'c' }); - - // Expect 4 enqueued, but not shipped - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - ]); - - // eslint-disable-next-line dot-notation - expect(analyticsClient['internalEventQueue$'].observed).toBe(false); - }); - - test('Discards only one type of the enqueued events based on event_type config', async () => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 1), toArray()) // Waiting for 3 enqueued + 1 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - const reportEventsMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); - analyticsClient.optIn({ - global: { enabled: true }, - event_types: { ['event-type-a']: { enabled: false } }, - }); - await jest.advanceTimersByTimeAsync(10); - - expect(reportEventsMock).toHaveBeenCalledTimes(1); - expect(reportEventsMock).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - - // Expect 3 enqueued events, and 1 sent_to_shipper batched request - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }); - - test('Discards the event at the shipper level (for a specific event)', async () => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - // Register 2 shippers and set 1 of them as disabled for event-type-a - const reportEventsMock1 = jest.fn(); - const reportEventsMock2 = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 }); - analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 }); - analyticsClient.optIn({ - global: { enabled: true }, - event_types: { - ['event-type-a']: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } }, - }, - }); - await jest.advanceTimersByTimeAsync(10); - - expect(reportEventsMock1).toHaveBeenCalledTimes(2); - expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock2).toHaveBeenCalledTimes(1); - expect(reportEventsMock2).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - - // Expect 3 enqueued events, and 2 sent_to_shipper batched requests - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 2, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }); - - test('Discards all the events at the shipper level (globally disabled)', async () => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - // Register 2 shippers and set 1 of them as globally disabled - const reportEventsMock1 = jest.fn(); - const reportEventsMock2 = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 }); - analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 }); - analyticsClient.optIn({ - global: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } }, - event_types: { - ['event-type-a']: { enabled: true }, - }, - }); - await jest.advanceTimersByTimeAsync(10); - - expect(reportEventsMock1).toHaveBeenCalledTimes(2); - expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock2).toHaveBeenCalledTimes(0); - - // Expect 3 enqueued events, and 2 sent_to_shipper batched requests - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 2, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }); - - test('Discards incoming events when opt-in response is false', async () => { - // Set OptIn and shipper first to test the "once-set up" scenario - const reportEventsMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); - analyticsClient.optIn({ global: { enabled: false } }); - - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3), toArray()) // Waiting for 3 enqueued - ); - - // Send multiple events of 1 type to test the non-grouping logic at this stage as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - expect(reportEventsMock).toHaveBeenCalledTimes(0); - - // Expect 2 enqueued, but not shipped - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - ]); - }); - - test('Forwards incoming events to the shippers when opt-in response is true', async () => { - // Set OptIn and shipper first to test the "once-set up" scenario - const reportEventsMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); - analyticsClient.optIn({ global: { enabled: true } }); - - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 * 2), toArray()) // Waiting for 2 events per each reportEvent call: enqueued and sent_to_shipper - ); - - // Send multiple events of 1 type to test the non-grouping logic at this stage as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - // This time the reportEvent API is called once per event (no grouping/batching applied at this stage) - expect(reportEventsMock).toHaveBeenCalledTimes(3); - expect(reportEventsMock).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock).toHaveBeenNthCalledWith(2, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock).toHaveBeenNthCalledWith(3, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - ]); - - // Expect 2 enqueued, but not shipped - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 1, - }, - ]); - }); - }); -}); diff --git a/packages/analytics/ebt/client/src/analytics_client/analytics_client.ts b/packages/analytics/ebt/client/src/analytics_client/analytics_client.ts deleted file mode 100644 index 52521ecde95de..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/analytics_client.ts +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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 { Mixed } from 'io-ts'; -import type { Observable } from 'rxjs'; -import { BehaviorSubject, Subject, combineLatest, from, merge } from 'rxjs'; -import { - buffer, - bufferCount, - concatMap, - delay, - filter, - groupBy, - map, - mergeMap, - share, - shareReplay, - skipWhile, - takeUntil, - tap, -} from 'rxjs'; -import type { LogMeta } from '@kbn/logging'; -import type { IShipper } from '../shippers'; -import type { - AnalyticsClientInitContext, - ContextProviderName, - ContextProviderOpts, - EventTypeOpts, - IAnalyticsClient, - OptInConfig, - RegisterShipperOpts, - ShipperClassConstructor, -} from './types'; -import type { Event, EventContext, EventType, TelemetryCounter } from '../events'; -import { ShippersRegistry } from './shippers_registry'; -import { OptInConfigService } from './opt_in_config'; -import { ContextService } from './context_service'; -import { schemaToIoTs, validateSchema } from '../schema/validation'; - -interface EventDebugLogMeta extends LogMeta { - ebt_event: Event<unknown>; -} - -export class AnalyticsClient implements IAnalyticsClient { - private readonly internalTelemetryCounter$ = new Subject<TelemetryCounter>(); - public readonly telemetryCounter$: Observable<TelemetryCounter> = - this.internalTelemetryCounter$.pipe(share()); // Using `share` so we can have multiple subscribers - /** - * This queue holds all the events until both conditions occur: - * 1. We know the user's optIn decision. - * 2. We have, at least, one registered shipper. - * @private - */ - private readonly internalEventQueue$ = new Subject<Event>(); - private readonly shippersRegistry = new ShippersRegistry(); - /** - * Observable used to report when a shipper is registered. - * @private - */ - private readonly shipperRegistered$ = new Subject<void>(); - private readonly eventTypeRegistry = new Map< - EventType, - EventTypeOpts<unknown> & { validator?: Mixed } - >(); - private readonly contextService: ContextService; - private readonly context$ = new BehaviorSubject<Partial<EventContext>>({}); - private readonly optInConfig$ = new BehaviorSubject<OptInConfigService | undefined>(undefined); - private readonly optInConfigWithReplay$ = this.optInConfig$.pipe( - filter((optInConfig): optInConfig is OptInConfigService => typeof optInConfig !== 'undefined'), - shareReplay(1) - ); - private readonly contextWithReplay$ = this.context$.pipe( - skipWhile(() => !this.optInConfig$.value), // Do not forward the context events until we have an optInConfig value - shareReplay(1) - ); - - constructor(private readonly initContext: AnalyticsClientInitContext) { - this.contextService = new ContextService( - this.context$, - this.initContext.isDev, - this.initContext.logger.get('context-service') - ); - this.reportEnqueuedEventsWhenClientIsReady(); - } - - public reportEvent = <EventTypeData extends object>( - eventType: EventType, - eventData: EventTypeData - ) => { - // Fetch the timestamp as soon as we receive the event. - const timestamp = new Date().toISOString(); - - this.internalTelemetryCounter$.next({ - type: 'enqueued', - source: 'client', - event_type: eventType, - code: 'enqueued', - count: 1, - }); - - const eventTypeOpts = this.eventTypeRegistry.get(eventType); - if (!eventTypeOpts) { - this.internalTelemetryCounter$.next({ - type: 'dropped', - source: 'client', - event_type: eventType, - code: 'UnregisteredType', - count: 1, - }); - throw new Error( - `Attempted to report event type "${eventType}", before registering it. Use the "registerEventType" API to register it.` - ); - } - - // If the validator is registered (dev-mode only), perform the validation. - if (eventTypeOpts.validator) { - validateSchema<EventTypeData>( - `Event Type '${eventType}'`, - eventTypeOpts.validator, - eventData - ); - } - - const event: Event = { - timestamp, - event_type: eventType, - context: this.context$.value, - properties: eventData as unknown as Record<string, unknown>, - }; - - this.initContext.logger.debug<EventDebugLogMeta>(`Report event "${eventType}"`, { - ebt_event: event, - }); - - const optInConfig = this.optInConfig$.value; - - if (optInConfig?.isEventTypeOptedIn(eventType) === false) { - // If opted out, skip early - return; - } - - if (typeof optInConfig === 'undefined') { - // If the opt-in config is not provided yet, we need to enqueue the event to an internal queue - this.internalEventQueue$.next(event); - } else { - this.sendToShipper(eventType, [event]); - } - }; - - public registerEventType = <EventTypeData>(eventTypeOps: EventTypeOpts<EventTypeData>) => { - if (this.eventTypeRegistry.get(eventTypeOps.eventType)) { - throw new Error(`Event Type "${eventTypeOps.eventType}" is already registered.`); - } - - this.eventTypeRegistry.set(eventTypeOps.eventType, { - ...eventTypeOps, - validator: this.initContext.isDev ? schemaToIoTs(eventTypeOps.schema) : undefined, - }); - }; - - public optIn = (optInConfig: OptInConfig) => { - const optInConfigInstance = new OptInConfigService(optInConfig); - this.optInConfig$.next(optInConfigInstance); - }; - - public registerContextProvider = <Context>(contextProviderOpts: ContextProviderOpts<Context>) => { - this.contextService.registerContextProvider(contextProviderOpts); - }; - - public removeContextProvider = (name: ContextProviderName) => { - this.contextService.removeContextProvider(name); - }; - - public registerShipper = <Shipper extends IShipper, ShipperConfig>( - ShipperClass: ShipperClassConstructor<Shipper, ShipperConfig>, - shipperConfig: ShipperConfig, - { exclusiveEventTypes = [] }: RegisterShipperOpts = {} - ) => { - const shipperName = ShipperClass.shipperName; - const shipper = new ShipperClass(shipperConfig, { - ...this.initContext, - logger: this.initContext.logger.get('shipper', shipperName), - }); - if (exclusiveEventTypes.length) { - // This feature is not intended to be supported in the MVP. - // I can remove it if we think it causes more bad than good. - exclusiveEventTypes.forEach((eventType) => { - this.shippersRegistry.addEventExclusiveShipper(eventType, shipperName, shipper); - }); - } else { - this.shippersRegistry.addGlobalShipper(shipperName, shipper); - } - - // Subscribe to the shipper's telemetryCounter$ and pass it over to the client's-level observable - shipper.telemetryCounter$?.subscribe((counter) => - this.internalTelemetryCounter$.next({ - ...counter, - source: shipperName, // Enforce the shipper's name in the `source` - }) - ); - - // Spread the optIn configuration updates - this.optInConfigWithReplay$.subscribe((optInConfig) => { - const isOptedIn = optInConfig.isShipperOptedIn(shipperName); - try { - shipper.optIn(isOptedIn); - } catch (err) { - this.initContext.logger.warn( - `Failed to set isOptedIn:${isOptedIn} in shipper ${shipperName}`, - err - ); - } - }); - - // Spread the global context if it has custom extendContext method - if (shipper.extendContext) { - this.contextWithReplay$.subscribe((context) => { - try { - shipper.extendContext!(context); - } catch (err) { - this.initContext.logger.warn( - `Shipper "${shipperName}" failed to extend the context`, - err - ); - } - }); - } - - // Notify that a shipper is registered - this.shipperRegistered$.next(); - }; - - public flush = async () => { - await Promise.all( - [...this.shippersRegistry.allShippers.entries()].map(async ([shipperName, shipper]) => { - try { - await shipper.flush(); - } catch (err) { - this.initContext.logger.warn(`Failed to flush shipper "${shipperName}"`, err); - } - }) - ); - }; - - public shutdown = async () => { - await this.flush(); - this.shippersRegistry.allShippers.forEach((shipper, shipperName) => { - try { - shipper.shutdown(); - } catch (err) { - this.initContext.logger.warn(`Failed to shutdown shipper "${shipperName}"`, err); - } - }); - this.internalEventQueue$.complete(); - this.internalTelemetryCounter$.complete(); - this.shipperRegistered$.complete(); - this.optInConfig$.complete(); - this.context$.complete(); - }; - - /** - * Forwards the `events` to the registered shippers, bearing in mind if the shipper is opted-in for that eventType. - * @param eventType The event type's name - * @param events A bulk array of events matching the eventType. - * @private - */ - private sendToShipper(eventType: EventType, events: Event[]) { - let sentToShipper = false; - this.shippersRegistry.getShippersForEventType(eventType).forEach((shipper, shipperName) => { - const isShipperOptedIn = this.optInConfig$.value?.isShipperOptedIn(shipperName, eventType); - - // Only send it to the non-explicitly opted-out shippers - if (isShipperOptedIn) { - sentToShipper = true; - try { - shipper.reportEvents(events); - } catch (err) { - this.initContext.logger.warn( - `Failed to report event "${eventType}" via shipper "${shipperName}"`, - err - ); - } - } - }); - if (sentToShipper) { - this.internalTelemetryCounter$.next({ - type: 'sent_to_shipper', - source: 'client', - event_type: eventType, - code: 'OK', - count: events.length, - }); - } - } - - /** - * Once the client is ready (it has a valid optInConfig and at least one shipper), - * flush any early events and ship them or discard them based on the optInConfig. - * @private - */ - private reportEnqueuedEventsWhenClientIsReady() { - // Observer that will emit when both events occur: the OptInConfig is set + a shipper has been registered - const configReceivedAndShipperReceivedObserver$ = combineLatest([ - this.optInConfigWithReplay$, - merge([ - this.shipperRegistered$, - // Merging shipperRegistered$ with the optInConfigWithReplay$ when optedIn is false, so that we don't need to wait for the shipper if opted-in === false - this.optInConfigWithReplay$.pipe(filter((cfg) => cfg?.isOptedIn() === false)), - ]), - ]); - - // Flush the internal queue when we get any optInConfig and, at least, 1 shipper - this.internalEventQueue$ - .pipe( - // Take until will close the observer once we reach the condition below - takeUntil(configReceivedAndShipperReceivedObserver$), - - // Accumulate the events until we can send them - buffer(configReceivedAndShipperReceivedObserver$), - - // Minimal delay only to make this chain async and let the optIn operation to complete first. - delay(0), - - // Re-emit the context to make sure all the shippers got it (only if opted-in) - tap(() => { - if (this.optInConfig$.value?.isOptedIn()) { - this.context$.next(this.context$.value); - } - }), - - // Minimal delay only to make this chain async and let - // the context update operation to complete first. - delay(0), - - // Flatten the array of events - concatMap((events) => from(events)), - - // Discard opted-out events - filter((event) => this.optInConfig$.value?.isEventTypeOptedIn(event.event_type) === true), - - // Let's group the requests per eventType for easier batching - groupBy((event) => event.event_type), - mergeMap((groupedObservable) => - groupedObservable.pipe( - bufferCount(1000), // Batching up-to 1000 events per event type for backpressure reasons - map((events) => ({ eventType: groupedObservable.key, events })) - ) - ) - ) - .subscribe(({ eventType, events }) => { - this.sendToShipper(eventType, events); - }); - } -} diff --git a/packages/analytics/ebt/client/src/analytics_client/context_service.test.ts b/packages/analytics/ebt/client/src/analytics_client/context_service.test.ts deleted file mode 100644 index e2b3f69b844f1..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/context_service.test.ts +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; -import { BehaviorSubject, firstValueFrom, lastValueFrom, Subject, take, toArray } from 'rxjs'; -import type { EventContext } from '../events'; -import { ContextService } from './context_service'; - -describe('ContextService', () => { - let globalContext$: Subject<Partial<EventContext>>; - let contextService: ContextService; - let logger: MockedLogger; - - beforeEach(() => { - globalContext$ = new BehaviorSubject<Partial<EventContext>>({}); - logger = loggerMock.create(); - contextService = new ContextService(globalContext$, true, logger); - }); - - test('Registers a context provider', async () => { - const context$ = new Subject<{ a_field: boolean }>(); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - const globalContextPromise = lastValueFrom(globalContext$.pipe(take(2), toArray())); - context$.next({ a_field: true }); - await expect(globalContextPromise).resolves.toEqual([ - {}, // Original empty state - { a_field: true }, - ]); - }); - - test('It does not break if context emits `undefined`', async () => { - contextService = new ContextService( - globalContext$, - false, // setting to `false` so the validation piece of logic does not kick in. - logger - ); - - const context$ = new Subject<{ a_field: boolean } | undefined | void>(); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - const globalContextPromise = lastValueFrom(globalContext$.pipe(take(3), toArray())); - context$.next(); - context$.next(undefined); - await expect(globalContextPromise).resolves.toEqual([ - {}, // Original empty state - {}, - {}, - ]); - }); - - test('It does not break for BehaviourSubjects (emitting as soon as they connect)', async () => { - const context$ = new BehaviorSubject<{ a_field: boolean }>({ a_field: true }); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - const globalContextPromise = lastValueFrom(globalContext$.pipe(take(1), toArray())); - await expect(globalContextPromise).resolves.toEqual([ - { a_field: true }, // No original empty state - ]); - }); - - test('Merges all the contexts together', async () => { - const contextA$ = new Subject<{ a_field: boolean }>(); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$: contextA$, - }); - - const contextB$ = new Subject<{ a_field?: boolean; b_field: number }>(); - contextService.registerContextProvider({ - name: 'contextProviderB', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - optional: true, - }, - }, - b_field: { - type: 'long', - _meta: { - description: 'b_field description', - }, - }, - }, - context$: contextB$, - }); - - const globalContextPromise = lastValueFrom(globalContext$.pipe(take(6), toArray())); - contextA$.next({ a_field: true }); - contextB$.next({ b_field: 1 }); - contextB$.next({ a_field: false, b_field: 1 }); - contextA$.next({ a_field: true }); - contextB$.next({ b_field: 2 }); - await expect(globalContextPromise).resolves.toEqual([ - {}, // Original empty state - { a_field: true }, - { a_field: true, b_field: 1 }, // Merged A & B - { a_field: false, b_field: 1 }, // a_field updated from B - { a_field: false, b_field: 1 }, // a_field still from B because it was registered later. - // We may want to change this last behaviour in the future but, for now, it's fine. - { a_field: true, b_field: 2 }, // a_field is now taken from A because B is not providing it yet. - ]); - }); - - test('The global context is not polluted by context providers removing reported fields', async () => { - const context$ = new Subject<{ a_field?: boolean; b_field: number }>(); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - optional: true, - }, - }, - b_field: { - type: 'long', - _meta: { - description: 'b_field description', - }, - }, - }, - context$, - }); - - const globalContextPromise = lastValueFrom(globalContext$.pipe(take(6), toArray())); - context$.next({ b_field: 1 }); - context$.next({ a_field: false, b_field: 1 }); - context$.next({ a_field: true, b_field: 1 }); - context$.next({ b_field: 1 }); - context$.next({ a_field: true, b_field: 2 }); - await expect(globalContextPromise).resolves.toEqual([ - {}, // Original empty state - { b_field: 1 }, - { a_field: false, b_field: 1 }, - { a_field: true, b_field: 1 }, - { b_field: 1 }, // a_field is removed because the context provider removed it. - { a_field: true, b_field: 2 }, - ]); - }); - - test('The undefined values are not forwarded to the global context', async () => { - const context$ = new Subject<{ a_field?: boolean; b_field: number }>(); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - optional: true, - }, - }, - b_field: { - type: 'long', - _meta: { - description: 'b_field description', - }, - }, - }, - context$, - }); - - const globalContextPromise = firstValueFrom(globalContext$.pipe(take(6), toArray())); - context$.next({ b_field: 1 }); - context$.next({ a_field: false, b_field: 1 }); - context$.next({ a_field: true, b_field: 1 }); - context$.next({ b_field: 1 }); - context$.next({ a_field: undefined, b_field: 2 }); - await expect(globalContextPromise).resolves.toEqual([ - {}, // Original empty state - { b_field: 1 }, - { a_field: false, b_field: 1 }, - { a_field: true, b_field: 1 }, - { b_field: 1 }, // a_field is removed because the context provider removed it. - { b_field: 2 }, // a_field is not forwarded because it is `undefined` - ]); - }); - - test('Fails to register 2 context providers with the same name', () => { - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$: new Subject<{ a_field: boolean }>(), - }); - expect(() => { - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$: new Subject<{ a_field: boolean }>(), - }); - }).toThrowErrorMatchingInlineSnapshot( - `"Context provider with name 'contextProviderA' already registered"` - ); - }); - - test('Does not remove the context provider after it completes', async () => { - const context$ = new Subject<{ a_field: boolean }>(); - - // eslint-disable-next-line dot-notation - const contextProvidersRegistry = contextService['contextProvidersRegistry']; - - // The context registry is empty - expect(contextProvidersRegistry.size).toBe(0); - - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - const globalContextPromise = lastValueFrom(globalContext$.pipe(take(4), toArray())); - context$.next({ a_field: true }); - // The size of the registry grows on the first emission - expect(contextProvidersRegistry.size).toBe(1); - - context$.next({ a_field: true }); - // Still in the registry - expect(contextProvidersRegistry.size).toBe(1); - context$.complete(); - // Still in the registry - expect(contextProvidersRegistry.size).toBe(1); - contextService.removeContextProvider('contextProviderA'); - // The context provider is removed from the registry - expect(contextProvidersRegistry.size).toBe(0); - await expect(globalContextPromise).resolves.toEqual([ - {}, // Original empty state - { a_field: true }, - { a_field: true }, - {}, - ]); - }); - - test('validates the input and logs the error if invalid', () => { - const context$ = new Subject<{ a_field: boolean } | undefined>(); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - context$.next(undefined); - expect(logger.error).toHaveBeenCalledTimes(1); - expect((logger.error.mock.calls[0][0] as Error).message).toContain( - `Failed to validate payload coming from "Context Provider 'contextProviderA'"` - ); - }); - - test('it does not stop the subscription after an error', async () => { - const context$ = new Subject<{ a_field: boolean } | undefined>(); - contextService.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, - }, - }, - context$, - }); - - const globalContextPromise = lastValueFrom(globalContext$.pipe(take(2), toArray())); - context$.next({ a_field: '123' as unknown as boolean }); // cause the error - expect(logger.error).toHaveBeenCalledTimes(1); - expect((logger.error.mock.calls[0][0] as Error).message).toContain( - `Failed to validate payload coming from "Context Provider 'contextProviderA'"` - ); - context$.next({ a_field: true }); // send a good one - await expect(globalContextPromise).resolves.toEqual([ - {}, // Original empty state - { a_field: true }, // 2nd emission (the errored one does not spread) - ]); - }); -}); diff --git a/packages/analytics/ebt/client/src/analytics_client/context_service.ts b/packages/analytics/ebt/client/src/analytics_client/context_service.ts deleted file mode 100644 index 490aea84aeb39..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/context_service.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 type { Subject, Subscription } from 'rxjs'; -import { filter } from 'rxjs'; -import type { Logger } from '@kbn/logging'; -import type { EventContext } from '../events'; -import type { ContextProviderName, ContextProviderOpts } from './types'; -import { schemaToIoTs, validateSchema } from '../schema/validation'; - -export class ContextService { - private readonly contextProvidersRegistry = new Map<ContextProviderName, unknown>(); - private readonly contextProvidersSubscriptions = new Map<ContextProviderName, Subscription>(); - - constructor( - private readonly context$: Subject<Partial<EventContext>>, - private readonly isDevMode: boolean, - private readonly logger: Logger - ) {} - - /** - * Registers a context provider, and subscribes to any updates from it. - * @param contextProviderOpts The options to register the context provider {@link ContextProviderOpts} - */ - public registerContextProvider<Context>({ - name, - context$, - schema, - }: ContextProviderOpts<Context>) { - if (this.contextProvidersSubscriptions.has(name)) { - throw new Error(`Context provider with name '${name}' already registered`); - } - - // Declare the validator only in dev-mode - const validator = this.isDevMode ? schemaToIoTs(schema) : undefined; - - const subscription = context$ - .pipe( - filter((context) => { - if (validator) { - try { - validateSchema( - `Context Provider '${name}'`, - validator, - context as Record<string, unknown> - ); - } catch (validationError) { - this.logger.error(validationError); - return false; - } - } - return true; - }) - ) - .subscribe((context) => { - // We store each context linked to the context provider, so they can increase and reduce - // the number of fields they report without having left-overs in the global context. - this.contextProvidersRegistry.set(name, context); - - // For every context change, we rebuild the global context. - // It's better to do it here than to rebuild it for every reportEvent. - this.updateGlobalContext(); - }); - - this.contextProvidersSubscriptions.set(name, subscription); - } - - /** - * Removes the context provider from the registry, unsubscribes from it, and rebuilds the global context. - * @param name The name of the context provider to remove. - */ - public removeContextProvider(name: ContextProviderName) { - this.contextProvidersSubscriptions.get(name)?.unsubscribe(); - this.contextProvidersRegistry.delete(name); - this.updateGlobalContext(); - } - - /** - * Loops through all the context providers and sets the global context - * @private - */ - private updateGlobalContext() { - this.context$.next( - [...this.contextProvidersRegistry.values()].reduce((acc: Partial<EventContext>, context) => { - return { - ...acc, - ...this.removeEmptyValues(context), - }; - }, {} as Partial<EventContext>) - ); - } - - private removeEmptyValues(context: unknown) { - if (!isObject(context)) { - return {}; - } - return Object.keys(context).reduce((acc, key) => { - if (context[key] !== undefined) { - acc[key] = context[key]; - } - return acc; - }, {} as Partial<EventContext>); - } -} - -function isObject(value: unknown): value is Record<string, unknown> { - return typeof value === 'object' && value !== null; -} diff --git a/packages/analytics/ebt/client/src/analytics_client/index.ts b/packages/analytics/ebt/client/src/analytics_client/index.ts deleted file mode 100644 index 77c4c8ce6fa16..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { - IAnalyticsClient, - // Types for the constructor - AnalyticsClientInitContext, - // Types for the registerShipper API - ShipperClassConstructor, - RegisterShipperOpts, - // Types for the optIn API - OptInConfig, - OptInConfigPerType, - ShipperName, - // Types for the registerContextProvider API - ContextProviderOpts, - ContextProviderName, - // Types for the registerEventType API - EventTypeOpts, -} from './types'; - -export { AnalyticsClient } from './analytics_client'; diff --git a/packages/analytics/ebt/client/src/analytics_client/mocks.ts b/packages/analytics/ebt/client/src/analytics_client/mocks.ts deleted file mode 100644 index d09bfa67dee82..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/mocks.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 { Subject } from 'rxjs'; -import type { IAnalyticsClient } from './types'; - -function createMockedAnalyticsClient(): jest.Mocked<IAnalyticsClient> { - return { - optIn: jest.fn(), - reportEvent: jest.fn(), - registerEventType: jest.fn(), - registerContextProvider: jest.fn(), - removeContextProvider: jest.fn(), - registerShipper: jest.fn(), - telemetryCounter$: new Subject(), - flush: jest.fn(), - shutdown: jest.fn(), - }; -} - -export const analyticsClientMock = { - create: createMockedAnalyticsClient, -}; diff --git a/packages/analytics/ebt/client/src/analytics_client/opt_in_config.test.ts b/packages/analytics/ebt/client/src/analytics_client/opt_in_config.test.ts deleted file mode 100644 index 8c34d79086a3c..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/opt_in_config.test.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { OptInConfigService } from './opt_in_config'; - -describe('OptInConfigService', () => { - describe('isOptedIn', () => { - test('Returns `true` when `global.enabled: true`', () => { - const config = new OptInConfigService({ global: { enabled: true } }); - expect(config.isOptedIn()).toBe(true); - }); - - test('Returns `false` when `global.enabled: false`', () => { - const config = new OptInConfigService({ global: { enabled: false } }); - expect(config.isOptedIn()).toBe(false); - }); - }); - - describe('isEventTypeOptedIn', () => { - test('Returns `true` when `global.enabled: true` and no eventType specific config is provided', () => { - const config = new OptInConfigService({ global: { enabled: true } }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(true); - }); - - test('Returns `false` when `global.enabled: false` and no eventType specific config is provided', () => { - const config = new OptInConfigService({ global: { enabled: false } }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(false); - }); - - test('Returns `true` when `global.enabled: true` and event_type config exists but not for the requested eventType', () => { - const config = new OptInConfigService({ - global: { enabled: true }, - event_types: { - 'test-event-2': { enabled: true }, - 'test-event-3': { enabled: false }, - }, - }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(true); - }); - - test('Returns `false` when `global.enabled: false` and event_type config exists but not for the requested eventType', () => { - const config = new OptInConfigService({ - global: { enabled: false }, - event_types: { - 'test-event-2': { enabled: true }, - 'test-event-3': { enabled: false }, - }, - }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(false); - }); - - test('Returns `true` when `global.enabled: true` and event_type config exists and it is `true`', () => { - const config = new OptInConfigService({ - global: { enabled: true }, - event_types: { - 'test-event-1': { enabled: true }, - }, - }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(true); - }); - - test('Returns `false` when `global.enabled: false` and event_type config exists and it is `true`', () => { - const config = new OptInConfigService({ - global: { enabled: false }, - event_types: { - 'test-event-1': { enabled: true }, - }, - }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(false); - }); - - test('Returns `false` when `global.enabled: true` and event_type config exists and it is `false`', () => { - const config = new OptInConfigService({ - global: { enabled: true }, - event_types: { - 'test-event-1': { enabled: false }, - }, - }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(false); - }); - - test('Returns `false` when `global.enabled: false` and event_type config exists and it is `false`', () => { - const config = new OptInConfigService({ - global: { enabled: false }, - event_types: { - 'test-event-1': { enabled: false }, - }, - }); - expect(config.isEventTypeOptedIn('test-event-1')).toBe(false); - }); - }); - describe('isShipperOptedIn', () => { - test('Returns `true` when `global.enabled: true` and no shipper specific config is provided', () => { - const config = new OptInConfigService({ global: { enabled: true } }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(true); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(true); - }); - - test('Returns `false` when `global.enabled: false` and no shipper specific config is provided', () => { - const config = new OptInConfigService({ global: { enabled: false } }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(false); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - test('Returns `true` when `global.enabled: true` and shipper config exists but not for the requested eventType', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-2': true, - 'test-shipper-3': false, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(true); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(true); - }); - - test('Returns `false` when `global.enabled: false` and shipper config exists but not for the requested eventType', () => { - const config = new OptInConfigService({ - global: { - enabled: false, - shippers: { - 'test-shipper-2': true, - 'test-shipper-3': false, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(false); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - test('Returns `true` when `global.enabled: true` and shipper config exists and it is `true`', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(true); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(true); - }); - - test('Returns `false` when `global.enabled: false` and shipper config exists and it is `true`', () => { - const config = new OptInConfigService({ - global: { - enabled: false, - shippers: { - 'test-shipper-1': true, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(false); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - test('Returns `false` when `global.enabled: true` and shipper config exists and it is `false`', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': false, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(false); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - test('Returns `false` when `global.enabled: false` and shipper config exists and it is `false`', () => { - const config = new OptInConfigService({ - global: { - enabled: false, - shippers: { - 'test-shipper-1': false, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1')).toBe(false); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - describe('with event_type config', () => { - test('Returns `true` when `global.enabled: true`, `shipper: true` and `event: true` (no `event.shippers`)', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - event_types: { - 'test-event-1': { - enabled: true, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(true); - }); - - test('Returns `true` when `global.enabled: true`, `shipper: true`, `event: true` (`event.shippers` exists but for others)', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - event_types: { - 'test-event-1': { - enabled: true, - shippers: { - 'test-shipper-2': false, - }, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(true); - }); - - test('Returns `true` when `global.enabled: true`, `shipper: true`, `event: true` (`event.shipper: true`)', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - event_types: { - 'test-event-1': { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(true); - }); - - test('Returns `false` when `global.enabled: false`, `shipper: true`, `event: true` (`event.shipper: true`)', () => { - const config = new OptInConfigService({ - global: { - enabled: false, - shippers: { - 'test-shipper-1': true, - }, - }, - event_types: { - 'test-event-1': { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - test('Returns `false` when `global.enabled: true`, `shipper: false`, `event: true` (`event.shipper: true`)', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': false, - }, - }, - event_types: { - 'test-event-1': { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - test('Returns `false` when `global.enabled: true`, `shipper: true`, `event: false` (`event.shipper: true`)', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - event_types: { - 'test-event-1': { - enabled: false, - shippers: { - 'test-shipper-1': true, - }, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - - test('Returns `false` when `global.enabled: true`, `shipper: true`, `event: true` (`event.shipper: false`)', () => { - const config = new OptInConfigService({ - global: { - enabled: true, - shippers: { - 'test-shipper-1': true, - }, - }, - event_types: { - 'test-event-1': { - enabled: true, - shippers: { - 'test-shipper-1': false, - }, - }, - }, - }); - expect(config.isShipperOptedIn('test-shipper-1', 'test-event-1')).toBe(false); - }); - }); - }); -}); diff --git a/packages/analytics/ebt/client/src/analytics_client/opt_in_config.ts b/packages/analytics/ebt/client/src/analytics_client/opt_in_config.ts deleted file mode 100644 index 6fece8b8e2a8f..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/opt_in_config.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { OptInConfig, ShipperName } from './types'; -import type { EventType } from '../events'; - -export class OptInConfigService { - constructor(private readonly optInConfig: OptInConfig) {} - - /** - * Is globally opted in? - */ - public isOptedIn(): boolean { - return this.optInConfig.global.enabled; - } - - /** - * Is the given event type opted in? - * @param eventType the event type to check - */ - public isEventTypeOptedIn(eventType: EventType): boolean { - if (!this.isOptedIn()) { - return false; - } - // In case of not provided a specific eventType consent, we assume opted-in - const isEventTypeOptedIn = - (this.optInConfig.event_types && this.optInConfig.event_types[eventType]?.enabled) ?? true; - - return isEventTypeOptedIn; - } - - /** - * Is the given shipper opted in? - * @param shipperName the shipper to check - * @param eventType the event type to check for the shipper - */ - public isShipperOptedIn(shipperName: ShipperName, eventType?: EventType): boolean { - if (!this.isOptedIn()) { - return false; - } - - // In case of not provided a specific shipper consent, we assume opted-in - const isShipperGloballyOptedIn: boolean = - (this.optInConfig.global.shippers && this.optInConfig.global.shippers[shipperName]) ?? true; - - if (!isShipperGloballyOptedIn) { - return false; - } - - if (eventType) { - if (!this.isEventTypeOptedIn(eventType)) { - return false; - } - - const eventTypeOptInConfig = - this.optInConfig.event_types && this.optInConfig.event_types[eventType]; - // In case of not provided a specific eventType-level shipper consent, we assume opted-in - const isEventTypeShipperOptedIn: boolean = - (eventTypeOptInConfig?.shippers && eventTypeOptInConfig.shippers[shipperName]) ?? true; - - return isEventTypeShipperOptedIn; - } else { - return isShipperGloballyOptedIn; - } - } -} diff --git a/packages/analytics/ebt/client/src/analytics_client/shippers_registry.test.ts b/packages/analytics/ebt/client/src/analytics_client/shippers_registry.test.ts deleted file mode 100644 index 9fbb4c308f56c..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/shippers_registry.test.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ShippersRegistry } from './shippers_registry'; -import { shippersMock } from '../shippers/mocks'; - -describe('ShippersRegistry', () => { - let shippersRegistry: ShippersRegistry; - - beforeEach(() => { - shippersRegistry = new ShippersRegistry(); - }); - - describe('Global Shippers', () => { - test('adds a shipper without an error', () => { - const shipper = shippersMock.createShipper(); - expect(shippersRegistry.allShippers.size).toBe(0); - shippersRegistry.addGlobalShipper('testShipper', shipper); - expect(shippersRegistry.allShippers.size).toBe(1); - }); - - test('fails to add the same shipper name twice (even when the shipper implementation is different)', () => { - const shipper1 = shippersMock.createShipper(); - const shipper2 = shippersMock.createShipper(); - shippersRegistry.addGlobalShipper('testShipper', shipper1); - expect(() => - shippersRegistry.addGlobalShipper('testShipper', shipper2) - ).toThrowErrorMatchingInlineSnapshot(`"Shipper \\"testShipper\\" is already registered"`); - }); - - test('adds multiple shippers with different names (even when the shipper implementation is the same)', () => { - const shipper = shippersMock.createShipper(); // Explicitly testing with the same shipper implementation - - expect(shippersRegistry.allShippers.size).toBe(0); - shippersRegistry.addGlobalShipper('testShipper1', shipper); - expect(shippersRegistry.allShippers.size).toBe(1); - shippersRegistry.addGlobalShipper('testShipper2', shipper); - expect(shippersRegistry.allShippers.size).toBe(2); - }); - - test('returns a global shipper if there is no event-type specific shipper', () => { - const shipper = shippersMock.createShipper(); - const shipperName = 'testShipper'; - expect(shippersRegistry.allShippers.size).toBe(0); - shippersRegistry.addGlobalShipper(shipperName, shipper); - expect(shippersRegistry.allShippers.size).toBe(1); - - const shippersForEventType = shippersRegistry.getShippersForEventType( - `RandomEvent${Date.now()}` - ); - // eslint-disable-next-line dot-notation - expect(shippersForEventType).toBe(shippersRegistry['globalShippers']); - expect(shippersForEventType.size).toBe(1); - expect(shippersForEventType.get(shipperName)).toBe(shipper); - }); - }); - - describe('Event-Exclusive Shippers', () => { - test('adds a shipper without an error', () => { - const shipper = shippersMock.createShipper(); - expect(shippersRegistry.allShippers.size).toBe(0); - shippersRegistry.addEventExclusiveShipper('testEvent', 'testShipper', shipper); - expect(shippersRegistry.allShippers.size).toBe(1); - }); - - test('fails to add the same shipper name twice (even when the shipper implementation is different)', () => { - const shipper1 = shippersMock.createShipper(); - const shipper2 = shippersMock.createShipper(); - shippersRegistry.addEventExclusiveShipper('testEvent', 'testShipper', shipper1); - expect(() => - shippersRegistry.addEventExclusiveShipper('testEvent', 'testShipper', shipper2) - ).toThrowErrorMatchingInlineSnapshot( - `"testShipper is already registered for event-type testEvent"` - ); - }); - - test('adds multiple shippers with different names (even when the shipper implementation is the same)', () => { - const shipper = shippersMock.createShipper(); // Explicitly testing with the same shipper implementation - - expect(shippersRegistry.allShippers.size).toBe(0); - shippersRegistry.addEventExclusiveShipper('testEvent', 'testShipper1', shipper); - expect(shippersRegistry.allShippers.size).toBe(1); - shippersRegistry.addEventExclusiveShipper('testEvent', 'testShipper2', shipper); - expect(shippersRegistry.allShippers.size).toBe(2); - }); - - test('adds the same shipper to different event types. The allShippers count does not increase', () => { - const shipper = shippersMock.createShipper(); // Explicitly testing with the same shipper implementation - - expect(shippersRegistry.allShippers.size).toBe(0); - shippersRegistry.addEventExclusiveShipper('testEvent1', 'testShipper', shipper); - expect(shippersRegistry.allShippers.size).toBe(1); - shippersRegistry.addEventExclusiveShipper('testEvent2', 'testShipper', shipper); - expect(shippersRegistry.allShippers.size).toBe(1); // This is still 1 because the shipper is the same - }); - - test('returns an event-specific shipper', () => { - const shipper = shippersMock.createShipper(); - const shipperName = 'testShipper'; - const eventTypeName = 'testEvent'; - expect(shippersRegistry.allShippers.size).toBe(0); - shippersRegistry.addEventExclusiveShipper(eventTypeName, shipperName, shipper); - expect(shippersRegistry.allShippers.size).toBe(1); - - const shippersForEventType = shippersRegistry.getShippersForEventType(eventTypeName); - expect(shippersForEventType.size).toBe(1); - expect(shippersForEventType.get(shipperName)).toBe(shipper); - - // No event-specific shipper found, returns global but no shippers found in global - const shippersForEventTypeNotFound = shippersRegistry.getShippersForEventType( - `RandomEvent${Date.now()}` - ); - // eslint-disable-next-line dot-notation - expect(shippersForEventTypeNotFound).toBe(shippersRegistry['globalShippers']); - expect(shippersForEventTypeNotFound.size).toBe(0); - }); - }); -}); diff --git a/packages/analytics/ebt/client/src/analytics_client/shippers_registry.ts b/packages/analytics/ebt/client/src/analytics_client/shippers_registry.ts deleted file mode 100644 index 8380775679861..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/shippers_registry.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 and the Server Side Public License, v 1; you may not 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 { EventType } from '../events'; -import type { IShipper } from '../shippers'; -import type { ShipperName } from './types'; - -/** - * Holds the map of the { [shipperName]: shipperInstance } - */ -export type ShippersMap = Map<ShipperName, IShipper>; - -export class ShippersRegistry { - /** - * Holds all the shippers: global and eventTypeExclusive. - * This helps to avoid looping over all the shippers when we just need them all. - */ - public readonly allShippers: ShippersMap = new Map(); - /** - * Holds the shippers that are not registered as exclusive to any event-type - */ - private readonly globalShippers: ShippersMap = new Map(); - /** - * Holds the shippers that are exclusive to an event-type in the format of { [eventType]: ShippersMap } - */ - private readonly eventTypeExclusiveShippers: Map<EventType, ShippersMap> = new Map(); - - /** - * Adds shipper to the registry. - * @param shipperName The unique name of the shipper. - * @param shipper The initialized shipper. - */ - public addGlobalShipper(shipperName: ShipperName, shipper: IShipper) { - if (this.globalShippers.get(shipperName)) { - throw new Error(`Shipper "${shipperName}" is already registered`); - } - this.globalShippers.set(shipperName, shipper); - this.allShippers.set(shipperName, shipper); - } - - /** - * Adds an event-type exclusive shipper. - * @param eventType The name of the event type - * @param shipperName The unique name for the shipper. - * @param shipper The initialized shipper. - */ - public addEventExclusiveShipper( - eventType: EventType, - shipperName: ShipperName, - shipper: IShipper - ) { - const eventExclusiveMap = this.eventTypeExclusiveShippers.get(eventType) || new Map(); - if (eventExclusiveMap.get(shipperName)) { - throw new Error(`${shipperName} is already registered for event-type ${eventType}`); - } - eventExclusiveMap.set(shipperName, shipper); - this.eventTypeExclusiveShippers.set(eventType, eventExclusiveMap); - this.allShippers.set(shipperName, shipper); - } - - /** - * Returns the shippers that must be used for the specified event type. - * @param eventType The name of the event type. - */ - public getShippersForEventType(eventType: EventType): ShippersMap { - return this.eventTypeExclusiveShippers.get(eventType) || this.globalShippers; - } -} diff --git a/packages/analytics/ebt/client/src/analytics_client/types.ts b/packages/analytics/ebt/client/src/analytics_client/types.ts deleted file mode 100644 index 18b55e88f5a91..0000000000000 --- a/packages/analytics/ebt/client/src/analytics_client/types.ts +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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 { Observable } from 'rxjs'; - -// If we are going to export this to a separate NPM module in the future, -// we'll need to revisit this import. -import type { Logger } from '@kbn/logging'; - -import type { IShipper } from '../shippers'; -import type { EventType, TelemetryCounter } from '../events'; -import type { RootSchema } from '../schema'; - -/** - * General settings of the analytics client - */ -export interface AnalyticsClientInitContext { - /** - * Boolean indicating if it's running in developer mode. - */ - isDev: boolean; - /** - * Specify if the shippers should send their data to the production or staging environments. - */ - sendTo: 'production' | 'staging'; - /** - * Application-provided logger. - */ - logger: Logger; -} - -/** - * Shipper Name used for indexed structures. Only used to improve the readability of the types - */ -export type ShipperName = string; - -/** - * Constructor of a {@link IShipper} - */ -export interface ShipperClassConstructor<Shipper extends IShipper, Config> { - /** - * The shipper's unique name - */ - shipperName: ShipperName; - - /** - * The constructor - * @param config The shipper's custom config - * @param initContext Common context {@link AnalyticsClientInitContext} - */ - new (config: Config, initContext: AnalyticsClientInitContext): Shipper; -} - -/** - * Optional options to register a shipper - */ -export interface RegisterShipperOpts { - /** - * List of event types that will be received only by this shipper. - * @deprecated - * @internal Set as internal and deprecated until we come up with the best design for this. - * Not in the scope of the initial MVP. - */ - exclusiveEventTypes?: EventType[]; -} - -/** - * Sets whether a type of event is enabled/disabled globally or per shipper. - */ -export interface OptInConfigPerType { - /** - * The event type is globally enabled. - */ - enabled: boolean; - /** - * Controls if an event type should be disabled for a specific type of shipper. - * @example If the event type is automatically tracked by ShipperA, the config would look like: - * ``` - * { - * enabled: true, - * shippers: { - * ShipperA: false - * } - * } - * ``` - */ - shippers?: Record<ShipperName, boolean | undefined>; -} - -/** - * Options for the optIn API - */ -export interface OptInConfig { - /** - * Controls the global enabled/disabled behaviour of the client and shippers. - */ - global: OptInConfigPerType; - /** - * Controls if an event type should be disabled for a specific type of shipper. - * @example If "clicks" are automatically tracked by ShipperA, the config would look like: - * ``` - * { - * global: { enabled: true }, - * event_types: { - * click: { - * enabled: true, - * shippers: { - * ShipperA: false - * } - * } - * } - * } - * ``` - */ - event_types?: Record<EventType, OptInConfigPerType | undefined>; -} - -/** - * ContextProviderName used for indexed structures. Only used to improve the readability of the types - */ -export type ContextProviderName = string; - -/** - * Definition of a context provider - */ -export interface ContextProviderOpts<Context> { - /** - * The name of the provider. - */ - name: ContextProviderName; - /** - * Observable that emits the custom context. - */ - context$: Observable<Context>; - /** - * Schema declaring and documenting the expected output in the context$ - * - * @remark During development, it may be used to validate the provided values. - */ - schema: RootSchema<Context>; -} - -/** - * Definition of an Event Type. - */ -export interface EventTypeOpts<EventTypeData> { - /** - * The event type's unique name. - */ - eventType: EventType; - /** - * Schema declaring and documenting the expected structure of this event type. - * - * @remark During development, it may be used to validate the provided values. - */ - schema: RootSchema<EventTypeData>; -} - -/** - * Analytics client's public APIs - */ -export interface IAnalyticsClient { - /** - * Reports a telemetry event. - * @param eventType The event type registered via the `registerEventType` API. - * @param eventData The properties matching the schema declared in the `registerEventType` API. - * - * @track-adoption - */ - reportEvent: <EventTypeData extends object>( - eventType: EventType, - eventData: EventTypeData - ) => void; - /** - * Registers the event type that will be emitted via the reportEvent API. - * @param eventTypeOps The definition of the event type {@link EventTypeOpts}. - */ - registerEventType: <EventTypeData>(eventTypeOps: EventTypeOpts<EventTypeData>) => void; - - /** - * Set up the shipper that will be used to report the telemetry events. - * @param Shipper The {@link IShipper} class to instantiate the shipper. - * @param shipperConfig The config specific to the Shipper to instantiate. - * @param opts Additional options to register the shipper {@link RegisterShipperOpts}. - */ - registerShipper: <Shipper extends IShipper, ShipperConfig>( - Shipper: ShipperClassConstructor<Shipper, ShipperConfig>, - shipperConfig: ShipperConfig, - opts?: RegisterShipperOpts - ) => void; - /** - * Used to control the user's consent to report the data. - * In the advanced mode, it allows to "cherry-pick" which events and shippers are enabled/disabled. - * @param optInConfig {@link OptInConfig} - */ - optIn: (optInConfig: OptInConfig) => void; - /** - * Registers the context provider to enrich any reported events. - * @param contextProviderOpts {@link ContextProviderOpts} - * - * @track-adoption - */ - registerContextProvider: <Context>(contextProviderOpts: ContextProviderOpts<Context>) => void; - /** - * Removes the context provider and stop enriching the events from its context. - * @param contextProviderName The name of the context provider to remove. - */ - removeContextProvider: (contextProviderName: ContextProviderName) => void; - /** - * Observable to emit the stats of the processed events. - */ - readonly telemetryCounter$: Observable<TelemetryCounter>; - /** - * Forces all shippers to send all their enqueued events and fulfills the returned promise. - */ - flush: () => Promise<void>; - /** - * Stops the client. Flushing any pending events in the process. - */ - shutdown: () => Promise<void>; -} diff --git a/packages/analytics/ebt/client/src/events/index.ts b/packages/analytics/ebt/client/src/events/index.ts deleted file mode 100644 index 85c3f2c3b6195..0000000000000 --- a/packages/analytics/ebt/client/src/events/index.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { - Event, - EventType, - EventContext, - TelemetryCounter, - TelemetryCounterType, -} from './types'; diff --git a/packages/analytics/ebt/client/src/events/types.ts b/packages/analytics/ebt/client/src/events/types.ts deleted file mode 100644 index 78b2f792e9e2b..0000000000000 --- a/packages/analytics/ebt/client/src/events/types.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 and the Server Side Public License, v 1; you may not 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 { ShipperName } from '../analytics_client'; - -/** - * Definition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}. - */ -export interface EventContext { - /** - * The UUID of the cluster - */ - cluster_uuid?: string; - /** - * The name of the cluster. - */ - cluster_name?: string; - /** - * The license ID. - */ - license_id?: string; - /** - * The unique user ID. - */ - userId?: string; - /** - * The Cloud ID. - */ - cloudId?: string; - /** - * `true` if the user is logged in via the Elastic Cloud authentication provider. - */ - isElasticCloudUser?: boolean; - /** - * The product's version. - */ - version?: string; - /** - * The name of the current page. - */ - pageName?: string; - /** - * The current application ID. - */ - applicationId?: string; - /** - * The current entity ID (dashboard ID, visualization ID, etc.). - */ - entityId?: string; - - /** - * Additional keys are allowed. - */ - [key: string]: unknown; -} - -/** - * Event Type used for indexed structures. Only used to improve the readability of the types - */ -export type EventType = string; - -/** - * Indicates if the event contains data about succeeded, failed or dropped events: - * - enqueued: The event was accepted and will be sent to the shippers when they become available (and opt-in === true). - * - sent_to_shipper: The event was sent to at least one shipper. - * - succeeded: The event was successfully sent by the shipper. - * - failed: There was an error when processing/shipping the event. Refer to the Telemetry Counter's code for the reason. - * - dropped: The event was dropped from the queue. Refer to the Telemetry Counter's code for the reason. - */ -export type TelemetryCounterType = - | 'enqueued' - | 'sent_to_shipper' - | 'succeeded' - | 'failed' - | 'dropped'; - -/** - * Shape of the events emitted by the telemetryCounter$ observable - */ -export interface TelemetryCounter { - /** - * {@link TelemetryCounterType} - */ - type: TelemetryCounterType; - /** - * Who emitted the event? It can be "client" or the name of the shipper. - */ - source: 'client' | ShipperName; - /** - * The event type the success/failure/drop event refers to. - */ - event_type: EventType; - /** - * Code to provide additional information about the success or failure. Examples are 200/400/504/ValidationError/UnknownError - */ - code: string; - /** - * The number of events that this counter refers to. - */ - count: number; -} - -/** - * Definition of the full event structure - */ -export interface Event<Properties = Record<string, unknown>> { - /** - * The time the event was generated in ISO format. - */ - timestamp: string; - /** - * The event type. - */ - event_type: EventType; - /** - * The specific properties of the event type. - */ - properties: Properties; - /** - * The {@link EventContext} enriched during the processing pipeline. - */ - context: EventContext; -} diff --git a/packages/analytics/ebt/client/src/mocks.ts b/packages/analytics/ebt/client/src/mocks.ts deleted file mode 100644 index 84a8f0854773a..0000000000000 --- a/packages/analytics/ebt/client/src/mocks.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 and the Server Side Public License, v 1; you may 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 { shippersMock } from './shippers/mocks'; -export { analyticsClientMock } from './analytics_client/mocks'; diff --git a/packages/analytics/ebt/client/src/schema/index.ts b/packages/analytics/ebt/client/src/schema/index.ts deleted file mode 100644 index e351a785c13ba..0000000000000 --- a/packages/analytics/ebt/client/src/schema/index.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. - */ - -export type { - RootSchema, - SchemaObject, - SchemaArray, - SchemaChildValue, - SchemaMeta, - SchemaValue, - SchemaMetaOptional, - PossibleSchemaTypes, - AllowedSchemaBooleanTypes, - AllowedSchemaNumberTypes, - AllowedSchemaStringTypes, - AllowedSchemaTypes, -} from './types'; diff --git a/packages/analytics/ebt/client/src/schema/types.test.ts b/packages/analytics/ebt/client/src/schema/types.test.ts deleted file mode 100644 index 9793528c21682..0000000000000 --- a/packages/analytics/ebt/client/src/schema/types.test.ts +++ /dev/null @@ -1,650 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PossibleSchemaTypes, RootSchema, SchemaValue } from './types'; - -describe('schema types', () => { - describe('PossibleSchemaTypes', () => { - test('it should only allow "string" types', () => { - let valueType: PossibleSchemaTypes<string> = 'keyword'; - valueType = 'text'; - valueType = 'date'; - - // @ts-expect-error - valueType = 'boolean'; - // @ts-expect-error - valueType = 'long'; - // @ts-expect-error - valueType = 'integer'; - // @ts-expect-error - valueType = 'short'; - // @ts-expect-error - valueType = 'byte'; - // @ts-expect-error - valueType = 'double'; - // @ts-expect-error - valueType = 'float'; - - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - test('it should only allow "number" types', () => { - let valueType: PossibleSchemaTypes<number> = 'long'; - valueType = 'integer'; - valueType = 'short'; - valueType = 'byte'; - valueType = 'double'; - valueType = 'float'; - valueType = 'date'; - - // @ts-expect-error - valueType = 'boolean'; - // @ts-expect-error - valueType = 'keyword'; - - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - test('it should only allow "boolean" types', () => { - let valueType: PossibleSchemaTypes<boolean> = 'boolean'; - // @ts-expect-error - valueType = 'integer'; - // @ts-expect-error - valueType = 'short'; - // @ts-expect-error - valueType = 'byte'; - // @ts-expect-error - valueType = 'double'; - // @ts-expect-error - valueType = 'float'; - // @ts-expect-error - valueType = 'date'; - - // @ts-expect-error - valueType = 'keyword'; - - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - }); - - describe('SchemaValue', () => { - describe('Pass Through', () => { - test('it should allow "pass_through" and enforce the _meta.description', () => { - let valueType: SchemaValue<string> = { - type: 'pass_through', - _meta: { - description: 'Some description', - }, - }; - - valueType = { - type: 'pass_through', - _meta: { - description: 'Some description', - optional: false, - }, - }; - - valueType = { - type: 'pass_through', - _meta: { - description: 'Some description', - // @ts-expect-error optional can't be true when the types don't set the value as optional - optional: true, - }, - }; - - // @ts-expect-error because it's missing the _meta.description - valueType = { type: 'pass_through' }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - test('it should enforce `_meta.optional: true`', () => { - let valueType: SchemaValue<string | undefined> = { - type: 'pass_through', - _meta: { - description: 'Some description', - optional: true, - }, - }; - - valueType = { - type: 'pass_through', - _meta: { - description: 'Some description', - // @ts-expect-error because optional can't be false when the value can be undefined - optional: false, - }, - }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - }); - - describe('Plain value', () => { - test('it should allow the correct type and enforce the _meta.description', () => { - let valueType: SchemaValue<string> = { - type: 'keyword', - _meta: { - description: 'Some description', - }, - }; - - valueType = { - type: 'keyword', - _meta: { - description: 'Some description', - optional: false, - }, - }; - - valueType = { - // @ts-expect-error because the type does not match - type: 'long', - _meta: { - description: 'Some description', - optional: false, - }, - }; - - valueType = { - type: 'keyword', - _meta: { - description: 'Some description', - // @ts-expect-error optional can't be true when the types don't set the value as optional - optional: true, - }, - }; - - // @ts-expect-error because it's missing the _meta.description - valueType = { type: 'keyword' }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - test('it should enforce `_meta.optional: true`', () => { - let valueType: SchemaValue<string | undefined> = { - type: 'keyword', - _meta: { - description: 'Some description', - optional: true, - }, - }; - - valueType = { - type: 'keyword', - _meta: { - description: 'Some description', - // @ts-expect-error because optional can't be false when the value can be undefined - optional: false, - }, - }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - }); - - describe('Date value', () => { - test('it should allow the correct type and enforce the _meta.description', () => { - let valueType: SchemaValue<Date> = { - type: 'date', - _meta: { - description: 'Some description', - }, - }; - - valueType = { - type: 'keyword', - _meta: { - description: 'Some description', - optional: false, - }, - }; - - valueType = { - // @ts-expect-error because the type does not match - type: 'long', - _meta: { - description: 'Some description', - optional: false, - }, - }; - - valueType = { - type: 'keyword', - _meta: { - description: 'Some description', - // @ts-expect-error optional can't be true when the types don't set the value as optional - optional: true, - }, - }; - - // @ts-expect-error because it's missing the _meta.description - valueType = { type: 'date' }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - test('it should enforce `_meta.optional: true`', () => { - let valueType: SchemaValue<Date | undefined> = { - type: 'date', - _meta: { - description: 'Some description', - optional: true, - }, - }; - - valueType = { - type: 'date', - _meta: { - description: 'Some description', - // @ts-expect-error because optional can't be false when the value can be undefined - optional: false, - }, - }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - }); - - describe('Object value', () => { - test('it should allow "pass_through" and enforce the _meta.description', () => { - let valueType: SchemaValue<{ a_value: string }> = { - type: 'pass_through', - _meta: { - description: 'Some description', - }, - }; - - // @ts-expect-error because it's missing the _meta.description - valueType = { type: 'pass_through' }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - - test('it should expect the proper object-schema definition, and allows some _meta at the object level as well', () => { - let valueType: SchemaValue<{ a_value: string }> = { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - }, - }, - }, - }; - - valueType = { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - optional: false, - }, - }, - }, - _meta: { - description: 'Description at the object level', - }, - }; - - valueType = { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - optional: false, - }, - }, - // @ts-expect-error b_value does not exist in the object definition - b_value: { - type: 'keyword', - _meta: { - description: 'Some description', - optional: true, - }, - }, - }, - _meta: { - description: 'Description at the object level', - }, - }; - - // @ts-expect-error because it's missing object properties - valueType = { properties: {} }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - - test('it should enforce `_meta.optional: true`', () => { - const objectValueType: SchemaValue<{ a_value: string } | undefined> = { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - }, - }, - }, - _meta: { - description: 'Optional object', - optional: true, - }, - }; - expect(objectValueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - - let valueType: SchemaValue<{ a_value?: string }> = { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - optional: true, - }, - }, - }, - }; - - valueType = { - properties: { - a_value: { - type: 'keyword', - // @ts-expect-error because it should provide optional: true - _meta: { - description: 'Some description', - }, - }, - }, - }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - }); - - describe('Array value', () => { - test('it should allow "pass_through" and enforce the _meta.description', () => { - let valueType: SchemaValue<Array<{ a_value: string }>> = { - type: 'pass_through', - _meta: { - description: 'Some description', - }, - }; - - // @ts-expect-error because it's missing the _meta.description - valueType = { type: 'pass_through' }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - - test('it should expect the proper array-schema definition, and allows some _meta at the object level as well', () => { - let valueType: SchemaValue<Array<{ a_value: string }>> = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - }, - }, - }, - }, - }; - - valueType = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - optional: false, - }, - }, - }, - _meta: { - description: 'Description at the object level', - }, - }, - }; - - // @ts-expect-error because it's missing the items definition - valueType = { type: 'array' }; - // @ts-expect-error because it's missing the items definition - valueType = { type: 'array', items: {} }; - // @ts-expect-error because it's missing the items' properties definition - valueType = { type: 'array', items: { properties: {} } }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - - test('it should enforce `_meta.optional: true`', () => { - const arrayValueType: SchemaValue<Array<{ a_value: string }> | undefined> = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - }, - }, - }, - }, - _meta: { - description: 'Optional object', - optional: true, - }, - }; - expect(arrayValueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - - const objectValueType: SchemaValue<Array<{ a_value: string } | undefined>> = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - }, - }, - }, - _meta: { - description: 'Optional object', - optional: true, - }, - }, - }; - expect(objectValueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - - let valueType: SchemaValue<Array<{ a_value?: string }>> = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - optional: true, - }, - }, - }, - }, - }; - - valueType = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - // @ts-expect-error because it should provide optional: true - _meta: { - description: 'Some description', - }, - }, - }, - }, - }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - - test('it should expect support readonly arrays', () => { - let valueType: SchemaValue<ReadonlyArray<{ a_value: string }>> = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - }, - }, - }, - }, - }; - - valueType = { - type: 'array', - items: { - properties: { - a_value: { - type: 'keyword', - _meta: { - description: 'Some description', - optional: false, - }, - }, - }, - _meta: { - description: 'Description at the object level', - }, - }, - }; - - // @ts-expect-error because it's missing the items definition - valueType = { type: 'array' }; - // @ts-expect-error because it's missing the items definition - valueType = { type: 'array', items: {} }; - // @ts-expect-error because it's missing the items' properties definition - valueType = { type: 'array', items: { properties: {} } }; - expect(valueType).not.toBeUndefined(); // <-- Only to stop the var-not-used complain - }); - }); - }); - - describe('RootSchema', () => { - const registerSchema = <Base>(schema: RootSchema<Base>) => schema; - test('it works with the explicit types', () => { - registerSchema<{ - my_keyword: string; - my_number?: number; - my_complex_unknown_meta_object: Record<string, unknown>; - my_array_of_str: string[]; - my_object: { my_timestamp: string }; - my_array_of_objects: Array<{ my_bool_prop: boolean }>; - }>({ - my_keyword: { - type: 'keyword', - _meta: { - description: 'Represents the key property...', - }, - }, - my_number: { - type: 'long', - _meta: { - description: 'Indicates the number of times...', - optional: true, - }, - }, - my_complex_unknown_meta_object: { - type: 'pass_through', - _meta: { - description: 'Unknown object that contains the key-values...', - }, - }, - my_array_of_str: { - type: 'array', - items: { - type: 'text', - _meta: { - description: 'List of tags...', - }, - }, - }, - my_object: { - properties: { - my_timestamp: { - type: 'date', - _meta: { - description: 'timestamp when the user...', - }, - }, - }, - }, - my_array_of_objects: { - type: 'array', - items: { - properties: { - my_bool_prop: { - type: 'boolean', - _meta: { - description: '`true` when...', - }, - }, - }, - }, - }, - }); - }); - test('it works with implicit types', () => { - registerSchema({}); - registerSchema({ - my_keyword: { - type: 'keyword', - _meta: { - description: 'Represents the key property...', - }, - }, - my_number: { - type: 'long', - _meta: { - description: 'Indicates the number of times...', - optional: true, - }, - }, - my_complex_unknown_meta_object: { - type: 'pass_through', - _meta: { - description: 'Unknown object that contains the key-values...', - }, - }, - my_array_of_str: { - type: 'array', - items: { - type: 'text', - _meta: { - description: 'List of tags...', - }, - }, - }, - my_object: { - properties: { - my_timestamp: { - type: 'date', - _meta: { - description: 'timestamp when the user...', - }, - }, - }, - }, - my_array_of_objects: { - type: 'array', - items: { - properties: { - my_bool_prop: { - type: 'boolean', - _meta: { - description: '`true` when...', - }, - }, - }, - }, - }, - }); - }); - }); -}); diff --git a/packages/analytics/ebt/client/src/schema/types.ts b/packages/analytics/ebt/client/src/schema/types.ts deleted file mode 100644 index caa638bed9dc8..0000000000000 --- a/packages/analytics/ebt/client/src/schema/types.ts +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** Types matching number values **/ -export type AllowedSchemaNumberTypes = - | 'long' - | 'integer' - | 'short' - | 'byte' - | 'double' - | 'float' - | 'date'; -/** Types matching string values **/ -export type AllowedSchemaStringTypes = 'keyword' | 'text' | 'date'; -/** Types matching boolean values **/ -export type AllowedSchemaBooleanTypes = 'boolean'; - -/** - * Possible type values in the schema - */ -export type AllowedSchemaTypes = - | AllowedSchemaNumberTypes - | AllowedSchemaStringTypes - | AllowedSchemaBooleanTypes; - -/** - * Helper to ensure the declared types match the schema types - */ -export type PossibleSchemaTypes<Value> = Value extends string | Date - ? AllowedSchemaStringTypes - : Value extends number - ? AllowedSchemaNumberTypes - : Value extends boolean - ? AllowedSchemaBooleanTypes - : // allow any schema type from the union if typescript is unable to resolve the exact U type - AllowedSchemaTypes; - -/** - * Schema to define a primitive value - */ -export interface SchemaChildValue<Value> { - /** The type of the value */ - type: PossibleSchemaTypes<NonNullable<Value>>; - /** Meta properties of the value: description and is optional */ - _meta: { - /** A description of the value */ - description: string; // Intentionally enforcing the descriptions here - } & SchemaMetaOptional<Value>; -} - -/** - * Type that defines all the possible values that the Schema accepts. - * These types definitions are helping to identify earlier the possible missing `properties` nesting when - * manually defining the schemas. - */ -export type SchemaValue<Value> = - // Always allow the pass_through no matter what the value is - | { - /** Type specification of a pass through object */ - type: 'pass_through'; - /** Meta properties of the pass through: description and is optional */ - _meta: { - /** A description of the value */ - description: string; // Intentionally enforcing the descriptions here - } & SchemaMetaOptional<Value>; - } - | (unknown extends Value - ? // If the Value is unknown (TS can't infer the type), allow any type of schema - SchemaArray<unknown, Value> | SchemaObject<Value> | SchemaChildValue<Value> - : // Otherwise, try to infer the type and enforce the schema - NonNullable<Value> extends Array<infer U> | ReadonlyArray<infer U> - ? SchemaArray<U, Value> - : NonNullable<Value> extends Date - ? SchemaChildValue<Value> - : NonNullable<Value> extends object - ? SchemaObject<Value> - : SchemaChildValue<Value>); - -/** - * Enforces { optional: true } if the value can be undefined - */ -export type SchemaMetaOptional<Value> = unknown extends Value - ? { optional?: boolean } - : undefined extends Value - ? { optional: true } - : { optional?: false }; - -/** - * Schema meta with optional description - */ -export interface SchemaMeta<Value> { - /** Meta properties of the pass through: description and is optional */ - _meta?: { - /** A description of the value */ - description?: string; - } & SchemaMetaOptional<Value>; -} - -/** - * Schema to represent an array - */ -export interface SchemaArray<Value, Base> extends SchemaMeta<Base> { - /** The type must be an array */ - type: 'array'; - /** The schema of the items in the array is defined in the `items` property */ - items: SchemaValue<Value>; -} - -/** - * Schema to represent an object - */ -export interface SchemaObject<Value> extends SchemaMeta<Value> { - /** - * The schemas of the keys of the object are defined in the `properties` object. - */ - properties: { - [Key in keyof Required<Value>]: SchemaValue<Value[Key]>; - }; -} - -/** - * Schema definition to match the structure of the properties provided. - * - * @example - * { - * my_keyword: { - * type: 'keyword', - * _meta: { - * description: 'Represents the key property...' - * } - * }, - * my_number: { - * type: 'long', - * _meta: { - * description: 'Indicates the number of times...', - * optional: true - * } - * }, - * my_complex_unknown_meta_object: { - * type: 'pass_through', - * _meta: { - * description: 'Unknown object that contains the key-values...' - * } - * }, - * my_array_of_str: { - * type: 'array', - * items: { - * type: 'text', - * _meta: { - * description: 'List of tags...' - * } - * } - * }, - * my_object: { - * properties: { - * my_timestamp: { - * type: 'date', - * _meta: { - * description: 'timestamp when the user...' - * } - * } - * } - * }, - * my_array_of_objects: { - * type: 'array', - * items: { - * properties: { - * my_bool_prop: { - * type: 'boolean', - * _meta: { - * description: '`true` when...' - * } - * } - * } - * } - * } - * } - */ -export type RootSchema<Base> = SchemaObject<Base>['properties']; diff --git a/packages/analytics/ebt/client/src/schema/validation/excess.test.ts b/packages/analytics/ebt/client/src/schema/validation/excess.test.ts deleted file mode 100644 index 8cb9aca7b060b..0000000000000 --- a/packages/analytics/ebt/client/src/schema/validation/excess.test.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import * as t from 'io-ts'; -import { either, isLeft } from 'fp-ts/lib/Either'; -import { excess } from './excess'; - -describe('excess', () => { - test('should pass validation when not found extra properties', () => { - const validator = excess(t.interface({ a_string: t.string, a_number: t.number })); - const invalidObj = { a_string: 'test', a_number: 1 }; - expect(validator.is(invalidObj)).toBe(true); - const result = validator.decode(invalidObj); - expect(isLeft(result)).toBe(false); - }); - - test('should not pass validation when found extra properties', () => { - const validator = excess(t.interface({ a_string: t.string, a_number: t.number })); - const invalidObj = { a_string: 'test', a_number: 1, another_string: 'test' }; - expect(validator.is(invalidObj)).toBe(false); - const result = validator.decode(invalidObj); - expect(isLeft(result)).toBe(true); - either.mapLeft(result, (validationError) => - expect(validationError[0].message).toBe(`excess key 'another_string' found`) - ); - }); - - test('should not pass validation when found a non-declared property in an all-optional object', () => { - const validator = excess(t.partial({ a_string: t.string, a_number: t.number })); - const invalidObj = { another_string: 'test' }; - expect(validator.is(invalidObj)).toBe(false); - const result = validator.decode(invalidObj); - expect(isLeft(result)).toBe(true); - either.mapLeft(result, (validationErrors) => - expect(validationErrors.map((err) => err.message)).toStrictEqual([ - `excess key 'another_string' found`, - ]) - ); - }); -}); diff --git a/packages/analytics/ebt/client/src/schema/validation/excess.ts b/packages/analytics/ebt/client/src/schema/validation/excess.ts deleted file mode 100644 index af6ec945f2a96..0000000000000 --- a/packages/analytics/ebt/client/src/schema/validation/excess.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// Extra IO-TS type to not allow more keys than the defined ones. -// Extracted from https://github.com/gcanti/io-ts/issues/322 - -import * as t from 'io-ts'; -import { either, Either, isRight, left, right, Right } from 'fp-ts/lib/Either'; - -const getIsCodec = - <T extends t.Any>(tag: string) => - (codec: t.Any): codec is T => - (codec as t.Any & { _tag: string })._tag === tag; - -const isInterfaceCodec = getIsCodec<t.InterfaceType<t.Props>>('InterfaceType'); -const isPartialCodec = getIsCodec<t.PartialType<t.Props>>('PartialType'); -const isIntersectionType = getIsCodec<t.IntersectionType<t.Mixed[]>>('IntersectionType'); - -const getProps = (codec: t.HasProps): t.Props => { - switch (codec._tag) { - case 'RefinementType': - case 'ReadonlyType': - return getProps(codec.type); - case 'InterfaceType': - case 'StrictType': - case 'PartialType': - return codec.props; - case 'IntersectionType': - return codec.types.reduce<t.Props>((props, type) => Object.assign(props, getProps(type)), {}); - } -}; - -const getNameFromProps = (props: t.Props, isPartial: boolean): string => - Object.keys(props) - .map((k) => `${k}${isPartial ? '?' : ''}: ${props[k].name}`) - .join(', '); - -/** - * Provides a human-readable definition of the io-ts validator. - * @param codec The io-ts declaration passed as an argument to the Excess method. - * @remarks Since we currently use it only with objects, we'll cover the IntersectionType and PartialType - */ -const getExcessTypeName = (codec: t.Any): string => { - if (isIntersectionType(codec)) { - return `{ ${codec.types - .map((subCodec) => { - if (isInterfaceCodec(subCodec)) { - return getNameFromProps(subCodec.props, false); - } - if (isPartialCodec(subCodec)) { - return getNameFromProps(subCodec.props, true); - } - return subCodec.name; - }) - .filter(Boolean) - .join(', ')} }`; - } - return `Excess<${codec.name}>`; -}; - -const stripKeys = <T>(o: T, props: t.Props): Either<string[], T> => { - const keys = Object.getOwnPropertyNames(o); - const propsKeys = Object.getOwnPropertyNames(props); - - propsKeys.forEach((pk) => { - const index = keys.indexOf(pk); - if (index !== -1) { - keys.splice(index, 1); - } - }); - - return keys.length ? left(keys) : right(o); -}; - -/** - * Validate if there are any keys that exist in the validated object, but they don't in the validation object. - * @param codec The io-ts schema to wrap with this validation - * @param name (optional) Replace the custom logic to name the validation error by providing a static name. - */ -export const excess = <C extends t.HasProps>( - codec: C, - name: string = getExcessTypeName(codec) -): ExcessType<C> => { - const props: t.Props = getProps(codec); - return new ExcessType<C>( - name, - (u): u is C => isRight(stripKeys(u, props)) && codec.is(u), - (u, c) => - either.chain(t.UnknownRecord.validate(u, c), () => - either.chain(codec.validate(u, c), (a) => - either.mapLeft(stripKeys<C>(a, props), (keys) => - keys.map((k) => ({ - value: a[k], - context: c, - message: `excess key '${k}' found`, - })) - ) - ) - ), - (a) => codec.encode((stripKeys(a, props) as Right<any>).right), - codec - ); -}; - -class ExcessType<C extends t.Any, A = C['_A'], O = A, I = unknown> extends t.Type<A, O, I> { - public readonly _tag: 'ExcessType' = 'ExcessType'; - constructor( - name: string, - is: ExcessType<C, A, O, I>['is'], - validate: ExcessType<C, A, O, I>['validate'], - encode: ExcessType<C, A, O, I>['encode'], - public readonly type: C - ) { - super(name, is, validate, encode); - } -} diff --git a/packages/analytics/ebt/client/src/schema/validation/index.ts b/packages/analytics/ebt/client/src/schema/validation/index.ts deleted file mode 100644 index 0c16f8f7c4cc7..0000000000000 --- a/packages/analytics/ebt/client/src/schema/validation/index.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 and the Server Side Public License, v 1; you may 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 { schemaToIoTs } from './schema_to_io_ts'; -export { validateSchema } from './validate_schema'; diff --git a/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.test.ts b/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.test.ts deleted file mode 100644 index 4a301a34d8656..0000000000000 --- a/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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 { AllowedSchemaTypes, RootSchema } from '../types'; -import { schemaToIoTs } from './schema_to_io_ts'; - -describe(`convertSchemaToIoTs`, () => { - test('fail with anything other than an object', () => { - // @ts-expect-error - expect(() => schemaToIoTs(null)).toThrow(); - }); - test('invalid type => errors with malformed schema', () => { - expect(() => - schemaToIoTs({ - // @ts-expect-error Non-valid type - an_invalid_field: { type: 'invalid', _meta: { description: 'Test description' } }, - }) - ).toThrow(/Malformed schema/); - }); - test('array type missing `items` => errors with malformed schema', () => { - expect(() => - schemaToIoTs({ - // @ts-expect-error Non-valid array-construct - an_invalid_field: { type: 'array' }, - }) - ).toThrow(/Malformed schema/); - }); - test('minimal schemas and empty value => pass', () => { - const validator = schemaToIoTs({}); - expect(validator.is({})).toBe(true); - }); - test('value has fields not defined in the schema => fail', () => { - const validator = schemaToIoTs({}); - expect(validator.is({ version: 'some-version' })).toBe(false); - expect(validator.is({ an_array: [{ docs: { missing: 1 } }] })).toBe(false); - }); - test('support optional fields', () => { - const validator = schemaToIoTs<unknown>({ - an_optional_field: { - type: 'keyword', - _meta: { - description: 'An optional field', - optional: true, - }, - }, - an_optional_obj: { - _meta: { optional: true }, - properties: { - other_field: { type: 'short', _meta: { description: 'Test description' } }, - }, - }, - an_optional_array: { - type: 'array', - items: { type: 'short', _meta: { description: 'Test description' } }, - _meta: { optional: true }, - }, - }); - expect(validator.is({})).toBe(true); - }); - test('value has nested-fields not defined in the schema => fail', () => { - const schemas: Array<RootSchema<unknown>> = [ - { - an_array: { - type: 'array', - _meta: { description: 'Test description' }, - items: { - properties: {}, - }, - }, - }, - { - an_array: { - type: 'array', - _meta: { description: 'Test description' }, - items: { - properties: { docs: { properties: {} } }, - }, - }, - }, - ]; - schemas.forEach((schema) => { - const validator = schemaToIoTs(schema); - expect(validator.is({ an_array: [{ docs: { missing: 1 } }] })).toBe(false); - }); - }); - test('value has nested-fields defined in the schema, but with wrong type => fail', () => { - const validator = schemaToIoTs({ - an_array: { - type: 'array', - items: { - properties: { - docs: { - properties: { - field: { type: 'short', _meta: { description: 'Test description' } }, - }, - }, - }, - }, - }, - }); - expect(validator.is({ an_array: [{ docs: { field: 'abc' } }] })).toBe(false); - }); - test.each([ - 'boolean', - 'byte', - 'double', - 'float', - 'integer', - 'long', - 'short', - ] as AllowedSchemaTypes[])('Expected type %s, but got string', (type) => { - const validator = schemaToIoTs({ - a_field: { type, _meta: { description: 'Test description' } }, - }); - expect(validator.is({ a_field: 'abc' })).toBe(false); - }); - test.each(['keyword', 'text', 'date'] as AllowedSchemaTypes[])( - 'Expected type %s, but got number', - (type) => { - const validator = schemaToIoTs({ - a_field: { type, _meta: { description: 'Test description' } }, - }); - expect(validator.is({ a_field: 1234 })).toBe(false); - } - ); - test('Support DYNAMIC_KEY', () => { - const validator = schemaToIoTs({ - a_field: { - properties: { DYNAMIC_KEY: { type: 'short', _meta: { description: 'Test description' } } }, - }, - }); - expect(validator.is({ a_field: { some_key: 1234 } })).toBe(true); - }); - test('Support DYNAMIC_KEY + known props', () => { - const validator = schemaToIoTs({ - a_field: { - properties: { - DYNAMIC_KEY: { type: 'short', _meta: { description: 'Test description' } }, - known_prop: { type: 'short', _meta: { description: 'Test description' } }, - }, - }, - }); - expect(validator.is({ a_field: { some_key: 1234, known_prop: 1234 } })).toBe(true); - }); - test('value has nested-fields defined in the schema => succeed', () => { - const validator = schemaToIoTs({ - an_array: { - type: 'array', - items: { - properties: { - docs: { - properties: { - field: { type: 'short', _meta: { description: 'Test description' } }, - }, - }, - }, - }, - }, - }); - expect(validator.is({ an_array: [{ docs: { field: 1 } }] })).toBe(true); - }); - - test('allow pass_through properties', () => { - const validator = schemaToIoTs({ - im_only_passing_through_data: { - type: 'pass_through', - _meta: { description: 'Test description' }, - }, - }); - expect(validator.is({ im_only_passing_through_data: [{ docs: { field: 1 } }] })).toBe(true); - }); -}); diff --git a/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.ts b/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.ts deleted file mode 100644 index 89b95dfe9a373..0000000000000 --- a/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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 type { RootSchema, SchemaArray, SchemaObject, SchemaValue } from '../types'; -import { excess } from './excess'; - -/** - * Is it a tuple of t.Mixed? - * @param schemas Array of io-ts schemas - */ -function isOneOfCandidate(schemas: t.Mixed[]): schemas is [t.Mixed, t.Mixed] { - return schemas.length === 2; -} - -/** - * Converts each {@link SchemaValue} to the io-ts equivalent - * @param value The {@link SchemaValue} to parse - */ -function schemaValueToIoTs<Value>(value: SchemaValue<Value>): t.Mixed { - // We need to check the pass_through type on top of everything - if ((value as { type: 'pass_through' }).type === 'pass_through') { - return t.unknown; - } - - if ('properties' in value) { - const { DYNAMIC_KEY, ...properties } = value.properties as SchemaObject<Value>['properties'] & { - DYNAMIC_KEY?: SchemaValue<unknown>; - }; - const schemas: t.Mixed[] = [schemaObjectToIoTs<Record<string, unknown>>({ properties })]; - if (DYNAMIC_KEY) { - schemas.push(t.record(t.string, schemaValueToIoTs(DYNAMIC_KEY))); - } - return isOneOfCandidate(schemas) ? t.union(schemas) : schemas[0]; - } else { - const valueType = value.type; // Copied in here because of TS reasons, it's not available in the `default` case - switch (valueType) { - case 'boolean': - return t.boolean; - case 'keyword': - case 'text': - case 'date': - return t.string; - case 'byte': - case 'double': - case 'float': - case 'integer': - case 'long': - case 'short': - return t.number; - case 'array': - if ('items' in value) { - return t.array(schemaValueToIoTs((value as SchemaArray<unknown, unknown>).items)); - } - throw new Error(`Schema type must include the "items" declaration.`); - default: - throw new Error(`Unsupported schema type ${valueType}.`); - } - } -} - -/** - * Loops through a list of [key, SchemaValue] tuples to convert them into a valid io-ts parameter to define objects. - * @param entries Array of tuples [key, {@link SchemaValue}]. Typically, coming from Object.entries(SchemaObject). - */ -function entriesToObjectIoTs<Value>( - entries: Array<[string, SchemaValue<Value>]> -): Record<string, t.Mixed> { - return Object.fromEntries( - entries.map(([key, value]) => { - try { - return [key, schemaValueToIoTs(value)]; - } catch (err) { - err.failedKey = [key, ...(err.failedKey || [])]; - throw err; - } - }) - ); -} - -/** - * Converts a {@link SchemaObject} to the io-ts equivalent. - * @param schemaObject The {@link SchemaObject} to parse. - */ -function schemaObjectToIoTs<Value>( - schemaObject: SchemaObject<Value> -): t.Type<Record<string, unknown>> { - const objectEntries: Array<[string, SchemaValue<unknown>]> = Object.entries( - schemaObject.properties - ); - - const requiredFields = objectEntries.filter(([key, { _meta }]) => _meta?.optional !== true); - const optionalFields = objectEntries.filter(([key, { _meta }]) => _meta?.optional === true); - - return excess( - t.intersection([ - t.interface(entriesToObjectIoTs(requiredFields)), - t.partial(entriesToObjectIoTs(optionalFields)), - ]) - ); -} - -/** - * Converts a {@link RootSchema} to an io-ts validation object. - * @param rootSchema The {@link RootSchema} to be parsed. - */ -export function schemaToIoTs<Base>(rootSchema: RootSchema<Base>): t.Type<Record<string, unknown>> { - try { - return schemaObjectToIoTs({ properties: rootSchema }); - } catch (err) { - if (err.failedKey) { - err.message = `Malformed schema for key [${err.failedKey.join('.')}]: ${err.message}`; - } - throw err; - } -} diff --git a/packages/analytics/ebt/client/src/schema/validation/validate_schema.test.ts b/packages/analytics/ebt/client/src/schema/validation/validate_schema.test.ts deleted file mode 100644 index dee7f96d50b02..0000000000000 --- a/packages/analytics/ebt/client/src/schema/validation/validate_schema.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { validateSchema } from './validate_schema'; -import { schemaToIoTs } from './schema_to_io_ts'; - -describe('validateSchema', () => { - describe('successful', () => { - test('valid object', () => { - expect(() => - validateSchema( - 'test source', - schemaToIoTs({ - an_object: { - properties: { a_field: { type: 'keyword', _meta: { description: 'A test field' } } }, - }, - }), - { an_object: { a_field: 'test' } } - ) - ).not.toThrow(); - }); - }); - describe('failed', () => { - test('object is valid but it has some extra fields not declared in the schema', () => { - expect(() => - validateSchema( - 'test source', - schemaToIoTs({ - an_object: { - properties: { a_field: { type: 'keyword', _meta: { description: 'A test field' } } }, - }, - }), - { an_object: { a_field: 'test' }, another_object: { a_field: 'test' } } - ) - ).toThrowErrorMatchingInlineSnapshot(` - "Failed to validate payload coming from \\"test source\\": - - []: excess key 'another_object' found" - `); - }); - - test('object is valid but it has some extra nested fields not declared in the schema', () => { - expect(() => - validateSchema( - 'test source', - schemaToIoTs({ - an_object: { - properties: { a_field: { type: 'keyword', _meta: { description: 'A test field' } } }, - }, - }), - { an_object: { a_field: 'test', an_extra_field: 'test' } } - ) - ).toThrowErrorMatchingInlineSnapshot(` - "Failed to validate payload coming from \\"test source\\": - - [an_object]: excess key 'an_extra_field' found" - `); - }); - - test('the object is not valid because it is missing a key', () => { - expect(() => - validateSchema( - 'test source', - schemaToIoTs<unknown>({ - an_object: { - properties: { a_field: { type: 'keyword', _meta: { description: 'A test field' } } }, - }, - an_optional_object: { - properties: { a_field: { type: 'keyword', _meta: { description: 'A test field' } } }, - _meta: { optional: true }, - }, - }), - { another_object: { a_field: 'test' } } - ) - ).toThrowErrorMatchingInlineSnapshot(` - "Failed to validate payload coming from \\"test source\\": - - [an_object]: {\\"expected\\":\\"{ a_field: string }\\",\\"actual\\":\\"undefined\\",\\"value\\":\\"undefined\\"}" - `); - }); - - test('lists multiple errors', () => { - expect(() => - validateSchema( - 'test source', - schemaToIoTs<unknown>({ - an_object: { - properties: { a_field: { type: 'keyword', _meta: { description: 'A test field' } } }, - }, - an_optional_object: { - properties: { a_field: { type: 'keyword', _meta: { description: 'A test field' } } }, - _meta: { optional: true }, - }, - }), - { - an_object: { a_field: 'test', an_extra_field: 'test' }, - an_optional_object: {}, - another_object: { a_field: 'test' }, - } - ) - ).toThrowErrorMatchingInlineSnapshot(` - "Failed to validate payload coming from \\"test source\\": - - [an_object]: excess key 'an_extra_field' found - - [an_optional_object.a_field]: {\\"expected\\":\\"string\\",\\"actual\\":\\"undefined\\",\\"value\\":\\"undefined\\"}" - `); - }); - }); -}); diff --git a/packages/analytics/ebt/client/src/schema/validation/validate_schema.ts b/packages/analytics/ebt/client/src/schema/validation/validate_schema.ts deleted file mode 100644 index 158acaa1fcd74..0000000000000 --- a/packages/analytics/ebt/client/src/schema/validation/validate_schema.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { Context, Type } from 'io-ts'; -import { either } from 'fp-ts/lib/Either'; - -/** - * Validates the event according to the schema validator generated by {@link convertSchemaToIoTs}. - * @throws Error when the event does not comply with the schema. - * @param validator The io-ts validator for the event. - * @param payload The payload to validate. - */ -export function validateSchema<Payload>( - sourceName: string, - validator: Type<Payload>, - payload: Payload -): void { - // Run io-ts validation to the event - const result = validator.decode(payload); - - either.mapLeft(result, (validationErrors) => { - const humanFriendlyErrors = validationErrors - .map( - (err) => `[${getFullPathKey(err.context)}]: ${err.message ?? readableContext(err.context)}` - ) - .filter((errMsg, idx, listOfErrMsgs) => listOfErrMsgs.indexOf(errMsg, idx + 1) === -1); - throw new Error( - `Failed to validate payload coming from "${sourceName}":\n\t- ${humanFriendlyErrors.join( - '\n\t- ' - )}` - ); - }); -} - -/** - * Picks the relevant fields of the validation error's context - * @param context The {@link Context} coming from the validation error - */ -function readableContext(context: Context) { - // The information provided, the last context is good enough. - // Otherwise, repeating the values for every nested key is too noisy. - const last = context[context.length - 1]; - return JSON.stringify({ - expected: last.type.name, - // Explicitly printing `undefined` to make it more obvious in the message - actual: typeof last.actual, - value: last.actual === undefined ? 'undefined' : last.actual, - }); -} - -/** - * Prints the full path to the key that raised the validation error. - * @param context The {@link Context} coming from the validation error - */ -function getFullPathKey(context: Context): string { - return ( - context - // Remove the context provided by InterfaceType and PartialType because their keys are simply numeric indices - .filter( - (ctx) => - !['InterfaceType', 'PartialType'].includes( - (ctx.type as Type<unknown> & { _tag: string })._tag - ) - ) - .map(({ key }) => key) - .filter(Boolean) - .join('.') - ); -} diff --git a/packages/analytics/ebt/client/src/shippers/index.ts b/packages/analytics/ebt/client/src/shippers/index.ts deleted file mode 100644 index 7a4ab7d85b9f2..0000000000000 --- a/packages/analytics/ebt/client/src/shippers/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 type { IShipper } from './types'; diff --git a/packages/analytics/ebt/client/src/shippers/mocks.ts b/packages/analytics/ebt/client/src/shippers/mocks.ts deleted file mode 100644 index fccdd4788f7d9..0000000000000 --- a/packages/analytics/ebt/client/src/shippers/mocks.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 { Subject } from 'rxjs'; -import type { IShipper } from './types'; -import type { TelemetryCounter } from '../events'; - -function createShipper(): jest.Mocked<IShipper> { - return new MockedShipper(); -} - -class MockedShipper implements IShipper { - public static shipperName = 'mocked-shipper'; - - constructor() {} - - public optIn = jest.fn(); - public reportEvents = jest.fn(); - public extendContext = jest.fn(); - public telemetryCounter$ = new Subject<TelemetryCounter>(); - public flush = jest.fn(); - public shutdown = jest.fn(); -} - -export const shippersMock = { - createShipper, - MockedShipper, -}; diff --git a/packages/analytics/ebt/client/src/shippers/types.ts b/packages/analytics/ebt/client/src/shippers/types.ts deleted file mode 100644 index c1e2ab8a81153..0000000000000 --- a/packages/analytics/ebt/client/src/shippers/types.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 { Observable } from 'rxjs'; -import { Event, EventContext, TelemetryCounter } from '../events'; - -/** - * Basic structure of a Shipper - */ -export interface IShipper { - /** - * Adapts and ships the event to the persisting/analytics solution. - * @param events batched events {@link Event} - */ - reportEvents: (events: Event[]) => void; - /** - * Stops/restarts the shipping mechanism based on the value of isOptedIn - * @param isOptedIn `true` for resume sending events. `false` to stop. - */ - optIn: (isOptedIn: boolean) => void; - /** - * Perform any necessary calls to the persisting/analytics solution to set the event's context. - * @param newContext The full new context to set {@link EventContext} - */ - extendContext?: (newContext: EventContext) => void; - /** - * Observable to emit the stats of the processed events. - */ - telemetryCounter$?: Observable<TelemetryCounter>; - /** - * Sends all the enqueued events and fulfills the returned promise. - */ - flush: () => Promise<void>; - /** - * Shutdown the shipper. - */ - shutdown: () => void; -} diff --git a/packages/analytics/ebt/index.ts b/packages/analytics/ebt/index.ts deleted file mode 100644 index ec407b406d406..0000000000000 --- a/packages/analytics/ebt/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// Exporting the types here as a utility only -// The recommended way of using this library is to import from the subdirectories /client, /shippers/* -// The reason is to avoid leaking server-side code to the browser, and vice-versa -export type { - AnalyticsClient, - // Types for the constructor - AnalyticsClientInitContext, - // Types for the registerShipper API - ShipperClassConstructor, - RegisterShipperOpts, - // Types for the optIn API - OptInConfig, - OptInConfigPerType, - ShipperName, - // Types for the registerContextProvider API - ContextProviderOpts, - ContextProviderName, - // Types for the registerEventType API - EventTypeOpts, - // Events - Event, - EventContext, - EventType, - TelemetryCounter, - TelemetryCounterType, - // Schema - RootSchema, - SchemaObject, - SchemaArray, - SchemaChildValue, - SchemaMeta, - SchemaValue, - SchemaMetaOptional, - PossibleSchemaTypes, - AllowedSchemaBooleanTypes, - AllowedSchemaNumberTypes, - AllowedSchemaStringTypes, - AllowedSchemaTypes, - // Shippers - IShipper, -} from './client'; -export type { ElasticV3ShipperOptions } from './shippers/elastic_v3/common'; -export type { ElasticV3BrowserShipper } from './shippers/elastic_v3/browser'; -export type { ElasticV3ServerShipper } from './shippers/elastic_v3/server'; -export type { - FullStoryShipperConfig, - FullStoryShipper, - FullStorySnippetConfig, -} from './shippers/fullstory'; diff --git a/packages/analytics/ebt/jest.config.js b/packages/analytics/ebt/jest.config.js deleted file mode 100644 index 7296624c287b5..0000000000000 --- a/packages/analytics/ebt/jest.config.js +++ /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 and the Server Side Public License, v 1; you may 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: ['<rootDir>/packages/analytics/ebt'], -}; diff --git a/packages/analytics/ebt/kibana.jsonc b/packages/analytics/ebt/kibana.jsonc deleted file mode 100644 index 947d6224b6933..0000000000000 --- a/packages/analytics/ebt/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/ebt", - "owner": "@elastic/kibana-core" -} diff --git a/packages/analytics/ebt/package.json b/packages/analytics/ebt/package.json deleted file mode 100644 index aee987a4c179b..0000000000000 --- a/packages/analytics/ebt/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/ebt", - "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/analytics/ebt/shippers/README.md b/packages/analytics/ebt/shippers/README.md deleted file mode 100644 index 3f312d1ecefe3..0000000000000 --- a/packages/analytics/ebt/shippers/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# @kbn/ebt/shippers/* - -This directory holds the implementation of the _built-in_ shippers provided by the Analytics client. At the moment, the shippers are: - -* [FullStory](./fullstory/README.md) -* [Elastic V3 (Browser shipper)](./elastic_v3/browser/README.md) -* [Elastic V3 (Server-side shipper)](./elastic_v3/server/README.md) diff --git a/packages/analytics/ebt/shippers/elastic_v3/browser/README.md b/packages/analytics/ebt/shippers/elastic_v3/browser/README.md deleted file mode 100644 index 1253a9619233d..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/browser/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# @kbn/ebt/shippers/elastic_v3/browser - -UI-side implementation of the Elastic V3 shipper for the `@kbn/ebt/client`. - -## How to use it - -This module is intended to be used **on the browser only**. Due to the nature of the UI events, they are usually more scattered in time, and we can assume a much lower load than the server. For that reason, it doesn't apply the necessary backpressure mechanisms to prevent the server from getting overloaded with too many events neither identifies if the server sits behind a firewall to discard any incoming events. Refer to `@kbn/ebt/shippers/elastic_v3/server` for the server-side implementation. - -```typescript -import { ElasticV3BrowserShipper } from "@kbn/ebt/shippers/elastic_v3/browser"; - -analytics.registerShipper(ElasticV3BrowserShipper, { channelName: 'myChannel', version: '1.0.0' }); -``` - -## Configuration - -| Name | Description | -|:-------------:|:-------------------------------------------------------------------------------------------| -| `channelName` | The name of the channel to send the events. | -| `version` | The version of the application generating the events. | -| `debug` | When `true`, it logs the responses from the remote Telemetry Service. Defaults to `false`. | - -## Transmission protocol - -This shipper sends the events to the Elastic Internal Telemetry Service. The incoming events are buffered for up to 1 second to attempt to send them in a single request. diff --git a/packages/analytics/ebt/shippers/elastic_v3/browser/index.ts b/packages/analytics/ebt/shippers/elastic_v3/browser/index.ts deleted file mode 100644 index 05619fba6e6e3..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/browser/index.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { ElasticV3ShipperOptions } from '../common'; -export { ElasticV3BrowserShipper } from './src/browser_shipper'; diff --git a/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.test.ts b/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.test.ts deleted file mode 100644 index fd8d7f680fd3c..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.test.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { loggerMock } from '@kbn/logging-mocks'; -import { firstValueFrom } from 'rxjs'; -import type { AnalyticsClientInitContext, Event } from '../../../../client'; -import { ElasticV3BrowserShipper } from './browser_shipper'; - -describe('ElasticV3BrowserShipper', () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'test-event-type', - context: {}, - properties: {}, - }, - ]; - - const initContext: AnalyticsClientInitContext = { - sendTo: 'staging', - isDev: true, - logger: loggerMock.create(), - }; - - let shipper: ElasticV3BrowserShipper; - - let fetchMock: jest.Mock; - - beforeEach(() => { - jest.useFakeTimers(); - - fetchMock = jest.fn().mockResolvedValue({ - status: 200, - ok: true, - text: () => Promise.resolve('{"status": "ok"}'), - }); - - Object.defineProperty(global, 'fetch', { - value: fetchMock, - writable: true, - }); - - shipper = new ElasticV3BrowserShipper( - { version: '1.2.3', channelName: 'test-channel', debug: true }, - initContext - ); - }); - - afterEach(() => { - shipper.shutdown(); - jest.useRealTimers(); - }); - - test("custom sendTo overrides Analytics client's", () => { - const prodShipper = new ElasticV3BrowserShipper( - { version: '1.2.3', channelName: 'test-channel', debug: true, sendTo: 'production' }, - initContext - ); - - // eslint-disable-next-line dot-notation - expect(prodShipper['url']).not.toEqual(shipper['url']); - }); - - test('set optIn should update the isOptedIn$ observable', () => { - // eslint-disable-next-line dot-notation - const internalOptIn$ = shipper['isOptedIn$']; - - // Initially undefined - expect(internalOptIn$.value).toBeUndefined(); - - shipper.optIn(true); - expect(internalOptIn$.value).toBe(true); - - shipper.optIn(false); - expect(internalOptIn$.value).toBe(false); - }); - - test('set extendContext should store local values: clusterUuid and licenseId', () => { - // eslint-disable-next-line dot-notation - const getInternalClusterUuid = () => shipper['clusterUuid']; - // eslint-disable-next-line dot-notation - const getInternalLicenseId = () => shipper['licenseId']; - - // Initial values - expect(getInternalClusterUuid()).toBe('UNKNOWN'); - expect(getInternalLicenseId()).toBeUndefined(); - - shipper.extendContext({ cluster_uuid: 'test-cluster-uuid' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); - expect(getInternalLicenseId()).toBeUndefined(); - - shipper.extendContext({ license_id: 'test-license-id' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); - expect(getInternalLicenseId()).toBe('test-license-id'); - - shipper.extendContext({ cluster_uuid: 'test-cluster-uuid-2', license_id: 'test-license-id-2' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid-2'); - expect(getInternalLicenseId()).toBe('test-license-id-2'); - }); - - test('calls to reportEvents do not call `fetch` straight away (buffer of 1s)', () => { - shipper.reportEvents(events); - expect(fetchMock).not.toHaveBeenCalled(); - }); - - test('calls to reportEvents do not call `fetch` after 1s because no optIn value is set yet', async () => { - shipper.reportEvents(events); - await jest.advanceTimersByTimeAsync(1000); - expect(fetchMock).not.toHaveBeenCalled(); - }); - - test('calls to reportEvents call `fetch` after 1s when optIn value is set to true', async () => { - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - await jest.advanceTimersByTimeAsync(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_browser", - "type": "succeeded", - } - `); - }); - - test('calls to reportEvents do not call `fetch` after 1s when optIn value is set to false', async () => { - shipper.reportEvents(events); - shipper.optIn(false); - await jest.advanceTimersByTimeAsync(1000); - expect(fetchMock).not.toHaveBeenCalled(); - }); - - test('calls to flush forces the client to send all the pending events', async () => { - shipper.optIn(true); - shipper.reportEvents(events); - const counter = firstValueFrom(shipper.telemetryCounter$); - await shipper.flush(); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_browser", - "type": "succeeded", - } - `); - }); - - test('calls to flush resolve immediately if there is nothing to send', async () => { - shipper.optIn(true); - await shipper.flush(); - expect(fetchMock).toHaveBeenCalledTimes(0); - }); - - test('calling flush multiple times does not keep hanging', async () => { - await expect(shipper.flush()).resolves.toBe(undefined); - await expect(shipper.flush()).resolves.toBe(undefined); - await Promise.all([shipper.flush(), shipper.flush()]); - }); - - test('calling flush after shutdown does not keep hanging', async () => { - shipper.shutdown(); - await expect(shipper.flush()).resolves.toBe(undefined); - }); - - test('calls to reportEvents call `fetch` when shutting down if optIn value is set to true', async () => { - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - shipper.shutdown(); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_browser", - "type": "succeeded", - } - `); - }); - - test('does not add the query.debug: true property to the request if the shipper is not set with the debug flag', async () => { - shipper = new ElasticV3BrowserShipper( - { version: '1.2.3', channelName: 'test-channel' }, - initContext - ); - shipper.reportEvents(events); - shipper.optIn(true); - await jest.advanceTimersByTimeAsync(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - } - ); - }); - - test('handles when the fetch request fails', async () => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - await jest.advanceTimersByTimeAsync(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "Failed to fetch", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_browser", - "type": "failed", - } - `); - }); - - test('handles when the fetch request fails (request completes but not OK response)', async () => { - fetchMock.mockResolvedValue({ - ok: false, - status: 400, - text: () => Promise.resolve('{"status": "not ok"}'), - }); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - await jest.advanceTimersByTimeAsync(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "400", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_browser", - "type": "failed", - } - `); - }); -}); diff --git a/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts b/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts deleted file mode 100644 index c063dcf86a95f..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.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 { - BehaviorSubject, - interval, - Subject, - bufferWhen, - concatMap, - skipWhile, - firstValueFrom, - map, - merge, -} from 'rxjs'; -import type { - AnalyticsClientInitContext, - Event, - EventContext, - IShipper, - TelemetryCounter, -} from '../../../../client'; -import { ElasticV3ShipperOptions, ErrorWithCode } from '../../common'; -import { buildHeaders, buildUrl, createTelemetryCounterHelper, eventsToNDJSON } from '../../common'; - -/** - * Elastic V3 shipper to use in the browser. - */ -export class ElasticV3BrowserShipper implements IShipper { - /** Shipper's unique name */ - public static shipperName = 'elastic_v3_browser'; - - /** Observable to emit the stats of the processed events. */ - public readonly telemetryCounter$ = new Subject<TelemetryCounter>(); - - private readonly reportTelemetryCounters = createTelemetryCounterHelper( - this.telemetryCounter$, - ElasticV3BrowserShipper.shipperName - ); - private readonly url: string; - - private readonly internalQueue$ = new Subject<Event>(); - private readonly flush$ = new Subject<void>(); - private readonly queueFlushed$ = new Subject<void>(); - - private readonly isOptedIn$ = new BehaviorSubject<boolean | undefined>(undefined); - private clusterUuid: string = 'UNKNOWN'; - private licenseId: string | undefined; - - /** - * Creates a new instance of the {@link ElasticV3BrowserShipper}. - * @param options {@link ElasticV3ShipperOptions} - * @param initContext {@link AnalyticsClientInitContext} - */ - constructor( - private readonly options: ElasticV3ShipperOptions, - private readonly initContext: AnalyticsClientInitContext - ) { - this.setUpInternalQueueSubscriber(); - this.url = buildUrl({ - sendTo: options.sendTo ?? initContext.sendTo, - channelName: options.channelName, - }); - } - - /** - * Uses the `cluster_uuid` and `license_id` from the context to hold them in memory for the generation of the headers - * used later on in the HTTP request. - * @param newContext The full new context to set {@link EventContext} - */ - public extendContext(newContext: EventContext) { - if (newContext.cluster_uuid) { - this.clusterUuid = newContext.cluster_uuid; - } - if (newContext.license_id) { - this.licenseId = newContext.license_id; - } - } - - /** - * When `false`, it flushes the internal queue and stops sending events. - * @param isOptedIn `true` for resume sending events. `false` to stop. - */ - public optIn(isOptedIn: boolean) { - this.isOptedIn$.next(isOptedIn); - } - - /** - * Enqueues the events to be sent to in a batched approach. - * @param events batched events {@link Event} - */ - public reportEvents(events: Event[]) { - events.forEach((event) => { - this.internalQueue$.next(event); - }); - } - - /** - * Triggers a flush of the internal queue to attempt to send any events held in the queue - * and resolves the returned promise once the queue is emptied. - */ - public async flush() { - if (this.flush$.isStopped) { - // If called after shutdown, return straight away - return; - } - - const promise = firstValueFrom(this.queueFlushed$); - this.flush$.next(); - await promise; - } - - /** - * Shuts down the shipper. - * Triggers a flush of the internal queue to attempt to send any events held in the queue. - */ - public shutdown() { - this.internalQueue$.complete(); // NOTE: When completing the observable, the buffer logic does not wait and releases any buffered events. - this.flush$.complete(); - } - - private setUpInternalQueueSubscriber() { - this.internalQueue$ - .pipe( - // Buffer events for 1 second or until we have an optIn value - bufferWhen(() => - merge( - this.flush$, - interval(1000).pipe(skipWhile(() => this.isOptedIn$.value === undefined)) - ) - ), - // Send events (one batch at a time) - concatMap(async (events) => { - // Only send if opted-in and there's anything to send - if (this.isOptedIn$.value === true && events.length > 0) { - await this.sendEvents(events); - } - }), - map(() => this.queueFlushed$.next()) - ) - .subscribe(); - } - - private async sendEvents(events: Event[]) { - try { - const code = await this.makeRequest(events); - this.reportTelemetryCounters(events, { code }); - } catch (error) { - this.reportTelemetryCounters(events, { code: error.code, error }); - } - } - - private async makeRequest(events: Event[]): Promise<string> { - const response = await fetch(this.url, { - method: 'POST', - body: eventsToNDJSON(events), - headers: buildHeaders(this.clusterUuid, this.options.version, this.licenseId), - ...(this.options.debug && { query: { debug: true } }), - // Allow the request to outlive the page in case the tab is closed - keepalive: true, - }); - - if (this.options.debug) { - this.initContext.logger.debug( - `[${ElasticV3BrowserShipper.shipperName}]: ${response.status} - ${await response.text()}` - ); - } - - if (!response.ok) { - throw new ErrorWithCode( - `${response.status} - ${await response.text()}`, - `${response.status}` - ); - } - - return `${response.status}`; - } -} diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/README.md b/packages/analytics/ebt/shippers/elastic_v3/common/README.md deleted file mode 100644 index f212c75961d86..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# @kbn/ebt/shippers/elastic_v3/common - -This package holds the common code for the Elastic V3 shippers: - -- Types defining the Shipper configuration `ElasticV3ShipperOptions` -- `buildUrl` utility helps decide which URL to use depending on whether the shipper is configured to send to production or staging. -- `eventsToNdjson` utility converts any array of events to NDJSON format. -- `reportTelemetryCounters` helps with building the TelemetryCounter to emit after processing an event. - -It should be considered an internal package and should not be used other than by the shipper implementations: `@kbn/ebt/shippers/elastic_v3/browser` and `@kbn/ebt/shippers/elastic_v3/server` diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/index.ts b/packages/analytics/ebt/shippers/elastic_v3/common/index.ts deleted file mode 100644 index c832c30664300..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/index.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 and the Server Side Public License, v 1; you may 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 { buildHeaders } from './src/build_headers'; -export { buildUrl } from './src/build_url'; -export type { BuildUrlOptions } from './src/build_url'; -export { ErrorWithCode } from './src/error_with_code'; -export { eventsToNDJSON } from './src/events_to_ndjson'; -export { createTelemetryCounterHelper } from './src/report_telemetry_counters'; -export type { ElasticV3ShipperOptions } from './src/types'; diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.test.ts deleted file mode 100644 index 171491757ea56..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.test.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 { buildUrl } from './build_url'; - -describe('buildUrl', () => { - test('returns production URL', () => { - expect(buildUrl({ sendTo: 'production', channelName: 'test-channel' })).toBe( - 'https://telemetry.elastic.co/v3/send/test-channel' - ); - }); - - test('returns staging URL', () => { - expect(buildUrl({ sendTo: 'staging', channelName: 'test-channel' })).toBe( - 'https://telemetry-staging.elastic.co/v3/send/test-channel' - ); - }); -}); diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.ts deleted file mode 100644 index 6b1ba324f0c37..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.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. - */ - -/** The options to build the URL of the V3 API. */ -export interface BuildUrlOptions { - /** Whether to send it to production or staging. */ - sendTo: 'production' | 'staging'; - /** The name of the channel to send the data to. */ - channelName: string; -} - -/** - * Builds the URL for the V3 API. - * @param urlOptions The options to build the URL of the V3 API. - */ -export function buildUrl(urlOptions: BuildUrlOptions): string { - const { sendTo, channelName } = urlOptions; - const baseUrl = - sendTo === 'production' - ? 'https://telemetry.elastic.co' - : 'https://telemetry-staging.elastic.co'; - return `${baseUrl}/v3/send/${channelName}`; -} diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.test.ts deleted file mode 100644 index 9be5d5bdc762c..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.test.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ErrorWithCode } from './error_with_code'; - -describe('ErrorWithCode', () => { - const error = new ErrorWithCode('test', 'test_code'); - test('message and code properties are publicly accessible', () => { - expect(error.message).toBe('test'); - expect(error.code).toBe('test_code'); - }); - test('extends error', () => { - expect(error).toBeInstanceOf(Error); - }); -}); diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.ts deleted file mode 100644 index a7bad9063bec7..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Custom error to report the error message with an additional error code. - */ -export class ErrorWithCode extends Error { - /** - * Constructor of the error. - * @param message The error message. - * @param code The code of the error. - */ - constructor(message: string, public readonly code: string) { - super(message); - } -} diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.test.ts deleted file mode 100644 index c9bcb58bb1977..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.test.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 and the Server Side Public License, v 1; you may not 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 { Event } from '../../../../client'; -import { eventsToNDJSON } from './events_to_ndjson'; - -describe('eventsToNDJSON', () => { - test('works with one event', () => { - const event: Event = { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type', - context: {}, - properties: {}, - }; - - // Mind the extra line at the bottom - expect(eventsToNDJSON([event])).toMatchInlineSnapshot(` - "{\\"timestamp\\":\\"2020-01-01T00:00:00.000Z\\",\\"event_type\\":\\"event_type\\",\\"context\\":{},\\"properties\\":{}} - " - `); - }); - - test('works with many events', () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type', - context: {}, - properties: {}, - }, - { - timestamp: '2020-01-02T00:00:00.000Z', - event_type: 'event_type', - context: {}, - properties: {}, - }, - ]; - - expect(eventsToNDJSON(events)).toMatchInlineSnapshot(` - "{\\"timestamp\\":\\"2020-01-01T00:00:00.000Z\\",\\"event_type\\":\\"event_type\\",\\"context\\":{},\\"properties\\":{}} - {\\"timestamp\\":\\"2020-01-02T00:00:00.000Z\\",\\"event_type\\":\\"event_type\\",\\"context\\":{},\\"properties\\":{}} - " - `); - }); -}); diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.ts deleted file mode 100644 index bfd5aabc1223a..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { Event } from '../../../../client'; - -/** - * Converts an array of events to a single ndjson string. - * @param events An array of events {@link Event} - */ -export function eventsToNDJSON(events: Event[]): string { - return `${events.map((event) => JSON.stringify(event)).join('\n')}\n`; -} diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts deleted file mode 100644 index a720a0c0129a6..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { firstValueFrom, Subject, take, toArray } from 'rxjs'; -import type { Event, TelemetryCounter } from '../../../../client'; -import { createTelemetryCounterHelper } from './report_telemetry_counters'; - -describe('reportTelemetryCounters', () => { - let reportTelemetryCounters: ReturnType<typeof createTelemetryCounterHelper>; - let telemetryCounter$: Subject<TelemetryCounter>; - - beforeEach(() => { - telemetryCounter$ = new Subject<TelemetryCounter>(); - reportTelemetryCounters = createTelemetryCounterHelper(telemetryCounter$, 'my_shipper'); - }); - - test('emits a success counter for one event', async () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_a', - context: {}, - properties: {}, - }, - ]; - - const counters = firstValueFrom(telemetryCounter$); - - reportTelemetryCounters(events); - - await expect(counters).resolves.toMatchInlineSnapshot(` - Object { - "code": "OK", - "count": 1, - "event_type": "event_type_a", - "source": "my_shipper", - "type": "succeeded", - } - `); - }); - - test('emits a success counter for one event with custom code', async () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_a', - context: {}, - properties: {}, - }, - ]; - - const counters = firstValueFrom(telemetryCounter$); - - reportTelemetryCounters(events, { code: 'my_code' }); - - await expect(counters).resolves.toMatchInlineSnapshot(` - Object { - "code": "my_code", - "count": 1, - "event_type": "event_type_a", - "source": "my_shipper", - "type": "succeeded", - } - `); - }); - - test('emits a counter with custom type', async () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_a', - context: {}, - properties: {}, - }, - ]; - - const counters = firstValueFrom(telemetryCounter$); - - reportTelemetryCounters(events, { - type: 'dropped', - code: 'my_code', - }); - - await expect(counters).resolves.toMatchInlineSnapshot(` - Object { - "code": "my_code", - "count": 1, - "event_type": "event_type_a", - "source": "my_shipper", - "type": "dropped", - } - `); - }); - - test('emits a failure counter for one event with error message as a code', async () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_a', - context: {}, - properties: {}, - }, - ]; - - const counters = firstValueFrom(telemetryCounter$); - - reportTelemetryCounters(events, { - error: new Error('Something went terribly wrong'), - }); - - await expect(counters).resolves.toMatchInlineSnapshot(` - Object { - "code": "Something went terribly wrong", - "count": 1, - "event_type": "event_type_a", - "source": "my_shipper", - "type": "failed", - } - `); - }); - - test('emits a failure counter for one event with custom code', async () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_a', - context: {}, - properties: {}, - }, - ]; - - const counters = firstValueFrom(telemetryCounter$); - - reportTelemetryCounters(events, { - code: 'my_code', - error: new Error('Something went terribly wrong'), - }); - - await expect(counters).resolves.toMatchInlineSnapshot(` - Object { - "code": "my_code", - "count": 1, - "event_type": "event_type_a", - "source": "my_shipper", - "type": "failed", - } - `); - }); - - test('emits a success counter for multiple events of different types', async () => { - const events: Event[] = [ - // 2 types a - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_a', - context: {}, - properties: {}, - }, - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_a', - context: {}, - properties: {}, - }, - // 1 type b - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'event_type_b', - context: {}, - properties: {}, - }, - ]; - - const counters = firstValueFrom(telemetryCounter$.pipe(take(2), toArray())); - - reportTelemetryCounters(events); - - await expect(counters).resolves.toMatchInlineSnapshot(` - Array [ - Object { - "code": "OK", - "count": 2, - "event_type": "event_type_a", - "source": "my_shipper", - "type": "succeeded", - }, - Object { - "code": "OK", - "count": 1, - "event_type": "event_type_b", - "source": "my_shipper", - "type": "succeeded", - }, - ] - `); - }); -}); diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.ts deleted file mode 100644 index 68260e653e78d..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { Subject } from 'rxjs'; -import type { Event, TelemetryCounter, TelemetryCounterType } from '../../../../client'; - -/** - * Creates a telemetry counter helper to make it easier to generate them - * @param telemetryCounter$ The observable that will be used to emit the telemetry counters - * @param source The name of the shipper that is sending the events. - */ -export function createTelemetryCounterHelper( - telemetryCounter$: Subject<TelemetryCounter>, - source: string -) { - /** - * Triggers a telemetry counter for each event type. - * @param events The events to trigger the telemetry counter for. - * @param type The type of telemetry counter to trigger. - * @param code The success or error code for additional detail about the result. - * @param error The error that occurred, if any. - */ - return ( - events: Event[], - { - type, - code, - error, - }: { - type?: TelemetryCounterType; - code?: string; - error?: Error; - } = {} - ) => { - const eventTypeCounts = countEventTypes(events); - Object.entries(eventTypeCounts).forEach(([eventType, count]) => { - telemetryCounter$.next({ - source, - type: type ?? (error ? 'failed' : 'succeeded'), - code: code ?? error?.message ?? 'OK', - count, - event_type: eventType, - }); - }); - }; -} - -function countEventTypes(events: Event[]) { - return events.reduce((acc, event) => { - if (acc[event.event_type]) { - acc[event.event_type] += 1; - } else { - acc[event.event_type] = 1; - } - return acc; - }, {} as Record<string, number>); -} diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts deleted file mode 100644 index c27aa448119fe..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/types.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Options for the Elastic V3 shipper - */ -export interface ElasticV3ShipperOptions { - /** - * The name of the channel to stream all the events to. - */ - channelName: string; - /** - * The product's version. - */ - version: string; - /** - * Provide it to override the Analytics client's default configuration. - */ - sendTo?: 'staging' | 'production'; - /** - * Should show debug information about the requests it makes to the V3 API. - */ - debug?: boolean; -} diff --git a/packages/analytics/ebt/shippers/elastic_v3/server/README.md b/packages/analytics/ebt/shippers/elastic_v3/server/README.md deleted file mode 100644 index 0c27cef5a0023..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/server/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# @kbn/ebt/shippers/elastic_v3/server - -Server-side implementation of the Elastic V3 shipper for the `@kbn/ebt/client`. - -## How to use it - -This module is intended to be used **on the server-side only**. It is specially designed to apply the necessary backpressure mechanisms to prevent the server from getting overloaded with too many events and identify if the server sits behind a firewall to discard any incoming events. Refer to `@kbn/ebt/shippers/elastic_v3/browser` for the browser-side implementation. - -```typescript -import { ElasticV3ServerShipper } from "@kbn/ebt/shippers/elastic_v3/server"; - -analytics.registerShipper(ElasticV3ServerShipper, { channelName: 'myChannel', version: '1.0.0' }); -``` - -## Configuration - -| Name | Description | -|:-------------:|:-------------------------------------------------------------------------------------------| -| `channelName` | The name of the channel to send the events. | -| `version` | The version of the application generating the events. | -| `debug` | When `true`, it logs the responses from the remote Telemetry Service. Defaults to `false`. | - -## Transmission protocol - -This shipper sends the events to the Elastic Internal Telemetry Service. It holds up to 1000 events in a shared queue. Any additional incoming events once it's full will be dropped. It sends the events from the queue in batches of up to 10kB every 10 seconds. When shutting down, it'll send all the remaining events in the queue. diff --git a/packages/analytics/ebt/shippers/elastic_v3/server/index.ts b/packages/analytics/ebt/shippers/elastic_v3/server/index.ts deleted file mode 100644 index c0031aac4b217..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/server/index.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { ElasticV3ShipperOptions } from '../common'; -export { ElasticV3ServerShipper } from './src/server_shipper'; diff --git a/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.mocks.ts b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.mocks.ts deleted file mode 100644 index 8da36242d48b6..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.mocks.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 and the Server Side Public License, v 1; you may 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 fetchMock = jest.fn().mockResolvedValue({ - status: 200, - ok: true, - text: () => Promise.resolve('{"status": "ok"}'), -}); - -jest.doMock('node-fetch', () => fetchMock); diff --git a/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.ts b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.ts deleted file mode 100644 index 1e973ed4c9819..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.ts +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { loggerMock } from '@kbn/logging-mocks'; -import { firstValueFrom } from 'rxjs'; -import type { AnalyticsClientInitContext, Event } from '../../../../client'; -import { fetchMock } from './server_shipper.test.mocks'; -import { ElasticV3ServerShipper } from './server_shipper'; - -const SECONDS = 1000; -const MINUTES = 60 * SECONDS; - -describe('ElasticV3ServerShipper', () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'test-event-type', - context: {}, - properties: {}, - }, - ]; - - const initContext: AnalyticsClientInitContext = { - sendTo: 'staging', - isDev: true, - logger: loggerMock.create(), - }; - - let shipper: ElasticV3ServerShipper; - - // eslint-disable-next-line dot-notation - const setLastBatchSent = (ms: number) => (shipper['lastBatchSent'] = ms); - - beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: false }); - - shipper = new ElasticV3ServerShipper( - { version: '1.2.3', channelName: 'test-channel', debug: true }, - initContext - ); - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = null; // The tests think connectivity is OK initially for easier testing. - }); - - afterEach(() => { - shipper.shutdown(); - jest.clearAllMocks(); - }); - - test('set optIn should update the isOptedIn$ observable', () => { - // eslint-disable-next-line dot-notation - const getInternalOptIn = () => shipper['isOptedIn$'].value; - - // Initially undefined - expect(getInternalOptIn()).toBeUndefined(); - - shipper.optIn(true); - expect(getInternalOptIn()).toBe(true); - - shipper.optIn(false); - expect(getInternalOptIn()).toBe(false); - }); - - test('clears the queue after optIn: false', () => { - shipper.reportEvents(events); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1); - - shipper.optIn(false); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - }); - - test('set extendContext should store local values: clusterUuid and licenseId', () => { - // eslint-disable-next-line dot-notation - const getInternalClusterUuid = () => shipper['clusterUuid']; - // eslint-disable-next-line dot-notation - const getInternalLicenseId = () => shipper['licenseId']; - - // Initial values - expect(getInternalClusterUuid()).toBe('UNKNOWN'); - expect(getInternalLicenseId()).toBeUndefined(); - - shipper.extendContext({ cluster_uuid: 'test-cluster-uuid' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); - expect(getInternalLicenseId()).toBeUndefined(); - - shipper.extendContext({ license_id: 'test-license-id' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); - expect(getInternalLicenseId()).toBe('test-license-id'); - - shipper.extendContext({ cluster_uuid: 'test-cluster-uuid-2', license_id: 'test-license-id-2' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid-2'); - expect(getInternalLicenseId()).toBe('test-license-id-2'); - }); - - test('calls to reportEvents do not call `fetch` straight away', () => { - shipper.reportEvents(events); - expect(fetchMock).not.toHaveBeenCalled(); - }); - - test('calls to reportEvents do not call `fetch` after 10 minutes because no optIn value is set yet', async () => { - shipper.reportEvents(events); - await jest.advanceTimersByTimeAsync(10 * MINUTES); - expect(fetchMock).not.toHaveBeenCalled(); - }); - - test('calls to reportEvents call `fetch` after 10 seconds when optIn value is set to true', async () => { - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "succeeded", - } - `); - }); - - test('calls to reportEvents do not call `fetch` after 10 seconds when optIn value is set to false', async () => { - shipper.reportEvents(events); - shipper.optIn(false); - setLastBatchSent(Date.now() - 10 * SECONDS); - await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).not.toHaveBeenCalled(); - }); - - test('calls to reportEvents call `fetch` when shutting down if optIn value is set to true', async () => { - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - shipper.shutdown(); - jest.advanceTimersToNextTimer(); // We are handling the shutdown in a promise, so we need to wait for the next tick. - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "succeeded", - } - `); - }); - - test('does not add the query.debug: true property to the request if the shipper is not set with the debug flag', async () => { - shipper = new ElasticV3ServerShipper( - { version: '1.2.3', channelName: 'test-channel' }, - initContext - ); - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = null; - shipper.reportEvents(events); - shipper.optIn(true); - setLastBatchSent(Date.now() - 10 * SECONDS); - await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - } - ); - }); - - test('sends when the queue overflows the 10kB leaky bucket one batch every 10s', async () => { - expect.assertions(2 * 9 + 2); - - shipper.reportEvents(new Array(1000).fill(events[0])); - shipper.optIn(true); - - // Due to the size of the test events, it matches 8 rounds. - for (let i = 0; i < 9; i++) { - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenNthCalledWith( - i + 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: new Array(103) - .fill( - '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n' - ) - .join(''), - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 103, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "succeeded", - } - `); - jest.advanceTimersToNextTimer(); - } - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1000 - 9 * 103); // 73 - - // If we call it again, it should not enqueue all the events (only the ones to fill the queue): - shipper.reportEvents(new Array(1000).fill(events[0])); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1000); - }); - - test('handles when the fetch request fails', async () => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "Failed to fetch", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "failed", - } - `); - }); - - test('handles when the fetch request fails (request completes but not OK response)', async () => { - fetchMock.mockResolvedValueOnce({ - ok: false, - status: 400, - text: () => Promise.resolve('{"status": "not ok"}'), - }); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "400", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "failed", - } - `); - }); - - describe('Connectivity Checks', () => { - describe('connectivity check when connectivity is confirmed (firstTimeOffline === null)', () => { - test.each([undefined, false, true])('does not run for opt-in %p', async (optInValue) => { - if (optInValue !== undefined) { - shipper.optIn(optInValue); - } - - // From the start, it doesn't check connectivity because already confirmed - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - - // Wait a big time (1 minute should be enough, but for the sake of tests...) - await jest.advanceTimersByTimeAsync(10 * MINUTES); - - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - }); - - describe('connectivity check with initial unknown state of the connectivity', () => { - beforeEach(() => { - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = undefined; // Initial unknown state of the connectivity - }); - - test.each([undefined, false])('does not run for opt-in %p', async (optInValue) => { - if (optInValue !== undefined) { - shipper.optIn(optInValue); - } - - // From the start, it doesn't check connectivity because already confirmed - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - - // Wait a big time (1 minute should be enough, but for the sake of tests...) - await jest.advanceTimersByTimeAsync(10 * MINUTES); - - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - - test('runs as soon as opt-in is set to true', () => { - shipper.optIn(true); - - // From the start, it doesn't check connectivity because opt-in is not true - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - }); - - describe('connectivity check with the connectivity confirmed to be faulty', () => { - beforeEach(() => { - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = 100; // Failed at some point - }); - - test.each([undefined, false])('does not run for opt-in %p', async (optInValue) => { - if (optInValue !== undefined) { - shipper.optIn(optInValue); - } - - // From the start, it doesn't check connectivity because already confirmed - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - - // Wait a big time (1 minute should be enough, but for the sake of tests...) - await jest.advanceTimersByTimeAsync(10 * MINUTES); - - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - - test('runs as soon as opt-in is set to true', () => { - shipper.optIn(true); - - // From the start, it doesn't check connectivity because opt-in is not true - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - }); - - describe('after report failure', () => { - // generate the report failure for each test - beforeEach(async () => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "Failed to fetch", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "failed", - } - `); - }); - - test('connectivity check runs periodically', async () => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - await jest.advanceTimersByTimeAsync(1 * MINUTES); - expect(fetchMock).toHaveBeenNthCalledWith( - 2, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - fetchMock.mockResolvedValueOnce({ ok: false }); - await jest.advanceTimersByTimeAsync(2 * MINUTES); - expect(fetchMock).toHaveBeenNthCalledWith( - 3, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - }); - - describe('after being offline for longer than 24h', () => { - beforeEach(() => { - shipper.optIn(true); - shipper.reportEvents(events); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1); - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = 100; - }); - - test('the following connectivity check clears the queue', async () => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - await jest.advanceTimersByTimeAsync(1 * MINUTES); - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - }); - - test('new events are not added to the queue', async () => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - await jest.advanceTimersByTimeAsync(1 * MINUTES); - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - - shipper.reportEvents(events); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - }); - - test('regains the connection', async () => { - fetchMock.mockResolvedValueOnce({ ok: true }); - await jest.advanceTimersByTimeAsync(1 * MINUTES); - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - // eslint-disable-next-line dot-notation - expect(shipper['firstTimeOffline']).toBe(null); - - await jest.advanceTimersByTimeAsync(10 * MINUTES); - expect(fetchMock).not.toHaveBeenNthCalledWith( - 2, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - }); - }); - - describe('flush method', () => { - test('resolves straight away if it should not send anything', async () => { - await expect(shipper.flush()).resolves.toBe(undefined); - }); - - test('resolves when all the ongoing requests are complete', async () => { - shipper.optIn(true); - shipper.reportEvents(events); - expect(fetchMock).toHaveBeenCalledTimes(0); - fetchMock.mockImplementation(async () => { - // eslint-disable-next-line dot-notation - expect(shipper['inFlightRequests$'].value).toBe(1); - }); - await expect(shipper.flush()).resolves.toBe(undefined); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - }); - - test('calling flush multiple times does not keep hanging', async () => { - await expect(shipper.flush()).resolves.toBe(undefined); - await expect(shipper.flush()).resolves.toBe(undefined); - await Promise.all([shipper.flush(), shipper.flush()]); - }); - - test('calling flush after shutdown does not keep hanging', async () => { - shipper.shutdown(); - await expect(shipper.flush()).resolves.toBe(undefined); - }); - }); -}); diff --git a/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts deleted file mode 100644 index d3b54da3314cf..0000000000000 --- a/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import fetch from 'node-fetch'; -import { - filter, - Subject, - ReplaySubject, - interval, - merge, - timer, - retryWhen, - tap, - delayWhen, - takeUntil, - map, - BehaviorSubject, - exhaustMap, - mergeMap, - skip, - firstValueFrom, -} from 'rxjs'; -import { - type ElasticV3ShipperOptions, - buildHeaders, - buildUrl, - createTelemetryCounterHelper, - eventsToNDJSON, - ErrorWithCode, -} from '../../common'; -import type { - AnalyticsClientInitContext, - Event, - EventContext, - IShipper, - TelemetryCounter, -} from '../../../../client'; - -const SECOND = 1000; -const MINUTE = 60 * SECOND; -const HOUR = 60 * MINUTE; -const KIB = 1024; -const MAX_NUMBER_OF_EVENTS_IN_INTERNAL_QUEUE = 1000; -const MIN_TIME_SINCE_LAST_SEND = 10 * SECOND; - -/** - * Elastic V3 shipper to use on the server side. - */ -export class ElasticV3ServerShipper implements IShipper { - /** Shipper's unique name */ - public static shipperName = 'elastic_v3_server'; - - /** Observable to emit the stats of the processed events. */ - public readonly telemetryCounter$ = new Subject<TelemetryCounter>(); - - private readonly reportTelemetryCounters = createTelemetryCounterHelper( - this.telemetryCounter$, - ElasticV3ServerShipper.shipperName - ); - - private readonly internalQueue: Event[] = []; - private readonly shutdown$ = new ReplaySubject<void>(1); - private readonly flush$ = new Subject<void>(); - private readonly inFlightRequests$ = new BehaviorSubject<number>(0); - private readonly isOptedIn$ = new BehaviorSubject<boolean | undefined>(undefined); - - private readonly url: string; - - private lastBatchSent = Date.now(); - - private clusterUuid: string = 'UNKNOWN'; - private licenseId?: string; - - /** - * Specifies when it went offline: - * - `undefined` means it doesn't know yet whether it is online or offline - * - `null` means it's online - * - `number` means it's offline since that time - * @private - */ - private firstTimeOffline?: number | null; - - /** - * Creates a new instance of the {@link ElasticV3ServerShipper}. - * @param options {@link ElasticV3ShipperOptions} - * @param initContext {@link AnalyticsClientInitContext} - */ - constructor( - private readonly options: ElasticV3ShipperOptions, - private readonly initContext: AnalyticsClientInitContext - ) { - this.url = buildUrl({ - sendTo: options.sendTo ?? initContext.sendTo, - channelName: options.channelName, - }); - this.setInternalSubscriber(); - this.checkConnectivity(); - } - - /** - * Uses the `cluster_uuid` and `license_id` from the context to hold them in memory for the generation of the headers - * used later on in the HTTP request. - * @param newContext The full new context to set {@link EventContext} - */ - public extendContext(newContext: EventContext) { - if (newContext.cluster_uuid) { - this.clusterUuid = newContext.cluster_uuid; - } - if (newContext.license_id) { - this.licenseId = newContext.license_id; - } - } - - /** - * When `false`, it flushes the internal queue and stops sending events. - * @param isOptedIn `true` for resume sending events. `false` to stop. - */ - public optIn(isOptedIn: boolean) { - this.isOptedIn$.next(isOptedIn); - - if (isOptedIn === false) { - this.internalQueue.length = 0; - } - } - - /** - * Enqueues the events to be sent via the leaky bucket algorithm. - * @param events batched events {@link Event} - */ - public reportEvents(events: Event[]) { - // If opted out OR offline for longer than 24 hours, skip processing any events. - if ( - this.isOptedIn$.value === false || - (this.firstTimeOffline && Date.now() - this.firstTimeOffline > 24 * HOUR) - ) { - return; - } - - const freeSpace = MAX_NUMBER_OF_EVENTS_IN_INTERNAL_QUEUE - this.internalQueue.length; - - // As per design, we only want store up-to 1000 events at a time. Drop anything that goes beyond that limit - if (freeSpace < events.length) { - const toDrop = events.length - freeSpace; - const droppedEvents = events.splice(-toDrop, toDrop); - this.reportTelemetryCounters(droppedEvents, { - type: 'dropped', - code: 'queue_full', - }); - } - - this.internalQueue.push(...events); - } - - /** - * Triggers a flush of the internal queue to attempt to send any events held in the queue - * and resolves the returned promise once the queue is emptied. - */ - public async flush() { - if (this.flush$.isStopped) { - // If called after shutdown, return straight away - return; - } - - const promise = firstValueFrom( - this.inFlightRequests$.pipe( - skip(1), // Skipping the first value because BehaviourSubjects always emit the current value on subscribe. - filter((count) => count === 0) // Wait until all the inflight requests are completed. - ) - ); - this.flush$.next(); - await promise; - } - - /** - * Shuts down the shipper. - * Triggers a flush of the internal queue to attempt to send any events held in the queue. - */ - public shutdown() { - this.shutdown$.next(); - this.flush$.complete(); - this.shutdown$.complete(); - this.isOptedIn$.complete(); - } - - /** - * Checks the server has connectivity to the remote endpoint. - * The frequency of the connectivity tests will back off, starting with 1 minute, and multiplying by 2 - * until it reaches 1 hour. Then, it’ll keep the 1h frequency until it reaches 24h without connectivity. - * At that point, it clears the queue and stops accepting events in the queue. - * The connectivity checks will continue to happen every 1 hour just in case it regains it at any point. - * @private - */ - private checkConnectivity() { - let backoff = 1 * MINUTE; - merge( - timer(0, 1 * MINUTE), - // Also react to opt-in changes to avoid being stalled for 1 minute for the first connectivity check. - // More details in: https://github.com/elastic/kibana/issues/135647 - this.isOptedIn$ - ) - .pipe( - takeUntil(this.shutdown$), - filter(() => this.isOptedIn$.value === true && this.firstTimeOffline !== null), - // Using exhaustMap here because one request at a time is enough to check the connectivity. - exhaustMap(async () => { - const { ok } = await fetch(this.url, { - method: 'OPTIONS', - }); - - if (!ok) { - throw new Error(`Failed to connect to ${this.url}`); - } - - this.firstTimeOffline = null; - backoff = 1 * MINUTE; - }), - retryWhen((errors) => - errors.pipe( - takeUntil(this.shutdown$), - tap(() => { - if (!this.firstTimeOffline) { - this.firstTimeOffline = Date.now(); - } else if (Date.now() - this.firstTimeOffline > 24 * HOUR) { - this.internalQueue.length = 0; - } - backoff = backoff * 2; - if (backoff > 1 * HOUR) { - backoff = 1 * HOUR; - } - }), - delayWhen(() => timer(backoff)) - ) - ) - ) - .subscribe(); - } - - private setInternalSubscriber() { - // Create an emitter that emits when MIN_TIME_SINCE_LAST_SEND have passed since the last time we sent the data - const minimumTimeSinceLastSent$ = interval(SECOND).pipe( - filter(() => Date.now() - this.lastBatchSent >= MIN_TIME_SINCE_LAST_SEND) - ); - - merge( - minimumTimeSinceLastSent$.pipe( - takeUntil(this.shutdown$), - map(() => ({ shouldFlush: false })) - ), - // Whenever a `flush` request comes in - this.flush$.pipe(map(() => ({ shouldFlush: true }))), - // Attempt to send one last time on shutdown, flushing the queue - this.shutdown$.pipe(map(() => ({ shouldFlush: true }))) - ) - .pipe( - // Only move ahead if it's opted-in and online, and there are some events in the queue - filter(() => { - const shouldSendAnything = - this.isOptedIn$.value === true && - this.firstTimeOffline === null && - this.internalQueue.length > 0; - - // If it should not send anything, re-emit the inflight request observable just in case it's already 0 - if (!shouldSendAnything) { - this.inFlightRequests$.next(this.inFlightRequests$.value); - } - - return shouldSendAnything; - }), - - // Send the events: - // 1. Set lastBatchSent and retrieve the events to send (clearing the queue) in a synchronous operation to avoid race conditions. - map(({ shouldFlush }) => { - this.lastBatchSent = Date.now(); - return this.getEventsToSend(shouldFlush); - }), - // 2. Skip empty buffers (just to be sure) - filter((events) => events.length > 0), - // 3. Actually send the events - // Using `mergeMap` here because we want to send events whenever the emitter says so: - // We don't want to skip emissions (exhaustMap) or enqueue them (concatMap). - mergeMap((eventsToSend) => this.sendEvents(eventsToSend)) - ) - .subscribe(); - } - - /** - * Calculates the size of the queue in bytes. - * @returns The number of bytes held in the queue. - * @private - */ - private getQueueByteSize(queue: Event[]) { - return queue.reduce((acc, event) => { - return acc + this.getEventSize(event); - }, 0); - } - - /** - * Calculates the size of the event in bytes. - * @param event The event to calculate the size of. - * @returns The number of bytes held in the event. - * @private - */ - private getEventSize(event: Event) { - return Buffer.from(JSON.stringify(event)).length; - } - - /** - * Returns a queue of events of up-to 10kB. Or all events in the queue if it's a FLUSH action. - * @remarks It mutates the internal queue by removing from it the events returned by this method. - * @private - */ - private getEventsToSend(shouldFlush: boolean): Event[] { - // If the internal queue is already smaller than the minimum batch size, or it's a flush action, do a direct assignment. - if (shouldFlush || this.getQueueByteSize(this.internalQueue) < 10 * KIB) { - return this.internalQueue.splice(0, this.internalQueue.length); - } - // Otherwise, we'll feed the events to the leaky bucket queue until we reach 10kB. - const queue: Event[] = []; - let queueByteSize = 0; - while (queueByteSize < 10 * KIB) { - const event = this.internalQueue.shift()!; - queueByteSize += this.getEventSize(event); - queue.push(event); - } - return queue; - } - - private async sendEvents(events: Event[]) { - this.initContext.logger.debug(`Reporting ${events.length} events...`); - this.inFlightRequests$.next(this.inFlightRequests$.value + 1); - try { - const code = await this.makeRequest(events); - this.reportTelemetryCounters(events, { code }); - this.initContext.logger.debug(`Reported ${events.length} events...`); - } catch (error) { - this.initContext.logger.debug(`Failed to report ${events.length} events...`); - this.initContext.logger.debug(error); - this.reportTelemetryCounters(events, { code: error.code, error }); - this.firstTimeOffline = undefined; - } - this.inFlightRequests$.next(Math.max(0, this.inFlightRequests$.value - 1)); - } - - private async makeRequest(events: Event[]): Promise<string> { - const response = await fetch(this.url, { - method: 'POST', - body: eventsToNDJSON(events), - headers: buildHeaders(this.clusterUuid, this.options.version, this.licenseId), - ...(this.options.debug && { query: { debug: true } }), - }); - - if (this.options.debug) { - this.initContext.logger.debug(`${response.status} - ${await response.text()}`); - } - - if (!response.ok) { - throw new ErrorWithCode( - `${response.status} - ${await response.text()}`, - `${response.status}` - ); - } - - return `${response.status}`; - } -} diff --git a/packages/analytics/ebt/shippers/fullstory/README.md b/packages/analytics/ebt/shippers/fullstory/README.md deleted file mode 100644 index a1e557a7600b8..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# @kbn/ebt/shippers/fullstory - -FullStory implementation as a shipper for the `@kbn/ebt/client`. - -## How to use it - -This module is intended to be used **on the browser only**. It does not support server-side events. - -```typescript -import { FullStoryShipper } from "@kbn/ebt/shippers/fullstory"; - -analytics.registerShipper(FullStoryShipper, { fullStoryOrgId: '12345' }) -``` - -## Configuration - -| Name | Description | -|:----------------:|:----------------------------------------------------------------------------------------------------------------------------------| -| `fullStoryOrgId` | FullStory account ID | -| `host` | The host to send the data to. Used to overcome AdBlockers by using custom DNSs. If not specified, it defaults to `fullstory.com`. | -| `scriptUrl` | The URL to load the FullStory client from. Falls back to `edge.fullstory.com/s/fs.js` if not specified. | -| `debug` | Whether the debug logs should be printed to the console. Defaults to `false`. | -| `namespace` | The name of the variable where the API is stored: `window[namespace]`. Defaults to `FS`. | - -## FullStory Custom Events Rate Limits - -FullStory limits the number of custom events that can be sent per second ([docs](https://help.fullstory.com/hc/en-us/articles/360020623234#custom-property-rate-limiting)). In order to comply with that limit, this shipper will only emit the event types registered in the allow-list defined in the constant [CUSTOM_EVENT_TYPES_ALLOWLIST](./src/fullstory_shipper.ts). We may change this behaviour in the future to a remotely-controlled list of events or rely on the opt-in _cherry-pick_ config mechanism of the Analytics Client. - -## Transmission protocol - -This shipper relies on FullStory official snippet. The internals about how it transfers the data are not documented. diff --git a/packages/analytics/ebt/shippers/fullstory/index.ts b/packages/analytics/ebt/shippers/fullstory/index.ts deleted file mode 100644 index 18c8ba5d36c26..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/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 and the Server Side Public License, v 1; you may 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 { FullStoryShipper } from './src/fullstory_shipper'; -export type { FullStoryShipperConfig } from './src/fullstory_shipper'; -export type { FullStorySnippetConfig } from './src/load_snippet'; diff --git a/packages/analytics/ebt/shippers/fullstory/src/format_payload.test.ts b/packages/analytics/ebt/shippers/fullstory/src/format_payload.test.ts deleted file mode 100644 index 6f9ad05bc9da7..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/format_payload.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { formatPayload } from './format_payload'; - -describe('formatPayload', () => { - test('appends `_str` to string values', () => { - const payload = { - foo: 'bar', - baz: ['qux'], - }; - - expect(formatPayload(payload)).toEqual({ - foo_str: payload.foo, - baz_strs: payload.baz, - }); - }); - - test('appends `_int` to integer values', () => { - const payload = { - foo: 1, - baz: [100000], - }; - - expect(formatPayload(payload)).toEqual({ - foo_int: payload.foo, - baz_ints: payload.baz, - }); - }); - - test('appends `_real` to integer values', () => { - const payload = { - foo: 1.5, - baz: [100000.5], - }; - - expect(formatPayload(payload)).toEqual({ - foo_real: payload.foo, - baz_reals: payload.baz, - }); - }); - - test('appends `_bool` to booleans values', () => { - const payload = { - foo: true, - baz: [false], - }; - - expect(formatPayload(payload)).toEqual({ - foo_bool: payload.foo, - baz_bools: payload.baz, - }); - }); - - test('appends `_date` to Date values', () => { - const payload = { - foo: new Date(), - baz: [new Date()], - }; - - expect(formatPayload(payload)).toEqual({ - foo_date: payload.foo, - baz_dates: payload.baz, - }); - }); - - test('supports nested values', () => { - const payload = { - nested: { - foo: 'bar', - baz: ['qux'], - }, - }; - - expect(formatPayload(payload)).toEqual({ - nested: { - foo_str: payload.nested.foo, - baz_strs: payload.nested.baz, - }, - }); - }); - - test('does not mutate reserved keys', () => { - const payload = { - uid: 'uid', - displayName: 'displayName', - email: 'email', - acctId: 'acctId', - website: 'website', - pageName: 'pageName', - }; - - expect(formatPayload(payload)).toEqual(payload); - }); - - test('removes undefined values', () => { - const payload = { - foo: undefined, - baz: [undefined], - }; - - expect(formatPayload(payload)).toEqual({}); - }); - - test('throws if null is provided', () => { - const payload = { - foo: null, - baz: [null], - }; - - expect(() => formatPayload(payload)).toThrowErrorMatchingInlineSnapshot( - `"Unsupported type: object"` - ); - }); - - describe('String to Date identification', () => { - test('appends `_date` to ISO string values', () => { - const payload = { - foo: new Date().toISOString(), - baz: [new Date().toISOString()], - }; - - expect(formatPayload(payload)).toEqual({ - foo_date: payload.foo, - baz_dates: payload.baz, - }); - }); - - test('appends `_str` to random string values', () => { - const payload = { - foo: 'test-1', - baz: ['test-1'], - }; - - expect(formatPayload(payload)).toEqual({ - foo_str: payload.foo, - baz_strs: payload.baz, - }); - }); - }); -}); diff --git a/packages/analytics/ebt/shippers/fullstory/src/format_payload.ts b/packages/analytics/ebt/shippers/fullstory/src/format_payload.ts deleted file mode 100644 index 03873617af8fc..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/format_payload.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 moment from 'moment'; - -// https://help.fullstory.com/hc/en-us/articles/360020623234#reserved-properties -const FULLSTORY_RESERVED_PROPERTIES = [ - 'uid', - 'displayName', - 'email', - 'acctId', - 'website', - // https://developer.fullstory.com/page-variables - 'pageName', -]; - -export function formatPayload(context: object): Record<string, unknown> { - // format context keys as required for env vars, see docs: https://help.fullstory.com/hc/en-us/articles/360020623234 - return Object.fromEntries( - Object.entries(context) - // Discard any undefined values - .map<[string, unknown]>(([key, value]) => { - return Array.isArray(value) - ? [key, value.filter((v) => typeof v !== 'undefined')] - : [key, value]; - }) - .filter( - ([, value]) => typeof value !== 'undefined' && (!Array.isArray(value) || value.length > 0) - ) - // Transform key names according to the FullStory needs - .map(([key, value]) => { - if (FULLSTORY_RESERVED_PROPERTIES.includes(key)) { - return [key, value]; - } - if (isRecord(value)) { - return [key, formatPayload(value)]; - } - const valueType = getFullStoryType(value); - const formattedKey = valueType ? `${key}_${valueType}` : key; - return [formattedKey, value]; - }) - ); -} - -function getFullStoryType(value: unknown) { - // For arrays, make the decision based on the first element - const isArray = Array.isArray(value); - const v = isArray ? value[0] : value; - let type: string; - switch (typeof v) { - case 'string': - type = moment(v, moment.ISO_8601, true).isValid() ? 'date' : 'str'; - break; - case 'number': - type = Number.isInteger(v) ? 'int' : 'real'; - break; - case 'boolean': - type = 'bool'; - break; - case 'object': - if (isDate(v)) { - type = 'date'; - break; - } - default: - throw new Error(`Unsupported type: ${typeof v}`); - } - - // convert to plural form for arrays - return isArray ? `${type}s` : type; -} - -function isRecord(value: unknown): value is Record<string, unknown> { - return typeof value === 'object' && value !== null && !Array.isArray(value) && !isDate(value); -} - -function isDate(value: unknown): value is Date { - return value instanceof Date; -} diff --git a/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.mocks.ts b/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.mocks.ts deleted file mode 100644 index 7608606c39395..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.mocks.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import * as RxJS from 'rxjs'; -import type { FullStoryApi } from './types'; - -export const fullStoryApiMock: jest.Mocked<FullStoryApi> = { - identify: jest.fn(), - setUserVars: jest.fn(), - setVars: jest.fn(), - consent: jest.fn(), - restart: jest.fn(), - shutdown: jest.fn(), - event: jest.fn(), -}; -jest.doMock('./load_snippet', () => { - return { - loadSnippet: () => fullStoryApiMock, - }; -}); - -jest.doMock('rxjs', () => { - return { - ...RxJS, - debounceTime: () => RxJS.identity, - }; -}); diff --git a/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.ts b/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.ts deleted file mode 100644 index 88f53fd7f837d..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.ts +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { loggerMock } from '@kbn/logging-mocks'; -import { fullStoryApiMock } from './fullstory_shipper.test.mocks'; -import { FullStoryShipper } from './fullstory_shipper'; - -describe('FullStoryShipper', () => { - let fullstoryShipper: FullStoryShipper; - - beforeEach(() => { - jest.resetAllMocks(); - fullstoryShipper = new FullStoryShipper( - { - debug: true, - fullStoryOrgId: 'test-org-id', - }, - { - logger: loggerMock.create(), - sendTo: 'staging', - isDev: true, - } - ); - }); - - afterEach(() => { - fullstoryShipper.shutdown(); - }); - - describe('extendContext', () => { - describe('FS.identify', () => { - test('calls `identify` when the userId is provided', () => { - const userId = 'test-user-id'; - fullstoryShipper.extendContext({ userId }); - expect(fullStoryApiMock.identify).toHaveBeenCalledWith(userId); - }); - - test('calls `identify` again only if the userId changes', () => { - const userId = 'test-user-id'; - fullstoryShipper.extendContext({ userId }); - expect(fullStoryApiMock.identify).toHaveBeenCalledTimes(1); - expect(fullStoryApiMock.identify).toHaveBeenCalledWith(userId); - - fullstoryShipper.extendContext({ userId }); - expect(fullStoryApiMock.identify).toHaveBeenCalledTimes(1); // still only called once - - fullstoryShipper.extendContext({ userId: `${userId}-1` }); - expect(fullStoryApiMock.identify).toHaveBeenCalledTimes(2); // called again because the user changed - expect(fullStoryApiMock.identify).toHaveBeenCalledWith(`${userId}-1`); - }); - }); - - describe('FS.setUserVars', () => { - test('calls `setUserVars` when isElasticCloudUser: true is provided', () => { - fullstoryShipper.extendContext({ isElasticCloudUser: true }); - expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({ - // eslint-disable-next-line @typescript-eslint/naming-convention - isElasticCloudUser_bool: true, - }); - }); - - test('calls `setUserVars` when isElasticCloudUser: false is provided', () => { - fullstoryShipper.extendContext({ isElasticCloudUser: false }); - expect(fullStoryApiMock.setUserVars).toHaveBeenCalledWith({ - // eslint-disable-next-line @typescript-eslint/naming-convention - isElasticCloudUser_bool: false, - }); - }); - }); - - describe('FS.setVars', () => { - test('calls `setVars` when version is provided', () => { - fullstoryShipper.extendContext({ version: '1.2.3' }); - expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', { - version_str: '1.2.3', - version_major_int: 1, - version_minor_int: 2, - version_patch_int: 3, - }); - }); - - test('calls `setVars` when cloudId is provided', () => { - fullstoryShipper.extendContext({ cloudId: 'test-es-org-id' }); - expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', { - // eslint-disable-next-line @typescript-eslint/naming-convention - cloudId_str: 'test-es-org-id', - }); - }); - - test('merges both: version and cloudId if both are provided', () => { - fullstoryShipper.extendContext({ version: '1.2.3', cloudId: 'test-es-org-id' }); - expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', { - // eslint-disable-next-line @typescript-eslint/naming-convention - cloudId_str: 'test-es-org-id', - version_str: '1.2.3', - version_major_int: 1, - version_minor_int: 2, - version_patch_int: 3, - }); - }); - - test('adds the rest of the context to `setVars` (only if they match one of the valid keys)', () => { - const context = { - userId: 'test-user-id', - version: '1.2.3', - cloudId: 'test-es-org-id', - labels: { serverless: 'test' }, - foo: 'bar', - }; - fullstoryShipper.extendContext(context); - expect(fullStoryApiMock.setVars).toHaveBeenCalledWith('page', { - version_str: '1.2.3', - version_major_int: 1, - version_minor_int: 2, - version_patch_int: 3, - // eslint-disable-next-line @typescript-eslint/naming-convention - cloudId_str: 'test-es-org-id', - labels: { serverless_str: 'test' }, - }); - }); - - test('emits once only if nothing changes', () => { - const context = { - userId: 'test-user-id', - version: '1.2.3', - cloudId: 'test-es-org-id', - labels: { serverless: 'test' }, - foo: 'bar', - }; - fullstoryShipper.extendContext(context); - fullstoryShipper.extendContext(context); - expect(fullStoryApiMock.setVars).toHaveBeenCalledTimes(1); - fullstoryShipper.extendContext(context); - expect(fullStoryApiMock.setVars).toHaveBeenCalledTimes(1); - }); - }); - }); - - describe('optIn', () => { - test('should call consent true and restart when isOptIn: true', () => { - fullstoryShipper.optIn(true); - expect(fullStoryApiMock.consent).toHaveBeenCalledWith(true); - expect(fullStoryApiMock.restart).toHaveBeenCalled(); - }); - - test('should call consent false and shutdown when isOptIn: false', () => { - fullstoryShipper.optIn(false); - expect(fullStoryApiMock.consent).toHaveBeenCalledWith(false); - expect(fullStoryApiMock.shutdown).toHaveBeenCalled(); - }); - }); - - describe('reportEvents', () => { - test('calls the API once per event in the array with the properties transformed', () => { - fullstoryShipper.reportEvents([ - { - event_type: 'test-event-1', - timestamp: '2020-01-01T00:00:00.000Z', - properties: { test: 'test-1' }, - context: { pageName: 'test-page-1' }, - }, - { - event_type: 'test-event-2', - timestamp: '2020-01-01T00:00:00.000Z', - properties: { other_property: 'test-2' }, - context: { pageName: 'test-page-1' }, - }, - ]); - - expect(fullStoryApiMock.event).toHaveBeenCalledTimes(2); - expect(fullStoryApiMock.event).toHaveBeenCalledWith('test-event-1', { - test_str: 'test-1', - }); - expect(fullStoryApiMock.event).toHaveBeenCalledWith('test-event-2', { - other_property_str: 'test-2', - }); - }); - - test('filters the events by the allow-list', () => { - fullstoryShipper = new FullStoryShipper( - { - eventTypesAllowlist: ['valid-event-1', 'valid-event-2'], - debug: true, - fullStoryOrgId: 'test-org-id', - }, - { - logger: loggerMock.create(), - sendTo: 'staging', - isDev: true, - } - ); - fullstoryShipper.reportEvents([ - { - event_type: 'test-event-1', // Should be filtered out. - timestamp: '2020-01-01T00:00:00.000Z', - properties: { test: 'test-1' }, - context: { pageName: 'test-page-1' }, - }, - { - event_type: 'valid-event-1', - timestamp: '2020-01-01T00:00:00.000Z', - properties: { test: 'test-1' }, - context: { pageName: 'test-page-1' }, - }, - { - event_type: 'valid-event-2', - timestamp: '2020-01-01T00:00:00.000Z', - properties: { test: 'test-2' }, - context: { pageName: 'test-page-1' }, - }, - ]); - - expect(fullStoryApiMock.event).toHaveBeenCalledTimes(2); - expect(fullStoryApiMock.event).toHaveBeenCalledWith('valid-event-1', { - test_str: 'test-1', - }); - expect(fullStoryApiMock.event).toHaveBeenCalledWith('valid-event-2', { - test_str: 'test-2', - }); - }); - }); -}); diff --git a/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts b/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts deleted file mode 100644 index c078348368eb0..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Subject, distinct, debounceTime, map, filter, Subscription } from 'rxjs'; -import { get, has } from 'lodash'; -import { set } from '@kbn/safer-lodash-set'; -import type { AnalyticsClientInitContext, EventContext, Event, IShipper } from '../../../client'; -import type { FullStoryApi } from './types'; -import type { FullStorySnippetConfig } from './load_snippet'; -import { formatPayload } from './format_payload'; -import { loadSnippet } from './load_snippet'; -import { getParsedVersion } from './get_parsed_version'; - -const PAGE_VARS_KEYS = [ - // Page-specific keys - 'pageName', - 'page', - 'entityId', - 'applicationId', - - // Deployment-specific keys - 'version', // x4, split to version_major, version_minor, version_patch for easier filtering - 'buildSha', // 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', - 'labels.serverless', - 'license_id', - 'license_status', - 'license_type', - - // Session-specific - 'session_id', - 'preferred_languages', -] as const; - -/** - * FullStory shipper configuration. - */ -export interface FullStoryShipperConfig extends FullStorySnippetConfig { - /** - * FullStory's custom events rate limit is very aggressive. - * If this setting is provided, it'll only send the event types specified in this list. - */ - eventTypesAllowlist?: string[]; - /** - * FullStory only allows calling setVars('page') once per navigation. - * This setting defines how much time to hold from calling the API while additional lazy context is being resolved. - */ - pageVarsDebounceTimeMs?: number; -} - -interface FullStoryUserVars { - userId?: string; - isElasticCloudUser?: boolean; - cloudIsElasticStaffOwned?: boolean; - cloudTrialEndDate?: string; -} - -type FullStoryPageContext = Pick<EventContext, (typeof PAGE_VARS_KEYS)[number]>; - -/** - * FullStory shipper. - */ -export class FullStoryShipper implements IShipper { - /** Shipper's unique name */ - public static shipperName = 'FullStory'; - - private readonly fullStoryApi: FullStoryApi; - private lastUserId: string | undefined; - private readonly eventTypesAllowlist?: string[]; - private readonly pageContext$ = new Subject<EventContext>(); - private readonly userContext$ = new Subject<FullStoryUserVars>(); - private readonly subscriptions = new Subscription(); - - /** - * Creates a new instance of the FullStoryShipper. - * @param config {@link FullStoryShipperConfig} - * @param initContext {@link AnalyticsClientInitContext} - */ - constructor( - config: FullStoryShipperConfig, - private readonly initContext: AnalyticsClientInitContext - ) { - const { eventTypesAllowlist, pageVarsDebounceTimeMs = 500, ...snippetConfig } = config; - this.fullStoryApi = loadSnippet(snippetConfig); - this.eventTypesAllowlist = eventTypesAllowlist; - - this.subscriptions.add( - this.userContext$ - .pipe( - distinct(({ userId, isElasticCloudUser, cloudIsElasticStaffOwned, cloudTrialEndDate }) => - [userId, isElasticCloudUser, cloudIsElasticStaffOwned, cloudTrialEndDate].join('-') - ) - ) - .subscribe((userVars) => this.updateUserVars(userVars)) - ); - - this.subscriptions.add( - this.pageContext$ - .pipe( - map((newContext) => { - // Cherry-picking fields because FS limits the number of fields that can be sent. - // > Note: You can capture up to 20 unique page properties (exclusive of pageName) for any given page - // > and up to 500 unique page properties across all pages. - // https://help.fullstory.com/hc/en-us/articles/1500004101581-FS-setVars-API-Sending-custom-page-data-to-FullStory - return PAGE_VARS_KEYS.reduce((acc, key) => { - if (has(newContext, key)) { - set(acc, key, get(newContext, key)); - } - return acc; - }, {} as Partial<FullStoryPageContext> & Record<string, unknown>); - }), - filter((pageVars) => Object.keys(pageVars).length > 0), - // Wait for anything to actually change. - distinct((pageVars) => { - const sortedKeys = Object.keys(pageVars).sort(); - return sortedKeys.map((key) => pageVars[key]).join('-'); - }), - // We need some debounce time to ensure everything is updated before calling FS because some properties cannot be changed twice for the same URL. - debounceTime(pageVarsDebounceTimeMs) - ) - .subscribe((pageVars) => { - this.initContext.logger.debug( - () => `Calling FS.setVars with context ${JSON.stringify(pageVars)}` - ); - this.fullStoryApi.setVars('page', { - ...formatPayload(pageVars), - ...(pageVars.version ? getParsedVersion(pageVars.version) : {}), - }); - }) - ); - } - - /** - * Calls `fs.identify`, `fs.setUserVars` and `fs.setVars` depending on the fields provided in the newContext. - * @param newContext The full new context to set {@link EventContext} - */ - public extendContext(newContext: EventContext): void { - this.initContext.logger.debug(() => `Received context ${JSON.stringify(newContext)}`); - - // FullStory requires different APIs for different type of contexts: - // User-level context. - this.userContext$.next(newContext); - // Event-level context. At the moment, only the scope `page` is supported by FullStory for webapps. - this.pageContext$.next(newContext); - } - - /** - * Stops/restarts the shipping mechanism based on the value of isOptedIn - * @param isOptedIn `true` for resume sending events. `false` to stop. - */ - public optIn(isOptedIn: boolean): void { - this.initContext.logger.debug(`Setting FS to optIn ${isOptedIn}`); - // FullStory uses 2 different opt-in methods: - // - `consent` is needed to allow collecting information about the components - // declared as "Record with user consent" (https://help.fullstory.com/hc/en-us/articles/360020623574). - // We need to explicitly call `consent` if for the "Record with user content" feature to work. - this.fullStoryApi.consent(isOptedIn); - // - `restart` and `shutdown` fully start/stop the collection of data. - if (isOptedIn) { - this.fullStoryApi.restart(); - } else { - this.fullStoryApi.shutdown(); - } - } - - /** - * Filters the events by the eventTypesAllowlist from the config. - * Then it transforms the event into a FS valid format and calls `fs.event`. - * @param events batched events {@link Event} - */ - public reportEvents(events: Event[]): void { - this.initContext.logger.debug(`Reporting ${events.length} events to FS`); - events - .filter((event) => this.eventTypesAllowlist?.includes(event.event_type) ?? true) - .forEach((event) => { - // We only read event.properties and discard the rest because the context is already sent in the other APIs. - this.fullStoryApi.event(event.event_type, formatPayload(event.properties)); - }); - } - - /** - * Flushes all internal queues of the shipper. - * It doesn't really do anything inside because this shipper doesn't hold any internal queues. - */ - public async flush() {} - - /** - * Shuts down the shipper. - */ - public shutdown() { - this.subscriptions.unsubscribe(); - } - - private updateUserVars({ - userId, - isElasticCloudUser, - cloudIsElasticStaffOwned, - cloudTrialEndDate, - }: FullStoryUserVars) { - // Call it only when the userId changes - if (userId && userId !== this.lastUserId) { - this.initContext.logger.debug(`Calling FS.identify with userId ${userId}`); - // We need to call the API for every new userId (restarting the session). - this.fullStoryApi.identify(userId); - this.lastUserId = userId; - } - - // User-level context - if ( - typeof isElasticCloudUser === 'boolean' || - typeof cloudIsElasticStaffOwned === 'boolean' || - cloudTrialEndDate - ) { - const userVars = { - isElasticCloudUser, - cloudIsElasticStaffOwned, - cloudTrialEndDate, - }; - this.initContext.logger.debug( - () => `Calling FS.setUserVars with ${JSON.stringify(userVars)}` - ); - this.fullStoryApi.setUserVars(formatPayload(userVars)); - } - } -} diff --git a/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.test.ts b/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.test.ts deleted file mode 100644 index b4938dbca3bc4..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getParsedVersion } from './get_parsed_version'; - -describe('getParsedVersion', () => { - test('parses a version string', () => { - expect(getParsedVersion('1.2.3')).toEqual({ - version_str: '1.2.3', - version_major_int: 1, - version_minor_int: 2, - version_patch_int: 3, - }); - }); - - test('parses a version string with extra label', () => { - expect(getParsedVersion('1.2.3-SNAPSHOT')).toEqual({ - version_str: '1.2.3-SNAPSHOT', - version_major_int: 1, - version_minor_int: 2, - version_patch_int: 3, - }); - }); - - test('does not throw for invalid version', () => { - expect(getParsedVersion('INVALID_VERSION')).toEqual({ - version_str: 'INVALID_VERSION', - version_major_int: NaN, - version_minor_int: NaN, - version_patch_int: NaN, - }); - }); -}); diff --git a/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.ts b/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.ts deleted file mode 100644 index 873b47a0cde8a..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.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. - */ - -export function getParsedVersion(version: string): { - version_str: string; - version_major_int: number; - version_minor_int: number; - version_patch_int: number; -} { - const [major, minor, patch] = version.split('.'); - return { - version_str: version, - version_major_int: parseInt(major, 10), - version_minor_int: parseInt(minor, 10), - version_patch_int: parseInt(patch, 10), - }; -} diff --git a/packages/analytics/ebt/shippers/fullstory/src/load_snippet.test.ts b/packages/analytics/ebt/shippers/fullstory/src/load_snippet.test.ts deleted file mode 100644 index 002f113f71f63..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/load_snippet.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { loadSnippet } from './load_snippet'; - -describe('loadSnippet', () => { - beforeAll(() => { - // Define necessary window and document global variables for the tests - jest - .spyOn(global.document, 'getElementsByTagName') - .mockReturnValue([ - { parentNode: { insertBefore: jest.fn() } }, - ] as unknown as HTMLCollectionOf<Element>); - }); - - it('should return the FullStory API', () => { - const fullStoryApi = loadSnippet({ debug: true, fullStoryOrgId: 'foo' }); - expect(fullStoryApi).toBeDefined(); - expect(fullStoryApi.event).toBeDefined(); - expect(fullStoryApi.consent).toBeDefined(); - expect(fullStoryApi.restart).toBeDefined(); - expect(fullStoryApi.shutdown).toBeDefined(); - expect(fullStoryApi.identify).toBeDefined(); - expect(fullStoryApi.setUserVars).toBeDefined(); - expect(fullStoryApi.setVars).toBeDefined(); - }); -}); diff --git a/packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts b/packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts deleted file mode 100644 index 1cbcc96e201ef..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts +++ /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 and the Server Side Public License, v 1; you may not 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 { FullStoryApi } from './types'; - -/** - * FullStory basic configuration. - */ -export interface FullStorySnippetConfig { - /** - * The FullStory account id. - */ - fullStoryOrgId: string; - /** - * The host to send the data to. Used to overcome AdBlockers by using custom DNSs. - * If not specified, it defaults to `fullstory.com`. - */ - host?: string; - /** - * The URL to load the FullStory client from. Falls back to `edge.fullstory.com/s/fs.js` if not specified. - */ - scriptUrl?: string; - /** - * Whether the debug logs should be printed to the console. - */ - debug?: boolean; - /** - * The name of the variable where the API is stored: `window[namespace]`. Defaults to `FS`. - */ - namespace?: string; -} - -export function loadSnippet({ - scriptUrl = 'https://edge.fullstory.com/s/fs.js', - fullStoryOrgId, - host = 'fullstory.com', - namespace = 'FS', - debug = false, -}: FullStorySnippetConfig): FullStoryApi { - window._fs_debug = debug; - window._fs_host = host; - window._fs_script = scriptUrl; - window._fs_org = fullStoryOrgId; - window._fs_namespace = namespace; - - /* eslint-disable dot-notation,prettier/prettier,@typescript-eslint/no-shadow,prefer-rest-params,@typescript-eslint/no-unused-expressions */ - (function(m,n,e,t,l,o,g,y){ - if (e in m) {if(m.console && m.console.log) { m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');} return;} - // @ts-expect-error - g=m[e]=function(a,b,s){g.q?g.q.push([a,b,s]):g._api(a,b,s);};g.q=[]; - // @ts-expect-error - o=n.createElement(t);o.async=1;o.crossOrigin='anonymous';o.src=_fs_script; - // @ts-expect-error - y=n.getElementsByTagName(t)[0];y.parentNode.insertBefore(o,y); - // @ts-expect-error - g.identify=function(i,v,s){g(l,{uid:i},s);if(v)g(l,v,s)};g.setUserVars=function(v,s){g(l,v,s)};g.event=function(i,v,s){g('event',{n:i,p:v},s)}; - // @ts-expect-error - g.anonymize=function(){g.identify(!!0)}; - // @ts-expect-error - g.shutdown=function(){g("rec",!1)};g.restart=function(){g("rec",!0)}; - // @ts-expect-error - g.log = function(a,b){g("log",[a,b])}; - // @ts-expect-error - g.consent=function(a){g("consent",!arguments.length||a)}; - // @ts-expect-error - g.identifyAccount=function(i,v){o='account';v=v||{};v.acctId=i;g(o,v)}; - // @ts-expect-error - g.clearUserCookie=function(){}; - // @ts-expect-error - g.setVars=function(n, p){g('setVars',[n,p]);}; - // @ts-expect-error - g._w={};y='XMLHttpRequest';g._w[y]=m[y];y='fetch';g._w[y]=m[y]; - // @ts-expect-error - if(m[y])m[y]=function(){return g._w[y].apply(this,arguments)}; - // @ts-expect-error - g._v="1.3.0"; - - })(window,document,window['_fs_namespace'],'script','user'); - - const fullStoryApi = window[namespace as 'FS']; - - if (!fullStoryApi) { - throw new Error('FullStory snippet failed to load. Check browser logs for more information.'); - } - - return fullStoryApi; -} diff --git a/packages/analytics/ebt/shippers/fullstory/src/types.ts b/packages/analytics/ebt/shippers/fullstory/src/types.ts deleted file mode 100644 index 6c448c2c4d2e1..0000000000000 --- a/packages/analytics/ebt/shippers/fullstory/src/types.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Definition of the FullStory API. - * Docs are available at https://developer.fullstory.com/. - */ -export interface FullStoryApi { - /** - * Identify a User - * https://developer.fullstory.com/identify - * @param userId - * @param userVars - */ - identify(userId: string, userVars?: Record<string, unknown>): void; - - /** - * Set User Variables - * https://developer.fullstory.com/user-variables - * @param userVars - */ - setUserVars(userVars: Record<string, unknown>): void; - - /** - * Setting page variables - * https://developer.fullstory.com/page-variables - * @param scope - * @param pageProperties - */ - setVars(scope: 'page', pageProperties: Record<string, unknown>): void; - - /** - * Sending custom event data into FullStory - * https://developer.fullstory.com/custom-events - * @param eventName - * @param eventProperties - */ - event(eventName: string, eventProperties: Record<string, unknown>): void; - - /** - * Selectively record parts of your site based on explicit user consent - * https://developer.fullstory.com/consent - * @param isOptedIn true if the user has opted in to tracking - */ - consent(isOptedIn: boolean): void; - - /** - * Restart session recording after it has been shutdown - * https://developer.fullstory.com/restart-recording - */ - restart(): void; - - /** - * Stop recording a session - * https://developer.fullstory.com/stop-recording - */ - shutdown(): void; -} - -declare global { - interface Window { - _fs_debug: boolean; - _fs_host: string; - _fs_org: string; - _fs_namespace: string; - _fs_script: string; - FS: FullStoryApi; - } -} diff --git a/packages/analytics/ebt/tsconfig.json b/packages/analytics/ebt/tsconfig.json deleted file mode 100644 index 38e6caee5d2a1..0000000000000 --- a/packages/analytics/ebt/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node", - "react" - ] - }, - "include": [ - "**/*.ts", - "**/*.tsx", - ], - "exclude": [ - "target/**/*" - ], - "kbn_references": [ - "@kbn/logging-mocks", - "@kbn/logging", - "@kbn/safer-lodash-set", - ] -} diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts index ec9d9e05aff23..ef023e5122840 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AnalyticsClient } from '@kbn/ebt/client'; +import { AnalyticsClient } from '@elastic/ebt/client'; import { Subject } from 'rxjs'; export const analyticsClientMock: jest.Mocked<AnalyticsClient> = { @@ -21,6 +21,6 @@ export const analyticsClientMock: jest.Mocked<AnalyticsClient> = { shutdown: jest.fn(), }; -jest.doMock('@kbn/ebt/client', () => ({ +jest.doMock('@elastic/ebt/client', () => ({ createAnalytics: () => analyticsClientMock, })); diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts index 091643741331e..9a176240b624c 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts @@ -7,8 +7,8 @@ */ import { of, Subscription } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/ebt/client'; -import { createAnalytics } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; +import { createAnalytics } from '@elastic/ebt/client'; import { registerPerformanceMetricEventType } from '@kbn/ebt-tools'; import type { CoreContext } from '@kbn/core-base-browser-internal'; import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-browser-internal'; @@ -32,9 +32,6 @@ export class AnalyticsService { this.analyticsClient = createAnalytics({ isDev: core.env.mode.dev, logger: core.logger.get('analytics'), - // TODO: We need to be able to edit sendTo once we resolve the telemetry config. - // For now, we are relying on whether it's a distributable or running from source. - sendTo: core.env.packageInfo.dist ? 'production' : 'staging', }); this.registerBuildInfoAnalyticsContext(core); 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 e545ec0fe7b2a..8a60a7c137d8b 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 @@ -7,7 +7,7 @@ */ import { fromEvent } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; /** HTML attributes that should be skipped from reporting because they might contain data we do not wish to collect */ const HTML_ATTRIBUTES_TO_REMOVE = [ diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.ts new file mode 100644 index 0000000000000..06b349d4c6043 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { trackPerformanceMeasureEntries } from './track_performance_measure_entries'; +import { analyticsClientMock } from './analytics_service.test.mocks'; + +interface MockEntryList { + getEntries: () => [object]; +} +type ObsCallback = (_entries: MockEntryList, _obs: object) => undefined; +const mockObs = { observe: jest.fn, disconnect: jest.fn }; + +const setupMockPerformanceObserver = (entries: [object]) => { + const mockPerformanceObserver = function (callback: ObsCallback) { + callback( + { + getEntries: () => entries, + }, + mockObs + ); + return mockObs; + }; + + (global.PerformanceObserver as unknown) = mockPerformanceObserver; +}; + +describe('trackPerformanceMeasureEntries', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("doesn't report an analytics event when not receiving events", () => { + setupMockPerformanceObserver([{}]); + trackPerformanceMeasureEntries(analyticsClientMock, true); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(0); + }); + + test("doesn't report an analytics event when receiving not 'kibana:performance' events", () => { + setupMockPerformanceObserver([ + { + name: '/', + entryType: 'measure', + startTime: 100, + duration: 1000, + detail: { + eventName: 'kibana:plugin_render_time', + type: 'anything', + }, + }, + ]); + trackPerformanceMeasureEntries(analyticsClientMock, true); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(0); + }); + + test("doesn't report an analytics event when receiving not 'measure' events", () => { + setupMockPerformanceObserver([ + { + name: '/', + entryType: 'anything', + startTime: 100, + duration: 1000, + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + }, + }, + ]); + trackPerformanceMeasureEntries(analyticsClientMock, true); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(0); + }); + + test('reports an analytics event when receiving "measure" and "kibana:performance" events', () => { + setupMockPerformanceObserver([ + { + name: '/', + entryType: 'measure', + startTime: 100, + duration: 1000, + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + }, + }, + ]); + trackPerformanceMeasureEntries(analyticsClientMock, true); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1); + }); + + test('reports an analytics event ignoring keys and values not allowed', () => { + setupMockPerformanceObserver([ + { + name: '/', + entryType: 'measure', + startTime: 100, + duration: 1000, + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics: { + key1: 'key1', + value1: 'value1', + key10: 'key10', + value10: 'value10', + anyKey: 'anyKey', + anyValue: 'anyValue', + }, + }, + }, + ]); + trackPerformanceMeasureEntries(analyticsClientMock, true); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', { + duration: 1000, + eventName: 'kibana:plugin_render_time', + key1: 'key1', + meta: { target: '/' }, + value1: 'value1', + }); + }); +}); diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts index 40d479b448b46..2f0ed608ce1be 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts @@ -5,9 +5,16 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; +const MAX_CUSTOM_METRICS = 9; +// The keys and values for the custom metrics are limited to 9 pairs +const ALLOWED_CUSTOM_METRICS_KEYS_VALUES = Array.from({ length: MAX_CUSTOM_METRICS }, (_, i) => [ + `key${i + 1}`, + `value${i + 1}`, +]).flat(); + export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDevMode: boolean) { function perfObserver( list: PerformanceObserverEntryList, @@ -18,6 +25,19 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev if (entry.entryType === 'measure' && entry.detail?.type === 'kibana:performance') { const target = entry?.name; const duration = entry.duration; + const customMetrics = Object.keys(entry.detail?.customMetrics ?? {}).reduce( + (acc, metric) => { + if (ALLOWED_CUSTOM_METRICS_KEYS_VALUES.includes(metric)) { + return { + ...acc, + [metric]: entry.detail.customMetrics[metric], + }; + } + + return acc; + }, + {} + ); if (isDevMode) { if (!target) { @@ -47,6 +67,7 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev reportPerformanceMetricEvent(analytics, { eventName: entry.detail.eventName, duration, + ...customMetrics, meta: { target, }, diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts index dd0ebde28addb..97ec6a4bc1490 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts @@ -7,7 +7,7 @@ */ import { debounceTime, fromEvent, map, merge, of, shareReplay } from 'rxjs'; -import type { AnalyticsClient, RootSchema } from '@kbn/ebt/client'; +import type { AnalyticsClient, RootSchema } from '@elastic/ebt/client'; export interface ViewportSize { viewport_width: number; diff --git a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json index ecac1746f44a2..152fbeeaa9edd 100644 --- a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json @@ -11,8 +11,7 @@ "@kbn/core-injected-metadata-browser-internal", "@kbn/core-analytics-browser", "@kbn/core-base-browser-mocks", - "@kbn/core-injected-metadata-browser-mocks", - "@kbn/ebt", + "@kbn/core-injected-metadata-browser-mocks" ], "exclude": ["target/**/*"] } diff --git a/packages/core/analytics/core-analytics-browser/index.ts b/packages/core/analytics/core-analytics-browser/index.ts index f20ccf31da2c4..1907260ef6bad 100644 --- a/packages/core/analytics/core-analytics-browser/index.ts +++ b/packages/core/analytics/core-analytics-browser/index.ts @@ -48,4 +48,4 @@ export type { AllowedSchemaTypes, // Shippers IShipper, -} from '@kbn/ebt/client'; +} from '@elastic/ebt/client'; diff --git a/packages/core/analytics/core-analytics-browser/src/types.ts b/packages/core/analytics/core-analytics-browser/src/types.ts index 779172acc9b9d..a41260565a8c1 100644 --- a/packages/core/analytics/core-analytics-browser/src/types.ts +++ b/packages/core/analytics/core-analytics-browser/src/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; /** * Exposes the public APIs of the AnalyticsClient during the setup phase. diff --git a/packages/core/analytics/core-analytics-browser/tsconfig.json b/packages/core/analytics/core-analytics-browser/tsconfig.json index 83571abe4bcf4..99505f519996c 100644 --- a/packages/core/analytics/core-analytics-browser/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser/tsconfig.json @@ -10,9 +10,7 @@ "include": [ "**/*.ts" ], - "kbn_references": [ - "@kbn/ebt", - ], + "kbn_references": [], "exclude": [ "target/**/*", ] diff --git a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts index d759a9520cc16..799665f8d927d 100644 --- a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts +++ b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AnalyticsClient } from '@kbn/ebt/client'; +import { AnalyticsClient } from '@elastic/ebt/client'; import { Subject } from 'rxjs'; export const analyticsClientMock: jest.Mocked<AnalyticsClient> = { @@ -21,6 +21,6 @@ export const analyticsClientMock: jest.Mocked<AnalyticsClient> = { flush: jest.fn(), }; -jest.doMock('@kbn/ebt/client', () => ({ +jest.doMock('@elastic/ebt/client', () => ({ createAnalytics: () => analyticsClientMock, })); diff --git a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts index 8e0ce76e08b96..2098f2f6e9362 100644 --- a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts +++ b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts @@ -7,8 +7,8 @@ */ import { of } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/ebt/client'; -import { createAnalytics } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; +import { createAnalytics } from '@elastic/ebt/client'; import { registerPerformanceMetricEventType } from '@kbn/ebt-tools'; import type { CoreContext } from '@kbn/core-base-server-internal'; import type { @@ -24,9 +24,6 @@ export class AnalyticsService { this.analyticsClient = createAnalytics({ isDev: core.env.mode.dev, logger: core.logger.get('analytics'), - // TODO: We need to be able to edit sendTo once we resolve the telemetry config. - // For now, we are relying on whether it's a distributable or running from source. - sendTo: core.env.packageInfo.dist ? 'production' : 'staging', }); this.registerBuildInfoAnalyticsContext(core); diff --git a/packages/core/analytics/core-analytics-server-internal/tsconfig.json b/packages/core/analytics/core-analytics-server-internal/tsconfig.json index 57a0fa3e04362..a9d2e7cf47b9d 100644 --- a/packages/core/analytics/core-analytics-server-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-server-internal/tsconfig.json @@ -15,8 +15,7 @@ "@kbn/core-base-server-internal", "@kbn/core-analytics-server", "@kbn/config-mocks", - "@kbn/core-base-server-mocks", - "@kbn/ebt", + "@kbn/core-base-server-mocks" ], "exclude": [ "target/**/*", diff --git a/packages/core/analytics/core-analytics-server/index.ts b/packages/core/analytics/core-analytics-server/index.ts index 90341deb693a3..952fdb29c1f43 100644 --- a/packages/core/analytics/core-analytics-server/index.ts +++ b/packages/core/analytics/core-analytics-server/index.ts @@ -48,4 +48,4 @@ export type { AllowedSchemaTypes, // Shippers IShipper, -} from '@kbn/ebt/client'; +} from '@elastic/ebt/client'; diff --git a/packages/core/analytics/core-analytics-server/src/contracts.ts b/packages/core/analytics/core-analytics-server/src/contracts.ts index 531c4ef6afd1f..5df79dae09287 100644 --- a/packages/core/analytics/core-analytics-server/src/contracts.ts +++ b/packages/core/analytics/core-analytics-server/src/contracts.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; /** * Exposes the public APIs of the AnalyticsClient during the preboot phase diff --git a/packages/core/analytics/core-analytics-server/tsconfig.json b/packages/core/analytics/core-analytics-server/tsconfig.json index aa81d68980cd9..99505f519996c 100644 --- a/packages/core/analytics/core-analytics-server/tsconfig.json +++ b/packages/core/analytics/core-analytics-server/tsconfig.json @@ -10,9 +10,7 @@ "include": [ "**/*.ts" ], - "kbn_references": [ - "@kbn/ebt" - ], + "kbn_references": [], "exclude": [ "target/**/*", ] diff --git a/packages/core/apps/core-apps-server-internal/src/core_app.test.ts b/packages/core/apps/core-apps-server-internal/src/core_app.test.ts index 3c5cfa068765c..b4b1837cad756 100644 --- a/packages/core/apps/core-apps-server-internal/src/core_app.test.ts +++ b/packages/core/apps/core-apps-server-internal/src/core_app.test.ts @@ -34,6 +34,7 @@ describe('CoreApp', () => { let httpResourcesRegistrar: ReturnType<typeof httpResourcesMock.createRegistrar>; beforeEach(() => { + jest.useFakeTimers(); coreContext = mockCoreContext.create(); internalCorePreboot = coreInternalLifecycleMock.createInternalPreboot(); @@ -55,37 +56,70 @@ describe('CoreApp', () => { afterEach(() => { registerBundleRoutesMock.mockReset(); + coreApp.stop(); + jest.clearAllTimers(); }); - describe('`/internal/core/_settings` route', () => { - it('is not registered by default', async () => { - const routerMock = mockRouter.create(); - internalCoreSetup.http.createRouter.mockReturnValue(routerMock); + describe('Dynamic Config feature', () => { + describe('`/internal/core/_settings` route', () => { + it('is not registered by default', async () => { + const routerMock = mockRouter.create(); + internalCoreSetup.http.createRouter.mockReturnValue(routerMock); + + const localCoreApp = new CoreAppsService(coreContext); + await localCoreApp.setup(internalCoreSetup, emptyPlugins()); + + expect(routerMock.versioned.put).not.toHaveBeenCalledWith( + expect.objectContaining({ + path: '/internal/core/_settings', + }) + ); + + // But the Saved Object is still registered + expect(internalCoreSetup.savedObjects.registerType).toHaveBeenCalledWith( + expect.objectContaining({ name: 'dynamic-config-overrides' }) + ); + }); + + it('is registered when enabled', async () => { + const routerMock = mockRouter.create(); + internalCoreSetup.http.createRouter.mockReturnValue(routerMock); - const localCoreApp = new CoreAppsService(coreContext); - await localCoreApp.setup(internalCoreSetup, emptyPlugins()); + coreContext.configService.atPath.mockReturnValue(of({ allowDynamicConfigOverrides: true })); + const localCoreApp = new CoreAppsService(coreContext); + await localCoreApp.setup(internalCoreSetup, emptyPlugins()); - expect(routerMock.versioned.put).not.toHaveBeenCalledWith( - expect.objectContaining({ + expect(routerMock.versioned.put).toHaveBeenCalledWith({ path: '/internal/core/_settings', - }) - ); - }); + access: 'internal', + options: { + tags: ['access:updateDynamicConfig'], + }, + }); + }); - it('is registered when enabled', async () => { - const routerMock = mockRouter.create(); - internalCoreSetup.http.createRouter.mockReturnValue(routerMock); + it('it fetches the persisted document when enabled', async () => { + const routerMock = mockRouter.create(); + internalCoreSetup.http.createRouter.mockReturnValue(routerMock); - coreContext.configService.atPath.mockReturnValue(of({ allowDynamicConfigOverrides: true })); - const localCoreApp = new CoreAppsService(coreContext); - await localCoreApp.setup(internalCoreSetup, emptyPlugins()); + coreContext.configService.atPath.mockReturnValue(of({ allowDynamicConfigOverrides: true })); + const localCoreApp = new CoreAppsService(coreContext); + await localCoreApp.setup(internalCoreSetup, emptyPlugins()); - expect(routerMock.versioned.put).toHaveBeenCalledWith({ - path: '/internal/core/_settings', - access: 'internal', - options: { - tags: ['access:updateDynamicConfig'], - }, + const internalCoreStart = coreInternalLifecycleMock.createInternalStart(); + localCoreApp.start(internalCoreStart); + + expect(internalCoreStart.savedObjects.createInternalRepository).toHaveBeenCalledWith([ + 'dynamic-config-overrides', + ]); + + const repository = + internalCoreStart.savedObjects.createInternalRepository.mock.results[0].value; + await jest.advanceTimersByTimeAsync(0); // "Advancing" 0ms is enough, but necessary to trigger the `timer` observable + expect(repository.get).toHaveBeenCalledWith( + 'dynamic-config-overrides', + 'dynamic-config-overrides' + ); }); }); }); diff --git a/packages/core/apps/core-apps-server-internal/src/core_app.ts b/packages/core/apps/core-apps-server-internal/src/core_app.ts index e6b7349d2edff..e9676c792292a 100644 --- a/packages/core/apps/core-apps-server-internal/src/core_app.ts +++ b/packages/core/apps/core-apps-server-internal/src/core_app.ts @@ -21,9 +21,27 @@ import type { } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; -import type { InternalCorePreboot, InternalCoreSetup } from '@kbn/core-lifecycle-server-internal'; +import type { + InternalCorePreboot, + InternalCoreSetup, + InternalCoreStart, +} from '@kbn/core-lifecycle-server-internal'; import type { InternalStaticAssets } from '@kbn/core-http-server-internal'; -import { firstValueFrom, map, type Observable } from 'rxjs'; +import { + combineLatest, + concatMap, + firstValueFrom, + map, + type Observable, + ReplaySubject, + shareReplay, + Subject, + takeUntil, + timer, +} from 'rxjs'; +import type { InternalSavedObjectsServiceStart } from '@kbn/core-saved-objects-server-internal'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; import { CoreAppConfig, type CoreAppConfigType, CoreAppPath } from './core_app_config'; import { registerBundleRoutes } from './bundle_routes'; import type { InternalCoreAppsServiceRequestHandlerContext } from './internal_types'; @@ -41,12 +59,17 @@ interface CommonRoutesParams { ) => Promise<IKibanaResponse>; } +const DYNAMIC_CONFIG_OVERRIDES_SO_TYPE = 'dynamic-config-overrides'; +const DYNAMIC_CONFIG_OVERRIDES_SO_ID = 'dynamic-config-overrides'; + /** @internal */ export class CoreAppsService { private readonly logger: Logger; private readonly env: Env; private readonly configService: IConfigService; private readonly config$: Observable<CoreAppConfig>; + private readonly savedObjectsStart$ = new ReplaySubject<InternalSavedObjectsServiceStart>(1); + private readonly stop$ = new Subject<void>(); constructor(core: CoreContext) { this.logger = core.logger.get('core-app'); @@ -70,8 +93,21 @@ export class CoreAppsService { async setup(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { this.logger.debug('Setting up core app.'); const config = await firstValueFrom(this.config$); - this.registerDefaultRoutes(coreSetup, uiPlugins, config); + this.registerDefaultRoutes(coreSetup, uiPlugins); this.registerStaticDirs(coreSetup, uiPlugins); + this.maybeRegisterDynamicConfigurationFeature({ + config, + coreSetup, + savedObjectsStart$: this.savedObjectsStart$, + }); + } + + start(coreStart: InternalCoreStart) { + this.savedObjectsStart$.next(coreStart.savedObjects); + } + + stop() { + this.stop$.next(); } private registerPrebootDefaultRoutes(corePreboot: InternalCorePreboot, uiPlugins: UiPlugins) { @@ -100,11 +136,7 @@ export class CoreAppsService { }); } - private registerDefaultRoutes( - coreSetup: InternalCoreSetup, - uiPlugins: UiPlugins, - config: CoreAppConfig - ) { + private registerDefaultRoutes(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { const httpSetup = coreSetup.http; const router = httpSetup.createRouter<InternalCoreAppsServiceRequestHandlerContext>(''); const resources = coreSetup.httpResources.createRegistrar(router); @@ -167,18 +199,80 @@ export class CoreAppsService { } } ); + } + + private maybeRegisterDynamicConfigurationFeature({ + config, + coreSetup, + savedObjectsStart$, + }: { + config: CoreAppConfig; + coreSetup: InternalCoreSetup; + savedObjectsStart$: Observable<InternalSavedObjectsServiceStart>; + }) { + // Always registering the Saved Objects to avoid ON/OFF conflicts in the migrations + coreSetup.savedObjects.registerType({ + name: DYNAMIC_CONFIG_OVERRIDES_SO_TYPE, + hidden: true, + hiddenFromHttpApis: true, + namespaceType: 'agnostic', + mappings: { + dynamic: false, + properties: {}, + }, + }); if (config.allowDynamicConfigOverrides) { - this.registerInternalCoreSettingsRoute(router); + const savedObjectsClient$ = savedObjectsStart$.pipe( + map((savedObjectsStart) => + savedObjectsStart.createInternalRepository([DYNAMIC_CONFIG_OVERRIDES_SO_TYPE]) + ), + shareReplay(1) + ); + + // Register the HTTP route + const router = coreSetup.http.createRouter<InternalCoreAppsServiceRequestHandlerContext>(''); + this.registerInternalCoreSettingsRoute(router, savedObjectsClient$); + + let latestOverrideVersion: string | undefined; // Use the document version to avoid calling override on every poll + // Poll for updates + combineLatest([savedObjectsClient$, timer(0, 10_000)]) + .pipe( + concatMap(async ([soClient]) => { + try { + const persistedOverrides = await soClient.get<Record<string, unknown>>( + DYNAMIC_CONFIG_OVERRIDES_SO_TYPE, + DYNAMIC_CONFIG_OVERRIDES_SO_ID + ); + if (latestOverrideVersion !== persistedOverrides.version) { + this.configService.setDynamicConfigOverrides(persistedOverrides.attributes); + latestOverrideVersion = persistedOverrides.version; + } + } catch (err) { + // Potential failures: + // - The SO document does not exist (404 error) => no need to log + // - The configuration overrides are invalid => they won't be applied and the validation error will be logged. + if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { + this.logger.warn(`Failed to apply the persisted dynamic config overrides: ${err}`); + } + } + }), + takeUntil(this.stop$) + ) + .subscribe(); } } /** * Registers the HTTP API that allows updating in-memory the settings that opted-in to be dynamically updatable. * @param router {@link IRouter} + * @param savedObjectClient$ An observable of a {@link SavedObjectsClientContract | savedObjects client} that will be used to update the document * @private */ - private registerInternalCoreSettingsRoute(router: IRouter) { + private registerInternalCoreSettingsRoute( + router: IRouter, + savedObjectClient$: Observable<SavedObjectsClientContract> + ) { router.versioned .put({ path: '/internal/core/_settings', @@ -201,7 +295,12 @@ export class CoreAppsService { }, async (context, req, res) => { try { - this.configService.setDynamicConfigOverrides(req.body); + const newGlobalOverrides = this.configService.setDynamicConfigOverrides(req.body); + const soClient = await firstValueFrom(savedObjectClient$); + await soClient.create(DYNAMIC_CONFIG_OVERRIDES_SO_TYPE, newGlobalOverrides, { + id: DYNAMIC_CONFIG_OVERRIDES_SO_ID, + overwrite: true, + }); } catch (err) { if (err instanceof ValidationError) { return res.badRequest({ body: err }); diff --git a/packages/core/apps/core-apps-server-internal/tsconfig.json b/packages/core/apps/core-apps-server-internal/tsconfig.json index 35698e91f6ddb..cc84d62f4faa4 100644 --- a/packages/core/apps/core-apps-server-internal/tsconfig.json +++ b/packages/core/apps/core-apps-server-internal/tsconfig.json @@ -34,6 +34,9 @@ "@kbn/monaco", "@kbn/core-http-server-internal", "@kbn/core-http-router-server-internal", + "@kbn/core-saved-objects-server-internal", + "@kbn/core-saved-objects-api-server", + "@kbn/core-saved-objects-server", ], "exclude": [ "target/**/*", diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts index 3bf7ab94af0de..2e19999bd8735 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts @@ -46,8 +46,8 @@ const { pollEsNodesVersion: pollEsNodesVersionActual } = jest.requireActual( const isValidConnectionMock = isValidConnection as jest.Mock; -const delay = async (durationMs: number) => - await new Promise((resolve) => setTimeout(resolve, durationMs)); +const TICK = 10; +const tick = (ticks = 1) => jest.advanceTimersByTime(TICK * ticks); const configService = configServiceMock.create(); @@ -67,11 +67,13 @@ beforeEach(() => { env = Env.createDefault(REPO_ROOT, getEnvOptions()); + jest.useFakeTimers(); + mockConfig$ = new BehaviorSubject({ hosts: ['http://1.2.3.4'], healthCheck: { - delay: duration(10), - startupDelay: duration(10), + delay: duration(TICK), + startupDelay: duration(TICK), }, ssl: { verificationMode: 'none', @@ -96,6 +98,7 @@ beforeEach(() => { afterEach(async () => { jest.clearAllMocks(); + jest.useRealTimers(); MockClusterClient.mockClear(); isScriptingEnabledMock.mockReset(); getClusterInfoMock.mockReset(); @@ -223,13 +226,18 @@ describe('#setup', () => { elasticsearchClientMock.createErrorTransportRequestPromise(new Error()) ); + expect(mockedClient.nodes.info).toHaveBeenCalledTimes(0); + const setupContract = await elasticsearchService.setup(setupDeps); - await delay(10); - expect(mockedClient.nodes.info).toHaveBeenCalledTimes(0); + expect(mockedClient.nodes.info).toHaveBeenCalledTimes(1); + + tick(); + + expect(mockedClient.nodes.info).toHaveBeenCalledTimes(2); await firstValueFrom(setupContract.esNodesCompatibility$); - expect(mockedClient.nodes.info).toHaveBeenCalledTimes(1); + expect(mockedClient.nodes.info).toHaveBeenCalledTimes(2); // shares the last value }); it('esNodeVersionCompatibility$ stops polling when unsubscribed from', async () => { @@ -238,13 +246,17 @@ describe('#setup', () => { elasticsearchClientMock.createErrorTransportRequestPromise(new Error()) ); + expect(mockedClient.nodes.info).toHaveBeenCalledTimes(0); + const setupContract = await elasticsearchService.setup(setupDeps); - expect(mockedClient.nodes.info).toHaveBeenCalledTimes(0); + expect(mockedClient.nodes.info).toHaveBeenCalledTimes(1); await firstValueFrom(setupContract.esNodesCompatibility$); - await delay(100); - expect(mockedClient.nodes.info).toHaveBeenCalledTimes(1); + + tick(); + + expect(mockedClient.nodes.info).toHaveBeenCalledTimes(2); }); }); @@ -276,6 +288,7 @@ describe('#start', () => { pollEsNodesVersionMocked.mockImplementation(() => observable$); await elasticsearchService.setup(setupDeps); + tick(); await elasticsearchService.start(); expect(loggingSystemMock.collect(coreContext.logger).error).toEqual([]); observable$.next({ @@ -290,7 +303,7 @@ describe('#start', () => { it('logs an info message about connecting to ES', async () => { isValidConnectionMock.mockImplementation(async () => { - await new Promise((r) => setTimeout(r, 50)); + tick(); }); await elasticsearchService.setup(setupDeps); @@ -309,7 +322,7 @@ describe('#start', () => { it('returns the information about the time spent waiting for Elasticsearch', async () => { isValidConnectionMock.mockImplementation(async () => { - await new Promise((r) => setTimeout(r, 50)); + tick(); }); await elasticsearchService.setup(setupDeps); @@ -487,11 +500,11 @@ describe('#stop', () => { setupContract.esNodesCompatibility$.pipe( concatMap(async () => { expect(mockedClient.nodes.info).toHaveBeenCalledTimes(1); - await delay(10); + tick(); expect(mockedClient.nodes.info).toHaveBeenCalledTimes(2); await elasticsearchService.stop(); - await delay(100); + tick(10); expect(mockedClient.nodes.info).toHaveBeenCalledTimes(2); }) ) diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts index 100fad846374c..87ac7b83d6fb0 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts @@ -6,8 +6,7 @@ * Side Public License, v 1. */ -import { firstValueFrom, Observable, Subject } from 'rxjs'; -import { map, takeUntil } from 'rxjs'; +import { map, takeUntil, firstValueFrom, Observable, Subject } from 'rxjs'; import type { Logger } from '@kbn/logging'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; @@ -107,6 +106,13 @@ export class ElasticsearchService internalClient: this.client.asInternalUser, }).pipe(takeUntil(this.stop$)); + // Log every error we may encounter in the connection to Elasticsearch + esNodesCompatibility$.subscribe(({ isCompatible, message }) => { + if (!isCompatible && message) { + this.log.error(message); + } + }); + this.esNodesCompatibility$ = esNodesCompatibility$; this.clusterInfo$ = getClusterInfo$(this.client.asInternalUser); @@ -138,13 +144,6 @@ export class ElasticsearchService const config = await firstValueFrom(this.config$); - // Log every error we may encounter in the connection to Elasticsearch - this.esNodesCompatibility$.subscribe(({ isCompatible, message }) => { - if (!isCompatible && message) { - this.log.error(message); - } - }); - let capabilities: ElasticsearchCapabilities; let elasticsearchWaitTime: number; diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/version_check/ensure_es_version.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/version_check/ensure_es_version.ts index 753a9bd079a4e..7fdeb434a9fcc 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/version_check/ensure_es_version.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/version_check/ensure_es_version.ts @@ -11,8 +11,12 @@ * that defined in Kibana's package.json. */ -import { interval, of, from, Observable, BehaviorSubject } from 'rxjs'; import { + interval, + of, + from, + Observable, + BehaviorSubject, map, distinctUntilChanged, catchError, diff --git a/packages/core/http/core-http-server-internal/src/https_redirect_server.test.ts b/packages/core/http/core-http-server-internal/src/https_redirect_server.test.ts index 3123fe8bba06c..6a755ff2af890 100644 --- a/packages/core/http/core-http-server-internal/src/https_redirect_server.test.ts +++ b/packages/core/http/core-http-server-internal/src/https_redirect_server.test.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import * as net from 'node:net'; + jest.mock('fs', () => ({ readFileSync: jest.fn(), })); @@ -28,14 +30,34 @@ function getServerListener(httpServer: HttpsRedirectServer) { return (httpServer as any).server.listener; } -beforeEach(() => { +async function getRandomAvailablePort(opts: Chance.Options): Promise<number> { + while (true) { + const candidatePort = chance.integer(opts); + try { + await new Promise<void>((resolve, reject) => { + const svr = net.createServer(); + svr.once('error', reject); + svr.listen({ host: '127.0.0.1', port: candidatePort }, () => { + svr.close(() => { + resolve(); + }); + }); + }); + return candidatePort; + } catch (err) { + // just keep trying to find another port + } + } +} + +beforeEach(async () => { config = { host: '127.0.0.1', maxPayload: new ByteSizeValue(1024), - port: chance.integer({ min: 10000, max: 15000 }), + port: await getRandomAvailablePort({ min: 10000, max: 15000 }), ssl: { enabled: true, - redirectHttpFromPort: chance.integer({ min: 20000, max: 30000 }), + redirectHttpFromPort: await getRandomAvailablePort({ min: 20000, max: 30000 }), }, cors: { enabled: false, @@ -55,7 +77,7 @@ test('throws if SSL is not enabled', async () => { ...config, ssl: { enabled: false, - redirectHttpFromPort: chance.integer({ min: 20000, max: 30000 }), + redirectHttpFromPort: await getRandomAvailablePort({ min: 20000, max: 30000 }), }, } as HttpConfig) ).rejects.toMatchSnapshot(); diff --git a/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap b/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap index 51caec4b95289..ca90bcbdb67ba 100644 --- a/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap +++ b/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap @@ -263,7 +263,6 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiProgress.valueText": [Function], "euiQuickSelect.applyButton": "Apply", "euiQuickSelect.fullDescription": [Function], - "euiQuickSelect.legendText": "Quick select a time range", "euiQuickSelect.nextLabel": "Next time window", "euiQuickSelect.previousLabel": "Previous time window", "euiQuickSelect.quickSelectTitle": "Quick select", @@ -275,7 +274,10 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiRecentlyUsed.legend": "Recently used date ranges", "euiRefreshInterval.fullDescriptionOff": [Function], "euiRefreshInterval.fullDescriptionOn": [Function], - "euiRefreshInterval.legend": "Refresh every", + "euiRefreshInterval.toggleAriaLabel": "Toggle refresh", + "euiRefreshInterval.toggleLabel": "Refresh every", + "euiRefreshInterval.unitsAriaLabel": "Refresh interval units", + "euiRefreshInterval.valueAriaLabel": "Refresh interval value", "euiRelativeTab.dateInputError": "Must be a valid range", "euiRelativeTab.fullDescription": [Function], "euiRelativeTab.numberInputError": "Must be >= 0", diff --git a/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx b/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx index df6071e9d14ed..34e6ef5ab0380 100644 --- a/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx +++ b/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx @@ -1312,9 +1312,6 @@ export const getEuiContextMapping = (): EuiTokensObject => { defaultMessage: 'Currently set to {timeTense} {timeValue} {timeUnit}.', values: { timeTense, timeValue, timeUnit }, }), - 'euiQuickSelect.legendText': i18n.translate('core.euiQuickSelect.legendText', { - defaultMessage: 'Quick select a time range', - }), 'euiQuickSelect.nextLabel': i18n.translate('core.euiQuickSelect.nextLabel', { defaultMessage: 'Next time window', }), @@ -1339,9 +1336,21 @@ export const getEuiContextMapping = (): EuiTokensObject => { 'euiRecentlyUsed.legend': i18n.translate('core.euiRecentlyUsed.legend', { defaultMessage: 'Recently used date ranges', }), - 'euiRefreshInterval.legend': i18n.translate('core.euiRefreshInterval.legend', { + 'euiRefreshInterval.toggleLabel': i18n.translate('core.euiRefreshInterval.toggleLabel', { defaultMessage: 'Refresh every', }), + 'euiRefreshInterval.toggleAriaLabel': i18n.translate( + 'core.euiRefreshInterval.toggleAriaLabel', + { + defaultMessage: 'Toggle refresh', + } + ), + 'euiRefreshInterval.valueAriaLabel': i18n.translate('core.euiRefreshInterval.valueAriaLabel', { + defaultMessage: 'Refresh interval value', + }), + 'euiRefreshInterval.unitsAriaLabel': i18n.translate('core.euiRefreshInterval.unitsAriaLabel', { + defaultMessage: 'Refresh interval units', + }), 'euiRefreshInterval.fullDescriptionOff': ({ optionValue, optionText }: EuiValues) => i18n.translate('core.euiRefreshInterval.fullDescriptionOff', { defaultMessage: 'Refresh is off, interval set to {optionValue} {optionText}.', diff --git a/packages/core/metrics/core-metrics-server-internal/src/routes/elu_history.ts b/packages/core/metrics/core-metrics-server-internal/src/routes/elu_history.ts index 4422bdbb88117..1c1825e94c11b 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/routes/elu_history.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/routes/elu_history.ts @@ -9,6 +9,7 @@ import type { IRouter } from '@kbn/core-http-server'; import type { OpsMetrics } from '@kbn/core-metrics-server'; import type { Observable } from 'rxjs'; +import apm from 'elastic-apm-node'; import { HistoryWindow } from './history_window'; interface ELUHistoryResponse { @@ -42,9 +43,21 @@ export function registerEluHistoryRoute(router: IRouter, metrics$: Observable<Op eluHistoryWindow.addObservation(metrics.process.event_loop_utilization.utilization); }); + // Report the same metrics to APM + apm.registerMetric('elu.history.short', () => + eluHistoryWindow.getAverage(HISTORY_WINDOW_SIZE_SHORT) + ); + apm.registerMetric('elu.history.medium', () => + eluHistoryWindow.getAverage(HISTORY_WINDOW_SIZE_MED) + ); + apm.registerMetric('elu.history.long', () => + eluHistoryWindow.getAverage(HISTORY_WINDOW_SIZE_LONG) + ); + router.versioned .get({ - access: 'public', // Public but needs to remain undocumented + access: 'internal', + enableQueryVersion: true, path: '/api/_elu_history', options: { authRequired: false, @@ -52,7 +65,7 @@ export function registerEluHistoryRoute(router: IRouter, metrics$: Observable<Op }) .addVersion( { - version: '2023-10-31', + version: '1', validate: false, }, async (ctx, req, res) => { diff --git a/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts b/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts index 35e012fe2f625..d7230668ff491 100644 --- a/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts +++ b/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { type RootSchema, type EventTypeOpts } from '@kbn/ebt/client'; +import { type RootSchema, type EventTypeOpts } from '@elastic/ebt/client'; export enum EventMetric { TOAST_DISMISSED = 'global_toast_list_toast_dismissed', diff --git a/packages/core/notifications/core-notifications-browser-internal/tsconfig.json b/packages/core/notifications/core-notifications-browser-internal/tsconfig.json index 88855bd5bd4bb..c3582c1b8925b 100644 --- a/packages/core/notifications/core-notifications-browser-internal/tsconfig.json +++ b/packages/core/notifications/core-notifications-browser-internal/tsconfig.json @@ -30,8 +30,7 @@ "@kbn/core-mount-utils-browser", "@kbn/react-kibana-context-render", "@kbn/core-analytics-browser", - "@kbn/core-analytics-browser-mocks", - "@kbn/ebt", + "@kbn/core-analytics-browser-mocks" ], "exclude": [ "target/**/*", diff --git a/packages/core/root/core-root-server-internal/src/server.ts b/packages/core/root/core-root-server-internal/src/server.ts index d380bffd8b359..35acaf8324e1c 100644 --- a/packages/core/root/core-root-server-internal/src/server.ts +++ b/packages/core/root/core-root-server-internal/src/server.ts @@ -450,6 +450,8 @@ export class Server { userProfile: userProfileStart, }; + this.coreApp.start(this.coreStart); + await this.plugins.start(this.coreStart); await this.http.start(); @@ -469,6 +471,7 @@ export class Server { public async stop() { this.log.debug('stopping server'); + this.coreApp.stop(); await this.analytics.stop(); await this.http.stop(); // HTTP server has to stop before savedObjects and ES clients are closed to be able to gracefully attempt to resolve any pending requests await this.plugins.stop(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.test.ts index 84ef7c232d775..e071c03e6c1f3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.test.ts @@ -103,7 +103,8 @@ describe('getSearchDsl', () => { mappings, opts.type, opts.sortField, - opts.sortOrder + opts.sortOrder, + opts.pit ); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.ts index 5af44df7172eb..7de0e922b2033 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/search_dsl.ts @@ -86,7 +86,7 @@ export function getSearchDsl( hasNoReferenceOperator, kueryNode, }), - ...getSortingParams(mappings, type, sortField, sortOrder), + ...getSortingParams(mappings, type, sortField, sortOrder, pit), ...(pit ? getPitParams(pit) : {}), search_after: searchAfter, }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.test.ts index e2a21cc03ce3b..db0482f7a345d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.test.ts @@ -238,4 +238,12 @@ describe('searchDsl/getSortParams', () => { }); }); }); + + describe('pit, no sortField', () => { + it('defaults to natural storage order sorting', () => { + expect(getSortingParams(MAPPINGS, 'saved', undefined, undefined, { id: 'abc123' })).toEqual({ + sort: ['_shard_doc'], + }); + }); + }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.ts index d2308736b5dc2..b4b95d84d0036 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search/search_dsl/sorting_params.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import Boom from '@hapi/boom'; +import type { SortOrder, SortCombinations } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server/src/apis'; import { getProperty, type IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; const TOP_LEVEL_FIELDS = ['_id', '_score']; @@ -16,10 +17,15 @@ export function getSortingParams( mappings: IndexMapping, type: string | string[], sortField?: string, - sortOrder?: estypes.SortOrder -): { sort?: estypes.SortCombinations[] } { + sortOrder?: SortOrder, + pit?: SavedObjectsPitParams +): { sort?: SortCombinations[] } { if (!sortField) { - return {}; + // if we are performing a PIT search, we must sort by some criteria + // in order to get the 'sort' property for each of the results. + // Defaulting to '_shard_doc' tells ES to sort by the natural stored order, + // giving the best performance + return pit ? { sort: ['_shard_doc'] } : {}; } const types = Array.isArray(type) ? type : [type]; diff --git a/packages/core/status/core-status-server-internal/src/status_service.ts b/packages/core/status/core-status-server-internal/src/status_service.ts index a24153342543c..39e36069c7b28 100644 --- a/packages/core/status/core-status-server-internal/src/status_service.ts +++ b/packages/core/status/core-status-server-internal/src/status_service.ts @@ -18,7 +18,7 @@ import { import { map, distinctUntilChanged, shareReplay, takeUntil, debounceTime } from 'rxjs'; import { isDeepStrictEqual } from 'util'; -import type { RootSchema } from '@kbn/ebt/client'; +import type { RootSchema } from '@elastic/ebt/client'; import type { Logger, LogMeta } from '@kbn/logging'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { PluginName } from '@kbn/core-base-common'; diff --git a/packages/core/status/core-status-server-internal/tsconfig.json b/packages/core/status/core-status-server-internal/tsconfig.json index 42d4e92c57e0a..bda646809e414 100644 --- a/packages/core/status/core-status-server-internal/tsconfig.json +++ b/packages/core/status/core-status-server-internal/tsconfig.json @@ -41,7 +41,6 @@ "@kbn/core-analytics-server-mocks", "@kbn/core-logging-server-internal", "@kbn/core-logging-server-mocks", - "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/packages/kbn-alerts-grouping/src/components/alerts_grouping.test.tsx b/packages/kbn-alerts-grouping/src/components/alerts_grouping.test.tsx index c60ef03ca35e5..87517def778cd 100644 --- a/packages/kbn-alerts-grouping/src/components/alerts_grouping.test.tsx +++ b/packages/kbn-alerts-grouping/src/components/alerts_grouping.test.tsx @@ -33,8 +33,8 @@ jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_get_alerts_group_aggregati useGetAlertsGroupAggregationsQuery: jest.fn(), })); -jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alert_data_view', () => ({ - useAlertDataView: jest.fn().mockReturnValue({ dataViews: [{ fields: [] }] }), +jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view', () => ({ + useAlertsDataView: jest.fn().mockReturnValue({ dataView: { fields: [] } }), })); jest.mock('../contexts/alerts_grouping_context', () => { diff --git a/packages/kbn-alerts-grouping/src/components/alerts_grouping.tsx b/packages/kbn-alerts-grouping/src/components/alerts_grouping.tsx index 130997dd393ce..f17d794668371 100644 --- a/packages/kbn-alerts-grouping/src/components/alerts_grouping.tsx +++ b/packages/kbn-alerts-grouping/src/components/alerts_grouping.tsx @@ -20,7 +20,7 @@ import type { Filter } from '@kbn/es-query'; import { isNoneGroup, useGrouping } from '@kbn/grouping'; import { isEqual } from 'lodash/fp'; import { i18n } from '@kbn/i18n'; -import { useAlertDataView } from '@kbn/alerts-ui-shared'; +import { useAlertsDataView } from '@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import { AlertsGroupingLevel, AlertsGroupingLevelProps } from './alerts_grouping_level'; import { AlertsGroupingProps } from '../types'; @@ -72,13 +72,12 @@ const AlertsGroupingInternal = (props: AlertsGroupingProps) => { const { dataViews, notifications, http } = services; const { grouping, updateGrouping } = useAlertsGroupingState(groupingId); - const { dataViews: alertDataViews } = useAlertDataView({ + const { dataView } = useAlertsDataView({ featureIds, dataViewsService: dataViews, http, toasts: notifications.toasts, }); - const dataView = useMemo(() => alertDataViews?.[0], [alertDataViews]); const [pageSize, setPageSize] = useLocalStorage<number[]>( `grouping-table-${groupingId}`, Array(MAX_GROUPING_LEVELS).fill(DEFAULT_PAGE_SIZE) diff --git a/packages/kbn-alerts-grouping/src/components/alerts_grouping_level.test.tsx b/packages/kbn-alerts-grouping/src/components/alerts_grouping_level.test.tsx index 45424f34d9cb4..f1295ee67dd73 100644 --- a/packages/kbn-alerts-grouping/src/components/alerts_grouping_level.test.tsx +++ b/packages/kbn-alerts-grouping/src/components/alerts_grouping_level.test.tsx @@ -24,7 +24,7 @@ mockUseGetAlertsGroupAggregationsQuery.mockReturnValue({ data: groupingSearchResponse, }); -jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alert_data_view', () => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view', () => ({ useAlertDataView: jest.fn().mockReturnValue({ dataViews: [{ fields: [] }] }), })); diff --git a/packages/kbn-alerts-ui-shared/src/alert_filter_controls/alert_filter_controls.test.tsx b/packages/kbn-alerts-ui-shared/src/alert_filter_controls/alert_filter_controls.test.tsx new file mode 100644 index 0000000000000..54b74e4b2259a --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/alert_filter_controls/alert_filter_controls.test.tsx @@ -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 React from 'react'; +import { render, screen } from '@testing-library/react'; +import { AlertFilterControls, AlertFilterControlsProps } from './alert_filter_controls'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { DEFAULT_CONTROLS } from './constants'; +import { useAlertsDataView } from '../common/hooks/use_alerts_data_view'; +import { FilterGroup } from './filter_group'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; + +jest.mock('./filter_group'); +jest.mocked(FilterGroup).mockReturnValue(<span data-test-subj="filter-group" />); + +jest.mock('../common/hooks/use_alerts_data_view'); +jest.mocked(useAlertsDataView).mockReturnValue({ + isLoading: false, + dataView: { + title: '.alerts-*', + fields: [ + { + name: 'event.action', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, +}); + +const mockServices = { + http: httpServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + dataViews: dataViewPluginMocks.createStartContract(), + storage: class { + get = jest.fn(); + set = jest.fn(); + } as unknown as AlertFilterControlsProps['services']['storage'], +}; +mockServices.dataViews.clearInstanceCache = jest.fn().mockResolvedValue(undefined); + +const setFilters = jest.fn(); + +const ControlGroupRenderer = (() => ( + <span /> +)) as unknown as AlertFilterControlsProps['ControlGroupRenderer']; + +describe('AlertFilterControls', () => { + const props: AlertFilterControlsProps = { + featureIds: [AlertConsumers.STACK_ALERTS], + defaultControls: DEFAULT_CONTROLS, + dataViewSpec: { + id: 'alerts-filters-dv', + }, + onFiltersChange: setFilters, + services: mockServices, + chainingSystem: 'HIERARCHICAL', + ControlGroupRenderer, + }; + + it('renders the filter group', async () => { + render(<AlertFilterControls {...props} />); + + expect(await screen.findByTestId('filter-group')).toBeInTheDocument(); + }); + + it('creates a data view if a spec with an id is provided', () => { + render(<AlertFilterControls {...props} />); + + expect(mockServices.dataViews.create).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'alerts-filters-dv', + }) + ); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/alert_filter_controls/alert_filter_controls.tsx b/packages/kbn-alerts-ui-shared/src/alert_filter_controls/alert_filter_controls.tsx index fc2b9fbbc207f..436480677d68b 100644 --- a/packages/kbn-alerts-ui-shared/src/alert_filter_controls/alert_filter_controls.tsx +++ b/packages/kbn-alerts-ui-shared/src/alert_filter_controls/alert_filter_controls.tsx @@ -15,7 +15,7 @@ import { AlertConsumers } from '@kbn/rule-data-utils'; import { HttpStart } from '@kbn/core-http-browser'; import { NotificationsStart } from '@kbn/core-notifications-browser'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; -import { useAlertDataView } from '../..'; +import { useAlertsDataView } from '../..'; import { FilterGroupLoading } from './loading'; import { DEFAULT_CONTROLS } from './constants'; import { FilterGroup } from './filter_group'; @@ -94,7 +94,7 @@ export const AlertFilterControls = (props: AlertFilterControlsProps) => { ...restFilterItemGroupProps } = props; const [loadingPageFilters, setLoadingPageFilters] = useState(true); - const { dataViews: alertDataViews, loading: loadingDataViews } = useAlertDataView({ + const { dataView, isLoading: isLoadingDataView } = useAlertsDataView({ featureIds, dataViewsService: dataViews, http, @@ -102,14 +102,14 @@ export const AlertFilterControls = (props: AlertFilterControlsProps) => { }); useEffect(() => { - if (!loadingDataViews) { + if (!isLoadingDataView) { // If a data view spec is provided, create a new data view if (dataViewSpec?.id) { (async () => { // Creates an adhoc data view starting from the alert data view // and applying the overrides specified in the dataViewSpec const spec = { - ...(alertDataViews?.[0] ?? {}), + ...(dataView ?? {}), ...(dataViewSpec ?? {}), } as DataViewSpec; await dataViews.create(spec); @@ -121,7 +121,7 @@ export const AlertFilterControls = (props: AlertFilterControlsProps) => { } return () => dataViews.clearInstanceCache(); - }, [dataViewSpec, alertDataViews, dataViews, loadingDataViews]); + }, [dataView, dataViewSpec, dataViews, isLoadingDataView]); const handleFilterChanges = useCallback( (newFilters: Filter[]) => { diff --git a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx index 9a095200a8506..c187ace912e30 100644 --- a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx +++ b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx @@ -6,21 +6,23 @@ * Side Public License, v 1. */ -import { useCallback, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import type { Query, TimeRange } from '@kbn/es-query'; import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; -import { AlertConsumers } from '@kbn/rule-data-utils'; +import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils'; import { NO_INDEX_PATTERNS } from './constants'; import { SEARCH_BAR_PLACEHOLDER } from './translations'; import type { AlertsSearchBarProps, QueryLanguageType } from './types'; -import { useLoadRuleTypesQuery, useAlertDataView, useRuleAADFields } from '../common/hooks'; +import { useLoadRuleTypesQuery, useAlertsDataView, useRuleAADFields } from '../common/hooks'; const SA_ALERTS = { type: 'alerts', fields: {} } as SuggestionsAbstraction; +const EMPTY_FEATURE_IDS: ValidFeatureId[] = []; + export const AlertsSearchBar = ({ appName, disableQueryLanguageSwitcher = false, - featureIds, + featureIds = EMPTY_FEATURE_IDS, ruleTypeId, query, filters, @@ -40,8 +42,8 @@ export const AlertsSearchBar = ({ dataViewsService, }: AlertsSearchBarProps) => { const [queryLanguage, setQueryLanguage] = useState<QueryLanguageType>('kuery'); - const { dataViews, loading } = useAlertDataView({ - featureIds: featureIds ?? [], + const { dataView } = useAlertsDataView({ + featureIds, http, toasts, dataViewsService, @@ -52,8 +54,15 @@ export const AlertsSearchBar = ({ toasts, }); - const indexPatterns = - ruleTypeId && aadFields?.length ? [{ title: ruleTypeId, fields: aadFields }] : dataViews; + const indexPatterns = useMemo(() => { + if (ruleTypeId && aadFields?.length) { + return [{ title: ruleTypeId, fields: aadFields }]; + } + if (dataView) { + return [dataView]; + } + return null; + }, [aadFields, dataView, ruleTypeId]); const ruleType = useLoadRuleTypesQuery({ filteredRuleTypes: ruleTypeId !== undefined ? [ruleTypeId] : [], @@ -99,7 +108,7 @@ export const AlertsSearchBar = ({ appName, disableQueryLanguageSwitcher, // @ts-expect-error - DataView fields prop and SearchBar indexPatterns props are overly broad - indexPatterns: loading || fieldsLoading ? NO_INDEX_PATTERNS : indexPatterns, + indexPatterns: !indexPatterns || fieldsLoading ? NO_INDEX_PATTERNS : indexPatterns, placeholder, query: { query: query ?? '', language: queryLanguage }, filters, diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alert_index_names.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alert_index_names.ts deleted file mode 100644 index 9cf7fe1d3ebc8..0000000000000 --- a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alert_index_names.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 { HttpSetup } from '@kbn/core/public'; -import { BASE_RAC_ALERTS_API_PATH } from '../constants'; - -export async function fetchAlertIndexNames({ - http, - features, -}: { - http: HttpSetup; - features: string; -}): Promise<string[]> { - const { index_name: indexNamesStr = [] } = await http.get<{ index_name: string[] }>( - `${BASE_RAC_ALERTS_API_PATH}/index`, - { - query: { features }, - } - ); - return indexNamesStr; -} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_fields/fetch_alerts_fields.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_fields/fetch_alerts_fields.ts index 3deafc37e8ce4..abe38a8fa3ab6 100644 --- a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_fields/fetch_alerts_fields.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_fields/fetch_alerts_fields.ts @@ -11,10 +11,11 @@ import type { BrowserFields } from '@kbn/alerting-types'; import type { FetchAlertsFieldsParams } from './types'; import { BASE_RAC_ALERTS_API_PATH } from '../../constants'; -export const fetchAlertsFields = ({ http, featureIds }: FetchAlertsFieldsParams) => - http.get<{ browserFields: BrowserFields; fields: FieldDescriptor[] }>( +export const fetchAlertsFields = ({ http, featureIds }: FetchAlertsFieldsParams) => { + return http.get<{ browserFields: BrowserFields; fields: FieldDescriptor[] }>( `${BASE_RAC_ALERTS_API_PATH}/browser_fields`, { query: { featureIds }, } ); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/fetch_alerts_index_names.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/fetch_alerts_index_names.test.ts new file mode 100644 index 0000000000000..0eaa288cf999f --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/fetch_alerts_index_names.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { httpServiceMock } from '@kbn/core/public/mocks'; +import { fetchAlertsIndexNames } from '.'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { BASE_RAC_ALERTS_API_PATH } from '../../constants'; + +describe('fetchAlertsIndexNames', () => { + const http = httpServiceMock.createStartContract(); + + it('calls the alerts/index API with the correct parameters', async () => { + const featureIds = [AlertConsumers.STACK_ALERTS, AlertConsumers.APM]; + const indexNames = ['test-index']; + http.get.mockResolvedValueOnce({ + index_name: indexNames, + }); + const result = await fetchAlertsIndexNames({ http, featureIds }); + expect(result).toEqual(indexNames); + expect(http.get).toHaveBeenLastCalledWith(`${BASE_RAC_ALERTS_API_PATH}/index`, { + query: { features: featureIds.join(',') }, + }); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/fetch_alerts_index_names.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/fetch_alerts_index_names.ts new file mode 100644 index 0000000000000..29b8f97a5cdee --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/fetch_alerts_index_names.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BASE_RAC_ALERTS_API_PATH } from '../../constants'; +import { FetchAlertsIndexNamesParams } from './types'; + +export const fetchAlertsIndexNames = async ({ http, featureIds }: FetchAlertsIndexNamesParams) => { + const { index_name: indexNames = [] } = await http.get<{ index_name: string[] }>( + `${BASE_RAC_ALERTS_API_PATH}/index`, + { + query: { features: featureIds.join(',') }, + } + ); + return indexNames; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/index.ts new file mode 100644 index 0000000000000..c1f3202108554 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/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 * from './fetch_alerts_index_names'; +export * from './types'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/types.ts new file mode 100644 index 0000000000000..ef74ad7dd09f6 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerts_index_names/types.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 { HttpSetup } from '@kbn/core-http-browser'; +import { ValidFeatureId } from '@kbn/rule-data-utils'; + +export interface FetchAlertsIndexNamesParams { + // Dependencies + http: HttpSetup; + + // Params + /** + * Array of feature ids used for authorization and area-based filtering + */ + featureIds: ValidFeatureId[]; +} diff --git a/packages/kbn-alerts-ui-shared/src/common/constants/alerts.ts b/packages/kbn-alerts-ui-shared/src/common/constants/alerts.ts new file mode 100644 index 0000000000000..858d5a3d53425 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/constants/alerts.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. + */ + +import type { DataViewField } from '@kbn/data-views-plugin/common'; + +export const DEFAULT_ALERTS_PAGE_SIZE = 10; +export const EMPTY_AAD_FIELDS: DataViewField[] = []; diff --git a/packages/kbn-alerts-ui-shared/src/common/constants/index.ts b/packages/kbn-alerts-ui-shared/src/common/constants/index.ts index c3619df3ef99d..da5117831d092 100644 --- a/packages/kbn-alerts-ui-shared/src/common/constants/index.ts +++ b/packages/kbn-alerts-ui-shared/src/common/constants/index.ts @@ -6,5 +6,6 @@ * Side Public License, v 1. */ +export * from './alerts'; export * from './i18n_weekdays'; export * from './routes'; diff --git a/packages/kbn-alerts-ui-shared/src/common/constants/routes.ts b/packages/kbn-alerts-ui-shared/src/common/constants/routes.ts index 3bd42bbfd3f81..90ba57feb128b 100644 --- a/packages/kbn-alerts-ui-shared/src/common/constants/routes.ts +++ b/packages/kbn-alerts-ui-shared/src/common/constants/routes.ts @@ -6,14 +6,10 @@ * Side Public License, v 1. */ -import type { DataViewField } from '@kbn/data-views-plugin/common'; - export const ALERTS_FEATURE_ID = 'alerts'; export const BASE_ALERTING_API_PATH = '/api/alerting'; export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting'; export const BASE_RAC_ALERTS_API_PATH = '/internal/rac/alerts'; -export const EMPTY_AAD_FIELDS: DataViewField[] = []; export const BASE_TRIGGERS_ACTIONS_UI_API_PATH = '/internal/triggers_actions_ui'; -export const DEFAULT_ALERTS_PAGE_SIZE = 10; export const BASE_ACTION_API_PATH = '/api/actions'; export const INTERNAL_BASE_ACTION_API_PATH = '/internal/actions'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts index ac7504eac2f82..06b1c77d5d3de 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts @@ -6,16 +6,14 @@ * Side Public License, v 1. */ -export * from './use_alert_data_view'; -export * from './use_find_alerts_query'; +export * from './use_alerts_data_view'; +export * from './use_create_rule'; +export * from './use_get_alerts_group_aggregations_query'; +export * from './use_health_check'; +export * from './use_load_alerting_framework_health'; export * from './use_load_rule_types_query'; -export * from './use_rule_aad_fields'; export * from './use_load_ui_config'; -export * from './use_health_check'; export * from './use_load_ui_health'; -export * from './use_load_alerting_framework_health'; -export * from './use_create_rule'; -export * from './use_update_rule'; export * from './use_resolve_rule'; -export * from './use_search_alerts_query'; -export * from './use_get_alerts_group_aggregations_query'; +export * from './use_rule_aad_fields'; +export * from './use_update_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_alert_data_view.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_alert_data_view.ts deleted file mode 100644 index dfa0bd12f85ae..0000000000000 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_alert_data_view.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 and the Server Side Public License, v 1; you may not 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 { i18n } from '@kbn/i18n'; -import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/common'; -import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils'; -import type { ToastsStart, HttpStart } from '@kbn/core/public'; - -import { useQuery } from '@tanstack/react-query'; -import { useFetchAlertsFieldsQuery } from './use_fetch_alerts_fields_query'; -import { fetchAlertIndexNames } from '../apis/fetch_alert_index_names'; - -export interface UseAlertDataViewResult { - dataViews?: DataView[]; - loading: boolean; -} - -export interface UseAlertDataViewProps { - featureIds: ValidFeatureId[]; - http: HttpStart; - dataViewsService: DataViewsContract; - toasts: ToastsStart; -} - -export function useAlertDataView(props: UseAlertDataViewProps): UseAlertDataViewResult { - const { http, dataViewsService, toasts, featureIds } = props; - - const [dataViews, setDataViews] = useState<DataView[]>([]); - const features = featureIds.sort().join(','); - const isOnlySecurity = featureIds.length === 1 && featureIds.includes(AlertConsumers.SIEM); - - const hasSecurityAndO11yFeatureIds = - featureIds.length > 1 && featureIds.includes(AlertConsumers.SIEM); - - const hasNoSecuritySolution = - featureIds.length > 0 && !isOnlySecurity && !hasSecurityAndO11yFeatureIds; - - const queryIndexNameFn = () => { - return fetchAlertIndexNames({ http, features }); - }; - - const onErrorFn = () => { - toasts.addDanger( - i18n.translate('alertsUIShared.hooks.useAlertDataView.useAlertDataMessage', { - defaultMessage: 'Unable to load alert data view', - }) - ); - }; - - const { - data: indexNames, - isSuccess: isIndexNameSuccess, - isInitialLoading: isIndexNameInitialLoading, - isLoading: isIndexNameLoading, - } = useQuery({ - queryKey: ['loadAlertIndexNames', features], - queryFn: queryIndexNameFn, - onError: onErrorFn, - refetchOnWindowFocus: false, - staleTime: 60 * 1000, // To prevent duplicated requests - enabled: featureIds.length > 0 && !hasSecurityAndO11yFeatureIds, - }); - - const { - data: { fields: alertFields }, - isSuccess: isAlertFieldsSuccess, - isInitialLoading: isAlertFieldsInitialLoading, - isLoading: isAlertFieldsLoading, - } = useFetchAlertsFieldsQuery( - { http, featureIds }, - { - onError: onErrorFn, - refetchOnWindowFocus: false, - staleTime: 60 * 1000, - enabled: hasNoSecuritySolution, - } - ); - - useEffect(() => { - return () => { - dataViews.map((dv) => { - dataViewsService.clearInstanceCache(dv.id); - }); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataViews]); - - // FUTURE ENGINEER this useEffect is for security solution user since - // we are using the user privilege to access the security alert index - useEffect(() => { - async function createDataView() { - const localDataview = await dataViewsService.create({ - title: (indexNames ?? []).join(','), - allowNoIndex: true, - }); - setDataViews([localDataview]); - } - - if (isOnlySecurity && isIndexNameSuccess) { - createDataView(); - } - }, [dataViewsService, indexNames, isIndexNameSuccess, isOnlySecurity]); - - // FUTURE ENGINEER this useEffect is for o11y and stack solution user since - // we are using the kibana user privilege to access the alert index - useEffect(() => { - if ( - indexNames && - alertFields && - !isOnlySecurity && - isAlertFieldsSuccess && - isIndexNameSuccess - ) { - setDataViews([ - { - title: (indexNames ?? []).join(','), - fieldFormatMap: {}, - fields: (alertFields ?? [])?.map((field) => { - return { - ...field, - ...(field.esTypes && field.esTypes.includes('flattened') ? { type: 'string' } : {}), - }; - }), - }, - ] as unknown as DataView[]); - } - }, [ - alertFields, - dataViewsService, - indexNames, - isIndexNameSuccess, - isOnlySecurity, - isAlertFieldsSuccess, - ]); - - return useMemo( - () => ({ - dataViews, - loading: - featureIds.length === 0 || hasSecurityAndO11yFeatureIds - ? false - : isOnlySecurity - ? isIndexNameInitialLoading || isIndexNameLoading || dataViews.length === 0 - : isIndexNameInitialLoading || - isIndexNameLoading || - isAlertFieldsInitialLoading || - isAlertFieldsLoading, - }), - [ - dataViews, - featureIds.length, - hasSecurityAndO11yFeatureIds, - isOnlySecurity, - isIndexNameInitialLoading, - isIndexNameLoading, - isAlertFieldsInitialLoading, - isAlertFieldsLoading, - ] - ); -} diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.test.tsx new file mode 100644 index 0000000000000..dbe46a9dbeefa --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.test.tsx @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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, { FunctionComponent } from 'react'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { fetchAlertsIndexNames } from '../apis/fetch_alerts_index_names'; +import { fetchAlertsFields } from '../apis/fetch_alerts_fields'; +import { testQueryClientConfig } from '../test_utils/test_query_client_config'; +import { useAlertsDataView } from './use_alerts_data_view'; + +jest.mock('../apis/fetch_alerts_index_names'); +const mockFetchAlertsIndexNames = jest + .mocked(fetchAlertsIndexNames) + .mockResolvedValue([ + '.alerts-observability.uptime.alerts-*', + '.alerts-observability.metrics.alerts-*', + '.alerts-observability.logs.alerts-*', + '.alerts-observability.apm.alerts-*', + ]); + +jest.mock('../apis/fetch_alerts_fields'); +const mockFetchAlertsFields = jest + .mocked(fetchAlertsFields) + .mockResolvedValue({ browserFields: {}, fields: [] }); + +const mockDataView = { fields: [] } as unknown as DataView; + +const mockServices = { + http: httpServiceMock.createStartContract(), + toasts: notificationServiceMock.createStartContract().toasts, + dataViewsService: dataViewPluginMocks.createStartContract(), +}; +mockServices.dataViewsService.create.mockResolvedValue(mockDataView); + +const queryClient = new QueryClient(testQueryClientConfig); + +const wrapper: FunctionComponent = ({ children }) => ( + <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> +); + +describe('useAlertsDataView', () => { + const observabilityFeatureIds = [ + AlertConsumers.APM, + AlertConsumers.INFRASTRUCTURE, + AlertConsumers.LOGS, + AlertConsumers.UPTIME, + ]; + + beforeEach(() => { + jest.clearAllMocks(); + queryClient.clear(); + }); + + it('starts with a loading state and without data', async () => { + const mockedAsyncDataView = { + isLoading: true, + dataView: undefined, + }; + + const { result, waitFor } = renderHook( + () => + useAlertsDataView({ + ...mockServices, + featureIds: observabilityFeatureIds, + }), + { + wrapper, + } + ); + + await waitFor(() => expect(result.current).toEqual(mockedAsyncDataView)); + }); + + it('fetches indexes and fields for non-siem feature ids, returning a DataViewBase object', async () => { + const { result, waitForValueToChange } = renderHook( + () => + useAlertsDataView({ + ...mockServices, + featureIds: observabilityFeatureIds, + }), + { + wrapper, + } + ); + + await waitForValueToChange(() => result.current.isLoading, { timeout: 5000 }); + + expect(mockFetchAlertsFields).toHaveBeenCalledTimes(1); + expect(mockFetchAlertsIndexNames).toHaveBeenCalledTimes(1); + expect(result.current.dataView).not.toBe(mockDataView); + }); + + it('only fetches index names for the siem feature id, returning a DataView', async () => { + const { result, waitFor } = renderHook( + () => useAlertsDataView({ ...mockServices, featureIds: [AlertConsumers.SIEM] }), + { + wrapper, + } + ); + + await waitFor(() => expect(mockFetchAlertsIndexNames).toHaveBeenCalledTimes(1)); + expect(mockFetchAlertsFields).toHaveBeenCalledTimes(0); + + await waitFor(() => expect(result.current.dataView).toBe(mockDataView)); + }); + + it('does not fetch anything if siem and other feature ids are mixed together', async () => { + const { result, waitFor } = renderHook( + () => + useAlertsDataView({ + ...mockServices, + featureIds: [AlertConsumers.SIEM, AlertConsumers.LOGS], + }), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current).toEqual({ + isLoading: false, + dataView: undefined, + }) + ); + expect(mockFetchAlertsIndexNames).toHaveBeenCalledTimes(0); + expect(mockFetchAlertsFields).toHaveBeenCalledTimes(0); + }); + + it('returns an undefined data view if any of the queries fails', async () => { + mockFetchAlertsIndexNames.mockRejectedValue('error'); + + const { result, waitFor } = renderHook( + () => useAlertsDataView({ ...mockServices, featureIds: observabilityFeatureIds }), + { + wrapper, + } + ); + + await waitFor(() => + expect(result.current).toEqual({ + isLoading: false, + dataView: undefined, + }) + ); + }); + + it('shows an error toast if any of the queries fails', async () => { + mockFetchAlertsIndexNames.mockRejectedValue('error'); + + const { waitFor } = renderHook( + () => useAlertsDataView({ ...mockServices, featureIds: observabilityFeatureIds }), + { + wrapper, + } + ); + + await waitFor(() => expect(mockServices.toasts.addDanger).toHaveBeenCalled()); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.ts new file mode 100644 index 0000000000000..5d8b3a9ee109f --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_alerts_data_view.ts @@ -0,0 +1,196 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 } from 'react'; +import { i18n } from '@kbn/i18n'; +import { DataView, DataViewsContract, FieldSpec } from '@kbn/data-views-plugin/common'; +import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils'; +import type { ToastsStart, HttpStart } from '@kbn/core/public'; + +import { DataViewBase } from '@kbn/es-query'; +import { useVirtualDataViewQuery } from './use_virtual_data_view_query'; +import { useFetchAlertsFieldsQuery } from './use_fetch_alerts_fields_query'; +import { useFetchAlertsIndexNamesQuery } from './use_fetch_alerts_index_names_query'; + +export interface UseAlertsDataViewParams { + // Dependencies + http: HttpStart; + dataViewsService: DataViewsContract; + toasts: ToastsStart; + + // Params + /** + * Array of feature ids used for authorization and area-based filtering + * + * Security data views must be requested in isolation (i.e. `['siem']`). If mixed with + * other feature ids, the resulting data view will be empty. + */ + featureIds: ValidFeatureId[]; +} + +export interface UseAlertsDataViewResult { + isLoading: boolean; + dataView?: Omit<DataViewBase, 'fields'> & { fields: FieldSpec[] }; +} + +/** + * Resolves the DataView or DataViewBase object + * + * Returns undefined if any of the dependencies are in error state + */ +const resolveDataView = ({ + isError, + fields, + indexNames, + virtualDataView, +}: { + isError: boolean; + virtualDataView?: DataView; + indexNames?: string[]; + fields?: { fields: FieldSpec[] }; +}) => { + if (isError) { + return; + } + // When the only feature id is Security Solution, use an in-memory data view: + // their alerting authorization is based on end-user privileges, which allows us to create + // an actual data view + if (virtualDataView) { + return virtualDataView; + } + // For all other feature id combinations, compute the data view from the fetched index names and + // fields since the Kibana-user-based authorization wouldn't allow us to create a data view + if (indexNames) { + return { + title: indexNames.join(','), + fieldFormatMap: {}, + fields: (fields?.fields ?? []).map((field) => { + return { + ...field, + ...(field.esTypes && field.esTypes.includes('flattened') ? { type: 'string' } : {}), + }; + }), + }; + } +}; + +/** + * Computes a {@link DataViewBase} object for alerts indices based on the provided feature ids + * + * @returns + * A {@link DataViewBase} object, intentionally not typed as a complete {@link DataView} object + * since only Security Solution uses an actual in-memory data view (when `featureIds = ['siem']). + * In all other cases the data view is computed from the index names and fields fetched from the + * alerting APIs. + */ +export const useAlertsDataView = ({ + http, + dataViewsService, + toasts, + featureIds, +}: UseAlertsDataViewParams): UseAlertsDataViewResult => { + const includesSecurity = featureIds.includes(AlertConsumers.SIEM); + const isOnlySecurity = featureIds.length === 1 && includesSecurity; + const hasMixedFeatureIds = featureIds.length > 1 && includesSecurity; + + const { + data: indexNames, + isError: isIndexNamesError, + isLoading: isLoadingIndexNames, + isInitialLoading: isInitialLoadingIndexNames, + } = useFetchAlertsIndexNamesQuery( + { http, featureIds }, + { + // Don't fetch index names when featureIds includes both Security Solution and other features + enabled: !!featureIds.length && (isOnlySecurity || !includesSecurity), + } + ); + + const { + data: fields, + isError: isFieldsError, + isLoading: isLoadingFields, + isInitialLoading: isInitialLoadingFields, + } = useFetchAlertsFieldsQuery( + { http, featureIds }, + { + // Don't fetch fields when featureIds includes Security Solution + enabled: !!featureIds.length && !includesSecurity, + } + ); + + const { data: virtualDataView, isError: isVirtualDataViewError } = useVirtualDataViewQuery( + { + dataViewsService, + indexNames, + }, + { + // Create data view only when featureIds = ['siem'] and indexNames have been fetched + enabled: isOnlySecurity && !!indexNames?.length, + } + ); + + useEffect(() => { + if (isIndexNamesError || isFieldsError || isVirtualDataViewError) { + toasts.addDanger( + i18n.translate('alertsUIShared.hooks.useAlertDataView.fetchErrorMessage', { + defaultMessage: 'Unable to load alert data view', + }) + ); + } + }, [isFieldsError, isIndexNamesError, isVirtualDataViewError, toasts]); + + const dataView = useMemo( + () => + resolveDataView({ + isError: isIndexNamesError || isFieldsError || isVirtualDataViewError, + fields, + indexNames, + virtualDataView: !isOnlySecurity ? undefined : virtualDataView, + }), + [ + fields, + indexNames, + isFieldsError, + isIndexNamesError, + isOnlySecurity, + isVirtualDataViewError, + virtualDataView, + ] + ); + + return useMemo(() => { + let isLoading: boolean; + if (!featureIds.length || hasMixedFeatureIds) { + isLoading = false; + } else { + if (isOnlySecurity) { + isLoading = isInitialLoadingIndexNames || isLoadingIndexNames || !dataView; + } else { + isLoading = + isInitialLoadingIndexNames || + isLoadingIndexNames || + isInitialLoadingFields || + isLoadingFields; + } + } + return { + dataView, + isLoading, + }; + }, [ + dataView, + featureIds.length, + hasMixedFeatureIds, + isInitialLoadingFields, + isInitialLoadingIndexNames, + isLoadingFields, + isLoadingIndexNames, + isOnlySecurity, + ]); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx index adcf3b37e38cb..20bdc513fa617 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.test.tsx @@ -7,12 +7,14 @@ */ import React, { FunctionComponent } from 'react'; -import type { HttpSetup } from '@kbn/core-http-browser'; import { AlertConsumers } from '@kbn/rule-data-utils'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import * as ReactQuery from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; import { testQueryClientConfig } from '../test_utils/test_query_client_config'; import { useFetchAlertsFieldsQuery } from './use_fetch_alerts_fields_query'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; + +const { QueryClient, QueryClientProvider } = ReactQuery; const queryClient = new QueryClient(testQueryClientConfig); @@ -20,9 +22,9 @@ const wrapper: FunctionComponent = ({ children }) => ( <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> ); -const mockHttpClient = { - get: jest.fn(), -} as unknown as HttpSetup; +const useQuerySpy = jest.spyOn(ReactQuery, 'useQuery'); + +const mockHttpClient = httpServiceMock.createStartContract(); const emptyData = { browserFields: {}, fields: [] }; @@ -57,6 +59,30 @@ describe('useFetchAlertsFieldsQuery', () => { expect(result.current.data).toEqual(emptyData); }); + it('should correctly override the `enabled` option', () => { + const { rerender } = renderHook( + ({ featureIds, enabled }: { featureIds: AlertConsumers[]; enabled?: boolean }) => + useFetchAlertsFieldsQuery({ http: mockHttpClient, featureIds }, { enabled }), + { + wrapper, + initialProps: { + featureIds: ['apm'], + enabled: false, + }, + } + ); + + expect(useQuerySpy).toHaveBeenCalledWith(expect.objectContaining({ enabled: false })); + + rerender({ featureIds: [], enabled: true }); + + expect(useQuerySpy).toHaveBeenCalledWith(expect.objectContaining({ enabled: false })); + + rerender({ featureIds: ['apm'] }); + + expect(useQuerySpy).toHaveBeenCalledWith(expect.objectContaining({ enabled: true })); + }); + it('should call the api only once', async () => { const { result, rerender, waitForValueToChange } = renderHook( () => useFetchAlertsFieldsQuery({ http: mockHttpClient, featureIds: ['apm'] }), diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.ts index 5d0f785335b22..f22ea573baf15 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query.ts @@ -27,7 +27,7 @@ export const useFetchAlertsFieldsQuery = ( { http, ...params }: UseFetchAlertsFieldsQueryParams, options?: Pick< QueryOptionsOverrides<typeof fetchAlertsFields>, - 'context' | 'onError' | 'refetchOnWindowFocus' | 'staleTime' | 'enabled' + 'placeholderData' | 'context' | 'onError' | 'refetchOnWindowFocus' | 'staleTime' | 'enabled' > ) => { const { featureIds } = params; @@ -37,10 +37,12 @@ export const useFetchAlertsFieldsQuery = ( ); return useQuery({ - queryKey: queryKeyPrefix.concat(JSON.stringify(featureIds)), + queryKey: queryKeyPrefix.concat(featureIds), queryFn: () => fetchAlertsFields({ http, featureIds: validFeatureIds }), - enabled: validFeatureIds.length > 0, - initialData: { browserFields: {}, fields: [] }, + placeholderData: { browserFields: {}, fields: [] }, + staleTime: 60 * 1000, + refetchOnWindowFocus: false, ...options, + enabled: validFeatureIds.length > 0 && (options?.enabled == null || options.enabled), }); }; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.test.tsx new file mode 100644 index 0000000000000..ebd4d534e09ee --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.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, { FunctionComponent } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks'; +import { testQueryClientConfig } from '../test_utils/test_query_client_config'; +import { useFetchAlertsIndexNamesQuery } from './use_fetch_alerts_index_names_query'; +import { fetchAlertsIndexNames } from '../apis/fetch_alerts_index_names'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; + +jest.mock('../apis/fetch_alerts_index_names'); + +const queryClient = new QueryClient(testQueryClientConfig); + +const wrapper: FunctionComponent = ({ children }) => ( + <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> +); + +const mockHttpClient = httpServiceMock.createStartContract(); +const mockFetchAlertsIndexNames = jest.mocked(fetchAlertsIndexNames); + +describe('useFetchAlertsIndexNamesQuery', () => { + beforeEach(() => { + mockFetchAlertsIndexNames.mockResolvedValue(['test-index']); + }); + + afterEach(() => { + jest.clearAllMocks(); + queryClient.clear(); + }); + + it('does not fetch if featureIds is empty', () => { + renderHook(() => useFetchAlertsIndexNamesQuery({ http: mockHttpClient, featureIds: [] }), { + wrapper, + }); + + expect(mockFetchAlertsIndexNames).not.toHaveBeenCalled(); + }); + + it('calls fetchAlertsIndexNames with the correct parameters', () => { + renderHook(() => useFetchAlertsIndexNamesQuery({ http: mockHttpClient, featureIds: ['apm'] }), { + wrapper, + }); + + expect(mockFetchAlertsIndexNames).toHaveBeenCalledWith({ + http: mockHttpClient, + featureIds: ['apm'], + }); + }); + + it('correctly caches the index names', async () => { + const { result, rerender, waitForValueToChange } = renderHook( + () => useFetchAlertsIndexNamesQuery({ http: mockHttpClient, featureIds: ['apm'] }), + { + wrapper, + } + ); + + await waitForValueToChange(() => result.current.data); + + expect(mockFetchAlertsIndexNames).toHaveBeenCalledTimes(1); + + rerender(); + + expect(mockFetchAlertsIndexNames).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.ts new file mode 100644 index 0000000000000..bc4c277ba5059 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_fetch_alerts_index_names_query.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 { useQuery } from '@tanstack/react-query'; +import { + fetchAlertsIndexNames, + FetchAlertsIndexNamesParams, +} from '../apis/fetch_alerts_index_names'; +import type { QueryOptionsOverrides } from '../types/tanstack_query_utility_types'; + +export type UseFetchAlertsIndexNamesQueryParams = FetchAlertsIndexNamesParams; + +export const queryKeyPrefix = ['alerts', fetchAlertsIndexNames.name]; + +/** + * Fetch alerts index names feature ids + * + * When testing components that depend on this hook, prefer mocking the {@link fetchAlertsIndexNames} function instead of the hook itself. + * @external https://tanstack.com/query/v4/docs/framework/react/guides/testing + */ +export const useFetchAlertsIndexNamesQuery = ( + { http, featureIds }: UseFetchAlertsIndexNamesQueryParams, + options?: Pick< + QueryOptionsOverrides<typeof fetchAlertsIndexNames>, + 'context' | 'onError' | 'refetchOnWindowFocus' | 'staleTime' | 'enabled' + > +) => { + return useQuery({ + queryKey: queryKeyPrefix.concat(featureIds), + queryFn: () => fetchAlertsIndexNames({ http, featureIds }), + enabled: featureIds.length > 0, + staleTime: 60 * 1000, + refetchOnWindowFocus: false, + ...options, + }); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx index 30624b22772cb..893e28c6dc4f9 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_search_alerts_query.test.tsx @@ -12,9 +12,10 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { IKibanaSearchResponse } from '@kbn/search-types'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook } from '@testing-library/react-hooks'; -import type { UseSearchAlertsQueryParams } from '../../..'; +import type { UseSearchAlertsQueryParams } from './use_search_alerts_query'; import { AlertsQueryContext } from '../contexts/alerts_query_context'; import { useSearchAlertsQuery } from './use_search_alerts_query'; +import { testQueryClientConfig } from '../test_utils/test_query_client_config'; const searchResponse = { id: '0', @@ -84,15 +85,7 @@ const expectedResponse: ReturnType<typeof useSearchAlertsQuery>['data'] = { ecsAlertsData: [], }; -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - cacheTime: 0, - staleTime: 0, - retry: false, - }, - }, -}); +const queryClient = new QueryClient(testQueryClientConfig); describe('useSearchAlertsQuery', () => { const mockDataPlugin = { diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.test.tsx new file mode 100644 index 0000000000000..4bf57fc918393 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.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, { FunctionComponent } from 'react'; +import * as ReactQuery from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks'; +import { testQueryClientConfig } from '../test_utils/test_query_client_config'; +import { queryKeyPrefix, useVirtualDataViewQuery } from './use_virtual_data_view_query'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; + +const { QueryClient, QueryClientProvider } = ReactQuery; +const useQuerySpy = jest.spyOn(ReactQuery, 'useQuery'); + +const queryClient = new QueryClient(testQueryClientConfig); + +const wrapper: FunctionComponent = ({ children }) => ( + <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> +); + +const mockDataView = { fields: [] } as unknown as DataView; + +const mockDataViewsService = dataViewPluginMocks.createStartContract(); +mockDataViewsService.create.mockResolvedValue(mockDataView); +mockDataViewsService.clearInstanceCache = jest.fn(); + +describe('useVirtualDataViewQuery', () => { + afterEach(() => { + jest.clearAllMocks(); + queryClient.clear(); + }); + + it('does not create a data view if indexNames is empty or nullish', () => { + const { rerender } = renderHook( + ({ indexNames }: { indexNames: string[] }) => + useVirtualDataViewQuery({ dataViewsService: mockDataViewsService, indexNames }), + { + wrapper, + } + ); + + expect(mockDataViewsService.create).not.toHaveBeenCalled(); + rerender({ indexNames: [] }); + expect(useQuerySpy).toHaveBeenCalledWith( + expect.objectContaining({ enabled: false, queryKey: queryKeyPrefix.concat([]) }) + ); + + expect(mockDataViewsService.create).not.toHaveBeenCalled(); + }); + + it('calls dataViewsService.create with the correct index names', () => { + const indexNames = ['.alerts-stack*', '.alerts-o11y*']; + renderHook( + () => useVirtualDataViewQuery({ dataViewsService: mockDataViewsService, indexNames }), + { + wrapper, + } + ); + + expect(mockDataViewsService.create).toHaveBeenCalledWith({ + title: indexNames.join(','), + allowNoIndex: true, + }); + }); + + it('correctly caches the data view', () => { + const { rerender } = renderHook( + () => + useVirtualDataViewQuery({ + dataViewsService: mockDataViewsService, + indexNames: ['.alerts-*'], + }), + { + wrapper, + } + ); + + expect(mockDataViewsService.create).toHaveBeenCalledTimes(1); + + rerender(); + + expect(mockDataViewsService.create).toHaveBeenCalledTimes(1); + }); + + it('removes the data view from the instance cache on unmount', async () => { + const { result, waitForValueToChange, unmount } = renderHook( + () => + useVirtualDataViewQuery({ + dataViewsService: mockDataViewsService, + indexNames: ['.alerts-*'], + }), + { + wrapper, + } + ); + + await waitForValueToChange(() => result.current.data); + + unmount(); + + expect(mockDataViewsService.clearInstanceCache).toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.ts new file mode 100644 index 0000000000000..46eeb8f3065ff --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_virtual_data_view_query.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 type { DataViewsContract } from '@kbn/data-views-plugin/common'; +import { useQuery } from '@tanstack/react-query'; +import { useEffect } from 'react'; +import { QueryOptionsOverrides } from '../types/tanstack_query_utility_types'; + +export interface UseVirtualDataViewParams { + // Dependencies + dataViewsService: DataViewsContract; + + // Params + /** + * The index names to create the data view for + */ + indexNames?: string[]; +} + +export const queryKeyPrefix = ['alerts', 'dataView']; + +/** + * Creates an in-memory data view, cached by index names + * + * When testing components that depend on this hook, prefer mocking {@link DataViewsContract}'s + * create and clearInstanceCache method instead of the hook itself. + * @external https://tanstack.com/query/v4/docs/framework/react/guides/testing + */ +export const useVirtualDataViewQuery = ( + { dataViewsService, indexNames }: UseVirtualDataViewParams, + options?: QueryOptionsOverrides<DataViewsContract['create']> +) => { + const query = useQuery({ + queryKey: queryKeyPrefix.concat(indexNames ?? []), + queryFn: () => + dataViewsService.create({ + title: (indexNames ?? []).join(','), + allowNoIndex: true, + }), + enabled: !!indexNames?.length, + staleTime: Infinity, + refetchOnWindowFocus: false, + ...options, + }); + + useEffect(() => { + // Cleanup the data view instance cache on unmount + if (query.data) { + return () => { + dataViewsService.clearInstanceCache(query.data.id); + }; + } + }, [dataViewsService, query.data]); + + return query; +}; diff --git a/packages/kbn-alerts-ui-shared/tsconfig.json b/packages/kbn-alerts-ui-shared/tsconfig.json index 653f26692bab1..79fc9d6fc9bdb 100644 --- a/packages/kbn-alerts-ui-shared/tsconfig.json +++ b/packages/kbn-alerts-ui-shared/tsconfig.json @@ -47,5 +47,7 @@ "@kbn/alerts-as-data-utils", "@kbn/test-jest-helpers", "@kbn/core-ui-settings-browser", + "@kbn/core-http-browser-mocks", + "@kbn/core-notifications-browser-mocks", ] } diff --git a/packages/kbn-apm-types/es_fields.ts b/packages/kbn-apm-types/es_fields.ts new file mode 100644 index 0000000000000..00e78c5196f7d --- /dev/null +++ b/packages/kbn-apm-types/es_fields.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/es_fields/apm'; diff --git a/packages/kbn-apm-types/es_schemas_raw.ts b/packages/kbn-apm-types/es_schemas_raw.ts new file mode 100644 index 0000000000000..ef0d4cbeb5897 --- /dev/null +++ b/packages/kbn-apm-types/es_schemas_raw.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/es_schemas/raw'; +export * from './src/es_schemas/raw/fields'; diff --git a/packages/kbn-apm-types/es_schemas_ui.ts b/packages/kbn-apm-types/es_schemas_ui.ts new file mode 100644 index 0000000000000..e974312a737ef --- /dev/null +++ b/packages/kbn-apm-types/es_schemas_ui.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 * from './src/es_schemas/ui'; +export * from './src/es_schemas/ui/fields'; diff --git a/packages/kbn-apm-types/index.ts b/packages/kbn-apm-types/index.ts new file mode 100644 index 0000000000000..8f15cbd897e28 --- /dev/null +++ b/packages/kbn-apm-types/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 * from './es_fields'; +export * from './es_schemas_raw'; +export * from './es_schemas_ui'; diff --git a/packages/kbn-apm-types/kibana.jsonc b/packages/kbn-apm-types/kibana.jsonc new file mode 100644 index 0000000000000..26b4ec0b1cf75 --- /dev/null +++ b/packages/kbn-apm-types/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/apm-types", + "owner": "@elastic/obs-ux-infra_services-team" +} diff --git a/packages/kbn-apm-types/package.json b/packages/kbn-apm-types/package.json new file mode 100644 index 0000000000000..3b15b82701d81 --- /dev/null +++ b/packages/kbn-apm-types/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/apm-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-apm-types/src/es_fields/apm.ts b/packages/kbn-apm-types/src/es_fields/apm.ts new file mode 100644 index 0000000000000..d17a1ed78db90 --- /dev/null +++ b/packages/kbn-apm-types/src/es_fields/apm.ts @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may 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 TIMESTAMP = 'timestamp.us'; +export const AGENT = 'agent'; +export const AGENT_NAME = 'agent.name'; +export const AGENT_VERSION = 'agent.version'; +export const AGENT_ACTIVATION_METHOD = 'agent.activation_method'; + +export const DESTINATION_ADDRESS = 'destination.address'; + +export const CLOUD = 'cloud'; +export const CLOUD_AVAILABILITY_ZONE = 'cloud.availability_zone'; +export const CLOUD_PROVIDER = 'cloud.provider'; +export const CLOUD_REGION = 'cloud.region'; +export const CLOUD_MACHINE_TYPE = 'cloud.machine.type'; +export const CLOUD_ACCOUNT_ID = 'cloud.account.id'; +export const CLOUD_INSTANCE_ID = 'cloud.instance.id'; +export const CLOUD_INSTANCE_NAME = 'cloud.instance.name'; +export const CLOUD_SERVICE_NAME = 'cloud.service.name'; + +export const EVENT_SUCCESS_COUNT = 'event.success_count'; + +export const SERVICE = 'service'; +export const SERVICE_NAME = 'service.name'; +export const SERVICE_ENVIRONMENT = 'service.environment'; +export const SERVICE_FRAMEWORK_NAME = 'service.framework.name'; +export const SERVICE_FRAMEWORK_VERSION = 'service.framework.version'; +export const SERVICE_LANGUAGE_NAME = 'service.language.name'; +export const SERVICE_LANGUAGE_VERSION = 'service.language.version'; +export const SERVICE_RUNTIME_NAME = 'service.runtime.name'; +export const SERVICE_RUNTIME_VERSION = 'service.runtime.version'; +export const SERVICE_NODE_NAME = 'service.node.name'; +export const SERVICE_VERSION = 'service.version'; +export const SERVICE_TARGET_TYPE = 'service.target.type'; +export const SERVICE_OVERFLOW_COUNT = 'service_transaction.aggregation.overflow_count'; + +export const URL_FULL = 'url.full'; +export const HTTP_REQUEST_METHOD = 'http.request.method'; +export const HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code'; +export const USER_ID = 'user.id'; +export const USER_AGENT_ORIGINAL = 'user_agent.original'; +export const USER_AGENT_NAME = 'user_agent.name'; + +export const OBSERVER_HOSTNAME = 'observer.hostname'; +export const OBSERVER_LISTENING = 'observer.listening'; +export const PROCESSOR_EVENT = 'processor.event'; + +export const TRANSACTION_DURATION = 'transaction.duration.us'; +export const TRANSACTION_DURATION_HISTOGRAM = 'transaction.duration.histogram'; +export const TRANSACTION_DURATION_SUMMARY = 'transaction.duration.summary'; +export const TRANSACTION_TYPE = 'transaction.type'; +export const TRANSACTION_RESULT = 'transaction.result'; +export const TRANSACTION_NAME = 'transaction.name'; +export const TRANSACTION_ID = 'transaction.id'; +export const TRANSACTION_SAMPLED = 'transaction.sampled'; +export const TRANSACTION_PAGE_URL = 'transaction.page.url'; +export const TRANSACTION_FAILURE_COUNT = 'transaction.failure_count'; +export const TRANSACTION_SUCCESS_COUNT = 'transaction.success_count'; +export const TRANSACTION_OVERFLOW_COUNT = 'transaction.aggregation.overflow_count'; +// for transaction metrics +export const TRANSACTION_ROOT = 'transaction.root'; +export const TRANSACTION_PROFILER_STACK_TRACE_IDS = 'transaction.profiler_stack_trace_ids'; + +export const EVENT_OUTCOME = 'event.outcome'; + +export const TRACE_ID = 'trace.id'; + +export const SPAN_DURATION = 'span.duration.us'; +export const SPAN_TYPE = 'span.type'; +export const SPAN_SUBTYPE = 'span.subtype'; +export const SPAN_SELF_TIME_SUM = 'span.self_time.sum.us'; +export const SPAN_ACTION = 'span.action'; +export const SPAN_NAME = 'span.name'; +export const SPAN_ID = 'span.id'; +export const SPAN_DESTINATION_SERVICE_RESOURCE = 'span.destination.service.resource'; +export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT = + 'span.destination.service.response_time.count'; + +export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM = + 'span.destination.service.response_time.sum.us'; + +export const SPAN_LINKS = 'span.links'; +export const SPAN_LINKS_TRACE_ID = 'span.links.trace.id'; +export const SPAN_LINKS_SPAN_ID = 'span.links.span.id'; + +export const SPAN_COMPOSITE_COUNT = 'span.composite.count'; +export const SPAN_COMPOSITE_SUM = 'span.composite.sum.us'; +export const SPAN_COMPOSITE_COMPRESSION_STRATEGY = 'span.composite.compression_strategy'; + +export const SPAN_SYNC = 'span.sync'; + +// Parent ID for a transaction or span +export const PARENT_ID = 'parent.id'; + +export const ERROR_ID = 'error.id'; +export const ERROR_GROUP_ID = 'error.grouping_key'; +export const ERROR_GROUP_NAME = 'error.grouping_name'; +export const ERROR_CULPRIT = 'error.culprit'; +export const ERROR_LOG_LEVEL = 'error.log.level'; +export const ERROR_LOG_MESSAGE = 'error.log.message'; +export const ERROR_EXCEPTION = 'error.exception'; +export const ERROR_EXC_MESSAGE = 'error.exception.message'; // only to be used in es queries, since error.exception is now an array +export const ERROR_EXC_HANDLED = 'error.exception.handled'; // only to be used in es queries, since error.exception is now an array +export const ERROR_EXC_TYPE = 'error.exception.type'; +export const ERROR_PAGE_URL = 'error.page.url'; +export const ERROR_TYPE = 'error.type'; + +// METRICS +export const METRIC_SYSTEM_FREE_MEMORY = 'system.memory.actual.free'; +export const METRIC_SYSTEM_TOTAL_MEMORY = 'system.memory.total'; +export const METRIC_SYSTEM_CPU_PERCENT = 'system.cpu.total.norm.pct'; +export const METRIC_PROCESS_CPU_PERCENT = 'system.process.cpu.total.norm.pct'; +export const METRIC_CGROUP_MEMORY_LIMIT_BYTES = 'system.process.cgroup.memory.mem.limit.bytes'; +export const METRIC_CGROUP_MEMORY_USAGE_BYTES = 'system.process.cgroup.memory.mem.usage.bytes'; + +export const METRIC_JAVA_HEAP_MEMORY_MAX = 'jvm.memory.heap.max'; +export const METRIC_JAVA_HEAP_MEMORY_COMMITTED = 'jvm.memory.heap.committed'; +export const METRIC_JAVA_HEAP_MEMORY_USED = 'jvm.memory.heap.used'; +export const METRIC_JAVA_NON_HEAP_MEMORY_MAX = 'jvm.memory.non_heap.max'; +export const METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED = 'jvm.memory.non_heap.committed'; +export const METRIC_JAVA_NON_HEAP_MEMORY_USED = 'jvm.memory.non_heap.used'; +export const METRIC_JAVA_THREAD_COUNT = 'jvm.thread.count'; +export const METRIC_JAVA_GC_COUNT = 'jvm.gc.count'; +export const METRIC_JAVA_GC_TIME = 'jvm.gc.time'; + +export const METRICSET_NAME = 'metricset.name'; +export const METRICSET_INTERVAL = 'metricset.interval'; + +export const LABEL_NAME = 'labels.name'; +export const LABEL_GC = 'labels.gc'; +export const LABEL_TYPE = 'labels.type'; +export const LABEL_TELEMETRY_AUTO_VERSION = 'labels.telemetry_auto_version'; +export const LABEL_LIFECYCLE_STATE = 'labels.lifecycle_state'; + +export const HOST = 'host'; +export const HOST_HOSTNAME = 'host.hostname'; // Do not use. Please use `HOST_NAME` instead. +export const HOST_NAME = 'host.name'; +export const HOST_OS_PLATFORM = 'host.os.platform'; +export const HOST_ARCHITECTURE = 'host.architecture'; +export const HOST_OS_VERSION = 'host.os.version'; + +export const CONTAINER_ID = 'container.id'; +export const CONTAINER = 'container'; +export const CONTAINER_IMAGE = 'container.image.name'; + +export const KUBERNETES = 'kubernetes'; +export const KUBERNETES_POD_NAME = 'kubernetes.pod.name'; +export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; + +export const FAAS_ID = 'faas.id'; +export const FAAS_NAME = 'faas.name'; +export const FAAS_COLDSTART = 'faas.coldstart'; +export const FAAS_TRIGGER_TYPE = 'faas.trigger.type'; +export const FAAS_DURATION = 'faas.duration'; +export const FAAS_COLDSTART_DURATION = 'faas.coldstart_duration'; +export const FAAS_BILLED_DURATION = 'faas.billed_duration'; + +// OpenTelemetry Metrics +export const METRIC_OTEL_SYSTEM_CPU_UTILIZATION = 'system.cpu.utilization'; +export const METRIC_OTEL_SYSTEM_MEMORY_UTILIZATION = 'system.memory.utilization'; + +export const METRIC_OTEL_JVM_PROCESS_CPU_PERCENT = 'process.runtime.jvm.cpu.utilization'; +export const METRIC_OTEL_JVM_PROCESS_MEMORY_USAGE = 'process.runtime.jvm.memory.usage'; +export const METRIC_OTEL_JVM_PROCESS_MEMORY_COMMITTED = 'process.runtime.jvm.memory.committed'; +export const METRIC_OTEL_JVM_PROCESS_MEMORY_LIMIT = 'process.runtime.jvm.memory.limit'; +export const METRIC_OTEL_JVM_PROCESS_THREADS_COUNT = 'process.runtime.jvm.threads.count'; +export const METRIC_OTEL_JVM_SYSTEM_CPU_PERCENT = 'process.runtime.jvm.system.cpu.utilization'; +export const METRIC_OTEL_JVM_GC_DURATION = 'process.runtime.jvm.gc.duration'; +export const VALUE_OTEL_JVM_PROCESS_MEMORY_HEAP = 'heap'; +export const VALUE_OTEL_JVM_PROCESS_MEMORY_NON_HEAP = 'non_heap'; + +// Metadata +export const TIER = '_tier'; +export const INDEX = '_index'; +export const DATA_STEAM_TYPE = 'data_stream.type'; + +// Mobile +export const NETWORK_CONNECTION_TYPE = 'network.connection.type'; +export const DEVICE_MODEL_IDENTIFIER = 'device.model.identifier'; +export const SESSION_ID = 'session.id'; +export const APP_LAUNCH_TIME = 'application.launch.time'; +export const EVENT_NAME = 'event.name'; + +// Location +export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code'; +export const CLIENT_GEO_REGION_ISO_CODE = 'client.geo.region_iso_code'; +export const CLIENT_GEO_COUNTRY_NAME = 'client.geo.country_name'; +export const CLIENT_GEO_CITY_NAME = 'client.geo.city_name'; +export const CLIENT_GEO_REGION_NAME = 'client.geo.region_name'; + +export const CHILD_ID = 'child.id'; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts b/packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.ts new file mode 100644 index 0000000000000..a750c39c775d2 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/apm_base_doc.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. + */ + +import { Observer } from './fields/observer'; + +// all documents types extend APMBaseDoc and inherit all properties +export interface APMBaseDoc { + '@timestamp': string; + agent: { + name: string; + version: string; + }; + parent?: { id: string }; // parent ID is not available on root transactions + trace?: { id: string }; + labels?: { + [key: string]: string | number | boolean; + }; + observer?: Observer; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts b/packages/kbn-apm-types/src/es_schemas/raw/error_raw.ts new file mode 100644 index 0000000000000..f0157a6a08376 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/error_raw.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 { APMBaseDoc } from './apm_base_doc'; +import { + Container, + Host, + Http, + Kubernetes, + Page, + Process, + Service, + Stackframe, + TimestampUs, + Url, + User, +} from './fields'; + +export interface Processor { + name: 'error'; + event: 'error'; +} + +export interface Exception { + attributes?: { + response?: string; + }; + code?: string; + message?: string; // either message or type are given + type?: string; + module?: string; + handled?: boolean; + stacktrace?: Stackframe[]; +} + +export interface Log { + message: string; + stacktrace?: Stackframe[]; +} + +export interface ErrorRaw extends APMBaseDoc { + processor: Processor; + timestamp: TimestampUs; + transaction?: { + id: string; + sampled?: boolean; + type: string; + }; + error: { + id: string; + culprit?: string; + grouping_key: string; + // either exception or log are given + exception?: Exception[]; + page?: Page; // special property for RUM: shared by error and transaction + log?: Log; + stack_trace?: string; + custom?: Record<string, unknown>; + }; + + // Shared by errors and transactions + container?: Container; + host?: Host; + http?: Http; + kubernetes?: Kubernetes; + process?: Process; + service: Service; + url?: Url; + user?: User; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/event_raw.ts b/packages/kbn-apm-types/src/es_schemas/raw/event_raw.ts new file mode 100644 index 0000000000000..f8d3124914239 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/event_raw.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 { APMBaseDoc } from './apm_base_doc'; +import { TimestampUs } from './fields/timestamp_us'; + +export interface EventRaw extends APMBaseDoc { + timestamp: TimestampUs; + transaction?: { + id: string; + sampled?: boolean; + type: string; + }; + log: { + message?: string; + }; + event: { + action: string; + category: string; + }; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.ts new file mode 100644 index 0000000000000..eaad379f5069b --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/cloud.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 and the Server Side Public License, v 1; you may 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 Cloud { + availability_zone?: string; + instance?: { + name: string; + id: string; + }; + machine?: { + type: string; + }; + project?: { + id: string; + name: string; + }; + provider?: string; + region?: string; + account?: { + id: string; + name: string; + }; + image?: { + id: string; + }; + service?: { + name: string; + }; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/container.ts new file mode 100644 index 0000000000000..ae6526ad9ff92 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/container.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 interface Container { + id?: string | null; + image?: string | null; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/event_outcome.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/event_outcome.ts new file mode 100644 index 0000000000000..53c19ab293d9b --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/event_outcome.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 type EventOutcome = 'success' | 'failure' | 'unknown'; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/faas.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/faas.ts new file mode 100644 index 0000000000000..9054839b82902 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/faas.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 interface Faas { + id: string; + coldstart?: boolean; + execution?: string; + trigger?: { + type?: string; + request_id?: string; + }; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/host.ts new file mode 100644 index 0000000000000..7a12a8eba824f --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/host.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 interface Host { + architecture?: string; + hostname?: string; + name?: string; + ip?: string; + os?: { + platform?: string; + }; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/http.ts new file mode 100644 index 0000000000000..3e16e5dceb80c --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/http.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 Http { + request?: { method: string; [key: string]: unknown }; + response?: { status_code: number; [key: string]: unknown }; + version?: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/index.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/index.ts new file mode 100644 index 0000000000000..aa8355934fbb4 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/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 and the Server Side Public License, v 1; you may 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 './cloud'; +export * from './container'; +export * from './event_outcome'; +export * from './faas'; +export * from './host'; +export * from './http'; +export * from './kubernetes'; +export * from './observer'; +export * from './page'; +export * from './process'; +export * from './service'; +export * from './span_links'; +export * from './stackframe'; +export * from './timestamp_us'; +export * from './url'; +export * from './user_agent'; +export * from './user'; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.ts new file mode 100644 index 0000000000000..09667f08d441c --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/kubernetes.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 Kubernetes { + pod?: { uid?: string | null; [key: string]: unknown }; + namespace?: string; + replicaset?: { + name?: string; + }; + deployment?: { + name?: string; + }; + container?: { + id?: string; + name?: string; + }; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/observer.ts new file mode 100644 index 0000000000000..b035c0210bb35 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/observer.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 interface Observer { + ephemeral_id?: string; + hostname?: string; + id?: string; + name?: string; + type?: string; + version: string; + version_major: number; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/page.ts new file mode 100644 index 0000000000000..1c2548cb777cd --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/page.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. + */ + +// only for RUM agent: shared by error and transaction +export interface Page { + url: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/process.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/process.ts new file mode 100644 index 0000000000000..25db0098f8d3d --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/process.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. + */ + +export interface Process { + args?: string[]; + pid: number; + ppid?: number; + title?: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/service.ts new file mode 100644 index 0000000000000..ff01bfc8517e8 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/service.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. + */ + +export interface Service { + name: string; + environment?: string; + framework?: { + name: string; + version?: string; + }; + node?: { + name?: string; + }; + runtime?: { + name: string; + version: string; + }; + language?: { + name: string; + version?: string; + }; + version?: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/span_links.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/span_links.ts new file mode 100644 index 0000000000000..13ffc4d7075f1 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/span_links.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 interface SpanLink { + trace: { id: string }; + span: { id: string }; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/stackframe.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/stackframe.ts new file mode 100644 index 0000000000000..b2b1cf8000103 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/stackframe.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. + */ + +interface Line { + column?: number; + number: number; +} + +interface Sourcemap { + error?: string; + updated?: boolean; +} + +interface StackframeBase { + abs_path?: string; + classname?: string; + context?: { + post?: string[]; + pre?: string[]; + }; + exclude_from_grouping?: boolean; + filename?: string; + function?: string; + module?: string; + library_frame?: boolean; + line?: Line; + sourcemap?: Sourcemap; + vars?: { + [key: string]: unknown; + }; +} + +export type StackframeWithLineContext = StackframeBase & { + line: Line & { + context: string; + }; +}; + +export type Stackframe = StackframeBase | StackframeWithLineContext; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/timestamp_us.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/timestamp_us.ts new file mode 100644 index 0000000000000..17c06c8e38156 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/timestamp_us.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 interface TimestampUs { + us: number; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/url.ts new file mode 100644 index 0000000000000..4fa149e6b65d6 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/url.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 Url { + domain?: string; + full: string; + original?: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/user.ts new file mode 100644 index 0000000000000..ced460ececd17 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/user.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 interface User { + id: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts b/packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.ts new file mode 100644 index 0000000000000..0658d408dbe54 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/fields/user_agent.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. + */ + +export interface UserAgent { + device?: { + name: string; + }; + name?: string; + original: string; + os?: { + name: string; + version?: string; + full?: string; + }; + version?: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/index.ts b/packages/kbn-apm-types/src/es_schemas/raw/index.ts new file mode 100644 index 0000000000000..addd3279f2586 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/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. + */ + +export * from './apm_base_doc'; +export * from './error_raw'; +export * from './event_raw'; +export * from './metric_raw'; +export * from './span_raw'; +export * from './transaction_raw'; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/metric_raw.ts b/packages/kbn-apm-types/src/es_schemas/raw/metric_raw.ts new file mode 100644 index 0000000000000..29a8dc921f3d9 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/metric_raw.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { APMBaseDoc } from './apm_base_doc'; +import { Cloud } from './fields/cloud'; +import { Container } from './fields/container'; +import { Host } from './fields/host'; +import { Kubernetes } from './fields/kubernetes'; +import { Service } from './fields/service'; + +type BaseMetric = APMBaseDoc & { + processor: { + name: 'metric'; + event: 'metric'; + }; + cloud?: Cloud; + container?: Container; + kubernetes?: Kubernetes; + service?: Service; + host?: Host; +}; + +type BaseBreakdownMetric = BaseMetric & { + transaction: { + name: string; + type: string; + }; + span: { + self_time: { + count: number; + sum: { + us: number; + }; + }; + }; +}; + +type TransactionBreakdownMetric = BaseBreakdownMetric & { + transaction: { + duration: { + count: number; + sum: { + us: number; + }; + }; + breakdown: { + count: number; + }; + }; +}; + +type SpanBreakdownMetric = BaseBreakdownMetric & { + span: { + type: string; + subtype?: string; + }; +}; + +type SystemMetric = BaseMetric & { + system: unknown; + service: { + node?: { + name: string; + }; + }; +}; + +type CGroupMetric = SystemMetric; +type JVMMetric = SystemMetric & { + jvm: unknown; +}; + +type TransactionDurationMetric = BaseMetric & { + transaction: { + name: string; + type: string; + result?: string; + duration: { + histogram: { + values: number[]; + counts: number[]; + }; + }; + }; + service: { + name: string; + node?: { + name: string; + }; + environment?: string; + version?: string; + }; +}; + +export type SpanDestinationMetric = BaseMetric & { + span: { + destination: { + service: { + resource: string; + response_time: { + count: number; + sum: { + us: number; + }; + }; + }; + }; + }; +}; + +export type MetricRaw = + | BaseMetric + | TransactionBreakdownMetric + | SpanBreakdownMetric + | TransactionDurationMetric + | SpanDestinationMetric + | SystemMetric + | CGroupMetric + | JVMMetric; diff --git a/packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts b/packages/kbn-apm-types/src/es_schemas/raw/span_raw.ts new file mode 100644 index 0000000000000..0d45d3bb00cc4 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/span_raw.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 { APMBaseDoc } from './apm_base_doc'; +import { EventOutcome } from './fields/event_outcome'; +import { Http } from './fields/http'; +import { SpanLink } from './fields/span_links'; +import { Stackframe } from './fields/stackframe'; +import { TimestampUs } from './fields/timestamp_us'; +import { Url } from './fields/url'; + +interface Processor { + name: 'transaction'; + event: 'span'; +} + +export interface SpanRaw extends APMBaseDoc { + processor: Processor; + trace: { id: string }; // trace is required + event?: { outcome?: EventOutcome }; + service: { + name: string; + environment?: string; + }; + span: { + destination?: { + service: { + resource: string; + }; + }; + action?: string; + duration: { us: number }; + id: string; + name: string; + stacktrace?: Stackframe[]; + subtype?: string; + sync?: boolean; + type: string; + http?: { + url?: { + original?: string; + }; + response: { + status_code: number; + }; + method?: string; + }; + db?: { + statement?: string; + type?: string; + }; + message?: { + queue?: { name: string }; + age?: { ms: number }; + body?: string; + headers?: Record<string, unknown>; + }; + composite?: { + count: number; + sum: { us: number }; + compression_strategy: string; + }; + links?: SpanLink[]; + }; + timestamp: TimestampUs; + transaction?: { + id: string; + }; + child?: { id: string[] }; + code?: { + stacktrace?: string; + }; + http?: Http; + url?: Url; +} diff --git a/packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts b/packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.ts new file mode 100644 index 0000000000000..6505e2808f795 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/raw/transaction_raw.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 { APMBaseDoc } from './apm_base_doc'; +import { Cloud } from './fields/cloud'; +import { Container } from './fields/container'; +import { EventOutcome } from './fields/event_outcome'; +import { Host } from './fields/host'; +import { Http } from './fields/http'; +import { Kubernetes } from './fields/kubernetes'; +import { Page } from './fields/page'; +import { Process } from './fields/process'; +import { Service } from './fields/service'; +import { TimestampUs } from './fields/timestamp_us'; +import { Url } from './fields/url'; +import { User } from './fields/user'; +import { UserAgent } from './fields/user_agent'; +import { Faas } from './fields/faas'; +import { SpanLink } from './fields/span_links'; + +interface Processor { + name: 'transaction'; + event: 'transaction'; +} + +export interface TransactionRaw extends APMBaseDoc { + processor: Processor; + timestamp: TimestampUs; + trace: { id: string }; // trace is required + event?: { outcome?: EventOutcome }; + transaction: { + duration: { us: number }; + id: string; + marks?: { + // "agent": not defined by APM Server - only sent by RUM agent + agent?: { + [name: string]: number; + }; + }; + name?: string; + page?: Page; // special property for RUM: shared by error and transaction + result?: string; + sampled: boolean; + span_count?: { + started?: number; + dropped?: number; + }; + type: string; + custom?: Record<string, unknown>; + message?: { + queue?: { name: string }; + age?: { ms: number }; + body?: string; + headers?: Record<string, unknown>; + }; + }; + + // Shared by errors and transactions + container?: Container; + ecs?: { version?: string }; + host?: Host; + http?: Http; + kubernetes?: Kubernetes; + process?: Process; + service: Service; + url?: Url; + user?: User; + user_agent?: UserAgent; + cloud?: Cloud; + faas?: Faas; + span?: { + links?: SpanLink[]; + }; +} diff --git a/packages/kbn-apm-types/src/es_schemas/ui/apm_error.ts b/packages/kbn-apm-types/src/es_schemas/ui/apm_error.ts new file mode 100644 index 0000000000000..fad4190a229ef --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/apm_error.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 { ErrorRaw } from '../raw/error_raw'; +import { Agent } from './fields/agent'; + +export interface APMError extends ErrorRaw { + agent: Agent; +} diff --git a/packages/kbn-apm-types/src/es_schemas/ui/event.ts b/packages/kbn-apm-types/src/es_schemas/ui/event.ts new file mode 100644 index 0000000000000..8d0fd78f5f0f9 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/event.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 { EventRaw } from '../raw/event_raw'; +import { Agent } from './fields/agent'; + +export interface Event extends EventRaw { + agent: Agent; +} diff --git a/packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts b/packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts new file mode 100644 index 0000000000000..85cb7340fdda6 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/fields/agent.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { AgentName } from '@kbn/elastic-agent-utils'; + +export type { ElasticAgentName, OpenTelemetryAgentName, AgentName } from '@kbn/elastic-agent-utils'; + +export interface Agent { + ephemeral_id?: string; + name: AgentName; + version: string; +} diff --git a/packages/kbn-apm-types/src/es_schemas/ui/fields/index.ts b/packages/kbn-apm-types/src/es_schemas/ui/fields/index.ts new file mode 100644 index 0000000000000..1e64e14e07f8a --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/fields/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 and the Server Side Public License, v 1; you may 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 './agent'; diff --git a/packages/kbn-apm-types/src/es_schemas/ui/index.ts b/packages/kbn-apm-types/src/es_schemas/ui/index.ts new file mode 100644 index 0000000000000..26f716289aaff --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/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. + */ + +export * from './apm_error'; +export * from './event'; +export * from './metric'; +export * from './span'; +export * from './transaction'; diff --git a/packages/kbn-apm-types/src/es_schemas/ui/metric.ts b/packages/kbn-apm-types/src/es_schemas/ui/metric.ts new file mode 100644 index 0000000000000..bd9391e269554 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/metric.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 { MetricRaw } from '../raw/metric_raw'; + +export type Metric = MetricRaw; diff --git a/packages/kbn-apm-types/src/es_schemas/ui/span.ts b/packages/kbn-apm-types/src/es_schemas/ui/span.ts new file mode 100644 index 0000000000000..5e09b84b87df2 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/span.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 { SpanRaw } from '../raw/span_raw'; +import { Agent } from './fields/agent'; + +export interface Span extends SpanRaw { + agent: Agent; +} diff --git a/packages/kbn-apm-types/src/es_schemas/ui/transaction.ts b/packages/kbn-apm-types/src/es_schemas/ui/transaction.ts new file mode 100644 index 0000000000000..ea5ccf5fd6434 --- /dev/null +++ b/packages/kbn-apm-types/src/es_schemas/ui/transaction.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 { TransactionRaw } from '../raw/transaction_raw'; +import { Agent } from './fields/agent'; + +// Make `transaction.name` required instead of optional. +// `transaction.name` can be missing in Elasticsearch but the UI will only aggregate on transactions with a name, +// and thus it doesn't make sense to treat it as optional +type InnerTransaction = TransactionRaw['transaction']; +interface InnerTransactionWithName extends InnerTransaction { + name: string; +} + +export interface Transaction extends TransactionRaw { + agent: Agent; + transaction: InnerTransactionWithName; +} diff --git a/packages/kbn-apm-types/tsconfig.json b/packages/kbn-apm-types/tsconfig.json new file mode 100644 index 0000000000000..312ebd695e48e --- /dev/null +++ b/packages/kbn-apm-types/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "node" + ] + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/elastic-agent-utils", + ] +} diff --git a/packages/kbn-avc-banner/src/index.tsx b/packages/kbn-avc-banner/src/index.tsx index 54ded0bfdd49d..2f71b5ddd9679 100644 --- a/packages/kbn-avc-banner/src/index.tsx +++ b/packages/kbn-avc-banner/src/index.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiCallOut, EuiSpacer, useEuiTheme } from '@elastic/eui'; @@ -14,6 +14,13 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import avcBannerBackground from './avc_banner_background.svg'; +// Logic to hide banner at EOY 2024 +export const useIsStillYear2024: () => boolean = () => { + return useMemo(() => { + return new Date().getFullYear() === 2024; + }, []); +}; + export const AVCResultsBanner2024: React.FC<{ onDismiss: () => void }> = ({ onDismiss }) => { const { docLinks } = useKibana().services; const { euiTheme } = useEuiTheme(); diff --git a/packages/kbn-capture-oas-snapshot-cli/src/kibana_worker.ts b/packages/kbn-capture-oas-snapshot-cli/src/kibana_worker.ts index a0fcef35c491c..e65e6bf98bce9 100644 --- a/packages/kbn-capture-oas-snapshot-cli/src/kibana_worker.ts +++ b/packages/kbn-capture-oas-snapshot-cli/src/kibana_worker.ts @@ -54,6 +54,7 @@ export type Result = 'ready'; if (serverless) { // Satisfy spaces config for serverless: set(settings, 'xpack.spaces.allowFeatureVisibility', false); + set(settings, 'xpack.spaces.allowSolutionVisibility', false); const { startKibana } = createTestServerlessInstances({ kibana: { settings, cliArgs }, }); diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index 4cec00e68dc42..d891c7ae5d095 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -284,6 +284,7 @@ "title", "version" ], + "dynamic-config-overrides": [], "endpoint:unified-user-artifact-manifest": [ "artifactIds", "policyId", diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 9242f775f7094..8bec33bcda085 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -975,6 +975,10 @@ } } }, + "dynamic-config-overrides": { + "dynamic": false, + "properties": {} + }, "endpoint:unified-user-artifact-manifest": { "dynamic": false, "properties": { diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index 67a8aa0c3d75d..e6a5d3dc331a5 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -726,4 +726,19 @@ describe('Dynamic Overrides', () => { await firstValueFrom(configService.getConfig$().pipe(map((cfg) => cfg.toRaw()))) ).toStrictEqual({ namespace1: { key: 'another-value' } }); }); + + test('is able to remove a field when setting it to `null`', async () => { + configService.addDynamicConfigPaths('namespace1', ['key']); + configService.setDynamicConfigOverrides({ 'namespace1.key': 'another-value' }); + + expect( + await firstValueFrom(configService.getConfig$().pipe(map((cfg) => cfg.toRaw()))) + ).toStrictEqual({ namespace1: { key: 'another-value' } }); + + configService.setDynamicConfigOverrides({ 'namespace1.key': null }); + + expect( + await firstValueFrom(configService.getConfig$().pipe(map((cfg) => cfg.toRaw()))) + ).toStrictEqual({ namespace1: {} }); + }); }); diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index 51a83f82664e2..7aba4861c7423 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -8,7 +8,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { SchemaTypeError, Type, ValidationError } from '@kbn/config-schema'; -import { cloneDeep, isEqual, merge } from 'lodash'; +import { cloneDeep, isEqual, merge, unset } from 'lodash'; import { set } from '@kbn/safer-lodash-set'; import { BehaviorSubject, combineLatest, firstValueFrom, Observable, identity } from 'rxjs'; import { distinctUntilChanged, first, map, shareReplay, tap } from 'rxjs'; @@ -64,7 +64,10 @@ export class ConfigService { private readonly schemas = new Map<string, Type<unknown>>(); private readonly deprecations = new BehaviorSubject<ConfigDeprecationWithContext[]>([]); private readonly dynamicPaths = new Map<string, string[]>(); - private readonly overrides$ = new BehaviorSubject<Record<string, unknown>>({}); + private readonly overrides$ = new BehaviorSubject<{ + additions: Record<string, unknown>; + removals: string[]; + }>({ additions: {}, removals: [] }); private readonly handledDeprecatedConfigs = new Map<string, DeprecatedConfigDetails[]>(); constructor( @@ -85,7 +88,8 @@ export class ConfigService { this.overrides$, ]).pipe( map(([rawConfig, deprecations, overrides]) => { - const overridden = merge(rawConfig, overrides); + const overridden = merge(rawConfig, overrides.additions); + overrides.removals.forEach((key) => unset(overridden, key)); const migrated = applyDeprecations(overridden, deprecations); this.deprecatedConfigPaths.next(migrated.changedPaths); return new ObjectToConfigAdapter(migrated.config); @@ -254,15 +258,23 @@ export class ConfigService { * @param newOverrides */ public setDynamicConfigOverrides(newOverrides: Record<string, unknown>) { - const globalOverrides = cloneDeep(this.overrides$.value); + const globalOverrides = cloneDeep(this.overrides$.value.additions); const flattenedOverrides = getFlattenedObject(newOverrides); const validateWithNamespace = new Set<string>(); + const flattenedKeysToRemove: string[] = []; // We don't want to remove keys until all the validations have been applied. + keyLoop: for (const key in flattenedOverrides) { // this if is enforced by an eslint rule :shrug: if (key in flattenedOverrides) { + // If set to `null`, delete the config from the overrides. + if (flattenedOverrides[key] === null) { + flattenedKeysToRemove.push(key); + continue; + } + for (const [configPath, dynamicConfigKeys] of this.dynamicPaths.entries()) { if ( key.startsWith(`${configPath}.`) && @@ -282,13 +294,17 @@ export class ConfigService { } } - const globalOverridesAsConfig = new ObjectToConfigAdapter( - merge({}, this.lastConfig, globalOverrides) - ); + const rawConfig = merge({}, this.lastConfig, globalOverrides); + flattenedKeysToRemove.forEach((key) => { + unset(globalOverrides, key); + unset(rawConfig, key); + }); + const globalOverridesAsConfig = new ObjectToConfigAdapter(rawConfig); validateWithNamespace.forEach((ns) => this.validateAtPath(ns, globalOverridesAsConfig.get(ns))); - this.overrides$.next(globalOverrides); + this.overrides$.next({ additions: globalOverrides, removals: flattenedKeysToRemove }); + return globalOverrides; } private async logDeprecation() { diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index fef52545f8d2f..dec15cf568e98 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -466,7 +466,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D }, securitySolution: { artifactControl: `${SECURITY_SOLUTION_DOCS}artifact-control.html`, - avcResults: `${ELASTIC_WEBSITE_URL}blog/elastic-security-malware-protection-test-av-comparatives`, + avcResults: `${ELASTIC_WEBSITE_URL}blog/elastic-av-comparatives-business-security-test`, trustedApps: `${SECURITY_SOLUTION_DOCS}trusted-apps-ov.html`, eventFilters: `${SECURITY_SOLUTION_DOCS}event-filters.html`, blocklist: `${SECURITY_SOLUTION_DOCS}blocklist.html`, @@ -496,6 +496,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D assetCriticality: `${SECURITY_SOLUTION_DOCS}asset-criticality.html`, }, detectionEngineOverview: `${SECURITY_SOLUTION_DOCS}detection-engine-overview.html`, + aiAssistant: `${SECURITY_SOLUTION_DOCS}security-assistant.html`, }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, @@ -618,6 +619,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D sloBurnRateRule: isServerless ? `${SERVERLESS_OBSERVABILITY_DOCS}create-slo-burn-rate-alert-rule` : `${OBSERVABILITY_DOCS}slo-burn-rate-alert.html`, + aiAssistant: `${OBSERVABILITY_DOCS}obs-ai-assistant.html`, }, alerting: { guide: isServerless diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index d271ed918327a..b92c59a624880 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -336,6 +336,7 @@ export interface DocLinks { readonly configureAlertSuppression: string; }; readonly securitySolution: { + readonly aiAssistant: string; readonly artifactControl: string; readonly avcResults: string; readonly trustedApps: string; @@ -441,6 +442,7 @@ export interface DocLinks { syntheticsProjectMonitors: string; syntheticsMigrateFromIntegration: string; sloBurnRateRule: string; + aiAssistant: string; }>; readonly alerting: Readonly<{ guide: string; diff --git a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts index de4271a474961..a1021b9aa9dcd 100644 --- a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts +++ b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { createAnalytics, type AnalyticsClient } from '@kbn/ebt/client'; +import { createAnalytics, type AnalyticsClient } from '@elastic/ebt/client'; import { loggerMock } from '@kbn/logging-mocks'; import { registerPerformanceMetricEventType, reportPerformanceMetricEvent } from './helpers'; import { METRIC_EVENT_SCHEMA } from './schema'; @@ -18,7 +18,6 @@ describe('performance metric event helpers', () => { beforeEach(() => { analyticsClient = createAnalytics({ isDev: true, // Explicitly setting `true` to ensure we have event validation to make sure the events sent pass our validation. - sendTo: 'staging', logger: loggerMock.create(), }); }); @@ -39,7 +38,6 @@ describe('performance metric event helpers', () => { beforeEach(() => { analyticsClient = createAnalytics({ isDev: true, // Explicitly setting `true` to ensure we have event validation to make sure the events sent pass our validation. - sendTo: 'staging', logger: loggerMock.create(), }); registerPerformanceMetricEventType(analyticsClient); diff --git a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts index 2763222c60d1f..518375f1137b7 100644 --- a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts +++ b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; import { type PerformanceMetricEvent, METRIC_EVENT_SCHEMA } from './schema'; const PERFORMANCE_METRIC_EVENT_TYPE = 'performance_metric'; diff --git a/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts b/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts index b841b9b8d6f1d..8919fdc485d13 100644 --- a/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts +++ b/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { RootSchema } from '@kbn/ebt/client'; +import type { RootSchema } from '@elastic/ebt/client'; /** * Structure of the `metric` event diff --git a/packages/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx b/packages/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx index 72f3d39d3c825..a59a458656825 100644 --- a/packages/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx +++ b/packages/kbn-ebt-tools/src/performance_metrics/context/performance_context.tsx @@ -11,6 +11,9 @@ import { afterFrame } from '@elastic/apm-rum-core'; import { useLocation } from 'react-router-dom'; import { perfomanceMarkers } from '../performance_markers'; import { PerformanceApi, PerformanceContext } from './use_performance_context'; +import { PerformanceMetricEvent } from '../../performance_metric_events'; + +export type CustomMetrics = Omit<PerformanceMetricEvent, 'eventName' | 'meta' | 'duration'>; function measureInteraction() { performance.mark(perfomanceMarkers.startPageChange); @@ -19,13 +22,18 @@ function measureInteraction() { /** * Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state. * @param pathname - The pathname of the page. + * @param customMetrics - Custom metrics to be included in the performance measure. */ - pageReady(pathname: string) { + pageReady(pathname: string, customMetrics?: CustomMetrics) { performance.mark(perfomanceMarkers.endPageReady); if (!trackedRoutes.includes(pathname)) { performance.measure(pathname, { - detail: { eventName: 'kibana:plugin_render_time', type: 'kibana:performance' }, + detail: { + eventName: 'kibana:plugin_render_time', + type: 'kibana:performance', + customMetrics, + }, start: perfomanceMarkers.startPageChange, end: perfomanceMarkers.endPageReady, }); @@ -52,9 +60,9 @@ export function PerformanceContextProvider({ children }: { children: React.React const api = useMemo<PerformanceApi>( () => ({ - onPageReady() { + onPageReady(customMetrics) { if (isRendered) { - interaction.pageReady(location.pathname); + interaction.pageReady(location.pathname, customMetrics); } }, }), diff --git a/packages/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx b/packages/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx index 3bc083186a869..28995b3bef336 100644 --- a/packages/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx +++ b/packages/kbn-ebt-tools/src/performance_metrics/context/use_performance_context.tsx @@ -7,9 +7,14 @@ */ import { createContext, useContext } from 'react'; +import { CustomMetrics } from './performance_context'; export interface PerformanceApi { - onPageReady(): void; + /** + * Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state. + * @param customMetrics - Custom metrics to be included in the performance measure. + */ + onPageReady(customMetrics?: CustomMetrics): void; } export const PerformanceContext = createContext<PerformanceApi | undefined>(undefined); diff --git a/packages/kbn-ebt-tools/tsconfig.json b/packages/kbn-ebt-tools/tsconfig.json index 881349b3f4d9b..680ae75219fff 100644 --- a/packages/kbn-ebt-tools/tsconfig.json +++ b/packages/kbn-ebt-tools/tsconfig.json @@ -6,8 +6,7 @@ }, "include": ["**/*.ts", "**/*.tsx"], "kbn_references": [ - "@kbn/logging-mocks", - "@kbn/ebt", + "@kbn/logging-mocks" ], "exclude": ["target/**/*"] } diff --git a/packages/kbn-es/index.ts b/packages/kbn-es/index.ts index 2f241828f400d..12bb617f3ecfb 100644 --- a/packages/kbn-es/index.ts +++ b/packages/kbn-es/index.ts @@ -21,4 +21,4 @@ export { readRolesDescriptorsFromResource, } from './src/utils'; export type { ArtifactLicense } from './src/artifact'; -export { SERVERLESS_ROLES_ROOT_PATH } from './src/paths'; +export { SERVERLESS_ROLES_ROOT_PATH, STATEFUL_ROLES_ROOT_PATH } from './src/paths'; diff --git a/packages/kbn-es/src/paths.ts b/packages/kbn-es/src/paths.ts index 2ee3ea7c7c205..b407630333c7e 100644 --- a/packages/kbn-es/src/paths.ts +++ b/packages/kbn-es/src/paths.ts @@ -25,6 +25,8 @@ export const ES_CONFIG = 'config/elasticsearch.yml'; export const ES_KEYSTORE_BIN = maybeUseBat('./bin/elasticsearch-keystore'); +export const STATEFUL_ROLES_ROOT_PATH = resolve(__dirname, './stateful_resources'); + export const SERVERLESS_OPERATOR_USERS_PATH = resolve( __dirname, './serverless_resources/operator_users.yml' diff --git a/packages/kbn-es/src/stateful_resources/roles.yml b/packages/kbn-es/src/stateful_resources/roles.yml new file mode 100644 index 0000000000000..49ae1fafad958 --- /dev/null +++ b/packages/kbn-es/src/stateful_resources/roles.yml @@ -0,0 +1,130 @@ +# ----- +# This file is for information purpose only. 'viewer' and 'editor' roles are defined in stateful Elasticsearch by default +# Source: https://github.com/elastic/elasticsearch/blob/4272164530807787d4d8b991e3095a6e79176dbf/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java#L861-L952 +# Note: inconsistency between these roles definition and the same roles of serverless project may break FTR deployment-agnostic tests +# ----- +viewer: + cluster: [] + indices: + - names: + - '.alerts*' + - '.preview.alerts*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.items-*' + - '.lists-*' + - '.siem-signals*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '/~(([.]|ilm-history-).*)/' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.profiling-*' + - 'profiling-*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + applications: + - application: 'kibana-.kibana' + privileges: + - 'read' + resources: + - '*' + run_as: [] + +editor: + cluster: [] + indices: + - names: + - 'observability-annotations' + privileges: + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + - names: + - '.items-*' + - '.lists-*' + - '.siem-signals*' + privileges: + - 'maintenance' + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + - names: + - '/~(([.]|ilm-history-).*)/' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.profiling-*' + - 'profiling-*' + privileges: + - 'read' + - 'view_index_metadata' + allow_restricted_indices: false + - names: + - '.alerts*' + - '.internal.alerts*' + - '.internal.preview.alerts*' + - '.preview.alerts*' + privileges: + - 'maintenance' + - 'read' + - 'view_index_metadata' + - 'write' + allow_restricted_indices: false + applications: + - application: 'kibana-.kibana' + privileges: + - 'all' + resources: + - '*' + run_as: [] + +# Admin role without 'remote_indices' access definition +# There is no such built-in role in stateful, and it's a role "similar" to the built-in 'admin' role in serverless +admin: + # TODO: 'all' should be replaced with explicit list both here and serverless for deployment-agnostic tests with 'admin' role to be compatible + cluster: ['all'] + indices: + - names: ['*'] + privileges: ['all'] + allow_restricted_indices: false + - names: ['*'] + privileges: + - 'monitor' + - 'read' + - 'read_cross_cluster' + - 'view_index_metadata' + allow_restricted_indices: true + applications: + - application: '*' + privileges: ['*'] + resources: ['*'] + run_as: ['*'] + +# temporarily added for testing purpose +system_indices_superuser: + cluster: ['all'] + indices: + - names: ['*'] + privileges: ['all'] + allow_restricted_indices: true + applications: + - application: '*' + privileges: ['*'] + resources: ['*'] + run_as: ['*'] diff --git a/packages/kbn-es/src/utils/docker.ts b/packages/kbn-es/src/utils/docker.ts index 5e462a74ccfb7..39e947793ae09 100644 --- a/packages/kbn-es/src/utils/docker.ts +++ b/packages/kbn-es/src/utils/docker.ts @@ -9,6 +9,7 @@ import chalk from 'chalk'; import execa from 'execa'; import fs from 'fs'; import Fsp from 'fs/promises'; +import pRetry from 'p-retry'; import { resolve, basename, join } from 'path'; import { Client, ClientOptions, HttpConnection } from '@elastic/elasticsearch'; @@ -381,6 +382,12 @@ export async function maybeCreateDockerNetwork(log: ToolingLog) { log.indent(-4); } +const RETRYABLE_DOCKER_PULL_ERROR_MESSAGES = [ + 'connection refused', + 'i/o timeout', + 'Client.Timeout', +]; + /** * * Pull a Docker image if needed. Ensures latest image. @@ -390,16 +397,33 @@ export async function maybeCreateDockerNetwork(log: ToolingLog) { 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 pull output - stdio: ['ignore', 'inherit', 'pipe'], - }).catch(({ message }) => { - const errorMessage = `Error pulling image. This is likely an issue authenticating with ${DOCKER_REGISTRY}. + await pRetry( + async () => { + await execa('docker', ['pull', image], { + // inherit is required to show Docker pull output + stdio: ['ignore', 'inherit', 'pipe'], + }).catch(({ message }) => { + const errorMessage = `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}`; - throw createCliError(errorMessage); - }); + throw createCliError(errorMessage); + }); + }, + { + retries: 2, + onFailedAttempt: (error) => { + // Only retry if retryable error messages are found in the error message. + if ( + RETRYABLE_DOCKER_PULL_ERROR_MESSAGES.every( + (msg) => !error?.message?.includes('connection refused') + ) + ) { + throw error; + } + }, + } + ); } /** diff --git a/packages/kbn-eslint-config/.eslintrc.js b/packages/kbn-eslint-config/.eslintrc.js index b14cd62dbf7ab..1391c842b235a 100644 --- a/packages/kbn-eslint-config/.eslintrc.js +++ b/packages/kbn-eslint-config/.eslintrc.js @@ -1,3 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + const { USES_STYLED_COMPONENTS } = require('@kbn/babel-preset/styled_components_files'); module.exports = { @@ -9,6 +28,7 @@ module.exports = { '@kbn/eslint-plugin-imports', '@kbn/eslint-plugin-telemetry', '@kbn/eslint-plugin-i18n', + 'eslint-plugin-depend', 'prettier', ], @@ -21,6 +41,19 @@ module.exports = { }, rules: { + // Suggests better replacements for packages: https://github.com/es-tooling/module-replacements/tree/main/docs/modules + 'depend/ban-dependencies': [ + 'error', + { + allowed: [ + '^@kbn/*', // internal packages + 'lodash', // https://github.com/es-tooling/module-replacements/blob/main/docs/modules/lodash-underscore.md + 'moment', // https://github.com/es-tooling/module-replacements/blob/main/docs/modules/momentjs.md + 'jquery', // https://github.com/es-tooling/module-replacements/blob/main/docs/modules/jquery.md + ], + }, + ], + 'prettier/prettier': [ 'error', { @@ -80,9 +113,7 @@ module.exports = { from: 'react-intl', to: '@kbn/i18n-react', disallowedMessage: `import from @kbn/i18n-react instead`, - exclude: [ - /packages[\/\\]kbn-i18n-react[\/\\]/, - ] + exclude: [/packages[\/\\]kbn-i18n-react[\/\\]/], }, { from: 'styled-components', diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.commands.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.commands.test.ts new file mode 100644 index 0000000000000..a636f4a448595 --- /dev/null +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.commands.test.ts @@ -0,0 +1,369 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors as parse } from '../ast_parser'; + +describe('commands', () => { + describe('correctly formatted, basic usage', () => { + it('SHOW', () => { + const query = 'SHOW info'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + { + type: 'command', + name: 'show', + args: [ + { + type: 'function', + name: 'info', + }, + ], + }, + ]); + }); + + it('META', () => { + const query = 'META functions'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + { + type: 'command', + name: 'meta', + args: [ + { + type: 'function', + name: 'functions', + }, + ], + }, + ]); + }); + + it('FROM', () => { + const query = 'FROM index'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + { + type: 'command', + name: 'from', + args: [ + { + type: 'source', + name: 'index', + }, + ], + }, + ]); + }); + + it('ROW', () => { + const query = 'ROW 1'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + { + type: 'command', + name: 'row', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + ]); + }); + + it('EVAL', () => { + const query = 'FROM index | EVAL 1'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'eval', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + ]); + }); + + it('STATS', () => { + const query = 'FROM index | STATS 1'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'stats', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + ]); + }); + + it('LIMIT', () => { + const query = 'FROM index | LIMIT 1'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'limit', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + ]); + }); + + it('KEEP', () => { + const query = 'FROM index | KEEP abc'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'keep', + args: [ + { + type: 'column', + name: 'abc', + }, + ], + }, + ]); + }); + + it('SORT', () => { + const query = 'FROM index | SORT 1'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'sort', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + ]); + }); + + it('WHERE', () => { + const query = 'FROM index | WHERE 1'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'where', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + ]); + }); + + it('DROP', () => { + const query = 'FROM index | DROP abc'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'drop', + args: [ + { + type: 'column', + name: 'abc', + }, + ], + }, + ]); + }); + + it('RENAME', () => { + const query = 'FROM index | RENAME a AS b, c AS d'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'rename', + args: [ + { + type: 'option', + name: 'as', + args: [ + { + type: 'column', + name: 'a', + }, + { + type: 'column', + name: 'b', + }, + ], + }, + { + type: 'option', + name: 'as', + args: [ + { + type: 'column', + name: 'c', + }, + { + type: 'column', + name: 'd', + }, + ], + }, + ], + }, + ]); + }); + + it('DISSECT', () => { + const query = 'FROM index | DISSECT a "b" APPEND_SEPARATOR="c"'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'dissect', + args: [ + { + type: 'column', + name: 'a', + }, + { + type: 'literal', + value: '"b"', + }, + { + type: 'option', + name: 'append_separator', + args: [ + { + type: 'literal', + value: '"c"', + }, + ], + }, + ], + }, + ]); + }); + + it('GROK', () => { + const query = 'FROM index | GROK a "b"'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'grok', + args: [ + { + type: 'column', + name: 'a', + }, + { + type: 'literal', + value: '"b"', + }, + ], + }, + ]); + }); + + it('ENRICH', () => { + const query = 'FROM index | ENRICH a ON b WITH c, d'; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'enrich', + args: [ + { + type: 'source', + name: 'a', + }, + { + type: 'option', + name: 'on', + args: [ + { + type: 'column', + name: 'b', + }, + ], + }, + { + type: 'option', + name: 'with', + }, + ], + }, + ]); + }); + + it('MV_EXPAND', () => { + const query = 'FROM index | MV_EXPAND a '; + const { ast } = parse(query); + + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'mv_expand', + args: [ + { + type: 'column', + name: 'a', + }, + ], + }, + ]); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.literal.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.literal.test.ts new file mode 100644 index 0000000000000..1f941c49f0fbe --- /dev/null +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.literal.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 { getAstAndSyntaxErrors as parse } from '../ast_parser'; +import { ESQLLiteral } from '../types'; + +describe('literal expression', () => { + it('numeric expression captures "value", and "name" fields', () => { + const text = 'ROW 1'; + const { ast } = parse(text); + const literal = ast[0].args[0] as ESQLLiteral; + + expect(literal).toMatchObject({ + type: 'literal', + literalType: 'integer', + name: '1', + value: 1, + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.sort.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.sort.test.ts new file mode 100644 index 0000000000000..ccfbceb890893 --- /dev/null +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.sort.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors as parse } from '../ast_parser'; + +describe('SORT', () => { + describe('correctly formatted', () => { + // Un-skip one https://github.com/elastic/kibana/issues/189491 fixed. + it.skip('example from documentation', () => { + const text = ` + FROM employees + | KEEP first_name, last_name, height + | SORT height DESC + `; + const { ast, errors } = parse(text); + + expect(errors.length).toBe(0); + expect(ast).toMatchObject([ + {}, + {}, + { + type: 'command', + name: 'sort', + args: [ + { + type: 'column', + name: 'height', + }, + ], + }, + ]); + }); + + // Un-skip once https://github.com/elastic/kibana/issues/189491 fixed. + it.skip('can parse various sorting columns with options', () => { + const text = + 'FROM a | SORT a, b ASC, c DESC, d NULLS FIRST, e NULLS LAST, f ASC NULLS FIRST, g DESC NULLS LAST'; + const { ast, errors } = parse(text); + + expect(errors.length).toBe(0); + expect(ast).toMatchObject([ + {}, + { + type: 'command', + name: 'sort', + args: [ + { + type: 'column', + name: 'a', + }, + { + type: 'column', + name: 'b', + }, + { + type: 'column', + name: 'c', + }, + { + type: 'column', + name: 'd', + }, + { + type: 'column', + name: 'e', + }, + { + type: 'column', + name: 'f', + }, + { + type: 'column', + name: 'g', + }, + ], + }, + ]); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.where.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.where.test.ts new file mode 100644 index 0000000000000..34148ec1aecd2 --- /dev/null +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.where.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors as parse } from '../ast_parser'; + +describe('WHERE', () => { + describe('correctly formatted', () => { + it('example from documentation', () => { + const text = ` + FROM employees + | KEEP first_name, last_name, still_hired + | WHERE still_hired == true + `; + const { ast, errors } = parse(text); + + expect(errors.length).toBe(0); + expect(ast).toMatchObject([ + {}, + {}, + { + type: 'command', + name: 'where', + args: [ + { + type: 'function', + name: '==', + }, + ], + }, + ]); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/ast_factory.ts b/packages/kbn-esql-ast/src/ast_factory.ts index f389be63b3afd..09d8cc174b842 100644 --- a/packages/kbn-esql-ast/src/ast_factory.ts +++ b/packages/kbn-esql-ast/src/ast_factory.ts @@ -30,6 +30,7 @@ import { type MetaCommandContext, type MetricsCommandContext, IndexPatternContext, + InlinestatsCommandContext, } from './antlr/esql_parser'; import { default as ESQLParserListener } from './antlr/esql_parser_listener'; import { @@ -197,6 +198,23 @@ export class AstListener implements ESQLParserListener { } } + /** + * Exit a parse tree produced by `esql_parser.inlinestatsCommand`. + * @param ctx the parse tree + */ + exitInlinestatsCommand(ctx: InlinestatsCommandContext) { + const command = createCommand('inlinestats', ctx); + this.ast.push(command); + + // STATS expression is optional + if (ctx._stats) { + command.args.push(...collectAllFields(ctx.fields(0))); + } + if (ctx._grouping) { + command.args.push(...visitByOption(ctx, ctx._stats ? ctx.fields(1) : ctx.fields(0))); + } + } + /** * Exit a parse tree produced by `esql_parser.limitCommand`. * @param ctx the parse tree @@ -205,7 +223,7 @@ export class AstListener implements ESQLParserListener { const command = createCommand('limit', ctx); this.ast.push(command); if (ctx.getToken(esql_parser.INTEGER_LITERAL, 0)) { - const literal = createLiteral('number', ctx.INTEGER_LITERAL()); + const literal = createLiteral('integer', ctx.INTEGER_LITERAL()); if (literal) { command.args.push(literal); } diff --git a/packages/kbn-esql-ast/src/ast_helpers.ts b/packages/kbn-esql-ast/src/ast_helpers.ts index cc6488d5bbc0c..6ebb3fb9c4490 100644 --- a/packages/kbn-esql-ast/src/ast_helpers.ts +++ b/packages/kbn-esql-ast/src/ast_helpers.ts @@ -35,7 +35,9 @@ import type { ESQLCommandMode, ESQLInlineCast, ESQLUnknownItem, + ESQLNumericLiteralType, FunctionSubtype, + ESQLNumericLiteral, } from './types'; export function nonNullable<T>(v: T): v is NonNullable<T> { @@ -87,11 +89,14 @@ export function createList(ctx: ParserRuleContext, values: ESQLLiteral[]): ESQLL }; } -export function createNumericLiteral(ctx: DecimalValueContext | IntegerValueContext): ESQLLiteral { +export function createNumericLiteral( + ctx: DecimalValueContext | IntegerValueContext, + literalType: ESQLNumericLiteralType +): ESQLLiteral { const text = ctx.getText(); return { type: 'literal', - literalType: 'number', + literalType, text, name: text, value: Number(text), @@ -100,10 +105,13 @@ export function createNumericLiteral(ctx: DecimalValueContext | IntegerValueCont }; } -export function createFakeMultiplyLiteral(ctx: ArithmeticUnaryContext): ESQLLiteral { +export function createFakeMultiplyLiteral( + ctx: ArithmeticUnaryContext, + literalType: ESQLNumericLiteralType +): ESQLLiteral { return { type: 'literal', - literalType: 'number', + literalType, text: ctx.getText(), name: ctx.getText(), value: ctx.PLUS() ? 1 : -1, @@ -158,12 +166,13 @@ export function createLiteral( location: getPosition(node.symbol), incomplete: isMissingText(text), }; - if (type === 'number') { + if (type === 'decimal' || type === 'integer') { return { ...partialLiteral, literalType: type, value: Number(text), - }; + paramType: 'number', + } as ESQLNumericLiteral<'decimal'> | ESQLNumericLiteral<'integer'>; } else if (type === 'param') { throw new Error('Should never happen'); } @@ -171,7 +180,7 @@ export function createLiteral( ...partialLiteral, literalType: type, value: text, - }; + } as ESQLLiteral; } export function createTimeUnit(ctx: QualifiedIntegerLiteralContext): ESQLTimeInterval { diff --git a/packages/kbn-esql-ast/src/ast_walker.ts b/packages/kbn-esql-ast/src/ast_walker.ts index 1b603975cf68f..7400d23d0dba2 100644 --- a/packages/kbn-esql-ast/src/ast_walker.ts +++ b/packages/kbn-esql-ast/src/ast_walker.ts @@ -61,6 +61,7 @@ import { InputNamedOrPositionalParamContext, InputParamContext, IndexPatternContext, + InlinestatsCommandContext, } from './antlr/esql_parser'; import { createSource, @@ -84,7 +85,7 @@ import { createUnknownItem, } from './ast_helpers'; import { getPosition } from './ast_position_utils'; -import type { +import { ESQLLiteral, ESQLColumn, ESQLFunction, @@ -289,7 +290,7 @@ function visitOperatorExpression( const arg = visitOperatorExpression(ctx.operatorExpression()); // this is a number sign thing const fn = createFunction('*', ctx, undefined, 'binary-expression'); - fn.args.push(createFakeMultiplyLiteral(ctx)); + fn.args.push(createFakeMultiplyLiteral(ctx, 'integer')); if (arg) { fn.args.push(arg); } @@ -328,16 +329,21 @@ function getConstant(ctx: ConstantContext): ESQLAstItem { // e.g. 1 year, 15 months return createTimeUnit(ctx); } + + // Decimal type covers multiple ES|QL types: long, double, etc. if (ctx instanceof DecimalLiteralContext) { - return createNumericLiteral(ctx.decimalValue()); + return createNumericLiteral(ctx.decimalValue(), 'decimal'); } + + // Integer type encompasses integer if (ctx instanceof IntegerLiteralContext) { - return createNumericLiteral(ctx.integerValue()); + return createNumericLiteral(ctx.integerValue(), 'integer'); } if (ctx instanceof BooleanLiteralContext) { return getBooleanValue(ctx); } if (ctx instanceof StringLiteralContext) { + // String literal covers multiple ES|QL types: text and keyword types return createLiteral('string', ctx.string_().QUOTED_STRING()); } if ( @@ -346,14 +352,18 @@ function getConstant(ctx: ConstantContext): ESQLAstItem { ctx instanceof StringArrayLiteralContext ) { const values: ESQLLiteral[] = []; + for (const numericValue of ctx.getTypedRuleContexts(NumericValueContext)) { + const isDecimal = + numericValue.decimalValue() !== null && numericValue.decimalValue() !== undefined; const value = numericValue.decimalValue() || numericValue.integerValue(); - values.push(createNumericLiteral(value!)); + values.push(createNumericLiteral(value!, isDecimal ? 'decimal' : 'integer')); } for (const booleanValue of ctx.getTypedRuleContexts(BooleanValueContext)) { values.push(getBooleanValue(booleanValue)!); } for (const string of ctx.getTypedRuleContexts(StringContext)) { + // String literal covers multiple ES|QL types: text and keyword types const literal = createLiteral('string', string.QUOTED_STRING()); if (literal) { values.push(literal); @@ -585,7 +595,10 @@ export function collectAllFields(ctx: FieldsContext | undefined): ESQLAstField[] return ast; } -export function visitByOption(ctx: StatsCommandContext, expr: FieldsContext | undefined) { +export function visitByOption( + ctx: StatsCommandContext | InlinestatsCommandContext, + expr: FieldsContext | undefined +) { if (!ctx.BY() || !expr) { return []; } diff --git a/packages/kbn-esql-ast/src/builder/index.test.ts b/packages/kbn-esql-ast/src/builder/index.test.ts new file mode 100644 index 0000000000000..d8199027ea1c8 --- /dev/null +++ b/packages/kbn-esql-ast/src/builder/index.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Builder } from '.'; + +test('can mint a numeric literal', () => { + const node = Builder.numericLiteral({ value: 42 }); + + expect(node).toMatchObject({ + type: 'literal', + literalType: 'integer', + name: '42', + value: 42, + }); +}); diff --git a/packages/kbn-esql-ast/src/builder/index.ts b/packages/kbn-esql-ast/src/builder/index.ts new file mode 100644 index 0000000000000..524301111ed4d --- /dev/null +++ b/packages/kbn-esql-ast/src/builder/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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ESQLDecimalLiteral, ESQLIntegerLiteral, ESQLNumericLiteralType } from '../types'; +import { AstNodeParserFields, AstNodeTemplate } from './types'; + +export class Builder { + /** + * Constructs fields which are only available when the node is minted by + * the parser. + */ + public static readonly parserFields = ({ + location = { min: 0, max: 0 }, + text = '', + incomplete = false, + }: Partial<AstNodeParserFields>): AstNodeParserFields => ({ + location, + text, + incomplete, + }); + + /** + * Constructs a integer literal node. + */ + public static readonly numericLiteral = ( + template: Omit< + AstNodeTemplate<ESQLIntegerLiteral | ESQLDecimalLiteral>, + 'literalType' | 'name' + >, + type: ESQLNumericLiteralType = 'integer' + ): ESQLIntegerLiteral | ESQLDecimalLiteral => { + const node: ESQLIntegerLiteral | ESQLDecimalLiteral = { + ...template, + ...Builder.parserFields(template), + type: 'literal', + literalType: type, + name: template.value.toString(), + }; + + return node; + }; +} diff --git a/packages/kbn-esql-ast/src/builder/types.ts b/packages/kbn-esql-ast/src/builder/types.ts new file mode 100644 index 0000000000000..60575c0d00994 --- /dev/null +++ b/packages/kbn-esql-ast/src/builder/types.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 type { ESQLProperNode, ESQLAstBaseItem } from '../types'; + +/** + * Node fields which are available only when the node is minted by the parser. + * When creating nodes manually, these fields are not available. + */ +export type AstNodeParserFields = Pick<ESQLAstBaseItem, 'text' | 'location' | 'incomplete'>; + +/** + * The node *template* transforms ES|QL AST nodes into a permissive shape, with + * the aim to: + * + * - Remove the `type` property, as the builder will set it. + * - Make properties like `text`, `location`, and `incomplete` optional, as they + * are a available only when the AST node is minted by the parser. + * - Make all other properties optional, for easy node creation. + */ +export type AstNodeTemplate<Node extends ESQLProperNode> = Omit< + Node, + 'type' | 'text' | 'location' | 'incomplete' +> & + Partial<Omit<Node, 'type'>>; diff --git a/packages/kbn-esql-ast/src/pretty_print/pretty_print_one_line.test.ts b/packages/kbn-esql-ast/src/pretty_print/pretty_print_one_line.test.ts new file mode 100644 index 0000000000000..2112eeabe483b --- /dev/null +++ b/packages/kbn-esql-ast/src/pretty_print/pretty_print_one_line.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors } from '../ast_parser'; +import { prettyPrintOneLine } from './pretty_print_one_line'; + +const reprint = (src: string) => { + const { ast } = getAstAndSyntaxErrors(src); + const text = prettyPrintOneLine(ast); + + // console.log(JSON.stringify(ast, null, 2)); + + return { text }; +}; + +describe('commands', () => { + describe('FROM', () => { + test('FROM command with a single source', () => { + const { text } = reprint('FROM index1'); + + expect(text).toBe('FROM index1'); + }); + + test('FROM command with multiple indices', () => { + const { text } = reprint('from index1, index2, index3'); + + expect(text).toBe('FROM index1, index2, index3'); + }); + + test('FROM command with METADATA', () => { + const { text } = reprint('FROM index1, index2 METADATA field1, field2'); + + expect(text).toBe('FROM index1, index2 METADATA field1, field2'); + }); + }); + + describe('SORT', () => { + test('order expression with no modifier', () => { + const { text } = reprint('FROM a | SORT b'); + + expect(text).toBe('FROM a | SORT b'); + }); + + /** @todo Enable once order expressions are supported. */ + test.skip('order expression with ASC modifier', () => { + const { text } = reprint('FROM a | SORT b ASC'); + + expect(text).toBe('FROM a | SORT b ASC'); + }); + + /** @todo Enable once order expressions are supported. */ + test.skip('order expression with ASC and NULLS FIRST modifier', () => { + const { text } = reprint('FROM a | SORT b ASC NULLS FIRST'); + + expect(text).toBe('FROM a | SORT b ASC NULLS FIRST'); + }); + }); + + describe('EXPLAIN', () => { + /** @todo Enable once query expressions are supported. */ + test.skip('a nested query', () => { + const { text } = reprint('EXPLAIN [ FROM 1 ]'); + + expect(text).toBe('EXPLAIN [ FROM 1 ]'); + }); + }); + + describe('SHOW', () => { + /** @todo Enable once show command args are parsed as columns. */ + test.skip('info page', () => { + const { text } = reprint('SHOW info'); + + expect(text).toBe('SHOW info'); + }); + }); + + describe('META', () => { + /** @todo Enable once show command args are parsed as columns. */ + test.skip('functions page', () => { + const { text } = reprint('META functions'); + + expect(text).toBe('META functions'); + }); + }); + + describe('STATS', () => { + test('with aggregates assignment', () => { + const { text } = reprint('FROM a | STATS var = agg(123, fn(true))'); + + expect(text).toBe('FROM a | STATS var = AGG(123, FN(TRUE))'); + }); + + test('with BY clause', () => { + const { text } = reprint('FROM a | STATS a(1), b(2) by asdf'); + + expect(text).toBe('FROM a | STATS A(1), B(2) BY asdf'); + }); + }); +}); + +describe('expressions', () => { + describe('source expressions', () => { + test('simple source expression', () => { + const { text } = reprint('from source'); + + expect(text).toBe('FROM source'); + }); + + test('sources with dots', () => { + const { text } = reprint('FROM a.b.c'); + + expect(text).toBe('FROM a.b.c'); + }); + + test('sources with slashes', () => { + const { text } = reprint('FROM a/b/c'); + + expect(text).toBe('FROM a/b/c'); + }); + + test('cluster source', () => { + const { text } = reprint('FROM cluster:index'); + + expect(text).toBe('FROM cluster:index'); + }); + + test('quoted source', () => { + const { text } = reprint('FROM "quoted"'); + + expect(text).toBe('FROM quoted'); + }); + + test('triple-quoted source', () => { + const { text } = reprint('FROM """quoted"""'); + + expect(text).toBe('FROM quoted'); + }); + }); + + describe('column expressions', () => { + test('simple columns expressions', () => { + const { text } = reprint('FROM a METADATA column1, _column2'); + + expect(text).toBe('FROM a METADATA column1, _column2'); + }); + + test('nested fields', () => { + const { text } = reprint('FROM a | KEEP a.b'); + + expect(text).toBe('FROM a | KEEP a.b'); + }); + + // Un-skip when "IdentifierPattern" is parsed correctly. + test.skip('quoted nested fields', () => { + const { text } = reprint('FROM index | KEEP `a`.`b`, c.`d`'); + + expect(text).toBe('FROM index | KEEP a.b, c.d'); + }); + + // Un-skip when identifier names are escaped correctly. + test.skip('special character in identifier', () => { + const { text } = reprint('FROM a | KEEP `a 👉 b`, a.`✅`'); + + expect(text).toBe('FROM a | KEEP `a 👉 b`, a.`✅`'); + }); + }); + + describe('"function" expressions', () => { + describe('function call expression', () => { + test('no argument function', () => { + const { text } = reprint('ROW fn()'); + + expect(text).toBe('ROW FN()'); + }); + + test('functions with arguments', () => { + const { text } = reprint('ROW gg(1), wp(1, 2, 3)'); + + expect(text).toBe('ROW GG(1), WP(1, 2, 3)'); + }); + + test('functions with star argument', () => { + const { text } = reprint('ROW f(*)'); + + expect(text).toBe('ROW F(*)'); + }); + }); + + describe('unary expression', () => { + test('NOT expression', () => { + const { text } = reprint('ROW NOT a'); + + expect(text).toBe('ROW NOT a'); + }); + }); + + describe('postfix unary expression', () => { + test('IS NOT NULL expression', () => { + const { text } = reprint('ROW a IS NOT NULL'); + + expect(text).toBe('ROW a IS NOT NULL'); + }); + }); + + describe('binary expression expression', () => { + test('arithmetic expression', () => { + const { text } = reprint('ROW 1 + 2'); + + expect(text).toBe('ROW 1 + 2'); + }); + + test('assignment expression', () => { + const { text } = reprint('FROM a | STATS a != 1'); + + expect(text).toBe('FROM a | STATS a != 1'); + }); + + test('regex expression - 1', () => { + const { text } = reprint('FROM a | WHERE a NOT RLIKE "a"'); + + expect(text).toBe('FROM a | WHERE a NOT RLIKE "a"'); + }); + + test('regex expression - 2', () => { + const { text } = reprint('FROM a | WHERE a LIKE "b"'); + + expect(text).toBe('FROM a | WHERE a LIKE "b"'); + }); + }); + }); + + describe('literals expressions', () => { + describe('numeric literal', () => { + test('null', () => { + const { text } = reprint('ROW null'); + + expect(text).toBe('ROW NULL'); + }); + + test('boolean', () => { + expect(reprint('ROW true').text).toBe('ROW TRUE'); + expect(reprint('ROW false').text).toBe('ROW FALSE'); + }); + + test('integer', () => { + const { text } = reprint('ROW 1'); + + expect(text).toBe('ROW 1'); + }); + + test('decimal', () => { + const { text } = reprint('ROW 1.2'); + + expect(text).toBe('ROW 1.2'); + }); + + test('string', () => { + const { text } = reprint('ROW "abc"'); + + expect(text).toBe('ROW "abc"'); + }); + + test('string w/ special chars', () => { + const { text } = reprint('ROW "as \\" 👍"'); + + expect(text).toBe('ROW "as \\" 👍"'); + }); + + describe('params', () => { + test('unnamed', () => { + const { text } = reprint('ROW ?'); + + expect(text).toBe('ROW ?'); + }); + + test('named', () => { + const { text } = reprint('ROW ?kappa'); + + expect(text).toBe('ROW ?kappa'); + }); + + test('positional', () => { + const { text } = reprint('ROW ?42'); + + expect(text).toBe('ROW ?42'); + }); + }); + }); + }); + + describe('list literal expressions', () => { + describe('integer list', () => { + test('one element list', () => { + expect(reprint('ROW [1]').text).toBe('ROW [1]'); + }); + + test('multiple elements', () => { + expect(reprint('ROW [1, 2]').text).toBe('ROW [1, 2]'); + expect(reprint('ROW [1, 2, -1]').text).toBe('ROW [1, 2, -1]'); + }); + }); + + describe('boolean list', () => { + test('one element list', () => { + expect(reprint('ROW [true]').text).toBe('ROW [TRUE]'); + }); + + test('multiple elements', () => { + expect(reprint('ROW [TRUE, false]').text).toBe('ROW [TRUE, FALSE]'); + expect(reprint('ROW [false, FALSE, false]').text).toBe('ROW [FALSE, FALSE, FALSE]'); + }); + }); + + describe('string list', () => { + test('one element list', () => { + expect(reprint('ROW ["a"]').text).toBe('ROW ["a"]'); + }); + + test('multiple elements', () => { + expect(reprint('ROW ["a", "b"]').text).toBe('ROW ["a", "b"]'); + expect(reprint('ROW ["foo", "42", "boden"]').text).toBe('ROW ["foo", "42", "boden"]'); + }); + }); + }); + + describe('cast expressions', () => { + test('various', () => { + expect(reprint('ROW a::string').text).toBe('ROW a::string'); + expect(reprint('ROW 123::string').text).toBe('ROW 123::string'); + expect(reprint('ROW "asdf"::number').text).toBe('ROW "asdf"::number'); + }); + }); + + describe('time interval expression', () => { + test('days', () => { + const { text } = reprint('ROW 1 d'); + + expect(text).toBe('ROW 1d'); + }); + + test('years', () => { + const { text } = reprint('ROW 42y'); + + expect(text).toBe('ROW 42y'); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/pretty_print/pretty_print_one_line.ts b/packages/kbn-esql-ast/src/pretty_print/pretty_print_one_line.ts new file mode 100644 index 0000000000000..94f4afd1acd11 --- /dev/null +++ b/packages/kbn-esql-ast/src/pretty_print/pretty_print_one_line.ts @@ -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 { ESQLAstQueryNode, Visitor } from '../visitor'; + +export const prettyPrintOneLine = (query: ESQLAstQueryNode) => { + const visitor = new Visitor() + .on('visitSourceExpression', (ctx) => { + return ctx.node.name; + }) + .on('visitColumnExpression', (ctx) => { + /** + * @todo: Add support for: (1) escaped characters, (2) nested fields. + */ + return ctx.node.name; + }) + .on('visitFunctionCallExpression', (ctx) => { + const node = ctx.node; + let operator = node.name.toUpperCase(); + + switch (node.subtype) { + case 'unary-expression': { + return `${operator} ${ctx.visitArgument(0)}`; + } + case 'postfix-unary-expression': { + return `${ctx.visitArgument(0)} ${operator}`; + } + case 'binary-expression': { + /** @todo Make `operator` printable. */ + switch (operator) { + case 'NOT_LIKE': { + operator = 'NOT LIKE'; + break; + } + case 'NOT_RLIKE': { + operator = 'NOT RLIKE'; + break; + } + } + return `${ctx.visitArgument(0)} ${operator} ${ctx.visitArgument(1)}`; + } + default: { + let args = ''; + + for (const arg of ctx.visitArguments()) { + args += (args ? ', ' : '') + arg; + } + + return `${operator}(${args})`; + } + } + }) + .on('visitLiteralExpression', (ctx) => { + const node = ctx.node; + + switch (node.literalType) { + case 'null': { + return 'NULL'; + } + case 'boolean': { + return String(node.value).toUpperCase() === 'TRUE' ? 'TRUE' : 'FALSE'; + } + case 'param': { + switch (node.paramType) { + case 'named': + case 'positional': + return '?' + node.value; + default: + return '?'; + } + } + case 'string': { + return node.value; + } + default: { + return String(ctx.node.value); + } + } + }) + .on('visitListLiteralExpression', (ctx) => { + let elements = ''; + + for (const arg of ctx.visitElements()) { + elements += (elements ? ', ' : '') + arg; + } + + return `[${elements}]`; + }) + .on('visitTimeIntervalLiteralExpression', (ctx) => { + /** @todo Rename to `fmt`. */ + return ctx.format(); + }) + .on('visitInlineCastExpression', (ctx) => { + /** @todo Add `.fmt()` helper. */ + return `${ctx.visitValue()}::${ctx.node.castType}`; + }) + .on('visitExpression', (ctx) => { + return ctx.node.text ?? '<EXPRESSION>'; + }) + .on('visitCommandOption', (ctx) => { + const option = ctx.node.name.toUpperCase(); + let args = ''; + + for (const arg of ctx.visitArguments()) { + args += (args ? ', ' : '') + arg; + } + + const argsFormatted = args ? ` ${args}` : ''; + const optionFormatted = `${option}${argsFormatted}`; + + return optionFormatted; + }) + .on('visitCommand', (ctx) => { + const cmd = ctx.node.name.toUpperCase(); + let args = ''; + let options = ''; + + for (const source of ctx.visitArguments()) { + args += (args ? ', ' : '') + source; + } + + for (const option of ctx.visitOptions()) { + options += (options ? ' ' : '') + option; + } + + const argsFormatted = args ? ` ${args}` : ''; + const optionsFormatted = options ? ` ${options}` : ''; + const cmdFormatted = `${cmd}${argsFormatted}${optionsFormatted}`; + + return cmdFormatted; + }) + .on('visitQuery', (ctx) => { + let text = ''; + + for (const cmd of ctx.visitCommands()) { + text += (text ? ' | ' : '') + cmd; + } + + return text; + }); + + return visitor.visitQuery(query); +}; diff --git a/packages/kbn-esql-ast/src/types.ts b/packages/kbn-esql-ast/src/types.ts index 257a004e78f10..12496835ea12b 100644 --- a/packages/kbn-esql-ast/src/types.ts +++ b/packages/kbn-esql-ast/src/types.ts @@ -35,6 +35,15 @@ export type ESQLAstField = ESQLFunction | ESQLColumn; */ export type ESQLAstItem = ESQLSingleAstItem | ESQLAstItem[]; +export type ESQLAstNodeWithArgs = ESQLCommand | ESQLCommandOption | ESQLFunction; + +/** + * *Proper* are nodes which are objects with `type` property, once we get rid + * of the nodes which are plain arrays, all nodes will be *proper* and we can + * remove this type. + */ +export type ESQLProperNode = ESQLSingleAstItem | ESQLAstCommand; + export interface ESQLLocation { min: number; max: number; @@ -170,19 +179,30 @@ export interface ESQLList extends ESQLAstBaseItem { values: ESQLLiteral[]; } +export type ESQLNumericLiteralType = 'decimal' | 'integer'; + export type ESQLLiteral = - | ESQLNumberLiteral + | ESQLDecimalLiteral + | ESQLIntegerLiteral | ESQLBooleanLiteral | ESQLNullLiteral | ESQLStringLiteral | ESQLParamLiteral<string>; +// Exporting here to prevent TypeScript error TS4058 +// Return type of exported function has or is using name 'ESQLNumericLiteral' from external module // @internal -export interface ESQLNumberLiteral extends ESQLAstBaseItem { +export interface ESQLNumericLiteral<T extends ESQLNumericLiteralType> extends ESQLAstBaseItem { type: 'literal'; - literalType: 'number'; + literalType: T; value: number; } +// We cast anything as decimal (e.g. 32.12) as generic decimal numeric type here +// @internal +export type ESQLDecimalLiteral = ESQLNumericLiteral<'decimal'>; + +// @internal +export type ESQLIntegerLiteral = ESQLNumericLiteral<'integer'>; // @internal export interface ESQLBooleanLiteral extends ESQLAstBaseItem { diff --git a/packages/kbn-esql-ast/src/visitor/README.md b/packages/kbn-esql-ast/src/visitor/README.md new file mode 100644 index 0000000000000..c952c8a34d8d9 --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/README.md @@ -0,0 +1,69 @@ +## High-level AST structure + +Broadly, there are two AST node types: (1) commands (say `FROM ...`, like +*statements* in other languages), and (2) expressions (say `a + b`, or `fn()`). + + +### Commands + +Commands in ES|QL are like *statements* in other languages. They are the top +level nodes in the AST. + +The root node of the AST is considered to bye the "query" node. It contains a +list of commands. + +``` +Quey = Command[] +``` + +Each command receives a list of positional arguments. For example: + +``` +COMMAND arg1, arg2, arg3 +``` + +A command may also receive additional lists of *named* arguments, we refer to +them as `option`s. For example: + +``` +COMMAND arg1, arg2, arg3 OPTION1 arg4, arg5 OPTION2 arg6, arg7 +``` + +Essentially, one can of command arguments as a list of expressions, with the +ability to add named arguments to the command. For example, the above command +arguments can be represented as: + +```js +{ + '': [arg1, arg2, arg3], + 'option1': [arg4, arg5], + 'option2': [arg6, arg7] +} +``` + +Each command has a command specific `visitCommandX` callback, where `X` is the +name of the command. If a command-specific callback is not found, the generic +`visitCommand` callback is called. + + +### Expressions + +Expressions just like expressions in other languages. Expressions can be deeply +nested, as one expression can contain other expressions. For example, math +expressions `1 + 2`, function call expressions `fn()`, identifier expressions +`my.index` and so on. + +As of this writing, the following expressions are defined: + +- Source identifier expression, `{type: "source"}`, like `tsdb_index` +- Column identifier expression, `{type: "column"}`, like `@timestamp` +- Function call expression, `{type: "function"}`, like `fn(123)` +- Literal expression, `{type: "literal"}`, like `123`, `"hello"` +- List literal expression, `{type: "list"}`, like `[1, 2, 3]`, `["a", "b", "c"]`, `[true, false]` +- Time interval expression, `{type: "interval"}`, like `1h`, `1d`, `1w` +- Inline cast expression, `{type: "cast"}`, like `abc::int`, `def::string` +- Unknown node, `{type: "unknown"}` + +Each expression has a `visitExpressionX` callback, where `X` is the type of the +expression. If a expression-specific callback is not found, the generic +`visitExpression` callback is called. diff --git a/packages/kbn-esql-ast/src/visitor/__tests__/expressions.test.ts b/packages/kbn-esql-ast/src/visitor/__tests__/expressions.test.ts new file mode 100644 index 0000000000000..efd30f035e7ca --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/__tests__/expressions.test.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors } from '../../ast_parser'; +import { Visitor } from '../visitor'; + +test('"visitExpression" captures all non-captured expressions', () => { + const { ast } = getAstAndSyntaxErrors(` + FROM index + | STATS 1, "str", [true], a = b BY field + | LIMIT 123 + `); + const visitor = new Visitor() + .on('visitExpression', (ctx) => { + return '<EXPRESSION>'; + }) + .on('visitCommand', (ctx) => { + const args = [...ctx.visitArguments()].join(', '); + return `${ctx.name()}${args ? ` ${args}` : ''}`; + }) + .on('visitQuery', (ctx) => { + return [...ctx.visitCommands()].join(' | '); + }); + const text = visitor.visitQuery(ast); + + expect(text).toBe( + 'FROM <EXPRESSION> | STATS <EXPRESSION>, <EXPRESSION>, <EXPRESSION>, <EXPRESSION> | LIMIT <EXPRESSION>' + ); +}); + +test('can terminate walk early, does not visit all literals', () => { + const numbers: number[] = []; + const { ast } = getAstAndSyntaxErrors(` + FROM index + | STATS 0, 1, 2, 3 + | LIMIT 123 + `); + const result = new Visitor() + .on('visitExpression', (ctx) => { + return 0; + }) + .on('visitLiteralExpression', (ctx) => { + numbers.push(ctx.node.value as number); + return ctx.node.value; + }) + .on('visitCommand', (ctx) => { + for (const res of ctx.visitArguments()) if (res) return res; + }) + .on('visitQuery', (ctx) => { + for (const res of ctx.visitCommands()) if (res) return res; + }) + .visitQuery(ast); + + expect(result).toBe(1); + expect(numbers).toEqual([0, 1]); +}); + +test('"visitColumnExpression" takes over all column visits', () => { + const { ast } = getAstAndSyntaxErrors(` + FROM index | STATS a + `); + const visitor = new Visitor() + .on('visitColumnExpression', (ctx) => { + return '<COLUMN>'; + }) + .on('visitExpression', (ctx) => { + return 'E'; + }) + .on('visitCommand', (ctx) => { + const args = [...ctx.visitArguments()].join(', '); + return `${ctx.name()}${args ? ` ${args}` : ''}`; + }) + .on('visitQuery', (ctx) => { + return [...ctx.visitCommands()].join(' | '); + }); + const text = visitor.visitQuery(ast); + + expect(text).toBe('FROM E | STATS <COLUMN>'); +}); + +test('"visitSourceExpression" takes over all source visits', () => { + const { ast } = getAstAndSyntaxErrors(` + FROM index + | STATS 1, "str", [true], a = b BY field + | LIMIT 123 + `); + const visitor = new Visitor() + .on('visitSourceExpression', (ctx) => { + return '<SOURCE>'; + }) + .on('visitExpression', (ctx) => { + return 'E'; + }) + .on('visitCommand', (ctx) => { + const args = [...ctx.visitArguments()].join(', '); + return `${ctx.name()}${args ? ` ${args}` : ''}`; + }) + .on('visitQuery', (ctx) => { + return [...ctx.visitCommands()].join(' | '); + }); + const text = visitor.visitQuery(ast); + + expect(text).toBe('FROM <SOURCE> | STATS E, E, E, E | LIMIT E'); +}); + +test('"visitFunctionCallExpression" takes over all literal visits', () => { + const { ast } = getAstAndSyntaxErrors(` + FROM index + | STATS 1, "str", [true], a = b BY field + | LIMIT 123 + `); + const visitor = new Visitor() + .on('visitFunctionCallExpression', (ctx) => { + return '<FUNCTION>'; + }) + .on('visitExpression', (ctx) => { + return 'E'; + }) + .on('visitCommand', (ctx) => { + const args = [...ctx.visitArguments()].join(', '); + return `${ctx.name()}${args ? ` ${args}` : ''}`; + }) + .on('visitQuery', (ctx) => { + return [...ctx.visitCommands()].join(' | '); + }); + const text = visitor.visitQuery(ast); + + expect(text).toBe('FROM E | STATS E, E, E, <FUNCTION> | LIMIT E'); +}); + +test('"visitLiteral" takes over all literal visits', () => { + const { ast } = getAstAndSyntaxErrors(` + FROM index + | STATS 1, "str", [true], a = b BY field + | LIMIT 123 + `); + const visitor = new Visitor() + .on('visitLiteralExpression', (ctx) => { + return '<LITERAL>'; + }) + .on('visitExpression', (ctx) => { + return 'E'; + }) + .on('visitCommand', (ctx) => { + const args = [...ctx.visitArguments()].join(', '); + return `${ctx.name()}${args ? ` ${args}` : ''}`; + }) + .on('visitQuery', (ctx) => { + return [...ctx.visitCommands()].join(' | '); + }); + const text = visitor.visitQuery(ast); + + expect(text).toBe('FROM E | STATS <LITERAL>, <LITERAL>, E, E | LIMIT <LITERAL>'); +}); diff --git a/packages/kbn-esql-ast/src/visitor/__tests__/scenarios.test.ts b/packages/kbn-esql-ast/src/visitor/__tests__/scenarios.test.ts new file mode 100644 index 0000000000000..ce338e8bd72ba --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/__tests__/scenarios.test.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * @category Visitor Real-world Scenarios + * + * This test suite contains real-world scenarios that demonstrate how to use the + * visitor to traverse the AST and make changes to it, or how to extract useful + */ + +import { getAstAndSyntaxErrors } from '../../ast_parser'; +import { ESQLAstQueryNode } from '../types'; +import { Visitor } from '../visitor'; + +test('change LIMIT from 24 to 42', () => { + const { ast } = getAstAndSyntaxErrors(` + FROM index + | STATS 1, "str", [true], a = b BY field + | LIMIT 24 + `); + + // Find the LIMIT node + const limit = () => + new Visitor() + .on('visitLimitCommand', (ctx) => ctx.numeric()) + .on('visitCommand', () => null) + .on('visitQuery', (ctx) => [...ctx.visitCommands()]) + .visitQuery(ast) + .filter(Boolean)[0]; + + expect(limit()).toBe(24); + + // Change LIMIT to 42 + new Visitor() + .on('visitLimitCommand', (ctx) => { + ctx.setLimit(42); + }) + .on('visitCommand', () => {}) + .on('visitQuery', (ctx) => [...ctx.visitCommands()]) + .visitQuery(ast); + + expect(limit()).toBe(42); +}); + +/** + * Implement this once sorting order expressions are available: + * + * - https://github.com/elastic/kibana/issues/189491 + */ +test.todo('can modify sorting orders'); + +test('can remove a specific WHERE command', () => { + const query = getAstAndSyntaxErrors(` + FROM employees + | KEEP first_name, last_name, still_hired + | WHERE still_hired == true + | WHERE last_name == "Jeo" + | WHERE 123 == salary + `); + + const print = () => + new Visitor() + .on('visitColumnExpression', (ctx) => ctx.node.name) + .on( + 'visitFunctionCallExpression', + (ctx) => `${ctx.node.name}(${[...ctx.visitArguments()].join(', ')})` + ) + .on('visitExpression', (ctx) => '<expr>') + .on('visitCommand', (ctx) => { + if (ctx.node.name === 'where') { + const args = [...ctx.visitArguments()].join(', '); + return `${ctx.name()}${args ? ` ${args}` : ''}`; + } else { + return ''; + } + }) + .on('visitQuery', (ctx) => [...ctx.visitCommands()].filter(Boolean).join(' | ')) + .visitQuery(query.ast); + + const removeFilter = (field: string) => { + query.ast = new Visitor() + .on('visitColumnExpression', (ctx) => (ctx.node.name === field ? null : ctx.node)) + .on('visitFunctionCallExpression', (ctx) => { + const args = [...ctx.visitArguments()]; + return args.some((arg) => arg === null) ? null : ctx.node; + }) + .on('visitExpression', (ctx) => ctx.node) + .on('visitCommand', (ctx) => { + if (ctx.node.name === 'where') { + ctx.node.args = [...ctx.visitArguments()].filter(Boolean); + } + return ctx.node; + }) + .on('visitQuery', (ctx) => [...ctx.visitCommands()].filter((cmd) => cmd.args.length)) + .visitQuery(query.ast); + }; + + expect(print()).toBe( + 'WHERE ==(still_hired, <expr>) | WHERE ==(last_name, <expr>) | WHERE ==(<expr>, salary)' + ); + removeFilter('last_name'); + expect(print()).toBe('WHERE ==(still_hired, <expr>) | WHERE ==(<expr>, salary)'); + removeFilter('still_hired'); + removeFilter('still_hired'); + expect(print()).toBe('WHERE ==(<expr>, salary)'); + removeFilter('still_hired'); + removeFilter('salary'); + removeFilter('salary'); + expect(print()).toBe(''); +}); + +export const prettyPrint = (ast: ESQLAstQueryNode) => + new Visitor() + .on('visitSourceExpression', (ctx) => { + return ctx.node.name; + }) + .on('visitColumnExpression', (ctx) => { + return ctx.node.name; + }) + .on('visitFunctionCallExpression', (ctx) => { + let args = ''; + for (const arg of ctx.visitArguments()) { + args += (args ? ', ' : '') + arg; + } + return `${ctx.node.name.toUpperCase()}${args ? `(${args})` : ''}`; + }) + .on('visitLiteralExpression', (ctx) => { + return ctx.node.value; + }) + .on('visitListLiteralExpression', (ctx) => { + return '<LIST>'; + }) + .on('visitTimeIntervalLiteralExpression', (ctx) => { + return '<TIME_INTERVAL>'; + }) + .on('visitInlineCastExpression', (ctx) => { + return '<CAST>'; + }) + .on('visitExpression', (ctx) => { + return '<EXPRESSION>'; + }) + .on('visitCommandOption', (ctx) => { + let args = ''; + for (const arg of ctx.visitArguments()) { + args += (args ? ', ' : '') + arg; + } + return ctx.node.name.toUpperCase() + (args ? ` ${args}` : ''); + }) + .on('visitCommand', (ctx) => { + let args = ''; + for (const source of ctx.visitArguments()) { + args += (args ? ', ' : '') + source; + } + return `${ctx.node.name.toUpperCase()}${args ? ` ${args}` : ''}`; + }) + .on('visitFromCommand', (ctx) => { + let sources = ''; + for (const source of ctx.visitSources()) { + sources += (sources ? ', ' : '') + source; + } + let options = ''; + for (const option of ctx.visitOptions()) { + options += ' ' + option; + } + return `FROM ${sources}${options}`; + }) + .on('visitLimitCommand', (ctx) => { + return `LIMIT ${ctx.numeric() ?? 0}`; + }) + .on('visitQuery', (ctx) => { + let text = ''; + for (const cmd of ctx.visitCommands()) { + text += (text ? ' | ' : '') + cmd; + } + return text; + }) + .visitQuery(ast); + +test('can print a query to text', () => { + const { ast } = getAstAndSyntaxErrors( + 'FROM index METADATA _id, asdf, 123 | STATS fn([1,2], 1d, 1::string, x in (1, 2)), a = b | LIMIT 1000' + ); + const text = prettyPrint(ast); + + expect(text).toBe( + 'FROM index METADATA _id, asdf, 123 | STATS FN(<LIST>, <TIME_INTERVAL>, <CAST>, IN(x, 1, 2)), =(a, b) | LIMIT 1000' + ); +}); diff --git a/packages/kbn-esql-ast/src/visitor/__tests__/visitor.test.ts b/packages/kbn-esql-ast/src/visitor/__tests__/visitor.test.ts new file mode 100644 index 0000000000000..24944f635ee44 --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/__tests__/visitor.test.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 { getAstAndSyntaxErrors } from '../../ast_parser'; +import { CommandVisitorContext, WhereCommandVisitorContext } from '../contexts'; +import { Visitor } from '../visitor'; + +test('can collect all command names in type safe way', () => { + const visitor = new Visitor() + .on('visitCommand', (ctx) => { + return ctx.node.name; + }) + .on('visitQuery', (ctx) => { + const cmds = []; + for (const cmd of ctx.visitCommands()) { + cmds.push(cmd); + } + return cmds; + }); + + const { ast } = getAstAndSyntaxErrors('FROM index | LIMIT 123'); + const res = visitor.visitQuery(ast); + + expect(res).toEqual(['from', 'limit']); +}); + +test('can pass inputs to visitors', () => { + const visitor = new Visitor() + .on('visitCommand', (ctx, prefix: string) => { + return prefix + ctx.node.name; + }) + .on('visitQuery', (ctx) => { + const cmds = []; + for (const cmd of ctx.visitCommands('pfx:')) { + cmds.push(cmd); + } + return cmds; + }); + + const { ast } = getAstAndSyntaxErrors('FROM index | LIMIT 123'); + const res = visitor.visitQuery(ast); + + expect(res).toEqual(['pfx:from', 'pfx:limit']); +}); + +test('can specify specific visitors for commands', () => { + const { ast } = getAstAndSyntaxErrors( + 'FROM index | SORT asfd | WHERE 1 | ENRICH adsf | LIMIT 123' + ); + const res = new Visitor() + .on('visitWhereCommand', () => 'where') + .on('visitSortCommand', () => 'sort') + .on('visitEnrichCommand', () => 'very rich') + .on('visitCommand', () => 'DEFAULT') + .on('visitQuery', (ctx) => [...ctx.visitCommands()]) + .visitQuery(ast); + + expect(res).toEqual(['DEFAULT', 'sort', 'where', 'very rich', 'DEFAULT']); +}); + +test('a command can access parent query node', () => { + const { ast } = getAstAndSyntaxErrors( + 'FROM index | SORT asfd | WHERE 1 | ENRICH adsf | LIMIT 123' + ); + new Visitor() + .on('visitWhereCommand', (ctx) => { + if (ctx.parent!.node !== ast) { + throw new Error('Expected parent to be query node'); + } + }) + .on('visitCommand', (ctx) => { + if (ctx.parent!.node !== ast) { + throw new Error('Expected parent to be query node'); + } + }) + .on('visitQuery', (ctx) => [...ctx.visitCommands()]) + .visitQuery(ast); +}); + +test('specific commands receive specific visitor contexts', () => { + const { ast } = getAstAndSyntaxErrors( + 'FROM index | SORT asfd | WHERE 1 | ENRICH adsf | LIMIT 123' + ); + + new Visitor() + .on('visitWhereCommand', (ctx) => { + if (!(ctx instanceof WhereCommandVisitorContext)) { + throw new Error('Expected WhereCommandVisitorContext'); + } + if (!(ctx instanceof CommandVisitorContext)) { + throw new Error('Expected WhereCommandVisitorContext'); + } + }) + .on('visitCommand', (ctx) => { + if (!(ctx instanceof CommandVisitorContext)) { + throw new Error('Expected CommandVisitorContext'); + } + }) + .on('visitQuery', (ctx) => [...ctx.visitCommands()]) + .visitQuery(ast); + + new Visitor() + .on('visitCommand', (ctx) => { + if (!(ctx instanceof CommandVisitorContext)) { + throw new Error('Expected CommandVisitorContext'); + } + if (ctx instanceof WhereCommandVisitorContext) { + throw new Error('Did not expect WhereCommandVisitorContext'); + } + }) + .on('visitQuery', (ctx) => [...ctx.visitCommands()]) + .visitQuery(ast); +}); diff --git a/packages/kbn-esql-ast/src/visitor/contexts.ts b/packages/kbn-esql-ast/src/visitor/contexts.ts new file mode 100644 index 0000000000000..a9e690b6067d2 --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/contexts.ts @@ -0,0 +1,490 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may 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 max-classes-per-file */ +// Splitting classes across files runs into issues with circular dependencies +// and makes it harder to understand the code structure. + +import { type GlobalVisitorContext, SharedData } from './global_visitor_context'; +import { firstItem, singleItems } from './utils'; +import type { + ESQLAstCommand, + ESQLAstItem, + ESQLAstNodeWithArgs, + ESQLColumn, + ESQLCommandOption, + ESQLDecimalLiteral, + ESQLFunction, + ESQLInlineCast, + ESQLIntegerLiteral, + ESQLList, + ESQLLiteral, + ESQLSource, + ESQLTimeInterval, +} from '../types'; +import type { + CommandVisitorInput, + ESQLAstExpressionNode, + ESQLAstQueryNode, + ExpressionVisitorInput, + ExpressionVisitorOutput, + UndefinedToVoid, + VisitorAstNode, + VisitorMethods, +} from './types'; +import { Builder } from '../builder'; + +const isNodeWithArgs = (x: unknown): x is ESQLAstNodeWithArgs => + !!x && typeof x === 'object' && Array.isArray((x as any).args); + +export class VisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData, + Node extends VisitorAstNode = VisitorAstNode +> { + constructor( + /** + * Global visitor context. + */ + public readonly ctx: GlobalVisitorContext<Methods, Data>, + + /** + * ES|QL AST node which is currently being visited. + */ + public readonly node: Node, + + /** + * Context of the parent node, from which the current node was reached + * during the AST traversal. + */ + public readonly parent: VisitorContext | null = null + ) {} + + public *visitArguments( + input: ExpressionVisitorInput<Methods> + ): Iterable<ExpressionVisitorOutput<Methods>> { + this.ctx.assertMethodExists('visitExpression'); + + const node = this.node; + + if (!isNodeWithArgs(node)) { + throw new Error('Node does not have arguments'); + } + + for (const arg of singleItems(node.args)) { + yield this.visitExpression(arg, input as any); + } + } + + public visitArgument( + index: number, + input: ExpressionVisitorInput<Methods> + ): ExpressionVisitorOutput<Methods> { + this.ctx.assertMethodExists('visitExpression'); + + const node = this.node; + + if (!isNodeWithArgs(node)) { + throw new Error('Node does not have arguments'); + } + + let i = 0; + for (const arg of singleItems(node.args)) { + if (i === index) { + return this.visitExpression(arg, input as any); + } + i++; + } + + throw new Error(`Argument at index ${index} not found`); + } + + public visitExpression( + expressionNode: ESQLAstExpressionNode, + input: ExpressionVisitorInput<Methods> + ): ExpressionVisitorOutput<Methods> { + return this.ctx.visitExpression(this, expressionNode, input); + } + + public visitCommand( + commandNode: ESQLAstCommand, + input: CommandVisitorInput<Methods> + ): ExpressionVisitorOutput<Methods> { + return this.ctx.visitCommand(this, commandNode, input); + } +} + +export class QueryVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends VisitorContext<Methods, Data, ESQLAstQueryNode> { + public *visitCommands( + input: UndefinedToVoid<Parameters<NonNullable<Methods['visitCommand']>>[1]> + ): Iterable< + | ReturnType<NonNullable<Methods['visitCommand']>> + | ReturnType<NonNullable<Methods['visitFromCommand']>> + > { + this.ctx.assertMethodExists('visitCommand'); + + for (const cmd of this.node) { + yield this.visitCommand(cmd, input as any); + } + } +} + +// Commands -------------------------------------------------------------------- + +export class CommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData, + Node extends ESQLAstCommand = ESQLAstCommand +> extends VisitorContext<Methods, Data, Node> { + public name(): string { + return this.node.name.toUpperCase(); + } + + public *options(): Iterable<ESQLCommandOption> { + for (const arg of this.node.args) { + if (Array.isArray(arg)) { + continue; + } + if (arg.type === 'option') { + yield arg; + } + } + } + + public *visitOptions( + input: UndefinedToVoid<Parameters<NonNullable<Methods['visitCommandOption']>>[1]> + ): Iterable<ReturnType<NonNullable<Methods['visitCommandOption']>>> { + this.ctx.assertMethodExists('visitCommandOption'); + + for (const option of this.options()) { + const sourceContext = new CommandOptionVisitorContext(this.ctx, option, this); + const result = this.ctx.methods.visitCommandOption!(sourceContext, input); + + yield result; + } + } + + public *arguments(option: '' | string = ''): Iterable<ESQLAstItem> { + option = option.toLowerCase(); + + if (!option) { + for (const arg of this.node.args) { + if (Array.isArray(arg)) { + yield arg; + continue; + } + if (arg.type !== 'option') { + yield arg; + } + } + } + + const optionNode = this.node.args.find( + (arg) => !Array.isArray(arg) && arg.type === 'option' && arg.name === option + ); + + if (optionNode) { + yield* (optionNode as ESQLCommandOption).args; + } + } + + public *visitArguments( + input: ExpressionVisitorInput<Methods>, + option: '' | string = '' + ): Iterable<ExpressionVisitorOutput<Methods>> { + this.ctx.assertMethodExists('visitExpression'); + + const node = this.node; + + if (!isNodeWithArgs(node)) { + throw new Error('Node does not have arguments'); + } + + for (const arg of singleItems(this.arguments(option))) { + yield this.visitExpression(arg, input as any); + } + } + + public *visitSources( + input: UndefinedToVoid<Parameters<NonNullable<Methods['visitSourceExpression']>>[1]> + ): Iterable<ReturnType<NonNullable<Methods['visitSourceExpression']>>> { + this.ctx.assertMethodExists('visitSourceExpression'); + + for (const arg of singleItems(this.node.args)) { + if (arg.type === 'source') { + const sourceContext = new SourceExpressionVisitorContext(this.ctx, arg, this); + const result = this.ctx.methods.visitSourceExpression!(sourceContext, input); + + yield result; + } + } + } +} + +export class CommandOptionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends VisitorContext<Methods, Data, ESQLCommandOption> {} + +// FROM <sources> [ METADATA <columns> ] +export class FromCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> { + /** + * Visit the METADATA part of the FROM command. + * + * FROM <sources> [ METADATA <columns> ] + * + * @param input Input object to pass to all "visitColumn" children methods. + * @returns An iterable of results of all the "visitColumn" visitor methods. + */ + public *visitMetadataColumns( + input: UndefinedToVoid<Parameters<NonNullable<Methods['visitColumnExpression']>>[1]> + ): Iterable<ReturnType<NonNullable<Methods['visitColumnExpression']>>> { + this.ctx.assertMethodExists('visitColumnExpression'); + + let metadataOption: ESQLCommandOption | undefined; + + for (const arg of singleItems(this.node.args)) { + if (arg.type === 'option' && arg.name === 'metadata') { + metadataOption = arg; + break; + } + } + + if (!metadataOption) { + return; + } + + for (const arg of singleItems(metadataOption.args)) { + if (arg.type === 'column') { + const columnContext = new ColumnExpressionVisitorContext(this.ctx, arg, this); + const result = this.ctx.methods.visitColumnExpression!(columnContext, input); + + yield result; + } + } + } +} + +// LIMIT <literal> +export class LimitCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data> { + /** + * @returns The first numeric literal argument of the command. + */ + public numericLiteral(): ESQLIntegerLiteral | ESQLDecimalLiteral | undefined { + const arg = firstItem(this.node.args); + + if ( + arg && + arg.type === 'literal' && + (arg.literalType === 'integer' || arg.literalType === 'decimal') + ) { + return arg; + } + } + + /** + * @returns The value of the first numeric literal argument of the command. + */ + public numeric(): number | undefined { + const literal = this.numericLiteral(); + + return literal?.value; + } + + public setLimit(value: number): void { + const literalNode = Builder.numericLiteral({ value }); + + this.node.args = [literalNode]; + } +} + +// EXPLAIN <query> +export class ExplainCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// ROW <columns> +export class RowCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// METRICS +export class MetricsCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// SHOW <identifier> +export class ShowCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// META <identifier> +export class MetaCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// EVAL <columns> +export class EvalCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// STATS <columns> [ BY <columns> ] +export class StatsCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// INLINESTATS <columns> [ BY <columns> ] +export class InlineStatsCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// LOOKUP <source> ON <column> +export class LookupCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// KEEP <columns> +export class KeepCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// SORT <columns> +export class SortCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// WHERE <expression> +export class WhereCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// DROP <columns> +export class DropCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// RENAME <column> AS <column> +export class RenameCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// DISSECT <column> <string> [ APPEND_SEPARATOR = <string> ] +export class DissectCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// GROK <column> <string> +export class GrokCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// ENRICH <column> [ ON <column> ] [ WITH <columns> ] +export class EnrichCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// MV_EXPAND <column> +export class MvExpandCommandVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends CommandVisitorContext<Methods, Data, ESQLAstCommand> {} + +// Expressions ----------------------------------------------------------------- + +export class ExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData, + Node extends ESQLAstExpressionNode = ESQLAstExpressionNode +> extends VisitorContext<Methods, Data, Node> {} + +export class ColumnExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends VisitorContext<Methods, Data, ESQLColumn> {} + +export class SourceExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends VisitorContext<Methods, Data, ESQLSource> {} + +export class FunctionCallExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends VisitorContext<Methods, Data, ESQLFunction> {} + +export class LiteralExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData, + Node extends ESQLLiteral = ESQLLiteral +> extends ExpressionVisitorContext<Methods, Data, Node> {} + +export class ListLiteralExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData, + Node extends ESQLList = ESQLList +> extends ExpressionVisitorContext<Methods, Data, Node> { + public *visitElements( + input: ExpressionVisitorInput<Methods> + ): Iterable<ExpressionVisitorOutput<Methods>> { + this.ctx.assertMethodExists('visitExpression'); + + for (const value of this.node.values) { + yield this.visitExpression(value, input as any); + } + } +} + +export class TimeIntervalLiteralExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends ExpressionVisitorContext<Methods, Data, ESQLTimeInterval> { + format(): string { + const node = this.node; + + return `${node.quantity}${node.unit}`; + } +} + +export class InlineCastExpressionVisitorContext< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> extends ExpressionVisitorContext<Methods, Data, ESQLInlineCast> { + public visitValue(input: ExpressionVisitorInput<Methods>): ExpressionVisitorOutput<Methods> { + this.ctx.assertMethodExists('visitExpression'); + + const value = firstItem([this.node.value])!; + + return this.visitExpression(value, input as any); + } +} diff --git a/packages/kbn-esql-ast/src/visitor/global_visitor_context.ts b/packages/kbn-esql-ast/src/visitor/global_visitor_context.ts new file mode 100644 index 0000000000000..d05a4ce326eb7 --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/global_visitor_context.ts @@ -0,0 +1,467 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 contexts from './contexts'; +import type { + ESQLAstCommand, + ESQLColumn, + ESQLFunction, + ESQLInlineCast, + ESQLList, + ESQLLiteral, + ESQLSource, + ESQLTimeInterval, +} from '../types'; +import type * as types from './types'; + +export type SharedData = Record<string, unknown>; + +/** + * Global shared visitor context available to all visitors when visiting the AST. + * It contains the shared data, which can be accessed and modified by all visitors. + */ +export class GlobalVisitorContext< + Methods extends types.VisitorMethods = types.VisitorMethods, + Data extends SharedData = SharedData +> { + constructor( + /** + * Visitor methods, used internally by the visitor to traverse the AST. + * @protected + */ + public readonly methods: Methods, + + /** + * Shared data, which can be accessed and modified by all visitors. + */ + public data: Data + ) {} + + public assertMethodExists<K extends keyof types.VisitorMethods>(name: K) { + if (!this.methods[name]) { + throw new Error(`${name}() method is not defined`); + } + } + + private visitWithSpecificContext< + Method extends keyof types.VisitorMethods, + Context extends contexts.VisitorContext + >( + method: Method, + context: Context, + input: types.VisitorInput<Methods, Method> + ): types.VisitorOutput<Methods, Method> { + this.assertMethodExists(method); + return this.methods[method]!(context as any, input); + } + + // Command visiting ---------------------------------------------------------- + + public visitCommandGeneric( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitCommand'> + ): types.VisitorOutput<Methods, 'visitCommand'> { + this.assertMethodExists('visitCommand'); + + const context = new contexts.CommandVisitorContext(this, node, parent); + const output = this.methods.visitCommand!(context, input); + + return output; + } + + public visitCommand( + parent: contexts.VisitorContext | null, + commandNode: ESQLAstCommand, + input: types.CommandVisitorInput<Methods> + ): types.CommandVisitorOutput<Methods> { + switch (commandNode.name) { + case 'from': { + if (!this.methods.visitFromCommand) break; + return this.visitFromCommand(parent, commandNode, input as any); + } + case 'limit': { + if (!this.methods.visitLimitCommand) break; + return this.visitLimitCommand(parent, commandNode, input as any); + } + case 'explain': { + if (!this.methods.visitExplainCommand) break; + return this.visitExplainCommand(parent, commandNode, input as any); + } + case 'row': { + if (!this.methods.visitRowCommand) break; + return this.visitRowCommand(parent, commandNode, input as any); + } + // TODO: uncomment this when the command is implemented + // case 'metrics': { + // if (!this.methods.visitMetricsCommand) break; + // return this.visitMetricsCommand(parent, commandNode, input as any); + // } + case 'show': { + if (!this.methods.visitShowCommand) break; + return this.visitShowCommand(parent, commandNode, input as any); + } + case 'meta': { + if (!this.methods.visitMetaCommand) break; + return this.visitMetaCommand(parent, commandNode, input as any); + } + case 'eval': { + if (!this.methods.visitEvalCommand) break; + return this.visitEvalCommand(parent, commandNode, input as any); + } + case 'stats': { + if (!this.methods.visitStatsCommand) break; + return this.visitStatsCommand(parent, commandNode, input as any); + } + case 'inline_stats': { + if (!this.methods.visitInlineStatsCommand) break; + return this.visitInlineStatsCommand(parent, commandNode, input as any); + } + case 'lookup': { + if (!this.methods.visitLookupCommand) break; + return this.visitLookupCommand(parent, commandNode, input as any); + } + case 'keep': { + if (!this.methods.visitKeepCommand) break; + return this.visitKeepCommand(parent, commandNode, input as any); + } + case 'sort': { + if (!this.methods.visitSortCommand) break; + return this.visitSortCommand(parent, commandNode, input as any); + } + case 'where': { + if (!this.methods.visitWhereCommand) break; + return this.visitWhereCommand(parent, commandNode, input as any); + } + case 'drop': { + if (!this.methods.visitDropCommand) break; + return this.visitDropCommand(parent, commandNode, input as any); + } + case 'rename': { + if (!this.methods.visitRenameCommand) break; + return this.visitRenameCommand(parent, commandNode, input as any); + } + case 'dissect': { + if (!this.methods.visitDissectCommand) break; + return this.visitDissectCommand(parent, commandNode, input as any); + } + case 'grok': { + if (!this.methods.visitGrokCommand) break; + return this.visitGrokCommand(parent, commandNode, input as any); + } + case 'enrich': { + if (!this.methods.visitEnrichCommand) break; + return this.visitEnrichCommand(parent, commandNode, input as any); + } + case 'mv_expand': { + if (!this.methods.visitMvExpandCommand) break; + return this.visitMvExpandCommand(parent, commandNode, input as any); + } + } + return this.visitCommandGeneric(parent, commandNode, input as any); + } + + public visitFromCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitFromCommand'> + ): types.VisitorOutput<Methods, 'visitFromCommand'> { + const context = new contexts.FromCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitFromCommand', context, input); + } + + public visitLimitCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitLimitCommand'> + ): types.VisitorOutput<Methods, 'visitLimitCommand'> { + const context = new contexts.LimitCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitLimitCommand', context, input); + } + + public visitExplainCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitExplainCommand'> + ): types.VisitorOutput<Methods, 'visitExplainCommand'> { + const context = new contexts.ExplainCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitExplainCommand', context, input); + } + + public visitRowCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitRowCommand'> + ): types.VisitorOutput<Methods, 'visitRowCommand'> { + const context = new contexts.RowCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitRowCommand', context, input); + } + + public visitMetricsCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitMetricsCommand'> + ): types.VisitorOutput<Methods, 'visitMetricsCommand'> { + const context = new contexts.MetricsCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitMetricsCommand', context, input); + } + + public visitShowCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitShowCommand'> + ): types.VisitorOutput<Methods, 'visitShowCommand'> { + const context = new contexts.ShowCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitShowCommand', context, input); + } + + public visitMetaCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitMetaCommand'> + ): types.VisitorOutput<Methods, 'visitMetaCommand'> { + const context = new contexts.MetaCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitMetaCommand', context, input); + } + + public visitEvalCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitEvalCommand'> + ): types.VisitorOutput<Methods, 'visitEvalCommand'> { + const context = new contexts.EvalCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitEvalCommand', context, input); + } + + public visitStatsCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitStatsCommand'> + ): types.VisitorOutput<Methods, 'visitStatsCommand'> { + const context = new contexts.StatsCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitStatsCommand', context, input); + } + + public visitInlineStatsCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitInlineStatsCommand'> + ): types.VisitorOutput<Methods, 'visitInlineStatsCommand'> { + const context = new contexts.InlineStatsCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitInlineStatsCommand', context, input); + } + + public visitLookupCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitLookupCommand'> + ): types.VisitorOutput<Methods, 'visitLookupCommand'> { + const context = new contexts.LookupCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitLookupCommand', context, input); + } + + public visitKeepCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitKeepCommand'> + ): types.VisitorOutput<Methods, 'visitKeepCommand'> { + const context = new contexts.KeepCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitKeepCommand', context, input); + } + + public visitSortCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitSortCommand'> + ): types.VisitorOutput<Methods, 'visitSortCommand'> { + const context = new contexts.SortCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitSortCommand', context, input); + } + + public visitWhereCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitWhereCommand'> + ): types.VisitorOutput<Methods, 'visitWhereCommand'> { + const context = new contexts.WhereCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitWhereCommand', context, input); + } + + public visitDropCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitDropCommand'> + ): types.VisitorOutput<Methods, 'visitDropCommand'> { + const context = new contexts.DropCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitDropCommand', context, input); + } + + public visitRenameCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitRenameCommand'> + ): types.VisitorOutput<Methods, 'visitRenameCommand'> { + const context = new contexts.RenameCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitRenameCommand', context, input); + } + + public visitDissectCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitDissectCommand'> + ): types.VisitorOutput<Methods, 'visitDissectCommand'> { + const context = new contexts.DissectCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitDissectCommand', context, input); + } + + public visitGrokCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitGrokCommand'> + ): types.VisitorOutput<Methods, 'visitGrokCommand'> { + const context = new contexts.GrokCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitGrokCommand', context, input); + } + + public visitEnrichCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitEnrichCommand'> + ): types.VisitorOutput<Methods, 'visitEnrichCommand'> { + const context = new contexts.EnrichCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitEnrichCommand', context, input); + } + + public visitMvExpandCommand( + parent: contexts.VisitorContext | null, + node: ESQLAstCommand, + input: types.VisitorInput<Methods, 'visitMvExpandCommand'> + ): types.VisitorOutput<Methods, 'visitMvExpandCommand'> { + const context = new contexts.MvExpandCommandVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitMvExpandCommand', context, input); + } + + // Expression visiting ------------------------------------------------------- + + public visitExpressionGeneric( + parent: contexts.VisitorContext | null, + node: types.ESQLAstExpressionNode, + input: types.VisitorInput<Methods, 'visitExpression'> + ): types.VisitorOutput<Methods, 'visitExpression'> { + this.assertMethodExists('visitExpression'); + + const context = new contexts.ExpressionVisitorContext(this, node, parent); + const output = this.methods.visitExpression!(context, input); + + return output; + } + + public visitExpression( + parent: contexts.VisitorContext | null, + expressionNode: types.ESQLAstExpressionNode, + input: types.ExpressionVisitorInput<Methods> + ): types.ExpressionVisitorOutput<Methods> { + if (Array.isArray(expressionNode)) { + throw new Error('should not happen'); + } + switch (expressionNode.type) { + case 'column': { + if (!this.methods.visitColumnExpression) break; + return this.visitColumnExpression(parent, expressionNode, input as any); + } + case 'source': { + if (!this.methods.visitSourceExpression) break; + return this.visitSourceExpression(parent, expressionNode, input as any); + } + case 'function': { + if (!this.methods.visitFunctionCallExpression) break; + return this.visitFunctionCallExpression(parent, expressionNode, input as any); + } + case 'literal': { + if (!this.methods.visitLiteralExpression) break; + return this.visitLiteralExpression(parent, expressionNode, input as any); + } + case 'list': { + if (!this.methods.visitListLiteralExpression) break; + return this.visitListLiteralExpression(parent, expressionNode, input as any); + } + case 'timeInterval': { + if (!this.methods.visitTimeIntervalLiteralExpression) break; + return this.visitTimeIntervalLiteralExpression(parent, expressionNode, input as any); + } + case 'inlineCast': { + if (!this.methods.visitInlineCastExpression) break; + return this.visitInlineCastExpression(parent, expressionNode, input as any); + } + } + return this.visitExpressionGeneric(parent, expressionNode, input as any); + } + + public visitColumnExpression( + parent: contexts.VisitorContext | null, + node: ESQLColumn, + input: types.VisitorInput<Methods, 'visitColumnExpression'> + ): types.VisitorOutput<Methods, 'visitColumnExpression'> { + const context = new contexts.ColumnExpressionVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitColumnExpression', context, input); + } + + public visitSourceExpression( + parent: contexts.VisitorContext | null, + node: ESQLSource, + input: types.VisitorInput<Methods, 'visitSourceExpression'> + ): types.VisitorOutput<Methods, 'visitSourceExpression'> { + const context = new contexts.SourceExpressionVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitSourceExpression', context, input); + } + + public visitFunctionCallExpression( + parent: contexts.VisitorContext | null, + node: ESQLFunction, + input: types.VisitorInput<Methods, 'visitFunctionCallExpression'> + ): types.VisitorOutput<Methods, 'visitFunctionCallExpression'> { + const context = new contexts.FunctionCallExpressionVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitFunctionCallExpression', context, input); + } + + public visitLiteralExpression( + parent: contexts.VisitorContext | null, + node: ESQLLiteral, + input: types.VisitorInput<Methods, 'visitLiteralExpression'> + ): types.VisitorOutput<Methods, 'visitLiteralExpression'> { + const context = new contexts.LiteralExpressionVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitLiteralExpression', context, input); + } + + public visitListLiteralExpression( + parent: contexts.VisitorContext | null, + node: ESQLList, + input: types.VisitorInput<Methods, 'visitListLiteralExpression'> + ): types.VisitorOutput<Methods, 'visitListLiteralExpression'> { + const context = new contexts.ListLiteralExpressionVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitListLiteralExpression', context, input); + } + + public visitTimeIntervalLiteralExpression( + parent: contexts.VisitorContext | null, + node: ESQLTimeInterval, + input: types.VisitorInput<Methods, 'visitTimeIntervalLiteralExpression'> + ): types.VisitorOutput<Methods, 'visitTimeIntervalLiteralExpression'> { + const context = new contexts.TimeIntervalLiteralExpressionVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitTimeIntervalLiteralExpression', context, input); + } + + public visitInlineCastExpression( + parent: contexts.VisitorContext | null, + node: ESQLInlineCast, + input: types.VisitorInput<Methods, 'visitInlineCastExpression'> + ): types.VisitorOutput<Methods, 'visitInlineCastExpression'> { + const context = new contexts.InlineCastExpressionVisitorContext(this, node, parent); + return this.visitWithSpecificContext('visitInlineCastExpression', context, input); + } +} diff --git a/packages/kbn-esql-ast/src/visitor/index.ts b/packages/kbn-esql-ast/src/visitor/index.ts new file mode 100644 index 0000000000000..9e46616a50002 --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/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 './types'; +export { Visitor, type VisitorOptions } from './visitor'; +export { GlobalVisitorContext, type SharedData } from './global_visitor_context'; +export * from './contexts'; diff --git a/packages/kbn-esql-ast/src/visitor/types.ts b/packages/kbn-esql-ast/src/visitor/types.ts new file mode 100644 index 0000000000000..a8ec5e9bd1785 --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/types.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 and the Server Side Public License, v 1; you may not 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 { SharedData } from './global_visitor_context'; +import type * as ast from '../types'; +import type * as contexts from './contexts'; + +/** + * We don't have a dedicated "query" AST node, so - for now - we use the root + * array of commands as the "query" node. + */ +export type ESQLAstQueryNode = ast.ESQLAst; + +/** + * Represents an "expression" node in the AST. + */ +// export type ESQLAstExpressionNode = ESQLAstItem; +export type ESQLAstExpressionNode = ast.ESQLSingleAstItem; + +/** + * All possible AST nodes supported by the visitor. + */ +export type VisitorAstNode = ESQLAstQueryNode | ast.ESQLAstNode; + +export type Visitor<Ctx extends contexts.VisitorContext, Input = unknown, Output = unknown> = ( + ctx: Ctx, + input: Input +) => Output; + +/** + * Retrieves the `Input` of a {@link Visitor} function. + */ +export type VisitorInput< + Methods extends VisitorMethods, + Method extends keyof Methods +> = UndefinedToVoid<Parameters<EnsureFunction<NonNullable<Methods[Method]>>>[1]>; + +/** + * Retrieves the `Output` of a {@link Visitor} function. + */ +export type VisitorOutput< + Methods extends VisitorMethods, + Method extends keyof Methods +> = ReturnType<EnsureFunction<NonNullable<Methods[Method]>>>; + +/** + * Input that satisfies any expression visitor input constraints. + */ +export type ExpressionVisitorInput<Methods extends VisitorMethods> = AnyToVoid< + | VisitorInput<Methods, 'visitExpression'> & + VisitorInput<Methods, 'visitColumnExpression'> & + VisitorInput<Methods, 'visitSourceExpression'> & + VisitorInput<Methods, 'visitFunctionCallExpression'> & + VisitorInput<Methods, 'visitLiteralExpression'> & + VisitorInput<Methods, 'visitListLiteralExpression'> & + VisitorInput<Methods, 'visitTimeIntervalLiteralExpression'> & + VisitorInput<Methods, 'visitInlineCastExpression'> +>; + +/** + * Input that satisfies any expression visitor output constraints. + */ +export type ExpressionVisitorOutput<Methods extends VisitorMethods> = + | VisitorOutput<Methods, 'visitExpression'> + | VisitorOutput<Methods, 'visitColumnExpression'> + | VisitorOutput<Methods, 'visitSourceExpression'> + | VisitorOutput<Methods, 'visitFunctionCallExpression'> + | VisitorOutput<Methods, 'visitLiteralExpression'> + | VisitorOutput<Methods, 'visitListLiteralExpression'> + | VisitorOutput<Methods, 'visitTimeIntervalLiteralExpression'> + | VisitorOutput<Methods, 'visitInlineCastExpression'>; + +/** + * Input that satisfies any command visitor input constraints. + */ +export type CommandVisitorInput<Methods extends VisitorMethods> = AnyToVoid< + | VisitorInput<Methods, 'visitCommand'> & + VisitorInput<Methods, 'visitFromCommand'> & + VisitorInput<Methods, 'visitLimitCommand'> & + VisitorInput<Methods, 'visitExplainCommand'> & + VisitorInput<Methods, 'visitRowCommand'> & + VisitorInput<Methods, 'visitMetricsCommand'> & + VisitorInput<Methods, 'visitShowCommand'> & + VisitorInput<Methods, 'visitMetaCommand'> & + VisitorInput<Methods, 'visitEvalCommand'> & + VisitorInput<Methods, 'visitStatsCommand'> & + VisitorInput<Methods, 'visitInlineStatsCommand'> & + VisitorInput<Methods, 'visitLookupCommand'> & + VisitorInput<Methods, 'visitKeepCommand'> & + VisitorInput<Methods, 'visitSortCommand'> & + VisitorInput<Methods, 'visitWhereCommand'> & + VisitorInput<Methods, 'visitDropCommand'> & + VisitorInput<Methods, 'visitRenameCommand'> & + VisitorInput<Methods, 'visitDissectCommand'> & + VisitorInput<Methods, 'visitGrokCommand'> & + VisitorInput<Methods, 'visitEnrichCommand'> & + VisitorInput<Methods, 'visitMvExpandCommand'> +>; + +/** + * Input that satisfies any command visitor output constraints. + */ +export type CommandVisitorOutput<Methods extends VisitorMethods> = + | VisitorOutput<Methods, 'visitCommand'> + | VisitorOutput<Methods, 'visitFromCommand'> + | VisitorOutput<Methods, 'visitLimitCommand'> + | VisitorOutput<Methods, 'visitExplainCommand'> + | VisitorOutput<Methods, 'visitRowCommand'> + | VisitorOutput<Methods, 'visitMetricsCommand'> + | VisitorOutput<Methods, 'visitShowCommand'> + | VisitorOutput<Methods, 'visitMetaCommand'> + | VisitorOutput<Methods, 'visitEvalCommand'> + | VisitorOutput<Methods, 'visitStatsCommand'> + | VisitorOutput<Methods, 'visitInlineStatsCommand'> + | VisitorOutput<Methods, 'visitLookupCommand'> + | VisitorOutput<Methods, 'visitKeepCommand'> + | VisitorOutput<Methods, 'visitSortCommand'> + | VisitorOutput<Methods, 'visitWhereCommand'> + | VisitorOutput<Methods, 'visitDropCommand'> + | VisitorOutput<Methods, 'visitRenameCommand'> + | VisitorOutput<Methods, 'visitDissectCommand'> + | VisitorOutput<Methods, 'visitGrokCommand'> + | VisitorOutput<Methods, 'visitEnrichCommand'> + | VisitorOutput<Methods, 'visitMvExpandCommand'>; + +export interface VisitorMethods< + Visitors extends VisitorMethods = any, + Data extends SharedData = SharedData +> { + visitQuery?: Visitor<contexts.QueryVisitorContext<Visitors, Data>, any, any>; + visitCommand?: Visitor<contexts.CommandVisitorContext<Visitors, Data>, any, any>; + visitFromCommand?: Visitor<contexts.FromCommandVisitorContext<Visitors, Data>, any, any>; + visitLimitCommand?: Visitor<contexts.LimitCommandVisitorContext<Visitors, Data>, any, any>; + visitExplainCommand?: Visitor<contexts.ExplainCommandVisitorContext<Visitors, Data>, any, any>; + visitRowCommand?: Visitor<contexts.RowCommandVisitorContext<Visitors, Data>, any, any>; + visitMetricsCommand?: Visitor<contexts.MetricsCommandVisitorContext<Visitors, Data>, any, any>; + visitShowCommand?: Visitor<contexts.ShowCommandVisitorContext<Visitors, Data>, any, any>; + visitMetaCommand?: Visitor<contexts.MetaCommandVisitorContext<Visitors, Data>, any, any>; + visitEvalCommand?: Visitor<contexts.EvalCommandVisitorContext<Visitors, Data>, any, any>; + visitStatsCommand?: Visitor<contexts.StatsCommandVisitorContext<Visitors, Data>, any, any>; + visitInlineStatsCommand?: Visitor< + contexts.InlineStatsCommandVisitorContext<Visitors, Data>, + any, + any + >; + visitLookupCommand?: Visitor<contexts.LookupCommandVisitorContext<Visitors, Data>, any, any>; + visitKeepCommand?: Visitor<contexts.KeepCommandVisitorContext<Visitors, Data>, any, any>; + visitSortCommand?: Visitor<contexts.SortCommandVisitorContext<Visitors, Data>, any, any>; + visitWhereCommand?: Visitor<contexts.WhereCommandVisitorContext<Visitors, Data>, any, any>; + visitDropCommand?: Visitor<contexts.DropCommandVisitorContext<Visitors, Data>, any, any>; + visitRenameCommand?: Visitor<contexts.RenameCommandVisitorContext<Visitors, Data>, any, any>; + visitDissectCommand?: Visitor<contexts.DissectCommandVisitorContext<Visitors, Data>, any, any>; + visitGrokCommand?: Visitor<contexts.GrokCommandVisitorContext<Visitors, Data>, any, any>; + visitEnrichCommand?: Visitor<contexts.EnrichCommandVisitorContext<Visitors, Data>, any, any>; + visitMvExpandCommand?: Visitor<contexts.MvExpandCommandVisitorContext<Visitors, Data>, any, any>; + visitCommandOption?: Visitor<contexts.CommandOptionVisitorContext<Visitors, Data>, any, any>; + visitExpression?: Visitor<contexts.ExpressionVisitorContext<Visitors, Data>, any, any>; + visitSourceExpression?: Visitor< + contexts.SourceExpressionVisitorContext<Visitors, Data>, + any, + any + >; + visitColumnExpression?: Visitor< + contexts.ColumnExpressionVisitorContext<Visitors, Data>, + any, + any + >; + visitFunctionCallExpression?: Visitor< + contexts.FunctionCallExpressionVisitorContext<Visitors, Data>, + any, + any + >; + visitLiteralExpression?: Visitor< + contexts.LiteralExpressionVisitorContext<Visitors, Data>, + any, + any + >; + visitListLiteralExpression?: Visitor< + contexts.ListLiteralExpressionVisitorContext<Visitors, Data>, + any, + any + >; + visitTimeIntervalLiteralExpression?: Visitor< + contexts.TimeIntervalLiteralExpressionVisitorContext<Visitors, Data>, + any, + any + >; + visitInlineCastExpression?: Visitor< + contexts.InlineCastExpressionVisitorContext<Visitors, Data>, + any, + any + >; +} + +/** + * Maps any AST node to the corresponding visitor context. + */ +export type AstNodeToVisitorName<Node extends VisitorAstNode> = Node extends ESQLAstQueryNode + ? 'visitQuery' + : Node extends ast.ESQLCommand + ? 'visitCommand' + : Node extends ast.ESQLCommandOption + ? 'visitCommandOption' + : Node extends ast.ESQLSource + ? 'visitSourceExpression' + : Node extends ast.ESQLColumn + ? 'visitColumnExpression' + : Node extends ast.ESQLFunction + ? 'visitFunctionCallExpression' + : Node extends ast.ESQLLiteral + ? 'visitLiteralExpression' + : Node extends ast.ESQLList + ? 'visitListLiteralExpression' + : Node extends ast.ESQLTimeInterval + ? 'visitTimeIntervalLiteralExpression' + : Node extends ast.ESQLInlineCast + ? 'visitInlineCastExpression' + : never; + +/** + * Maps any AST node to the corresponding visitor context. + */ +export type AstNodeToVisitor< + Node extends VisitorAstNode, + Methods extends VisitorMethods = VisitorMethods +> = Methods[AstNodeToVisitorName<Node>]; + +/** + * Maps any AST node to its corresponding visitor context. + */ +export type AstNodeToContext< + Node extends VisitorAstNode, + Methods extends VisitorMethods = VisitorMethods +> = Parameters<EnsureFunction<AstNodeToVisitor<Node, Methods>>>[0]; + +/** + * Asserts that a type is a function. + */ +export type EnsureFunction<T> = T extends (...args: any[]) => any ? T : never; + +/** + * Converts `undefined` to `void`. This allows to make optional a function + * parameter or the return value. + */ +export type UndefinedToVoid<T> = T extends undefined ? void : T; + +/** Returns `Y` if `T` is `any`, or `N` otherwise. */ +export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N; + +/** Converts `any` type to `void`. */ +export type AnyToVoid<T> = IfAny<T, void, T>; diff --git a/packages/kbn-esql-ast/src/visitor/utils.ts b/packages/kbn-esql-ast/src/visitor/utils.ts new file mode 100644 index 0000000000000..d79cc6fd5ed1a --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/utils.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 { ESQLAstItem, ESQLSingleAstItem } from '../types'; + +/** + * Normalizes AST "item" list to only contain *single* items. + * + * @param items A list of single or nested items. + */ +export function* singleItems(items: Iterable<ESQLAstItem>): Iterable<ESQLSingleAstItem> { + for (const item of items) { + if (Array.isArray(item)) { + yield* singleItems(item); + } else { + yield item; + } + } +} + +/** + * Returns the first normalized "single item" from the "item" list. + * + * @param items Returns the first "single item" from the "item" list. + * @returns A "single item", if any. + */ +export const firstItem = (items: ESQLAstItem[]): ESQLSingleAstItem | undefined => { + for (const item of singleItems(items)) { + return item; + } +}; diff --git a/packages/kbn-esql-ast/src/visitor/visitor.ts b/packages/kbn-esql-ast/src/visitor/visitor.ts new file mode 100644 index 0000000000000..3956fe126723e --- /dev/null +++ b/packages/kbn-esql-ast/src/visitor/visitor.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 { GlobalVisitorContext, SharedData } from './global_visitor_context'; +import { QueryVisitorContext } from './contexts'; +import { VisitorContext } from './contexts'; +import type { + AstNodeToVisitorName, + EnsureFunction, + ESQLAstQueryNode, + UndefinedToVoid, + VisitorMethods, +} from './types'; + +export interface VisitorOptions< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> { + visitors?: Methods; + data?: Data; +} + +export class Visitor< + Methods extends VisitorMethods = VisitorMethods, + Data extends SharedData = SharedData +> { + public readonly ctx: GlobalVisitorContext<Methods, Data>; + + constructor(protected readonly options: VisitorOptions<Methods, Data> = {}) { + this.ctx = new GlobalVisitorContext<Methods, Data>( + options.visitors ?? ({} as Methods), + options.data ?? ({} as Data) + ); + } + + public visitors<NewMethods extends VisitorMethods<Methods, Data>>( + visitors: NewMethods + ): Visitor<Methods & NewMethods, Data> { + Object.assign(this.ctx.methods, visitors); + return this as any; + } + + public on< + K extends keyof VisitorMethods<Methods, Data>, + F extends VisitorMethods<Methods, Data>[K] + >(visitor: K, fn: F): Visitor<Methods & { [KK in K]: F }, Data> { + (this.ctx.methods as any)[visitor] = fn; + return this as any; + } + + /** + * Traverse any AST node given any visitor context. + * + * @param node AST node to traverse. + * @param ctx Traversal context. + * @returns Result of the visitor callback. + */ + public visit<Ctx extends VisitorContext<Methods, Data>>( + ctx: Ctx, + input: UndefinedToVoid<Parameters<NonNullable<Methods[AstNodeToVisitorName<Ctx['node']>]>>[1]> + ): ReturnType<EnsureFunction<Methods[AstNodeToVisitorName<Ctx['node']>]>> { + const node = ctx.node; + if (node instanceof Array) { + this.ctx.assertMethodExists('visitQuery'); + return this.ctx.methods.visitQuery!(ctx as any, input) as ReturnType< + NonNullable<Methods['visitQuery']> + >; + } else if (node && typeof node === 'object') { + switch (node.type) { + case 'command': + this.ctx.assertMethodExists('visitCommand'); + return this.ctx.methods.visitCommand!(ctx as any, input) as ReturnType< + NonNullable<Methods['visitCommand']> + >; + } + } + throw new Error(`Unsupported node type: ${typeof node}`); + } + + /** + * Traverse the root node of ES|QL query with default context. + * + * @param node Query node to traverse. + * @returns The result of the query visitor. + */ + public visitQuery( + node: ESQLAstQueryNode, + input: UndefinedToVoid<Parameters<NonNullable<Methods['visitQuery']>>[1]> + ) { + const queryContext = new QueryVisitorContext(this.ctx, node, null); + return this.visit(queryContext, input); + } +} diff --git a/packages/kbn-esql-ast/src/walker/helpers.ts b/packages/kbn-esql-ast/src/walker/helpers.ts new file mode 100644 index 0000000000000..73f0f8d09360c --- /dev/null +++ b/packages/kbn-esql-ast/src/walker/helpers.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 { ESQLProperNode } from '../types'; + +export type NodeMatchTemplateKey<V> = V | V[] | RegExp; +export type NodeMatchTemplate = { + [K in keyof ESQLProperNode]?: NodeMatchTemplateKey<ESQLProperNode[K]>; +}; + +/** + * Creates a predicate function which matches a single AST node against a + * template object. The template object should have the same keys as the + * AST node, and the values should be: + * + * - An array matches if the node key is in the array. + * - A RegExp matches if the node key matches the RegExp. + * - Any other value matches if the node key is triple-equal to the value. + * + * @param template Template from which to create a predicate function. + * @returns A predicate function that matches nodes against the template. + */ +export const templateToPredicate = ( + template: NodeMatchTemplate +): ((node: ESQLProperNode) => boolean) => { + const keys = Object.keys(template) as Array<keyof ESQLProperNode>; + const predicate = (child: ESQLProperNode) => { + for (const key of keys) { + const matcher = template[key]; + if (matcher instanceof Array) { + if (!(matcher as any[]).includes(child[key])) { + return false; + } + } else if (matcher instanceof RegExp) { + if (!matcher.test(String(child[key]))) { + return false; + } + } else if (child[key] !== matcher) { + return false; + } + } + + return true; + }; + + return predicate; +}; diff --git a/packages/kbn-esql-ast/src/walker/walker.test.ts b/packages/kbn-esql-ast/src/walker/walker.test.ts index 2bc666a1e0421..59375b275b162 100644 --- a/packages/kbn-esql-ast/src/walker/walker.test.ts +++ b/packages/kbn-esql-ast/src/walker/walker.test.ts @@ -81,6 +81,24 @@ describe('structurally can walk all nodes', () => { ]); }); + test('"visitAny" can capture command nodes', () => { + const { ast } = getAstAndSyntaxErrors('FROM index | STATS a = 123 | WHERE 123 | LIMIT 10'); + const commands: ESQLCommand[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'command') commands.push(node); + }, + }); + + expect(commands.map(({ name }) => name).sort()).toStrictEqual([ + 'from', + 'limit', + 'stats', + 'where', + ]); + }); + describe('command options', () => { test('can visit command options', () => { const { ast } = getAstAndSyntaxErrors('FROM index METADATA _index'); @@ -93,19 +111,47 @@ describe('structurally can walk all nodes', () => { expect(options.length).toBe(1); expect(options[0].name).toBe('metadata'); }); + + test('"visitAny" can capture an options node', () => { + const { ast } = getAstAndSyntaxErrors('FROM index METADATA _index'); + const options: ESQLCommandOption[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'option') options.push(node); + }, + }); + + expect(options.length).toBe(1); + expect(options[0].name).toBe('metadata'); + }); }); describe('command mode', () => { test('visits "mode" nodes', () => { const { ast } = getAstAndSyntaxErrors('FROM index | ENRICH a:b'); - const options: ESQLCommandMode[] = []; + const modes: ESQLCommandMode[] = []; walk(ast, { - visitCommandMode: (opt) => options.push(opt), + visitCommandMode: (opt) => modes.push(opt), }); - expect(options.length).toBe(1); - expect(options[0].name).toBe('a'); + expect(modes.length).toBe(1); + expect(modes[0].name).toBe('a'); + }); + + test('"visitAny" can capture a mode node', () => { + const { ast } = getAstAndSyntaxErrors('FROM index | ENRICH a:b'); + const modes: ESQLCommandMode[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'mode') modes.push(node); + }, + }); + + expect(modes.length).toBe(1); + expect(modes[0].name).toBe('a'); }); }); @@ -123,6 +169,20 @@ describe('structurally can walk all nodes', () => { expect(sources[0].name).toBe('index'); }); + test('"visitAny" can capture a source node', () => { + const { ast } = getAstAndSyntaxErrors('FROM index'); + const sources: ESQLSource[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'source') sources.push(node); + }, + }); + + expect(sources.length).toBe(1); + expect(sources[0].name).toBe('index'); + }); + test('iterates through all sources', () => { const { ast } = getAstAndSyntaxErrors('METRICS index, index2, index3, index4'); const sources: ESQLSource[] = []; @@ -142,7 +202,7 @@ describe('structurally can walk all nodes', () => { }); describe('columns', () => { - test('can through a single column', () => { + test('can walk through a single column', () => { const query = 'ROW x = 1'; const { ast } = getAstAndSyntaxErrors(query); const columns: ESQLColumn[] = []; @@ -159,6 +219,25 @@ describe('structurally can walk all nodes', () => { ]); }); + test('"visitAny" can capture a column', () => { + const query = 'ROW x = 1'; + const { ast } = getAstAndSyntaxErrors(query); + const columns: ESQLColumn[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'column') columns.push(node); + }, + }); + + expect(columns).toMatchObject([ + { + type: 'column', + name: 'x', + }, + ]); + }); + test('can walk through multiple columns', () => { const query = 'FROM index | STATS a = 123, b = 456'; const { ast } = getAstAndSyntaxErrors(query); @@ -181,6 +260,52 @@ describe('structurally can walk all nodes', () => { }); }); + describe('functions', () => { + test('can walk through functions', () => { + const query = 'FROM a | STATS fn(1), agg(true)'; + const { ast } = getAstAndSyntaxErrors(query); + const nodes: ESQLFunction[] = []; + + walk(ast, { + visitFunction: (node) => nodes.push(node), + }); + + expect(nodes).toMatchObject([ + { + type: 'function', + name: 'fn', + }, + { + type: 'function', + name: 'agg', + }, + ]); + }); + + test('"visitAny" can capture function nodes', () => { + const query = 'FROM a | STATS fn(1), agg(true)'; + const { ast } = getAstAndSyntaxErrors(query); + const nodes: ESQLFunction[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'function') nodes.push(node); + }, + }); + + expect(nodes).toMatchObject([ + { + type: 'function', + name: 'fn', + }, + { + type: 'function', + name: 'agg', + }, + ]); + }); + }); + describe('literals', () => { test('can walk a single literal', () => { const query = 'ROW x = 1'; @@ -211,7 +336,7 @@ describe('structurally can walk all nodes', () => { expect(columns).toMatchObject([ { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '123', }, { @@ -244,7 +369,7 @@ describe('structurally can walk all nodes', () => { expect(columns).toMatchObject([ { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '1', }, { @@ -264,7 +389,7 @@ describe('structurally can walk all nodes', () => { }, { type: 'literal', - literalType: 'number', + literalType: 'decimal', name: '3.14', }, ]); @@ -288,12 +413,12 @@ describe('structurally can walk all nodes', () => { values: [ { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '1', }, { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '2', }, ], @@ -301,6 +426,20 @@ describe('structurally can walk all nodes', () => { ]); }); + test('"visitAny" can capture a list literal', () => { + const query = 'ROW x = [1, 2]'; + const { ast } = getAstAndSyntaxErrors(query); + const lists: ESQLList[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'list') lists.push(node); + }, + }); + + expect(lists.length).toBe(1); + }); + test('can walk plain literals inside list literal', () => { const query = 'ROW x = [1, 2] + [3.3]'; const { ast } = getAstAndSyntaxErrors(query); @@ -318,12 +457,12 @@ describe('structurally can walk all nodes', () => { values: [ { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '1', }, { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '2', }, ], @@ -333,7 +472,7 @@ describe('structurally can walk all nodes', () => { values: [ { type: 'literal', - literalType: 'number', + literalType: 'decimal', name: '3.3', }, ], @@ -342,17 +481,17 @@ describe('structurally can walk all nodes', () => { expect(literals).toMatchObject([ { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '1', }, { type: 'literal', - literalType: 'number', + literalType: 'integer', name: '2', }, { type: 'literal', - literalType: 'number', + literalType: 'decimal', name: '3.3', }, ]); @@ -492,7 +631,6 @@ describe('structurally can walk all nodes', () => { test('can visit time interval nodes', () => { const query = 'FROM index | STATS a = 123 BY 1h'; const { ast } = getAstAndSyntaxErrors(query); - const intervals: ESQLTimeInterval[] = []; walk(ast, { @@ -507,11 +645,48 @@ describe('structurally can walk all nodes', () => { }, ]); }); + + test('"visitAny" can capture time interval expressions', () => { + const query = 'FROM index | STATS a = 123 BY 1h'; + const { ast } = getAstAndSyntaxErrors(query); + const intervals: ESQLTimeInterval[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'timeInterval') intervals.push(node); + }, + }); + + expect(intervals).toMatchObject([ + { + type: 'timeInterval', + quantity: 1, + unit: 'h', + }, + ]); + }); + + test('"visitAny" does not capture time interval node if type-specific callback provided', () => { + const query = 'FROM index | STATS a = 123 BY 1h'; + const { ast } = getAstAndSyntaxErrors(query); + const intervals1: ESQLTimeInterval[] = []; + const intervals2: ESQLTimeInterval[] = []; + + walk(ast, { + visitTimeIntervalLiteral: (node) => intervals1.push(node), + visitAny: (node) => { + if (node.type === 'timeInterval') intervals2.push(node); + }, + }); + + expect(intervals1.length).toBe(1); + expect(intervals2.length).toBe(0); + }); }); describe('cast expression', () => { test('can visit cast expression', () => { - const query = 'FROM index | STATS a = 123::number'; + const query = 'FROM index | STATS a = 123::integer'; const { ast } = getAstAndSyntaxErrors(query); const casts: ESQLInlineCast[] = []; @@ -523,10 +698,34 @@ describe('structurally can walk all nodes', () => { expect(casts).toMatchObject([ { type: 'inlineCast', - castType: 'number', + castType: 'integer', + value: { + type: 'literal', + literalType: 'integer', + value: 123, + }, + }, + ]); + }); + + test('"visitAny" can capture cast expression', () => { + const query = 'FROM index | STATS a = 123::integer'; + const { ast } = getAstAndSyntaxErrors(query); + const casts: ESQLInlineCast[] = []; + + walk(ast, { + visitAny: (node) => { + if (node.type === 'inlineCast') casts.push(node); + }, + }); + + expect(casts).toMatchObject([ + { + type: 'inlineCast', + castType: 'integer', value: { type: 'literal', - literalType: 'number', + literalType: 'integer', value: 123, }, }, @@ -576,7 +775,7 @@ describe('Walker.commands()', () => { }); }); -describe('Walker.params', () => { +describe('Walker.params()', () => { test('can collect all params', () => { const query = 'ROW x = ?'; const { ast } = getAstAndSyntaxErrors(query); @@ -613,10 +812,195 @@ describe('Walker.params', () => { }); }); +describe('Walker.find()', () => { + test('can find a bucket() function', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const fn = Walker.find( + getAstAndSyntaxErrors(query).ast!, + (node) => node.type === 'function' && node.name === 'bucket' + ); + + expect(fn).toMatchObject({ + type: 'function', + name: 'bucket', + }); + }); + + test('finds the first "fn" function', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const fn = Walker.find( + getAstAndSyntaxErrors(query).ast!, + (node) => node.type === 'function' && node.name === 'fn' + ); + + expect(fn).toMatchObject({ + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }); + }); +}); + +describe('Walker.findAll()', () => { + test('find all "fn" functions', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const list = Walker.findAll( + getAstAndSyntaxErrors(query).ast!, + (node) => node.type === 'function' && node.name === 'fn' + ); + + expect(list).toMatchObject([ + { + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + { + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 2, + }, + ], + }, + ]); + }); +}); + +describe('Walker.match()', () => { + test('can find a bucket() function', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const fn = Walker.match(getAstAndSyntaxErrors(query).ast!, { + type: 'function', + name: 'bucket', + }); + + expect(fn).toMatchObject({ + type: 'function', + name: 'bucket', + }); + }); + + test('finds the first "fn" function', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const fn = Walker.match(getAstAndSyntaxErrors(query).ast!, { type: 'function', name: 'fn' }); + + expect(fn).toMatchObject({ + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }); + }); +}); + +describe('Walker.matchAll()', () => { + test('find all "fn" functions', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const list = Walker.matchAll(getAstAndSyntaxErrors(query).ast!, { + type: 'function', + name: 'fn', + }); + + expect(list).toMatchObject([ + { + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + { + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 2, + }, + ], + }, + ]); + }); + + test('find all "fn" and "agg" functions', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const list = Walker.matchAll(getAstAndSyntaxErrors(query).ast!, { + type: 'function', + name: ['fn', 'agg'], + }); + + expect(list).toMatchObject([ + { + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 1, + }, + ], + }, + { + type: 'function', + name: 'fn', + args: [ + { + type: 'literal', + value: 2, + }, + ], + }, + { + type: 'function', + name: 'agg', + }, + ]); + }); + + test('find all functions which start with "b" or "a"', () => { + const query = 'FROM b | STATS var0 = bucket(bytes, 1 hour), fn(1), fn(2), agg(true)'; + const list = Walker.matchAll(getAstAndSyntaxErrors(query).ast!, { + type: 'function', + name: /^a|b/i, + }); + + expect(list).toMatchObject([ + { + type: 'function', + name: 'bucket', + }, + { + type: 'function', + name: 'agg', + }, + ]); + }); +}); + describe('Walker.hasFunction()', () => { test('can find assignment expression', () => { - const query1 = 'METRICS source bucket(bytes, 1 hour)'; - const query2 = 'METRICS source var0 = bucket(bytes, 1 hour)'; + const query1 = 'FROM a | STATS bucket(bytes, 1 hour)'; + const query2 = 'FROM b | STATS var0 = bucket(bytes, 1 hour)'; const has1 = Walker.hasFunction(getAstAndSyntaxErrors(query1).ast!, '='); const has2 = Walker.hasFunction(getAstAndSyntaxErrors(query2).ast!, '='); diff --git a/packages/kbn-esql-ast/src/walker/walker.ts b/packages/kbn-esql-ast/src/walker/walker.ts index 20e052d211fe1..e6ed54517435e 100644 --- a/packages/kbn-esql-ast/src/walker/walker.ts +++ b/packages/kbn-esql-ast/src/walker/walker.ts @@ -19,11 +19,13 @@ import type { ESQLList, ESQLLiteral, ESQLParamLiteral, + ESQLProperNode, ESQLSingleAstItem, ESQLSource, ESQLTimeInterval, ESQLUnknownItem, } from '../types'; +import { NodeMatchTemplate, templateToPredicate } from './helpers'; type Node = ESQLAstNode | ESQLAstNode[]; @@ -40,6 +42,13 @@ export interface WalkerOptions { visitTimeIntervalLiteral?: (node: ESQLTimeInterval) => void; visitInlineCast?: (node: ESQLInlineCast) => void; visitUnknown?: (node: ESQLUnknownItem) => void; + + /** + * Called for any node type that does not have a specific visitor. + * + * @param node Any valid AST node. + */ + visitAny?: (node: ESQLProperNode) => void; } export type WalkerAstNode = ESQLAstNode | ESQLAstNode[]; @@ -102,6 +111,82 @@ export class Walker { return params; }; + /** + * Finds and returns the first node that matches the search criteria. + * + * @param node AST node to start the search from. + * @param predicate A function that returns true if the node matches the search criteria. + * @returns The first node that matches the search criteria. + */ + public static readonly find = ( + node: WalkerAstNode, + predicate: (node: ESQLProperNode) => boolean + ): ESQLProperNode | undefined => { + let found: ESQLProperNode | undefined; + Walker.walk(node, { + visitAny: (child) => { + if (!found && predicate(child)) { + found = child; + } + }, + }); + return found; + }; + + /** + * Finds and returns all nodes that match the search criteria. + * + * @param node AST node to start the search from. + * @param predicate A function that returns true if the node matches the search criteria. + * @returns All nodes that match the search criteria. + */ + public static readonly findAll = ( + node: WalkerAstNode, + predicate: (node: ESQLProperNode) => boolean + ): ESQLProperNode[] => { + const list: ESQLProperNode[] = []; + Walker.walk(node, { + visitAny: (child) => { + if (predicate(child)) { + list.push(child); + } + }, + }); + return list; + }; + + /** + * Matches a single node against a template object. Returns the first node + * that matches the template. + * + * @param node AST node to match against the template. + * @param template Template object to match against the node. + * @returns The first node that matches the template + */ + public static readonly match = ( + node: WalkerAstNode, + template: NodeMatchTemplate + ): ESQLProperNode | undefined => { + const predicate = templateToPredicate(template); + return Walker.find(node, predicate); + }; + + /** + * Matches all nodes against a template object. Returns all nodes that match + * the template. + * + * @param node AST node to match against the template. + * @param template Template object to match against the node. + * @returns All nodes that match the template + */ + public static readonly matchAll = ( + node: WalkerAstNode, + template: NodeMatchTemplate + ): ESQLProperNode[] => { + const predicate = templateToPredicate(template); + return Walker.findAll(node, predicate); + }; + /** * Finds the first function that matches the predicate. * @@ -161,7 +246,8 @@ export class Walker { } public walkCommand(node: ESQLAstCommand): void { - this.options.visitCommand?.(node); + const { options } = this; + (options.visitCommand ?? options.visitAny)?.(node); switch (node.name) { default: { this.walk(node.args); @@ -171,7 +257,8 @@ export class Walker { } public walkOption(node: ESQLCommandOption): void { - this.options.visitCommandOption?.(node); + const { options } = this; + (options.visitCommandOption ?? options.visitAny)?.(node); for (const child of node.args) { this.walkAstItem(child); } @@ -188,11 +275,13 @@ export class Walker { } public walkMode(node: ESQLCommandMode): void { - this.options.visitCommandMode?.(node); + const { options } = this; + (options.visitCommandMode ?? options.visitAny)?.(node); } public walkListLiteral(node: ESQLList): void { - this.options.visitListLiteral?.(node); + const { options } = this; + (options.visitListLiteral ?? options.visitAny)?.(node); for (const value of node.values) { this.walkAstItem(value); } @@ -215,11 +304,11 @@ export class Walker { break; } case 'source': { - options.visitSource?.(node); + (options.visitSource ?? options.visitAny)?.(node); break; } case 'column': { - options.visitColumn?.(node); + (options.visitColumn ?? options.visitAny)?.(node); break; } case 'literal': { @@ -231,22 +320,23 @@ export class Walker { break; } case 'timeInterval': { - options.visitTimeIntervalLiteral?.(node); + (options.visitTimeIntervalLiteral ?? options.visitAny)?.(node); break; } case 'inlineCast': { - options.visitInlineCast?.(node); + (options.visitInlineCast ?? options.visitAny)?.(node); break; } case 'unknown': { - options.visitUnknown?.(node); + (options.visitUnknown ?? options.visitAny)?.(node); break; } } } public walkFunction(node: ESQLFunction): void { - this.options.visitFunction?.(node); + const { options } = this; + (options.visitFunction ?? options.visitAny)?.(node); const args = node.args; const length = args.length; for (let i = 0; i < length; i++) { diff --git a/packages/kbn-esql-utils/constants.ts b/packages/kbn-esql-utils/constants.ts index eae1004e6da7d..b13469938dcd9 100644 --- a/packages/kbn-esql-utils/constants.ts +++ b/packages/kbn-esql-utils/constants.ts @@ -6,3 +6,4 @@ * Side Public License, v 1. */ export const ENABLE_ESQL = 'enableESQL'; +export const FEEDBACK_LINK = 'https://ela.st/esql-feedback'; diff --git a/packages/kbn-esql-utils/index.ts b/packages/kbn-esql-utils/index.ts index b3d8698ea7075..ecbd0f7728f38 100644 --- a/packages/kbn-esql-utils/index.ts +++ b/packages/kbn-esql-utils/index.ts @@ -27,4 +27,4 @@ export { TextBasedLanguages, } from './src'; -export { ENABLE_ESQL } from './constants'; +export { ENABLE_ESQL, FEEDBACK_LINK } from './constants'; diff --git a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts index 45aac1344725d..26d3b1c0c4a89 100644 --- a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts +++ b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.test.ts @@ -5,11 +5,98 @@ * 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 { getInitialESQLQuery } from './get_initial_esql_query'; +const getDataView = (name: string, dataViewFields: DataView['fields'], timeFieldName?: string) => { + dataViewFields.getByName = (fieldName: string) => { + return dataViewFields.find((field) => field.name === fieldName); + }; + return { + id: `${name}-id`, + title: name, + metaFields: ['_index', '_score'], + fields: dataViewFields, + type: 'default', + getName: () => name, + getIndexPattern: () => name, + getFieldByName: jest.fn((fieldName: string) => dataViewFields.getByName(fieldName)), + timeFieldName, + isPersisted: () => true, + toSpec: () => ({}), + toMinimalSpec: () => ({}), + } as unknown as DataView; +}; + describe('getInitialESQLQuery', () => { - it('should work correctly', () => { - expect(getInitialESQLQuery('logs*')).toBe('FROM logs* | LIMIT 10'); + it('should NOT add the where clause if there is @timestamp in the index', () => { + const fields = [ + { + name: '@timestamp', + displayName: '@timestamp', + type: 'date', + scripted: false, + filterable: true, + aggregatable: true, + sortable: true, + }, + { + name: 'message', + displayName: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + ] as DataView['fields']; + const dataView = getDataView('logs*', fields, '@timestamp'); + expect(getInitialESQLQuery(dataView)).toBe('FROM logs* | LIMIT 10'); + }); + + it('should NOT add the where clause if there is @timestamp in the index although the dataview timefielName is different', () => { + const fields = [ + { + name: '@timestamp', + displayName: '@timestamp', + type: 'date', + scripted: false, + filterable: true, + aggregatable: true, + sortable: true, + }, + { + name: 'message', + displayName: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + ] as DataView['fields']; + const dataView = getDataView('logs*', fields, 'timestamp'); + expect(getInitialESQLQuery(dataView)).toBe('FROM logs* | LIMIT 10'); + }); + + it('should append a where clause correctly if there is no @timestamp in the index fields', () => { + const fields = [ + { + name: '@custom_timestamp', + displayName: '@custom_timestamp', + type: 'date', + scripted: false, + filterable: true, + aggregatable: true, + sortable: true, + }, + { + name: 'message', + displayName: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + ] as DataView['fields']; + const dataView = getDataView('logs*', fields, '@custom_timestamp'); + expect(getInitialESQLQuery(dataView)).toBe( + 'FROM logs* | WHERE @custom_timestamp >= ?start AND @custom_timestamp <= ?end | LIMIT 10' + ); }); }); diff --git a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts index 302f3c364f1a6..1d78432b14269 100644 --- a/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts +++ b/packages/kbn-esql-utils/src/utils/get_initial_esql_query.ts @@ -5,11 +5,20 @@ * 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'; /** - * Builds an ES|QL query for the provided index or index pattern - * @param indexOrIndexPattern + * Builds an ES|QL query for the provided dataView + * If there is @timestamp field in the index, we don't add the WHERE clause + * If there is no @timestamp and there is a dataView timeFieldName, we add the WHERE clause with the timeFieldName + * @param dataView */ -export function getInitialESQLQuery(indexOrIndexPattern: string): string { - return `FROM ${indexOrIndexPattern} | LIMIT 10`; +export function getInitialESQLQuery(dataView: DataView): string { + const hasAtTimestampField = dataView?.fields?.getByName?.('@timestamp')?.type === 'date'; + const timeFieldName = dataView?.timeFieldName; + const filterByTimeParams = + !hasAtTimestampField && timeFieldName + ? ` | WHERE ${timeFieldName} >= ?start AND ${timeFieldName} <= ?end` + : ''; + return `FROM ${dataView.getIndexPattern()}${filterByTimeParams} | LIMIT 10`; } diff --git a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts index aa92c7bd024d5..2f4a9c7cf4c33 100644 --- a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts +++ b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts @@ -12,7 +12,6 @@ import { join } from 'path'; import _ from 'lodash'; import type { RecursivePartial } from '@kbn/utility-types'; import { FunctionDefinition } from '../src/definitions/types'; -import { esqlToKibanaType } from '../src/shared/esql_to_kibana_type'; const aliasTable: Record<string, string[]> = { to_version: ['to_ver'], @@ -26,7 +25,7 @@ const aliasTable: Record<string, string[]> = { const aliases = new Set(Object.values(aliasTable).flat()); const evalSupportedCommandsAndOptions = { - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], }; @@ -62,7 +61,7 @@ const validateLogFunctions = `(fnDef: ESQLFunction) => { // do not really care here about the base and field // just need to check both values are not negative for (const arg of fnDef.args) { - if (isLiteralItem(arg) && arg.value < 0) { + if (isLiteralItem(arg) && Number(arg.value) < 0) { messages.push({ type: 'warning' as const, code: 'logOfNegativeValue', @@ -214,12 +213,14 @@ const functionEnrichments: Record<string, RecursivePartial<FunctionDefinition>> ], }, mv_sort: { - signatures: new Array(6).fill({ + signatures: new Array(9).fill({ params: [{}, { literalOptions: ['asc', 'desc'] }], }), }, }; +const convertDateTime = (s: string) => (s === 'datetime' ? 'date' : s); + /** * Builds a function definition object from a row of the "meta functions" table * @param {Array<any>} value — the row of the "meta functions" table, corresponding to a single function definition @@ -240,10 +241,10 @@ function getFunctionDefinition(ESFunctionDefinition: Record<string, any>): Funct ...signature, params: signature.params.map((param: any) => ({ ...param, - type: esqlToKibanaType(param.type), + type: convertDateTime(param.type), description: undefined, })), - returnType: esqlToKibanaType(signature.returnType), + returnType: convertDateTime(signature.returnType), variadic: undefined, // we don't support variadic property minParams: signature.variadic ? signature.params.filter((param: any) => !param.optional).length diff --git a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_validation_tests.ts b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_validation_tests.ts index 8d6394fe96af6..f431d3cddb2c5 100644 --- a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_validation_tests.ts +++ b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_validation_tests.ts @@ -18,15 +18,17 @@ import { getFunctionSignatures } from '../src/definitions/helpers'; import { timeUnits } from '../src/definitions/literals'; import { nonNullable } from '../src/shared/helpers'; import { - SupportedFieldType, + SupportedDataType, FunctionDefinition, - supportedFieldTypes, - isSupportedFieldType, + dataTypes, + isSupportedDataType, + fieldTypes, } from '../src/definitions/types'; import { FUNCTION_DESCRIBE_BLOCK_NAME } from '../src/validation/function_describe_block_name'; import { getMaxMinNumberOfParams } from '../src/validation/helpers'; +import { ESQL_NUMBER_TYPES, isNumericType, isStringType } from '../src/shared/esql_types'; -export const fieldNameFromType = (type: SupportedFieldType) => `${camelCase(type)}Field`; +export const fieldNameFromType = (type: SupportedDataType) => `${camelCase(type)}Field`; function main() { const testCasesByFunction: Map<string, Map<string, string[]>> = new Map(); @@ -141,8 +143,8 @@ function generateImplicitDateCastingTestsForFunction( const allSignaturesWithDateParams = definition.signatures.filter((signature) => signature.params.some( (param, i) => - param.type === 'date' && - !definition.signatures.some((def) => getParamAtPosition(def, i)?.type === 'string') // don't count parameters that already accept a string + (param.type === 'date' || param.type === 'date_period') && + !definition.signatures.some((def) => isStringType(getParamAtPosition(def, i)?.type)) // don't count parameters that already accept a string ) ); @@ -300,8 +302,8 @@ function generateWhereCommandTestsForEvalFunction( // TODO: not sure why there's this constraint... const supportedFunction = signatures.some( ({ returnType, params }) => - ['number', 'string'].includes(returnType) && - params.every(({ type }) => ['number', 'string'].includes(type)) + [...ESQL_NUMBER_TYPES, 'string'].includes(returnType as string) && + params.every(({ type }) => [...ESQL_NUMBER_TYPES, 'string'].includes(type as string)) ); if (!supportedFunction) { @@ -311,12 +313,12 @@ function generateWhereCommandTestsForEvalFunction( const supportedSignatures = signatures.filter(({ returnType }) => // TODO — not sure why the tests have this limitation... seems like any type // that can be part of a boolean expression should be allowed in a where clause - ['number', 'string'].includes(returnType) + [...ESQL_NUMBER_TYPES, 'string'].includes(returnType as string) ); for (const { params, returnType, ...restSign } of supportedSignatures) { const correctMapping = getFieldMapping(params); testCases.set( - `from a_index | where ${returnType !== 'number' ? 'length(' : ''}${ + `from a_index | where ${!isNumericType(returnType) ? 'length(' : ''}${ // hijacking a bit this function to produce a function call getFunctionSignatures( { @@ -326,7 +328,7 @@ function generateWhereCommandTestsForEvalFunction( }, { withTypes: false } )[0].declaration - }${returnType !== 'number' ? ')' : ''} > 0`, + }${!isNumericType(returnType) ? ')' : ''} > 0`, [] ); @@ -337,7 +339,7 @@ function generateWhereCommandTestsForEvalFunction( supportedTypesAndFieldNames ); testCases.set( - `from a_index | where ${returnType !== 'number' ? 'length(' : ''}${ + `from a_index | where ${!isNumericType(returnType) ? 'length(' : ''}${ // hijacking a bit this function to produce a function call getFunctionSignatures( { @@ -347,7 +349,7 @@ function generateWhereCommandTestsForEvalFunction( }, { withTypes: false } )[0].declaration - }${returnType !== 'number' ? ')' : ''} > 0`, + }${!isNumericType(returnType) ? ')' : ''} > 0`, expectedErrors ); } @@ -357,7 +359,7 @@ function generateWhereCommandTestsForAggFunction( { name, alias, signatures, ...defRest }: FunctionDefinition, testCases: Map<string, string[]> ) { - // statsSignatures.some(({ returnType, params }) => ['number'].includes(returnType)) + // statsSignatures.some(({ returnType, params }) => [...ESQL_NUMBER_TYPES].includes(returnType)) for (const { params, ...signRest } of signatures) { const fieldMapping = getFieldMapping(params); @@ -542,7 +544,7 @@ function generateEvalCommandTestsForEvalFunction( signatureWithGreatestNumberOfParams.params ).concat({ name: 'extraArg', - type: 'number', + type: 'integer', }); // get the expected args from the first signature in case of errors @@ -660,7 +662,7 @@ function generateStatsCommandTestsForAggFunction( testCases.set(`from a_index | stats var = ${correctSignature}`, []); testCases.set(`from a_index | stats ${correctSignature}`, []); - if (signRest.returnType === 'number') { + if (isNumericType(signRest.returnType)) { testCases.set(`from a_index | stats var = round(${correctSignature})`, []); testCases.set(`from a_index | stats round(${correctSignature})`, []); testCases.set( @@ -713,8 +715,8 @@ function generateStatsCommandTestsForAggFunction( } // test only numeric functions for now - if (params[0].type === 'number') { - const nestedBuiltin = 'numberField / 2'; + if (isNumericType(params[0].type)) { + const nestedBuiltin = 'doubleField / 2'; const fieldMappingWithNestedBuiltinFunctions = getFieldMapping(params); fieldMappingWithNestedBuiltinFunctions[0].name = nestedBuiltin; @@ -726,16 +728,16 @@ function generateStatsCommandTestsForAggFunction( }, { withTypes: false } )[0].declaration; - // from a_index | STATS aggFn( numberField / 2 ) + // from a_index | STATS aggFn( doubleField / 2 ) testCases.set(`from a_index | stats ${fnSignatureWithBuiltinString}`, []); testCases.set(`from a_index | stats var0 = ${fnSignatureWithBuiltinString}`, []); - testCases.set(`from a_index | stats avg(numberField), ${fnSignatureWithBuiltinString}`, []); + testCases.set(`from a_index | stats avg(doubleField), ${fnSignatureWithBuiltinString}`, []); testCases.set( - `from a_index | stats avg(numberField), var0 = ${fnSignatureWithBuiltinString}`, + `from a_index | stats avg(doubleField), var0 = ${fnSignatureWithBuiltinString}`, [] ); - const nestedEvalAndBuiltin = 'round(numberField / 2)'; + const nestedEvalAndBuiltin = 'round(doubleField / 2)'; const fieldMappingWithNestedEvalAndBuiltinFunctions = getFieldMapping(params); fieldMappingWithNestedBuiltinFunctions[0].name = nestedEvalAndBuiltin; @@ -747,18 +749,18 @@ function generateStatsCommandTestsForAggFunction( }, { withTypes: false } )[0].declaration; - // from a_index | STATS aggFn( round(numberField / 2) ) + // from a_index | STATS aggFn( round(doubleField / 2) ) testCases.set(`from a_index | stats ${fnSignatureWithEvalAndBuiltinString}`, []); testCases.set(`from a_index | stats var0 = ${fnSignatureWithEvalAndBuiltinString}`, []); testCases.set( - `from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString}`, + `from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString}`, [] ); testCases.set( - `from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString}`, + `from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString}`, [] ); - // from a_index | STATS aggFn(round(numberField / 2) ) BY round(numberField / 2) + // from a_index | STATS aggFn(round(doubleField / 2) ) BY round(doubleField / 2) testCases.set( `from a_index | stats ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}`, [] @@ -768,19 +770,19 @@ function generateStatsCommandTestsForAggFunction( [] ); testCases.set( - `from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ipField`, + `from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ipField`, [] ); testCases.set( - `from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ipField`, + `from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ipField`, [] ); testCases.set( - `from a_index | stats avg(numberField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ${nestedBuiltin}`, + `from a_index | stats avg(doubleField), ${fnSignatureWithEvalAndBuiltinString} by ${nestedEvalAndBuiltin}, ${nestedBuiltin}`, [] ); testCases.set( - `from a_index | stats avg(numberField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ${nestedBuiltin}`, + `from a_index | stats avg(doubleField), var0 = ${fnSignatureWithEvalAndBuiltinString} by var1 = ${nestedEvalAndBuiltin}, ${nestedBuiltin}`, [] ); } @@ -798,7 +800,7 @@ function generateStatsCommandTestsForAggFunction( .filter(({ constantOnly }) => !constantOnly) .map( (_) => - `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]` + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(doubleField)] of type [double]` ); testCases.set( `from a_index | stats var = ${ @@ -904,7 +906,7 @@ function generateStatsCommandTestsForGroupingFunction( fieldReplacedType // if a param of type time_literal or chrono_literal it will always be a literal // so no way to test the constantOnly thing - .filter((type) => !['time_literal', 'chrono_literal'].includes(type)) + .filter((type) => !['time_literal'].includes(type as string)) .map((type) => `Argument of [${name}] must be a constant, received [${type}Field]`) ); } @@ -964,10 +966,18 @@ function generateSortCommandTestsForAggFunction( const generateSortCommandTestsForGroupingFunction = generateSortCommandTestsForAggFunction; -const fieldTypesToConstants: Record<SupportedFieldType, string> = { - string: '"a"', - number: '5', - date: 'now()', +const fieldTypesToConstants: Record<SupportedDataType, string> = { + text: '"a"', + keyword: '"a"', + double: '5.5', + integer: '5', + long: '5', + unsigned_long: '5', + counter_integer: '5', + counter_long: '5', + counter_double: '5.5', + date: 'to_datetime("2021-01-01T00:00:00Z")', + date_period: 'to_date_period("2021-01-01/2021-01-02")', boolean: 'true', version: 'to_version("1.0.0")', ip: 'to_ip("127.0.0.1")', @@ -975,14 +985,20 @@ const fieldTypesToConstants: Record<SupportedFieldType, string> = { geo_shape: 'to_geoshape("POINT (30 10)")', cartesian_point: 'to_cartesianpoint("POINT (30 10)")', cartesian_shape: 'to_cartesianshape("POINT (30 10)")', + null: 'NULL', + time_duration: '1 day', + // the following are never supplied + // by the ES function definitions. Just making types happy + time_literal: '1 day', + unsupported: '', }; -const supportedTypesAndFieldNames = supportedFieldTypes.map((type) => ({ +const supportedTypesAndFieldNames = fieldTypes.map((type) => ({ name: fieldNameFromType(type), type, })); -const supportedTypesAndConstants = supportedFieldTypes.map((type) => ({ +const supportedTypesAndConstants = dataTypes.map((type) => ({ name: fieldTypesToConstants[type], type, })); @@ -1003,8 +1019,8 @@ function prepareNestedFunction(fnSignature: FunctionDefinition): string { } const toAvgSignature = statsAggregationFunctionDefinitions.find(({ name }) => name === 'avg')!; - const toInteger = evalFunctionDefinitions.find(({ name }) => name === 'to_integer')!; +const toDoubleSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_double')!; const toStringSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_string')!; const toDateSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_datetime')!; const toBooleanSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_boolean')!; @@ -1019,10 +1035,12 @@ const toCartesianShapeSignature = evalFunctionDefinitions.find( )!; const toVersionSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_version')!; -const nestedFunctions: Record<SupportedFieldType, string> = { - number: prepareNestedFunction(toInteger), - string: prepareNestedFunction(toStringSignature), - date: prepareNestedFunction(toDateSignature), +// We don't have full list for long, unsigned_long, etc. +const nestedFunctions: Record<SupportedDataType, string> = { + double: prepareNestedFunction(toDoubleSignature), + integer: prepareNestedFunction(toInteger), + text: prepareNestedFunction(toStringSignature), + keyword: prepareNestedFunction(toStringSignature), boolean: prepareNestedFunction(toBooleanSignature), ip: prepareNestedFunction(toIpSignature), version: prepareNestedFunction(toVersionSignature), @@ -1030,10 +1048,12 @@ const nestedFunctions: Record<SupportedFieldType, string> = { geo_shape: prepareNestedFunction(toGeoShapeSignature), cartesian_point: prepareNestedFunction(toCartesianPointSignature), cartesian_shape: prepareNestedFunction(toCartesianShapeSignature), + // @ts-expect-error + datetime: prepareNestedFunction(toDateSignature), }; function getFieldName( - typeString: SupportedFieldType, + typeString: SupportedDataType, { useNestedFunction, isStats }: { useNestedFunction: boolean; isStats: boolean } ) { if (useNestedFunction && isStats) { @@ -1069,7 +1089,7 @@ function tweakSignatureForRowCommand(signature: string): string { */ let ret = signature; for (const [type, value] of Object.entries(fieldTypesToConstants)) { - ret = ret.replace(new RegExp(fieldNameFromType(type as SupportedFieldType), 'g'), value); + ret = ret.replace(new RegExp(fieldNameFromType(type as SupportedDataType), 'g'), value); } return ret; } @@ -1086,9 +1106,10 @@ function getFieldMapping( number: '5', date: 'now()', }; + return params.map(({ name: _name, type, constantOnly, literalOptions, ...rest }) => { - const typeString: string = type; - if (isSupportedFieldType(typeString)) { + const typeString: string = type as string; + if (isSupportedDataType(typeString)) { if (useLiterals && literalOptions) { return { name: `"${literalOptions[0]}"`, @@ -1124,7 +1145,7 @@ function getFieldMapping( ...rest, }; } - return { name: 'stringField', type, ...rest }; + return { name: 'textField', type, ...rest }; }); } @@ -1132,7 +1153,7 @@ function generateIncorrectlyTypedParameters( name: string, signatures: FunctionDefinition['signatures'], currentParams: FunctionDefinition['signatures'][number]['params'], - availableFields: Array<{ name: string; type: SupportedFieldType }> + availableFields: Array<{ name: string; type: SupportedDataType }> ) { const literalValues = { string: `"a"`, @@ -1153,7 +1174,7 @@ function generateIncorrectlyTypedParameters( if (type !== 'any') { // try to find an unacceptable field - const unacceptableField: { name: string; type: SupportedFieldType } | undefined = + const unacceptableField: { name: string; type: SupportedDataType } | undefined = availableFields // sort to make the test deterministic .sort((a, b) => a.type.localeCompare(b.type)) @@ -1173,7 +1194,7 @@ function generateIncorrectlyTypedParameters( } // failed to find a bad field... they must all be acceptable - const acceptableField: { name: string; type: SupportedFieldType } | undefined = + const acceptableField: { name: string; type: SupportedDataType } | undefined = type === 'any' ? availableFields[0] : availableFields.find(({ type: fieldType }) => fieldType === type); @@ -1225,8 +1246,12 @@ function generateIncorrectlyTypedParameters( } const fieldName = wrongFieldMapping[i].name; if ( - fieldName === 'numberField' && - signatures.every((signature) => getParamAtPosition(signature, i)?.type !== 'string') + fieldName === 'doubleField' && + signatures.every( + (signature) => + getParamAtPosition(signature, i)?.type !== 'keyword' || + getParamAtPosition(signature, i)?.type !== 'text' + ) ) { return; } diff --git a/packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts index f3c159247e260..273679ab1f267 100644 --- a/packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/__tests__/helpers.ts @@ -7,22 +7,27 @@ */ import { camelCase } from 'lodash'; -import { supportedFieldTypes } from '../definitions/types'; +import { ESQLRealField } from '../validation/types'; +import { fieldTypes } from '../definitions/types'; -export const fields = [ - ...supportedFieldTypes.map((type) => ({ name: `${camelCase(type)}Field`, type })), - { name: 'any#Char$Field', type: 'number' }, - { name: 'kubernetes.something.something', type: 'number' }, +export const fields: ESQLRealField[] = [ + ...fieldTypes + .map((type) => ({ name: `${camelCase(type)}Field`, type })) + .filter((f) => f.type !== 'unsupported'), + { name: 'any#Char$Field', type: 'double' }, + { name: 'kubernetes.something.something', type: 'double' }, { name: '@timestamp', type: 'date' }, ]; -export const enrichFields = [ - { name: 'otherField', type: 'string' }, - { name: 'yetAnotherField', type: 'number' }, +export const enrichFields: ESQLRealField[] = [ + { name: 'otherField', type: 'text' }, + { name: 'yetAnotherField', type: 'double' }, ]; // eslint-disable-next-line @typescript-eslint/naming-convention -export const unsupported_field = [{ name: 'unsupported_field', type: 'unsupported' }]; +export const unsupported_field: ESQLRealField[] = [ + { name: 'unsupported_field', type: 'unsupported' }, +]; export const indexes = [ 'a_index', @@ -58,7 +63,8 @@ export function getCallbackMocks() { return unsupported_field; } if (/dissect|grok/.test(query)) { - return [{ name: 'firstWord', type: 'string' }]; + const field: ESQLRealField = { name: 'firstWord', type: 'text' }; + return [field]; } return fields; }), diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts index 3752fad6c580f..911f8760f60b7 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.from.test.ts @@ -14,6 +14,9 @@ const visibleIndices = indexes .map(({ name, suggestedAs }) => suggestedAs || name) .sort(); +const addTrailingSpace = (strings: string[], predicate: (s: string) => boolean = (_s) => true) => + strings.map((string) => (predicate(string) ? `${string} ` : string)); + const metadataFields = [...METADATA_FIELDS].sort(); describe('autocomplete.suggest', () => { @@ -33,17 +36,17 @@ describe('autocomplete.suggest', () => { test('suggests visible indices on space', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from /', visibleIndices); - await assertSuggestions('FROM /', visibleIndices); - await assertSuggestions('from /index', visibleIndices); + await assertSuggestions('from /', addTrailingSpace(visibleIndices)); + await assertSuggestions('FROM /', addTrailingSpace(visibleIndices)); + await assertSuggestions('from /index', addTrailingSpace(visibleIndices)); }); test('suggests visible indices on comma', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('FROM a,/', visibleIndices); - await assertSuggestions('FROM a, /', visibleIndices); - await assertSuggestions('from *,/', visibleIndices); + await assertSuggestions('FROM a,/', addTrailingSpace(visibleIndices)); + await assertSuggestions('FROM a, /', addTrailingSpace(visibleIndices)); + await assertSuggestions('from *,/', addTrailingSpace(visibleIndices)); }); test('can suggest integration data sources', async () => { @@ -52,17 +55,21 @@ describe('autocomplete.suggest', () => { .filter(({ hidden }) => !hidden) .map(({ name, suggestedAs }) => suggestedAs || name) .sort(); + const expectedSuggestions = addTrailingSpace( + visibleDataSources, + (s) => !integrations.find(({ name }) => name === s) + ); const { assertSuggestions, callbacks } = await setup(); const cb = { ...callbacks, getSources: jest.fn().mockResolvedValue(dataSources), }; - assertSuggestions('from /', visibleDataSources, { callbacks: cb }); - assertSuggestions('FROM /', visibleDataSources, { callbacks: cb }); - assertSuggestions('FROM a,/', visibleDataSources, { callbacks: cb }); - assertSuggestions('from a, /', visibleDataSources, { callbacks: cb }); - assertSuggestions('from *,/', visibleDataSources, { callbacks: cb }); + await assertSuggestions('from /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('FROM /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('FROM a,/', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('from a, /', expectedSuggestions, { callbacks: cb }); + await assertSuggestions('from *,/', expectedSuggestions, { callbacks: cb }); }); }); @@ -71,7 +78,7 @@ describe('autocomplete.suggest', () => { test('on <kbd>SPACE</kbd> without comma ",", suggests adding metadata', async () => { const { assertSuggestions } = await setup(); - const expected = ['METADATA $0', ',', '|'].sort(); + const expected = ['METADATA $0', ',', '| '].sort(); await assertSuggestions('from a, b /', expected); }); @@ -86,10 +93,10 @@ describe('autocomplete.suggest', () => { test('on <kbd>SPACE</kbd> after "METADATA" column suggests command and pipe operators', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a, b [metadata _index /]', [',', '|']); - await assertSuggestions('from a, b metadata _index /', [',', '|']); - await assertSuggestions('from a, b metadata _index, _source /', [',', '|']); - await assertSuggestions(`from a, b metadata ${METADATA_FIELDS.join(', ')} /`, ['|']); + await assertSuggestions('from a, b [metadata _index /]', [',', '| ']); + await assertSuggestions('from a, b metadata _index /', [',', '| ']); + await assertSuggestions('from a, b metadata _index, _source /', [',', '| ']); + await assertSuggestions(`from a, b metadata ${METADATA_FIELDS.join(', ')} /`, ['| ']); }); test('filters out already used metadata fields', async () => { diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts index 7378760781d97..cdf4a6239a9ab 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/autocomplete.command.stats.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { ESQL_COMMON_NUMERIC_TYPES, ESQL_NUMBER_TYPES } from '../../shared/esql_types'; import { setup, getFunctionSignaturesByReturnType, getFieldNamesByType } from './helpers'; const allAggFunctions = getFunctionSignaturesByReturnType('stats', 'any', { @@ -42,7 +43,7 @@ describe('autocomplete.suggest', () => { describe('... <aggregates> ...', () => { test('lists possible aggregations on space after command', async () => { const { assertSuggestions } = await setup(); - const expected = ['var0 =', ...allAggFunctions, ...allEvaFunctions]; + const expected = ['var0 = ', ...allAggFunctions, ...allEvaFunctions]; await assertSuggestions('from a | stats /', expected); await assertSuggestions('FROM a | STATS /', expected); @@ -57,14 +58,14 @@ describe('autocomplete.suggest', () => { test('on space after aggregate field', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a=min(b) /', ['BY $0', ',', '|']); + await assertSuggestions('from a | stats a=min(b) /', ['BY $0', ',', '| ']); }); test('on space after aggregate field with comma', async () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | stats a=max(b), /', [ - 'var0 =', + 'var0 = ', ...allAggFunctions, ...allEvaFunctions, ]); @@ -74,51 +75,76 @@ describe('autocomplete.suggest', () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | stats by bucket(/', [ - ...getFieldNamesByType(['number', 'date']).map((field) => `${field},`), - ...getFunctionSignaturesByReturnType('eval', ['date', 'number'], { scalar: true }).map( - (s) => ({ ...s, text: `${s.text},` }) + ...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date']).map( + (field) => `${field}, ` ), + ...getFunctionSignaturesByReturnType('eval', ['date', ...ESQL_COMMON_NUMERIC_TYPES], { + scalar: true, + }).map((s) => ({ ...s, text: `${s.text},` })), ]); - + const roundParameterTypes = ['double', 'integer', 'long', 'unsigned_long'] as const; await assertSuggestions('from a | stats round(/', [ - ...getFunctionSignaturesByReturnType('stats', 'number', { agg: true, grouping: true }), - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'round', - ]), + ...getFunctionSignaturesByReturnType('stats', roundParameterTypes, { + agg: true, + grouping: true, + }), + ...getFieldNamesByType(roundParameterTypes), + ...getFunctionSignaturesByReturnType( + 'eval', + roundParameterTypes, + { scalar: true }, + undefined, + ['round'] + ), ]); await assertSuggestions('from a | stats round(round(/', [ - ...getFunctionSignaturesByReturnType('stats', 'number', { agg: true }), - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'round', - ]), + ...getFunctionSignaturesByReturnType('stats', roundParameterTypes, { agg: true }), + ...getFieldNamesByType(roundParameterTypes), + ...getFunctionSignaturesByReturnType( + 'eval', + ESQL_NUMBER_TYPES, + { scalar: true }, + undefined, + ['round'] + ), ]); await assertSuggestions('from a | stats avg(round(/', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'round', - ]), + ...getFieldNamesByType(roundParameterTypes), + ...getFunctionSignaturesByReturnType( + 'eval', + ESQL_NUMBER_TYPES, + { scalar: true }, + undefined, + ['round'] + ), ]); await assertSuggestions('from a | stats avg(/', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + ...getFieldNamesByType(ESQL_NUMBER_TYPES), + ...getFunctionSignaturesByReturnType('eval', ESQL_NUMBER_TYPES, { scalar: true }), ]); await assertSuggestions('from a | stats round(avg(/', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'round', - ]), + ...getFieldNamesByType(ESQL_NUMBER_TYPES), + ...getFunctionSignaturesByReturnType( + 'eval', + ESQL_NUMBER_TYPES, + { scalar: true }, + undefined, + ['round'] + ), ]); }); test('when typing inside function left paren', async () => { const { assertSuggestions } = await setup(); const expected = [ - ...getFieldNamesByType(['number', 'date', 'boolean', 'ip']), - ...getFunctionSignaturesByReturnType('stats', ['number', 'date', 'boolean', 'ip'], { - scalar: true, - }), + ...getFieldNamesByType([...ESQL_COMMON_NUMERIC_TYPES, 'date', 'boolean', 'ip']), + ...getFunctionSignaturesByReturnType( + 'stats', + [...ESQL_COMMON_NUMERIC_TYPES, 'date', 'date_period', 'boolean', 'ip'], + { + scalar: true, + } + ), ]; await assertSuggestions('from a | stats a=min(/)', expected); @@ -130,29 +156,35 @@ describe('autocomplete.suggest', () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | stats avg(b/) by stringField', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + ...getFieldNamesByType(ESQL_NUMBER_TYPES), + ...getFunctionSignaturesByReturnType( + 'eval', + ['double', 'integer', 'long', 'unsigned_long'], + { + scalar: true, + } + ), ]); }); test('when typing right paren', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a = min(b)/ | sort b', ['BY $0', ',', '|']); + await assertSuggestions('from a | stats a = min(b)/ | sort b', ['BY $0', ',', '| ']); }); test('increments suggested variable name counter', async () => { const { assertSuggestions } = await setup(); await assertSuggestions('from a | eval var0=round(b), var1=round(c) | stats /', [ - 'var2 =', + 'var2 = ', ...allAggFunctions, 'var0', 'var1', ...allEvaFunctions, ]); await assertSuggestions('from a | stats var0=min(b),var1=c,/', [ - 'var2 =', + 'var2 = ', ...allAggFunctions, ...allEvaFunctions, ]); @@ -163,8 +195,8 @@ describe('autocomplete.suggest', () => { test('on space after "BY" keyword', async () => { const { assertSuggestions } = await setup(); const expected = [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]; @@ -177,26 +209,27 @@ describe('autocomplete.suggest', () => { test('on space after grouping field', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats a=c by d /', [',', '|']); + await assertSuggestions('from a | stats a=c by d /', [',', '| ']); }); test('after comma "," in grouping fields', async () => { const { assertSuggestions } = await setup(); + const fields = getFieldNamesByType('any').map((field) => `${field} `); await assertSuggestions('from a | stats a=c by d, /', [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...fields, ...allEvaFunctions, ...allGroupingFunctions, ]); await assertSuggestions('from a | stats a=min(b),/', [ - 'var0 =', + 'var0 = ', ...allAggFunctions, ...allEvaFunctions, ]); await assertSuggestions('from a | stats avg(b) by c, /', [ - 'var0 =', - ...getFieldNamesByType('any'), + 'var0 = ', + ...fields, ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ...allGroupingFunctions, ]); @@ -205,19 +238,24 @@ describe('autocomplete.suggest', () => { test('on space before expression right hand side operand', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats avg(b) by numberField % /', [ - ...getFieldNamesByType('number'), + await assertSuggestions('from a | stats avg(b) by integerField % /', [ + ...getFieldNamesByType('integer'), + ...getFieldNamesByType('double'), + ...getFieldNamesByType('long'), '`avg(b)`', - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + ...getFunctionSignaturesByReturnType('eval', ['integer', 'double', 'long'], { + scalar: true, + }), + ...allGroupingFunctions, ]); await assertSuggestions('from a | stats avg(b) by var0 = /', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]); await assertSuggestions('from a | stats avg(b) by c, var0 = /', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...allEvaFunctions, ...allGroupingFunctions, ]); @@ -226,7 +264,12 @@ describe('autocomplete.suggest', () => { test('on space after expression right hand side operand', async () => { const { assertSuggestions } = await setup(); - await assertSuggestions('from a | stats avg(b) by numberField % 2 /', [',', '|']); + await assertSuggestions('from a | stats avg(b) by doubleField % 2 /', [',', '| ']); + + await assertSuggestions( + 'from a | stats var0 = AVG(doubleField) BY var1 = BUCKET(dateField, 1 day)/', + [',', '| ', '+ $0', '- $0'] + ); }); }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts index 3d9ed7058a4a8..4c4ad9902ac8c 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/helpers.ts @@ -18,6 +18,14 @@ import type { ESQLCallbacks } from '../../shared/types'; import type { EditorContext, SuggestionRawDefinition } from '../types'; import { TIME_SYSTEM_PARAMS } from '../factories'; import { getFunctionSignatures } from '../../definitions/helpers'; +import { ESQLRealField } from '../../validation/types'; +import { + FieldType, + fieldTypes, + FunctionParameterType, + FunctionReturnType, + SupportedDataType, +} from '../../definitions/types'; export interface Integration { name: string; @@ -38,23 +46,13 @@ export const TIME_PICKER_SUGGESTION: PartialSuggestionWithText = { export const triggerCharacters = [',', '(', '=', ' ']; -export const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [ - ...[ - 'string', - 'number', - 'date', - 'boolean', - 'ip', - 'geo_point', - 'geo_shape', - 'cartesian_point', - 'cartesian_shape', - ].map((type) => ({ +export const fields: Array<ESQLRealField & { suggestedAs?: string }> = [ + ...fieldTypes.map((type) => ({ name: `${camelCase(type)}Field`, type, })), - { name: 'any#Char$Field', type: 'number', suggestedAs: '`any#Char$Field`' }, - { name: 'kubernetes.something.something', type: 'number' }, + { name: 'any#Char$Field', type: 'double', suggestedAs: '`any#Char$Field`' }, + { name: 'kubernetes.something.something', type: 'double' }, ]; export const indexes = ( @@ -124,7 +122,7 @@ export const policies = [ */ export function getFunctionSignaturesByReturnType( command: string, - _expectedReturnType: string | string[], + _expectedReturnType: Readonly<FunctionReturnType | 'any' | Array<FunctionReturnType | 'any'>>, { agg, grouping, @@ -140,7 +138,7 @@ export function getFunctionSignaturesByReturnType( builtin?: boolean; skipAssign?: boolean; } = {}, - paramsTypes?: string[], + paramsTypes?: Readonly<FunctionParameterType[]>, ignored?: string[], option?: string ): PartialSuggestionWithText[] { @@ -177,7 +175,7 @@ export function getFunctionSignaturesByReturnType( } const filteredByReturnType = signatures.filter( ({ returnType }) => - expectedReturnType.includes('any') || expectedReturnType.includes(returnType) + expectedReturnType.includes('any') || expectedReturnType.includes(returnType as string) ); if (!filteredByReturnType.length) { return false; @@ -226,14 +224,16 @@ export function getFunctionSignaturesByReturnType( }); } -export function getFieldNamesByType(_requestedType: string | string[]) { +export function getFieldNamesByType( + _requestedType: Readonly<FieldType | 'any' | Array<FieldType | 'any'>> +) { const requestedType = Array.isArray(_requestedType) ? _requestedType : [_requestedType]; return fields .filter(({ type }) => requestedType.includes('any') || requestedType.includes(type)) .map(({ name, suggestedAs }) => suggestedAs || name); } -export function getLiteralsByType(_type: string | string[]) { +export function getLiteralsByType(_type: SupportedDataType | SupportedDataType[]) { const type = Array.isArray(_type) ? _type : [_type]; if (type.includes('time_literal')) { // return only singular @@ -242,13 +242,13 @@ export function getLiteralsByType(_type: string | string[]) { return []; } -export function getDateLiteralsByFieldType(_requestedType: string | string[]) { +export function getDateLiteralsByFieldType(_requestedType: FieldType | FieldType[]) { const requestedType = Array.isArray(_requestedType) ? _requestedType : [_requestedType]; return requestedType.includes('date') ? [TIME_PICKER_SUGGESTION, ...TIME_SYSTEM_PARAMS] : []; } export function createCustomCallbackMocks( - customFields?: Array<{ name: string; type: string }>, + customFields?: ESQLRealField[], customSources?: Array<{ name: string; hidden: boolean }>, customPolicies?: Array<{ name: string; diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index d4ad5157d979a..dcb0aaa76adb8 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -9,16 +9,21 @@ import { suggest } from './autocomplete'; import { evalFunctionDefinitions } from '../definitions/functions'; import { timeUnitsToSuggest } from '../definitions/literals'; -import { commandDefinitions } from '../definitions/commands'; +import { commandDefinitions as unmodifiedCommandDefinitions } from '../definitions/commands'; import { getSafeInsertText, getUnitDuration, - TRIGGER_SUGGESTION_COMMAND, TIME_SYSTEM_PARAMS, + TRIGGER_SUGGESTION_COMMAND, } from './factories'; import { camelCase, partition } from 'lodash'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; -import { FunctionParameter } from '../definitions/types'; +import { + FunctionParameter, + isFieldType, + isSupportedDataType, + SupportedDataType, +} from '../definitions/types'; import { getParamAtPosition } from './helper'; import { nonNullable } from '../shared/helpers'; import { @@ -34,7 +39,13 @@ import { TIME_PICKER_SUGGESTION, } from './__tests__/helpers'; import { METADATA_FIELDS } from '../shared/constants'; +import { ESQL_COMMON_NUMERIC_TYPES, ESQL_STRING_TYPES } from '../shared/esql_types'; + +const roundParameterTypes = ['double', 'integer', 'long', 'unsigned_long'] as const; +const powParameterTypes = ['double', 'integer', 'long', 'unsigned_long'] as const; +const log10ParameterTypes = ['double', 'integer', 'long', 'unsigned_long'] as const; +const commandDefinitions = unmodifiedCommandDefinitions.filter(({ hidden }) => !hidden); describe('autocomplete', () => { type TestArgs = [ string, @@ -105,6 +116,7 @@ describe('autocomplete', () => { }, }); + // const sourceCommands = ['row', 'from', 'show', 'metrics']; Uncomment when metrics is being released const sourceCommands = ['row', 'from', 'show']; describe('New command', () => { @@ -141,7 +153,7 @@ describe('autocomplete', () => { describe('show', () => { testSuggestions('show ', ['INFO']); for (const fn of ['info']) { - testSuggestions(`show ${fn} `, ['|']); + testSuggestions(`show ${fn} `, ['| ']); } }); @@ -149,143 +161,137 @@ describe('autocomplete', () => { const allEvalFns = getFunctionSignaturesByReturnType('where', 'any', { scalar: true, }); - testSuggestions('from a | where ', [...getFieldNamesByType('any'), ...allEvalFns]); + testSuggestions('from a | where ', [ + ...getFieldNamesByType('any').map((field) => `${field} `), + ...allEvalFns, + ]); testSuggestions('from a | eval var0 = 1 | where ', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((name) => `${name} `), 'var0', ...allEvalFns, ]); - testSuggestions('from a | where stringField ', [ - // all functions compatible with a stringField type - ...getFunctionSignaturesByReturnType( - 'where', - 'boolean', - { - builtin: true, - }, - ['string'] - ), - ]); - testSuggestions('from a | where stringField >= ', [ - ...getFieldNamesByType('string'), - ...getFunctionSignaturesByReturnType('where', 'string', { scalar: true }), - ]); - // Skip these tests until the insensitive case equality gets restored back - testSuggestions.skip('from a | where stringField =~ ', [ - ...getFieldNamesByType('string'), - ...getFunctionSignaturesByReturnType('where', 'string', { scalar: true }), - ]); - testSuggestions('from a | where stringField >= stringField ', [ - '|', - ...getFunctionSignaturesByReturnType( - 'where', - 'boolean', - { - builtin: true, - }, - ['boolean'] - ), - ]); - testSuggestions.skip('from a | where stringField =~ stringField ', [ - '|', + testSuggestions('from a | where keywordField ', [ + // all functions compatible with a keywordField type ...getFunctionSignaturesByReturnType( 'where', 'boolean', { builtin: true, }, - ['boolean'] + undefined, + ['and', 'or', 'not'] ), ]); + const expectedComparisonWithTextFieldSuggestions = [ + ...getFieldNamesByType(['text', 'keyword', 'ip', 'version']), + ...getFunctionSignaturesByReturnType('where', ['text', 'keyword', 'ip', 'version'], { + scalar: true, + }), + ]; + testSuggestions('from a | where textField >= ', expectedComparisonWithTextFieldSuggestions); + testSuggestions( + 'from a | where textField >= textField', + expectedComparisonWithTextFieldSuggestions + ); for (const op of ['and', 'or']) { - testSuggestions(`from a | where stringField >= stringField ${op} `, [ + testSuggestions(`from a | where keywordField >= keywordField ${op} `, [ ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), ]); - testSuggestions(`from a | where stringField >= stringField ${op} numberField `, [ - ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['number']), + testSuggestions(`from a | where keywordField >= keywordField ${op} doubleField `, [ + ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']), ]); - testSuggestions(`from a | where stringField >= stringField ${op} numberField == `, [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }), + testSuggestions(`from a | where keywordField >= keywordField ${op} doubleField == `, [ + ...getFieldNamesByType(ESQL_COMMON_NUMERIC_TYPES), + ...getFunctionSignaturesByReturnType('where', ESQL_COMMON_NUMERIC_TYPES, { + scalar: true, + }), ]); } - testSuggestions('from a | stats a=avg(numberField) | where a ', [ + testSuggestions('from a | stats a=avg(doubleField) | where a ', [ ...getFunctionSignaturesByReturnType('where', 'any', { builtin: true, skipAssign: true }, [ - 'number', + 'double', ]), ]); // Mind this test: suggestion is aware of previous commands when checking for fields - // in this case the numberField has been wiped by the STATS command and suggest cannot find it's type + // in this case the doubleField has been wiped by the STATS command and suggest cannot find it's type // @TODO: verify this is the correct behaviour in this case or if we want a "generic" suggestion anyway testSuggestions( - 'from a | stats a=avg(numberField) | where numberField ', + 'from a | stats a=avg(doubleField) | where doubleField ', [], undefined, undefined, // make the fields suggest aware of the previous STATS, leave the other callbacks untouched - [[{ name: 'a', type: 'number' }], undefined, undefined] + [[{ name: 'a', type: 'double' }], undefined, undefined] ); // The editor automatically inject the final bracket, so it is not useful to test with just open bracket testSuggestions( 'from a | where log10()', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }, undefined, [ - 'log10', - ]), + ...getFieldNamesByType(log10ParameterTypes), + ...getFunctionSignaturesByReturnType( + 'where', + log10ParameterTypes, + { scalar: true }, + undefined, + ['log10'] + ), ], '(' ); - testSuggestions('from a | where log10(numberField) ', [ - ...getFunctionSignaturesByReturnType('where', 'number', { builtin: true }, ['number']), - ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['number']), + testSuggestions('from a | where log10(doubleField) ', [ + ...getFunctionSignaturesByReturnType('where', 'double', { builtin: true }, ['double']), + ...getFunctionSignaturesByReturnType('where', 'boolean', { builtin: true }, ['double']), ]); testSuggestions( - 'from a | WHERE pow(numberField, )', + 'from a | WHERE pow(doubleField, )', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }, undefined, [ - 'pow', - ]), + ...getFieldNamesByType(powParameterTypes), + ...getFunctionSignaturesByReturnType( + 'where', + powParameterTypes, + { scalar: true }, + undefined, + ['pow'] + ), ], ',' ); - testSuggestions('from index | WHERE stringField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); - testSuggestions('from index | WHERE stringField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); + testSuggestions('from index | WHERE keywordField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); + testSuggestions('from index | WHERE keywordField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); testSuggestions('from index | WHERE not ', [ ...getFieldNamesByType('boolean'), ...getFunctionSignaturesByReturnType('eval', 'boolean', { scalar: true }), ]); - testSuggestions('from index | WHERE numberField in ', ['( $0 )']); - testSuggestions('from index | WHERE numberField not in ', ['( $0 )']); + testSuggestions('from index | WHERE doubleField in ', ['( $0 )']); + testSuggestions('from index | WHERE doubleField not in ', ['( $0 )']); testSuggestions( - 'from index | WHERE numberField not in ( )', + 'from index | WHERE doubleField not in ( )', [ - ...getFieldNamesByType('number').filter((name) => name !== 'numberField'), - ...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }), + ...getFieldNamesByType('double').filter((name) => name !== 'doubleField'), + ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), ], '(' ); testSuggestions( - 'from index | WHERE numberField in ( `any#Char$Field`, )', + 'from index | WHERE doubleField in ( `any#Char$Field`, )', [ - ...getFieldNamesByType('number').filter( - (name) => name !== '`any#Char$Field`' && name !== 'numberField' + ...getFieldNamesByType('double').filter( + (name) => name !== '`any#Char$Field`' && name !== 'doubleField' ), - ...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }), + ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), ], undefined, 54 // after the first suggestions ); testSuggestions( - 'from index | WHERE numberField not in ( `any#Char$Field`, )', + 'from index | WHERE doubleField not in ( `any#Char$Field`, )', [ - ...getFieldNamesByType('number').filter( - (name) => name !== '`any#Char$Field`' && name !== 'numberField' + ...getFieldNamesByType('double').filter( + (name) => name !== '`any#Char$Field`' && name !== 'doubleField' ), - ...getFunctionSignaturesByReturnType('where', 'number', { scalar: true }), + ...getFunctionSignaturesByReturnType('where', 'double', { scalar: true }), ], undefined, 58 // after the first suggestions @@ -296,15 +302,19 @@ describe('autocomplete', () => { const constantPattern = '"%{WORD:firstWord}"'; const subExpressions = [ '', - `grok stringField |`, - `grok stringField ${constantPattern} |`, - `dissect stringField ${constantPattern} append_separator = ":" |`, - `dissect stringField ${constantPattern} |`, + `grok keywordField |`, + `grok keywordField ${constantPattern} |`, + `dissect keywordField ${constantPattern} append_separator = ":" |`, + `dissect keywordField ${constantPattern} |`, ]; for (const subExpression of subExpressions) { - testSuggestions(`from a | ${subExpression} grok `, getFieldNamesByType('string')); - testSuggestions(`from a | ${subExpression} grok stringField `, [constantPattern], ' '); - testSuggestions(`from a | ${subExpression} grok stringField ${constantPattern} `, ['|']); + // Unskip once https://github.com/elastic/kibana/issues/190070 is fixed + testSuggestions.skip( + `from a | ${subExpression} grok `, + getFieldNamesByType(ESQL_STRING_TYPES) + ); + testSuggestions(`from a | ${subExpression} grok keywordField `, [constantPattern], ' '); + testSuggestions(`from a | ${subExpression} grok keywordField ${constantPattern} `, ['| ']); } }); @@ -312,79 +322,83 @@ describe('autocomplete', () => { const constantPattern = '"%{firstWord}"'; const subExpressions = [ '', - `dissect stringField |`, - `dissect stringField ${constantPattern} |`, - `dissect stringField ${constantPattern} append_separator = ":" |`, + `dissect keywordField |`, + `dissect keywordField ${constantPattern} |`, + `dissect keywordField ${constantPattern} append_separator = ":" |`, ]; for (const subExpression of subExpressions) { - testSuggestions(`from a | ${subExpression} dissect `, getFieldNamesByType('string')); - testSuggestions(`from a | ${subExpression} dissect stringField `, [constantPattern], ' '); + // Unskip once https://github.com/elastic/kibana/issues/190070 is fixed + testSuggestions.skip( + `from a | ${subExpression} dissect `, + getFieldNamesByType(ESQL_STRING_TYPES) + ); + testSuggestions(`from a | ${subExpression} dissect keywordField `, [constantPattern], ' '); testSuggestions( - `from a | ${subExpression} dissect stringField ${constantPattern} `, - ['APPEND_SEPARATOR = $0', '|'], + `from a | ${subExpression} dissect keywordField ${constantPattern} `, + ['APPEND_SEPARATOR = $0', '| '], ' ' ); testSuggestions( - `from a | ${subExpression} dissect stringField ${constantPattern} append_separator = `, + `from a | ${subExpression} dissect keywordField ${constantPattern} append_separator = `, ['":"', '";"'] ); testSuggestions( - `from a | ${subExpression} dissect stringField ${constantPattern} append_separator = ":" `, - ['|'] + `from a | ${subExpression} dissect keywordField ${constantPattern} append_separator = ":" `, + ['| '] ); } }); describe('sort', () => { testSuggestions('from a | sort ', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((name) => `${name} `), ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), ]); - testSuggestions('from a | sort stringField ', ['ASC', 'DESC', ',', '|']); - testSuggestions('from a | sort stringField desc ', ['NULLS FIRST', 'NULLS LAST', ',', '|']); + testSuggestions('from a | sort keywordField ', ['ASC ', 'DESC ', ',', '| ']); + testSuggestions('from a | sort keywordField desc ', ['NULLS FIRST ', 'NULLS LAST ', ',', '| ']); // @TODO: improve here - // testSuggestions('from a | sort stringField desc ', ['first', 'last']); + // testSuggestions('from a | sort keywordField desc ', ['first', 'last']); }); describe('limit', () => { - testSuggestions('from a | limit ', ['10', '100', '1000']); - testSuggestions('from a | limit 4 ', ['|']); + testSuggestions('from a | limit ', ['10 ', '100 ', '1000 ']); + testSuggestions('from a | limit 4 ', ['| ']); }); describe('mv_expand', () => { testSuggestions('from a | mv_expand ', getFieldNamesByType('any')); - testSuggestions('from a | mv_expand a ', ['|']); + testSuggestions('from a | mv_expand a ', ['| ']); }); describe('rename', () => { testSuggestions('from a | rename ', getFieldNamesByType('any')); - testSuggestions('from a | rename stringField ', ['AS $0'], ' '); - testSuggestions('from a | rename stringField as ', ['var0']); + testSuggestions('from a | rename keywordField ', ['AS $0'], ' '); + testSuggestions('from a | rename keywordField as ', ['var0']); }); for (const command of ['keep', 'drop']) { describe(command, () => { testSuggestions(`from a | ${command} `, getFieldNamesByType('any')); testSuggestions( - `from a | ${command} stringField, `, - getFieldNamesByType('any').filter((name) => name !== 'stringField') + `from a | ${command} keywordField, `, + getFieldNamesByType('any').filter((name) => name !== 'keywordField') ); testSuggestions( - `from a | ${command} stringField,`, - getFieldNamesByType('any').filter((name) => name !== 'stringField'), + `from a | ${command} keywordField,`, + getFieldNamesByType('any').filter((name) => name !== 'keywordField'), ',' ); testSuggestions( - `from a_index | eval round(numberField) + 1 | eval \`round(numberField) + 1\` + 1 | eval \`\`\`round(numberField) + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`round(numberField) + 1\`\`\`\` + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`\`\`\`\`\`\`\`\`round(numberField) + 1\`\`\`\`\`\`\`\` + 1\`\`\`\` + 1\`\` + 1\` + 1 | ${command} `, + `from a_index | eval round(doubleField) + 1 | eval \`round(doubleField) + 1\` + 1 | eval \`\`\`round(doubleField) + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`round(doubleField) + 1\`\`\`\` + 1\`\` + 1\` + 1 | eval \`\`\`\`\`\`\`\`\`\`\`\`\`\`\`round(doubleField) + 1\`\`\`\`\`\`\`\` + 1\`\`\`\` + 1\`\` + 1\` + 1 | ${command} `, [ ...getFieldNamesByType('any'), - '`round(numberField) + 1`', - '```round(numberField) + 1`` + 1`', - '```````round(numberField) + 1```` + 1`` + 1`', - '```````````````round(numberField) + 1```````` + 1```` + 1`` + 1`', - '```````````````````````````````round(numberField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`', + '`round(doubleField) + 1`', + '```round(doubleField) + 1`` + 1`', + '```````round(doubleField) + 1```` + 1`` + 1`', + '```````````````round(doubleField) + 1```````` + 1```` + 1`` + 1`', + '```````````````````````````````round(doubleField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`', ] ); }); @@ -410,81 +424,73 @@ describe('autocomplete', () => { testSuggestions(`from a ${prevCommand}| enrich _${mode.toUpperCase()}:`, policyNames, ':'); testSuggestions(`from a ${prevCommand}| enrich _${camelCase(mode)}:`, policyNames, ':'); } - testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '|']); - testSuggestions(`from a ${prevCommand}| enrich policy on `, [ - 'stringField', - 'numberField', - 'dateField', - 'booleanField', - 'ipField', - 'geoPointField', - 'geoShapeField', - 'cartesianPointField', - 'cartesianShapeField', - '`any#Char$Field`', - 'kubernetes.something.something', - ]); - testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['WITH $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy `, ['ON $0', 'WITH $0', '| ']); + testSuggestions(`from a ${prevCommand}| enrich policy on `, getFieldNamesByType('any')); + testSuggestions(`from a ${prevCommand}| enrich policy on b `, ['WITH $0', ',', '| ']); testSuggestions( `from a ${prevCommand}| enrich policy on b with `, - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], ' ' ); - testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 `, ['= $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 `, ['= $0', ',', '| ']); testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = `, [ ...getPolicyFields('policy'), ]); - testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField `, [ + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = keywordField `, [ ',', - '|', + '| ', ]); - testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField, `, [ - 'var1 =', + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = keywordField, `, [ + 'var1 = ', ...getPolicyFields('policy'), ]); - testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = stringField, var1 `, [ + testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = keywordField, var1 `, [ '= $0', ',', - '|', + '| ', ]); testSuggestions( - `from a ${prevCommand}| enrich policy on b with var0 = stringField, var1 = `, + `from a ${prevCommand}| enrich policy on b with var0 = keywordField, var1 = `, [...getPolicyFields('policy')] ); testSuggestions( `from a ${prevCommand}| enrich policy with `, - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], ' ' ); - testSuggestions(`from a ${prevCommand}| enrich policy with stringField `, ['= $0', ',', '|']); + testSuggestions(`from a ${prevCommand}| enrich policy with keywordField `, [ + '= $0', + ',', + '| ', + ]); } }); describe('eval', () => { testSuggestions('from a | eval ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); - testSuggestions('from a | eval numberField ', [ + testSuggestions('from a | eval doubleField ', [ ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ - 'number', + 'double', ]), ',', - '|', + '| ', ]); - testSuggestions('from index | EVAL stringField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); - testSuggestions('from index | EVAL stringField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); - testSuggestions('from index | EVAL numberField in ', ['( $0 )']); + testSuggestions('from index | EVAL keywordField not ', ['LIKE $0', 'RLIKE $0', 'IN $0']); + testSuggestions('from index | EVAL keywordField NOT ', ['LIKE $0', 'RLIKE $0', 'IN $0']); + testSuggestions('from index | EVAL doubleField in ', ['( $0 )']); testSuggestions( - 'from index | EVAL numberField in ( )', + 'from index | EVAL doubleField in ( )', [ - ...getFieldNamesByType('number').filter((name) => name !== 'numberField'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + ...getFieldNamesByType('double').filter((name) => name !== 'doubleField'), + ...getFunctionSignaturesByReturnType('eval', 'double', { scalar: true }), ], '(' ); - testSuggestions('from index | EVAL numberField not in ', ['( $0 )']); + testSuggestions('from index | EVAL doubleField not in ', ['( $0 )']); testSuggestions('from index | EVAL not ', [ ...getFieldNamesByType('boolean'), ...getFunctionSignaturesByReturnType('eval', 'boolean', { scalar: true }), @@ -492,27 +498,31 @@ describe('autocomplete', () => { testSuggestions('from a | eval a=', [ ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); - testSuggestions('from a | eval a=abs(numberField), b= ', [ + testSuggestions('from a | eval a=abs(doubleField), b= ', [ ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); - testSuggestions('from a | eval a=numberField, ', [ - 'var0 =', + testSuggestions('from a | eval a=doubleField, ', [ + 'var0 = ', ...getFieldNamesByType('any'), 'a', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); // Skip this test until the insensitive case equality gets restored back - testSuggestions.skip('from a | eval a=stringField =~ ', [ - ...getFieldNamesByType('string'), - ...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }), + testSuggestions.skip('from a | eval a=keywordField =~ ', [ + ...getFieldNamesByType(ESQL_STRING_TYPES), + ...getFunctionSignaturesByReturnType('eval', ESQL_STRING_TYPES, { scalar: true }), ]); testSuggestions( 'from a | eval a=round()', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'round', - ]), + ...getFieldNamesByType(roundParameterTypes), + ...getFunctionSignaturesByReturnType( + 'eval', + roundParameterTypes, + { scalar: true }, + undefined, + ['round'] + ), ], '(' ); @@ -539,64 +549,68 @@ describe('autocomplete', () => { [], ' ' ); - testSuggestions('from a | eval a=round(numberField) ', [ + testSuggestions('from a | eval a=round(doubleField) ', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ - 'number', + 'double', ]), ]); testSuggestions( - 'from a | eval a=round(numberField, ', + 'from a | eval a=round(doubleField, ', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ + ...getFieldNamesByType('integer'), + ...getFunctionSignaturesByReturnType('eval', 'integer', { scalar: true }, undefined, [ 'round', ]), ], ' ' ); testSuggestions( - 'from a | eval round(numberField, ', + 'from a | eval round(doubleField, ', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ + ...getFieldNamesByType('integer'), + ...getFunctionSignaturesByReturnType('eval', 'integer', { scalar: true }, undefined, [ 'round', ]), ], ' ' ); - testSuggestions('from a | eval a=round(numberField),', [ - 'var0 =', + testSuggestions('from a | eval a=round(doubleField),', [ + 'var0 = ', ...getFieldNamesByType('any'), 'a', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ]); - testSuggestions('from a | eval a=round(numberField) + ', [ - ...getFieldNamesByType('number'), - 'a', // @TODO remove this - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + testSuggestions('from a | eval a=round(doubleField) + ', [ + ...getFieldNamesByType(ESQL_COMMON_NUMERIC_TYPES), + ...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { + scalar: true, + }), ]); - testSuggestions('from a | eval a=round(numberField)+ ', [ - ...getFieldNamesByType('number'), - 'a', // @TODO remove this - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + testSuggestions('from a | eval a=round(doubleField)+ ', [ + ...getFieldNamesByType(ESQL_COMMON_NUMERIC_TYPES), + ...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { + scalar: true, + }), ]); - testSuggestions('from a | eval a=numberField+ ', [ - ...getFieldNamesByType('number'), - 'a', // @TODO remove this - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + testSuggestions('from a | eval a=doubleField+ ', [ + ...getFieldNamesByType(ESQL_COMMON_NUMERIC_TYPES), + ...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { + scalar: true, + }), ]); testSuggestions('from a | eval a=`any#Char$Field`+ ', [ - ...getFieldNamesByType('number'), - 'a', // @TODO remove this - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }), + ...getFieldNamesByType(ESQL_COMMON_NUMERIC_TYPES), + ...getFunctionSignaturesByReturnType('eval', ESQL_COMMON_NUMERIC_TYPES, { + scalar: true, + }), ]); testSuggestions( - 'from a | stats avg(numberField) by stringField | eval ', + 'from a | stats avg(doubleField) by keywordField | eval ', [ - 'var0 =', - '`avg(numberField)`', + 'var0 = ', + '`avg(doubleField)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], ' ', @@ -605,32 +619,32 @@ describe('autocomplete', () => { [[], undefined, undefined] ); testSuggestions( - 'from a | eval abs(numberField) + 1 | eval ', + 'from a | eval abs(doubleField) + 1 | eval ', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), - '`abs(numberField) + 1`', + '`abs(doubleField) + 1`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], ' ' ); testSuggestions( - 'from a | stats avg(numberField) by stringField | eval ', + 'from a | stats avg(doubleField) by keywordField | eval ', [ - 'var0 =', - '`avg(numberField)`', + 'var0 = ', + '`avg(doubleField)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], ' ', undefined, // make aware EVAL of the previous STATS command with the buggy field name from expression - [[{ name: 'avg_numberField_', type: 'number' }], undefined, undefined] + [[{ name: 'avg_doubleField_', type: 'double' }], undefined, undefined] ); testSuggestions( - 'from a | stats avg(numberField), avg(kubernetes.something.something) by stringField | eval ', + 'from a | stats avg(doubleField), avg(kubernetes.something.something) by keywordField | eval ', [ - 'var0 =', - '`avg(numberField)`', + 'var0 = ', + '`avg(doubleField)`', '`avg(kubernetes.something.something)`', ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -639,46 +653,59 @@ describe('autocomplete', () => { // make aware EVAL of the previous STATS command with the buggy field name from expression [ [ - { name: 'avg_numberField_', type: 'number' }, - { name: 'avg_kubernetes.something.something_', type: 'number' }, + { name: 'avg_doubleField_', type: 'double' }, + { name: 'avg_kubernetes.something.something_', type: 'double' }, ], undefined, undefined, ] ); + testSuggestions( - 'from a | eval a=round(numberField), b=round()', + 'from a | eval a=round(doubleField), b=round()', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'round', - ]), + ...getFieldNamesByType(roundParameterTypes), + ...getFunctionSignaturesByReturnType( + 'eval', + roundParameterTypes, + { scalar: true }, + undefined, + ['round'] + ), ], '(' ); // test that comma is correctly added to the suggestions if minParams is not reached yet testSuggestions('from a | eval a=concat( ', [ - ...getFieldNamesByType('string').map((v) => `${v},`), - ...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [ - 'concat', - ]).map((v) => ({ ...v, text: `${v.text},` })), + ...getFieldNamesByType(['text', 'keyword']).map((v) => `${v}, `), + ...getFunctionSignaturesByReturnType( + 'eval', + ['text', 'keyword'], + { scalar: true }, + undefined, + ['concat'] + ).map((v) => ({ ...v, text: `${v.text},` })), ]); testSuggestions( - 'from a | eval a=concat(stringField, ', + 'from a | eval a=concat(textField, ', [ - ...getFieldNamesByType('string'), - ...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [ - 'concat', - ]), + ...getFieldNamesByType(['text', 'keyword']), + ...getFunctionSignaturesByReturnType( + 'eval', + ['text', 'keyword'], + { scalar: true }, + undefined, + ['concat'] + ), ], ' ' ); // test that the arg type is correct after minParams testSuggestions( - 'from a | eval a=cidr_match(ipField, stringField, ', + 'from a | eval a=cidr_match(ipField, textField, ', [ - ...getFieldNamesByType('string'), - ...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [ + ...getFieldNamesByType('text'), + ...getFunctionSignaturesByReturnType('eval', 'text', { scalar: true }, undefined, [ 'cidr_match', ]), ], @@ -686,7 +713,7 @@ describe('autocomplete', () => { ); // test that comma is correctly added to the suggestions if minParams is not reached yet testSuggestions('from a | eval a=cidr_match( ', [ - ...getFieldNamesByType('ip').map((v) => `${v},`), + ...getFieldNamesByType('ip').map((v) => `${v}, `), ...getFunctionSignaturesByReturnType('eval', 'ip', { scalar: true }, undefined, [ 'cidr_match', ]).map((v) => ({ ...v, text: `${v.text},` })), @@ -694,10 +721,14 @@ describe('autocomplete', () => { testSuggestions( 'from a | eval a=cidr_match(ipField, ', [ - ...getFieldNamesByType('string'), - ...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [ - 'cidr_match', - ]), + ...getFieldNamesByType(['text', 'keyword']), + ...getFunctionSignaturesByReturnType( + 'eval', + ['text', 'keyword'], + { scalar: true }, + undefined, + ['cidr_match'] + ), ], ' ' ); @@ -705,27 +736,34 @@ describe('autocomplete', () => { // round(round( // round(round(round( // etc... + for (const nesting of [1, 2, 3, 4]) { testSuggestions( `from a | eval a=${Array(nesting).fill('round(').join('')}`, [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'round', - ]), + ...getFieldNamesByType(roundParameterTypes), + ...getFunctionSignaturesByReturnType( + 'eval', + roundParameterTypes, + { scalar: true }, + undefined, + ['round'] + ), ], '(' ); } + const absParameterTypes = ['double', 'integer', 'long', 'unsigned_long'] as const; + // Smoke testing for suggestions in previous position than the end of the statement testSuggestions( - 'from a | eval var0 = abs(numberField) | eval abs(var0)', + 'from a | eval var0 = abs(doubleField) | eval abs(var0)', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ - 'number', + 'double', ]), ], undefined, @@ -734,10 +772,14 @@ describe('autocomplete', () => { testSuggestions( 'from a | eval var0 = abs(b) | eval abs(var0)', [ - ...getFieldNamesByType('number'), - ...getFunctionSignaturesByReturnType('eval', 'number', { scalar: true }, undefined, [ - 'abs', - ]), + ...getFieldNamesByType(absParameterTypes), + ...getFunctionSignaturesByReturnType( + 'eval', + absParameterTypes, + { scalar: true }, + undefined, + ['abs'] + ), ], undefined, 26 /* b column in abs */ @@ -746,7 +788,7 @@ describe('autocomplete', () => { // Test suggestions for each possible param, within each signature variation, for each function for (const fn of evalFunctionDefinitions) { // skip this fn for the moment as it's quite hard to test - if (fn.name !== 'bucket') { + if (!['bucket', 'date_extract', 'date_diff', 'case'].includes(fn.name)) { for (const signature of fn.signatures) { signature.params.forEach((param, i) => { if (i < signature.params.length) { @@ -764,11 +806,11 @@ describe('autocomplete', () => { // get all possible types for this param const [constantOnlyParamDefs, acceptsFieldParamDefs] = partition( allParamDefs, - (p) => p.constantOnly || /_literal/.test(p.type) + (p) => p.constantOnly || /_literal/.test(p.type as string) ); - const getTypesFromParamDefs = (paramDefs: FunctionParameter[]) => - Array.from(new Set(paramDefs.map((p) => p.type))); + const getTypesFromParamDefs = (paramDefs: FunctionParameter[]): SupportedDataType[] => + Array.from(new Set(paramDefs.map((p) => p.type))).filter(isSupportedDataType); const suggestedConstants = param.literalSuggestions || param.literalOptions; @@ -777,16 +819,20 @@ describe('autocomplete', () => { if (!requiresMoreArgs || s === '' || (typeof s === 'object' && s.text === '')) { return s; } - return typeof s === 'string' ? `${s},` : { ...s, text: `${s.text},` }; + return typeof s === 'string' ? `${s}, ` : { ...s, text: `${s.text},` }; }; testSuggestions( `from a | eval ${fn.name}(${Array(i).fill('field').join(', ')}${i ? ',' : ''} )`, suggestedConstants?.length - ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`) + ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`) : [ - ...getDateLiteralsByFieldType(getTypesFromParamDefs(acceptsFieldParamDefs)), - ...getFieldNamesByType(getTypesFromParamDefs(acceptsFieldParamDefs)), + ...getDateLiteralsByFieldType( + getTypesFromParamDefs(acceptsFieldParamDefs).filter(isFieldType) + ), + ...getFieldNamesByType( + getTypesFromParamDefs(acceptsFieldParamDefs).filter(isFieldType) + ), ...getFunctionSignaturesByReturnType( 'eval', getTypesFromParamDefs(acceptsFieldParamDefs), @@ -803,10 +849,14 @@ describe('autocomplete', () => { i ? ',' : '' } )`, suggestedConstants?.length - ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ',' : ''}`) + ? suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`) : [ - ...getDateLiteralsByFieldType(getTypesFromParamDefs(acceptsFieldParamDefs)), - ...getFieldNamesByType(getTypesFromParamDefs(acceptsFieldParamDefs)), + ...getDateLiteralsByFieldType( + getTypesFromParamDefs(acceptsFieldParamDefs).filter(isFieldType) + ), + ...getFieldNamesByType( + getTypesFromParamDefs(acceptsFieldParamDefs).filter(isFieldType) + ), ...getFunctionSignaturesByReturnType( 'eval', getTypesFromParamDefs(acceptsFieldParamDefs), @@ -822,6 +872,23 @@ describe('autocomplete', () => { }); } } + + // The above test fails cause it expects nested functions like + // DATE_EXTRACT(concat("aligned_day_","of_week_in_month"), date) to also be suggested + // which is actually valid according to func signature + // but currently, our autocomplete only suggests the literal suggestions + if (['date_extract', 'date_diff'].includes(fn.name)) { + const firstParam = fn.signatures[0].params[0]; + const suggestedConstants = firstParam?.literalSuggestions || firstParam?.literalOptions; + const requiresMoreArgs = true; + + testSuggestions( + `from a | eval ${fn.name}(`, + suggestedConstants?.length + ? [...suggestedConstants.map((option) => `"${option}"${requiresMoreArgs ? ', ' : ''}`)] + : [] + ); + } } testSuggestions('from a | eval var0 = bucket(@timestamp, ', getUnitDuration(1), ' '); @@ -834,65 +901,46 @@ describe('autocomplete', () => { [ ...dateSuggestions, ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ - 'number', + 'integer', ]), ], ' ' ); testSuggestions('from a | eval a = 1 year ', [ ',', - '|', + '| ', ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ 'time_interval', ]), ]); - testSuggestions( - 'from a | eval a = 1 day + 2 ', - [ - ...dateSuggestions, - ',', - '|', - ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ - 'number', - ]), - ], - ' ' - ); + testSuggestions('from a | eval a = 1 day + 2 ', [',', '| ']); testSuggestions( 'from a | eval 1 day + 2 ', [ ...dateSuggestions, ...getFunctionSignaturesByReturnType('eval', 'any', { builtin: true, skipAssign: true }, [ - 'number', + 'integer', ]), ], ' ' ); testSuggestions( 'from a | eval var0=date_trunc()', - [ - ...[...TIME_SYSTEM_PARAMS].map((t) => `${t},`), - ...getLiteralsByType('time_literal').map((t) => `${t},`), - ...getFunctionSignaturesByReturnType('eval', 'date', { scalar: true }, undefined, [ - 'date_trunc', - ]).map((t) => ({ ...t, text: `${t.text},` })), - ...getFieldNamesByType('date').map((t) => `${t},`), - TIME_PICKER_SUGGESTION, - ], + getLiteralsByType('time_literal').map((t) => `${t}, `), '(' ); testSuggestions( 'from a | eval var0=date_trunc(2 )', - [...dateSuggestions.map((t) => `${t},`), ','], + [...dateSuggestions.map((t) => `${t}, `), ','], ' ' ); }); }); describe('values suggestions', () => { - testSuggestions('FROM "a"', ['a', 'b'], undefined, 7, [ + testSuggestions('FROM "a"', ['a ', 'b '], undefined, 7, [ , [ { name: 'a', hidden: false }, @@ -917,7 +965,7 @@ describe('autocomplete', () => { describe('callbacks', () => { it('should send the fields query without the last command', async () => { const callbackMocks = createCustomCallbackMocks(undefined, undefined, undefined); - const statement = 'from a | drop stringField | eval var0 = abs(numberField) '; + const statement = 'from a | drop keywordField | eval var0 = abs(doubleField) '; const triggerOffset = statement.lastIndexOf(' '); const context = createCompletionContext(statement[triggerOffset]); await suggest( @@ -928,12 +976,12 @@ describe('autocomplete', () => { callbackMocks ); expect(callbackMocks.getFieldsFor).toHaveBeenCalledWith({ - query: 'from a | drop stringField', + query: 'from a | drop keywordField', }); }); it('should send the fields query aware of the location', async () => { const callbackMocks = createCustomCallbackMocks(undefined, undefined, undefined); - const statement = 'from a | drop | eval var0 = abs(numberField) '; + const statement = 'from a | drop | eval var0 = abs(doubleField) '; const triggerOffset = statement.lastIndexOf('p') + 1; // drop <here> const context = createCompletionContext(statement[triggerOffset]); await suggest( @@ -947,50 +995,6 @@ describe('autocomplete', () => { }); }); - describe('auto triggers', () => { - function getSuggestionsFor(statement: string) { - const callbackMocks = createCustomCallbackMocks(undefined, undefined, undefined); - const triggerOffset = statement.lastIndexOf(' ') + 1; // drop <here> - const context = createCompletionContext(statement[triggerOffset]); - return suggest( - statement, - triggerOffset + 1, - context, - async (text) => (text ? getAstAndSyntaxErrors(text) : { ast: [], errors: [] }), - callbackMocks - ); - } - it('should trigger further suggestions for functions', async () => { - const suggestions = await getSuggestionsFor('from a | eval '); - // test that all functions will retrigger suggestions - expect( - suggestions - .filter(({ kind }) => kind === 'Function') - .every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - // now test that non-function won't retrigger - expect( - suggestions - .filter(({ kind }) => kind !== 'Function') - .every(({ command }) => command == null) - ).toBeTruthy(); - }); - it('should trigger further suggestions for commands', async () => { - const suggestions = await getSuggestionsFor('from a | '); - // test that all commands will retrigger suggestions - expect( - suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - }); - it('should trigger further suggestions after enrich mode', async () => { - const suggestions = await getSuggestionsFor('from a | enrich _any:'); - // test that all commands will retrigger suggestions - expect( - suggestions.every(({ command }) => command === TRIGGER_SUGGESTION_COMMAND) - ).toBeTruthy(); - }); - }); - /** * Monaco asks for suggestions in at least two different scenarios. * 1. When the user types a non-whitespace character (e.g. 'FROM k') - this is the Invoke trigger kind @@ -1021,21 +1025,47 @@ describe('autocomplete', () => { 10 ); - // function argument - testSuggestions( - 'FROM kibana_sample_data_logs | EVAL TRIM(e)', - [ - ...getFieldNamesByType('string'), - ...getFunctionSignaturesByReturnType('eval', 'string', { scalar: true }, undefined, [ - 'trim', - ]), - ], - undefined, - 42 - ); + describe('function arguments', () => { + // function argument + testSuggestions( + 'FROM kibana_sample_data_logs | EVAL TRIM(e)', + [ + ...getFieldNamesByType(['text', 'keyword']), + ...getFunctionSignaturesByReturnType( + 'eval', + ['text', 'keyword'], + { scalar: true }, + undefined, + ['trim'] + ), + ], + undefined, + 42 + ); + + // subsequent function argument + const expectedDateDiff2ndArgSuggestions = [ + TIME_PICKER_SUGGESTION, + ...TIME_SYSTEM_PARAMS.map((t) => `${t}, `), + ...getFieldNamesByType('date').map((name) => `${name}, `), + ...getFunctionSignaturesByReturnType('eval', 'date', { scalar: true }).map((s) => ({ + ...s, + text: `${s.text},`, + })), + ]; + testSuggestions( + 'FROM a | EVAL DATE_DIFF("day", )', + expectedDateDiff2ndArgSuggestions, + undefined, + 31 + ); + + // trigger character case for comparison + testSuggestions('FROM a | EVAL DATE_DIFF("day", )', expectedDateDiff2ndArgSuggestions, ' '); + }); // FROM source - testSuggestions('FROM k', ['index1', 'index2'], undefined, 6, [ + testSuggestions('FROM k', ['index1 ', 'index2 '], undefined, 6, [ , [ { name: 'index1', hidden: false }, @@ -1044,7 +1074,7 @@ describe('autocomplete', () => { ]); // FROM source METADATA - testSuggestions('FROM index1 M', [',', 'METADATA $0', '|'], undefined, 13); + testSuggestions('FROM index1 M', [',', 'METADATA $0', '| '], undefined, 13); // FROM source METADATA field testSuggestions('FROM index1 METADATA _', METADATA_FIELDS, undefined, 22); @@ -1053,7 +1083,7 @@ describe('autocomplete', () => { testSuggestions( 'FROM index1 | EVAL b', [ - 'var0 =', + 'var0 = ', ...getFieldNamesByType('any'), ...getFunctionSignaturesByReturnType('eval', 'any', { scalar: true }), ], @@ -1069,7 +1099,13 @@ describe('autocomplete', () => { ); // DISSECT field - testSuggestions('FROM index1 | DISSECT b', getFieldNamesByType('string'), undefined, 23); + // enable once https://github.com/elastic/kibana/issues/190070 is fixed + testSuggestions.skip( + 'FROM index1 | DISSECT b', + getFieldNamesByType(ESQL_STRING_TYPES), + undefined, + 23 + ); // DROP (first field) testSuggestions('FROM index1 | DROP f', getFieldNamesByType('any'), undefined, 20); @@ -1086,7 +1122,7 @@ describe('autocomplete', () => { ); // ENRICH policy ON - testSuggestions('FROM index1 | ENRICH policy O', ['ON $0', 'WITH $0', '|'], undefined, 29); + testSuggestions('FROM index1 | ENRICH policy O', ['ON $0', 'WITH $0', '| '], undefined, 29); // ENRICH policy ON field testSuggestions('FROM index1 | ENRICH policy ON f', getFieldNamesByType('any'), undefined, 32); @@ -1094,20 +1130,26 @@ describe('autocomplete', () => { // ENRICH policy WITH policyfield testSuggestions( 'FROM index1 | ENRICH policy WITH v', - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], undefined, 34 ); testSuggestions( 'FROM index1 | ENRICH policy WITH \tv', - ['var0 =', ...getPolicyFields('policy')], + ['var0 = ', ...getPolicyFields('policy')], undefined, 34 ); // GROK field - testSuggestions('FROM index1 | GROK f', getFieldNamesByType('string'), undefined, 20); + // enable once https://github.com/elastic/kibana/issues/190070 + testSuggestions.skip( + 'FROM index1 | GROK f', + getFieldNamesByType(ESQL_STRING_TYPES), + undefined, + 20 + ); // KEEP (first field) testSuggestions('FROM index1 | KEEP f', getFieldNamesByType('any'), undefined, 20); @@ -1123,7 +1165,7 @@ describe('autocomplete', () => { // LIMIT argument // Here we actually test that the invoke trigger kind does not work // because it isn't very useful to see literal suggestions when typing a number - testSuggestions('FROM a | LIMIT 1', ['|'], undefined, 16); + testSuggestions('FROM a | LIMIT 1', ['| '], undefined, 16); // MV_EXPAND field testSuggestions('FROM index1 | MV_EXPAND f', getFieldNamesByType('any'), undefined, 25); @@ -1142,41 +1184,49 @@ describe('autocomplete', () => { 'FROM index1 | SORT f', [ ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ], undefined, 20 ); // SORT field order - testSuggestions('FROM index1 | SORT stringField a', ['ASC', 'DESC', ',', '|'], undefined, 32); + testSuggestions( + 'FROM index1 | SORT keywordField a', + ['ASC ', 'DESC ', ',', '| '], + undefined, + 33 + ); // SORT field order nulls testSuggestions( - 'FROM index1 | SORT stringField ASC n', - ['NULLS FIRST', 'NULLS LAST', ',', '|'], + 'FROM index1 | SORT keywordField ASC n', + ['NULLS FIRST ', 'NULLS LAST ', ',', '| '], undefined, - 36 + 37 ); // STATS argument testSuggestions( 'FROM index1 | STATS f', - ['var0 =', ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true })], + [ + 'var0 = ', + ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true }), + ], undefined, 21 ); // STATS argument BY - testSuggestions('FROM index1 | STATS AVG(booleanField) B', ['BY $0', ',', '|'], undefined, 39); + testSuggestions('FROM index1 | STATS AVG(booleanField) B', ['BY $0', ',', '| '], undefined, 39); // STATS argument BY expression testSuggestions( 'FROM index1 | STATS field BY f', [ - 'var0 =', + 'var0 = ', ...getFunctionSignaturesByReturnType('stats', 'any', { grouping: true, scalar: true }), - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ], undefined, 30 @@ -1186,7 +1236,7 @@ describe('autocomplete', () => { testSuggestions( 'FROM index1 | WHERE f', [ - ...getFieldNamesByType('any'), + ...getFieldNamesByType('any').map((field) => `${field} `), ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }), ], undefined, @@ -1195,17 +1245,289 @@ describe('autocomplete', () => { // WHERE argument comparison testSuggestions( - 'FROM index1 | WHERE stringField i', + 'FROM index1 | WHERE keywordField i', getFunctionSignaturesByReturnType( 'where', 'boolean', { builtin: true, }, - ['string'] + undefined, + ['and', 'or', 'not'] ), undefined, - 33 + 34 + ); + }); + + describe('advancing the cursor and opening the suggestion menu automatically ✨', () => { + const attachTriggerCommand = ( + s: string | PartialSuggestionWithText + ): PartialSuggestionWithText => + typeof s === 'string' + ? { + text: s, + command: TRIGGER_SUGGESTION_COMMAND, + } + : { ...s, command: TRIGGER_SUGGESTION_COMMAND }; + + const attachAsSnippet = (s: PartialSuggestionWithText): PartialSuggestionWithText => ({ + ...s, + asSnippet: true, + }); + + // Source command + testSuggestions( + 'F', + ['FROM $0', 'ROW $0', 'SHOW $0'].map(attachTriggerCommand).map(attachAsSnippet), + undefined, + 1 + ); + + // Pipe command + testSuggestions( + 'FROM a | E', + commandDefinitions + .filter(({ name }) => !sourceCommands.includes(name)) + .map(({ name }) => attachTriggerCommand(name.toUpperCase() + ' $0')) + .map(attachAsSnippet), // TODO consider making this check more fundamental + undefined, + 10 + ); + + describe('function arguments', () => { + // literalSuggestions parameter + const dateDiffFirstParamSuggestions = + evalFunctionDefinitions.find(({ name }) => name === 'date_diff')?.signatures[0].params?.[0] + .literalSuggestions ?? []; + testSuggestions( + 'FROM a | EVAL DATE_DIFF()', + dateDiffFirstParamSuggestions.map((s) => `"${s}", `).map(attachTriggerCommand), + undefined, + 24 + ); + + // field parameter + + const expectedStringSuggestionsWhenMoreArgsAreNeeded = [ + ...getFieldNamesByType(ESQL_STRING_TYPES) + .map((field) => `${field}, `) + .map(attachTriggerCommand), + ...getFunctionSignaturesByReturnType( + 'eval', + ESQL_STRING_TYPES, + { scalar: true }, + undefined, + ['replace'] + ).map((s) => ({ + ...s, + text: `${s.text},`, + })), + ]; + + testSuggestions( + 'FROM a | EVAL REPLACE()', + expectedStringSuggestionsWhenMoreArgsAreNeeded, + undefined, + 22 + ); + + // subsequent parameter + testSuggestions( + 'FROM a | EVAL REPLACE(keywordField, )', + expectedStringSuggestionsWhenMoreArgsAreNeeded, + undefined, + 36 + ); + + // final parameter — should not advance! + testSuggestions( + 'FROM a | EVAL REPLACE(keywordField, keywordField, )', + [ + ...getFieldNamesByType(ESQL_STRING_TYPES).map((field) => ({ + text: field, + command: undefined, + })), + ...getFunctionSignaturesByReturnType( + 'eval', + ESQL_STRING_TYPES, + { scalar: true }, + undefined, + ['replace'] + ), + ], + undefined, + 50 + ); + + // Trigger character because this is how it will actually be... the user will press + // space-bar... this may change if we fix the tokenization of timespan literals + // such that "2 days" is a single monaco token + testSuggestions( + 'FROM a | EVAL DATE_TRUNC(2 )', + [...timeUnitsToSuggest.map((s) => `${s.name}, `).map(attachTriggerCommand), ','], + ' ' + ); + }); + + // PIPE (|) + testSuggestions( + 'FROM a ', + [attachTriggerCommand('| '), ',', attachAsSnippet(attachTriggerCommand('METADATA $0'))], + undefined, + 7 + ); + + // Assignment + testSuggestions(`FROM a | ENRICH policy on b with `, [ + attachTriggerCommand('var0 = '), + ...getPolicyFields('policy'), + ]); + + // FROM source + // + // Using an Invoke trigger kind here because that's what Monaco uses when the show suggestions + // action is triggered (e.g. accepting the "FROM" suggestion) + testSuggestions( + 'FROM ', + [ + { text: 'index1 ', command: TRIGGER_SUGGESTION_COMMAND }, + { text: 'index2 ', command: TRIGGER_SUGGESTION_COMMAND }, + ], + undefined, + 5, + [ + , + [ + { name: 'index1', hidden: false }, + { name: 'index2', hidden: false }, + ], + ] + ); + + // FROM source METADATA + testSuggestions( + 'FROM index1 M', + [',', attachAsSnippet(attachTriggerCommand('METADATA $0')), '| '], + undefined, + 13 + ); + + // LIMIT number + testSuggestions('FROM a | LIMIT ', ['10 ', '100 ', '1000 '].map(attachTriggerCommand)); + + // SORT field + testSuggestions( + 'FROM a | SORT ', + [ + ...getFieldNamesByType('any').map((field) => `${field} `), + ...getFunctionSignaturesByReturnType('sort', 'any', { scalar: true }), + ].map(attachTriggerCommand), + undefined, + 14 + ); + + // SORT field order + testSuggestions( + 'FROM a | SORT field ', + [',', ...['ASC ', 'DESC ', '| '].map(attachTriggerCommand)], + undefined, + 20 + ); + + // SORT field order nulls + testSuggestions( + 'FROM a | SORT field ASC ', + [',', ...['NULLS FIRST ', 'NULLS LAST ', '| '].map(attachTriggerCommand)], + undefined, + 24 + ); + + // STATS argument + testSuggestions( + 'FROM a | STATS ', + [ + 'var0 = ', + ...getFunctionSignaturesByReturnType('stats', 'any', { scalar: true, agg: true }).map( + attachAsSnippet + ), + ].map(attachTriggerCommand), + undefined, + 15 + ); + + // STATS argument BY + testSuggestions( + 'FROM a | STATS AVG(numberField) ', + [',', attachAsSnippet(attachTriggerCommand('BY $0')), attachTriggerCommand('| ')], + undefined, + 32 + ); + + // STATS argument BY field + const allByCompatibleFunctions = getFunctionSignaturesByReturnType( + 'stats', + 'any', + { + scalar: true, + grouping: true, + }, + undefined, + undefined, + 'by' + ); + testSuggestions( + 'FROM a | STATS AVG(numberField) BY ', + [ + attachTriggerCommand('var0 = '), + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...allByCompatibleFunctions, + ], + undefined, + 35 + ); + + // STATS argument BY assignment (checking field suggestions) + testSuggestions( + 'FROM a | STATS AVG(numberField) BY var0 = ', + [ + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...allByCompatibleFunctions, + ], + undefined, + 41 + ); + + // WHERE argument (field suggestions) + testSuggestions( + 'FROM a | WHERE ', + [ + ...getFieldNamesByType('any') + .map((field) => `${field} `) + .map(attachTriggerCommand), + ...getFunctionSignaturesByReturnType('where', 'any', { scalar: true }).map(attachAsSnippet), + ], + undefined, + 15 + ); + + // WHERE argument comparison + testSuggestions( + 'FROM a | WHERE keywordField ', + getFunctionSignaturesByReturnType( + 'where', + 'boolean', + { + builtin: true, + }, + ['keyword'] + ).map((s) => (s.text.toLowerCase().includes('null') ? s : attachTriggerCommand(s))), + undefined, + 28 ); }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts index 807bc32ba82b0..e775c3f05fe5f 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts @@ -13,9 +13,11 @@ import type { ESQLCommand, ESQLCommandOption, ESQLFunction, + ESQLLiteral, ESQLSingleAstItem, } from '@kbn/esql-ast'; import { partition } from 'lodash'; +import { ESQL_NUMBER_TYPES, compareTypesWithLiterals, isNumericType } from '../shared/esql_types'; import type { EditorContext, SuggestionRawDefinition } from './types'; import { lookupColumn, @@ -43,6 +45,7 @@ import { nonNullable, getColumnExists, findPreviousWord, + noCaseCompare, } from '../shared/helpers'; import { collectVariables, excludeVariablesFromCurrentCommand } from '../shared/variables'; import type { ESQLPolicy, ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; @@ -88,10 +91,11 @@ import { getParamAtPosition, getQueryForFields, getSourcesFromCommands, + getSupportedTypesForBinaryOperators, isAggFunctionUsedAlready, removeQuoteForSuggestedSources, } from './helper'; -import { FunctionParameter } from '../definitions/types'; +import { FunctionParameter, FunctionReturnType, SupportedDataType } from '../definitions/types'; type GetSourceFn = () => Promise<SuggestionRawDefinition[]>; type GetDataStreamsForIntegrationFn = ( @@ -99,7 +103,8 @@ type GetDataStreamsForIntegrationFn = ( ) => Promise<Array<{ name: string; title?: string }> | undefined>; type GetFieldsByTypeFn = ( type: string | string[], - ignored?: string[] + ignored?: string[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ) => Promise<SuggestionRawDefinition[]>; type GetFieldsMapFn = () => Promise<Map<string, ESQLRealField>>; type GetPoliciesFn = () => Promise<SuggestionRawDefinition[]>; @@ -124,7 +129,7 @@ function appendEnrichFields( // @TODO: improve this const newMap: Map<string, ESQLRealField> = new Map(fieldsMap); for (const field of policyMetadata.enrichFields) { - newMap.set(field, { name: field, type: 'number' }); + newMap.set(field, { name: field, type: 'double' }); } return newMap; } @@ -183,8 +188,8 @@ function correctQuerySyntax(_query: string, context: EditorContext) { (context.triggerCharacter && charThatNeedMarkers.includes(context.triggerCharacter)) || // monaco.editor.CompletionTriggerKind['Invoke'] === 0 (context.triggerKind === 0 && unclosedRoundBrackets === 0) || - (context.triggerCharacter === ' ' && - (isMathFunction(query, query.length) || isComma(query.trimEnd()[query.trimEnd().length - 1]))) + (context.triggerCharacter === ' ' && isMathFunction(query, query.length)) || + isComma(query.trimEnd()[query.trimEnd().length - 1]) ) { query += EDITOR_MARKER; } @@ -241,6 +246,7 @@ export async function suggest( if (!ast.length) { return suggestions.filter(isSourceCommand); } + return suggestions.filter((def) => !isSourceCommand(def)); } @@ -306,12 +312,19 @@ export async function suggest( return []; } -function getFieldsByTypeRetriever(queryString: string, resourceRetriever?: ESQLCallbacks) { +function getFieldsByTypeRetriever( + queryString: string, + resourceRetriever?: ESQLCallbacks +): { getFieldsByType: GetFieldsByTypeFn; getFieldsMap: GetFieldsMapFn } { const helpers = getFieldsByTypeHelper(queryString, resourceRetriever); return { - getFieldsByType: async (expectedType: string | string[] = 'any', ignored: string[] = []) => { + getFieldsByType: async ( + expectedType: string | string[] = 'any', + ignored: string[] = [], + options + ) => { const fields = await helpers.getFieldsByType(expectedType, ignored); - return buildFieldsDefinitionsWithMetadata(fields); + return buildFieldsDefinitionsWithMetadata(fields, options); }, getFieldsMap: helpers.getFieldsMap, }; @@ -427,7 +440,13 @@ function areCurrentArgsValid( function extractFinalTypeFromArg( arg: ESQLAstItem, references: Pick<ReferenceMaps, 'fields' | 'variables'> -): string | undefined { +): + | ESQLLiteral['literalType'] + | SupportedDataType + | FunctionReturnType + | 'timeInterval' + | string // @TODO remove this + | undefined { if (Array.isArray(arg)) { return extractFinalTypeFromArg(arg[0], references); } @@ -732,7 +751,7 @@ async function getExpressionSuggestionsByType( workoutBuiltinOptions(rightArg, references) ) ); - if (nodeArgType === 'number' && isLiteralItem(rightArg)) { + if (isNumericType(nodeArgType) && isLiteralItem(rightArg)) { // ... EVAL var = 1 <suggest> suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit'])); } @@ -740,7 +759,7 @@ async function getExpressionSuggestionsByType( if (rightArg.args.some(isTimeIntervalItem)) { const lastFnArg = rightArg.args[rightArg.args.length - 1]; const lastFnArgType = extractFinalTypeFromArg(lastFnArg, references); - if (lastFnArgType === 'number' && isLiteralItem(lastFnArg)) + if (isNumericType(lastFnArgType) && isLiteralItem(lastFnArg)) // ... EVAL var = 1 year + 2 <suggest> suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit'])); } @@ -769,7 +788,7 @@ async function getExpressionSuggestionsByType( option, argDef, nodeArg, - nodeArgType || 'any', + (nodeArgType as string) || 'any', references, getFieldsByType )) @@ -777,7 +796,7 @@ async function getExpressionSuggestionsByType( if (nodeArg.args.some(isTimeIntervalItem)) { const lastFnArg = nodeArg.args[nodeArg.args.length - 1]; const lastFnArgType = extractFinalTypeFromArg(lastFnArg, references); - if (lastFnArgType === 'number' && isLiteralItem(lastFnArg)) + if (isNumericType(lastFnArgType) && isLiteralItem(lastFnArg)) // ... EVAL var = 1 year + 2 <suggest> suggestions.push(...getCompatibleLiterals(command.name, ['time_literal_unit'])); } @@ -790,10 +809,17 @@ async function getExpressionSuggestionsByType( // if the definition includes a list of constants, suggest them if (argDef.values) { // ... | <COMMAND> ... <suggest enums> - suggestions.push(...buildConstantsDefinitions(argDef.values)); + suggestions.push( + ...buildConstantsDefinitions(argDef.values, undefined, undefined, { + advanceCursorAndOpenSuggestions: true, + }) + ); } // If the type is specified try to dig deeper in the definition to suggest the best candidate - if (['string', 'number', 'boolean'].includes(argDef.type) && !argDef.values) { + if ( + ['string', 'text', 'keyword', 'boolean', ...ESQL_NUMBER_TYPES].includes(argDef.type) && + !argDef.values + ) { // it can be just literal values (i.e. "string") if (argDef.constantOnly) { // ... | <COMMAND> ... <suggest> @@ -810,6 +836,7 @@ async function getExpressionSuggestionsByType( // ... | <COMMAND> <suggest> // In this case start suggesting something not strictly based on type suggestions.push( + ...(await getFieldsByType('any', [], { advanceCursorAndOpenSuggestions: true })), ...(await getFieldsOrFunctionsSuggestions( ['any'], command.name, @@ -817,7 +844,7 @@ async function getExpressionSuggestionsByType( getFieldsByType, { functions: true, - fields: true, + fields: false, variables: anyVariables, } )) @@ -856,7 +883,7 @@ async function getExpressionSuggestionsByType( option, argDef, nodeArg, - nodeArgType, + nodeArgType as string, references, getFieldsByType )) @@ -971,6 +998,7 @@ async function getBuiltinFunctionNextArgument( ) { const suggestions = []; const isFnComplete = isFunctionArgComplete(nodeArg, references); + if (isFnComplete.complete) { // i.e. ... | <COMMAND> field > 0 <suggest> // i.e. ... | <COMMAND> field + otherN <suggest> @@ -997,21 +1025,24 @@ async function getBuiltinFunctionNextArgument( if (isFnComplete.reason === 'fewArgs') { const fnDef = getFunctionDefinition(nodeArg.name); - if (fnDef?.signatures.every(({ params }) => params.some(({ type }) => isArrayType(type)))) { + if ( + fnDef?.signatures.every(({ params }) => + params.some(({ type }) => isArrayType(type as string)) + ) + ) { suggestions.push(listCompleteItem); } else { const finalType = nestedType || nodeArgType || 'any'; + const supportedTypes = getSupportedTypesForBinaryOperators(fnDef, finalType as string); suggestions.push( ...(await getFieldsOrFunctionsSuggestions( // this is a special case with AND/OR // <COMMAND> expression AND/OR <suggest> // technically another boolean value should be suggested, but it is a better experience // to actually suggest a wider set of fields/functions - [ - finalType === 'boolean' && getFunctionDefinition(nodeArg.name)?.type === 'builtin' - ? 'any' - : finalType, - ], + finalType === 'boolean' && getFunctionDefinition(nodeArg.name)?.type === 'builtin' + ? ['any'] + : (supportedTypes as string[]), command.name, option?.name, getFieldsByType, @@ -1082,7 +1113,11 @@ async function getFieldsOrFunctionsSuggestions( } = {} ): Promise<SuggestionRawDefinition[]> { const filteredFieldsByType = pushItUpInTheList( - (await (fields ? getFieldsByType(types, ignoreFields) : [])) as SuggestionRawDefinition[], + (await (fields + ? getFieldsByType(types, ignoreFields, { + advanceCursorAndOpenSuggestions: commandName === 'sort', + }) + : [])) as SuggestionRawDefinition[], functions ); @@ -1116,11 +1151,7 @@ async function getFieldsOrFunctionsSuggestions( } } - // could also be in stats (bucket) but our autocomplete is not great yet - const displayDateSuggestions = types.includes('date') && ['where', 'eval'].includes(commandName); - const suggestions = filteredFieldsByType.concat( - displayDateSuggestions ? getDateLiterals() : [], functions ? getCompatibleFunctionDefinition(commandName, optionName, types, ignoreFn) : [], variables ? pushItUpInTheList(buildVariablesDefinitions(filteredVariablesByType), functions) @@ -1182,6 +1213,8 @@ async function getFunctionArgsSuggestions( ? refSignature.minParams - 1 > argIndex : false); + const shouldAddComma = hasMoreMandatoryArgs && fnDefinition.type !== 'builtin'; + const suggestedConstants = Array.from( new Set( fnDefinition.signatures.reduce<string[]>((acc, signature) => { @@ -1202,13 +1235,13 @@ async function getFunctionArgsSuggestions( ); if (suggestedConstants.length) { - return buildValueDefinitions(suggestedConstants).map((suggestion) => ({ - ...suggestion, - text: addCommaIf(hasMoreMandatoryArgs && fnDefinition.type !== 'builtin', suggestion.text), - })); + return buildValueDefinitions(suggestedConstants, { + addComma: hasMoreMandatoryArgs, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }); } - const suggestions = []; + const suggestions: SuggestionRawDefinition[] = []; const noArgDefined = !arg; const isUnknownColumn = arg && @@ -1262,7 +1295,9 @@ async function getFunctionArgsSuggestions( // if existing arguments are preset already, use them to filter out incompatible signatures .filter((signature) => { if (existingTypes.length) { - return existingTypes.every((type, index) => signature.params[index].type === type); + return existingTypes.every((type, index) => + compareTypesWithLiterals(signature.params[index].type, type) + ); } return true; }); @@ -1287,55 +1322,70 @@ async function getFunctionArgsSuggestions( // const [constantOnlyParamDefs, paramDefsWhichSupportFields] = partition( allParamDefinitionsForThisPosition, - (paramDef) => paramDef.constantOnly || /_literal$/.test(paramDef.type) + (paramDef) => paramDef.constantOnly || /_literal$/.test(paramDef.type as string) ); const getTypesFromParamDefs = (paramDefs: FunctionParameter[]) => { return Array.from(new Set(paramDefs.map(({ type }) => type))); }; + // Literals suggestions.push( - ...getCompatibleLiterals(command.name, getTypesFromParamDefs(constantOnlyParamDefs)) + ...getCompatibleLiterals( + command.name, + getTypesFromParamDefs(constantOnlyParamDefs) as string[], + undefined, + { addComma: shouldAddComma, advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs } + ) + ); + + // Fields + suggestions.push( + ...pushItUpInTheList( + await getFieldsByType(getTypesFromParamDefs(paramDefsWhichSupportFields) as string[], [], { + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }), + true + ) ); + // Functions suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - getTypesFromParamDefs(paramDefsWhichSupportFields), + ...getCompatibleFunctionDefinition( command.name, option?.name, - getFieldsByType, - { - functions: true, - fields: true, - variables: variablesExcludingCurrentCommandOnes, - }, - // do not repropose the same function as arg - // i.e. avoid cases like abs(abs(abs(...))) with suggestions - { - ignoreFn: fnToIgnore, - } - )) + getTypesFromParamDefs(paramDefsWhichSupportFields) as string[], + fnToIgnore + ).map((suggestion) => ({ + ...suggestion, + text: addCommaIf(shouldAddComma, suggestion.text), + })) ); + + // could also be in stats (bucket) but our autocomplete is not great yet + if ( + getTypesFromParamDefs(paramDefsWhichSupportFields).includes('date') && + ['where', 'eval'].includes(command.name) + ) + suggestions.push( + ...getDateLiterals({ + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }) + ); } // for eval and row commands try also to complete numeric literals with time intervals where possible if (arg) { if (command.name !== 'stats') { - if (isLiteralItem(arg) && arg.literalType === 'number') { + if (isLiteralItem(arg) && isNumericType(arg.literalType)) { // ... | EVAL fn(2 <suggest>) suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - ['time_literal_unit'], - command.name, - option?.name, - getFieldsByType, - { - functions: false, - fields: false, - variables: variablesExcludingCurrentCommandOnes, - literals: true, - } - )) + ...getCompatibleLiterals(command.name, ['time_literal_unit'], undefined, { + addComma: shouldAddComma, + advanceCursorAndOpenSuggestions: hasMoreMandatoryArgs, + }) ); } } @@ -1344,24 +1394,9 @@ async function getFunctionArgsSuggestions( // suggest a comma if there's another argument for the function suggestions.push(commaCompleteItem); } - // if there are other arguments in the function, inject automatically a comma after each suggestion - return suggestions.map((suggestion) => - suggestion !== commaCompleteItem - ? { - ...suggestion, - text: - hasMoreMandatoryArgs && fnDefinition.type !== 'builtin' - ? `${suggestion.text},` - : suggestion.text, - } - : suggestion - ); } - return suggestions.map(({ text, ...rest }) => ({ - ...rest, - text: addCommaIf(hasMoreMandatoryArgs && fnDefinition.type !== 'builtin' && text !== '', text), - })); + return suggestions; } async function getListArgsSuggestions( @@ -1401,7 +1436,7 @@ async function getListArgsSuggestions( const otherArgs = node.args.filter(Array.isArray).flat().filter(isColumnItem); suggestions.push( ...(await getFieldsOrFunctionsSuggestions( - [argType], + [argType as string], command.name, undefined, getFieldsByType, @@ -1516,7 +1551,7 @@ async function getOptionArgsSuggestions( innerText ); - if (isNewExpression || findPreviousWord(innerText) === 'WITH') { + if (isNewExpression || noCaseCompare(findPreviousWord(innerText), 'WITH')) { suggestions.push(buildNewVarDefinition(findNewVariable(anyEnhancedVariables))); } @@ -1590,19 +1625,6 @@ async function getOptionArgsSuggestions( } if (command.name === 'stats') { - suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - ['column'], - command.name, - option.name, - getFieldsByType, - { - functions: false, - fields: true, - } - )) - ); - const argDef = optionDef?.signature.params[argIndex]; const nodeArgType = extractFinalTypeFromArg(nodeArg, references); @@ -1618,13 +1640,29 @@ async function getOptionArgsSuggestions( option, { type: argDef?.type || 'any' }, nodeArg, - nodeArgType, + nodeArgType as string, references, getFieldsByType )) ); } } + + // If it's a complete expression then propose some final suggestions + if ( + (!nodeArgType && + option.name === 'by' && + option.args.length && + !isNewExpression && + !isAssignment(lastArg)) || + (isAssignment(lastArg) && isAssignmentComplete(lastArg)) + ) { + suggestions.push( + ...getFinalSuggestions({ + comma: optionDef?.signature.multipleParams ?? option.name === 'by', + }) + ); + } } if (optionDef) { @@ -1646,20 +1684,27 @@ async function getOptionArgsSuggestions( }) ); } else if (isNewExpression || (isAssignment(nodeArg) && !isAssignmentComplete(nodeArg))) { - // Otherwise try to complete the expression suggesting some columns suggestions.push( - ...(await getFieldsOrFunctionsSuggestions( - types[0] === 'column' ? ['any'] : types, - command.name, - option.name, - getFieldsByType, - { - functions: option.name === 'by', - fields: true, - } - )) + ...(await getFieldsByType(types[0] === 'column' ? ['any'] : types, [], { + advanceCursorAndOpenSuggestions: true, + })) ); + if (option.name === 'by') { + suggestions.push( + ...(await getFieldsOrFunctionsSuggestions( + types[0] === 'column' ? ['any'] : types, + command.name, + option.name, + getFieldsByType, + { + functions: true, + fields: false, + } + )) + ); + } + if (command.name === 'stats' && isNewExpression) { suggestions.push(buildNewVarDefinition(findNewVariable(anyVariables))); } diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts index e0ab600aa1382..640f62430f5df 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/complete_items.ts @@ -16,6 +16,7 @@ import { TRIGGER_SUGGESTION_COMMAND, buildConstantsDefinitions, } from './factories'; +import { FunctionParameterType, FunctionReturnType } from '../definitions/types'; export function getAssignmentDefinitionCompletitionItem() { const assignFn = builtinFunctions.find(({ name }) => name === '=')!; @@ -54,8 +55,8 @@ export const getNextTokenForNot = ( export const getBuiltinCompatibleFunctionDefinition = ( command: string, option: string | undefined, - argType: string, - returnTypes?: string[], + argType: FunctionParameterType, + returnTypes?: FunctionReturnType[], { skipAssign }: { skipAssign?: boolean } = {} ): SuggestionRawDefinition[] => { const compatibleFunctions = builtinFunctions.filter( @@ -81,9 +82,9 @@ export const getBuiltinCompatibleFunctionDefinition = ( .map(getSuggestionBuiltinDefinition); }; -export const commandAutocompleteDefinitions: SuggestionRawDefinition[] = getAllCommands().map( - getSuggestionCommandDefinition -); +export const commandAutocompleteDefinitions: SuggestionRawDefinition[] = getAllCommands() + .filter(({ hidden }) => !hidden) + .map(getSuggestionCommandDefinition); function buildCharCompleteItem( label: string, @@ -98,13 +99,16 @@ function buildCharCompleteItem( sortText, }; } -export const pipeCompleteItem = buildCharCompleteItem( - '|', - i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.pipeDoc', { +export const pipeCompleteItem: SuggestionRawDefinition = { + label: '|', + text: '| ', + kind: 'Keyword', + detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.pipeDoc', { defaultMessage: 'Pipe (|)', }), - { sortText: 'C', quoted: false } -); + sortText: 'C', + command: TRIGGER_SUGGESTION_COMMAND, +}; export const commaCompleteItem = buildCharCompleteItem( ',', diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts index f54511c8e304d..c9d0185d5c301 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts @@ -22,7 +22,8 @@ import { import { shouldBeQuotedSource, getCommandDefinition, shouldBeQuotedText } from '../shared/helpers'; import { buildDocumentation, buildFunctionDocumentation } from './documentation_util'; import { DOUBLE_BACKTICK, SINGLE_TICK_REGEX } from '../shared/constants'; -import type { ESQLRealField } from '../validation/types'; +import { ESQLRealField } from '../validation/types'; +import { isNumericType } from '../shared/esql_types'; const allFunctions = statsAggregationFunctionDefinitions .concat(evalFunctionDefinitions) @@ -101,7 +102,8 @@ export const getCompatibleFunctionDefinition = ( return fnSupportedByCommand .filter((mathDefinition) => mathDefinition.signatures.some( - (signature) => returnTypes[0] === 'any' || returnTypes.includes(signature.returnType) + (signature) => + returnTypes[0] === 'any' || returnTypes.includes(signature.returnType as string) ) ) .map(getSuggestionFunctionDefinition); @@ -129,7 +131,8 @@ export function getSuggestionCommandDefinition( } export const buildFieldsDefinitionsWithMetadata = ( - fields: ESQLRealField[] + fields: ESQLRealField[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => { return fields.map((field) => { const description = field.metadata?.description; @@ -137,7 +140,10 @@ export const buildFieldsDefinitionsWithMetadata = ( const titleCaseType = field.type.charAt(0).toUpperCase() + field.type.slice(1); return { label: field.name, - text: getSafeInsertText(field.name), + text: + getSafeInsertText(field.name) + + (options?.addComma ? ',' : '') + + (options?.advanceCursorAndOpenSuggestions ? ' ' : ''), kind: 'Variable', detail: titleCaseType, documentation: description @@ -150,6 +156,7 @@ ${description}`, : undefined, // If there is a description, it is a field from ECS, so it should be sorted to the top sortText: description ? '1D' : 'D', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, }; }); }; @@ -184,9 +191,8 @@ export const buildSourcesDefinitions = ( ): SuggestionRawDefinition[] => sources.map(({ name, isIntegration, title }) => ({ label: title ?? name, - text: getSafeInsertSourceText(name), + text: getSafeInsertSourceText(name) + (!isIntegration ? ' ' : ''), isSnippet: isIntegration, - ...(isIntegration && { command: TRIGGER_SUGGESTION_COMMAND }), kind: isIntegration ? 'Class' : 'Issue', detail: isIntegration ? i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.integrationDefinition', { @@ -196,16 +202,24 @@ export const buildSourcesDefinitions = ( defaultMessage: `Index`, }), sortText: 'A', + command: TRIGGER_SUGGESTION_COMMAND, })); export const buildConstantsDefinitions = ( userConstants: string[], detail?: string, - sortText?: string + sortText?: string, + /** + * Whether or not to advance the cursor and open the suggestions dialog after inserting the constant. + */ + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => userConstants.map((label) => ({ label, - text: label, + text: + label + + (options?.addComma ? ',' : '') + + (options?.advanceCursorAndOpenSuggestions ? ' ' : ''), kind: 'Constant', detail: detail ?? @@ -213,32 +227,35 @@ export const buildConstantsDefinitions = ( defaultMessage: `Constant`, }), sortText: sortText ?? 'A', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, })); export const buildValueDefinitions = ( values: string[], - detail?: string + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } ): SuggestionRawDefinition[] => values.map((value) => ({ label: `"${value}"`, - text: `"${value}"`, - detail: - detail ?? - i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.valueDefinition', { - defaultMessage: 'Literal value', - }), + text: `"${value}"${options?.addComma ? ',' : ''}${ + options?.advanceCursorAndOpenSuggestions ? ' ' : '' + }`, + detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.valueDefinition', { + defaultMessage: 'Literal value', + }), kind: 'Value', + command: options?.advanceCursorAndOpenSuggestions ? TRIGGER_SUGGESTION_COMMAND : undefined, })); export const buildNewVarDefinition = (label: string): SuggestionRawDefinition => { return { label, - text: `${label} =`, + text: `${label} = `, kind: 'Variable', detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.newVarDoc', { defaultMessage: 'Define a new variable', }), sortText: '1', + command: TRIGGER_SUGGESTION_COMMAND, }; }; @@ -357,21 +374,39 @@ export function getUnitDuration(unit: number = 1) { * "magical" logic. Maybe this is really the same thing as the literalOptions parameter * definition property... */ -export function getCompatibleLiterals(commandName: string, types: string[], names?: string[]) { +export function getCompatibleLiterals( + commandName: string, + types: string[], + names?: string[], + options?: { advanceCursorAndOpenSuggestions?: boolean; addComma?: boolean } +) { const suggestions: SuggestionRawDefinition[] = []; - if (types.includes('number')) { + if (types.some(isNumericType)) { if (commandName === 'limit') { // suggest 10/100/1000 for limit - suggestions.push(...buildConstantsDefinitions(['10', '100', '1000'], '')); + suggestions.push( + ...buildConstantsDefinitions(['10', '100', '1000'], '', undefined, { + advanceCursorAndOpenSuggestions: true, + }) + ); } } if (types.includes('time_literal')) { // filter plural for now and suggest only unit + singular - suggestions.push(...buildConstantsDefinitions(getUnitDuration(1))); // i.e. 1 year + suggestions.push( + ...buildConstantsDefinitions(getUnitDuration(1), undefined, undefined, options) + ); // i.e. 1 year } // this is a special type built from the suggestion system, not inherited from the AST if (types.includes('time_literal_unit')) { - suggestions.push(...buildConstantsDefinitions(timeUnitsToSuggest.map(({ name }) => name))); // i.e. year, month, ... + suggestions.push( + ...buildConstantsDefinitions( + timeUnitsToSuggest.map(({ name }) => name), + undefined, + undefined, + options + ) + ); // i.e. year, month, ... } if (types.includes('string')) { if (names) { @@ -382,25 +417,31 @@ export function getCompatibleLiterals(commandName: string, types: string[], name [commandName === 'grok' ? '"%{WORD:firstWord}"' : '"%{firstWord}"'], i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.aPatternString', { defaultMessage: 'A pattern string', - }) + }), + undefined, + options ) ); } else { - suggestions.push(...buildConstantsDefinitions(['string'], '')); + suggestions.push(...buildConstantsDefinitions(['string'], '', undefined, options)); } } } return suggestions; } -export function getDateLiterals() { +export function getDateLiterals(options?: { + advanceCursorAndOpenSuggestions?: boolean; + addComma?: boolean; +}) { return [ ...buildConstantsDefinitions( TIME_SYSTEM_PARAMS, i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.namedParamDefinition', { defaultMessage: 'Named parameter', }), - '1A' + '1A', + options ), { label: i18n.translate( diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts index f9586670b29e6..65f6601c51c13 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts @@ -80,3 +80,15 @@ export function removeQuoteForSuggestedSources(suggestions: SuggestionRawDefinit text: d.text.startsWith('"') && d.text.endsWith('"') ? d.text.slice(1, -1) : d.text, })); } + +export function getSupportedTypesForBinaryOperators( + fnDef: FunctionDefinition | undefined, + previousType: string +) { + // Retrieve list of all 'right' supported types that match the left hand side of the function + return fnDef && Array.isArray(fnDef?.signatures) + ? fnDef.signatures + .filter(({ params }) => params.find((p) => p.name === 'left' && p.type === previousType)) + .map(({ params }) => params[1].type) + : [previousType]; +} diff --git a/packages/kbn-esql-validation-autocomplete/src/code_actions/actions.test.ts b/packages/kbn-esql-validation-autocomplete/src/code_actions/actions.test.ts index a4ff739297201..cca1197ed629b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/code_actions/actions.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/code_actions/actions.test.ts @@ -11,32 +11,39 @@ import { validateQuery } from '../validation/validation'; import { getAllFunctions } from '../shared/helpers'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { CodeActionOptions } from './types'; +import { ESQLRealField } from '../validation/types'; +import { FieldType } from '../definitions/types'; function getCallbackMocks() { return { - getFieldsFor: jest.fn(async ({ query }) => - /enrich/.test(query) - ? [ - { name: 'otherField', type: 'string' }, - { name: 'yetAnotherField', type: 'number' }, - ] - : /unsupported_index/.test(query) - ? [{ name: 'unsupported_field', type: 'unsupported' }] - : [ - ...['string', 'number', 'date', 'boolean', 'ip'].map((type) => ({ - name: `${type}Field`, - type, - })), - { name: 'geoPointField', type: 'geo_point' }, - { name: 'any#Char$Field', type: 'number' }, - { name: 'kubernetes.something.something', type: 'number' }, - { - name: `listField`, - type: `list`, - }, - { name: '@timestamp', type: 'date' }, - ] - ), + getFieldsFor: jest.fn<Promise<ESQLRealField[]>, any>(async ({ query }) => { + if (/enrich/.test(query)) { + const fields: ESQLRealField[] = [ + { name: 'otherField', type: 'keyword' }, + { name: 'yetAnotherField', type: 'double' }, + ]; + return fields; + } + + if (/unsupported_index/.test(query)) { + const fields: ESQLRealField[] = [{ name: 'unsupported_field', type: 'unsupported' }]; + return fields; + } + + const localDataTypes: FieldType[] = ['keyword', 'double', 'date', 'boolean', 'ip']; + const fields: ESQLRealField[] = [ + ...localDataTypes.map((type) => ({ + name: `${type}Field`, + type, + })), + { name: 'geoPointField', type: 'geo_point' }, + { name: 'any#Char$Field', type: 'double' }, + { name: 'kubernetes.something.something', type: 'double' }, + { name: '@timestamp', type: 'date' }, + ]; + + return fields; + }), getSources: jest.fn(async () => ['index', '.secretIndex', 'my-index'].map((name) => ({ name, @@ -162,29 +169,29 @@ describe('quick fixes logic', () => { { relaxOnMissingCallbacks: false }, ]) { for (const command of ['KEEP', 'DROP', 'EVAL']) { - testQuickFixes(`FROM index | ${command} stringField`, [], options); - // strongField => stringField - testQuickFixes(`FROM index | ${command} strongField`, ['stringField'], options); + testQuickFixes(`FROM index | ${command} keywordField`, [], options); + // koywordField => keywordField + testQuickFixes(`FROM index | ${command} koywordField`, ['keywordField'], options); testQuickFixes( - `FROM index | ${command} numberField, strongField`, - ['stringField'], + `FROM index | ${command} numberField, koywordField`, + ['keywordField'], options ); } - testQuickFixes(`FROM index | EVAL round(strongField)`, ['stringField'], options); - testQuickFixes(`FROM index | EVAL var0 = round(strongField)`, ['stringField'], options); - testQuickFixes(`FROM index | WHERE round(strongField) > 0`, ['stringField'], options); - testQuickFixes(`FROM index | WHERE 0 < round(strongField)`, ['stringField'], options); - testQuickFixes(`FROM index | RENAME strongField as newField`, ['stringField'], options); + testQuickFixes(`FROM index | EVAL round(koywordField)`, ['keywordField'], options); + testQuickFixes(`FROM index | EVAL var0 = round(koywordField)`, ['keywordField'], options); + testQuickFixes(`FROM index | WHERE round(koywordField) > 0`, ['keywordField'], options); + testQuickFixes(`FROM index | WHERE 0 < round(koywordField)`, ['keywordField'], options); + testQuickFixes(`FROM index | RENAME koywordField as newField`, ['keywordField'], options); // This levarage the knowledge of the enrich policy fields to suggest the right field testQuickFixes( `FROM index | ENRICH policy | KEEP yetAnotherField2`, ['yetAnotherField'], options ); - testQuickFixes(`FROM index | ENRICH policy ON strongField`, ['stringField'], options); + testQuickFixes(`FROM index | ENRICH policy ON koywordField`, ['keywordField'], options); testQuickFixes( - `FROM index | ENRICH policy ON stringField WITH yetAnotherField2`, + `FROM index | ENRICH policy ON keywordField WITH yetAnotherField2`, ['yetAnotherField'], options ); @@ -209,29 +216,29 @@ describe('quick fixes logic', () => { { relaxOnMissingCallbacks: false }, ]) { for (const command of ['KEEP', 'DROP', 'EVAL']) { - testQuickFixes(`FROM index | ${command} stringField`, [], options); - // strongField => stringField - testQuickFixes(`FROM index | ${command} strongField`, ['stringField'], options); + testQuickFixes(`FROM index | ${command} keywordField`, [], options); + // koywordField => keywordField + testQuickFixes(`FROM index | ${command} koywordField`, ['keywordField'], options); testQuickFixes( - `FROM index | ${command} numberField, strongField`, - ['stringField'], + `FROM index | ${command} numberField, koywordField`, + ['keywordField'], options ); } - testQuickFixes(`FROM index | EVAL round(strongField)`, ['stringField'], options); - testQuickFixes(`FROM index | EVAL var0 = round(strongField)`, ['stringField'], options); - testQuickFixes(`FROM index | WHERE round(strongField) > 0`, ['stringField'], options); - testQuickFixes(`FROM index | WHERE 0 < round(strongField)`, ['stringField'], options); - testQuickFixes(`FROM index | RENAME strongField as newField`, ['stringField'], options); + testQuickFixes(`FROM index | EVAL round(koywordField)`, ['keywordField'], options); + testQuickFixes(`FROM index | EVAL var0 = round(koywordField)`, ['keywordField'], options); + testQuickFixes(`FROM index | WHERE round(koywordField) > 0`, ['keywordField'], options); + testQuickFixes(`FROM index | WHERE 0 < round(koywordField)`, ['keywordField'], options); + testQuickFixes(`FROM index | RENAME koywordField as newField`, ['keywordField'], options); // This levarage the knowledge of the enrich policy fields to suggest the right field testQuickFixes( `FROM index | ENRICH policy | KEEP yetAnotherField2`, ['yetAnotherField'], options ); - testQuickFixes(`FROM index | ENRICH policy ON strongField`, ['stringField'], options); + testQuickFixes(`FROM index | ENRICH policy ON koywordField`, ['keywordField'], options); testQuickFixes( - `FROM index | ENRICH policy ON stringField WITH yetAnotherField2`, + `FROM index | ENRICH policy ON keywordField WITH yetAnotherField2`, ['yetAnotherField'], options ); @@ -329,8 +336,8 @@ describe('quick fixes logic', () => { { relaxOnMissingCallbacks: false }, { relaxOnMissingCallbacks: false }, ]) { - testQuickFixes(`FROM index | WHERE stringField like 'asda'`, ['"asda"'], options); - testQuickFixes(`FROM index | WHERE stringField not like 'asda'`, ['"asda"'], options); + testQuickFixes(`FROM index | WHERE keywordField like 'asda'`, ['"asda"'], options); + testQuickFixes(`FROM index | WHERE keywordField not like 'asda'`, ['"asda"'], options); } }); @@ -407,7 +414,7 @@ describe('quick fixes logic', () => { describe('callbacks', () => { it('should not crash if specific callback functions are not passed', async () => { const callbackMocks = getCallbackMocks(); - const statement = `from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`; + const statement = `from a | eval b = a | enrich policy | dissect keywordField "%{firstWord}"`; const { errors } = await validateQuery( statement, getAstAndSyntaxErrors, @@ -427,7 +434,7 @@ describe('quick fixes logic', () => { it('should not crash if specific callback functions are not passed with relaxed option', async () => { const callbackMocks = getCallbackMocks(); - const statement = `from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`; + const statement = `from a | eval b = a | enrich policy | dissect keywordField "%{firstWord}"`; const { errors } = await validateQuery( statement, getAstAndSyntaxErrors, @@ -453,7 +460,7 @@ describe('quick fixes logic', () => { it('should not crash no callbacks are passed', async () => { const callbackMocks = getCallbackMocks(); - const statement = `from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`; + const statement = `from a | eval b = a | enrich policy | dissect keywordField "%{firstWord}"`; const { errors } = await validateQuery( statement, getAstAndSyntaxErrors, @@ -469,7 +476,7 @@ describe('quick fixes logic', () => { it('should not crash no callbacks are passed with relaxed option', async () => { const callbackMocks = getCallbackMocks(); - const statement = `from a | eval b = a | enrich policy | dissect stringField "%{firstWord}"`; + const statement = `from a | eval b = a | enrich policy | dissect keywordField "%{firstWord}"`; const { errors } = await validateQuery( statement, getAstAndSyntaxErrors, diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts index a27b8a68a9be0..262d7a2fc7b32 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts @@ -7,15 +7,18 @@ */ import { i18n } from '@kbn/i18n'; -import type { FunctionDefinition, FunctionParameterType } from './types'; +import { ESQL_COMMON_NUMERIC_TYPES, ESQL_NUMBER_TYPES } from '../shared/esql_types'; +import type { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types'; function createNumericAggDefinition({ name, description, + returnType, args = [], }: { name: string; description: string; + returnType?: (numericType: FunctionParameterType) => FunctionReturnType; args?: Array<{ name: string; type: FunctionParameterType; @@ -28,11 +31,11 @@ function createNumericAggDefinition({ name, type: 'agg', description, - supportedCommands: ['stats', 'metrics'], + supportedCommands: ['stats', 'inlinestats', 'metrics'], signatures: [ - { + ...ESQL_NUMBER_TYPES.map((numericType) => ({ params: [ - { name: 'column', type: 'number', noNestingFunctions: true }, + { name: 'column', type: numericType, noNestingFunctions: true }, ...args.map(({ name: paramName, type, constantOnly }) => ({ name: paramName, type, @@ -40,8 +43,8 @@ function createNumericAggDefinition({ constantOnly, })), ], - returnType: 'number', - }, + returnType: returnType ? returnType(numericType) : numericType, + })), ], examples: [ `from index | stats result = ${name}(field${extraParamsExample})`, @@ -56,18 +59,28 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.avgDoc', { defaultMessage: 'Returns the average of the values in a field', }), + returnType: () => 'double' as FunctionReturnType, }, { name: 'sum', description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.sumDoc', { defaultMessage: 'Returns the sum of the values in a field.', }), + returnType: (numericType: FunctionParameterType): FunctionReturnType => { + switch (numericType) { + case 'double': + return 'double'; + default: + return 'long'; + } + }, }, { name: 'median', description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.medianDoc', { defaultMessage: 'Returns the 50% percentile.', }), + returnType: () => 'double' as FunctionReturnType, }, { name: 'median_absolute_deviation', @@ -78,35 +91,61 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ 'Returns the median of each data point’s deviation from the median of the entire sample.', } ), - }, - { - name: 'percentile', - description: i18n.translate( - 'kbn-esql-validation-autocomplete.esql.definitions.percentiletDoc', - { - defaultMessage: 'Returns the n percentile of a field.', - } - ), - args: [{ name: 'percentile', type: 'number' as const, value: '90', constantOnly: true }], + returnType: () => 'double' as FunctionReturnType, }, ] .map(createNumericAggDefinition) .concat([ + { + name: 'percentile', + description: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.definitions.percentiletDoc', + { + defaultMessage: 'Returns the n percentile of a field.', + } + ), + type: 'agg', + supportedCommands: ['stats', 'inlinestats', 'metrics'], + signatures: [ + ...ESQL_COMMON_NUMERIC_TYPES.map((numericType: FunctionParameterType) => { + return ESQL_COMMON_NUMERIC_TYPES.map((weightType: FunctionParameterType) => ({ + params: [ + { + name: 'column', + type: numericType, + noNestingFunctions: true, + }, + { + name: 'percentile', + type: weightType, + noNestingFunctions: true, + constantOnly: true, + }, + ], + returnType: 'double' as FunctionReturnType, + })); + }).flat(), + ], + }, { name: 'max', description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.maxDoc', { defaultMessage: 'Returns the maximum value in a field.', }), type: 'agg', - supportedCommands: ['stats', 'metrics'], + supportedCommands: ['stats', 'inlinestats', 'metrics'], signatures: [ + ...ESQL_COMMON_NUMERIC_TYPES.map((type) => ({ + params: [{ name: 'column', type, noNestingFunctions: true }], + returnType: type, + })), { - params: [{ name: 'column', type: 'number', noNestingFunctions: true }], - returnType: 'number', + params: [{ name: 'column', type: 'date', noNestingFunctions: true }], + returnType: 'date', }, { - params: [{ name: 'column', type: 'date', noNestingFunctions: true }], - returnType: 'number', + params: [{ name: 'column', type: 'date_period', noNestingFunctions: true }], + returnType: 'date_period', }, { params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }], @@ -125,15 +164,19 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ defaultMessage: 'Returns the minimum value in a field.', }), type: 'agg', - supportedCommands: ['stats', 'metrics'], + supportedCommands: ['stats', 'inlinestats', 'metrics'], signatures: [ + ...ESQL_COMMON_NUMERIC_TYPES.map((type) => ({ + params: [{ name: 'column', type, noNestingFunctions: true }], + returnType: type, + })), { - params: [{ name: 'column', type: 'number', noNestingFunctions: true }], - returnType: 'number', + params: [{ name: 'column', type: 'date', noNestingFunctions: true }], + returnType: 'date', }, { - params: [{ name: 'column', type: 'date', noNestingFunctions: true }], - returnType: 'number', + params: [{ name: 'column', type: 'date_period', noNestingFunctions: true }], + returnType: 'date_period', }, { params: [{ name: 'column', type: 'boolean', noNestingFunctions: true }], @@ -154,7 +197,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.countDoc', { defaultMessage: 'Returns the count of the values in a field.', }), - supportedCommands: ['stats', 'metrics'], + supportedCommands: ['stats', 'inlinestats', 'metrics'], signatures: [ { params: [ @@ -166,7 +209,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ optional: true, }, ], - returnType: 'number', + returnType: 'long', }, ], examples: [`from index | stats result = count(field)`, `from index | stats count(field)`], @@ -180,14 +223,19 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ defaultMessage: 'Returns the count of distinct values in a field.', } ), - supportedCommands: ['stats', 'metrics'], + supportedCommands: ['stats', 'inlinestats', 'metrics'], signatures: [ { params: [ { name: 'column', type: 'any', noNestingFunctions: true }, - { name: 'precision', type: 'number', noNestingFunctions: true, optional: true }, + ...ESQL_NUMBER_TYPES.map((type) => ({ + name: 'precision', + type, + noNestingFunctions: true, + optional: true, + })), ], - returnType: 'number', + returnType: 'long', }, ], examples: [ @@ -204,7 +252,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ defaultMessage: 'Returns the count of distinct values in a field.', } ), - supportedCommands: ['stats', 'metrics'], + supportedCommands: ['stats', 'inlinestats', 'metrics'], signatures: [ { params: [{ name: 'column', type: 'cartesian_point', noNestingFunctions: true }], @@ -258,14 +306,14 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ }, { name: 'limit', - type: 'number', + type: 'integer', noNestingFunctions: true, optional: false, constantOnly: true, }, { name: 'order', - type: 'string', + type: 'keyword', noNestingFunctions: true, optional: false, constantOnly: true, @@ -290,25 +338,27 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ 'An aggregation that computes the weighted average of numeric values that are extracted from the aggregated documents.', } ), - supportedCommands: ['stats', 'metrics'], + supportedCommands: ['stats', 'inlinestats', 'metrics'], signatures: [ - { - params: [ - { - name: 'number', - type: 'number', - noNestingFunctions: true, - optional: false, - }, - { - name: 'weight', - type: 'number', - noNestingFunctions: true, - optional: false, - }, - ], - returnType: 'number', - }, + ...ESQL_COMMON_NUMERIC_TYPES.map((numericType: FunctionParameterType) => { + return ESQL_COMMON_NUMERIC_TYPES.map((weightType: FunctionParameterType) => ({ + params: [ + { + name: 'number', + type: numericType, + noNestingFunctions: true, + optional: false, + }, + { + name: 'weight', + type: weightType, + noNestingFunctions: true, + optional: false, + }, + ], + returnType: 'double' as FunctionReturnType, + })); + }).flat(), ], examples: [ `from employees | stats w_avg = weighted_avg(salary, height) by languages | eval w_avg = round(w_avg)`, diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts index a32cba3d191c3..d3a510ff3afad 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts @@ -7,14 +7,14 @@ */ import { i18n } from '@kbn/i18n'; +import { ESQL_NUMBER_TYPES, isNumericType } from '../shared/esql_types'; import type { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types'; +type MathFunctionSignature = [FunctionParameterType, FunctionParameterType, FunctionReturnType]; + function createMathDefinition( name: string, - types: Array< - | (FunctionParameterType & FunctionReturnType) - | [FunctionParameterType, FunctionParameterType, FunctionReturnType] - >, + functionSignatures: MathFunctionSignature[], description: string, validate?: FunctionDefinition['validate'] ): FunctionDefinition { @@ -24,28 +24,41 @@ function createMathDefinition( description, supportedCommands: ['eval', 'where', 'row', 'stats', 'metrics', 'sort'], supportedOptions: ['by'], - signatures: types.map((type) => { - if (Array.isArray(type)) { - return { - params: [ - { name: 'left', type: type[0] }, - { name: 'right', type: type[1] }, - ], - returnType: type[2], - }; - } + signatures: functionSignatures.map((functionSignature) => { + const [lhs, rhs, result] = functionSignature; return { params: [ - { name: 'left', type }, - { name: 'right', type }, + { name: 'left', type: lhs }, + { name: 'right', type: rhs }, ], - returnType: type, + returnType: result, }; }), validate, }; } +// https://www.elastic.co/guide/en/elasticsearch/reference/master/esql-functions-operators.html#_less_than +const baseComparisonTypeTable: MathFunctionSignature[] = [ + ['date', 'date', 'boolean'], + ['double', 'double', 'boolean'], + ['double', 'integer', 'boolean'], + ['double', 'long', 'boolean'], + ['integer', 'double', 'boolean'], + ['integer', 'integer', 'boolean'], + ['integer', 'long', 'boolean'], + ['ip', 'ip', 'boolean'], + ['keyword', 'keyword', 'boolean'], + ['keyword', 'text', 'boolean'], + ['long', 'double', 'boolean'], + ['long', 'integer', 'boolean'], + ['long', 'long', 'boolean'], + ['text', 'keyword', 'boolean'], + ['text', 'text', 'boolean'], + ['unsigned_long', 'unsigned_long', 'boolean'], + ['version', 'version', 'boolean'], +]; + function createComparisonDefinition( { name, @@ -58,6 +71,17 @@ function createComparisonDefinition( }, validate?: FunctionDefinition['validate'] ): FunctionDefinition { + const commonSignatures = baseComparisonTypeTable.map((functionSignature) => { + const [lhs, rhs, result] = functionSignature; + return { + params: [ + { name: 'left', type: lhs }, + { name: 'right', type: rhs }, + ], + returnType: result, + }; + }); + return { type: 'builtin' as const, name, @@ -66,41 +90,7 @@ function createComparisonDefinition( supportedOptions: ['by'], validate, signatures: [ - { - params: [ - { name: 'left', type: 'number' }, - { name: 'right', type: 'number' }, - ], - returnType: 'boolean', - }, - { - params: [ - { name: 'left', type: 'string' }, - { name: 'right', type: 'string' }, - ], - returnType: 'boolean', - }, - { - params: [ - { name: 'left', type: 'date' }, - { name: 'right', type: 'date' }, - ], - returnType: 'boolean', - }, - { - params: [ - { name: 'left', type: 'ip' }, - { name: 'right', type: 'ip' }, - ], - returnType: 'boolean', - }, - { - params: [ - { name: 'left', type: 'version' }, - { name: 'right', type: 'version' }, - ], - returnType: 'boolean', - }, + ...commonSignatures, // constant strings okay because of implicit casting for // string to version and ip // @@ -113,13 +103,13 @@ function createComparisonDefinition( { params: [ { name: 'left', type }, - { name: 'right', type: 'string' as const, constantOnly: true }, + { name: 'right', type: 'text' as const, constantOnly: true }, ], returnType: 'boolean' as const, }, { params: [ - { name: 'right', type: 'string' as const, constantOnly: true }, + { name: 'left', type: 'text' as const, constantOnly: true }, { name: 'right', type }, ], returnType: 'boolean' as const, @@ -130,31 +120,111 @@ function createComparisonDefinition( }; } +const addTypeTable: MathFunctionSignature[] = [ + ['date_period', 'date_period', 'date_period'], + ['date_period', 'date', 'date'], + ['date', 'date_period', 'date'], + ['date', 'time_duration', 'date'], + ['date', 'time_literal', 'date'], + ['double', 'double', 'double'], + ['double', 'integer', 'double'], + ['double', 'long', 'double'], + ['integer', 'double', 'double'], + ['integer', 'integer', 'integer'], + ['integer', 'long', 'long'], + ['long', 'double', 'double'], + ['long', 'integer', 'long'], + ['long', 'long', 'long'], + ['time_duration', 'date', 'date'], + ['time_duration', 'time_duration', 'time_duration'], + ['unsigned_long', 'unsigned_long', 'unsigned_long'], + ['time_literal', 'date', 'date'], +]; + +const subtractTypeTable: MathFunctionSignature[] = [ + ['date_period', 'date_period', 'date_period'], + ['date', 'date_period', 'date'], + ['date', 'time_duration', 'date'], + ['date', 'time_literal', 'date'], + ['double', 'double', 'double'], + ['double', 'integer', 'double'], + ['double', 'long', 'double'], + ['integer', 'double', 'double'], + ['integer', 'integer', 'integer'], + ['integer', 'long', 'long'], + ['long', 'double', 'double'], + ['long', 'integer', 'long'], + ['long', 'long', 'long'], + ['time_duration', 'date', 'date'], + ['time_duration', 'time_duration', 'time_duration'], + ['unsigned_long', 'unsigned_long', 'unsigned_long'], + ['time_literal', 'date', 'date'], +]; + +const multiplyTypeTable: MathFunctionSignature[] = [ + ['double', 'double', 'double'], + ['double', 'integer', 'double'], + ['double', 'long', 'double'], + ['integer', 'double', 'double'], + ['integer', 'integer', 'integer'], + ['integer', 'long', 'long'], + ['long', 'double', 'double'], + ['long', 'integer', 'long'], + ['long', 'long', 'long'], + ['unsigned_long', 'unsigned_long', 'unsigned_long'], +]; + +const divideTypeTable: MathFunctionSignature[] = [ + ['double', 'double', 'double'], + ['double', 'integer', 'double'], + ['double', 'long', 'double'], + ['integer', 'double', 'double'], + ['integer', 'integer', 'integer'], + ['integer', 'long', 'long'], + ['long', 'double', 'double'], + ['long', 'integer', 'long'], + ['long', 'long', 'long'], + ['unsigned_long', 'unsigned_long', 'unsigned_long'], +]; + +const modulusTypeTable: MathFunctionSignature[] = [ + ['double', 'double', 'double'], + ['double', 'integer', 'double'], + ['double', 'long', 'double'], + ['integer', 'double', 'double'], + ['integer', 'integer', 'integer'], + ['integer', 'long', 'long'], + ['long', 'double', 'double'], + ['long', 'integer', 'long'], + ['long', 'long', 'long'], + ['unsigned_long', 'unsigned_long', 'unsigned_long'], +]; + export const mathFunctions: FunctionDefinition[] = [ createMathDefinition( '+', - ['number', ['date', 'time_literal', 'date'], ['time_literal', 'date', 'date']], + addTypeTable, i18n.translate('kbn-esql-validation-autocomplete.esql.definition.addDoc', { defaultMessage: 'Add (+)', }) ), createMathDefinition( '-', - ['number', ['date', 'time_literal', 'date'], ['time_literal', 'date', 'date']], + subtractTypeTable, i18n.translate('kbn-esql-validation-autocomplete.esql.definition.subtractDoc', { defaultMessage: 'Subtract (-)', }) ), createMathDefinition( '*', - ['number'], + multiplyTypeTable, i18n.translate('kbn-esql-validation-autocomplete.esql.definition.multiplyDoc', { defaultMessage: 'Multiply (*)', }) ), createMathDefinition( '/', - ['number'], + divideTypeTable, i18n.translate('kbn-esql-validation-autocomplete.esql.definition.divideDoc', { defaultMessage: 'Divide (/)', }), @@ -162,7 +232,7 @@ export const mathFunctions: FunctionDefinition[] = [ const [left, right] = fnDef.args; const messages = []; if (!Array.isArray(left) && !Array.isArray(right)) { - if (right.type === 'literal' && right.literalType === 'number') { + if (right.type === 'literal' && isNumericType(right.literalType)) { if (right.value === 0) { messages.push({ type: 'warning' as const, @@ -187,7 +257,7 @@ export const mathFunctions: FunctionDefinition[] = [ ), createMathDefinition( '%', - ['number'], + modulusTypeTable, i18n.translate('kbn-esql-validation-autocomplete.esql.definition.moduleDoc', { defaultMessage: 'Module (%)', }), @@ -195,7 +265,7 @@ export const mathFunctions: FunctionDefinition[] = [ const [left, right] = fnDef.args; const messages = []; if (!Array.isArray(left) && !Array.isArray(right)) { - if (right.type === 'literal' && right.literalType === 'number') { + if (right.type === 'literal' && isNumericType(right.literalType)) { if (right.value === 0) { messages.push({ type: 'warning' as const, @@ -238,13 +308,13 @@ const comparisonFunctions: FunctionDefinition[] = [ { params: [ { name: 'left', type: 'boolean' as const }, - { name: 'right', type: 'string' as const, constantOnly: true }, + { name: 'right', type: 'keyword' as const, constantOnly: true }, ], returnType: 'boolean' as const, }, { params: [ - { name: 'right', type: 'string' as const, constantOnly: true }, + { name: 'left', type: 'keyword' as const, constantOnly: true }, { name: 'right', type: 'boolean' as const }, ], returnType: 'boolean' as const, @@ -268,13 +338,13 @@ const comparisonFunctions: FunctionDefinition[] = [ { params: [ { name: 'left', type: 'boolean' as const }, - { name: 'right', type: 'string' as const, constantOnly: true }, + { name: 'right', type: 'keyword' as const, constantOnly: true }, ], returnType: 'boolean' as const, }, { params: [ - { name: 'right', type: 'string' as const, constantOnly: true }, + { name: 'left', type: 'keyword' as const, constantOnly: true }, { name: 'right', type: 'boolean' as const }, ], returnType: 'boolean' as const, @@ -347,8 +417,15 @@ const likeFunctions: FunctionDefinition[] = [ signatures: [ { params: [ - { name: 'left', type: 'string' as const }, - { name: 'right', type: 'string' as const }, + { name: 'left', type: 'text' as const }, + { name: 'right', type: 'text' as const }, + ], + returnType: 'boolean', + }, + { + params: [ + { name: 'left', type: 'keyword' as const }, + { name: 'right', type: 'keyword' as const }, ], returnType: 'boolean', }, @@ -383,17 +460,24 @@ const inFunctions: FunctionDefinition[] = [ description, supportedCommands: ['eval', 'where', 'row', 'sort'], signatures: [ - { + ...ESQL_NUMBER_TYPES.map((type) => ({ params: [ - { name: 'left', type: 'number' }, + { name: 'left', type: type as FunctionParameterType }, + { name: 'right', type: 'any[]' as FunctionParameterType }, + ], + returnType: 'boolean' as FunctionReturnType, + })), + { + params: [ + { name: 'left', type: 'keyword' }, { name: 'right', type: 'any[]' }, ], returnType: 'boolean', }, { params: [ - { name: 'left', type: 'string' }, + { name: 'left', type: 'text' }, { name: 'right', type: 'any[]' }, ], returnType: 'boolean', @@ -507,7 +591,16 @@ const otherDefinitions: FunctionDefinition[] = [ description: i18n.translate('kbn-esql-validation-autocomplete.esql.definition.assignDoc', { defaultMessage: 'Assign (=)', }), - supportedCommands: ['eval', 'stats', 'metrics', 'row', 'dissect', 'where', 'enrich'], + supportedCommands: [ + 'eval', + 'stats', + 'inlinestats', + 'metrics', + 'row', + 'dissect', + 'where', + 'enrich', + ], supportedOptions: ['by', 'with'], signatures: [ { diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts index 033518ac16c37..1718841711602 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts @@ -32,6 +32,121 @@ import { } from './options'; import type { CommandDefinition } from './types'; +const statsValidator = (command: ESQLCommand) => { + const messages: ESQLMessage[] = []; + const commandName = command.name.toUpperCase(); + if (!command.args.length) { + messages.push({ + location: command.location, + text: i18n.translate('kbn-esql-validation-autocomplete.esql.validation.statsNoArguments', { + defaultMessage: + 'At least one aggregation or grouping expression required in [{commandName}]', + values: { commandName }, + }), + type: 'error', + code: 'statsNoArguments', + }); + } + + // now that all functions are supported, there's a specific check to perform + // unfortunately the logic here is a bit complex as it needs to dig deeper into the args + // until an agg function is detected + // in the long run this might be integrated into the validation function + const statsArg = command.args + .flatMap((arg) => (isAssignment(arg) ? arg.args[1] : arg)) + .filter(isFunctionItem); + + if (statsArg.length) { + function isAggFunction(arg: ESQLAstItem): arg is ESQLFunction { + return isFunctionItem(arg) && getFunctionDefinition(arg.name)?.type === 'agg'; + } + function isOtherFunction(arg: ESQLAstItem): arg is ESQLFunction { + return isFunctionItem(arg) && getFunctionDefinition(arg.name)?.type !== 'agg'; + } + + function checkAggExistence(arg: ESQLFunction): boolean { + // TODO the grouping function check may not + // hold true for all future cases + if (isAggFunction(arg)) { + return true; + } + if (isOtherFunction(arg)) { + return (arg as ESQLFunction).args.filter(isFunctionItem).some(checkAggExistence); + } + return false; + } + // first check: is there an agg function somewhere? + const noAggsExpressions = statsArg.filter((arg) => !checkAggExistence(arg)); + + if (noAggsExpressions.length) { + messages.push( + ...noAggsExpressions.map((fn) => ({ + location: fn.location, + text: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.validation.statsNoAggFunction', + { + defaultMessage: + 'At least one aggregation function required in [{commandName}], found [{expression}]', + values: { + expression: fn.text, + commandName, + }, + } + ), + type: 'error' as const, + code: 'statsNoAggFunction', + })) + ); + } else { + function isConstantOrAggFn(arg: ESQLAstItem): boolean { + return isLiteralItem(arg) || isAggFunction(arg); + } + // now check that: + // * the agg function is at root level + // * or if it's a builtin function, then all operands are agg functions or literals + // * or if it's a eval function then all arguments are agg functions or literals + function checkFunctionContent(arg: ESQLFunction) { + // TODO the grouping function check may not + // hold true for all future cases + if (isAggFunction(arg)) { + return true; + } + return (arg as ESQLFunction).args.every( + (subArg): boolean => + isConstantOrAggFn(subArg) || + (isOtherFunction(subArg) ? checkFunctionContent(subArg) : false) + ); + } + // @TODO: improve here the check to get the last instance of the invalidExpression + // to provide a better location for the error message + // i.e. STATS round(round(round( a + sum(b) ))) + // should return the location of the + node, just before the agg one + const invalidExpressions = statsArg.filter((arg) => !checkFunctionContent(arg)); + + if (invalidExpressions.length) { + messages.push( + ...invalidExpressions.map((fn) => ({ + location: fn.location, + text: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.validation.noCombinationOfAggAndNonAggValues', + { + defaultMessage: + 'Cannot combine aggregation and non-aggregation values in [{commandName}], found [{expression}]', + values: { + expression: fn.text, + commandName, + }, + } + ), + type: 'error' as const, + code: 'statsNoCombinationOfAggAndNonAggValues', + })) + ); + } + } + } + return messages; +}; export const commandDefinitions: CommandDefinition[] = [ { name: 'row', @@ -77,6 +192,7 @@ export const commandDefinitions: CommandDefinition[] = [ }, { name: 'metrics', + hidden: true, description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.metricsDoc', { defaultMessage: 'A metrics-specific source command, use this command to load data from TSDB indices. ' + @@ -118,120 +234,29 @@ export const commandDefinitions: CommandDefinition[] = [ }, options: [byOption], modes: [], - validate: (command: ESQLCommand) => { - const messages: ESQLMessage[] = []; - if (!command.args.length) { - messages.push({ - location: command.location, - text: i18n.translate( - 'kbn-esql-validation-autocomplete.esql.validation.statsNoArguments', - { - defaultMessage: 'At least one aggregation or grouping expression required in [STATS]', - } - ), - type: 'error', - code: 'statsNoArguments', - }); - } - - // now that all functions are supported, there's a specific check to perform - // unfortunately the logic here is a bit complex as it needs to dig deeper into the args - // until an agg function is detected - // in the long run this might be integrated into the validation function - const statsArg = command.args - .flatMap((arg) => (isAssignment(arg) ? arg.args[1] : arg)) - .filter(isFunctionItem); - - if (statsArg.length) { - function isAggFunction(arg: ESQLAstItem): arg is ESQLFunction { - return isFunctionItem(arg) && getFunctionDefinition(arg.name)?.type === 'agg'; - } - function isOtherFunction(arg: ESQLAstItem): arg is ESQLFunction { - return isFunctionItem(arg) && getFunctionDefinition(arg.name)?.type !== 'agg'; - } - - function checkAggExistence(arg: ESQLFunction): boolean { - // TODO the grouping function check may not - // hold true for all future cases - if (isAggFunction(arg)) { - return true; - } - if (isOtherFunction(arg)) { - return (arg as ESQLFunction).args.filter(isFunctionItem).some(checkAggExistence); - } - return false; - } - // first check: is there an agg function somewhere? - const noAggsExpressions = statsArg.filter((arg) => !checkAggExistence(arg)); - - if (noAggsExpressions.length) { - messages.push( - ...noAggsExpressions.map((fn) => ({ - location: fn.location, - text: i18n.translate( - 'kbn-esql-validation-autocomplete.esql.validation.statsNoAggFunction', - { - defaultMessage: - 'At least one aggregation function required in [STATS], found [{expression}]', - values: { - expression: fn.text, - }, - } - ), - type: 'error' as const, - code: 'statsNoAggFunction', - })) - ); - } else { - function isConstantOrAggFn(arg: ESQLAstItem): boolean { - return isLiteralItem(arg) || isAggFunction(arg); - } - // now check that: - // * the agg function is at root level - // * or if it's a builtin function, then all operands are agg functions or literals - // * or if it's a eval function then all arguments are agg functions or literals - function checkFunctionContent(arg: ESQLFunction) { - // TODO the grouping function check may not - // hold true for all future cases - if (isAggFunction(arg)) { - return true; - } - return (arg as ESQLFunction).args.every( - (subArg): boolean => - isConstantOrAggFn(subArg) || - (isOtherFunction(subArg) ? checkFunctionContent(subArg) : false) - ); - } - // @TODO: improve here the check to get the last instance of the invalidExpression - // to provide a better location for the error message - // i.e. STATS round(round(round( a + sum(b) ))) - // should return the location of the + node, just before the agg one - const invalidExpressions = statsArg.filter((arg) => !checkFunctionContent(arg)); - - if (invalidExpressions.length) { - messages.push( - ...invalidExpressions.map((fn) => ({ - location: fn.location, - text: i18n.translate( - 'kbn-esql-validation-autocomplete.esql.validation.noCombinationOfAggAndNonAggValues', - { - defaultMessage: - 'Cannot combine aggregation and non-aggregation values in [STATS], found [{expression}]', - values: { - expression: fn.text, - }, - } - ), - type: 'error' as const, - code: 'statsNoCombinationOfAggAndNonAggValues', - })) - ); - } - } + validate: statsValidator, + }, + { + name: 'inlinestats', + hidden: true, + description: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.definitions.inlineStatsDoc', + { + defaultMessage: + 'Calculates an aggregate result and merges that result back into the stream of input data. Without the optional `BY` clause this will produce a single result which is appended to each row. With a `BY` clause this will produce one result per grouping and merge the result into the stream based on matching group keys.', } - return messages; + ), + examples: ['… | EVAL bar = a * b | INLINESTATS m = MAX(bar) BY b'], + signature: { + multipleParams: true, + params: [{ name: 'expression', type: 'function', optional: true }], }, + options: [byOption], + modes: [], + // Reusing the same validation logic as stats command + validate: statsValidator, }, + { name: 'eval', description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.evalDoc', { @@ -273,7 +298,7 @@ export const commandDefinitions: CommandDefinition[] = [ examples: ['… | limit 100', '… | limit 0'], signature: { multipleParams: false, - params: [{ name: 'size', type: 'number', constantOnly: true }], + params: [{ name: 'size', type: 'integer', constantOnly: true }], }, options: [], modes: [], @@ -390,6 +415,7 @@ export const commandDefinitions: CommandDefinition[] = [ signature: { multipleParams: false, params: [ + // innerType: 'string' is interpreted as keyword and text (see columnParamsWithInnerTypes) { name: 'column', type: 'column', innerType: 'string' }, { name: 'pattern', type: 'string', constantOnly: true }, ], @@ -407,6 +433,7 @@ export const commandDefinitions: CommandDefinition[] = [ signature: { multipleParams: false, params: [ + // innerType: 'string' is interpreted as keyword and text (see columnParamsWithInnerTypes) { name: 'column', type: 'column', innerType: 'string' }, { name: 'pattern', type: 'string', constantOnly: true }, ], diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts index a02a09757d033..14626048ced92 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts @@ -43,14 +43,44 @@ const absDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', optional: false, }, ], - returnType: 'number', + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'unsigned_long', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -72,14 +102,44 @@ const acosDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=.9\n| EVAL acos=ACOS(a)'], @@ -99,85 +159,56 @@ const asinDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a=.9\n| EVAL asin=ASIN(a)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const atanDefinition: FunctionDefinition = { - type: 'eval', - name: 'atan', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.atan', { - defaultMessage: - 'Returns the arctangent of the input\nnumeric expression as an angle, expressed in radians.', - }), - alias: undefined, - signatures: [ { params: [ { name: 'number', - type: 'number', + type: 'integer', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a=12.9\n| EVAL atan=ATAN(a)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const atan2Definition: FunctionDefinition = { - type: 'eval', - name: 'atan2', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.atan2', { - defaultMessage: - 'The angle between the positive x-axis and the ray from the\norigin to the point (x , y) in the Cartesian plane, expressed in radians.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'y_coordinate', - type: 'number', + name: 'number', + type: 'long', optional: false, }, + ], + returnType: 'double', + }, + { + params: [ { - name: 'x_coordinate', - type: 'number', + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW y=12.9, x=.6\n| EVAL atan2=ATAN2(y, x)'], + examples: ['ROW a=.9\n| EVAL asin=ASIN(a)'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const cbrtDefinition: FunctionDefinition = { +const atanDefinition: FunctionDefinition = { type: 'eval', - name: 'cbrt', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cbrt', { + name: 'atan', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.atan', { defaultMessage: - 'Returns the cube root of a number. The input can be any numeric value, the return value is always a double.\nCube roots of infinities are null.', + 'Returns the arctangent of the input\nnumeric expression as an angle, expressed in radians.', }), alias: undefined, signatures: [ @@ -185,493 +216,1065 @@ const cbrtDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW d = 1000.0\n| EVAL c = cbrt(d)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const ceilDefinition: FunctionDefinition = { - type: 'eval', - name: 'ceil', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ceil', { - defaultMessage: 'Round a number up to the nearest integer.', - }), - alias: undefined, - signatures: [ { params: [ { name: 'number', - type: 'number', + type: 'integer', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a=1.8\n| EVAL a=CEIL(a)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const cidrMatchDefinition: FunctionDefinition = { - type: 'eval', - name: 'cidr_match', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cidr_match', { - defaultMessage: - 'Returns true if the provided IP is contained in one of the provided CIDR blocks.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'ip', - type: 'ip', + name: 'number', + type: 'long', optional: false, }, + ], + returnType: 'double', + }, + { + params: [ { - name: 'blockX', - type: 'string', + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'boolean', - minParams: 2, + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: [ - 'FROM hosts \n| WHERE CIDR_MATCH(ip1, "127.0.0.2/32", "127.0.0.3/32") \n| KEEP card, host, ip0, ip1', - ], + examples: ['ROW a=12.9\n| EVAL atan=ATAN(a)'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const coalesceDefinition: FunctionDefinition = { +const atan2Definition: FunctionDefinition = { type: 'eval', - name: 'coalesce', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.coalesce', { + name: 'atan2', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.atan2', { defaultMessage: - 'Returns the first of its arguments that is not null. If all arguments are null, it returns `null`.', + 'The angle between the positive x-axis and the ray from the\norigin to the point (x , y) in the Cartesian plane, expressed in radians.', }), alias: undefined, signatures: [ { params: [ { - name: 'first', - type: 'boolean', + name: 'y_coordinate', + type: 'double', + optional: false, + }, + { + name: 'x_coordinate', + type: 'double', optional: false, }, ], - returnType: 'boolean', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'boolean', + name: 'y_coordinate', + type: 'double', optional: false, }, { - name: 'rest', - type: 'boolean', - optional: true, + name: 'x_coordinate', + type: 'integer', + optional: false, }, ], - returnType: 'boolean', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'cartesian_point', + name: 'y_coordinate', + type: 'double', optional: false, }, { - name: 'rest', - type: 'cartesian_point', - optional: true, + name: 'x_coordinate', + type: 'long', + optional: false, }, ], - returnType: 'cartesian_point', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'cartesian_shape', + name: 'y_coordinate', + type: 'double', optional: false, }, { - name: 'rest', - type: 'cartesian_shape', - optional: true, + name: 'x_coordinate', + type: 'unsigned_long', + optional: false, }, ], - returnType: 'cartesian_shape', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'date', + name: 'y_coordinate', + type: 'integer', optional: false, }, { - name: 'rest', - type: 'date', - optional: true, + name: 'x_coordinate', + type: 'double', + optional: false, }, ], - returnType: 'date', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'geo_point', + name: 'y_coordinate', + type: 'integer', optional: false, }, { - name: 'rest', - type: 'geo_point', - optional: true, + name: 'x_coordinate', + type: 'integer', + optional: false, }, ], - returnType: 'geo_point', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'geo_shape', + name: 'y_coordinate', + type: 'integer', optional: false, }, { - name: 'rest', - type: 'geo_shape', - optional: true, + name: 'x_coordinate', + type: 'long', + optional: false, }, ], - returnType: 'geo_shape', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'number', + name: 'y_coordinate', + type: 'integer', + optional: false, + }, + { + name: 'x_coordinate', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'number', + name: 'y_coordinate', + type: 'long', optional: false, }, { - name: 'rest', - type: 'number', - optional: true, + name: 'x_coordinate', + type: 'double', + optional: false, }, ], - returnType: 'number', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'ip', + name: 'y_coordinate', + type: 'long', optional: false, }, { - name: 'rest', - type: 'ip', - optional: true, + name: 'x_coordinate', + type: 'integer', + optional: false, }, ], - returnType: 'ip', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'string', + name: 'y_coordinate', + type: 'long', optional: false, }, - ], - returnType: 'string', - minParams: 1, + { + name: 'x_coordinate', + type: 'long', + optional: false, + }, + ], + returnType: 'double', }, { params: [ { - name: 'first', - type: 'string', + name: 'y_coordinate', + type: 'long', optional: false, }, { - name: 'rest', - type: 'string', - optional: true, + name: 'x_coordinate', + type: 'unsigned_long', + optional: false, }, ], - returnType: 'string', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'version', + name: 'y_coordinate', + type: 'unsigned_long', optional: false, }, { - name: 'rest', - type: 'version', - optional: true, + name: 'x_coordinate', + type: 'double', + optional: false, }, ], - returnType: 'version', - minParams: 1, + returnType: 'double', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a=null, b="b"\n| EVAL COALESCE(a, b)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const concatDefinition: FunctionDefinition = { - type: 'eval', - name: 'concat', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.concat', { - defaultMessage: 'Concatenates two or more strings.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'string1', - type: 'string', + name: 'y_coordinate', + type: 'unsigned_long', optional: false, }, { - name: 'string2', - type: 'string', + name: 'x_coordinate', + type: 'integer', optional: false, }, ], - returnType: 'string', - minParams: 2, + returnType: 'double', + }, + { + params: [ + { + name: 'y_coordinate', + type: 'unsigned_long', + optional: false, + }, + { + name: 'x_coordinate', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'y_coordinate', + type: 'unsigned_long', + optional: false, + }, + { + name: 'x_coordinate', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: [ - 'FROM employees\n| KEEP first_name, last_name\n| EVAL fullname = CONCAT(first_name, " ", last_name)', - ], + examples: ['ROW y=12.9, x=.6\n| EVAL atan2=ATAN2(y, x)'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const cosDefinition: FunctionDefinition = { +const cbrtDefinition: FunctionDefinition = { type: 'eval', - name: 'cos', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cos', { - defaultMessage: 'Returns the cosine of an angle.', + name: 'cbrt', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cbrt', { + defaultMessage: + 'Returns the cube root of a number. The input can be any numeric value, the return value is always a double.\nCube roots of infinities are null.', }), alias: undefined, signatures: [ { params: [ { - name: 'angle', - type: 'number', + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW a=1.8 \n| EVAL cos=COS(a)'], + examples: ['ROW d = 1000.0\n| EVAL c = cbrt(d)'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const coshDefinition: FunctionDefinition = { +const ceilDefinition: FunctionDefinition = { type: 'eval', - name: 'cosh', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cosh', { - defaultMessage: 'Returns the hyperbolic cosine of an angle.', + name: 'ceil', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ceil', { + defaultMessage: 'Round a number up to the nearest integer.', }), alias: undefined, signatures: [ { params: [ { - name: 'angle', - type: 'number', + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'unsigned_long', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW a=1.8 \n| EVAL cosh=COSH(a)'], + examples: ['ROW a=1.8\n| EVAL a=CEIL(a)'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const dateDiffDefinition: FunctionDefinition = { +const cidrMatchDefinition: FunctionDefinition = { type: 'eval', - name: 'date_diff', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_diff', { + name: 'cidr_match', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cidr_match', { defaultMessage: - 'Subtracts the `startTimestamp` from the `endTimestamp` and returns the difference in multiples of `unit`.\nIf `startTimestamp` is later than the `endTimestamp`, negative values are returned.', + 'Returns true if the provided IP is contained in one of the provided CIDR blocks.', }), alias: undefined, signatures: [ { params: [ { - name: 'unit', - type: 'string', + name: 'ip', + type: 'ip', optional: false, - literalOptions: [ - 'year', - 'years', - 'yy', - 'yyyy', - 'quarter', - 'quarters', - 'qq', - 'q', - 'month', - 'months', - 'mm', - 'm', - 'dayofyear', - 'dy', - 'y', - 'day', - 'days', - 'dd', - 'd', - 'week', - 'weeks', - 'wk', - 'ww', - 'weekday', - 'weekdays', - 'dw', - 'hour', - 'hours', - 'hh', - 'minute', - 'minutes', - 'mi', - 'n', - 'second', - 'seconds', - 'ss', - 's', - 'millisecond', - 'milliseconds', - 'ms', - 'microsecond', - 'microseconds', - 'mcs', - 'nanosecond', - 'nanoseconds', - 'ns', - ], - literalSuggestions: [ - 'year', - 'quarter', - 'month', - 'week', - 'day', - 'hour', - 'minute', - 'second', - 'millisecond', - 'microsecond', - 'nanosecond', - ], }, { - name: 'startTimestamp', - type: 'date', + name: 'blockX', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + minParams: 2, + }, + { + params: [ + { + name: 'ip', + type: 'ip', optional: false, }, { - name: 'endTimestamp', - type: 'date', + name: 'blockX', + type: 'text', optional: false, }, ], - returnType: 'number', + returnType: 'boolean', + minParams: 2, }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ - 'ROW date1 = TO_DATETIME("2023-12-02T11:00:00.000Z"), date2 = TO_DATETIME("2023-12-02T11:00:00.001Z")\n| EVAL dd_ms = DATE_DIFF("microseconds", date1, date2)', + 'FROM hosts \n| WHERE CIDR_MATCH(ip1, "127.0.0.2/32", "127.0.0.3/32") \n| KEEP card, host, ip0, ip1', ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const dateExtractDefinition: FunctionDefinition = { +const coalesceDefinition: FunctionDefinition = { type: 'eval', - name: 'date_extract', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_extract', { - defaultMessage: 'Extracts parts of a date, like year, month, day, hour.', + name: 'coalesce', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.coalesce', { + defaultMessage: + 'Returns the first of its arguments that is not null. If all arguments are null, it returns `null`.', }), alias: undefined, signatures: [ { params: [ { - name: 'datePart', - type: 'string', + name: 'first', + type: 'boolean', optional: false, - literalOptions: [ - 'ALIGNED_DAY_OF_WEEK_IN_MONTH', + }, + ], + returnType: 'boolean', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'boolean', + optional: false, + }, + { + name: 'rest', + type: 'boolean', + optional: true, + }, + ], + returnType: 'boolean', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'cartesian_point', + optional: false, + }, + { + name: 'rest', + type: 'cartesian_point', + optional: true, + }, + ], + returnType: 'cartesian_point', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'cartesian_shape', + optional: false, + }, + { + name: 'rest', + type: 'cartesian_shape', + optional: true, + }, + ], + returnType: 'cartesian_shape', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'date', + optional: false, + }, + { + name: 'rest', + type: 'date', + optional: true, + }, + ], + returnType: 'date', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'geo_point', + optional: false, + }, + { + name: 'rest', + type: 'geo_point', + optional: true, + }, + ], + returnType: 'geo_point', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'geo_shape', + optional: false, + }, + { + name: 'rest', + type: 'geo_shape', + optional: true, + }, + ], + returnType: 'geo_shape', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'integer', + optional: false, + }, + { + name: 'rest', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'ip', + optional: false, + }, + { + name: 'rest', + type: 'ip', + optional: true, + }, + ], + returnType: 'ip', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'keyword', + optional: false, + }, + { + name: 'rest', + type: 'keyword', + optional: true, + }, + ], + returnType: 'keyword', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'long', + optional: false, + }, + { + name: 'rest', + type: 'long', + optional: true, + }, + ], + returnType: 'long', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'text', + optional: false, + }, + ], + returnType: 'text', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'text', + optional: false, + }, + { + name: 'rest', + type: 'text', + optional: true, + }, + ], + returnType: 'text', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'version', + optional: false, + }, + { + name: 'rest', + type: 'version', + optional: true, + }, + ], + returnType: 'version', + minParams: 1, + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a=null, b="b"\n| EVAL COALESCE(a, b)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const concatDefinition: FunctionDefinition = { + type: 'eval', + name: 'concat', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.concat', { + defaultMessage: 'Concatenates two or more strings.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'string1', + type: 'keyword', + optional: false, + }, + { + name: 'string2', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + minParams: 2, + }, + { + params: [ + { + name: 'string1', + type: 'keyword', + optional: false, + }, + { + name: 'string2', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', + minParams: 2, + }, + { + params: [ + { + name: 'string1', + type: 'text', + optional: false, + }, + { + name: 'string2', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + minParams: 2, + }, + { + params: [ + { + name: 'string1', + type: 'text', + optional: false, + }, + { + name: 'string2', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', + minParams: 2, + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'FROM employees\n| KEEP first_name, last_name\n| EVAL fullname = CONCAT(first_name, " ", last_name)', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const cosDefinition: FunctionDefinition = { + type: 'eval', + name: 'cos', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cos', { + defaultMessage: 'Returns the cosine of an angle.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'angle', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a=1.8 \n| EVAL cos=COS(a)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const coshDefinition: FunctionDefinition = { + type: 'eval', + name: 'cosh', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.cosh', { + defaultMessage: 'Returns the hyperbolic cosine of an angle.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'angle', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a=1.8 \n| EVAL cosh=COSH(a)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const dateDiffDefinition: FunctionDefinition = { + type: 'eval', + name: 'date_diff', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_diff', { + defaultMessage: + 'Subtracts the `startTimestamp` from the `endTimestamp` and returns the difference in multiples of `unit`.\nIf `startTimestamp` is later than the `endTimestamp`, negative values are returned.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'unit', + type: 'keyword', + optional: false, + literalOptions: [ + 'year', + 'years', + 'yy', + 'yyyy', + 'quarter', + 'quarters', + 'qq', + 'q', + 'month', + 'months', + 'mm', + 'm', + 'dayofyear', + 'dy', + 'y', + 'day', + 'days', + 'dd', + 'd', + 'week', + 'weeks', + 'wk', + 'ww', + 'weekday', + 'weekdays', + 'dw', + 'hour', + 'hours', + 'hh', + 'minute', + 'minutes', + 'mi', + 'n', + 'second', + 'seconds', + 'ss', + 's', + 'millisecond', + 'milliseconds', + 'ms', + 'microsecond', + 'microseconds', + 'mcs', + 'nanosecond', + 'nanoseconds', + 'ns', + ], + literalSuggestions: [ + 'year', + 'quarter', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + 'millisecond', + 'microsecond', + 'nanosecond', + ], + }, + { + name: 'startTimestamp', + type: 'date', + optional: false, + }, + { + name: 'endTimestamp', + type: 'date', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'unit', + type: 'text', + optional: false, + }, + { + name: 'startTimestamp', + type: 'date', + optional: false, + }, + { + name: 'endTimestamp', + type: 'date', + optional: false, + }, + ], + returnType: 'integer', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'ROW date1 = TO_DATETIME("2023-12-02T11:00:00.000Z"), date2 = TO_DATETIME("2023-12-02T11:00:00.001Z")\n| EVAL dd_ms = DATE_DIFF("microseconds", date1, date2)', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const dateExtractDefinition: FunctionDefinition = { + type: 'eval', + name: 'date_extract', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_extract', { + defaultMessage: 'Extracts parts of a date, like year, month, day, hour.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'datePart', + type: 'keyword', + optional: false, + literalOptions: [ + 'ALIGNED_DAY_OF_WEEK_IN_MONTH', 'ALIGNED_DAY_OF_WEEK_IN_YEAR', 'ALIGNED_WEEK_OF_MONTH', 'ALIGNED_WEEK_OF_YEAR', @@ -704,977 +1307,2648 @@ const dateExtractDefinition: FunctionDefinition = { ], }, { - name: 'date', - type: 'date', + name: 'date', + type: 'date', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'datePart', + type: 'text', + optional: false, + }, + { + name: 'date', + type: 'date', + optional: false, + }, + ], + returnType: 'long', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'ROW date = DATE_PARSE("yyyy-MM-dd", "2022-05-06")\n| EVAL year = DATE_EXTRACT("year", date)', + 'FROM sample_data\n| WHERE DATE_EXTRACT("hour_of_day", @timestamp) < 9 AND DATE_EXTRACT("hour_of_day", @timestamp) >= 17', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const dateFormatDefinition: FunctionDefinition = { + type: 'eval', + name: 'date_format', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_format', { + defaultMessage: 'Returns a string representation of a date, in the provided format.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'dateFormat', + type: 'keyword', + optional: true, + }, + { + name: 'date', + type: 'date', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'dateFormat', + type: 'text', + optional: true, + }, + { + name: 'date', + type: 'date', + optional: false, + }, + ], + returnType: 'keyword', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'FROM employees\n| KEEP first_name, last_name, hire_date\n| EVAL hired = DATE_FORMAT("YYYY-MM-dd", hire_date)', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const dateParseDefinition: FunctionDefinition = { + type: 'eval', + name: 'date_parse', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_parse', { + defaultMessage: + 'Returns a date by parsing the second argument using the format specified in the first argument.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'datePattern', + type: 'keyword', + optional: true, + }, + { + name: 'dateString', + type: 'keyword', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'datePattern', + type: 'keyword', + optional: true, + }, + { + name: 'dateString', + type: 'text', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'datePattern', + type: 'text', + optional: true, + }, + { + name: 'dateString', + type: 'keyword', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'datePattern', + type: 'text', + optional: true, + }, + { + name: 'dateString', + type: 'text', + optional: false, + }, + ], + returnType: 'date', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW date_string = "2022-05-06"\n| EVAL date = DATE_PARSE("yyyy-MM-dd", date_string)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const dateTruncDefinition: FunctionDefinition = { + type: 'eval', + name: 'date_trunc', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_trunc', { + defaultMessage: 'Rounds down a date to the closest interval.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'interval', + type: 'time_literal', + optional: false, + }, + { + name: 'date', + type: 'date', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'interval', + type: 'time_duration', + optional: false, + }, + { + name: 'date', + type: 'date', + optional: false, + }, + ], + returnType: 'date', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'FROM employees\n| KEEP first_name, last_name, hire_date\n| EVAL year_hired = DATE_TRUNC(1 year, hire_date)', + 'FROM employees\n| EVAL year = DATE_TRUNC(1 year, hire_date)\n| STATS hires = COUNT(emp_no) BY year\n| SORT year', + 'FROM sample_data\n| EVAL error = CASE(message LIKE "*error*", 1, 0)\n| EVAL hour = DATE_TRUNC(1 hour, @timestamp)\n| STATS error_rate = AVG(error) by hour\n| SORT hour', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const eDefinition: FunctionDefinition = { + type: 'eval', + name: 'e', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.e', { + defaultMessage: "Returns Euler's number.", + }), + alias: undefined, + signatures: [ + { + params: [], + returnType: 'double', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW E()'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const endsWithDefinition: FunctionDefinition = { + type: 'eval', + name: 'ends_with', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ends_with', { + defaultMessage: + 'Returns a boolean that indicates whether a keyword string ends with another string.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'str', + type: 'keyword', + optional: false, + }, + { + name: 'suffix', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'str', + type: 'keyword', + optional: false, + }, + { + name: 'suffix', + type: 'text', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'str', + type: 'text', + optional: false, + }, + { + name: 'suffix', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'str', + type: 'text', + optional: false, + }, + { + name: 'suffix', + type: 'text', + optional: false, + }, + ], + returnType: 'boolean', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['FROM employees\n| KEEP last_name\n| EVAL ln_E = ENDS_WITH(last_name, "d")'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const expDefinition: FunctionDefinition = { + type: 'eval', + name: 'exp', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.exp', { + defaultMessage: 'Returns the value of e raised to the power of the given number.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW d = 5.0\n| EVAL s = EXP(d)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const floorDefinition: FunctionDefinition = { + type: 'eval', + name: 'floor', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.floor', { + defaultMessage: 'Round a number down to the nearest integer.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'unsigned_long', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a=1.8\n| EVAL a=FLOOR(a)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const fromBase64Definition: FunctionDefinition = { + type: 'eval', + name: 'from_base64', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.from_base64', { + defaultMessage: 'Decode a base64 string.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['row a = "ZWxhc3RpYw==" \n| eval d = from_base64(a)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const greatestDefinition: FunctionDefinition = { + type: 'eval', + name: 'greatest', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.greatest', { + defaultMessage: + 'Returns the maximum value from multiple columns. This is similar to `MV_MAX`\nexcept it is intended to run on multiple columns at once.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'first', + type: 'boolean', + optional: false, + }, + ], + returnType: 'boolean', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'boolean', + optional: false, + }, + { + name: 'rest', + type: 'boolean', + optional: true, + }, + ], + returnType: 'boolean', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'double', + optional: false, + }, + { + name: 'rest', + type: 'double', + optional: true, + }, + ], + returnType: 'double', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'integer', + optional: false, + }, + { + name: 'rest', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'ip', + optional: false, + }, + { + name: 'rest', + type: 'ip', + optional: true, + }, + ], + returnType: 'ip', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'keyword', + optional: false, + }, + { + name: 'rest', + type: 'keyword', + optional: true, + }, + ], + returnType: 'keyword', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'long', + optional: false, + }, + { + name: 'rest', + type: 'long', + optional: true, + }, + ], + returnType: 'long', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'text', + optional: false, + }, + ], + returnType: 'text', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'text', + optional: false, + }, + { + name: 'rest', + type: 'text', + optional: true, + }, + ], + returnType: 'text', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'version', + optional: false, + }, + { + name: 'rest', + type: 'version', + optional: true, + }, + ], + returnType: 'version', + minParams: 1, + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a = 10, b = 20\n| EVAL g = GREATEST(a, b)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const ipPrefixDefinition: FunctionDefinition = { + type: 'eval', + name: 'ip_prefix', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ip_prefix', { + defaultMessage: 'Truncates an IP to a given prefix length.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'ip', + type: 'ip', + optional: false, + }, + { + name: 'prefixLengthV4', + type: 'integer', + optional: false, + }, + { + name: 'prefixLengthV6', + type: 'integer', + optional: false, + }, + ], + returnType: 'ip', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'row ip4 = to_ip("1.2.3.4"), ip6 = to_ip("fe80::cae2:65ff:fece:feb9")\n| eval ip4_prefix = ip_prefix(ip4, 24, 0), ip6_prefix = ip_prefix(ip6, 0, 112);', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const leastDefinition: FunctionDefinition = { + type: 'eval', + name: 'least', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.least', { + defaultMessage: + 'Returns the minimum value from multiple columns. This is similar to `MV_MIN` except it is intended to run on multiple columns at once.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'first', + type: 'boolean', + optional: false, + }, + ], + returnType: 'boolean', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'boolean', + optional: false, + }, + { + name: 'rest', + type: 'boolean', + optional: true, + }, + ], + returnType: 'boolean', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'double', + optional: false, + }, + { + name: 'rest', + type: 'double', + optional: true, + }, + ], + returnType: 'double', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'integer', + optional: false, + }, + { + name: 'rest', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'ip', + optional: false, + }, + { + name: 'rest', + type: 'ip', + optional: true, + }, + ], + returnType: 'ip', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'keyword', + optional: false, + }, + { + name: 'rest', + type: 'keyword', + optional: true, + }, + ], + returnType: 'keyword', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'long', + optional: false, + }, + { + name: 'rest', + type: 'long', + optional: true, + }, + ], + returnType: 'long', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'text', + optional: false, + }, + ], + returnType: 'text', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'text', + optional: false, + }, + { + name: 'rest', + type: 'text', + optional: true, + }, + ], + returnType: 'text', + minParams: 1, + }, + { + params: [ + { + name: 'first', + type: 'version', + optional: false, + }, + { + name: 'rest', + type: 'version', + optional: true, + }, + ], + returnType: 'version', + minParams: 1, + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a = 10, b = 20\n| EVAL l = LEAST(a, b)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const leftDefinition: FunctionDefinition = { + type: 'eval', + name: 'left', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.left', { + defaultMessage: + "Returns the substring that extracts 'length' chars from 'string' starting from the left.", + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'length', + type: 'integer', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'length', + type: 'integer', + optional: false, + }, + ], + returnType: 'keyword', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'FROM employees\n| KEEP last_name\n| EVAL left = LEFT(last_name, 3)\n| SORT last_name ASC\n| LIMIT 5', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const lengthDefinition: FunctionDefinition = { + type: 'eval', + name: 'length', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.length', { + defaultMessage: 'Returns the character length of a string.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + ], + returnType: 'integer', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['FROM employees\n| KEEP first_name, last_name\n| EVAL fn_length = LENGTH(first_name)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const locateDefinition: FunctionDefinition = { + type: 'eval', + name: 'locate', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.locate', { + defaultMessage: + 'Returns an integer that indicates the position of a keyword substring within another string.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'substring', + type: 'keyword', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'substring', + type: 'keyword', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'substring', + type: 'text', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'substring', + type: 'text', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'substring', + type: 'keyword', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'substring', + type: 'keyword', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'substring', + type: 'text', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'substring', + type: 'text', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['row a = "hello"\n| eval a_ll = locate(a, "ll")'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const logDefinition: FunctionDefinition = { + type: 'eval', + name: 'log', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.log', { + defaultMessage: + 'Returns the logarithm of a value to a base. The input can be any numeric value, the return value is always a double.\n\nLogs of zero, negative numbers, and base of one return `null` as well as a warning.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'base', + type: 'double', + optional: true, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'double', + optional: true, + }, + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'double', + optional: true, + }, + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'double', + optional: true, + }, + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'double', + optional: true, + }, + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'integer', + optional: true, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'integer', + optional: true, + }, + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'integer', + optional: true, + }, + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'integer', + optional: true, + }, + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'integer', + optional: true, + }, + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'long', + optional: true, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'long', + optional: true, + }, + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'long', + optional: true, + }, + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'long', + optional: true, + }, + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'long', + optional: true, + }, + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'unsigned_long', + optional: true, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'unsigned_long', + optional: true, + }, + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'unsigned_long', + optional: true, + }, + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'unsigned_long', + optional: true, + }, + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'base', + type: 'unsigned_long', + optional: true, + }, + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: (fnDef: ESQLFunction) => { + const messages = []; + // do not really care here about the base and field + // just need to check both values are not negative + for (const arg of fnDef.args) { + if (isLiteralItem(arg) && typeof arg.value === 'number' && arg.value < 0) { + messages.push({ + type: 'warning' as const, + code: 'logOfNegativeValue', + text: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.divide.warning.logOfNegativeValue', + { + defaultMessage: 'Log of a negative number results in null: {value}', + values: { + value: arg.value, + }, + } + ), + location: arg.location, + }); + } + } + return messages; + }, + examples: [ + 'ROW base = 2.0, value = 8.0\n| EVAL s = LOG(base, value)', + 'row value = 100\n| EVAL s = LOG(value);', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const log10Definition: FunctionDefinition = { + type: 'eval', + name: 'log10', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.log10', { + defaultMessage: + 'Returns the logarithm of a value to base 10. The input can be any numeric value, the return value is always a double.\n\nLogs of 0 and negative numbers return `null` as well as a warning.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: (fnDef: ESQLFunction) => { + const messages = []; + // do not really care here about the base and field + // just need to check both values are not negative + for (const arg of fnDef.args) { + if (isLiteralItem(arg) && typeof arg.value === 'number' && arg.value < 0) { + messages.push({ + type: 'warning' as const, + code: 'logOfNegativeValue', + text: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.divide.warning.logOfNegativeValue', + { + defaultMessage: 'Log of a negative number results in null: {value}', + values: { + value: arg.value, + }, + } + ), + location: arg.location, + }); + } + } + return messages; + }, + examples: ['ROW d = 1000.0 \n| EVAL s = LOG10(d)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const ltrimDefinition: FunctionDefinition = { + type: 'eval', + name: 'ltrim', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ltrim', { + defaultMessage: 'Removes leading whitespaces from a string.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', optional: false, }, ], - returnType: 'number', + returnType: 'text', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ - 'ROW date = DATE_PARSE("yyyy-MM-dd", "2022-05-06")\n| EVAL year = DATE_EXTRACT("year", date)', - 'FROM sample_data\n| WHERE DATE_EXTRACT("hour_of_day", @timestamp) < 9 AND DATE_EXTRACT("hour_of_day", @timestamp) >= 17', + 'ROW message = " some text ", color = " red "\n| EVAL message = LTRIM(message)\n| EVAL color = LTRIM(color)\n| EVAL message = CONCAT("\'", message, "\'")\n| EVAL color = CONCAT("\'", color, "\'")', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const mvAppendDefinition: FunctionDefinition = { + type: 'eval', + name: 'mv_append', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_append', { + defaultMessage: 'Concatenates values of two multi-value fields.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'field1', + type: 'boolean', + optional: false, + }, + { + name: 'field2', + type: 'boolean', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field1', + type: 'cartesian_point', + optional: false, + }, + { + name: 'field2', + type: 'cartesian_point', + optional: false, + }, + ], + returnType: 'cartesian_point', + }, + { + params: [ + { + name: 'field1', + type: 'cartesian_shape', + optional: false, + }, + { + name: 'field2', + type: 'cartesian_shape', + optional: false, + }, + ], + returnType: 'cartesian_shape', + }, + { + params: [ + { + name: 'field1', + type: 'date', + optional: false, + }, + { + name: 'field2', + type: 'date', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'field1', + type: 'double', + optional: false, + }, + { + name: 'field2', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field1', + type: 'geo_point', + optional: false, + }, + { + name: 'field2', + type: 'geo_point', + optional: false, + }, + ], + returnType: 'geo_point', + }, + { + params: [ + { + name: 'field1', + type: 'geo_shape', + optional: false, + }, + { + name: 'field2', + type: 'geo_shape', + optional: false, + }, + ], + returnType: 'geo_shape', + }, + { + params: [ + { + name: 'field1', + type: 'integer', + optional: false, + }, + { + name: 'field2', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field1', + type: 'ip', + optional: false, + }, + { + name: 'field2', + type: 'ip', + optional: false, + }, + ], + returnType: 'ip', + }, + { + params: [ + { + name: 'field1', + type: 'keyword', + optional: false, + }, + { + name: 'field2', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'field1', + type: 'long', + optional: false, + }, + { + name: 'field2', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field1', + type: 'text', + optional: false, + }, + { + name: 'field2', + type: 'text', + optional: false, + }, + ], + returnType: 'text', + }, + { + params: [ + { + name: 'field1', + type: 'version', + optional: false, + }, + { + name: 'field2', + type: 'version', + optional: false, + }, + ], + returnType: 'version', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const mvAvgDefinition: FunctionDefinition = { + type: 'eval', + name: 'mv_avg', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_avg', { + defaultMessage: + 'Converts a multivalued field into a single valued field containing the average of all of the values.', + }), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', + }, ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a=[3, 5, 1, 6]\n| EVAL avg_a = MV_AVG(a)'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const dateFormatDefinition: FunctionDefinition = { +const mvConcatDefinition: FunctionDefinition = { type: 'eval', - name: 'date_format', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_format', { - defaultMessage: 'Returns a string representation of a date, in the provided format.', + name: 'mv_concat', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_concat', { + defaultMessage: + 'Converts a multivalued string expression into a single valued column containing the concatenation of all values separated by a delimiter.', }), alias: undefined, signatures: [ { params: [ { - name: 'dateFormat', - type: 'string', - optional: true, + name: 'string', + type: 'keyword', + optional: false, }, { - name: 'date', - type: 'date', + name: 'delim', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'delim', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'delim', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'delim', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ - 'FROM employees\n| KEEP first_name, last_name, hire_date\n| EVAL hired = DATE_FORMAT("YYYY-MM-dd", hire_date)', + 'ROW a=["foo", "zoo", "bar"]\n| EVAL j = MV_CONCAT(a, ", ")', + 'ROW a=[10, 9, 8]\n| EVAL j = MV_CONCAT(TO_STRING(a), ", ")', ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const dateParseDefinition: FunctionDefinition = { +const mvCountDefinition: FunctionDefinition = { type: 'eval', - name: 'date_parse', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_parse', { + name: 'mv_count', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_count', { defaultMessage: - 'Returns a date by parsing the second argument using the format specified in the first argument.', + 'Converts a multivalued expression into a single valued column containing a count of the number of values.', }), alias: undefined, signatures: [ { params: [ { - name: 'datePattern', - type: 'string', - optional: true, + name: 'field', + type: 'boolean', + optional: false, }, + ], + returnType: 'integer', + }, + { + params: [ { - name: 'dateString', - type: 'string', + name: 'field', + type: 'cartesian_point', optional: false, }, ], - returnType: 'date', + returnType: 'integer', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW date_string = "2022-05-06"\n| EVAL date = DATE_PARSE("yyyy-MM-dd", date_string)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const dateTruncDefinition: FunctionDefinition = { - type: 'eval', - name: 'date_trunc', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.date_trunc', { - defaultMessage: 'Rounds down a date to the closest interval.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'interval', - type: 'time_literal', + name: 'field', + type: 'cartesian_shape', optional: false, }, + ], + returnType: 'integer', + }, + { + params: [ { - name: 'date', + name: 'field', type: 'date', optional: false, }, ], - returnType: 'date', + returnType: 'integer', }, { params: [ { - name: 'interval', - type: 'date', + name: 'field', + type: 'double', optional: false, }, + ], + returnType: 'integer', + }, + { + params: [ { - name: 'date', - type: 'date', + name: 'field', + type: 'geo_point', optional: false, }, ], - returnType: 'date', + returnType: 'integer', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: [ - 'FROM employees\n| KEEP first_name, last_name, hire_date\n| EVAL year_hired = DATE_TRUNC(1 year, hire_date)', - 'FROM employees\n| EVAL year = DATE_TRUNC(1 year, hire_date)\n| STATS hires = COUNT(emp_no) BY year\n| SORT year', - 'FROM sample_data\n| EVAL error = CASE(message LIKE "*error*", 1, 0)\n| EVAL hour = DATE_TRUNC(1 hour, @timestamp)\n| STATS error_rate = AVG(error) by hour\n| SORT hour', - ], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const eDefinition: FunctionDefinition = { - type: 'eval', - name: 'e', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.e', { - defaultMessage: "Returns Euler's number.", - }), - alias: undefined, - signatures: [ { - params: [], - returnType: 'number', + params: [ + { + name: 'field', + type: 'geo_shape', + optional: false, + }, + ], + returnType: 'integer', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW E()'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const endsWithDefinition: FunctionDefinition = { - type: 'eval', - name: 'ends_with', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ends_with', { - defaultMessage: - 'Returns a boolean that indicates whether a keyword string ends with another string.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'str', - type: 'string', + name: 'field', + type: 'integer', optional: false, }, + ], + returnType: 'integer', + }, + { + params: [ { - name: 'suffix', - type: 'string', + name: 'field', + type: 'ip', optional: false, }, ], - returnType: 'boolean', + returnType: 'integer', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['FROM employees\n| KEEP last_name\n| EVAL ln_E = ENDS_WITH(last_name, "d")'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const expDefinition: FunctionDefinition = { - type: 'eval', - name: 'exp', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.exp', { - defaultMessage: 'Returns the value of e raised to the power of the given number.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'number', - type: 'number', + name: 'field', + type: 'keyword', optional: false, }, ], - returnType: 'number', + returnType: 'integer', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW d = 5.0\n| EVAL s = EXP(d)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const floorDefinition: FunctionDefinition = { - type: 'eval', - name: 'floor', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.floor', { - defaultMessage: 'Round a number down to the nearest integer.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'number', - type: 'number', + name: 'field', + type: 'long', optional: false, }, ], - returnType: 'number', + returnType: 'integer', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a=1.8\n| EVAL a=FLOOR(a)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const fromBase64Definition: FunctionDefinition = { - type: 'eval', - name: 'from_base64', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.from_base64', { - defaultMessage: 'Decode a base64 string.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'string', - type: 'string', + name: 'field', + type: 'text', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'version', optional: false, }, ], - returnType: 'string', + returnType: 'integer', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['row a = "ZWxhc3RpYw==" \n| eval d = from_base64(a)'], + examples: ['ROW a=["foo", "zoo", "bar"]\n| EVAL count_a = MV_COUNT(a)'], }; -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const greatestDefinition: FunctionDefinition = { - type: 'eval', - name: 'greatest', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.greatest', { - defaultMessage: - 'Returns the maximum value from multiple columns. This is similar to `MV_MAX`\nexcept it is intended to run on multiple columns at once.', +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const mvDedupeDefinition: FunctionDefinition = { + type: 'eval', + name: 'mv_dedupe', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_dedupe', { + defaultMessage: 'Remove duplicate values from a multivalued field.', }), alias: undefined, signatures: [ { params: [ { - name: 'first', + name: 'field', type: 'boolean', optional: false, }, ], returnType: 'boolean', - minParams: 1, }, { params: [ { - name: 'first', - type: 'boolean', + name: 'field', + type: 'cartesian_point', optional: false, }, - { - name: 'rest', - type: 'boolean', - optional: true, - }, ], - returnType: 'boolean', - minParams: 1, + returnType: 'cartesian_point', }, { params: [ { - name: 'first', - type: 'number', + name: 'field', + type: 'cartesian_shape', optional: false, }, - { - name: 'rest', - type: 'number', - optional: true, - }, ], - returnType: 'number', - minParams: 1, + returnType: 'cartesian_shape', }, { params: [ { - name: 'first', - type: 'number', + name: 'field', + type: 'date', optional: false, }, ], - returnType: 'number', - minParams: 1, + returnType: 'date', }, { params: [ { - name: 'first', - type: 'ip', + name: 'field', + type: 'double', optional: false, }, - { - name: 'rest', - type: 'ip', - optional: true, - }, ], - returnType: 'ip', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'string', + name: 'field', + type: 'geo_point', optional: false, }, ], - returnType: 'string', - minParams: 1, + returnType: 'geo_point', }, { params: [ { - name: 'first', - type: 'string', + name: 'field', + type: 'geo_shape', optional: false, }, + ], + returnType: 'geo_shape', + }, + { + params: [ { - name: 'rest', - type: 'string', - optional: true, + name: 'field', + type: 'integer', + optional: false, }, ], - returnType: 'string', - minParams: 1, + returnType: 'integer', }, { params: [ { - name: 'first', - type: 'version', + name: 'field', + type: 'ip', optional: false, }, + ], + returnType: 'ip', + }, + { + params: [ { - name: 'rest', - type: 'version', - optional: true, + name: 'field', + type: 'keyword', + optional: false, }, ], - returnType: 'version', - minParams: 1, + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a = 10, b = 20\n| EVAL g = GREATEST(a, b)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const ipPrefixDefinition: FunctionDefinition = { - type: 'eval', - name: 'ip_prefix', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ip_prefix', { - defaultMessage: 'Truncates an IP to a given prefix length.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'ip', - type: 'ip', + name: 'field', + type: 'long', optional: false, }, + ], + returnType: 'long', + }, + { + params: [ { - name: 'prefixLengthV4', - type: 'number', + name: 'field', + type: 'text', optional: false, }, + ], + returnType: 'text', + }, + { + params: [ { - name: 'prefixLengthV6', - type: 'number', + name: 'field', + type: 'version', optional: false, }, ], - returnType: 'ip', + returnType: 'version', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: [ - 'row ip4 = to_ip("1.2.3.4"), ip6 = to_ip("fe80::cae2:65ff:fece:feb9")\n| eval ip4_prefix = ip_prefix(ip4, 24, 0), ip6_prefix = ip_prefix(ip6, 0, 112);', - ], + examples: ['ROW a=["foo", "foo", "bar", "foo"]\n| EVAL dedupe_a = MV_DEDUPE(a)'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const leastDefinition: FunctionDefinition = { +const mvFirstDefinition: FunctionDefinition = { type: 'eval', - name: 'least', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.least', { + name: 'mv_first', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_first', { defaultMessage: - 'Returns the minimum value from multiple columns. This is similar to `MV_MIN` except it is intended to run on multiple columns at once.', + "Converts a multivalued expression into a single valued column containing the\nfirst value. This is most useful when reading from a function that emits\nmultivalued columns in a known order like `SPLIT`.\n\nThe order that multivalued fields are read from\nunderlying storage is not guaranteed. It is *frequently* ascending, but don't\nrely on that. If you need the minimum value use `MV_MIN` instead of\n`MV_FIRST`. `MV_MIN` has optimizations for sorted values so there isn't a\nperformance benefit to `MV_FIRST`.", }), alias: undefined, signatures: [ { params: [ { - name: 'first', + name: 'field', type: 'boolean', optional: false, }, ], returnType: 'boolean', - minParams: 1, }, { params: [ { - name: 'first', - type: 'boolean', + name: 'field', + type: 'cartesian_point', optional: false, }, - { - name: 'rest', - type: 'boolean', - optional: true, - }, ], - returnType: 'boolean', - minParams: 1, + returnType: 'cartesian_point', }, { params: [ { - name: 'first', - type: 'number', + name: 'field', + type: 'cartesian_shape', optional: false, }, - { - name: 'rest', - type: 'number', - optional: true, - }, ], - returnType: 'number', - minParams: 1, + returnType: 'cartesian_shape', }, { params: [ { - name: 'first', - type: 'number', + name: 'field', + type: 'date', optional: false, }, ], - returnType: 'number', - minParams: 1, + returnType: 'date', }, { params: [ { - name: 'first', - type: 'ip', + name: 'field', + type: 'double', optional: false, }, - { - name: 'rest', - type: 'ip', - optional: true, - }, ], - returnType: 'ip', - minParams: 1, + returnType: 'double', }, { params: [ { - name: 'first', - type: 'string', + name: 'field', + type: 'geo_point', optional: false, }, ], - returnType: 'string', - minParams: 1, + returnType: 'geo_point', }, { params: [ { - name: 'first', - type: 'string', + name: 'field', + type: 'geo_shape', optional: false, }, - { - name: 'rest', - type: 'string', - optional: true, - }, ], - returnType: 'string', - minParams: 1, + returnType: 'geo_shape', }, { params: [ { - name: 'first', - type: 'version', + name: 'field', + type: 'integer', optional: false, }, - { - name: 'rest', - type: 'version', - optional: true, - }, ], - returnType: 'version', - minParams: 1, + returnType: 'integer', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a = 10, b = 20\n| EVAL l = LEAST(a, b)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const leftDefinition: FunctionDefinition = { - type: 'eval', - name: 'left', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.left', { - defaultMessage: - "Returns the substring that extracts 'length' chars from 'string' starting from the left.", - }), - alias: undefined, - signatures: [ { params: [ { - name: 'string', - type: 'string', - optional: false, - }, - { - name: 'length', - type: 'number', + name: 'field', + type: 'ip', optional: false, }, ], - returnType: 'string', + returnType: 'ip', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: [ - 'FROM employees\n| KEEP last_name\n| EVAL left = LEFT(last_name, 3)\n| SORT last_name ASC\n| LIMIT 5', - ], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const lengthDefinition: FunctionDefinition = { - type: 'eval', - name: 'length', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.length', { - defaultMessage: 'Returns the character length of a string.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'string', - type: 'string', + name: 'field', + type: 'keyword', optional: false, }, ], - returnType: 'number', + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['FROM employees\n| KEEP first_name, last_name\n| EVAL fn_length = LENGTH(first_name)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const locateDefinition: FunctionDefinition = { - type: 'eval', - name: 'locate', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.locate', { - defaultMessage: - 'Returns an integer that indicates the position of a keyword substring within another string.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'string', - type: 'string', - optional: false, - }, - { - name: 'substring', - type: 'string', + name: 'field', + type: 'long', optional: false, }, ], - returnType: 'number', + returnType: 'long', }, { params: [ { - name: 'string', - type: 'string', + name: 'field', + type: 'text', optional: false, }, + ], + returnType: 'text', + }, + { + params: [ { - name: 'substring', - type: 'string', + name: 'field', + type: 'unsigned_long', optional: false, }, + ], + returnType: 'unsigned_long', + }, + { + params: [ { - name: 'start', - type: 'number', - optional: true, + name: 'field', + type: 'version', + optional: false, }, ], - returnType: 'number', + returnType: 'version', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['row a = "hello"\n| eval a_ll = locate(a, "ll")'], + examples: ['ROW a="foo;bar;baz"\n| EVAL first_a = MV_FIRST(SPLIT(a, ";"))'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const logDefinition: FunctionDefinition = { +const mvLastDefinition: FunctionDefinition = { type: 'eval', - name: 'log', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.log', { + name: 'mv_last', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_last', { defaultMessage: - 'Returns the logarithm of a value to a base. The input can be any numeric value, the return value is always a double.\n\nLogs of zero, negative numbers, and base of one return `null` as well as a warning.', + "Converts a multivalue expression into a single valued column containing the last\nvalue. This is most useful when reading from a function that emits multivalued\ncolumns in a known order like `SPLIT`.\n\nThe order that multivalued fields are read from\nunderlying storage is not guaranteed. It is *frequently* ascending, but don't\nrely on that. If you need the maximum value use `MV_MAX` instead of\n`MV_LAST`. `MV_MAX` has optimizations for sorted values so there isn't a\nperformance benefit to `MV_LAST`.", }), alias: undefined, signatures: [ { params: [ { - name: 'base', - type: 'number', - optional: true, + name: 'field', + type: 'boolean', + optional: false, }, ], - returnType: 'number', + returnType: 'boolean', }, { params: [ { - name: 'base', - type: 'number', - optional: true, - }, - { - name: 'number', - type: 'number', + name: 'field', + type: 'cartesian_point', optional: false, }, ], - returnType: 'number', + returnType: 'cartesian_point', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: (fnDef: ESQLFunction) => { - const messages = []; - // do not really care here about the base and field - // just need to check both values are not negative - for (const arg of fnDef.args) { - if (isLiteralItem(arg) && Number(arg.value) < 0) { - messages.push({ - type: 'warning' as const, - code: 'logOfNegativeValue', - text: i18n.translate( - 'kbn-esql-validation-autocomplete.esql.divide.warning.logOfNegativeValue', - { - defaultMessage: 'Log of a negative number results in null: {value}', - values: { - value: arg.value, - }, - } - ), - location: arg.location, - }); - } - } - return messages; - }, - examples: [ - 'ROW base = 2.0, value = 8.0\n| EVAL s = LOG(base, value)', - 'row value = 100\n| EVAL s = LOG(value);', - ], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const log10Definition: FunctionDefinition = { - type: 'eval', - name: 'log10', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.log10', { - defaultMessage: - 'Returns the logarithm of a value to base 10. The input can be any numeric value, the return value is always a double.\n\nLogs of 0 and negative numbers return `null` as well as a warning.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'number', - type: 'number', + name: 'field', + type: 'cartesian_shape', optional: false, }, ], - returnType: 'number', + returnType: 'cartesian_shape', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: (fnDef: ESQLFunction) => { - const messages = []; - // do not really care here about the base and field - // just need to check both values are not negative - for (const arg of fnDef.args) { - if (isLiteralItem(arg) && Number(arg.value) < 0) { - messages.push({ - type: 'warning' as const, - code: 'logOfNegativeValue', - text: i18n.translate( - 'kbn-esql-validation-autocomplete.esql.divide.warning.logOfNegativeValue', - { - defaultMessage: 'Log of a negative number results in null: {value}', - values: { - value: arg.value, - }, - } - ), - location: arg.location, - }); - } - } - return messages; - }, - examples: ['ROW d = 1000.0 \n| EVAL s = LOG10(d)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const ltrimDefinition: FunctionDefinition = { - type: 'eval', - name: 'ltrim', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.ltrim', { - defaultMessage: 'Removes leading whitespaces from a string.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'string', - type: 'string', + name: 'field', + type: 'date', optional: false, }, ], - returnType: 'string', + returnType: 'date', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: [ - 'ROW message = " some text ", color = " red "\n| EVAL message = LTRIM(message)\n| EVAL color = LTRIM(color)\n| EVAL message = CONCAT("\'", message, "\'")\n| EVAL color = CONCAT("\'", color, "\'")', - ], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvAppendDefinition: FunctionDefinition = { - type: 'eval', - name: 'mv_append', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_append', { - defaultMessage: 'Concatenates values of two multi-value fields.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'field1', - type: 'boolean', + name: 'field', + type: 'double', optional: false, }, + ], + returnType: 'double', + }, + { + params: [ { - name: 'field2', - type: 'boolean', + name: 'field', + type: 'geo_point', optional: false, }, ], - returnType: 'boolean', + returnType: 'geo_point', }, { params: [ { - name: 'field1', - type: 'cartesian_point', + name: 'field', + type: 'geo_shape', optional: false, }, + ], + returnType: 'geo_shape', + }, + { + params: [ { - name: 'field2', - type: 'cartesian_point', + name: 'field', + type: 'integer', optional: false, }, ], - returnType: 'cartesian_point', + returnType: 'integer', }, { params: [ { - name: 'field1', - type: 'cartesian_shape', + name: 'field', + type: 'ip', optional: false, }, + ], + returnType: 'ip', + }, + { + params: [ { - name: 'field2', - type: 'cartesian_shape', + name: 'field', + type: 'keyword', optional: false, }, ], - returnType: 'cartesian_shape', + returnType: 'keyword', }, { params: [ { - name: 'field1', - type: 'date', + name: 'field', + type: 'long', optional: false, }, + ], + returnType: 'long', + }, + { + params: [ { - name: 'field2', - type: 'date', + name: 'field', + type: 'text', optional: false, }, ], - returnType: 'date', + returnType: 'text', }, { params: [ { - name: 'field1', - type: 'number', + name: 'field', + type: 'unsigned_long', optional: false, }, + ], + returnType: 'unsigned_long', + }, + { + params: [ { - name: 'field2', - type: 'number', + name: 'field', + type: 'version', optional: false, }, ], - returnType: 'number', + returnType: 'version', }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a="foo;bar;baz"\n| EVAL last_a = MV_LAST(SPLIT(a, ";"))'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const mvMaxDefinition: FunctionDefinition = { + type: 'eval', + name: 'mv_max', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_max', { + defaultMessage: + 'Converts a multivalued expression into a single valued column containing the maximum value.', + }), + alias: undefined, + signatures: [ { params: [ { - name: 'field1', - type: 'geo_point', + name: 'field', + type: 'boolean', optional: false, }, + ], + returnType: 'boolean', + }, + { + params: [ { - name: 'field2', - type: 'geo_point', + name: 'field', + type: 'date', optional: false, }, ], - returnType: 'geo_point', + returnType: 'date', }, { params: [ { - name: 'field1', - type: 'geo_shape', + name: 'field', + type: 'double', optional: false, }, + ], + returnType: 'double', + }, + { + params: [ { - name: 'field2', - type: 'geo_shape', + name: 'field', + type: 'integer', optional: false, }, ], - returnType: 'geo_shape', + returnType: 'integer', }, { params: [ { - name: 'field1', + name: 'field', type: 'ip', optional: false, }, + ], + returnType: 'ip', + }, + { + params: [ { - name: 'field2', - type: 'ip', + name: 'field', + type: 'keyword', optional: false, }, ], - returnType: 'ip', + returnType: 'keyword', }, { params: [ { - name: 'field1', - type: 'string', + name: 'field', + type: 'long', optional: false, }, + ], + returnType: 'long', + }, + { + params: [ { - name: 'field2', - type: 'string', + name: 'field', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'text', }, { params: [ { - name: 'field1', - type: 'version', + name: 'field', + type: 'unsigned_long', optional: false, }, + ], + returnType: 'unsigned_long', + }, + { + params: [ { - name: 'field2', + name: 'field', type: 'version', optional: false, }, @@ -1682,19 +3956,22 @@ const mvAppendDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: [], + examples: [ + 'ROW a=[3, 5, 1]\n| EVAL max_a = MV_MAX(a)', + 'ROW a=["foo", "zoo", "bar"]\n| EVAL max_a = MV_MAX(a)', + ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvAvgDefinition: FunctionDefinition = { +const mvMedianDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_avg', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_avg', { + name: 'mv_median', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_median', { defaultMessage: - 'Converts a multivalued field into a single valued field containing the average of all of the values.', + 'Converts a multivalued field into a single valued field containing the median value.', }), alias: undefined, signatures: [ @@ -1702,61 +3979,59 @@ const mvAvgDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a=[3, 5, 1, 6]\n| EVAL avg_a = MV_AVG(a)'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvConcatDefinition: FunctionDefinition = { - type: 'eval', - name: 'mv_concat', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_concat', { - defaultMessage: - 'Converts a multivalued string expression into a single valued column containing the concatenation of all values separated by a delimiter.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'string', - type: 'string', + name: 'number', + type: 'integer', optional: false, }, + ], + returnType: 'integer', + }, + { + params: [ { - name: 'delim', - type: 'string', + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'string', + returnType: 'unsigned_long', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ - 'ROW a=["foo", "zoo", "bar"]\n| EVAL j = MV_CONCAT(a, ", ")', - 'ROW a=[10, 9, 8]\n| EVAL j = MV_CONCAT(TO_STRING(a), ", ")', + 'ROW a=[3, 5, 1]\n| EVAL median_a = MV_MEDIAN(a)', + 'ROW a=[3, 7, 1, 6]\n| EVAL median_a = MV_MEDIAN(a)', ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvCountDefinition: FunctionDefinition = { +const mvMinDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_count', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_count', { + name: 'mv_min', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_min', { defaultMessage: - 'Converts a multivalued expression into a single valued column containing a count of the number of values.', + 'Converts a multivalued expression into a single valued column containing the minimum value.', }), alias: undefined, signatures: [ @@ -1768,87 +4043,87 @@ const mvCountDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'boolean', }, { params: [ { name: 'field', - type: 'cartesian_point', + type: 'date', optional: false, }, ], - returnType: 'number', + returnType: 'date', }, { params: [ { name: 'field', - type: 'cartesian_shape', + type: 'double', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ { name: 'field', - type: 'date', + type: 'integer', optional: false, }, ], - returnType: 'number', + returnType: 'integer', }, { params: [ { name: 'field', - type: 'number', + type: 'ip', optional: false, }, ], - returnType: 'number', + returnType: 'ip', }, { params: [ { name: 'field', - type: 'geo_point', + type: 'keyword', optional: false, }, ], - returnType: 'number', + returnType: 'keyword', }, { params: [ { name: 'field', - type: 'geo_shape', + type: 'long', optional: false, }, ], - returnType: 'number', + returnType: 'long', }, { params: [ { name: 'field', - type: 'ip', + type: 'text', optional: false, }, ], - returnType: 'number', + returnType: 'text', }, { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'unsigned_long', }, { params: [ @@ -1858,21 +4133,62 @@ const mvCountDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'version', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [ + 'ROW a=[2, 1]\n| EVAL min_a = MV_MIN(a)', + 'ROW a=["foo", "bar"]\n| EVAL min_a = MV_MIN(a)', + ], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const mvPseriesWeightedSumDefinition: FunctionDefinition = { + type: 'eval', + name: 'mv_pseries_weighted_sum', + description: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.definitions.mv_pseries_weighted_sum', + { + defaultMessage: + 'Converts a multivalued expression into a single-valued column by multiplying every element on the input list by its corresponding term in P-Series and computing the sum.', + } + ), + alias: undefined, + signatures: [ + { + params: [ + { + name: 'number', + type: 'double', + optional: false, + }, + { + name: 'p', + type: 'double', + optional: false, + }, + ], + returnType: 'double', }, ], supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW a=["foo", "zoo", "bar"]\n| EVAL count_a = MV_COUNT(a)'], + examples: [ + 'ROW a = [70.0, 45.0, 21.0, 21.0, 21.0]\n| EVAL sum = MV_PSERIES_WEIGHTED_SUM(a, 1.5)\n| KEEP sum', + ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvDedupeDefinition: FunctionDefinition = { +const mvSliceDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_dedupe', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_dedupe', { - defaultMessage: 'Remove duplicate values from a multivalued field.', + name: 'mv_slice', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_slice', { + defaultMessage: + 'Returns a subset of the multivalued field using the start and end index values.', }), alias: undefined, signatures: [ @@ -1883,6 +4199,16 @@ const mvDedupeDefinition: FunctionDefinition = { type: 'boolean', optional: false, }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], returnType: 'boolean', }, @@ -1893,6 +4219,16 @@ const mvDedupeDefinition: FunctionDefinition = { type: 'cartesian_point', optional: false, }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], returnType: 'cartesian_point', }, @@ -1903,6 +4239,16 @@ const mvDedupeDefinition: FunctionDefinition = { type: 'cartesian_shape', optional: false, }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], returnType: 'cartesian_shape', }, @@ -1913,6 +4259,16 @@ const mvDedupeDefinition: FunctionDefinition = { type: 'date', optional: false, }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], returnType: 'date', }, @@ -1920,11 +4276,21 @@ const mvDedupeDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'number', + type: 'double', + optional: false, + }, + { + name: 'start', + type: 'integer', optional: false, }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], - returnType: 'number', + returnType: 'double', }, { params: [ @@ -1933,6 +4299,16 @@ const mvDedupeDefinition: FunctionDefinition = { type: 'geo_point', optional: false, }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], returnType: 'geo_point', }, @@ -1943,28 +4319,118 @@ const mvDedupeDefinition: FunctionDefinition = { type: 'geo_shape', optional: false, }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, + ], + returnType: 'geo_shape', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'ip', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, + ], + returnType: 'ip', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], - returnType: 'geo_shape', + returnType: 'long', }, { params: [ { name: 'field', - type: 'ip', + type: 'text', optional: false, }, - ], - returnType: 'ip', - }, - { - params: [ { - name: 'field', - type: 'string', + name: 'start', + type: 'integer', optional: false, }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], - returnType: 'string', + returnType: 'text', }, { params: [ @@ -1973,23 +4439,35 @@ const mvDedupeDefinition: FunctionDefinition = { type: 'version', optional: false, }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'end', + type: 'integer', + optional: true, + }, ], returnType: 'version', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW a=["foo", "foo", "bar", "foo"]\n| EVAL dedupe_a = MV_DEDUPE(a)'], + examples: [ + 'row a = [1, 2, 2, 3]\n| eval a1 = mv_slice(a, 1), a2 = mv_slice(a, 2, 3)', + 'row a = [1, 2, 2, 3]\n| eval a1 = mv_slice(a, -2), a2 = mv_slice(a, -3, -1)', + ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvFirstDefinition: FunctionDefinition = { +const mvSortDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_first', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_first', { - defaultMessage: - "Converts a multivalued expression into a single valued column containing the\nfirst value. This is most useful when reading from a function that emits\nmultivalued columns in a known order like `SPLIT`.\n\nThe order that multivalued fields are read from\nunderlying storage is not guaranteed. It is *frequently* ascending, but don't\nrely on that. If you need the minimum value use `MV_MIN` instead of\n`MV_FIRST`. `MV_MIN` has optimizations for sorted values so there isn't a\nperformance benefit to `MV_FIRST`.", + name: 'mv_sort', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_sort', { + defaultMessage: 'Sorts a multivalued field in lexicographical order.', }), alias: undefined, signatures: [ @@ -2000,6 +4478,12 @@ const mvFirstDefinition: FunctionDefinition = { type: 'boolean', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], returnType: 'boolean', }, @@ -2007,81 +4491,113 @@ const mvFirstDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'cartesian_point', + type: 'date', optional: false, }, - ], - returnType: 'cartesian_point', - }, - { - params: [ { - name: 'field', - type: 'cartesian_shape', - optional: false, + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], }, ], - returnType: 'cartesian_shape', + returnType: 'date', }, { params: [ { name: 'field', - type: 'date', + type: 'double', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], - returnType: 'date', + returnType: 'double', }, { params: [ { name: 'field', - type: 'number', + type: 'integer', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], - returnType: 'number', + returnType: 'integer', }, { params: [ { name: 'field', - type: 'geo_point', + type: 'ip', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], - returnType: 'geo_point', + returnType: 'ip', }, { params: [ { name: 'field', - type: 'geo_shape', + type: 'keyword', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], - returnType: 'geo_shape', + returnType: 'keyword', }, { params: [ { name: 'field', - type: 'ip', + type: 'long', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], - returnType: 'ip', + returnType: 'long', }, { params: [ { name: 'field', - type: 'string', + type: 'text', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], - returnType: 'string', + returnType: 'text', }, { params: [ @@ -2090,874 +4606,837 @@ const mvFirstDefinition: FunctionDefinition = { type: 'version', optional: false, }, + { + name: 'order', + type: 'keyword', + optional: true, + literalOptions: ['asc', 'desc'], + }, ], returnType: 'version', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW a="foo;bar;baz"\n| EVAL first_a = MV_FIRST(SPLIT(a, ";"))'], + + examples: ['ROW a = [4, 2, -3, 2]\n| EVAL sa = mv_sort(a), sd = mv_sort(a, "DESC")'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvLastDefinition: FunctionDefinition = { +const mvSumDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_last', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_last', { + name: 'mv_sum', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_sum', { defaultMessage: - "Converts a multivalue expression into a single valued column containing the last\nvalue. This is most useful when reading from a function that emits multivalued\ncolumns in a known order like `SPLIT`.\n\nThe order that multivalued fields are read from\nunderlying storage is not guaranteed. It is *frequently* ascending, but don't\nrely on that. If you need the maximum value use `MV_MAX` instead of\n`MV_LAST`. `MV_MAX` has optimizations for sorted values so there isn't a\nperformance benefit to `MV_LAST`.", + 'Converts a multivalued field into a single valued field containing the sum of all of the values.', }), alias: undefined, signatures: [ { params: [ { - name: 'field', - type: 'boolean', + name: 'number', + type: 'double', optional: false, }, ], - returnType: 'boolean', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'cartesian_point', + name: 'number', + type: 'integer', optional: false, }, ], - returnType: 'cartesian_point', + returnType: 'integer', }, { params: [ { - name: 'field', - type: 'cartesian_shape', + name: 'number', + type: 'long', optional: false, }, ], - returnType: 'cartesian_shape', + returnType: 'long', }, { params: [ { - name: 'field', - type: 'date', + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'date', + returnType: 'unsigned_long', }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: ['ROW a=[3, 5, 6]\n| EVAL sum_a = MV_SUM(a)'], +}; + +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const mvZipDefinition: FunctionDefinition = { + type: 'eval', + name: 'mv_zip', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_zip', { + defaultMessage: + 'Combines the values from two multivalued fields with a delimiter that joins them together.', + }), + alias: undefined, + signatures: [ { params: [ { - name: 'field', - type: 'number', + name: 'string1', + type: 'keyword', + optional: false, + }, + { + name: 'string2', + type: 'keyword', optional: false, }, ], - returnType: 'number', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'geo_point', + name: 'string1', + type: 'keyword', + optional: false, + }, + { + name: 'string2', + type: 'keyword', optional: false, }, + { + name: 'delim', + type: 'keyword', + optional: true, + }, ], - returnType: 'geo_point', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'geo_shape', + name: 'string1', + type: 'keyword', + optional: false, + }, + { + name: 'string2', + type: 'keyword', optional: false, }, + { + name: 'delim', + type: 'text', + optional: true, + }, ], - returnType: 'geo_shape', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'ip', + name: 'string1', + type: 'keyword', + optional: false, + }, + { + name: 'string2', + type: 'text', optional: false, }, ], - returnType: 'ip', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'string', + name: 'string1', + type: 'keyword', + optional: false, + }, + { + name: 'string2', + type: 'text', optional: false, }, + { + name: 'delim', + type: 'keyword', + optional: true, + }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'version', + name: 'string1', + type: 'keyword', optional: false, }, + { + name: 'string2', + type: 'text', + optional: false, + }, + { + name: 'delim', + type: 'text', + optional: true, + }, ], - returnType: 'version', + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a="foo;bar;baz"\n| EVAL last_a = MV_LAST(SPLIT(a, ";"))'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvMaxDefinition: FunctionDefinition = { - type: 'eval', - name: 'mv_max', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_max', { - defaultMessage: - 'Converts a multivalued expression into a single valued column containing the maximum value.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'field', - type: 'boolean', + name: 'string1', + type: 'text', + optional: false, + }, + { + name: 'string2', + type: 'keyword', optional: false, }, ], - returnType: 'boolean', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'date', + name: 'string1', + type: 'text', + optional: false, + }, + { + name: 'string2', + type: 'keyword', optional: false, }, + { + name: 'delim', + type: 'keyword', + optional: true, + }, ], - returnType: 'date', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'number', + name: 'string1', + type: 'text', optional: false, }, + { + name: 'string2', + type: 'keyword', + optional: false, + }, + { + name: 'delim', + type: 'text', + optional: true, + }, ], - returnType: 'number', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'ip', + name: 'string1', + type: 'text', + optional: false, + }, + { + name: 'string2', + type: 'text', optional: false, }, ], - returnType: 'ip', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'string', + name: 'string1', + type: 'text', + optional: false, + }, + { + name: 'string2', + type: 'text', optional: false, }, + { + name: 'delim', + type: 'keyword', + optional: true, + }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ { - name: 'field', - type: 'version', + name: 'string1', + type: 'text', + optional: false, + }, + { + name: 'string2', + type: 'text', optional: false, }, + { + name: 'delim', + type: 'text', + optional: true, + }, ], - returnType: 'version', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ - 'ROW a=[3, 5, 1]\n| EVAL max_a = MV_MAX(a)', - 'ROW a=["foo", "zoo", "bar"]\n| EVAL max_a = MV_MAX(a)', + 'ROW a = ["x", "y", "z"], b = ["1", "2"]\n| EVAL c = mv_zip(a, b, "-")\n| KEEP a, b, c', ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvMedianDefinition: FunctionDefinition = { +const nowDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_median', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_median', { - defaultMessage: - 'Converts a multivalued field into a single valued field containing the median value.', + name: 'now', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.now', { + defaultMessage: 'Returns current date and time.', }), alias: undefined, signatures: [ { - params: [ - { - name: 'number', - type: 'number', - optional: false, - }, - ], - returnType: 'number', + params: [], + returnType: 'date', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: [ - 'ROW a=[3, 5, 1]\n| EVAL median_a = MV_MEDIAN(a)', - 'ROW a=[3, 7, 1, 6]\n| EVAL median_a = MV_MEDIAN(a)', - ], + examples: ['ROW current_date = NOW()', 'FROM sample_data\n| WHERE @timestamp > NOW() - 1 hour'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvMinDefinition: FunctionDefinition = { +const piDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_min', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_min', { - defaultMessage: - 'Converts a multivalued expression into a single valued column containing the minimum value.', + name: 'pi', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.pi', { + defaultMessage: "Returns Pi, the ratio of a circle's circumference to its diameter.", }), alias: undefined, signatures: [ { - params: [ - { - name: 'field', - type: 'boolean', - optional: false, - }, - ], - returnType: 'boolean', - }, - { - params: [ - { - name: 'field', - type: 'date', - optional: false, - }, - ], - returnType: 'date', - }, - { - params: [ - { - name: 'field', - type: 'number', - optional: false, - }, - ], - returnType: 'number', - }, - { - params: [ - { - name: 'field', - type: 'ip', - optional: false, - }, - ], - returnType: 'ip', - }, - { - params: [ - { - name: 'field', - type: 'string', - optional: false, - }, - ], - returnType: 'string', - }, - { - params: [ - { - name: 'field', - type: 'version', - optional: false, - }, - ], - returnType: 'version', + params: [], + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: [ - 'ROW a=[2, 1]\n| EVAL min_a = MV_MIN(a)', - 'ROW a=["foo", "bar"]\n| EVAL min_a = MV_MIN(a)', - ], + examples: ['ROW PI()'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvSliceDefinition: FunctionDefinition = { +const powDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_slice', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_slice', { - defaultMessage: - 'Returns a subset of the multivalued field using the start and end index values.', + name: 'pow', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.pow', { + defaultMessage: 'Returns the value of `base` raised to the power of `exponent`.', }), alias: undefined, signatures: [ { params: [ { - name: 'field', - type: 'boolean', + name: 'base', + type: 'double', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'double', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'boolean', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'cartesian_point', + name: 'base', + type: 'double', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'integer', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'cartesian_point', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'cartesian_shape', + name: 'base', + type: 'double', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'long', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'cartesian_shape', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'date', + name: 'base', + type: 'double', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'unsigned_long', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'date', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'number', + name: 'base', + type: 'integer', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'double', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'number', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'geo_point', + name: 'base', + type: 'integer', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'integer', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'geo_point', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'geo_shape', + name: 'base', + type: 'integer', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'long', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'geo_shape', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'ip', + name: 'base', + type: 'integer', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'unsigned_long', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'ip', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'string', + name: 'base', + type: 'long', optional: false, }, { - name: 'start', - type: 'number', + name: 'exponent', + type: 'double', optional: false, }, - { - name: 'end', - type: 'number', - optional: true, - }, ], - returnType: 'string', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'version', - optional: false, - }, - { - name: 'start', - type: 'number', + name: 'base', + type: 'long', optional: false, - }, - { - name: 'end', - type: 'number', - optional: true, - }, - ], - returnType: 'version', - }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: [ - 'row a = [1, 2, 2, 3]\n| eval a1 = mv_slice(a, 1), a2 = mv_slice(a, 2, 3)', - 'row a = [1, 2, 2, 3]\n| eval a1 = mv_slice(a, -2), a2 = mv_slice(a, -3, -1)', - ], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvSortDefinition: FunctionDefinition = { - type: 'eval', - name: 'mv_sort', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_sort', { - defaultMessage: 'Sorts a multivalued field in lexicographical order.', - }), - alias: undefined, - signatures: [ + }, + { + name: 'exponent', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, { params: [ { - name: 'field', - type: 'boolean', + name: 'base', + type: 'long', optional: false, }, { - name: 'order', - type: 'string', - optional: true, - literalOptions: ['asc', 'desc'], + name: 'exponent', + type: 'long', + optional: false, }, ], - returnType: 'boolean', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'date', + name: 'base', + type: 'long', optional: false, }, { - name: 'order', - type: 'string', - optional: true, - literalOptions: ['asc', 'desc'], + name: 'exponent', + type: 'unsigned_long', + optional: false, }, ], - returnType: 'date', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'number', + name: 'base', + type: 'unsigned_long', optional: false, }, { - name: 'order', - type: 'string', - optional: true, - literalOptions: ['asc', 'desc'], + name: 'exponent', + type: 'double', + optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'ip', + name: 'base', + type: 'unsigned_long', optional: false, }, { - name: 'order', - type: 'string', - optional: true, - literalOptions: ['asc', 'desc'], + name: 'exponent', + type: 'integer', + optional: false, }, ], - returnType: 'ip', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'string', + name: 'base', + type: 'unsigned_long', optional: false, }, { - name: 'order', - type: 'string', - optional: true, - literalOptions: ['asc', 'desc'], + name: 'exponent', + type: 'long', + optional: false, }, ], - returnType: 'string', + returnType: 'double', }, { params: [ { - name: 'field', - type: 'version', + name: 'base', + type: 'unsigned_long', optional: false, }, { - name: 'order', - type: 'string', - optional: true, - literalOptions: ['asc', 'desc'], + name: 'exponent', + type: 'unsigned_long', + optional: false, }, ], - returnType: 'version', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW a = [4, 2, -3, 2]\n| EVAL sa = mv_sort(a), sd = mv_sort(a, "DESC")'], + examples: [ + 'ROW base = 2.0, exponent = 2\n| EVAL result = POW(base, exponent)', + 'ROW base = 4, exponent = 0.5\n| EVAL s = POW(base, exponent)', + ], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvSumDefinition: FunctionDefinition = { +const repeatDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_sum', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_sum', { + name: 'repeat', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.repeat', { defaultMessage: - 'Converts a multivalued field into a single valued field containing the sum of all of the values.', + 'Returns a string constructed by concatenating `string` with itself the specified `number` of times.', }), alias: undefined, signatures: [ { params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, { name: 'number', - type: 'number', + type: 'integer', optional: false, }, ], - returnType: 'number', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['ROW a=[3, 5, 6]\n| EVAL sum_a = MV_SUM(a)'], + examples: ['ROW a = "Hello!"\n| EVAL triple_a = REPEAT(a, 3);'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts -const mvZipDefinition: FunctionDefinition = { +const replaceDefinition: FunctionDefinition = { type: 'eval', - name: 'mv_zip', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.mv_zip', { + name: 'replace', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.replace', { defaultMessage: - 'Combines the values from two multivalued fields with a delimiter that joins them together.', + 'The function substitutes in the string `str` any match of the regular expression `regex`\nwith the replacement string `newStr`.', }), alias: undefined, signatures: [ { params: [ { - name: 'string1', - type: 'string', + name: 'string', + type: 'keyword', optional: false, }, { - name: 'string2', - type: 'string', + name: 'regex', + type: 'keyword', + optional: false, + }, + { + name: 'newString', + type: 'keyword', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ { - name: 'string1', - type: 'string', + name: 'string', + type: 'keyword', optional: false, }, { - name: 'string2', - type: 'string', + name: 'regex', + type: 'keyword', optional: false, }, { - name: 'delim', - type: 'string', - optional: true, + name: 'newString', + type: 'text', + optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: [ - 'ROW a = ["x", "y", "z"], b = ["1", "2"]\n| EVAL c = mv_zip(a, b, "-")\n| KEEP a, b, c', - ], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const nowDefinition: FunctionDefinition = { - type: 'eval', - name: 'now', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.now', { - defaultMessage: 'Returns current date and time.', - }), - alias: undefined, - signatures: [ { - params: [], - returnType: 'date', + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'regex', + type: 'text', + optional: false, + }, + { + name: 'newString', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW current_date = NOW()', 'FROM sample_data\n| WHERE @timestamp > NOW() - 1 hour'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const piDefinition: FunctionDefinition = { - type: 'eval', - name: 'pi', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.pi', { - defaultMessage: "Returns Pi, the ratio of a circle's circumference to its diameter.", - }), - alias: undefined, - signatures: [ { - params: [], - returnType: 'number', + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'regex', + type: 'text', + optional: false, + }, + { + name: 'newString', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW PI()'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const powDefinition: FunctionDefinition = { - type: 'eval', - name: 'pow', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.pow', { - defaultMessage: 'Returns the value of `base` raised to the power of `exponent`.', - }), - alias: undefined, - signatures: [ { params: [ { - name: 'base', - type: 'number', + name: 'string', + type: 'text', optional: false, }, { - name: 'exponent', - type: 'number', + name: 'regex', + type: 'keyword', + optional: false, + }, + { + name: 'newString', + type: 'keyword', optional: false, }, ], - returnType: 'number', + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: [ - 'ROW base = 2.0, exponent = 2\n| EVAL result = POW(base, exponent)', - 'ROW base = 4, exponent = 0.5\n| EVAL s = POW(base, exponent)', - ], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const repeatDefinition: FunctionDefinition = { - type: 'eval', - name: 'repeat', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.repeat', { - defaultMessage: - 'Returns a string constructed by concatenating `string` with itself the specified `number` of times.', - }), - alias: undefined, - signatures: [ { params: [ { name: 'string', - type: 'string', + type: 'text', optional: false, }, { - name: 'number', - type: 'number', + name: 'regex', + type: 'keyword', + optional: false, + }, + { + name: 'newString', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'regex', + type: 'text', + optional: false, + }, + { + name: 'newString', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', }, - ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], - supportedOptions: ['by'], - validate: undefined, - examples: ['ROW a = "Hello!"\n| EVAL triple_a = REPEAT(a, 3);'], -}; - -// Do not edit this manually... generated by scripts/generate_function_definitions.ts -const replaceDefinition: FunctionDefinition = { - type: 'eval', - name: 'replace', - description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.replace', { - defaultMessage: - 'The function substitutes in the string `str` any match of the regular expression `regex`\nwith the replacement string `newStr`.', - }), - alias: undefined, - signatures: [ { params: [ { name: 'string', - type: 'string', + type: 'text', optional: false, }, { name: 'regex', - type: 'string', + type: 'text', optional: false, }, { name: 'newString', - type: 'string', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW str = "Hello World"\n| EVAL str = REPLACE(str, "World", "Universe")\n| KEEP str'], @@ -2977,19 +5456,34 @@ const rightDefinition: FunctionDefinition = { params: [ { name: 'string', - type: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'length', + type: 'integer', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', optional: false, }, { name: 'length', - type: 'number', + type: 'integer', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3011,29 +5505,89 @@ const roundDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'double', + optional: false, + }, + { + name: 'decimals', + type: 'integer', + optional: true, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + { + name: 'decimals', + type: 'integer', + optional: true, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'number', + type: 'long', optional: false, }, ], - returnType: 'number', + returnType: 'long', }, { params: [ { name: 'number', - type: 'number', + type: 'long', optional: false, }, { name: 'decimals', - type: 'number', + type: 'integer', optional: true, }, ], - returnType: 'number', + returnType: 'long', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'unsigned_long', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3054,14 +5608,24 @@ const rtrimDefinition: FunctionDefinition = { params: [ { name: 'string', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'text', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3083,14 +5647,44 @@ const signumDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW d = 100.0\n| EVAL s = SIGNUM(d)'], @@ -3109,14 +5703,44 @@ const sinDefinition: FunctionDefinition = { params: [ { name: 'angle', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL sin=SIN(a)'], @@ -3135,14 +5759,44 @@ const sinhDefinition: FunctionDefinition = { params: [ { name: 'angle', - type: 'number', + type: 'double', optional: false, }, ], - returnType: 'number', + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL sinh=SINH(a)'], @@ -3161,19 +5815,64 @@ const splitDefinition: FunctionDefinition = { params: [ { name: 'string', - type: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'delim', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'delim', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', + optional: false, + }, + { + name: 'delim', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', optional: false, }, { name: 'delim', - type: 'string', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW words="foo;bar;baz;qux;quux;corge"\n| EVAL word = SPLIT(words, ";")'], @@ -3193,14 +5892,44 @@ const sqrtDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW d = 100.0\n| EVAL s = SQRT(d)'], @@ -3337,7 +6066,7 @@ const stContainsDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3476,7 +6205,7 @@ const stDisjointDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3507,7 +6236,7 @@ const stDistanceDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ @@ -3522,10 +6251,10 @@ const stDistanceDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3664,7 +6393,7 @@ const stIntersectsDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3803,7 +6532,7 @@ const stWithinDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3829,7 +6558,7 @@ const stXDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ @@ -3839,10 +6568,10 @@ const stXDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3868,7 +6597,7 @@ const stYDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ @@ -3878,10 +6607,10 @@ const stYDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3903,19 +6632,64 @@ const startsWithDefinition: FunctionDefinition = { params: [ { name: 'str', - type: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'prefix', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'str', + type: 'keyword', + optional: false, + }, + { + name: 'prefix', + type: 'text', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'str', + type: 'text', + optional: false, + }, + { + name: 'prefix', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'str', + type: 'text', optional: false, }, { name: 'prefix', - type: 'string', + type: 'text', optional: false, }, ], returnType: 'boolean', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['FROM employees\n| KEEP last_name\n| EVAL ln_S = STARTS_WITH(last_name, "B")'], @@ -3935,24 +6709,44 @@ const substringDefinition: FunctionDefinition = { params: [ { name: 'string', - type: 'string', + type: 'keyword', + optional: false, + }, + { + name: 'start', + type: 'integer', + optional: false, + }, + { + name: 'length', + type: 'integer', + optional: true, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', optional: false, }, { name: 'start', - type: 'number', + type: 'integer', optional: false, }, { name: 'length', - type: 'number', + type: 'integer', optional: true, }, ], - returnType: 'string', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3975,14 +6769,44 @@ const tanDefinition: FunctionDefinition = { params: [ { name: 'angle', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL tan=TAN(a)'], @@ -4001,14 +6825,44 @@ const tanhDefinition: FunctionDefinition = { params: [ { name: 'angle', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'long', optional: false, }, ], - returnType: 'number', + returnType: 'double', + }, + { + params: [ + { + name: 'angle', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL tanh=TANH(a)'], @@ -4025,10 +6879,10 @@ const tauDefinition: FunctionDefinition = { signatures: [ { params: [], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW TAU()'], @@ -4047,14 +6901,24 @@ const toBase64Definition: FunctionDefinition = { params: [ { name: 'string', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['row a = "elastic" \n| eval e = to_base64(a)'], @@ -4084,7 +6948,47 @@ const toBooleanDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], @@ -4094,14 +6998,14 @@ const toBooleanDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], returnType: 'boolean', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW str = ["true", "TRuE", "false", "", "yes", "1"]\n| EVAL bool = TO_BOOLEAN(str)'], @@ -4134,14 +7038,24 @@ const toCartesianpointDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'cartesian_point', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], returnType: 'cartesian_point', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4186,14 +7100,24 @@ const toCartesianshapeDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'cartesian_shape', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], returnType: 'cartesian_shape', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4225,7 +7149,47 @@ const toDatetimeDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + ], + returnType: 'date', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], @@ -4235,14 +7199,14 @@ const toDatetimeDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], returnType: 'date', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4264,14 +7228,44 @@ const toDegreesDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW rad = [1.57, 3.14, 4.71]\n| EVAL deg = TO_DEGREES(rad)'], @@ -4291,44 +7285,114 @@ const toDoubleDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'boolean', + type: 'boolean', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field', + type: 'counter_double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field', + type: 'counter_integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field', + type: 'counter_long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field', + type: 'date', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'field', + type: 'keyword', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ { name: 'field', - type: 'number', + type: 'long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ { name: 'field', - type: 'date', + type: 'text', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4360,14 +7424,24 @@ const toGeopointDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'geo_point', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], returnType: 'geo_point', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW wkt = "POINT(42.97109630194 14.7552534413725)"\n| EVAL pt = TO_GEOPOINT(wkt)'], @@ -4407,14 +7481,24 @@ const toGeoshapeDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'geo_shape', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], returnType: 'geo_shape', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4440,17 +7524,17 @@ const toIntegerDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'integer', }, { params: [ { name: 'field', - type: 'number', + type: 'counter_integer', optional: false, }, ], - returnType: 'number', + returnType: 'integer', }, { params: [ @@ -4460,20 +7544,70 @@ const toIntegerDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + ], + returnType: 'integer', + }, + { + params: [ + { + name: 'field', + type: 'text', + optional: false, + }, + ], + returnType: 'integer', }, { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'integer', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW long = [5013792, 2147483647, 501379200000]\n| EVAL int = TO_INTEGER(long)'], @@ -4502,14 +7636,24 @@ const toIpDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'ip', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], returnType: 'ip', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4535,17 +7679,27 @@ const toLongDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'counter_integer', + optional: false, + }, + ], + returnType: 'long', }, { params: [ { name: 'field', - type: 'number', + type: 'counter_long', optional: false, }, ], - returnType: 'number', + returnType: 'long', }, { params: [ @@ -4555,20 +7709,70 @@ const toLongDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + ], + returnType: 'long', + }, + { + params: [ + { + name: 'field', + type: 'text', + optional: false, + }, + ], + returnType: 'long', }, { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'long', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4589,14 +7793,24 @@ const toLowerDefinition: FunctionDefinition = { params: [ { name: 'str', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'str', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'text', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW message = "Some Text"\n| EVAL message_lower = TO_LOWER(message)'], @@ -4615,14 +7829,44 @@ const toRadiansDefinition: FunctionDefinition = { params: [ { name: 'number', - type: 'number', + type: 'double', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'integer', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'long', + optional: false, + }, + ], + returnType: 'double', + }, + { + params: [ + { + name: 'number', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'double', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW deg = [90.0, 180.0, 270.0]\n| EVAL rad = TO_RADIANS(deg)'], @@ -4645,7 +7889,7 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ @@ -4655,7 +7899,7 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ @@ -4665,7 +7909,7 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ @@ -4675,17 +7919,17 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ { name: 'field', - type: 'number', + type: 'double', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ @@ -4695,7 +7939,7 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ @@ -4705,7 +7949,17 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + ], + returnType: 'keyword', }, { params: [ @@ -4715,17 +7969,47 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'field', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', }, { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, { params: [ @@ -4735,10 +8019,10 @@ const toStringDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'string', + returnType: 'keyword', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=10\n| EVAL j = TO_STRING(a)', 'ROW a=[10, 9, 8]\n| EVAL j = TO_STRING(a)'], @@ -4765,7 +8049,7 @@ const toUnsignedLongDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'unsigned_long', }, { params: [ @@ -4775,30 +8059,70 @@ const toUnsignedLongDefinition: FunctionDefinition = { optional: false, }, ], - returnType: 'number', + returnType: 'unsigned_long', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, + }, + ], + returnType: 'unsigned_long', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + }, + ], + returnType: 'unsigned_long', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + }, + ], + returnType: 'unsigned_long', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + }, + ], + returnType: 'unsigned_long', }, { params: [ { name: 'field', - type: 'number', + type: 'text', optional: false, }, ], - returnType: 'number', + returnType: 'unsigned_long', }, { params: [ { name: 'field', - type: 'string', + type: 'unsigned_long', optional: false, }, ], - returnType: 'number', + returnType: 'unsigned_long', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4819,14 +8143,24 @@ const toUpperDefinition: FunctionDefinition = { params: [ { name: 'str', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'str', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'text', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW message = "Some Text"\n| EVAL message_upper = TO_UPPER(message)'], @@ -4845,7 +8179,17 @@ const toVersionDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'version', + }, + { + params: [ + { + name: 'field', + type: 'text', optional: false, }, ], @@ -4862,7 +8206,7 @@ const toVersionDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW v = TO_VERSION("1.2.3")'], @@ -4881,14 +8225,24 @@ const trimDefinition: FunctionDefinition = { params: [ { name: 'string', - type: 'string', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'string', + type: 'text', optional: false, }, ], - returnType: 'string', + returnType: 'text', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4921,7 +8275,7 @@ const caseDefinition: FunctionDefinition = { returnType: 'any', }, ], - supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4970,6 +8324,7 @@ export const evalFunctionDefinitions = [ mvMaxDefinition, mvMedianDefinition, mvMinDefinition, + mvPseriesWeightedSumDefinition, mvSliceDefinition, mvSortDefinition, mvSumDefinition, diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/grouping.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/grouping.ts index 79ac91d14403a..043c6e44d55bc 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/grouping.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/grouping.ts @@ -7,8 +7,53 @@ */ import { i18n } from '@kbn/i18n'; -import { FunctionDefinition } from './types'; +import { FunctionDefinition, FunctionParameterType, FunctionReturnType } from './types'; +const groupingTypeTable: Array< + [ + FunctionParameterType, + FunctionParameterType, + FunctionParameterType | null, + FunctionParameterType | null, + FunctionReturnType + ] +> = [ + // field // bucket //from // to //result + ['date', 'date_period', null, null, 'date'], + ['date', 'integer', 'date', 'date', 'date'], + // Modified time_duration to time_literal + ['date', 'time_literal', null, null, 'date'], + ['double', 'double', null, null, 'double'], + ['double', 'integer', 'double', 'double', 'double'], + ['double', 'integer', 'double', 'integer', 'double'], + ['double', 'integer', 'double', 'long', 'double'], + ['double', 'integer', 'integer', 'double', 'double'], + ['double', 'integer', 'integer', 'integer', 'double'], + ['double', 'integer', 'integer', 'long', 'double'], + ['double', 'integer', 'long', 'double', 'double'], + ['double', 'integer', 'long', 'integer', 'double'], + ['double', 'integer', 'long', 'long', 'double'], + ['integer', 'double', null, null, 'double'], + ['integer', 'integer', 'double', 'double', 'double'], + ['integer', 'integer', 'double', 'integer', 'double'], + ['integer', 'integer', 'double', 'long', 'double'], + ['integer', 'integer', 'integer', 'double', 'double'], + ['integer', 'integer', 'integer', 'integer', 'double'], + ['integer', 'integer', 'integer', 'long', 'double'], + ['integer', 'integer', 'long', 'double', 'double'], + ['integer', 'integer', 'long', 'integer', 'double'], + ['integer', 'integer', 'long', 'long', 'double'], + ['long', 'double', null, null, 'double'], + ['long', 'integer', 'double', 'double', 'double'], + ['long', 'integer', 'double', 'integer', 'double'], + ['long', 'integer', 'double', 'long', 'double'], + ['long', 'integer', 'integer', 'double', 'double'], + ['long', 'integer', 'integer', 'integer', 'double'], + ['long', 'integer', 'integer', 'long', 'double'], + ['long', 'integer', 'long', 'double', 'double'], + ['long', 'integer', 'long', 'integer', 'double'], + ['long', 'integer', 'long', 'long', 'double'], +]; export const groupingFunctionDefinitions: FunctionDefinition[] = [ { name: 'bucket', @@ -21,65 +66,18 @@ export const groupingFunctionDefinitions: FunctionDefinition[] = [ supportedCommands: ['stats'], supportedOptions: ['by'], signatures: [ - { - params: [ - { name: 'field', type: 'date' }, - { name: 'buckets', type: 'time_literal', constantOnly: true }, - ], - returnType: 'date', - }, - { - params: [ - { name: 'field', type: 'number' }, - { name: 'buckets', type: 'number', constantOnly: true }, - ], - returnType: 'number', - }, - { - params: [ - { name: 'field', type: 'date' }, - { name: 'buckets', type: 'number', constantOnly: true }, - { name: 'startDate', type: 'string', constantOnly: true }, - { name: 'endDate', type: 'string', constantOnly: true }, - ], - returnType: 'date', - }, - { - params: [ - { name: 'field', type: 'date' }, - { name: 'buckets', type: 'number', constantOnly: true }, - { name: 'startDate', type: 'date', constantOnly: true }, - { name: 'endDate', type: 'date', constantOnly: true }, - ], - returnType: 'date', - }, - { - params: [ - { name: 'field', type: 'date' }, - { name: 'buckets', type: 'number', constantOnly: true }, - { name: 'startDate', type: 'string', constantOnly: true }, - { name: 'endDate', type: 'date', constantOnly: true }, - ], - returnType: 'date', - }, - { - params: [ - { name: 'field', type: 'date' }, - { name: 'buckets', type: 'number', constantOnly: true }, - { name: 'startDate', type: 'date', constantOnly: true }, - { name: 'endDate', type: 'string', constantOnly: true }, - ], - returnType: 'date', - }, - { - params: [ - { name: 'field', type: 'number' }, - { name: 'buckets', type: 'number', constantOnly: true }, - { name: 'startValue', type: 'number', constantOnly: true }, - { name: 'endValue', type: 'number', constantOnly: true }, - ], - returnType: 'number', - }, + ...groupingTypeTable.map((signature) => { + const [fieldType, bucketType, fromType, toType, resultType] = signature; + return { + params: [ + { name: 'field', type: fieldType }, + { name: 'buckets', type: bucketType, constantOnly: true }, + ...(fromType ? [{ name: 'startDate', type: fromType, constantOnly: true }] : []), + ...(toType ? [{ name: 'endDate', type: toType, constantOnly: true }] : []), + ], + returnType: resultType, + }; + }), ], examples: [ 'from index | eval hd = bucket(bytes, 1 hour)', diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts index a44db712ab230..3773a7c30e3d1 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/helpers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { CommandDefinition, FunctionDefinition } from './types'; +import type { CommandDefinition, FunctionDefinition, FunctionParameterType } from './types'; /** * Given a function definition, this function will return a list of function signatures @@ -104,7 +104,7 @@ export function printArguments( optional, }: { name: string; - type: string | string[]; + type: FunctionParameterType | FunctionParameterType[]; optional?: boolean; }, withTypes: boolean diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts index dbf0b7782d1a4..2e8c32b5be003 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts @@ -8,49 +8,90 @@ import type { ESQLCommand, ESQLCommandOption, ESQLFunction, ESQLMessage } from '@kbn/esql-ast'; -export const supportedFieldTypes = [ - 'number', - 'date', - 'string', +/** + * All supported field types in ES|QL. This is all the types + * that can come back in the table from a query. + */ +export const fieldTypes = [ 'boolean', + 'date', + 'double', 'ip', + 'keyword', + 'integer', + 'long', + 'text', + 'unsigned_long', + 'version', 'cartesian_point', 'cartesian_shape', 'geo_point', 'geo_shape', - 'version', + 'counter_integer', + 'counter_long', + 'counter_double', + 'unsupported', ] as const; -export const isSupportedFieldType = (type: string): type is SupportedFieldType => - supportedFieldTypes.includes(type as SupportedFieldType); +export type FieldType = (typeof fieldTypes)[number]; + +export const isFieldType = (type: string | FunctionParameterType): type is FieldType => + fieldTypes.includes(type as FieldType); -export type SupportedFieldType = (typeof supportedFieldTypes)[number]; +/** + * This is the list of all data types that are supported in ES|QL. + * + * Not all of these can be used as field types. Some can only be literals, + * others may be the value of a field, but cannot be used in the index mapping. + * + * This is a partial list. The full list is here and we may need to expand this type as + * the capabilities of the client-side engines grow. + * https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java + */ +export const dataTypes = [ + ...fieldTypes, + 'null', + 'time_literal', // @TODO consider merging time_literal with time_duration + 'time_duration', + 'date_period', +] as const; -export type FunctionParameterType = - | SupportedFieldType - | 'null' - | 'any' - | 'chrono_literal' - | 'time_literal' - | 'number[]' - | 'string[]' +export type SupportedDataType = (typeof dataTypes)[number]; + +export const isSupportedDataType = ( + type: string | FunctionParameterType +): type is SupportedDataType => dataTypes.includes(type as SupportedDataType); + +/** + * This is a set of array types. These aren't official ES|QL types, but they are + * currently used in the function definitions in a couple of specific scenarios. + * + * The fate of these is uncertain. They may be removed in the future. + */ +type ArrayType = + | 'double[]' + | 'unsigned_long[]' + | 'long[]' + | 'integer[]' + | 'counter_integer[]' + | 'counter_long[]' + | 'counter_double[]' + | 'keyword[]' + | 'text[]' | 'boolean[]' | 'any[]' - | 'date[]'; - -export type FunctionReturnType = - | 'number' - | 'date' - | 'any' - | 'boolean' - | 'string' - | 'cartesian_point' - | 'cartesian_shape' - | 'geo_point' - | 'geo_shape' - | 'ip' - | 'version' - | 'void'; + | 'date[]' + | 'date_period[]'; + +/** + * This is the type of a parameter in a function definition. + */ +export type FunctionParameterType = Omit<SupportedDataType, 'unsupported'> | ArrayType | 'any'; + +/** + * This is the return type of a function definition. + */ +export type FunctionReturnType = Omit<SupportedDataType, 'unsupported'> | 'any' | 'void'; export interface FunctionDefinition { type: 'builtin' | 'agg' | 'eval'; @@ -106,6 +147,10 @@ export interface CommandBaseDefinition { name: string; alias?: string; description: string; + /** + * Whether to show or hide in autocomplete suggestion list + */ + hidden?: boolean; signature: { multipleParams: boolean; // innerType here is useful to drill down the type in case of "column" diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/esql_to_kibana_type.ts b/packages/kbn-esql-validation-autocomplete/src/shared/esql_to_kibana_type.ts deleted file mode 100644 index f13052288f29f..0000000000000 --- a/packages/kbn-esql-validation-autocomplete/src/shared/esql_to_kibana_type.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 and the Server Side Public License, v 1; you may 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 ESQL_NUMBER_TYPES = [ - 'double', - 'unsigned_long', - 'long', - 'integer', - 'int', - 'counter_integer', - 'counter_long', - 'counter_double', -]; - -const ESQL_TEXT_TYPES = ['text', 'keyword', 'string']; - -export const esqlToKibanaType = (elasticsearchType: string) => { - if (ESQL_NUMBER_TYPES.includes(elasticsearchType)) { - return 'number'; - } - - if (ESQL_TEXT_TYPES.includes(elasticsearchType)) { - return 'string'; - } - - if (['datetime', 'time_duration'].includes(elasticsearchType)) { - return 'date'; - } - - if (elasticsearchType === 'bool') { - return 'boolean'; - } - - if (elasticsearchType === 'date_period') { - return 'time_literal'; // TODO - consider aligning with Elasticsearch - } - - return elasticsearchType; -}; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts b/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts new file mode 100644 index 0000000000000..fada6bea88134 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/shared/esql_types.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ESQLDecimalLiteral, ESQLLiteral, ESQLNumericLiteralType } from '@kbn/esql-ast/src/types'; +import { FunctionParameterType } from '../definitions/types'; + +export const ESQL_COMMON_NUMERIC_TYPES = ['double', 'long', 'integer'] as const; +export const ESQL_NUMERIC_DECIMAL_TYPES = [ + 'double', + 'unsigned_long', + 'long', + 'counter_long', + 'counter_double', +] as const; +export const ESQL_NUMBER_TYPES = [ + 'integer', + 'counter_integer', + ...ESQL_NUMERIC_DECIMAL_TYPES, +] as const; + +export const ESQL_STRING_TYPES = ['keyword', 'text'] as const; +export const ESQL_DATE_TYPES = ['datetime', 'date_period'] as const; + +/** + * + * @param type + * @returns + */ +export function isStringType(type: unknown) { + return typeof type === 'string' && ['keyword', 'text'].includes(type); +} + +export function isNumericType(type: unknown): type is ESQLNumericLiteralType { + return ( + typeof type === 'string' && + [...ESQL_NUMBER_TYPES, 'decimal'].includes(type as (typeof ESQL_NUMBER_TYPES)[number]) + ); +} + +export function isNumericDecimalType(type: unknown): type is ESQLDecimalLiteral { + return ( + typeof type === 'string' && + ESQL_NUMERIC_DECIMAL_TYPES.includes(type as (typeof ESQL_NUMERIC_DECIMAL_TYPES)[number]) + ); +} + +/** + * Compares two types, taking into account literal types + * @TODO strengthen typing here (remove `string`) + */ +export const compareTypesWithLiterals = ( + a: ESQLLiteral['literalType'] | FunctionParameterType | string, + b: ESQLLiteral['literalType'] | FunctionParameterType | string +) => { + if (a === b) { + return true; + } + if (a === 'decimal') { + return isNumericDecimalType(b); + } + if (b === 'decimal') { + return isNumericDecimalType(a); + } + if (a === 'string') { + return isStringType(b); + } + if (b === 'string') { + return isStringType(a); + } + return false; +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts index 68658b29251b5..0b9f704a43bcc 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts @@ -33,7 +33,7 @@ import { withOption, appendSeparatorOption, } from '../definitions/options'; -import type { +import { CommandDefinition, CommandOptionsDefinition, FunctionParameter, @@ -43,7 +43,7 @@ import type { } from '../definitions/types'; import type { ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; import { removeMarkerArgFromArgsList } from './context'; -import { esqlToKibanaType } from './esql_to_kibana_type'; +import { isNumericDecimalType } from './esql_types'; import type { ReasonTypes } from './types'; export function nonNullable<T>(v: T): v is NonNullable<T> { @@ -127,7 +127,7 @@ export function isComma(char: string) { } export function isSourceCommand({ label }: { label: string }) { - return ['FROM', 'ROW', 'SHOW'].includes(label); + return ['FROM', 'ROW', 'SHOW', 'METRICS'].includes(label); } let fnLookups: Map<string, FunctionDefinition> | undefined; @@ -226,6 +226,14 @@ function compareLiteralType(argType: string, item: ESQLLiteral) { return true; } + if (item.literalType === 'decimal' && isNumericDecimalType(argType)) { + return true; + } + + if (item.literalType === 'string' && (argType === 'text' || argType === 'keyword')) { + return true; + } + if (item.literalType !== 'string') { if (argType === item.literalType) { return true; @@ -234,7 +242,7 @@ function compareLiteralType(argType: string, item: ESQLLiteral) { } // date-type parameters accept string literals because of ES auto-casting - return ['string', 'date'].includes(argType); + return ['string', 'date', 'date', 'date_period'].includes(argType); } /** @@ -245,7 +253,14 @@ export function lookupColumn( { fields, variables }: Pick<ReferenceMaps, 'fields' | 'variables'> ): ESQLRealField | ESQLVariable | undefined { const columnName = getQuotedColumnName(column); - return fields.get(columnName) || variables.get(columnName)?.[0]; + return ( + fields.get(columnName) || + variables.get(columnName)?.[0] || + // It's possible columnName has backticks "`fieldName`" + // so we need to access the original name as well + fields.get(column.name) || + variables.get(column.name)?.[0] + ); } const ARRAY_REGEXP = /\[\]$/; @@ -255,10 +270,18 @@ export function isArrayType(type: string) { } const arrayToSingularMap: Map<FunctionParameterType, FunctionParameterType> = new Map([ - ['number[]', 'number'], - ['date[]', 'date'], + ['double[]', 'double'], + ['unsigned_long[]', 'unsigned_long'], + ['long[]', 'long'], + ['integer[]', 'integer'], + ['counter_integer[]', 'counter_integer'], + ['counter_long[]', 'counter_long'], + ['counter_double[]', 'counter_double'], + ['keyword[]', 'keyword'], + ['text[]', 'text'], + ['datetime[]', 'date'], + ['date_period[]', 'date_period'], ['boolean[]', 'boolean'], - ['string[]', 'string'], ['any[]', 'any'], ]); @@ -407,7 +430,8 @@ export function checkFunctionArgMatchesDefinition( return true; } if (arg.type === 'literal') { - return compareLiteralType(argType, arg); + const matched = compareLiteralType(argType as string, arg); + return matched; } if (arg.type === 'function') { if (isSupportedFunction(arg.name, parentCommand).supported) { @@ -428,11 +452,21 @@ export function checkFunctionArgMatchesDefinition( } const wrappedTypes = Array.isArray(validHit.type) ? validHit.type : [validHit.type]; // if final type is of type any make it pass for now - return wrappedTypes.some((ct) => ['any', 'null'].includes(ct) || argType === ct); + return wrappedTypes.some( + (ct) => + ['any', 'null'].includes(ct) || + argType === ct || + (ct === 'string' && ['text', 'keyword'].includes(argType as string)) + ); } if (arg.type === 'inlineCast') { - // TODO - remove with https://github.com/elastic/kibana/issues/174710 - return argType === esqlToKibanaType(arg.castType); + const lowerArgType = argType?.toLowerCase(); + const lowerArgCastType = arg.castType?.toLowerCase(); + return ( + lowerArgType === lowerArgCastType || + // for valid shorthand casts like 321.12::int or "false"::bool + (['int', 'bool'].includes(lowerArgCastType) && argType.startsWith(lowerArgCastType)) + ); } } @@ -610,3 +644,8 @@ export const isParam = (x: unknown): x is ESQLParamLiteral => typeof x === 'object' && (x as ESQLParamLiteral).type === 'literal' && (x as ESQLParamLiteral).literalType === 'param'; + +/** + * Compares two strings in a case-insensitive manner + */ +export const noCaseCompare = (a: string, b: string) => a.toLowerCase() === b.toLowerCase(); diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts b/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts index 22c38cd286e19..16855a0498250 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts @@ -35,7 +35,7 @@ function addToVariables( if (isColumnItem(oldArg) && isColumnItem(newArg)) { const newVariable: ESQLVariable = { name: newArg.name, - type: 'number' /* fallback to number */, + type: 'double' /* fallback to number */, location: newArg.location, }; // Now workout the exact type @@ -107,7 +107,7 @@ function addVariableFromAssignment( const rightHandSideArgType = getAssignRightHandSideType(assignOperation.args[1], fields); addToVariableOccurrencies(variables, { name: assignOperation.args[0].name, - type: rightHandSideArgType || 'number' /* fallback to number */, + type: (rightHandSideArgType as string) || 'double' /* fallback to number */, location: assignOperation.args[0].location, }); } @@ -125,7 +125,7 @@ function addVariableFromExpression( queryString, expressionOperation.location ); - const expressionType = 'number'; + const expressionType = 'double'; addToVariableOccurrencies(variables, { name: forwardThinkingVariableName, type: expressionType, @@ -156,9 +156,9 @@ export function collectVariables( ): Map<string, ESQLVariable[]> { const variables = new Map<string, ESQLVariable[]>(); for (const command of commands) { - if (['row', 'eval', 'stats', 'metrics'].includes(command.name)) { + if (['row', 'eval', 'stats', 'inlinestats', 'metrics'].includes(command.name)) { collectVariablesFromList(command.args, fields, queryString, variables); - if (command.name === 'stats') { + if (command.name === 'stats' || command.name === 'inlinestats') { const commandOptionsWithAssignment = command.args.filter( (arg) => isOptionItem(arg) && arg.name === 'by' ) as ESQLCommandOption[]; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/helpers.ts index 9d28f88115b42..02f7c30d96ff9 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/helpers.ts @@ -31,7 +31,7 @@ export const setup = async () => { return await validateQuery(query, getAstAndSyntaxErrors, opts, cb); }; - const assertErrors = (errors: unknown[], expectedErrors: string[]) => { + const assertErrors = (errors: unknown[], expectedErrors: string[], query?: string) => { const errorMessages: string[] = []; for (const error of errors) { if (error && typeof error === 'object') { @@ -46,7 +46,16 @@ export const setup = async () => { errorMessages.push(String(error)); } } - expect(errorMessages.sort()).toStrictEqual(expectedErrors.sort()); + + try { + expect(errorMessages.sort()).toStrictEqual(expectedErrors.sort()); + } catch (error) { + throw Error(`${query}\n + Received: + '${errorMessages.sort()}' + Expected: + ${expectedErrors.sort()}`); + } }; const expectErrors = async ( @@ -57,9 +66,9 @@ export const setup = async () => { cb: ESQLCallbacks = callbacks ) => { const { errors, warnings } = await validateQuery(query, getAstAndSyntaxErrors, opts, cb); - assertErrors(errors, expectedErrors); + assertErrors(errors, expectedErrors, query); if (expectedWarnings) { - assertErrors(warnings, expectedWarnings); + assertErrors(warnings, expectedWarnings, query); } }; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts index 06d23a30c441d..c24a44dadef40 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts @@ -89,7 +89,7 @@ export const validationFromCommandTestSuite = (setup: helpers.Setup) => { ]); }); - test('errors on unknown index', async () => { + test.skip('errors on unknown index', async () => { const { expectErrors } = await setup(); await expectErrors(`FROM index, missingIndex`, ['Unknown index [missingIndex]']); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.inlinestats.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.inlinestats.ts new file mode 100644 index 0000000000000..08a22e83b8e88 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.inlinestats.ts @@ -0,0 +1,386 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 helpers from '../helpers'; + +export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { + describe('validation', () => { + describe('command', () => { + describe('INLINESTATS <aggregates> [ BY <grouping> ]', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS by textField', []); + await expectErrors( + `FROM index + | EVAL doubleField * 3.281 + | INLINESTATS avg_doubleField = AVG(\`doubleField * 3.281\`)`, + [] + ); + await expectErrors( + `FROM index | INLINESTATS AVG(doubleField) by round(doubleField) + 1 | EVAL \`round(doubleField) + 1\` / 2`, + [] + ); + }); + + test('errors on invalid command start', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS ', [ + 'At least one aggregation or grouping expression required in [INLINESTATS]', + ]); + }); + + describe('... <aggregates> ...', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS avg(doubleField) by 1', []); + await expectErrors('from a_index | INLINESTATS count(`doubleField`)', []); + await expectErrors('from a_index | INLINESTATS count(*)', []); + await expectErrors('from a_index | INLINESTATS count()', []); + await expectErrors('from a_index | INLINESTATS var0 = count(*)', []); + await expectErrors('from a_index | INLINESTATS var0 = count()', []); + await expectErrors('from a_index | INLINESTATS var0 = avg(doubleField), count(*)', []); + await expectErrors(`from a_index | INLINESTATS sum(case(false, 0, 1))`, []); + await expectErrors(`from a_index | INLINESTATS var0 = sum( case(false, 0, 1))`, []); + + // "or" must accept "null" + await expectErrors('from a_index | INLINESTATS count(textField == "a" or null)', []); + }); + + test('sub-command can reference aggregated field', async () => { + const { expectErrors } = await setup(); + + for (const subCommand of ['keep', 'drop', 'eval']) { + await expectErrors( + 'from a_index | INLINESTATS count(`doubleField`) | ' + + subCommand + + ' `count(``doubleField``)` ', + [] + ); + } + }); + + test('errors on agg and non-agg mix', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + 'from a_index | INLINESTATS sum( doubleField ) + abs( doubleField ) ', + [ + 'Cannot combine aggregation and non-aggregation values in [INLINESTATS], found [sum(doubleField)+abs(doubleField)]', + ] + ); + await expectErrors( + 'from a_index | INLINESTATS abs( doubleField + sum( doubleField )) ', + [ + 'Cannot combine aggregation and non-aggregation values in [INLINESTATS], found [abs(doubleField+sum(doubleField))]', + ] + ); + }); + + test('errors on each aggregation field, which does not contain at least one agg function', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS doubleField + 1', [ + 'At least one aggregation function required in [INLINESTATS], found [doubleField+1]', + ]); + await expectErrors('from a_index | INLINESTATS doubleField + 1, textField', [ + 'At least one aggregation function required in [INLINESTATS], found [doubleField+1]', + 'Expected an aggregate function or group but got [textField] of type [FieldAttribute]', + ]); + await expectErrors( + 'from a_index | INLINESTATS doubleField + 1, doubleField + 2, count()', + [ + 'At least one aggregation function required in [INLINESTATS], found [doubleField+1]', + 'At least one aggregation function required in [INLINESTATS], found [doubleField+2]', + ] + ); + await expectErrors( + 'from a_index | INLINESTATS doubleField + 1, doubleField + count(), count()', + ['At least one aggregation function required in [INLINESTATS], found [doubleField+1]'] + ); + await expectErrors('from a_index | INLINESTATS 5 + doubleField + 1', [ + 'At least one aggregation function required in [INLINESTATS], found [5+doubleField+1]', + ]); + await expectErrors('from a_index | INLINESTATS doubleField + 1 by ipField', [ + 'At least one aggregation function required in [INLINESTATS], found [doubleField+1]', + ]); + }); + + test('errors when input is not an aggregate function', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS doubleField ', [ + 'Expected an aggregate function or group but got [doubleField] of type [FieldAttribute]', + ]); + }); + + test('various errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS doubleField=', [ + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + await expectErrors('from a_index | INLINESTATS doubleField=5 by ', [ + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + await expectErrors('from a_index | INLINESTATS avg(doubleField) by wrongField', [ + 'Unknown column [wrongField]', + ]); + await expectErrors('from a_index | INLINESTATS avg(doubleField) by wrongField + 1', [ + 'Unknown column [wrongField]', + ]); + await expectErrors( + 'from a_index | INLINESTATS avg(doubleField) by var0 = wrongField + 1', + ['Unknown column [wrongField]'] + ); + await expectErrors('from a_index | INLINESTATS var0 = avg(fn(number)), count(*)', [ + 'Unknown function [fn]', + ]); + }); + + test('semantic errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS count(round(*))', [ + 'Using wildcards (*) in round is not allowed', + ]); + await expectErrors('from a_index | INLINESTATS count(count(*))', [ + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`, + ]); + }); + }); + + describe('... BY <grouping>', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + 'from a_index | INLINESTATS avg(doubleField), percentile(doubleField, 50) by ipField', + [] + ); + await expectErrors( + 'from a_index | INLINESTATS avg(doubleField), percentile(doubleField, 50) BY ipField', + [] + ); + await expectErrors( + 'from a_index | INLINESTATS avg(doubleField), percentile(doubleField, 50) + 1 by ipField', + [] + ); + for (const op of ['+', '-', '*', '/', '%']) { + await expectErrors( + `from a_index | INLINESTATS avg(doubleField) ${op} percentile(doubleField, 50) BY ipField`, + [] + ); + } + }); + + test('cannot specify <grouping> without <aggregates>', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS by ', [ + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + }); + + test('syntax errors in <aggregates>', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS count(* + 1) BY ipField', [ + "SyntaxError: no viable alternative at input 'count(* +'", + ]); + await expectErrors( + 'from a_index | INLINESTATS count(* + round(doubleField)) BY ipField', + ["SyntaxError: no viable alternative at input 'count(* +'"] + ); + }); + + test('semantic errors in <aggregates>', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | INLINESTATS count(round(*)) BY ipField', [ + 'Using wildcards (*) in round is not allowed', + ]); + await expectErrors('from a_index | INLINESTATS count(count(*)) BY ipField', [ + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`, + ]); + }); + + test('various errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + 'from a_index | INLINESTATS avg(doubleField) by percentile(doubleField)', + ['INLINESTATS BY does not support function percentile'] + ); + await expectErrors( + 'from a_index | INLINESTATS avg(doubleField) by textField, percentile(doubleField) by ipField', + [ + "SyntaxError: mismatched input 'by' expecting <EOF>", + 'INLINESTATS BY does not support function percentile', + ] + ); + }); + + describe('constant-only parameters', () => { + test('no errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + 'from index | INLINESTATS by bucket(dateField, 1 + 30 / 10, "", "")', + [] + ); + await expectErrors( + 'from index | INLINESTATS by bucket(dateField, 1 + 30 / 10, concat("", ""), "")', + ['Argument of [bucket] must be [date], found value [concat("","")] type [keyword]'] + ); + }); + + test('errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from index | INLINESTATS by bucket(dateField, pi(), "", "")', [ + 'Argument of [bucket] must be [integer], found value [pi()] type [double]', + ]); + + await expectErrors( + 'from index | INLINESTATS by bucket(dateField, abs(doubleField), "", "")', + ['Argument of [bucket] must be a constant, received [abs(doubleField)]'] + ); + await expectErrors( + 'from index | INLINESTATS by bucket(dateField, abs(length(doubleField)), "", "")', + ['Argument of [bucket] must be a constant, received [abs(length(doubleField))]'] + ); + await expectErrors( + 'from index | INLINESTATS by bucket(dateField, doubleField, textField, textField)', + [ + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [textField]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); + }); + }); + }); + + describe('nesting', () => { + const NESTING_LEVELS = 4; + const NESTED_DEPTHS = Array(NESTING_LEVELS) + .fill(0) + .map((_, i) => i + 1); + + for (const nesting of NESTED_DEPTHS) { + describe(`depth = ${nesting}`, () => { + describe('builtin', () => { + const builtinWrapping = Array(nesting).fill('+1').join(''); + + test('no errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from a_index | INLINESTATS 5 + avg(doubleField) ${builtinWrapping}`, + [] + ); + await expectErrors( + `from a_index | INLINESTATS 5 ${builtinWrapping} + avg(doubleField)`, + [] + ); + }); + + test('errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from a_index | INLINESTATS 5 ${builtinWrapping} + doubleField`, + [ + `At least one aggregation function required in [INLINESTATS], found [5${builtinWrapping}+doubleField]`, + ] + ); + await expectErrors( + `from a_index | INLINESTATS 5 + doubleField ${builtinWrapping}`, + [ + `At least one aggregation function required in [INLINESTATS], found [5+doubleField${builtinWrapping}]`, + ] + ); + await expectErrors( + `from a_index | INLINESTATS 5 + doubleField ${builtinWrapping}, var0 = sum(doubleField)`, + [ + `At least one aggregation function required in [INLINESTATS], found [5+doubleField${builtinWrapping}]`, + ] + ); + }); + }); + + describe('EVAL', () => { + const evalWrapping = Array(nesting).fill('round(').join(''); + const closingWrapping = Array(nesting).fill(')').join(''); + + test('no errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from a_index | INLINESTATS ${evalWrapping} sum(doubleField) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | INLINESTATS ${evalWrapping} sum(doubleField) ${closingWrapping} + ${evalWrapping} sum(doubleField) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | INLINESTATS ${evalWrapping} sum(doubleField + doubleField) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | INLINESTATS ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | INLINESTATS ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping} + ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | INLINESTATS sum(${evalWrapping} doubleField ${closingWrapping} )`, + [] + ); + await expectErrors( + `from a_index | INLINESTATS sum(${evalWrapping} doubleField ${closingWrapping} ) + sum(${evalWrapping} doubleField ${closingWrapping} )`, + [] + ); + }); + + test('errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from a_index | INLINESTATS ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}`, + [ + `Cannot combine aggregation and non-aggregation values in [INLINESTATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`, + ] + ); + await expectErrors( + `from a_index | INLINESTATS ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var0 = sum(doubleField)`, + [ + `Cannot combine aggregation and non-aggregation values in [INLINESTATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`, + ] + ); + await expectErrors( + `from a_index | INLINESTATS var0 = ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var1 = sum(doubleField)`, + [ + `Cannot combine aggregation and non-aggregation values in [INLINESTATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`, + ] + ); + }); + }); + }); + } + }); + }); + }); + }); +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts index 44c15c722a1de..ea5df88553888 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts @@ -85,7 +85,7 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { await expectErrors(`METRICS average()`, ['Unknown index [average()]']); await expectErrors(`metrics custom_function()`, ['Unknown index [custom_function()]']); await expectErrors(`metrics indexes*`, ['Unknown index [indexes*]']); - await expectErrors('metrics numberField', ['Unknown index [numberField]']); + await expectErrors('metrics doubleField', ['Unknown index [doubleField]']); await expectErrors('metrics policy', ['Unknown index [policy]']); }); }); @@ -95,26 +95,26 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors('METRICS a_index count()', []); - await expectErrors('metrics a_index avg(numberField) by 1', []); - await expectErrors('metrics a_index count(`numberField`)', []); + await expectErrors('metrics a_index avg(doubleField) by 1', []); + await expectErrors('metrics a_index count(`doubleField`)', []); await expectErrors('metrics a_index count(*)', []); await expectErrors('metrics index var0 = count(*)', []); await expectErrors('metrics a_index var0 = count()', []); - await expectErrors('metrics a_index var0 = avg(numberField), count(*)', []); + await expectErrors('metrics a_index var0 = avg(doubleField), count(*)', []); await expectErrors(`metrics a_index sum(case(false, 0, 1))`, []); await expectErrors(`metrics a_index var0 = sum( case(false, 0, 1))`, []); - await expectErrors('metrics a_index count(stringField == "a" or null)', []); - await expectErrors('metrics other_index max(numberField) by stringField', []); + await expectErrors('metrics a_index count(textField == "a" or null)', []); + await expectErrors('metrics other_index max(doubleField) by textField', []); }); test('syntax errors', async () => { const { expectErrors } = await setup(); - await expectErrors('metrics a_index numberField=', [ + await expectErrors('metrics a_index doubleField=', [ expect.any(String), "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - await expectErrors('metrics a_index numberField=5 by ', [ + await expectErrors('metrics a_index doubleField=5 by ', [ expect.any(String), "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); @@ -131,29 +131,29 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { test('errors when no aggregation function specified', async () => { const { expectErrors } = await setup(); - await expectErrors('metrics a_index numberField + 1', [ - 'At least one aggregation function required in [METRICS], found [numberField+1]', + await expectErrors('metrics a_index doubleField + 1', [ + 'At least one aggregation function required in [METRICS], found [doubleField+1]', ]); - await expectErrors('metrics a_index a = numberField + 1', [ - 'At least one aggregation function required in [METRICS], found [a=numberField+1]', + await expectErrors('metrics a_index a = doubleField + 1', [ + 'At least one aggregation function required in [METRICS], found [a=doubleField+1]', ]); - await expectErrors('metrics a_index a = numberField + 1, stringField', [ - 'At least one aggregation function required in [METRICS], found [a=numberField+1]', - 'Expected an aggregate function or group but got [stringField] of type [FieldAttribute]', + await expectErrors('metrics a_index a = doubleField + 1, textField', [ + 'At least one aggregation function required in [METRICS], found [a=doubleField+1]', + 'Expected an aggregate function or group but got [textField] of type [FieldAttribute]', ]); - await expectErrors('metrics a_index numberField + 1 by ipField', [ - 'At least one aggregation function required in [METRICS], found [numberField+1]', + await expectErrors('metrics a_index doubleField + 1 by ipField', [ + 'At least one aggregation function required in [METRICS], found [doubleField+1]', ]); }); test('errors on agg and non-agg mix', async () => { const { expectErrors } = await setup(); - await expectErrors('METRICS a_index sum( numberField ) + abs( numberField ) ', [ - 'Cannot combine aggregation and non-aggregation values in [METRICS], found [sum(numberField)+abs(numberField)]', + await expectErrors('METRICS a_index sum( doubleField ) + abs( doubleField ) ', [ + 'Cannot combine aggregation and non-aggregation values in [METRICS], found [sum(doubleField)+abs(doubleField)]', ]); - await expectErrors('METRICS a_index abs( numberField + sum( numberField )) ', [ - 'Cannot combine aggregation and non-aggregation values in [METRICS], found [abs(numberField+sum(numberField))]', + await expectErrors('METRICS a_index abs( doubleField + sum( doubleField )) ', [ + 'Cannot combine aggregation and non-aggregation values in [METRICS], found [abs(doubleField+sum(doubleField))]', ]); }); @@ -169,8 +169,8 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { test('errors when input is not an aggregate function', async () => { const { expectErrors } = await setup(); - await expectErrors('metrics a_index numberField ', [ - 'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]', + await expectErrors('metrics a_index doubleField ', [ + 'Expected an aggregate function or group but got [doubleField] of type [FieldAttribute]', ]); }); @@ -179,9 +179,9 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { for (const subCommand of ['keep', 'drop', 'eval']) { await expectErrors( - 'metrics a_index count(`numberField`) | ' + + 'metrics a_index count(`doubleField`) | ' + subCommand + - ' `count(``numberField``)` ', + ' `count(``doubleField``)` ', [] ); } @@ -194,7 +194,7 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { 'Using wildcards (*) in round is not allowed', ]); await expectErrors('metrics a_index count(count(*))', [ - `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`, ]); }); }); @@ -204,21 +204,21 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors( - 'metrics a_index avg(numberField), percentile(numberField, 50) by ipField', + 'metrics a_index avg(doubleField), percentile(doubleField, 50) by ipField', [] ); await expectErrors( - 'metrics a_index avg(numberField), percentile(numberField, 50) BY ipField', + 'metrics a_index avg(doubleField), percentile(doubleField, 50) BY ipField', [] ); await expectErrors( - 'metrics a_index avg(numberField), percentile(numberField, 50) + 1 by ipField', + 'metrics a_index avg(doubleField), percentile(doubleField, 50) + 1 by ipField', [] ); - await expectErrors('metrics a_index avg(numberField) by stringField | limit 100', []); + await expectErrors('metrics a_index avg(doubleField) by textField | limit 100', []); for (const op of ['+', '-', '*', '/', '%']) { await expectErrors( - `metrics a_index avg(numberField) ${op} percentile(numberField, 50) BY ipField`, + `metrics a_index avg(doubleField) ${op} percentile(doubleField, 50) BY ipField`, [] ); } @@ -227,9 +227,9 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { test('syntax does not allow <grouping> clause without <aggregates>', async () => { const { expectErrors } = await setup(); - await expectErrors('metrics a_index BY stringField', [ + await expectErrors('metrics a_index BY textField', [ 'Expected an aggregate function or group but got [BY] of type [FieldAttribute]', - "SyntaxError: extraneous input 'stringField' expecting <EOF>", + "SyntaxError: extraneous input 'textField' expecting <EOF>", ]); }); @@ -239,7 +239,7 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { await expectErrors('metrics a_index count(* + 1) BY ipField', [ "SyntaxError: no viable alternative at input 'count(* +'", ]); - await expectErrors('metrics a_index \n count(* + round(numberField)) BY ipField', [ + await expectErrors('metrics a_index \n count(* + round(doubleField)) BY ipField', [ "SyntaxError: no viable alternative at input 'count(* +'", ]); }); @@ -251,20 +251,20 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { 'Using wildcards (*) in round is not allowed', ]); await expectErrors('metrics a_index count(count(*)) BY ipField', [ - `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`, ]); }); test('errors on unknown field', async () => { const { expectErrors } = await setup(); - await expectErrors('metrics a_index avg(numberField) by wrongField', [ + await expectErrors('metrics a_index avg(doubleField) by wrongField', [ 'Unknown column [wrongField]', ]); - await expectErrors('metrics a_index avg(numberField) by wrongField + 1', [ + await expectErrors('metrics a_index avg(doubleField) by wrongField + 1', [ 'Unknown column [wrongField]', ]); - await expectErrors('metrics a_index avg(numberField) by var0 = wrongField + 1', [ + await expectErrors('metrics a_index avg(doubleField) by var0 = wrongField + 1', [ 'Unknown column [wrongField]', ]); }); @@ -272,11 +272,11 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { test('various errors', async () => { const { expectErrors } = await setup(); - await expectErrors('METRICS a_index avg(numberField) by percentile(numberField)', [ + await expectErrors('METRICS a_index avg(doubleField) by percentile(doubleField)', [ 'METRICS BY does not support function percentile', ]); await expectErrors( - 'METRICS a_index avg(numberField) by stringField, percentile(numberField) by ipField', + 'METRICS a_index avg(doubleField) by textField, percentile(doubleField) by ipField', [ "SyntaxError: mismatched input 'by' expecting <EOF>", 'METRICS BY does not support function percentile', diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts index 5a98d362dc002..f5bd10fe0ca83 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts @@ -15,15 +15,15 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { test('no errors on correct usage', async () => { const { expectErrors } = await setup(); - await expectErrors('from a_index | stats by stringField', []); + await expectErrors('from a_index | stats by textField', []); await expectErrors( `FROM index - | EVAL numberField * 3.281 - | STATS avg_numberField = AVG(\`numberField * 3.281\`)`, + | EVAL doubleField * 3.281 + | STATS avg_doubleField = AVG(\`doubleField * 3.281\`)`, [] ); await expectErrors( - `FROM index | STATS AVG(numberField) by round(numberField) + 1 | EVAL \`round(numberField) + 1\` / 2`, + `FROM index | STATS AVG(doubleField) by round(doubleField) + 1 | EVAL \`round(doubleField) + 1\` / 2`, [] ); }); @@ -40,18 +40,18 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { test('no errors on correct usage', async () => { const { expectErrors } = await setup(); - await expectErrors('from a_index | stats avg(numberField) by 1', []); - await expectErrors('from a_index | stats count(`numberField`)', []); + await expectErrors('from a_index | stats avg(doubleField) by 1', []); + await expectErrors('from a_index | stats count(`doubleField`)', []); await expectErrors('from a_index | stats count(*)', []); await expectErrors('from a_index | stats count()', []); await expectErrors('from a_index | stats var0 = count(*)', []); await expectErrors('from a_index | stats var0 = count()', []); - await expectErrors('from a_index | stats var0 = avg(numberField), count(*)', []); + await expectErrors('from a_index | stats var0 = avg(doubleField), count(*)', []); await expectErrors(`from a_index | stats sum(case(false, 0, 1))`, []); await expectErrors(`from a_index | stats var0 = sum( case(false, 0, 1))`, []); // "or" must accept "null" - await expectErrors('from a_index | stats count(stringField == "a" or null)', []); + await expectErrors('from a_index | stats count(textField == "a" or null)', []); }); test('sub-command can reference aggregated field', async () => { @@ -59,9 +59,9 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { for (const subCommand of ['keep', 'drop', 'eval']) { await expectErrors( - 'from a_index | stats count(`numberField`) | ' + + 'from a_index | stats count(`doubleField`) | ' + subCommand + - ' `count(``numberField``)` ', + ' `count(``doubleField``)` ', [] ); } @@ -70,64 +70,64 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { test('errors on agg and non-agg mix', async () => { const { expectErrors } = await setup(); - await expectErrors('from a_index | STATS sum( numberField ) + abs( numberField ) ', [ - 'Cannot combine aggregation and non-aggregation values in [STATS], found [sum(numberField)+abs(numberField)]', + await expectErrors('from a_index | STATS sum( doubleField ) + abs( doubleField ) ', [ + 'Cannot combine aggregation and non-aggregation values in [STATS], found [sum(doubleField)+abs(doubleField)]', ]); - await expectErrors('from a_index | STATS abs( numberField + sum( numberField )) ', [ - 'Cannot combine aggregation and non-aggregation values in [STATS], found [abs(numberField+sum(numberField))]', + await expectErrors('from a_index | STATS abs( doubleField + sum( doubleField )) ', [ + 'Cannot combine aggregation and non-aggregation values in [STATS], found [abs(doubleField+sum(doubleField))]', ]); }); test('errors on each aggregation field, which does not contain at least one agg function', async () => { const { expectErrors } = await setup(); - await expectErrors('from a_index | stats numberField + 1', [ - 'At least one aggregation function required in [STATS], found [numberField+1]', + await expectErrors('from a_index | stats doubleField + 1', [ + 'At least one aggregation function required in [STATS], found [doubleField+1]', ]); - await expectErrors('from a_index | stats numberField + 1, stringField', [ - 'At least one aggregation function required in [STATS], found [numberField+1]', - 'Expected an aggregate function or group but got [stringField] of type [FieldAttribute]', + await expectErrors('from a_index | stats doubleField + 1, textField', [ + 'At least one aggregation function required in [STATS], found [doubleField+1]', + 'Expected an aggregate function or group but got [textField] of type [FieldAttribute]', ]); - await expectErrors('from a_index | stats numberField + 1, numberField + 2, count()', [ - 'At least one aggregation function required in [STATS], found [numberField+1]', - 'At least one aggregation function required in [STATS], found [numberField+2]', + await expectErrors('from a_index | stats doubleField + 1, doubleField + 2, count()', [ + 'At least one aggregation function required in [STATS], found [doubleField+1]', + 'At least one aggregation function required in [STATS], found [doubleField+2]', ]); await expectErrors( - 'from a_index | stats numberField + 1, numberField + count(), count()', - ['At least one aggregation function required in [STATS], found [numberField+1]'] + 'from a_index | stats doubleField + 1, doubleField + count(), count()', + ['At least one aggregation function required in [STATS], found [doubleField+1]'] ); - await expectErrors('from a_index | stats 5 + numberField + 1', [ - 'At least one aggregation function required in [STATS], found [5+numberField+1]', + await expectErrors('from a_index | stats 5 + doubleField + 1', [ + 'At least one aggregation function required in [STATS], found [5+doubleField+1]', ]); - await expectErrors('from a_index | stats numberField + 1 by ipField', [ - 'At least one aggregation function required in [STATS], found [numberField+1]', + await expectErrors('from a_index | stats doubleField + 1 by ipField', [ + 'At least one aggregation function required in [STATS], found [doubleField+1]', ]); }); test('errors when input is not an aggregate function', async () => { const { expectErrors } = await setup(); - await expectErrors('from a_index | stats numberField ', [ - 'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]', + await expectErrors('from a_index | stats doubleField ', [ + 'Expected an aggregate function or group but got [doubleField] of type [FieldAttribute]', ]); }); test('various errors', async () => { const { expectErrors } = await setup(); - await expectErrors('from a_index | stats numberField=', [ + await expectErrors('from a_index | stats doubleField=', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - await expectErrors('from a_index | stats numberField=5 by ', [ + await expectErrors('from a_index | stats doubleField=5 by ', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - await expectErrors('from a_index | stats avg(numberField) by wrongField', [ + await expectErrors('from a_index | stats avg(doubleField) by wrongField', [ 'Unknown column [wrongField]', ]); - await expectErrors('from a_index | stats avg(numberField) by wrongField + 1', [ + await expectErrors('from a_index | stats avg(doubleField) by wrongField + 1', [ 'Unknown column [wrongField]', ]); - await expectErrors('from a_index | stats avg(numberField) by var0 = wrongField + 1', [ + await expectErrors('from a_index | stats avg(doubleField) by var0 = wrongField + 1', [ 'Unknown column [wrongField]', ]); await expectErrors('from a_index | stats var0 = avg(fn(number)), count(*)', [ @@ -142,7 +142,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { 'Using wildcards (*) in round is not allowed', ]); await expectErrors('from a_index | stats count(count(*))', [ - `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`, ]); }); }); @@ -152,20 +152,20 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors( - 'from a_index | stats avg(numberField), percentile(numberField, 50) by ipField', + 'from a_index | stats avg(doubleField), percentile(doubleField, 50) by ipField', [] ); await expectErrors( - 'from a_index | stats avg(numberField), percentile(numberField, 50) BY ipField', + 'from a_index | stats avg(doubleField), percentile(doubleField, 50) BY ipField', [] ); await expectErrors( - 'from a_index | stats avg(numberField), percentile(numberField, 50) + 1 by ipField', + 'from a_index | stats avg(doubleField), percentile(doubleField, 50) + 1 by ipField', [] ); for (const op of ['+', '-', '*', '/', '%']) { await expectErrors( - `from a_index | stats avg(numberField) ${op} percentile(numberField, 50) BY ipField`, + `from a_index | stats avg(doubleField) ${op} percentile(doubleField, 50) BY ipField`, [] ); } @@ -185,7 +185,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { await expectErrors('from a_index | stats count(* + 1) BY ipField', [ "SyntaxError: no viable alternative at input 'count(* +'", ]); - await expectErrors('from a_index | stats count(* + round(numberField)) BY ipField', [ + await expectErrors('from a_index | stats count(* + round(doubleField)) BY ipField', [ "SyntaxError: no viable alternative at input 'count(* +'", ]); }); @@ -197,18 +197,18 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { 'Using wildcards (*) in round is not allowed', ]); await expectErrors('from a_index | stats count(count(*)) BY ipField', [ - `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [long]`, ]); }); test('various errors', async () => { const { expectErrors } = await setup(); - await expectErrors('from a_index | stats avg(numberField) by percentile(numberField)', [ + await expectErrors('from a_index | stats avg(doubleField) by percentile(doubleField)', [ 'STATS BY does not support function percentile', ]); await expectErrors( - 'from a_index | stats avg(numberField) by stringField, percentile(numberField) by ipField', + 'from a_index | stats avg(doubleField) by textField, percentile(doubleField) by ipField', [ "SyntaxError: mismatched input 'by' expecting <EOF>", 'STATS BY does not support function percentile', @@ -220,34 +220,37 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { test('no errors', async () => { const { expectErrors } = await setup(); - await expectErrors('from index | stats by bucket(dateField, pi(), "", "")', []); await expectErrors( 'from index | stats by bucket(dateField, 1 + 30 / 10, "", "")', [] ); await expectErrors( 'from index | stats by bucket(dateField, 1 + 30 / 10, concat("", ""), "")', - [] + ['Argument of [bucket] must be [date], found value [concat("","")] type [keyword]'] ); }); test('errors', async () => { const { expectErrors } = await setup(); + await expectErrors('from index | stats by bucket(dateField, pi(), "", "")', [ + 'Argument of [bucket] must be [integer], found value [pi()] type [double]', + ]); + await expectErrors( - 'from index | stats by bucket(dateField, abs(numberField), "", "")', - ['Argument of [bucket] must be a constant, received [abs(numberField)]'] + 'from index | stats by bucket(dateField, abs(doubleField), "", "")', + ['Argument of [bucket] must be a constant, received [abs(doubleField)]'] ); await expectErrors( - 'from index | stats by bucket(dateField, abs(length(numberField)), "", "")', - ['Argument of [bucket] must be a constant, received [abs(length(numberField))]'] + 'from index | stats by bucket(dateField, abs(length(doubleField)), "", "")', + ['Argument of [bucket] must be a constant, received [abs(length(doubleField))]'] ); await expectErrors( - 'from index | stats by bucket(dateField, numberField, stringField, stringField)', + 'from index | stats by bucket(dateField, doubleField, textField, textField)', [ - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [stringField]', - 'Argument of [bucket] must be a constant, received [stringField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [textField]', + 'Argument of [bucket] must be a constant, received [textField]', ] ); }); @@ -269,11 +272,11 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors( - `from a_index | stats 5 + avg(numberField) ${builtinWrapping}`, + `from a_index | stats 5 + avg(doubleField) ${builtinWrapping}`, [] ); await expectErrors( - `from a_index | stats 5 ${builtinWrapping} + avg(numberField)`, + `from a_index | stats 5 ${builtinWrapping} + avg(doubleField)`, [] ); }); @@ -281,16 +284,16 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { test('errors', async () => { const { expectErrors } = await setup(); - await expectErrors(`from a_index | stats 5 ${builtinWrapping} + numberField`, [ - `At least one aggregation function required in [STATS], found [5${builtinWrapping}+numberField]`, + await expectErrors(`from a_index | stats 5 ${builtinWrapping} + doubleField`, [ + `At least one aggregation function required in [STATS], found [5${builtinWrapping}+doubleField]`, ]); - await expectErrors(`from a_index | stats 5 + numberField ${builtinWrapping}`, [ - `At least one aggregation function required in [STATS], found [5+numberField${builtinWrapping}]`, + await expectErrors(`from a_index | stats 5 + doubleField ${builtinWrapping}`, [ + `At least one aggregation function required in [STATS], found [5+doubleField${builtinWrapping}]`, ]); await expectErrors( - `from a_index | stats 5 + numberField ${builtinWrapping}, var0 = sum(numberField)`, + `from a_index | stats 5 + doubleField ${builtinWrapping}, var0 = sum(doubleField)`, [ - `At least one aggregation function required in [STATS], found [5+numberField${builtinWrapping}]`, + `At least one aggregation function required in [STATS], found [5+doubleField${builtinWrapping}]`, ] ); }); @@ -304,31 +307,31 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors( - `from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping}`, + `from a_index | stats ${evalWrapping} sum(doubleField) ${closingWrapping}`, [] ); await expectErrors( - `from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping} + ${evalWrapping} sum(numberField) ${closingWrapping}`, + `from a_index | stats ${evalWrapping} sum(doubleField) ${closingWrapping} + ${evalWrapping} sum(doubleField) ${closingWrapping}`, [] ); await expectErrors( - `from a_index | stats ${evalWrapping} sum(numberField + numberField) ${closingWrapping}`, + `from a_index | stats ${evalWrapping} sum(doubleField + doubleField) ${closingWrapping}`, [] ); await expectErrors( - `from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`, + `from a_index | stats ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`, [] ); await expectErrors( - `from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping} + ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`, + `from a_index | stats ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping} + ${evalWrapping} sum(doubleField + round(doubleField)) ${closingWrapping}`, [] ); await expectErrors( - `from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} )`, + `from a_index | stats sum(${evalWrapping} doubleField ${closingWrapping} )`, [] ); await expectErrors( - `from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} ) + sum(${evalWrapping} numberField ${closingWrapping} )`, + `from a_index | stats sum(${evalWrapping} doubleField ${closingWrapping} ) + sum(${evalWrapping} doubleField ${closingWrapping} )`, [] ); }); @@ -337,21 +340,21 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { const { expectErrors } = await setup(); await expectErrors( - `from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}`, + `from a_index | stats ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}`, [ - `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`, + `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`, ] ); await expectErrors( - `from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var0 = sum(numberField)`, + `from a_index | stats ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var0 = sum(doubleField)`, [ - `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`, + `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`, ] ); await expectErrors( - `from a_index | stats var0 = ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var1 = sum(numberField)`, + `from a_index | stats var0 = ${evalWrapping} doubleField + sum(doubleField) ${closingWrapping}, var1 = sum(doubleField)`, [ - `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`, + `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}doubleField+sum(doubleField)${closingWrapping}]`, ] ); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.inlinestats.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.inlinestats.ts new file mode 100644 index 0000000000000..cc4a33cb72b88 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.inlinestats.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. + */ + +import * as helpers from './helpers'; +import { validationStatsCommandTestSuite } from './test_suites/validation.command.stats'; + +validationStatsCommandTestSuite(helpers.setup); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.eval.date_diff.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.eval.date_diff.test.ts new file mode 100644 index 0000000000000..fd21ceb9b681c --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.eval.date_diff.test.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 { setup } from './helpers'; + +describe('validation', () => { + describe('command', () => { + test('date_diff', async () => { + const { expectErrors } = await setup(); + await expectErrors( + 'row var = date_diff("month", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")', + [] + ); + await expectErrors( + 'row var = date_diff("mm", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")', + [] + ); + await expectErrors( + 'row var = date_diff("bogus", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")', + [] + ); + await expectErrors( + 'from a_index | eval date_diff(textField, "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")', + [] + ); + await expectErrors( + 'from a_index | eval date_diff("month", dateField, "2023-12-02T11:00:00.000Z")', + [] + ); + await expectErrors( + 'from a_index | eval date_diff("month", "2023-12-02T11:00:00.000Z", dateField)', + [] + ); + await expectErrors('from a_index | eval date_diff("month", textField, dateField)', [ + 'Argument of [date_diff] must be [date], found value [textField] type [text]', + ]); + await expectErrors('from a_index | eval date_diff("month", dateField, textField)', [ + 'Argument of [date_diff] must be [date], found value [textField] type [text]', + ]); + await expectErrors( + 'from a_index | eval var = date_diff("year", to_datetime(textField), to_datetime(textField))', + [] + ); + await expectErrors('from a_index | eval date_diff(doubleField, textField, textField)', [ + 'Argument of [date_diff] must be [date], found value [textField] type [text]', + 'Argument of [date_diff] must be [date], found value [textField] type [text]', + 'Argument of [date_diff] must be [keyword], found value [doubleField] type [double]', + ]); + }); + }); +}); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.params.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.params.test.ts index db132d4d3e488..d732838ed919e 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.params.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.params.test.ts @@ -23,18 +23,18 @@ test('should allow param inside agg function argument', async () => { test('allow params in WHERE command expressions', async () => { const { validate } = await setup(); - const res1 = await validate('FROM index | WHERE stringField >= ?start'); + const res1 = await validate('FROM index | WHERE textField >= ?start'); const res2 = await validate(` FROM index - | WHERE stringField >= ?start - | WHERE stringField <= ?0 - | WHERE stringField == ? + | WHERE textField >= ?start + | WHERE textField <= ?0 + | WHERE textField == ? `); const res3 = await validate(` FROM index - | WHERE stringField >= ?start - AND stringField <= ?0 - AND stringField == ? + | WHERE textField >= ?start + AND textField <= ?0 + AND textField == ? `); expect(res1).toMatchObject({ errors: [], warnings: [] }); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index d230f9e2facc9..41cc3027bfe58 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -9,25 +9,45 @@ ], "fields": [ { - "name": "numberField", - "type": "number" + "name": "booleanField", + "type": "boolean" }, { "name": "dateField", "type": "date" }, { - "name": "stringField", - "type": "string" - }, - { - "name": "booleanField", - "type": "boolean" + "name": "doubleField", + "type": "double" }, { "name": "ipField", "type": "ip" }, + { + "name": "keywordField", + "type": "keyword" + }, + { + "name": "integerField", + "type": "integer" + }, + { + "name": "longField", + "type": "long" + }, + { + "name": "textField", + "type": "text" + }, + { + "name": "unsignedLongField", + "type": "unsigned_long" + }, + { + "name": "versionField", + "type": "version" + }, { "name": "cartesianPointField", "type": "cartesian_point" @@ -45,16 +65,24 @@ "type": "geo_shape" }, { - "name": "versionField", - "type": "version" + "name": "counterIntegerField", + "type": "counter_integer" + }, + { + "name": "counterLongField", + "type": "counter_long" + }, + { + "name": "counterDoubleField", + "type": "counter_double" }, { "name": "any#Char$Field", - "type": "number" + "type": "double" }, { "name": "kubernetes.something.something", - "type": "number" + "type": "double" }, { "name": "@timestamp", @@ -68,11 +96,11 @@ "enrichFields": [ { "name": "otherField", - "type": "string" + "type": "text" }, { "name": "yetAnotherField", - "type": "number" + "type": "double" }, { "name": "otherStringField", @@ -417,9 +445,9 @@ "warning": [] }, { - "query": "row var = (numberField > 0)", + "query": "row var = (doubleField > 0)", "error": [ - "Unknown column [numberField]" + "Unknown column [doubleField]" ], "warning": [] }, @@ -441,8 +469,8 @@ { "query": "row var = false > false", "error": [ - "Argument of [>] must be [number], found value [false] type [boolean]", - "Argument of [>] must be [number], found value [false] type [boolean]" + "Argument of [>] must be [date], found value [false] type [boolean]", + "Argument of [>] must be [date], found value [false] type [boolean]" ], "warning": [] }, @@ -467,9 +495,9 @@ "warning": [] }, { - "query": "row var = (numberField >= 0)", + "query": "row var = (doubleField >= 0)", "error": [ - "Unknown column [numberField]" + "Unknown column [doubleField]" ], "warning": [] }, @@ -491,8 +519,8 @@ { "query": "row var = false >= false", "error": [ - "Argument of [>=] must be [number], found value [false] type [boolean]", - "Argument of [>=] must be [number], found value [false] type [boolean]" + "Argument of [>=] must be [date], found value [false] type [boolean]", + "Argument of [>=] must be [date], found value [false] type [boolean]" ], "warning": [] }, @@ -517,9 +545,9 @@ "warning": [] }, { - "query": "row var = (numberField < 0)", + "query": "row var = (doubleField < 0)", "error": [ - "Unknown column [numberField]" + "Unknown column [doubleField]" ], "warning": [] }, @@ -541,8 +569,8 @@ { "query": "row var = false < false", "error": [ - "Argument of [<] must be [number], found value [false] type [boolean]", - "Argument of [<] must be [number], found value [false] type [boolean]" + "Argument of [<] must be [date], found value [false] type [boolean]", + "Argument of [<] must be [date], found value [false] type [boolean]" ], "warning": [] }, @@ -567,9 +595,9 @@ "warning": [] }, { - "query": "row var = (numberField <= 0)", + "query": "row var = (doubleField <= 0)", "error": [ - "Unknown column [numberField]" + "Unknown column [doubleField]" ], "warning": [] }, @@ -591,8 +619,8 @@ { "query": "row var = false <= false", "error": [ - "Argument of [<=] must be [number], found value [false] type [boolean]", - "Argument of [<=] must be [number], found value [false] type [boolean]" + "Argument of [<=] must be [date], found value [false] type [boolean]", + "Argument of [<=] must be [date], found value [false] type [boolean]" ], "warning": [] }, @@ -617,9 +645,9 @@ "warning": [] }, { - "query": "row var = (numberField == 0)", + "query": "row var = (doubleField == 0)", "error": [ - "Unknown column [numberField]" + "Unknown column [doubleField]" ], "warning": [] }, @@ -664,9 +692,9 @@ "warning": [] }, { - "query": "row var = (numberField != 0)", + "query": "row var = (doubleField != 0)", "error": [ - "Unknown column [numberField]" + "Unknown column [doubleField]" ], "warning": [] }, @@ -713,7 +741,7 @@ { "query": "row var = now() + now()", "error": [ - "Argument of [+] must be [time_literal], found value [now()] type [date]" + "Argument of [+] must be [date_period], found value [now()] type [date]" ], "warning": [] }, @@ -730,7 +758,7 @@ { "query": "row var = now() - now()", "error": [ - "Argument of [-] must be [time_literal], found value [now()] type [date]" + "Argument of [-] must be [date_period], found value [now()] type [date]" ], "warning": [] }, @@ -747,8 +775,8 @@ { "query": "row var = now() * now()", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [now()] type [date]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [now()] type [date]" ], "warning": [] }, @@ -765,8 +793,8 @@ { "query": "row var = now() / now()", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [now()] type [date]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [now()] type [date]" ], "warning": [] }, @@ -783,8 +811,8 @@ { "query": "row var = now() % now()", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [now()] type [date]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [now()] type [date]" ], "warning": [] }, @@ -811,28 +839,28 @@ { "query": "row var = 5 like \"?a\"", "error": [ - "Argument of [like] must be [string], found value [5] type [number]" + "Argument of [like] must be [text], found value [5] type [integer]" ], "warning": [] }, { "query": "row var = 5 NOT like \"?a\"", "error": [ - "Argument of [not_like] must be [string], found value [5] type [number]" + "Argument of [not_like] must be [text], found value [5] type [integer]" ], "warning": [] }, { "query": "row var = NOT 5 like \"?a\"", "error": [ - "Argument of [like] must be [string], found value [5] type [number]" + "Argument of [like] must be [text], found value [5] type [integer]" ], "warning": [] }, { "query": "row var = NOT 5 NOT like \"?a\"", "error": [ - "Argument of [not_like] must be [string], found value [5] type [number]" + "Argument of [not_like] must be [text], found value [5] type [integer]" ], "warning": [] }, @@ -859,28 +887,28 @@ { "query": "row var = 5 rlike \"?a\"", "error": [ - "Argument of [rlike] must be [string], found value [5] type [number]" + "Argument of [rlike] must be [text], found value [5] type [integer]" ], "warning": [] }, { "query": "row var = 5 NOT rlike \"?a\"", "error": [ - "Argument of [not_rlike] must be [string], found value [5] type [number]" + "Argument of [not_rlike] must be [text], found value [5] type [integer]" ], "warning": [] }, { "query": "row var = NOT 5 rlike \"?a\"", "error": [ - "Argument of [rlike] must be [string], found value [5] type [number]" + "Argument of [rlike] must be [text], found value [5] type [integer]" ], "warning": [] }, { "query": "row var = NOT 5 NOT rlike \"?a\"", "error": [ - "Argument of [not_rlike] must be [string], found value [5] type [number]" + "Argument of [not_rlike] must be [text], found value [5] type [integer]" ], "warning": [] }, @@ -966,24 +994,24 @@ { "query": "row var = now() * 1 year", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 year] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 year] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 year", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 year] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 year] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 year", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 year] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 year] type [duration]" ], "warning": [] }, @@ -1031,24 +1059,24 @@ { "query": "row var = now() * 1 years", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 years] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 years] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 years", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 years] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 years] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 years", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 years] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 years] type [duration]" ], "warning": [] }, @@ -1096,24 +1124,24 @@ { "query": "row var = now() * 1 quarter", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 quarter] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 quarter] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 quarter", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 quarter] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 quarter] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 quarter", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 quarter] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 quarter] type [duration]" ], "warning": [] }, @@ -1161,24 +1189,24 @@ { "query": "row var = now() * 1 quarters", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 quarters] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 quarters] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 quarters", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 quarters] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 quarters] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 quarters", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 quarters] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 quarters] type [duration]" ], "warning": [] }, @@ -1226,24 +1254,24 @@ { "query": "row var = now() * 1 month", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 month] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 month] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 month", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 month] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 month] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 month", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 month] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 month] type [duration]" ], "warning": [] }, @@ -1291,24 +1319,24 @@ { "query": "row var = now() * 1 months", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 months] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 months] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 months", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 months] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 months] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 months", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 months] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 months] type [duration]" ], "warning": [] }, @@ -1356,24 +1384,24 @@ { "query": "row var = now() * 1 week", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 week] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 week] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 week", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 week] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 week] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 week", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 week] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 week] type [duration]" ], "warning": [] }, @@ -1421,24 +1449,24 @@ { "query": "row var = now() * 1 weeks", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 weeks] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 weeks] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 weeks", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 weeks] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 weeks] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 weeks", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 weeks] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 weeks] type [duration]" ], "warning": [] }, @@ -1486,24 +1514,24 @@ { "query": "row var = now() * 1 day", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 day] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 day] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 day", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 day] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 day] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 day", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 day] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 day] type [duration]" ], "warning": [] }, @@ -1551,24 +1579,24 @@ { "query": "row var = now() * 1 days", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 days] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 days] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 days", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 days] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 days] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 days", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 days] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 days] type [duration]" ], "warning": [] }, @@ -1616,24 +1644,24 @@ { "query": "row var = now() * 1 hour", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 hour] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 hour] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 hour", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 hour] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 hour] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 hour", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 hour] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 hour] type [duration]" ], "warning": [] }, @@ -1681,24 +1709,24 @@ { "query": "row var = now() * 1 hours", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 hours] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 hours] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 hours", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 hours] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 hours] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 hours", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 hours] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 hours] type [duration]" ], "warning": [] }, @@ -1746,24 +1774,24 @@ { "query": "row var = now() * 1 minute", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 minute] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 minute] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 minute", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 minute] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 minute] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 minute", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 minute] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 minute] type [duration]" ], "warning": [] }, @@ -1811,24 +1839,24 @@ { "query": "row var = now() * 1 minutes", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 minutes] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 minutes] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 minutes", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 minutes] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 minutes] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 minutes", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 minutes] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 minutes] type [duration]" ], "warning": [] }, @@ -1876,24 +1904,24 @@ { "query": "row var = now() * 1 second", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 second] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 second] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 second", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 second] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 second] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 second", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 second] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 second] type [duration]" ], "warning": [] }, @@ -1941,24 +1969,24 @@ { "query": "row var = now() * 1 seconds", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 seconds] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 seconds] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 seconds", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 seconds] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 seconds] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 seconds", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 seconds] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 seconds] type [duration]" ], "warning": [] }, @@ -2006,24 +2034,24 @@ { "query": "row var = now() * 1 millisecond", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 millisecond] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 millisecond] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 millisecond", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 millisecond] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 millisecond] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 millisecond", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 millisecond] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 millisecond] type [duration]" ], "warning": [] }, @@ -2071,24 +2099,24 @@ { "query": "row var = now() * 1 milliseconds", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 milliseconds] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 milliseconds] type [duration]" ], "warning": [] }, { "query": "row var = now() / 1 milliseconds", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 milliseconds] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 milliseconds] type [duration]" ], "warning": [] }, { "query": "row var = now() % 1 milliseconds", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 milliseconds] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 milliseconds] type [duration]" ], "warning": [] }, @@ -2099,35 +2127,20 @@ ], "warning": [] }, - { - "query": "show functions", - "error": [ - "SyntaxError: token recognition error at: 'f'", - "SyntaxError: token recognition error at: 'u'", - "SyntaxError: token recognition error at: 'n'", - "SyntaxError: token recognition error at: 'c'", - "SyntaxError: token recognition error at: 't'", - "SyntaxError: token recognition error at: 'io'", - "SyntaxError: token recognition error at: 'n'", - "SyntaxError: token recognition error at: 's'", - "SyntaxError: missing 'info' at '<EOF>'" - ], - "warning": [] - }, { "query": "show info", "error": [], "warning": [] }, { - "query": "show numberField", + "query": "show doubleField", "error": [ - "SyntaxError: token recognition error at: 'n'", + "SyntaxError: token recognition error at: 'd'", + "SyntaxError: token recognition error at: 'o'", "SyntaxError: token recognition error at: 'u'", - "SyntaxError: token recognition error at: 'm'", "SyntaxError: token recognition error at: 'b'", + "SyntaxError: token recognition error at: 'l'", "SyntaxError: token recognition error at: 'e'", - "SyntaxError: token recognition error at: 'r'", "SyntaxError: token recognition error at: 'F'", "SyntaxError: token recognition error at: 'ie'", "SyntaxError: token recognition error at: 'l'", @@ -2163,16 +2176,16 @@ "warning": [] }, { - "query": "from index | limit numberField", + "query": "from index | limit doubleField", "error": [ - "SyntaxError: mismatched input 'numberField' expecting INTEGER_LITERAL" + "SyntaxError: mismatched input 'doubleField' expecting INTEGER_LITERAL" ], "warning": [] }, { - "query": "from index | limit stringField", + "query": "from index | limit textField", "error": [ - "SyntaxError: mismatched input 'stringField' expecting INTEGER_LITERAL" + "SyntaxError: mismatched input 'textField' expecting INTEGER_LITERAL" ], "warning": [] }, @@ -2194,12 +2207,12 @@ "warning": [] }, { - "query": "from index | keep stringField, numberField, dateField", + "query": "from index | keep keywordField, doubleField, integerField, dateField", "error": [], "warning": [] }, { - "query": "from index | keep `stringField`, `numberField`, `dateField`", + "query": "from index | keep `keywordField`, `doubleField`, `integerField`, `dateField`", "error": [], "warning": [] }, @@ -2221,7 +2234,7 @@ "warning": [] }, { - "query": "from index | keep missingField, numberField, dateField", + "query": "from index | keep missingField, doubleField, dateField", "error": [ "Unknown column [missingField]" ], @@ -2240,28 +2253,28 @@ "warning": [] }, { - "query": "from index | project stringField, numberField, dateField", + "query": "from index | project textField, doubleField, dateField", "error": [ "SyntaxError: mismatched input 'project' expecting {'dissect', 'drop', 'enrich', 'eval', 'grok', 'inlinestats', 'keep', 'limit', 'lookup', 'mv_expand', 'rename', 'sort', 'stats', 'where'}" ], "warning": [] }, { - "query": "from index | PROJECT stringField, numberField, dateField", + "query": "from index | PROJECT textField, doubleField, dateField", "error": [ "SyntaxError: mismatched input 'PROJECT' expecting {'dissect', 'drop', 'enrich', 'eval', 'grok', 'inlinestats', 'keep', 'limit', 'lookup', 'mv_expand', 'rename', 'sort', 'stats', 'where'}" ], "warning": [] }, { - "query": "from index | project missingField, numberField, dateField", + "query": "from index | project missingField, doubleField, dateField", "error": [ "SyntaxError: mismatched input 'project' expecting {'dissect', 'drop', 'enrich', 'eval', 'grok', 'inlinestats', 'keep', 'limit', 'lookup', 'mv_expand', 'rename', 'sort', 'stats', 'where'}" ], "warning": [] }, { - "query": "from index | keep s*", + "query": "from index | keep k*", "error": [], "warning": [] }, @@ -2271,17 +2284,17 @@ "warning": [] }, { - "query": "from index | keep s*Field", + "query": "from index | keep k*Field", "error": [], "warning": [] }, { - "query": "from index | keep string*Field", + "query": "from index | keep key*Field", "error": [], "warning": [] }, { - "query": "from index | keep s*, n*", + "query": "from index | keep k*, i*", "error": [], "warning": [] }, @@ -2307,19 +2320,12 @@ "warning": [] }, { - "query": "from unsupported_index | keep unsupported_field", - "error": [], - "warning": [ - "Field [unsupported_field] cannot be retrieved, it is unsupported or not indexed; returning null" - ] - }, - { - "query": "FROM index | STATS ROUND(AVG(numberField * 1.5)), COUNT(*), MIN(numberField * 10) | KEEP `MIN(numberField * 10)`", + "query": "FROM index | STATS ROUND(AVG(doubleField * 1.5)), COUNT(*), MIN(doubleField * 10) | KEEP `MIN(doubleField * 10)`", "error": [], "warning": [] }, { - "query": "FROM index | STATS COUNT(*), MIN(numberField * 10), MAX(numberField)| KEEP `COUNT(*)`", + "query": "FROM index | STATS COUNT(*), MIN(doubleField * 10), MAX(doubleField)| KEEP `COUNT(*)`", "error": [], "warning": [] }, @@ -2331,7 +2337,7 @@ "warning": [] }, { - "query": "from index | drop stringField, numberField, dateField", + "query": "from index | drop textField, doubleField, dateField", "error": [], "warning": [] }, @@ -2346,7 +2352,7 @@ "warning": [] }, { - "query": "from index | drop missingField, numberField, dateField", + "query": "from index | drop missingField, doubleField, dateField", "error": [ "Unknown column [missingField]" ], @@ -2358,12 +2364,12 @@ "warning": [] }, { - "query": "from index | drop s*", + "query": "from index | drop t*", "error": [], "warning": [] }, { - "query": "from index | drop s**Field", + "query": "from index | drop t**Field", "error": [], "warning": [] }, @@ -2373,7 +2379,7 @@ "warning": [] }, { - "query": "from index | drop s*F*d", + "query": "from index | drop t*F*d", "error": [], "warning": [] }, @@ -2383,18 +2389,20 @@ "warning": [] }, { - "query": "from index | drop s*Field", + "query": "from index | drop t*Field", "error": [], "warning": [] }, { - "query": "from index | drop string*Field", + "query": "from index | drop textField", "error": [], "warning": [] }, { - "query": "from index | drop s*, n*", - "error": [], + "query": "from index | drop s*, d*", + "error": [ + "Unknown column [s*]" + ], "warning": [] }, { @@ -2426,7 +2434,7 @@ "warning": [] }, { - "query": "from index | drop stringField, *", + "query": "from index | drop textField, *", "error": [ "Removing all fields is not allowed [*]" ], @@ -2440,19 +2448,19 @@ ] }, { - "query": "from index | drop stringField, @timestamp", + "query": "from index | drop textField, @timestamp", "error": [], "warning": [ "Drop [@timestamp] will remove all time filters to the search results" ] }, { - "query": "FROM index | STATS ROUND(AVG(numberField * 1.5)), COUNT(*), MIN(numberField * 10) | DROP `MIN(numberField * 10)`", + "query": "FROM index | STATS ROUND(AVG(doubleField * 1.5)), COUNT(*), MIN(doubleField * 10) | DROP `MIN(doubleField * 10)`", "error": [], "warning": [] }, { - "query": "FROM index | STATS COUNT(*), MIN(numberField * 10), MAX(numberField)| DROP `COUNT(*)`", + "query": "FROM index | STATS COUNT(*), MIN(doubleField * 10), MAX(doubleField)| DROP `COUNT(*)`", "error": [], "warning": [] }, @@ -2464,12 +2472,12 @@ "warning": [] }, { - "query": "from a_index | mv_expand stringField", + "query": "from a_index | mv_expand textField", "error": [], "warning": [] }, { - "query": "from a_index | mv_expand numberField", + "query": "from a_index | mv_expand integerField", "error": [], "warning": [] }, @@ -2489,7 +2497,7 @@ "warning": [] }, { - "query": "from a_index | mv_expand numberField, b", + "query": "from a_index | mv_expand doubleField, b", "error": [ "SyntaxError: token recognition error at: ','", "SyntaxError: extraneous input 'b' expecting <EOF>" @@ -2524,7 +2532,7 @@ "warning": [] }, { - "query": "from a_index | rename stringField", + "query": "from a_index | rename textField", "error": [ "SyntaxError: mismatched input '<EOF>' expecting 'as'" ], @@ -2539,7 +2547,7 @@ "warning": [] }, { - "query": "from a_index | rename stringField as", + "query": "from a_index | rename textField as", "error": [ "SyntaxError: missing ID_PATTERN at '<EOF>'" ], @@ -2554,22 +2562,22 @@ "warning": [] }, { - "query": "from a_index | rename stringField as b", + "query": "from a_index | rename textField as b", "error": [], "warning": [] }, { - "query": "from a_index | rename stringField AS b", + "query": "from a_index | rename textField AS b", "error": [], "warning": [] }, { - "query": "from a_index | rename stringField As b", + "query": "from a_index | rename textField As b", "error": [], "warning": [] }, { - "query": "from a_index | rename stringField As b, b AS c", + "query": "from a_index | rename textField As b, b AS c", "error": [], "warning": [] }, @@ -2584,26 +2592,34 @@ "warning": [] }, { - "query": "from a_index | eval numberField + 1 | rename `numberField + 1` as a", + "query": "from a_index | eval doubleField + 1 | rename `doubleField + 1` as a", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) | rename `avg(numberField)` as avg0", + "query": "from a_index | stats avg(doubleField) | rename `avg(doubleField)` as avg0", "error": [], "warning": [] }, { - "query": "from a_index |eval numberField + 1 | rename `numberField + 1` as ", + "query": "from a_index |eval doubleField + 1 | rename `doubleField + 1` as ", "error": [ "SyntaxError: missing ID_PATTERN at '<EOF>'" ], "warning": [] }, + { + "query": "from a_index | rename key* as keywords", + "error": [ + "Using wildcards (*) in RENAME is not allowed [key*]", + "Unknown column [keywords]" + ], + "warning": [] + }, { "query": "from a_index | rename s* as strings", "error": [ - "Using wildcards (*) in RENAME is not allowed [s*]", + "Unknown column [s*]", "Unknown column [strings]" ], "warning": [] @@ -2628,29 +2644,29 @@ "warning": [] }, { - "query": "from a_index | dissect stringField", + "query": "from a_index | dissect textField", "error": [ "SyntaxError: missing QUOTED_STRING at '<EOF>'" ], "warning": [] }, { - "query": "from a_index | dissect stringField 2", + "query": "from a_index | dissect textField 2", "error": [ "SyntaxError: mismatched input '2' expecting QUOTED_STRING" ], "warning": [] }, { - "query": "from a_index | dissect stringField .", + "query": "from a_index | dissect textField .", "error": [ "SyntaxError: mismatched input '<EOF>' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - "Unknown column [stringField.]" + "Unknown column [textField.]" ], "warning": [] }, { - "query": "from a_index | dissect stringField %a", + "query": "from a_index | dissect textField %a", "error": [ "SyntaxError: mismatched input '%' expecting QUOTED_STRING", "SyntaxError: mismatched input '<EOF>' expecting '='" @@ -2658,26 +2674,26 @@ "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\"", + "query": "from a_index | dissect textField \"%{firstWord}\"", "error": [], "warning": [] }, { - "query": "from a_index | dissect numberField \"%{firstWord}\"", + "query": "from a_index | dissect doubleField \"%{firstWord}\"", "error": [ - "DISSECT only supports string type values, found [numberField] of type [number]" + "DISSECT only supports string type values, found [doubleField] of type [double]" ], "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\" option ", + "query": "from a_index | dissect textField \"%{firstWord}\" option ", "error": [ "SyntaxError: mismatched input '<EOF>' expecting '='" ], "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\" option = ", + "query": "from a_index | dissect textField \"%{firstWord}\" option = ", "error": [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET}", "Invalid option for DISSECT: [option]" @@ -2685,33 +2701,33 @@ "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\" option = 1", + "query": "from a_index | dissect textField \"%{firstWord}\" option = 1", "error": [ "Invalid option for DISSECT: [option]" ], "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\" append_separator = \"-\"", + "query": "from a_index | dissect textField \"%{firstWord}\" append_separator = \"-\"", "error": [], "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\" ignore_missing = true", + "query": "from a_index | dissect textField \"%{firstWord}\" ignore_missing = true", "error": [ "Invalid option for DISSECT: [ignore_missing]" ], "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\" append_separator = true", + "query": "from a_index | dissect textField \"%{firstWord}\" append_separator = true", "error": [ "Invalid value for DISSECT append_separator: expected a string, but was [true]" ], "warning": [] }, { - "query": "from a_index | dissect stringField \"%{firstWord}\" | keep firstWord", + "query": "from a_index | dissect textField \"%{firstWord}\" | keep firstWord", "error": [], "warning": [] }, @@ -2723,48 +2739,48 @@ "warning": [] }, { - "query": "from a_index | grok stringField", + "query": "from a_index | grok textField", "error": [ "SyntaxError: missing QUOTED_STRING at '<EOF>'" ], "warning": [] }, { - "query": "from a_index | grok stringField 2", + "query": "from a_index | grok textField 2", "error": [ "SyntaxError: mismatched input '2' expecting QUOTED_STRING" ], "warning": [] }, { - "query": "from a_index | grok stringField .", + "query": "from a_index | grok textField .", "error": [ "SyntaxError: mismatched input '<EOF>' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - "Unknown column [stringField.]" + "Unknown column [textField.]" ], "warning": [] }, { - "query": "from a_index | grok stringField %a", + "query": "from a_index | grok textField %a", "error": [ "SyntaxError: mismatched input '%' expecting QUOTED_STRING" ], "warning": [] }, { - "query": "from a_index | grok stringField \"%{firstWord}\"", + "query": "from a_index | grok textField \"%{firstWord}\"", "error": [], "warning": [] }, { - "query": "from a_index | grok numberField \"%{firstWord}\"", + "query": "from a_index | grok doubleField \"%{firstWord}\"", "error": [ - "GROK only supports string type values, found [numberField] of type [number]" + "GROK only supports string type values, found [doubleField] of type [double]" ], "warning": [] }, { - "query": "from a_index | grok stringField \"%{firstWord}\" | keep firstWord", + "query": "from a_index | grok textField \"%{firstWord}\" | keep firstWord", "error": [], "warning": [] }, @@ -2866,22 +2882,22 @@ "warning": [] }, { - "query": "from a_index | where numberField > 0", + "query": "from a_index | where doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where NOT numberField > 0", + "query": "from a_index | where NOT doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where (numberField > 0)", + "query": "from a_index | where (doubleField > 0)", "error": [], "warning": [] }, { - "query": "from a_index | where (NOT (numberField > 0))", + "query": "from a_index | where (NOT (doubleField > 0))", "error": [], "warning": [] }, @@ -2891,12 +2907,12 @@ "warning": [] }, { - "query": "from a_index | where stringField > stringField", + "query": "from a_index | where textField > textField", "error": [], "warning": [] }, { - "query": "from a_index | where numberField > numberField", + "query": "from a_index | where doubleField > doubleField", "error": [], "warning": [] }, @@ -2908,8 +2924,8 @@ { "query": "from a_index | where booleanField > booleanField", "error": [ - "Argument of [>] must be [number], found value [booleanField] type [boolean]", - "Argument of [>] must be [number], found value [booleanField] type [boolean]" + "Argument of [>] must be [date], found value [booleanField] type [boolean]", + "Argument of [>] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, @@ -2919,22 +2935,22 @@ "warning": [] }, { - "query": "from a_index | where numberField >= 0", + "query": "from a_index | where doubleField >= 0", "error": [], "warning": [] }, { - "query": "from a_index | where NOT numberField >= 0", + "query": "from a_index | where NOT doubleField >= 0", "error": [], "warning": [] }, { - "query": "from a_index | where (numberField >= 0)", + "query": "from a_index | where (doubleField >= 0)", "error": [], "warning": [] }, { - "query": "from a_index | where (NOT (numberField >= 0))", + "query": "from a_index | where (NOT (doubleField >= 0))", "error": [], "warning": [] }, @@ -2944,12 +2960,12 @@ "warning": [] }, { - "query": "from a_index | where stringField >= stringField", + "query": "from a_index | where textField >= textField", "error": [], "warning": [] }, { - "query": "from a_index | where numberField >= numberField", + "query": "from a_index | where doubleField >= doubleField", "error": [], "warning": [] }, @@ -2961,8 +2977,8 @@ { "query": "from a_index | where booleanField >= booleanField", "error": [ - "Argument of [>=] must be [number], found value [booleanField] type [boolean]", - "Argument of [>=] must be [number], found value [booleanField] type [boolean]" + "Argument of [>=] must be [date], found value [booleanField] type [boolean]", + "Argument of [>=] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, @@ -2972,22 +2988,22 @@ "warning": [] }, { - "query": "from a_index | where numberField < 0", + "query": "from a_index | where doubleField < 0", "error": [], "warning": [] }, { - "query": "from a_index | where NOT numberField < 0", + "query": "from a_index | where NOT doubleField < 0", "error": [], "warning": [] }, { - "query": "from a_index | where (numberField < 0)", + "query": "from a_index | where (doubleField < 0)", "error": [], "warning": [] }, { - "query": "from a_index | where (NOT (numberField < 0))", + "query": "from a_index | where (NOT (doubleField < 0))", "error": [], "warning": [] }, @@ -2997,12 +3013,12 @@ "warning": [] }, { - "query": "from a_index | where stringField < stringField", + "query": "from a_index | where textField < textField", "error": [], "warning": [] }, { - "query": "from a_index | where numberField < numberField", + "query": "from a_index | where doubleField < doubleField", "error": [], "warning": [] }, @@ -3014,8 +3030,8 @@ { "query": "from a_index | where booleanField < booleanField", "error": [ - "Argument of [<] must be [number], found value [booleanField] type [boolean]", - "Argument of [<] must be [number], found value [booleanField] type [boolean]" + "Argument of [<] must be [date], found value [booleanField] type [boolean]", + "Argument of [<] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, @@ -3025,22 +3041,22 @@ "warning": [] }, { - "query": "from a_index | where numberField <= 0", + "query": "from a_index | where doubleField <= 0", "error": [], "warning": [] }, { - "query": "from a_index | where NOT numberField <= 0", + "query": "from a_index | where NOT doubleField <= 0", "error": [], "warning": [] }, { - "query": "from a_index | where (numberField <= 0)", + "query": "from a_index | where (doubleField <= 0)", "error": [], "warning": [] }, { - "query": "from a_index | where (NOT (numberField <= 0))", + "query": "from a_index | where (NOT (doubleField <= 0))", "error": [], "warning": [] }, @@ -3050,12 +3066,12 @@ "warning": [] }, { - "query": "from a_index | where stringField <= stringField", + "query": "from a_index | where textField <= textField", "error": [], "warning": [] }, { - "query": "from a_index | where numberField <= numberField", + "query": "from a_index | where doubleField <= doubleField", "error": [], "warning": [] }, @@ -3067,8 +3083,8 @@ { "query": "from a_index | where booleanField <= booleanField", "error": [ - "Argument of [<=] must be [number], found value [booleanField] type [boolean]", - "Argument of [<=] must be [number], found value [booleanField] type [boolean]" + "Argument of [<=] must be [date], found value [booleanField] type [boolean]", + "Argument of [<=] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, @@ -3078,22 +3094,22 @@ "warning": [] }, { - "query": "from a_index | where numberField == 0", + "query": "from a_index | where doubleField == 0", "error": [], "warning": [] }, { - "query": "from a_index | where NOT numberField == 0", + "query": "from a_index | where NOT doubleField == 0", "error": [], "warning": [] }, { - "query": "from a_index | where (numberField == 0)", + "query": "from a_index | where (doubleField == 0)", "error": [], "warning": [] }, { - "query": "from a_index | where (NOT (numberField == 0))", + "query": "from a_index | where (NOT (doubleField == 0))", "error": [], "warning": [] }, @@ -3103,12 +3119,12 @@ "warning": [] }, { - "query": "from a_index | where stringField == stringField", + "query": "from a_index | where textField == textField", "error": [], "warning": [] }, { - "query": "from a_index | where numberField == numberField", + "query": "from a_index | where doubleField == doubleField", "error": [], "warning": [] }, @@ -3128,22 +3144,22 @@ "warning": [] }, { - "query": "from a_index | where numberField != 0", + "query": "from a_index | where doubleField != 0", "error": [], "warning": [] }, { - "query": "from a_index | where NOT numberField != 0", + "query": "from a_index | where NOT doubleField != 0", "error": [], "warning": [] }, { - "query": "from a_index | where (numberField != 0)", + "query": "from a_index | where (doubleField != 0)", "error": [], "warning": [] }, { - "query": "from a_index | where (NOT (numberField != 0))", + "query": "from a_index | where (NOT (doubleField != 0))", "error": [], "warning": [] }, @@ -3153,12 +3169,12 @@ "warning": [] }, { - "query": "from a_index | where stringField != stringField", + "query": "from a_index | where textField != textField", "error": [], "warning": [] }, { - "query": "from a_index | where numberField != numberField", + "query": "from a_index | where doubleField != doubleField", "error": [], "warning": [] }, @@ -3178,82 +3194,82 @@ "warning": [] }, { - "query": "from a_index | where - numberField > 0", + "query": "from a_index | where - doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where - round(numberField) > 0", + "query": "from a_index | where - round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + - numberField > 0", + "query": "from a_index | where 1 + - doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 - numberField > 0", + "query": "from a_index | where 1 - doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where - numberField > 0", + "query": "from a_index | where - doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where - round(numberField) > 0", + "query": "from a_index | where - round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + - numberField > 0", + "query": "from a_index | where 1 + - doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 - numberField > 0", + "query": "from a_index | where 1 - doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where + numberField > 0", + "query": "from a_index | where + doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where + round(numberField) > 0", + "query": "from a_index | where + round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + + numberField > 0", + "query": "from a_index | where 1 + + doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + numberField > 0", + "query": "from a_index | where 1 + doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where + numberField > 0", + "query": "from a_index | where + doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where + round(numberField) > 0", + "query": "from a_index | where + round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + + numberField > 0", + "query": "from a_index | where 1 + + doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + numberField > 0", + "query": "from a_index | where 1 + doubleField > 0", "error": [], "warning": [] }, @@ -3263,82 +3279,82 @@ "warning": [] }, { - "query": "from a_index | where -- numberField > 0", + "query": "from a_index | where -- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where -- round(numberField) > 0", + "query": "from a_index | where -- round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + -- numberField > 0", + "query": "from a_index | where 1 + -- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 -- numberField > 0", + "query": "from a_index | where 1 -- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where -+ numberField > 0", + "query": "from a_index | where -+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where -+ round(numberField) > 0", + "query": "from a_index | where -+ round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + -+ numberField > 0", + "query": "from a_index | where 1 + -+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 -+ numberField > 0", + "query": "from a_index | where 1 -+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +- numberField > 0", + "query": "from a_index | where +- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +- round(numberField) > 0", + "query": "from a_index | where +- round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + +- numberField > 0", + "query": "from a_index | where 1 + +- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 +- numberField > 0", + "query": "from a_index | where 1 +- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where ++ numberField > 0", + "query": "from a_index | where ++ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where ++ round(numberField) > 0", + "query": "from a_index | where ++ round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + ++ numberField > 0", + "query": "from a_index | where 1 + ++ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 ++ numberField > 0", + "query": "from a_index | where 1 ++ doubleField > 0", "error": [], "warning": [] }, @@ -3348,82 +3364,82 @@ "warning": [] }, { - "query": "from a_index | where --- numberField > 0", + "query": "from a_index | where --- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where --- round(numberField) > 0", + "query": "from a_index | where --- round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + --- numberField > 0", + "query": "from a_index | where 1 + --- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 --- numberField > 0", + "query": "from a_index | where 1 --- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where -+- numberField > 0", + "query": "from a_index | where -+- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where -+- round(numberField) > 0", + "query": "from a_index | where -+- round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + -+- numberField > 0", + "query": "from a_index | where 1 + -+- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 -+- numberField > 0", + "query": "from a_index | where 1 -+- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +-+ numberField > 0", + "query": "from a_index | where +-+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +-+ round(numberField) > 0", + "query": "from a_index | where +-+ round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + +-+ numberField > 0", + "query": "from a_index | where 1 + +-+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 +-+ numberField > 0", + "query": "from a_index | where 1 +-+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +++ numberField > 0", + "query": "from a_index | where +++ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +++ round(numberField) > 0", + "query": "from a_index | where +++ round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + +++ numberField > 0", + "query": "from a_index | where 1 + +++ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 +++ numberField > 0", + "query": "from a_index | where 1 +++ doubleField > 0", "error": [], "warning": [] }, @@ -3433,82 +3449,82 @@ "warning": [] }, { - "query": "from a_index | where ---- numberField > 0", + "query": "from a_index | where ---- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where ---- round(numberField) > 0", + "query": "from a_index | where ---- round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + ---- numberField > 0", + "query": "from a_index | where 1 + ---- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 ---- numberField > 0", + "query": "from a_index | where 1 ---- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where -+-+ numberField > 0", + "query": "from a_index | where -+-+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where -+-+ round(numberField) > 0", + "query": "from a_index | where -+-+ round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + -+-+ numberField > 0", + "query": "from a_index | where 1 + -+-+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 -+-+ numberField > 0", + "query": "from a_index | where 1 -+-+ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +-+- numberField > 0", + "query": "from a_index | where +-+- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where +-+- round(numberField) > 0", + "query": "from a_index | where +-+- round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + +-+- numberField > 0", + "query": "from a_index | where 1 + +-+- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 +-+- numberField > 0", + "query": "from a_index | where 1 +-+- doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where ++++ numberField > 0", + "query": "from a_index | where ++++ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where ++++ round(numberField) > 0", + "query": "from a_index | where ++++ round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 + ++++ numberField > 0", + "query": "from a_index | where 1 + ++++ doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | where 1 ++++ numberField > 0", + "query": "from a_index | where 1 ++++ doubleField > 0", "error": [], "warning": [] }, @@ -3518,166 +3534,166 @@ "warning": [] }, { - "query": "from a_index | where *+ numberField", + "query": "from a_index | where *+ doubleField", "error": [ "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | where /+ numberField", + "query": "from a_index | where /+ doubleField", "error": [ "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | where %+ numberField", + "query": "from a_index | where %+ doubleField", "error": [ "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | where numberField =~ 0", + "query": "from a_index | where doubleField =~ 0", "error": [ - "Argument of [=~] must be [string], found value [numberField] type [number]", - "Argument of [=~] must be [string], found value [0] type [number]" + "Argument of [=~] must be [text], found value [doubleField] type [double]", + "Argument of [=~] must be [text], found value [0] type [number]" ], "warning": [] }, { - "query": "from a_index | where NOT numberField =~ 0", + "query": "from a_index | where NOT doubleField =~ 0", "error": [ - "Argument of [=~] must be [string], found value [numberField] type [number]", - "Argument of [=~] must be [string], found value [0] type [number]" + "Argument of [=~] must be [text], found value [doubleField] type [double]", + "Argument of [=~] must be [text], found value [0] type [number]" ], "warning": [] }, { - "query": "from a_index | where (numberField =~ 0)", + "query": "from a_index | where (doubleField =~ 0)", "error": [ - "Argument of [=~] must be [string], found value [numberField] type [number]", - "Argument of [=~] must be [string], found value [0] type [number]" + "Argument of [=~] must be [text], found value [doubleField] type [double]", + "Argument of [=~] must be [text], found value [0] type [number]" ], "warning": [] }, { - "query": "from a_index | where (NOT (numberField =~ 0))", + "query": "from a_index | where (NOT (doubleField =~ 0))", "error": [ - "Argument of [=~] must be [string], found value [numberField] type [number]", - "Argument of [=~] must be [string], found value [0] type [number]" + "Argument of [=~] must be [text], found value [doubleField] type [double]", + "Argument of [=~] must be [text], found value [0] type [number]" ], "warning": [] }, { "query": "from a_index | where 1 =~ 0", "error": [ - "Argument of [=~] must be [string], found value [1] type [number]", - "Argument of [=~] must be [string], found value [0] type [number]" + "Argument of [=~] must be [text], found value [1] type [number]", + "Argument of [=~] must be [text], found value [0] type [number]" ], "warning": [] }, { - "query": "from a_index | eval stringField =~ 0", + "query": "from a_index | eval textField =~ 0", "error": [ - "Argument of [=~] must be [string], found value [0] type [number]" + "Argument of [=~] must be [text], found value [0] type [number]" ], "warning": [] }, { - "query": "from a_index | where stringField like \"?a\"", + "query": "from a_index | where textField like \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where stringField NOT like \"?a\"", + "query": "from a_index | where textField NOT like \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where NOT stringField like \"?a\"", + "query": "from a_index | where NOT textField like \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where NOT stringField NOT like \"?a\"", + "query": "from a_index | where NOT textField NOT like \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where numberField like \"?a\"", + "query": "from a_index | where doubleField like \"?a\"", "error": [ - "Argument of [like] must be [string], found value [numberField] type [number]" + "Argument of [like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | where numberField NOT like \"?a\"", + "query": "from a_index | where doubleField NOT like \"?a\"", "error": [ - "Argument of [not_like] must be [string], found value [numberField] type [number]" + "Argument of [not_like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | where NOT numberField like \"?a\"", + "query": "from a_index | where NOT doubleField like \"?a\"", "error": [ - "Argument of [like] must be [string], found value [numberField] type [number]" + "Argument of [like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | where NOT numberField NOT like \"?a\"", + "query": "from a_index | where NOT doubleField NOT like \"?a\"", "error": [ - "Argument of [not_like] must be [string], found value [numberField] type [number]" + "Argument of [not_like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | where stringField rlike \"?a\"", + "query": "from a_index | where textField rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where stringField NOT rlike \"?a\"", + "query": "from a_index | where textField NOT rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where NOT stringField rlike \"?a\"", + "query": "from a_index | where NOT textField rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where NOT stringField NOT rlike \"?a\"", + "query": "from a_index | where NOT textField NOT rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | where numberField rlike \"?a\"", + "query": "from a_index | where doubleField rlike \"?a\"", "error": [ - "Argument of [rlike] must be [string], found value [numberField] type [number]" + "Argument of [rlike] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | where numberField NOT rlike \"?a\"", + "query": "from a_index | where doubleField NOT rlike \"?a\"", "error": [ - "Argument of [not_rlike] must be [string], found value [numberField] type [number]" + "Argument of [not_rlike] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | where NOT numberField rlike \"?a\"", + "query": "from a_index | where NOT doubleField rlike \"?a\"", "error": [ - "Argument of [rlike] must be [string], found value [numberField] type [number]" + "Argument of [rlike] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | where NOT numberField NOT rlike \"?a\"", + "query": "from a_index | where NOT doubleField NOT rlike \"?a\"", "error": [ - "Argument of [not_rlike] must be [string], found value [numberField] type [number]" + "Argument of [not_rlike] must be [text], found value [doubleField] type [double]" ], "warning": [] }, @@ -3694,42 +3710,42 @@ "warning": [] }, { - "query": "from a_index | where numberField IS NULL", + "query": "from a_index | where booleanField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where numberField IS null", + "query": "from a_index | where booleanField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where numberField is null", + "query": "from a_index | where booleanField is null", "error": [], "warning": [] }, { - "query": "from a_index | where numberField is NULL", + "query": "from a_index | where booleanField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where numberField IS NOT NULL", + "query": "from a_index | where booleanField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where numberField IS NOT null", + "query": "from a_index | where booleanField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where numberField IS not NULL", + "query": "from a_index | where booleanField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where numberField Is nOt NuLL", + "query": "from a_index | where booleanField Is nOt NuLL", "error": [], "warning": [] }, @@ -3774,282 +3790,282 @@ "warning": [] }, { - "query": "from a_index | where stringField IS NULL", + "query": "from a_index | where doubleField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where stringField IS null", + "query": "from a_index | where doubleField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where stringField is null", + "query": "from a_index | where doubleField is null", "error": [], "warning": [] }, { - "query": "from a_index | where stringField is NULL", + "query": "from a_index | where doubleField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where stringField IS NOT NULL", + "query": "from a_index | where doubleField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where stringField IS NOT null", + "query": "from a_index | where doubleField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where stringField IS not NULL", + "query": "from a_index | where doubleField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where stringField Is nOt NuLL", + "query": "from a_index | where doubleField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField IS NULL", + "query": "from a_index | where ipField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField IS null", + "query": "from a_index | where ipField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField is null", + "query": "from a_index | where ipField is null", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField is NULL", + "query": "from a_index | where ipField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField IS NOT NULL", + "query": "from a_index | where ipField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField IS NOT null", + "query": "from a_index | where ipField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField IS not NULL", + "query": "from a_index | where ipField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where booleanField Is nOt NuLL", + "query": "from a_index | where ipField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | where ipField IS NULL", + "query": "from a_index | where keywordField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where ipField IS null", + "query": "from a_index | where keywordField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where ipField is null", + "query": "from a_index | where keywordField is null", "error": [], "warning": [] }, { - "query": "from a_index | where ipField is NULL", + "query": "from a_index | where keywordField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where ipField IS NOT NULL", + "query": "from a_index | where keywordField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where ipField IS NOT null", + "query": "from a_index | where keywordField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where ipField IS not NULL", + "query": "from a_index | where keywordField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where ipField Is nOt NuLL", + "query": "from a_index | where keywordField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField IS NULL", + "query": "from a_index | where integerField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField IS null", + "query": "from a_index | where integerField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField is null", + "query": "from a_index | where integerField is null", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField is NULL", + "query": "from a_index | where integerField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField IS NOT NULL", + "query": "from a_index | where integerField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField IS NOT null", + "query": "from a_index | where integerField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField IS not NULL", + "query": "from a_index | where integerField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianPointField Is nOt NuLL", + "query": "from a_index | where integerField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField IS NULL", + "query": "from a_index | where longField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField IS null", + "query": "from a_index | where longField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField is null", + "query": "from a_index | where longField is null", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField is NULL", + "query": "from a_index | where longField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField IS NOT NULL", + "query": "from a_index | where longField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField IS NOT null", + "query": "from a_index | where longField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField IS not NULL", + "query": "from a_index | where longField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where cartesianShapeField Is nOt NuLL", + "query": "from a_index | where longField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField IS NULL", + "query": "from a_index | where textField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField IS null", + "query": "from a_index | where textField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField is null", + "query": "from a_index | where textField is null", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField is NULL", + "query": "from a_index | where textField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField IS NOT NULL", + "query": "from a_index | where textField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField IS NOT null", + "query": "from a_index | where textField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField IS not NULL", + "query": "from a_index | where textField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoPointField Is nOt NuLL", + "query": "from a_index | where textField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField IS NULL", + "query": "from a_index | where unsignedLongField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField IS null", + "query": "from a_index | where unsignedLongField IS null", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField is null", + "query": "from a_index | where unsignedLongField is null", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField is NULL", + "query": "from a_index | where unsignedLongField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField IS NOT NULL", + "query": "from a_index | where unsignedLongField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField IS NOT null", + "query": "from a_index | where unsignedLongField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField IS not NULL", + "query": "from a_index | where unsignedLongField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | where geoShapeField Is nOt NuLL", + "query": "from a_index | where unsignedLongField Is nOt NuLL", "error": [], "warning": [] }, @@ -4094,22536 +4110,32828 @@ "warning": [] }, { - "query": "from a_index | where stringField == \"a\" or null", + "query": "from a_index | where cartesianPointField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval ", - "error": [ - "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | where cartesianPointField IS null", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField ", + "query": "from a_index | where cartesianPointField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval b = stringField", + "query": "from a_index | where cartesianPointField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField + 1", + "query": "from a_index | where cartesianPointField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField + ", - "error": [ - "SyntaxError: no viable alternative at input 'numberField + '" - ], + "query": "from a_index | where cartesianPointField IS NOT null", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField + 1", - "error": [ - "Argument of [+] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | where cartesianPointField IS not NULL", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=b", - "error": [ - "Unknown column [b]" - ], + "query": "from a_index | where cartesianPointField Is nOt NuLL", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=b, ", - "error": [ - "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - "Unknown column [b]" - ], + "query": "from a_index | where cartesianShapeField IS NULL", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=round", - "error": [ - "Unknown column [round]" - ], + "query": "from a_index | where cartesianShapeField IS null", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(", - "error": [ - "SyntaxError: no viable alternative at input 'round('" - ], + "query": "from a_index | where cartesianShapeField is null", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(numberField) ", + "query": "from a_index | where cartesianShapeField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(numberField), ", - "error": [ - "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | where cartesianShapeField IS NOT NULL", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(numberField) + round(numberField) ", + "query": "from a_index | where cartesianShapeField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(numberField) + round(stringField) ", - "error": [ - "Argument of [round] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | where cartesianShapeField IS not NULL", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(numberField) + round(stringField), numberField ", - "error": [ - "Argument of [round] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | where cartesianShapeField Is nOt NuLL", + "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(numberField) + round(numberField), numberField ", + "query": "from a_index | where geoPointField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=round(numberField) + round(numberField), b = numberField ", + "query": "from a_index | where geoPointField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=[1, 2, 3]", + "query": "from a_index | where geoPointField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=[true, false]", + "query": "from a_index | where geoPointField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=[\"a\", \"b\"]", + "query": "from a_index | where geoPointField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=null", + "query": "from a_index | where geoPointField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField IS NULL", + "query": "from a_index | where geoPointField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField IS null", + "query": "from a_index | where geoPointField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField is null", + "query": "from a_index | where geoShapeField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField is NULL", + "query": "from a_index | where geoShapeField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField IS NOT NULL", + "query": "from a_index | where geoShapeField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField IS NOT null", + "query": "from a_index | where geoShapeField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField IS not NULL", + "query": "from a_index | where geoShapeField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField IS NULL", + "query": "from a_index | where geoShapeField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField IS null", + "query": "from a_index | where geoShapeField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField is null", + "query": "from a_index | where geoShapeField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField is NULL", + "query": "from a_index | where counterIntegerField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField IS NOT NULL", + "query": "from a_index | where counterIntegerField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField IS NOT null", + "query": "from a_index | where counterIntegerField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField IS not NULL", + "query": "from a_index | where counterIntegerField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField IS NULL", + "query": "from a_index | where counterIntegerField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField IS null", + "query": "from a_index | where counterIntegerField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField is null", + "query": "from a_index | where counterIntegerField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField is NULL", + "query": "from a_index | where counterIntegerField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField IS NOT NULL", + "query": "from a_index | where counterLongField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField IS NOT null", + "query": "from a_index | where counterLongField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField IS not NULL", + "query": "from a_index | where counterLongField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField IS NULL", + "query": "from a_index | where counterLongField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField IS null", + "query": "from a_index | where counterLongField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField is null", + "query": "from a_index | where counterLongField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField is NULL", + "query": "from a_index | where counterLongField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField IS NOT NULL", + "query": "from a_index | where counterLongField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField IS NOT null", + "query": "from a_index | where counterDoubleField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField IS not NULL", + "query": "from a_index | where counterDoubleField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField IS NULL", + "query": "from a_index | where counterDoubleField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField IS null", + "query": "from a_index | where counterDoubleField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField is null", + "query": "from a_index | where counterDoubleField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField is NULL", + "query": "from a_index | where counterDoubleField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField IS NOT NULL", + "query": "from a_index | where counterDoubleField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField IS NOT null", + "query": "from a_index | where counterDoubleField Is nOt NuLL", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField IS not NULL", + "query": "from a_index | where textField == \"a\" or null", "error": [], "warning": [] }, { - "query": "from a_index | eval cartesianPointField IS NULL", - "error": [], + "query": "from a_index | eval ", + "error": [ + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + ], "warning": [] }, { - "query": "from a_index | eval cartesianPointField IS null", + "query": "from a_index | eval textField ", "error": [], "warning": [] }, { - "query": "from a_index | eval cartesianPointField is null", + "query": "from a_index | eval b = textField", "error": [], "warning": [] }, { - "query": "from a_index | eval cartesianPointField is NULL", + "query": "from a_index | eval doubleField + 1", "error": [], "warning": [] }, { - "query": "from a_index | eval cartesianPointField IS NOT NULL", - "error": [], + "query": "from a_index | eval doubleField + ", + "error": [ + "SyntaxError: no viable alternative at input 'doubleField + '" + ], "warning": [] }, { - "query": "from a_index | eval cartesianPointField IS NOT null", - "error": [], + "query": "from a_index | eval textField + 1", + "error": [ + "Argument of [+] must be [double], found value [textField] type [text]" + ], "warning": [] }, { - "query": "from a_index | eval cartesianPointField IS not NULL", - "error": [], + "query": "from a_index | eval a=b", + "error": [ + "Unknown column [b]" + ], "warning": [] }, { - "query": "from a_index | eval cartesianShapeField IS NULL", - "error": [], + "query": "from a_index | eval a=b, ", + "error": [ + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "Unknown column [b]" + ], "warning": [] }, { - "query": "from a_index | eval cartesianShapeField IS null", - "error": [], + "query": "from a_index | eval a=round", + "error": [ + "Unknown column [round]" + ], "warning": [] }, { - "query": "from a_index | eval cartesianShapeField is null", - "error": [], + "query": "from a_index | eval a=round(", + "error": [ + "SyntaxError: no viable alternative at input 'round('" + ], "warning": [] }, { - "query": "from a_index | eval cartesianShapeField is NULL", + "query": "from a_index | eval a=round(doubleField) ", "error": [], "warning": [] }, { - "query": "from a_index | eval cartesianShapeField IS NOT NULL", - "error": [], + "query": "from a_index | eval a=round(doubleField), ", + "error": [ + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + ], "warning": [] }, { - "query": "from a_index | eval cartesianShapeField IS NOT null", + "query": "from a_index | eval a=round(doubleField) + round(doubleField) ", "error": [], "warning": [] }, { - "query": "from a_index | eval cartesianShapeField IS not NULL", - "error": [], + "query": "from a_index | eval a=round(doubleField) + round(textField) ", + "error": [ + "Argument of [round] must be [double], found value [textField] type [text]" + ], "warning": [] }, { - "query": "from a_index | eval geoPointField IS NULL", + "query": "from a_index | eval a=round(doubleField) + round(textField), doubleField ", + "error": [ + "Argument of [round] must be [double], found value [textField] type [text]" + ], + "warning": [] + }, + { + "query": "from a_index | eval a=round(doubleField) + round(doubleField), doubleField ", "error": [], "warning": [] }, { - "query": "from a_index | eval geoPointField IS null", + "query": "from a_index | eval a=round(doubleField) + round(doubleField), b = doubleField ", "error": [], "warning": [] }, { - "query": "from a_index | eval geoPointField is null", + "query": "from a_index | eval a=[1, 2, 3]", "error": [], "warning": [] }, { - "query": "from a_index | eval geoPointField is NULL", + "query": "from a_index | eval a=[true, false]", "error": [], "warning": [] }, { - "query": "from a_index | eval geoPointField IS NOT NULL", + "query": "from a_index | eval a=[\"a\", \"b\"]", "error": [], "warning": [] }, { - "query": "from a_index | eval geoPointField IS NOT null", + "query": "from a_index | eval a=null", "error": [], "warning": [] }, { - "query": "from a_index | eval geoPointField IS not NULL", + "query": "from a_index | eval booleanField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval geoShapeField IS NULL", + "query": "from a_index | eval booleanField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval geoShapeField IS null", + "query": "from a_index | eval booleanField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval geoShapeField is null", + "query": "from a_index | eval booleanField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval geoShapeField is NULL", + "query": "from a_index | eval booleanField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval geoShapeField IS NOT NULL", + "query": "from a_index | eval booleanField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval geoShapeField IS NOT null", + "query": "from a_index | eval booleanField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval geoShapeField IS not NULL", + "query": "from a_index | eval dateField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField IS NULL", + "query": "from a_index | eval dateField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField IS null", + "query": "from a_index | eval dateField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField is null", + "query": "from a_index | eval dateField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField is NULL", + "query": "from a_index | eval dateField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField IS NOT NULL", + "query": "from a_index | eval dateField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField IS NOT null", + "query": "from a_index | eval dateField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField IS not NULL", + "query": "from a_index | eval doubleField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval - numberField", + "query": "from a_index | eval doubleField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=- numberField", + "query": "from a_index | eval doubleField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=- round(numberField)", + "query": "from a_index | eval doubleField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + - numberField", + "query": "from a_index | eval doubleField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 - numberField", + "query": "from a_index | eval doubleField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval - numberField", + "query": "from a_index | eval doubleField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=- numberField", + "query": "from a_index | eval ipField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=- round(numberField)", + "query": "from a_index | eval ipField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + - numberField", + "query": "from a_index | eval ipField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 - numberField", + "query": "from a_index | eval ipField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval + numberField", + "query": "from a_index | eval ipField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+ numberField", + "query": "from a_index | eval ipField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+ round(numberField)", + "query": "from a_index | eval ipField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + + numberField", + "query": "from a_index | eval keywordField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + numberField", + "query": "from a_index | eval keywordField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval + numberField", + "query": "from a_index | eval keywordField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+ numberField", + "query": "from a_index | eval keywordField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+ round(numberField)", + "query": "from a_index | eval keywordField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + + numberField", + "query": "from a_index | eval keywordField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + numberField", + "query": "from a_index | eval keywordField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval not booleanField", + "query": "from a_index | eval integerField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval -- numberField", + "query": "from a_index | eval integerField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-- numberField", + "query": "from a_index | eval integerField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-- round(numberField)", + "query": "from a_index | eval integerField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + -- numberField", + "query": "from a_index | eval integerField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 -- numberField", + "query": "from a_index | eval integerField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval -+ numberField", + "query": "from a_index | eval integerField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-+ numberField", + "query": "from a_index | eval longField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-+ round(numberField)", + "query": "from a_index | eval longField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + -+ numberField", + "query": "from a_index | eval longField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 -+ numberField", + "query": "from a_index | eval longField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval +- numberField", + "query": "from a_index | eval longField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+- numberField", + "query": "from a_index | eval longField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+- round(numberField)", + "query": "from a_index | eval longField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + +- numberField", + "query": "from a_index | eval textField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 +- numberField", + "query": "from a_index | eval textField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval ++ numberField", + "query": "from a_index | eval textField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=++ numberField", + "query": "from a_index | eval textField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=++ round(numberField)", + "query": "from a_index | eval textField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + ++ numberField", + "query": "from a_index | eval textField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 ++ numberField", + "query": "from a_index | eval textField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval not not booleanField", + "query": "from a_index | eval unsignedLongField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval --- numberField", + "query": "from a_index | eval unsignedLongField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=--- numberField", + "query": "from a_index | eval unsignedLongField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=--- round(numberField)", + "query": "from a_index | eval unsignedLongField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + --- numberField", + "query": "from a_index | eval unsignedLongField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 --- numberField", + "query": "from a_index | eval unsignedLongField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval -+- numberField", + "query": "from a_index | eval unsignedLongField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-+- numberField", + "query": "from a_index | eval versionField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-+- round(numberField)", + "query": "from a_index | eval versionField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + -+- numberField", + "query": "from a_index | eval versionField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 -+- numberField", + "query": "from a_index | eval versionField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval +-+ numberField", + "query": "from a_index | eval versionField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+-+ numberField", + "query": "from a_index | eval versionField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+-+ round(numberField)", + "query": "from a_index | eval versionField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + +-+ numberField", + "query": "from a_index | eval cartesianPointField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 +-+ numberField", + "query": "from a_index | eval cartesianPointField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval +++ numberField", + "query": "from a_index | eval cartesianPointField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+++ numberField", + "query": "from a_index | eval cartesianPointField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+++ round(numberField)", + "query": "from a_index | eval cartesianPointField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + +++ numberField", + "query": "from a_index | eval cartesianPointField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 +++ numberField", + "query": "from a_index | eval cartesianPointField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval not not not booleanField", + "query": "from a_index | eval cartesianShapeField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval ---- numberField", + "query": "from a_index | eval cartesianShapeField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=---- numberField", + "query": "from a_index | eval cartesianShapeField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=---- round(numberField)", + "query": "from a_index | eval cartesianShapeField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + ---- numberField", + "query": "from a_index | eval cartesianShapeField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 ---- numberField", + "query": "from a_index | eval cartesianShapeField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval -+-+ numberField", + "query": "from a_index | eval cartesianShapeField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-+-+ numberField", + "query": "from a_index | eval geoPointField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=-+-+ round(numberField)", + "query": "from a_index | eval geoPointField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + -+-+ numberField", + "query": "from a_index | eval geoPointField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 -+-+ numberField", + "query": "from a_index | eval geoPointField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval +-+- numberField", + "query": "from a_index | eval geoPointField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+-+- numberField", + "query": "from a_index | eval geoPointField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=+-+- round(numberField)", + "query": "from a_index | eval geoPointField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + +-+- numberField", + "query": "from a_index | eval geoShapeField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 +-+- numberField", + "query": "from a_index | eval geoShapeField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval ++++ numberField", + "query": "from a_index | eval geoShapeField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval a=++++ numberField", + "query": "from a_index | eval geoShapeField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval a=++++ round(numberField)", + "query": "from a_index | eval geoShapeField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + ++++ numberField", + "query": "from a_index | eval geoShapeField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 ++++ numberField", + "query": "from a_index | eval geoShapeField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval not not not not booleanField", + "query": "from a_index | eval counterIntegerField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval *+ numberField", - "error": [ - "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | eval counterIntegerField IS null", + "error": [], "warning": [] }, { - "query": "from a_index | eval /+ numberField", - "error": [ - "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | eval counterIntegerField is null", + "error": [], "warning": [] }, { - "query": "from a_index | eval %+ numberField", - "error": [ - "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | eval counterIntegerField is NULL", + "error": [], "warning": [] }, { - "query": "from a_index | eval log10(-1)", + "query": "from a_index | eval counterIntegerField IS NOT NULL", "error": [], - "warning": [ - "Log of a negative number results in null: -1" - ] + "warning": [] }, { - "query": "from a_index | eval log(-1)", + "query": "from a_index | eval counterIntegerField IS NOT null", "error": [], - "warning": [ - "Log of a negative number results in null: -1" - ] + "warning": [] }, { - "query": "from a_index | eval log(-1, 20)", + "query": "from a_index | eval counterIntegerField IS not NULL", "error": [], - "warning": [ - "Log of a negative number results in null: -1" - ] + "warning": [] }, { - "query": "from a_index | eval log(-1, -20)", + "query": "from a_index | eval counterLongField IS NULL", "error": [], - "warning": [ - "Log of a negative number results in null: -1", - "Log of a negative number results in null: -20" - ] + "warning": [] }, { - "query": "from a_index | eval var0 = log(-1, -20)", + "query": "from a_index | eval counterLongField IS null", "error": [], - "warning": [ - "Log of a negative number results in null: -1", - "Log of a negative number results in null: -20" - ] + "warning": [] }, { - "query": "from a_index | eval numberField > 0", + "query": "from a_index | eval counterLongField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval NOT numberField > 0", + "query": "from a_index | eval counterLongField is NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField > 0)", + "query": "from a_index | eval counterLongField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval (NOT (numberField > 0))", + "query": "from a_index | eval counterLongField IS NOT null", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 > 0", + "query": "from a_index | eval counterLongField IS not NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField > stringField", + "query": "from a_index | eval counterDoubleField IS NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField > numberField", + "query": "from a_index | eval counterDoubleField IS null", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField > dateField", + "query": "from a_index | eval counterDoubleField is null", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField > booleanField", - "error": [ - "Argument of [>] must be [number], found value [booleanField] type [boolean]", - "Argument of [>] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval counterDoubleField is NULL", + "error": [], "warning": [] }, { - "query": "from a_index | eval ipField > ipField", + "query": "from a_index | eval counterDoubleField IS NOT NULL", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField > stringField", - "error": [ - "Argument of [>] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval counterDoubleField IS NOT null", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField > numberField", - "error": [ - "Argument of [>] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval counterDoubleField IS not NULL", + "error": [], "warning": [] }, { - "query": "from a_index | eval numberField > \"2022\"", - "error": [ - "Argument of [>] must be [number], found value [\"2022\"] type [string]" - ], + "query": "from a_index | eval - doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField > stringField", - "error": [ - "Argument of [>] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval a=- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField > dateField", - "error": [ - "Argument of [>] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval a=- round(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField > 0", - "error": [ - "Argument of [>] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval 1 + - doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField > now()", - "error": [ - "Argument of [>] must be [string], found value [now()] type [date]" - ], + "query": "from a_index | eval 1 - doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField > \"2022\"", + "query": "from a_index | eval - doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"2022\" > dateField", + "query": "from a_index | eval a=- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField > \"1.2.3\"", + "query": "from a_index | eval a=- round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval \"1.2.3\" > versionField", + "query": "from a_index | eval 1 + - doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField > \"true\"", - "error": [ - "Argument of [>] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval 1 - doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval \"true\" > booleanField", - "error": [ - "Argument of [>] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval + doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval ipField > \"136.36.3.205\"", + "query": "from a_index | eval a=+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"136.36.3.205\" > ipField", + "query": "from a_index | eval a=+ round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField >= 0", + "query": "from a_index | eval 1 + + doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval NOT numberField >= 0", + "query": "from a_index | eval 1 + doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField >= 0)", + "query": "from a_index | eval + doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval (NOT (numberField >= 0))", + "query": "from a_index | eval a=+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 >= 0", + "query": "from a_index | eval a=+ round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField >= stringField", + "query": "from a_index | eval 1 + + doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField >= numberField", + "query": "from a_index | eval 1 + doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField >= dateField", + "query": "from a_index | eval not booleanField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField >= booleanField", - "error": [ - "Argument of [>=] must be [number], found value [booleanField] type [boolean]", - "Argument of [>=] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval -- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval ipField >= ipField", + "query": "from a_index | eval a=-- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField >= stringField", - "error": [ - "Argument of [>=] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval a=-- round(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField >= numberField", - "error": [ - "Argument of [>=] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval 1 + -- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval numberField >= \"2022\"", - "error": [ - "Argument of [>=] must be [number], found value [\"2022\"] type [string]" - ], + "query": "from a_index | eval 1 -- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField >= stringField", - "error": [ - "Argument of [>=] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval -+ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField >= dateField", - "error": [ - "Argument of [>=] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval a=-+ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField >= 0", - "error": [ - "Argument of [>=] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval a=-+ round(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField >= now()", - "error": [ - "Argument of [>=] must be [string], found value [now()] type [date]" - ], + "query": "from a_index | eval 1 + -+ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField >= \"2022\"", + "query": "from a_index | eval 1 -+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"2022\" >= dateField", + "query": "from a_index | eval +- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField >= \"1.2.3\"", + "query": "from a_index | eval a=+- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"1.2.3\" >= versionField", + "query": "from a_index | eval a=+- round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField >= \"true\"", - "error": [ - "Argument of [>=] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval 1 + +- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval \"true\" >= booleanField", - "error": [ - "Argument of [>=] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval 1 +- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval ipField >= \"136.36.3.205\"", + "query": "from a_index | eval ++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"136.36.3.205\" >= ipField", + "query": "from a_index | eval a=++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField < 0", + "query": "from a_index | eval a=++ round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval NOT numberField < 0", + "query": "from a_index | eval 1 + ++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField < 0)", + "query": "from a_index | eval 1 ++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval (NOT (numberField < 0))", + "query": "from a_index | eval not not booleanField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 < 0", + "query": "from a_index | eval --- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField < stringField", + "query": "from a_index | eval a=--- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField < numberField", + "query": "from a_index | eval a=--- round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField < dateField", + "query": "from a_index | eval 1 + --- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField < booleanField", - "error": [ - "Argument of [<] must be [number], found value [booleanField] type [boolean]", - "Argument of [<] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval 1 --- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval ipField < ipField", + "query": "from a_index | eval -+- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField < stringField", - "error": [ - "Argument of [<] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval a=-+- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField < numberField", - "error": [ - "Argument of [<] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval a=-+- round(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval numberField < \"2022\"", - "error": [ - "Argument of [<] must be [number], found value [\"2022\"] type [string]" - ], + "query": "from a_index | eval 1 + -+- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField < stringField", - "error": [ - "Argument of [<] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval 1 -+- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField < dateField", - "error": [ - "Argument of [<] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval +-+ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField < 0", - "error": [ - "Argument of [<] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval a=+-+ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField < now()", - "error": [ - "Argument of [<] must be [string], found value [now()] type [date]" - ], + "query": "from a_index | eval a=+-+ round(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField < \"2022\"", + "query": "from a_index | eval 1 + +-+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"2022\" < dateField", + "query": "from a_index | eval 1 +-+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField < \"1.2.3\"", + "query": "from a_index | eval +++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"1.2.3\" < versionField", + "query": "from a_index | eval a=+++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField < \"true\"", - "error": [ - "Argument of [<] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval a=+++ round(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval \"true\" < booleanField", - "error": [ - "Argument of [<] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval 1 + +++ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval ipField < \"136.36.3.205\"", + "query": "from a_index | eval 1 +++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"136.36.3.205\" < ipField", + "query": "from a_index | eval not not not booleanField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField <= 0", + "query": "from a_index | eval ---- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval NOT numberField <= 0", + "query": "from a_index | eval a=---- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField <= 0)", + "query": "from a_index | eval a=---- round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval (NOT (numberField <= 0))", + "query": "from a_index | eval 1 + ---- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 <= 0", + "query": "from a_index | eval 1 ---- doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField <= stringField", + "query": "from a_index | eval -+-+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField <= numberField", + "query": "from a_index | eval a=-+-+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField <= dateField", + "query": "from a_index | eval a=-+-+ round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField <= booleanField", - "error": [ - "Argument of [<=] must be [number], found value [booleanField] type [boolean]", - "Argument of [<=] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval 1 + -+-+ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval ipField <= ipField", + "query": "from a_index | eval 1 -+-+ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField <= stringField", - "error": [ - "Argument of [<=] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval +-+- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField <= numberField", - "error": [ - "Argument of [<=] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval a=+-+- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval numberField <= \"2022\"", - "error": [ - "Argument of [<=] must be [number], found value [\"2022\"] type [string]" - ], + "query": "from a_index | eval a=+-+- round(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField <= stringField", - "error": [ - "Argument of [<=] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval 1 + +-+- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField <= dateField", - "error": [ - "Argument of [<=] must be [string], found value [dateField] type [date]" - ], + "query": "from a_index | eval 1 +-+- doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField <= 0", - "error": [ - "Argument of [<=] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval ++++ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval stringField <= now()", - "error": [ - "Argument of [<=] must be [string], found value [now()] type [date]" - ], + "query": "from a_index | eval a=++++ doubleField", + "error": [], "warning": [] }, { - "query": "from a_index | eval dateField <= \"2022\"", + "query": "from a_index | eval a=++++ round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval \"2022\" <= dateField", + "query": "from a_index | eval 1 + ++++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField <= \"1.2.3\"", + "query": "from a_index | eval 1 ++++ doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"1.2.3\" <= versionField", + "query": "from a_index | eval not not not not booleanField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField <= \"true\"", + "query": "from a_index | eval *+ doubleField", "error": [ - "Argument of [<=] must be [string], found value [booleanField] type [boolean]" + "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | eval \"true\" <= booleanField", + "query": "from a_index | eval /+ doubleField", "error": [ - "Argument of [<=] must be [string], found value [booleanField] type [boolean]" + "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | eval ipField <= \"136.36.3.205\"", - "error": [], + "query": "from a_index | eval %+ doubleField", + "error": [ + "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + ], "warning": [] }, { - "query": "from a_index | eval \"136.36.3.205\" <= ipField", + "query": "from a_index | eval log10(-1)", "error": [], - "warning": [] + "warning": [ + "Log of a negative number results in null: -1" + ] }, { - "query": "from a_index | eval numberField == 0", + "query": "from a_index | eval log(-1)", "error": [], - "warning": [] + "warning": [ + "Log of a negative number results in null: -1" + ] }, { - "query": "from a_index | eval NOT numberField == 0", + "query": "from a_index | eval log(-1, 20)", + "error": [], + "warning": [ + "Log of a negative number results in null: -1" + ] + }, + { + "query": "from a_index | eval log(-1, -20)", + "error": [], + "warning": [ + "Log of a negative number results in null: -1", + "Log of a negative number results in null: -20" + ] + }, + { + "query": "from a_index | eval var0 = log(-1, -20)", + "error": [], + "warning": [ + "Log of a negative number results in null: -1", + "Log of a negative number results in null: -20" + ] + }, + { + "query": "from a_index | eval doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField == 0)", + "query": "from a_index | eval NOT doubleField > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval (NOT (numberField == 0))", + "query": "from a_index | eval (doubleField > 0)", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 == 0", + "query": "from a_index | eval (NOT (doubleField > 0))", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField == stringField", + "query": "from a_index | eval 1 > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField == numberField", + "query": "from a_index | eval textField > textField", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField == dateField", + "query": "from a_index | eval doubleField > doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField == booleanField", + "query": "from a_index | eval dateField > dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField == ipField", + "query": "from a_index | eval booleanField > booleanField", + "error": [ + "Argument of [>] must be [date], found value [booleanField] type [boolean]", + "Argument of [>] must be [date], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval ipField > ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField == stringField", + "query": "from a_index | eval doubleField > textField", "error": [ - "Argument of [==] must be [number], found value [stringField] type [string]" + "Argument of [>] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval stringField == numberField", + "query": "from a_index | eval keywordField > doubleField", "error": [ - "Argument of [==] must be [number], found value [stringField] type [string]" + "Argument of [>] must be [double], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval numberField == \"2022\"", + "query": "from a_index | eval doubleField > \"2022\"", "error": [ - "Argument of [==] must be [number], found value [\"2022\"] type [string]" + "Argument of [>] must be [date], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval dateField == stringField", + "query": "from a_index | eval dateField > keywordField", "error": [ - "Argument of [==] must be [string], found value [dateField] type [date]" + "Argument of [>] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval stringField == dateField", + "query": "from a_index | eval keywordField > dateField", "error": [ - "Argument of [==] must be [string], found value [dateField] type [date]" + "Argument of [>] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval stringField == 0", + "query": "from a_index | eval textField > 0", "error": [ - "Argument of [==] must be [number], found value [stringField] type [string]" + "Argument of [>] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval stringField == now()", + "query": "from a_index | eval textField > now()", "error": [ - "Argument of [==] must be [string], found value [now()] type [date]" + "Argument of [>] must be [date], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval dateField == \"2022\"", + "query": "from a_index | eval dateField > \"2022\"", "error": [], "warning": [] }, { - "query": "from a_index | eval \"2022\" == dateField", + "query": "from a_index | eval \"2022\" > dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField == \"1.2.3\"", + "query": "from a_index | eval versionField > \"1.2.3\"", "error": [], "warning": [] }, { - "query": "from a_index | eval \"1.2.3\" == versionField", + "query": "from a_index | eval \"1.2.3\" > versionField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField == \"true\"", - "error": [], + "query": "from a_index | eval booleanField > \"true\"", + "error": [ + "Argument of [>] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval \"true\" == booleanField", - "error": [], + "query": "from a_index | eval \"true\" > booleanField", + "error": [ + "Argument of [>] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval ipField == \"136.36.3.205\"", + "query": "from a_index | eval ipField > \"136.36.3.205\"", "error": [], "warning": [] }, { - "query": "from a_index | eval \"136.36.3.205\" == ipField", + "query": "from a_index | eval \"136.36.3.205\" > ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField != 0", + "query": "from a_index | eval doubleField >= 0", "error": [], "warning": [] }, { - "query": "from a_index | eval NOT numberField != 0", + "query": "from a_index | eval NOT doubleField >= 0", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField != 0)", + "query": "from a_index | eval (doubleField >= 0)", "error": [], "warning": [] }, { - "query": "from a_index | eval (NOT (numberField != 0))", + "query": "from a_index | eval (NOT (doubleField >= 0))", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 != 0", + "query": "from a_index | eval 1 >= 0", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField != stringField", + "query": "from a_index | eval textField >= textField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField != numberField", + "query": "from a_index | eval doubleField >= doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField != dateField", + "query": "from a_index | eval dateField >= dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField != booleanField", - "error": [], + "query": "from a_index | eval booleanField >= booleanField", + "error": [ + "Argument of [>=] must be [date], found value [booleanField] type [boolean]", + "Argument of [>=] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval ipField != ipField", + "query": "from a_index | eval ipField >= ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField != stringField", + "query": "from a_index | eval doubleField >= textField", "error": [ - "Argument of [!=] must be [number], found value [stringField] type [string]" + "Argument of [>=] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval stringField != numberField", + "query": "from a_index | eval keywordField >= doubleField", "error": [ - "Argument of [!=] must be [number], found value [stringField] type [string]" + "Argument of [>=] must be [double], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval numberField != \"2022\"", + "query": "from a_index | eval doubleField >= \"2022\"", "error": [ - "Argument of [!=] must be [number], found value [\"2022\"] type [string]" + "Argument of [>=] must be [date], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval dateField != stringField", + "query": "from a_index | eval dateField >= keywordField", "error": [ - "Argument of [!=] must be [string], found value [dateField] type [date]" + "Argument of [>=] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval stringField != dateField", + "query": "from a_index | eval keywordField >= dateField", "error": [ - "Argument of [!=] must be [string], found value [dateField] type [date]" + "Argument of [>=] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval stringField != 0", + "query": "from a_index | eval textField >= 0", "error": [ - "Argument of [!=] must be [number], found value [stringField] type [string]" + "Argument of [>=] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval stringField != now()", + "query": "from a_index | eval textField >= now()", "error": [ - "Argument of [!=] must be [string], found value [now()] type [date]" + "Argument of [>=] must be [date], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval dateField != \"2022\"", + "query": "from a_index | eval dateField >= \"2022\"", "error": [], "warning": [] }, { - "query": "from a_index | eval \"2022\" != dateField", + "query": "from a_index | eval \"2022\" >= dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField != \"1.2.3\"", + "query": "from a_index | eval versionField >= \"1.2.3\"", "error": [], "warning": [] }, { - "query": "from a_index | eval \"1.2.3\" != versionField", + "query": "from a_index | eval \"1.2.3\" >= versionField", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField != \"true\"", - "error": [], + "query": "from a_index | eval booleanField >= \"true\"", + "error": [ + "Argument of [>=] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval \"true\" != booleanField", + "query": "from a_index | eval \"true\" >= booleanField", + "error": [ + "Argument of [>=] must be [date], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval ipField >= \"136.36.3.205\"", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField != \"136.36.3.205\"", + "query": "from a_index | eval \"136.36.3.205\" >= ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval \"136.36.3.205\" != ipField", + "query": "from a_index | eval doubleField < 0", "error": [], "warning": [] }, { - "query": "from a_index | eval versionField in (\"1.2.3\", \"4.5.6\", to_version(\"2.3.2\"))", + "query": "from a_index | eval NOT doubleField < 0", "error": [], "warning": [] }, { - "query": "from a_index | eval dateField in (\"2023-12-12\", \"2024-12-12\", date_parse(\"yyyy-MM-dd\", \"2025-12-12\"))", + "query": "from a_index | eval (doubleField < 0)", "error": [], "warning": [] }, { - "query": "from a_index | eval booleanField in (\"true\", \"false\", false)", + "query": "from a_index | eval (NOT (doubleField < 0))", "error": [], "warning": [] }, { - "query": "from a_index | eval ipField in (\"136.36.3.205\", \"136.36.3.206\", to_ip(\"136.36.3.207\"))", + "query": "from a_index | eval 1 < 0", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField + 1", + "query": "from a_index | eval textField < textField", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField + 1)", + "query": "from a_index | eval doubleField < doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 + 1", + "query": "from a_index | eval dateField < dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval now() + now()", + "query": "from a_index | eval booleanField < booleanField", "error": [ - "Argument of [+] must be [time_literal], found value [now()] type [date]" + "Argument of [<] must be [date], found value [booleanField] type [boolean]", + "Argument of [<] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval 1 + \"1\"", - "error": [ - "Argument of [+] must be [number], found value [\"1\"] type [string]" - ], + "query": "from a_index | eval ipField < ipField", + "error": [], "warning": [] }, { - "query": "from a_index | eval \"1\" + 1", + "query": "from a_index | eval doubleField < textField", "error": [ - "Argument of [+] must be [number], found value [\"1\"] type [string]" + "Argument of [<] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval numberField - 1", - "error": [], + "query": "from a_index | eval keywordField < doubleField", + "error": [ + "Argument of [<] must be [double], found value [keywordField] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | eval (numberField - 1)", - "error": [], + "query": "from a_index | eval doubleField < \"2022\"", + "error": [ + "Argument of [<] must be [date], found value [doubleField] type [double]" + ], "warning": [] }, { - "query": "from a_index | eval 1 - 1", - "error": [], + "query": "from a_index | eval dateField < keywordField", + "error": [ + "Argument of [<] must be [date], found value [keywordField] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | eval now() - now()", + "query": "from a_index | eval keywordField < dateField", "error": [ - "Argument of [-] must be [time_literal], found value [now()] type [date]" + "Argument of [<] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval 1 - \"1\"", + "query": "from a_index | eval textField < 0", "error": [ - "Argument of [-] must be [number], found value [\"1\"] type [string]" + "Argument of [<] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval \"1\" - 1", + "query": "from a_index | eval textField < now()", "error": [ - "Argument of [-] must be [number], found value [\"1\"] type [string]" + "Argument of [<] must be [date], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval numberField * 1", + "query": "from a_index | eval dateField < \"2022\"", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField * 1)", + "query": "from a_index | eval \"2022\" < dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 * 1", + "query": "from a_index | eval versionField < \"1.2.3\"", "error": [], "warning": [] }, { - "query": "from a_index | eval now() * now()", - "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [now()] type [date]" - ], + "query": "from a_index | eval \"1.2.3\" < versionField", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 * \"1\"", + "query": "from a_index | eval booleanField < \"true\"", "error": [ - "Argument of [*] must be [number], found value [\"1\"] type [string]" + "Argument of [<] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval \"1\" * 1", + "query": "from a_index | eval \"true\" < booleanField", "error": [ - "Argument of [*] must be [number], found value [\"1\"] type [string]" + "Argument of [<] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval numberField / 1", + "query": "from a_index | eval ipField < \"136.36.3.205\"", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField / 1)", + "query": "from a_index | eval \"136.36.3.205\" < ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 / 1", + "query": "from a_index | eval doubleField <= 0", "error": [], "warning": [] }, { - "query": "from a_index | eval now() / now()", - "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [now()] type [date]" - ], + "query": "from a_index | eval NOT doubleField <= 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 / \"1\"", - "error": [ - "Argument of [/] must be [number], found value [\"1\"] type [string]" - ], + "query": "from a_index | eval (doubleField <= 0)", + "error": [], "warning": [] }, { - "query": "from a_index | eval \"1\" / 1", - "error": [ - "Argument of [/] must be [number], found value [\"1\"] type [string]" - ], + "query": "from a_index | eval (NOT (doubleField <= 0))", + "error": [], "warning": [] }, { - "query": "from a_index | eval numberField % 1", + "query": "from a_index | eval 1 <= 0", "error": [], "warning": [] }, { - "query": "from a_index | eval (numberField % 1)", + "query": "from a_index | eval textField <= textField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 % 1", + "query": "from a_index | eval doubleField <= doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval now() % now()", + "query": "from a_index | eval dateField <= dateField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval booleanField <= booleanField", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [now()] type [date]" + "Argument of [<=] must be [date], found value [booleanField] type [boolean]", + "Argument of [<=] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval 1 % \"1\"", + "query": "from a_index | eval ipField <= ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval doubleField <= textField", "error": [ - "Argument of [%] must be [number], found value [\"1\"] type [string]" + "Argument of [<=] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval \"1\" % 1", + "query": "from a_index | eval keywordField <= doubleField", "error": [ - "Argument of [%] must be [number], found value [\"1\"] type [string]" + "Argument of [<=] must be [double], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval 1/0", - "error": [], - "warning": [ - "Cannot divide by zero: 1/0" - ] + "query": "from a_index | eval doubleField <= \"2022\"", + "error": [ + "Argument of [<=] must be [date], found value [doubleField] type [double]" + ], + "warning": [] }, { - "query": "from a_index | eval var = 1/0", - "error": [], - "warning": [ - "Cannot divide by zero: 1/0" - ] + "query": "from a_index | eval dateField <= keywordField", + "error": [ + "Argument of [<=] must be [date], found value [keywordField] type [keyword]" + ], + "warning": [] }, { - "query": "from a_index | eval 1 + 1/0", - "error": [], - "warning": [ - "Cannot divide by zero: 1/0" - ] + "query": "from a_index | eval keywordField <= dateField", + "error": [ + "Argument of [<=] must be [date], found value [keywordField] type [keyword]" + ], + "warning": [] }, { - "query": "from a_index | eval 1%0", - "error": [], - "warning": [ - "Module by zero can return null value: 1%0" - ] - }, - { - "query": "from a_index | eval var = 1%0", - "error": [], - "warning": [ - "Module by zero can return null value: 1%0" - ] - }, - { - "query": "from a_index | eval 1 + 1%0", - "error": [], - "warning": [ - "Module by zero can return null value: 1%0" - ] - }, - { - "query": "from a_index | eval stringField like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval stringField NOT like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval NOT stringField like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval NOT stringField NOT like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval numberField like \"?a\"", - "error": [ - "Argument of [like] must be [string], found value [numberField] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | eval numberField NOT like \"?a\"", - "error": [ - "Argument of [not_like] must be [string], found value [numberField] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | eval NOT numberField like \"?a\"", + "query": "from a_index | eval textField <= 0", "error": [ - "Argument of [like] must be [string], found value [numberField] type [number]" + "Argument of [<=] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval NOT numberField NOT like \"?a\"", + "query": "from a_index | eval textField <= now()", "error": [ - "Argument of [not_like] must be [string], found value [numberField] type [number]" + "Argument of [<=] must be [date], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval stringField rlike \"?a\"", + "query": "from a_index | eval dateField <= \"2022\"", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField NOT rlike \"?a\"", + "query": "from a_index | eval \"2022\" <= dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval NOT stringField rlike \"?a\"", + "query": "from a_index | eval versionField <= \"1.2.3\"", "error": [], "warning": [] }, { - "query": "from a_index | eval NOT stringField NOT rlike \"?a\"", + "query": "from a_index | eval \"1.2.3\" <= versionField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField rlike \"?a\"", - "error": [ - "Argument of [rlike] must be [string], found value [numberField] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | eval numberField NOT rlike \"?a\"", - "error": [ - "Argument of [not_rlike] must be [string], found value [numberField] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | eval NOT numberField rlike \"?a\"", + "query": "from a_index | eval booleanField <= \"true\"", "error": [ - "Argument of [rlike] must be [string], found value [numberField] type [number]" + "Argument of [<=] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval NOT numberField NOT rlike \"?a\"", + "query": "from a_index | eval \"true\" <= booleanField", "error": [ - "Argument of [not_rlike] must be [string], found value [numberField] type [number]" + "Argument of [<=] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval 1 in (1, 2, 3)", + "query": "from a_index | eval ipField <= \"136.36.3.205\"", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField in (1, 2, 3)", + "query": "from a_index | eval \"136.36.3.205\" <= ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField not in (1, 2, 3)", + "query": "from a_index | eval doubleField == 0", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField not in (1, 2, 3, numberField)", + "query": "from a_index | eval NOT doubleField == 0", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 in (1, 2, 3, round(numberField))", + "query": "from a_index | eval (doubleField == 0)", "error": [], "warning": [] }, { - "query": "from a_index | eval \"a\" in (\"a\", \"b\", \"c\")", + "query": "from a_index | eval (NOT (doubleField == 0))", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField in (\"a\", \"b\", \"c\")", + "query": "from a_index | eval 1 == 0", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField not in (\"a\", \"b\", \"c\")", + "query": "from a_index | eval textField == textField", "error": [], "warning": [] }, { - "query": "from a_index | eval stringField not in (\"a\", \"b\", \"c\", stringField)", + "query": "from a_index | eval doubleField == doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 in (\"a\", \"b\", \"c\")", + "query": "from a_index | eval dateField == dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField in (\"a\", \"b\", \"c\")", + "query": "from a_index | eval booleanField == booleanField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField not in (\"a\", \"b\", \"c\")", + "query": "from a_index | eval ipField == ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval numberField not in (1, 2, 3, stringField)", - "error": [], + "query": "from a_index | eval doubleField == textField", + "error": [ + "Argument of [==] must be [double], found value [textField] type [text]" + ], "warning": [] }, { - "query": "from a_index | eval avg(numberField)", + "query": "from a_index | eval keywordField == doubleField", "error": [ - "EVAL does not support function avg" + "Argument of [==] must be [double], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | stats avg(numberField) | eval `avg(numberField)` + 1", - "error": [], + "query": "from a_index | eval doubleField == \"2022\"", + "error": [ + "Argument of [==] must be [date], found value [doubleField] type [double]" + ], "warning": [] }, { - "query": "from a_index | eval not", + "query": "from a_index | eval dateField == keywordField", "error": [ - "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - "Error: [not] function expects exactly one argument, got 0." + "Argument of [==] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval in", + "query": "from a_index | eval keywordField == dateField", "error": [ - "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "Argument of [==] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval stringField in stringField", + "query": "from a_index | eval textField == 0", "error": [ - "SyntaxError: missing '(' at 'stringField'", - "SyntaxError: mismatched input '<EOF>' expecting {',', ')'}" + "Argument of [==] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval stringField in stringField)", + "query": "from a_index | eval textField == now()", "error": [ - "SyntaxError: missing '(' at 'stringField'", - "Error: [in] function expects exactly 2 arguments, got 1." + "Argument of [==] must be [date], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval stringField not in stringField", - "error": [ - "SyntaxError: missing '(' at 'stringField'", - "SyntaxError: mismatched input '<EOF>' expecting {',', ')'}" - ], + "query": "from a_index | eval dateField == \"2022\"", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort([\"a\", \"b\"], \"bogus\")", + "query": "from a_index | eval \"2022\" == dateField", "error": [], - "warning": [ - "Invalid option [\"bogus\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." - ] + "warning": [] }, { - "query": "from a_index | eval mv_sort([\"a\", \"b\"], \"ASC\")", + "query": "from a_index | eval versionField == \"1.2.3\"", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort([\"a\", \"b\"], \"DESC\")", + "query": "from a_index | eval \"1.2.3\" == versionField", "error": [], "warning": [] }, { - "query": "from a_index | eval result = case(false, 0, 1), round(result)", + "query": "from a_index | eval booleanField == \"true\"", "error": [], "warning": [] }, { - "query": "from a_index | eval result = case(false, 0, 1) | stats sum(result)", + "query": "from a_index | eval \"true\" == booleanField", "error": [], "warning": [] }, { - "query": "from a_index | eval result = case(false, 0, 1) | stats var0 = sum(result)", + "query": "from a_index | eval ipField == \"136.36.3.205\"", "error": [], "warning": [] }, { - "query": "from a_index | eval round(case(false, 0, 1))", + "query": "from a_index | eval \"136.36.3.205\" == ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 anno", - "error": [ - "EVAL does not support [date_period] in expression [1 anno]" - ], + "query": "from a_index | eval doubleField != 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = 1 anno", - "error": [ - "Unexpected time interval qualifier: 'anno'" - ], + "query": "from a_index | eval NOT doubleField != 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval now() + 1 anno", - "error": [ - "Unexpected time interval qualifier: 'anno'" - ], + "query": "from a_index | eval (doubleField != 0)", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 year", - "error": [ - "EVAL does not support [date_period] in expression [1 year]" - ], + "query": "from a_index | eval (NOT (doubleField != 0))", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 year", - "error": [ - "EVAL does not support [date_period] in expression [1 year]" - ], + "query": "from a_index | eval 1 != 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 year", + "query": "from a_index | eval textField != textField", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 year", + "query": "from a_index | eval doubleField != doubleField", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 YEAR", + "query": "from a_index | eval dateField != dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Year", + "query": "from a_index | eval booleanField != booleanField", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 year", + "query": "from a_index | eval ipField != ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 year + 1 year", + "query": "from a_index | eval doubleField != textField", "error": [ - "Argument of [+] must be [date], found value [1 year] type [duration]" + "Argument of [!=] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 year", + "query": "from a_index | eval keywordField != doubleField", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 year] type [duration]" + "Argument of [!=] must be [double], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 year", + "query": "from a_index | eval doubleField != \"2022\"", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 year] type [duration]" + "Argument of [!=] must be [date], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 year", + "query": "from a_index | eval dateField != keywordField", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 year] type [duration]" + "Argument of [!=] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval 1 years", + "query": "from a_index | eval keywordField != dateField", "error": [ - "EVAL does not support [date_period] in expression [1 years]" + "Argument of [!=] must be [date], found value [keywordField] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval 1 years", + "query": "from a_index | eval textField != 0", "error": [ - "EVAL does not support [date_period] in expression [1 years]" + "Argument of [!=] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 years", - "error": [], + "query": "from a_index | eval textField != now()", + "error": [ + "Argument of [!=] must be [date], found value [textField] type [text]" + ], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 years", + "query": "from a_index | eval dateField != \"2022\"", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 YEARS", + "query": "from a_index | eval \"2022\" != dateField", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Years", + "query": "from a_index | eval versionField != \"1.2.3\"", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 years", + "query": "from a_index | eval \"1.2.3\" != versionField", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 years + 1 year", - "error": [ - "Argument of [+] must be [date], found value [1 years] type [duration]" - ], + "query": "from a_index | eval booleanField != \"true\"", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 years", - "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 years] type [duration]" - ], + "query": "from a_index | eval \"true\" != booleanField", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 years", - "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 years] type [duration]" - ], + "query": "from a_index | eval ipField != \"136.36.3.205\"", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 years", - "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 years] type [duration]" - ], + "query": "from a_index | eval \"136.36.3.205\" != ipField", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 quarter", - "error": [ - "EVAL does not support [date_period] in expression [1 quarter]" - ], + "query": "from a_index | eval versionField in (\"1.2.3\", \"4.5.6\", to_version(\"2.3.2\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 quarter", - "error": [ - "EVAL does not support [date_period] in expression [1 quarter]" - ], + "query": "from a_index | eval dateField in (\"2023-12-12\", \"2024-12-12\", date_parse(\"yyyy-MM-dd\", \"2025-12-12\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 quarter", + "query": "from a_index | eval booleanField in (\"true\", \"false\", false)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 quarter", + "query": "from a_index | eval ipField in (\"136.36.3.205\", \"136.36.3.206\", to_ip(\"136.36.3.207\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 QUARTER", + "query": "from a_index | eval doubleField + 1", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Quarter", + "query": "from a_index | eval (doubleField + 1)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 quarter", + "query": "from a_index | eval 1 + 1", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 quarter + 1 year", + "query": "from a_index | eval now() + now()", "error": [ - "Argument of [+] must be [date], found value [1 quarter] type [duration]" + "Argument of [+] must be [date_period], found value [now()] type [date]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 quarter", + "query": "from a_index | eval 1 + \"1\"", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 quarter] type [duration]" + "Argument of [+] must be [date_period], found value [1] type [integer]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 quarter", + "query": "from a_index | eval \"1\" + 1", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 quarter] type [duration]" + "Argument of [+] must be [date_period], found value [1] type [integer]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 quarter", - "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 quarter] type [duration]" - ], + "query": "from a_index | eval doubleField - 1", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 quarters", - "error": [ - "EVAL does not support [date_period] in expression [1 quarters]" - ], + "query": "from a_index | eval (doubleField - 1)", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 quarters", + "query": "from a_index | eval 1 - 1", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval now() - now()", "error": [ - "EVAL does not support [date_period] in expression [1 quarters]" + "Argument of [-] must be [date_period], found value [now()] type [date]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 quarters", - "error": [], + "query": "from a_index | eval 1 - \"1\"", + "error": [ + "Argument of [-] must be [date_period], found value [1] type [integer]" + ], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 quarters", - "error": [], + "query": "from a_index | eval \"1\" - 1", + "error": [ + "Argument of [-] must be [date_period], found value [1] type [integer]" + ], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 QUARTERS", + "query": "from a_index | eval doubleField * 1", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Quarters", + "query": "from a_index | eval (doubleField * 1)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 quarters", + "query": "from a_index | eval 1 * 1", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 quarters + 1 year", + "query": "from a_index | eval now() * now()", "error": [ - "Argument of [+] must be [date], found value [1 quarters] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [now()] type [date]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 quarters", + "query": "from a_index | eval 1 * \"1\"", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 quarters] type [duration]" + "Argument of [*] must be [double], found value [\"1\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 quarters", + "query": "from a_index | eval \"1\" * 1", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 quarters] type [duration]" + "Argument of [*] must be [double], found value [\"1\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 quarters", - "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 quarters] type [duration]" - ], + "query": "from a_index | eval doubleField / 1", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 month", - "error": [ - "EVAL does not support [date_period] in expression [1 month]" - ], + "query": "from a_index | eval (doubleField / 1)", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 month", + "query": "from a_index | eval 1 / 1", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval now() / now()", "error": [ - "EVAL does not support [date_period] in expression [1 month]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [now()] type [date]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 month", - "error": [], + "query": "from a_index | eval 1 / \"1\"", + "error": [ + "Argument of [/] must be [double], found value [\"1\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 month", - "error": [], + "query": "from a_index | eval \"1\" / 1", + "error": [ + "Argument of [/] must be [double], found value [\"1\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 MONTH", + "query": "from a_index | eval doubleField % 1", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Month", + "query": "from a_index | eval (doubleField % 1)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 month", + "query": "from a_index | eval 1 % 1", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 month + 1 year", + "query": "from a_index | eval now() % now()", "error": [ - "Argument of [+] must be [date], found value [1 month] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [now()] type [date]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 month", + "query": "from a_index | eval 1 % \"1\"", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 month] type [duration]" + "Argument of [%] must be [double], found value [\"1\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 month", + "query": "from a_index | eval \"1\" % 1", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 month] type [duration]" + "Argument of [%] must be [double], found value [\"1\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 month", - "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 month] type [duration]" - ], - "warning": [] + "query": "from a_index | eval 1/0", + "error": [], + "warning": [ + "Cannot divide by zero: 1/0" + ] }, { - "query": "from a_index | eval 1 months", - "error": [ - "EVAL does not support [date_period] in expression [1 months]" - ], - "warning": [] + "query": "from a_index | eval var = 1/0", + "error": [], + "warning": [ + "Cannot divide by zero: 1/0" + ] }, { - "query": "from a_index | eval 1 months", - "error": [ - "EVAL does not support [date_period] in expression [1 months]" - ], - "warning": [] + "query": "from a_index | eval 1 + 1/0", + "error": [], + "warning": [ + "Cannot divide by zero: 1/0" + ] }, { - "query": "from a_index | eval var = now() - 1 months", + "query": "from a_index | eval 1%0", "error": [], - "warning": [] + "warning": [ + "Module by zero can return null value: 1%0" + ] }, { - "query": "from a_index | eval var = dateField - 1 months", + "query": "from a_index | eval var = 1%0", "error": [], - "warning": [] + "warning": [ + "Module by zero can return null value: 1%0" + ] }, { - "query": "from a_index | eval var = dateField - 1 MONTHS", + "query": "from a_index | eval 1 + 1%0", "error": [], - "warning": [] + "warning": [ + "Module by zero can return null value: 1%0" + ] }, { - "query": "from a_index | eval var = dateField - 1 Months", + "query": "from a_index | eval textField like \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 months", + "query": "from a_index | eval textField NOT like \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 months + 1 year", - "error": [ - "Argument of [+] must be [date], found value [1 months] type [duration]" - ], + "query": "from a_index | eval NOT textField like \"?a\"", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 months", - "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 months] type [duration]" - ], + "query": "from a_index | eval NOT textField NOT like \"?a\"", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 months", + "query": "from a_index | eval doubleField like \"?a\"", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 months] type [duration]" + "Argument of [like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 months", + "query": "from a_index | eval doubleField NOT like \"?a\"", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 months] type [duration]" + "Argument of [not_like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval 1 week", + "query": "from a_index | eval NOT doubleField like \"?a\"", "error": [ - "EVAL does not support [date_period] in expression [1 week]" + "Argument of [like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval 1 week", + "query": "from a_index | eval NOT doubleField NOT like \"?a\"", "error": [ - "EVAL does not support [date_period] in expression [1 week]" + "Argument of [not_like] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 week", + "query": "from a_index | eval textField rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 week", + "query": "from a_index | eval textField NOT rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 WEEK", + "query": "from a_index | eval NOT textField rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Week", + "query": "from a_index | eval NOT textField NOT rlike \"?a\"", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 week", - "error": [], + "query": "from a_index | eval doubleField rlike \"?a\"", + "error": [ + "Argument of [rlike] must be [text], found value [doubleField] type [double]" + ], "warning": [] }, { - "query": "from a_index | eval 1 week + 1 year", + "query": "from a_index | eval doubleField NOT rlike \"?a\"", "error": [ - "Argument of [+] must be [date], found value [1 week] type [duration]" + "Argument of [not_rlike] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 week", + "query": "from a_index | eval NOT doubleField rlike \"?a\"", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 week] type [duration]" + "Argument of [rlike] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 week", + "query": "from a_index | eval NOT doubleField NOT rlike \"?a\"", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 week] type [duration]" + "Argument of [not_rlike] must be [text], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 week", - "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 week] type [duration]" - ], + "query": "from a_index | eval 1 in (1, 2, 3)", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 weeks", - "error": [ - "EVAL does not support [date_period] in expression [1 weeks]" - ], + "query": "from a_index | eval doubleField in (1, 2, 3)", + "error": [], "warning": [] }, { - "query": "from a_index | eval 1 weeks", - "error": [ - "EVAL does not support [date_period] in expression [1 weeks]" - ], + "query": "from a_index | eval doubleField not in (1, 2, 3)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 weeks", + "query": "from a_index | eval doubleField not in (1, 2, 3, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 weeks", + "query": "from a_index | eval 1 in (1, 2, 3, round(doubleField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 WEEKS", + "query": "from a_index | eval \"a\" in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Weeks", + "query": "from a_index | eval textField in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 weeks", + "query": "from a_index | eval textField not in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 weeks + 1 year", + "query": "from a_index | eval textField not in (\"a\", \"b\", \"c\", textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval 1 in (\"a\", \"b\", \"c\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval doubleField in (\"a\", \"b\", \"c\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval doubleField not in (\"a\", \"b\", \"c\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval doubleField not in (1, 2, 3, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval avg(doubleField)", "error": [ - "Argument of [+] must be [date], found value [1 weeks] type [duration]" + "EVAL does not support function avg" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 weeks", + "query": "from a_index | stats avg(doubleField) | eval `avg(doubleField)` + 1", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval not", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 weeks] type [duration]" + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "Error: [not] function expects exactly one argument, got 0." ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 weeks", + "query": "from a_index | eval in", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 weeks] type [duration]" + "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 weeks", + "query": "from a_index | eval textField in textField", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 weeks] type [duration]" + "SyntaxError: missing '(' at 'textField'", + "SyntaxError: mismatched input '<EOF>' expecting {',', ')'}" ], "warning": [] }, { - "query": "from a_index | eval 1 day", + "query": "from a_index | eval textField in textField)", "error": [ - "EVAL does not support [date_period] in expression [1 day]" + "SyntaxError: missing '(' at 'textField'", + "Error: [in] function expects exactly 2 arguments, got 1." ], "warning": [] }, { - "query": "from a_index | eval 1 day", + "query": "from a_index | eval textField not in textField", "error": [ - "EVAL does not support [date_period] in expression [1 day]" + "SyntaxError: missing '(' at 'textField'", + "SyntaxError: mismatched input '<EOF>' expecting {',', ')'}" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 day", + "query": "from a_index | eval mv_sort([\"a\", \"b\"], \"bogus\")", + "error": [], + "warning": [ + "Invalid option [\"bogus\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] + }, + { + "query": "from a_index | eval mv_sort([\"a\", \"b\"], \"ASC\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 day", + "query": "from a_index | eval mv_sort([\"a\", \"b\"], \"DESC\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 DAY", + "query": "from a_index | eval result = case(false, 0, 1), round(result)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Day", + "query": "from a_index | eval result = case(false, 0, 1) | stats sum(result)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 day", + "query": "from a_index | eval result = case(false, 0, 1) | stats var0 = sum(result)", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 day + 1 year", - "error": [ - "Argument of [+] must be [date], found value [1 day] type [duration]" - ], + "query": "from a_index | eval round(case(false, 0, 1))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 day", + "query": "from a_index | eval 1 anno", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 day] type [duration]" + "EVAL does not support [date_period] in expression [1 anno]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 day", + "query": "from a_index | eval var = 1 anno", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 day] type [duration]" + "Unexpected time interval qualifier: 'anno'" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 day", + "query": "from a_index | eval now() + 1 anno", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 day] type [duration]" + "Unexpected time interval qualifier: 'anno'" ], "warning": [] }, { - "query": "from a_index | eval 1 days", + "query": "from a_index | eval 1 year", "error": [ - "EVAL does not support [date_period] in expression [1 days]" + "EVAL does not support [date_period] in expression [1 year]" ], "warning": [] }, { - "query": "from a_index | eval 1 days", + "query": "from a_index | eval 1 year", "error": [ - "EVAL does not support [date_period] in expression [1 days]" + "EVAL does not support [date_period] in expression [1 year]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 days", + "query": "from a_index | eval var = now() - 1 year", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 days", + "query": "from a_index | eval var = dateField - 1 year", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 DAYS", + "query": "from a_index | eval var = dateField - 1 YEAR", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Days", + "query": "from a_index | eval var = dateField - 1 Year", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 days", + "query": "from a_index | eval var = dateField + 1 year", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 days + 1 year", + "query": "from a_index | eval 1 year + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 days] type [duration]" + "Argument of [+] must be [date], found value [1 year] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 days", + "query": "from a_index | eval var = now() * 1 year", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 days] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 year] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 days", + "query": "from a_index | eval var = now() / 1 year", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 days] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 year] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 days", + "query": "from a_index | eval var = now() % 1 year", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 days] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 year] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 hour", + "query": "from a_index | eval 1 years", "error": [ - "EVAL does not support [date_period] in expression [1 hour]" + "EVAL does not support [date_period] in expression [1 years]" ], "warning": [] }, { - "query": "from a_index | eval 1 hour", + "query": "from a_index | eval 1 years", "error": [ - "EVAL does not support [date_period] in expression [1 hour]" + "EVAL does not support [date_period] in expression [1 years]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 hour", + "query": "from a_index | eval var = now() - 1 years", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 hour", + "query": "from a_index | eval var = dateField - 1 years", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 HOUR", + "query": "from a_index | eval var = dateField - 1 YEARS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Hour", + "query": "from a_index | eval var = dateField - 1 Years", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 hour", + "query": "from a_index | eval var = dateField + 1 years", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 hour + 1 year", + "query": "from a_index | eval 1 years + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 hour] type [duration]" + "Argument of [+] must be [date], found value [1 years] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 hour", + "query": "from a_index | eval var = now() * 1 years", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 hour] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 years] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 hour", + "query": "from a_index | eval var = now() / 1 years", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 hour] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 years] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 hour", + "query": "from a_index | eval var = now() % 1 years", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 hour] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 years] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 hours", + "query": "from a_index | eval 1 quarter", "error": [ - "EVAL does not support [date_period] in expression [1 hours]" + "EVAL does not support [date_period] in expression [1 quarter]" ], "warning": [] }, { - "query": "from a_index | eval 1 hours", + "query": "from a_index | eval 1 quarter", "error": [ - "EVAL does not support [date_period] in expression [1 hours]" + "EVAL does not support [date_period] in expression [1 quarter]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 hours", + "query": "from a_index | eval var = now() - 1 quarter", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 hours", + "query": "from a_index | eval var = dateField - 1 quarter", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 HOURS", + "query": "from a_index | eval var = dateField - 1 QUARTER", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Hours", + "query": "from a_index | eval var = dateField - 1 Quarter", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 hours", + "query": "from a_index | eval var = dateField + 1 quarter", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 hours + 1 year", + "query": "from a_index | eval 1 quarter + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 hours] type [duration]" + "Argument of [+] must be [date], found value [1 quarter] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 hours", + "query": "from a_index | eval var = now() * 1 quarter", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 hours] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 quarter] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 hours", + "query": "from a_index | eval var = now() / 1 quarter", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 hours] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 quarter] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 hours", + "query": "from a_index | eval var = now() % 1 quarter", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 hours] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 quarter] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 minute", + "query": "from a_index | eval 1 quarters", "error": [ - "EVAL does not support [date_period] in expression [1 minute]" + "EVAL does not support [date_period] in expression [1 quarters]" ], "warning": [] }, { - "query": "from a_index | eval 1 minute", + "query": "from a_index | eval 1 quarters", "error": [ - "EVAL does not support [date_period] in expression [1 minute]" + "EVAL does not support [date_period] in expression [1 quarters]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 minute", + "query": "from a_index | eval var = now() - 1 quarters", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 minute", + "query": "from a_index | eval var = dateField - 1 quarters", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 MINUTE", + "query": "from a_index | eval var = dateField - 1 QUARTERS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Minute", + "query": "from a_index | eval var = dateField - 1 Quarters", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 minute", + "query": "from a_index | eval var = dateField + 1 quarters", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 minute + 1 year", + "query": "from a_index | eval 1 quarters + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 minute] type [duration]" + "Argument of [+] must be [date], found value [1 quarters] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 minute", + "query": "from a_index | eval var = now() * 1 quarters", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 minute] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 quarters] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 minute", + "query": "from a_index | eval var = now() / 1 quarters", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 minute] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 quarters] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 minute", + "query": "from a_index | eval var = now() % 1 quarters", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 minute] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 quarters] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 minutes", + "query": "from a_index | eval 1 month", "error": [ - "EVAL does not support [date_period] in expression [1 minutes]" + "EVAL does not support [date_period] in expression [1 month]" ], "warning": [] }, { - "query": "from a_index | eval 1 minutes", + "query": "from a_index | eval 1 month", "error": [ - "EVAL does not support [date_period] in expression [1 minutes]" + "EVAL does not support [date_period] in expression [1 month]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 minutes", + "query": "from a_index | eval var = now() - 1 month", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 minutes", + "query": "from a_index | eval var = dateField - 1 month", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 MINUTES", + "query": "from a_index | eval var = dateField - 1 MONTH", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Minutes", + "query": "from a_index | eval var = dateField - 1 Month", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 minutes", + "query": "from a_index | eval var = dateField + 1 month", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 minutes + 1 year", + "query": "from a_index | eval 1 month + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 minutes] type [duration]" + "Argument of [+] must be [date], found value [1 month] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 minutes", + "query": "from a_index | eval var = now() * 1 month", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 minutes] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 month] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 minutes", + "query": "from a_index | eval var = now() / 1 month", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 minutes] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 month] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 minutes", + "query": "from a_index | eval var = now() % 1 month", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 minutes] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 month] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 second", + "query": "from a_index | eval 1 months", "error": [ - "EVAL does not support [date_period] in expression [1 second]" + "EVAL does not support [date_period] in expression [1 months]" ], "warning": [] }, { - "query": "from a_index | eval 1 second", + "query": "from a_index | eval 1 months", "error": [ - "EVAL does not support [date_period] in expression [1 second]" + "EVAL does not support [date_period] in expression [1 months]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 second", + "query": "from a_index | eval var = now() - 1 months", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 second", + "query": "from a_index | eval var = dateField - 1 months", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 SECOND", + "query": "from a_index | eval var = dateField - 1 MONTHS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Second", + "query": "from a_index | eval var = dateField - 1 Months", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 second", + "query": "from a_index | eval var = dateField + 1 months", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 second + 1 year", + "query": "from a_index | eval 1 months + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 second] type [duration]" + "Argument of [+] must be [date], found value [1 months] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 second", + "query": "from a_index | eval var = now() * 1 months", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 second] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 months] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 second", + "query": "from a_index | eval var = now() / 1 months", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 second] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 months] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 second", + "query": "from a_index | eval var = now() % 1 months", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 second] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 months] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 seconds", + "query": "from a_index | eval 1 week", "error": [ - "EVAL does not support [date_period] in expression [1 seconds]" + "EVAL does not support [date_period] in expression [1 week]" ], "warning": [] }, { - "query": "from a_index | eval 1 seconds", + "query": "from a_index | eval 1 week", "error": [ - "EVAL does not support [date_period] in expression [1 seconds]" + "EVAL does not support [date_period] in expression [1 week]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 seconds", + "query": "from a_index | eval var = now() - 1 week", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 seconds", + "query": "from a_index | eval var = dateField - 1 week", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 SECONDS", + "query": "from a_index | eval var = dateField - 1 WEEK", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Seconds", + "query": "from a_index | eval var = dateField - 1 Week", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 seconds", + "query": "from a_index | eval var = dateField + 1 week", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 seconds + 1 year", + "query": "from a_index | eval 1 week + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 seconds] type [duration]" + "Argument of [+] must be [date], found value [1 week] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 seconds", + "query": "from a_index | eval var = now() * 1 week", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 seconds] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 week] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 seconds", + "query": "from a_index | eval var = now() / 1 week", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 seconds] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 week] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 seconds", + "query": "from a_index | eval var = now() % 1 week", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 seconds] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 week] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 millisecond", + "query": "from a_index | eval 1 weeks", "error": [ - "EVAL does not support [date_period] in expression [1 millisecond]" + "EVAL does not support [date_period] in expression [1 weeks]" ], "warning": [] }, { - "query": "from a_index | eval 1 millisecond", + "query": "from a_index | eval 1 weeks", "error": [ - "EVAL does not support [date_period] in expression [1 millisecond]" + "EVAL does not support [date_period] in expression [1 weeks]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 millisecond", + "query": "from a_index | eval var = now() - 1 weeks", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 millisecond", + "query": "from a_index | eval var = dateField - 1 weeks", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 MILLISECOND", + "query": "from a_index | eval var = dateField - 1 WEEKS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Millisecond", + "query": "from a_index | eval var = dateField - 1 Weeks", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 millisecond", + "query": "from a_index | eval var = dateField + 1 weeks", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 millisecond + 1 year", + "query": "from a_index | eval 1 weeks + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 millisecond] type [duration]" + "Argument of [+] must be [date], found value [1 weeks] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 millisecond", + "query": "from a_index | eval var = now() * 1 weeks", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 millisecond] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 weeks] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 millisecond", + "query": "from a_index | eval var = now() / 1 weeks", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 millisecond] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 weeks] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 millisecond", + "query": "from a_index | eval var = now() % 1 weeks", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 millisecond] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 weeks] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 milliseconds", + "query": "from a_index | eval 1 day", "error": [ - "EVAL does not support [date_period] in expression [1 milliseconds]" + "EVAL does not support [date_period] in expression [1 day]" ], "warning": [] }, { - "query": "from a_index | eval 1 milliseconds", + "query": "from a_index | eval 1 day", "error": [ - "EVAL does not support [date_period] in expression [1 milliseconds]" + "EVAL does not support [date_period] in expression [1 day]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 milliseconds", + "query": "from a_index | eval var = now() - 1 day", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 milliseconds", + "query": "from a_index | eval var = dateField - 1 day", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 MILLISECONDS", + "query": "from a_index | eval var = dateField - 1 DAY", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Milliseconds", + "query": "from a_index | eval var = dateField - 1 Day", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 milliseconds", + "query": "from a_index | eval var = dateField + 1 day", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 milliseconds + 1 year", + "query": "from a_index | eval 1 day + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 milliseconds] type [duration]" + "Argument of [+] must be [date], found value [1 day] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 milliseconds", + "query": "from a_index | eval var = now() * 1 day", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 milliseconds] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 day] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 milliseconds", + "query": "from a_index | eval var = now() / 1 day", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 milliseconds] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 day] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 milliseconds", + "query": "from a_index | eval var = now() % 1 day", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 milliseconds] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 day] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 ms", + "query": "from a_index | eval 1 days", "error": [ - "EVAL does not support [date_period] in expression [1 ms]" + "EVAL does not support [date_period] in expression [1 days]" ], "warning": [] }, { - "query": "from a_index | eval 1 ms", + "query": "from a_index | eval 1 days", "error": [ - "EVAL does not support [date_period] in expression [1 ms]" + "EVAL does not support [date_period] in expression [1 days]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 ms", + "query": "from a_index | eval var = now() - 1 days", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 ms", + "query": "from a_index | eval var = dateField - 1 days", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 MS", + "query": "from a_index | eval var = dateField - 1 DAYS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Ms", + "query": "from a_index | eval var = dateField - 1 Days", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 ms", + "query": "from a_index | eval var = dateField + 1 days", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 ms + 1 year", + "query": "from a_index | eval 1 days + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 ms] type [duration]" + "Argument of [+] must be [date], found value [1 days] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 ms", + "query": "from a_index | eval var = now() * 1 days", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 ms] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 days] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 ms", + "query": "from a_index | eval var = now() / 1 days", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 ms] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 days] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 ms", + "query": "from a_index | eval var = now() % 1 days", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 ms] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 days] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 s", + "query": "from a_index | eval 1 hour", "error": [ - "EVAL does not support [date_period] in expression [1 s]" + "EVAL does not support [date_period] in expression [1 hour]" ], "warning": [] }, { - "query": "from a_index | eval 1 s", + "query": "from a_index | eval 1 hour", "error": [ - "EVAL does not support [date_period] in expression [1 s]" + "EVAL does not support [date_period] in expression [1 hour]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 s", + "query": "from a_index | eval var = now() - 1 hour", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 s", + "query": "from a_index | eval var = dateField - 1 hour", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 S", + "query": "from a_index | eval var = dateField - 1 HOUR", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 S", + "query": "from a_index | eval var = dateField - 1 Hour", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 s", + "query": "from a_index | eval var = dateField + 1 hour", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 s + 1 year", + "query": "from a_index | eval 1 hour + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 s] type [duration]" + "Argument of [+] must be [date], found value [1 hour] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 s", + "query": "from a_index | eval var = now() * 1 hour", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 s] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 hour] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 s", + "query": "from a_index | eval var = now() / 1 hour", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 s] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 hour] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 s", + "query": "from a_index | eval var = now() % 1 hour", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 s] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 hour] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 m", + "query": "from a_index | eval 1 hours", "error": [ - "EVAL does not support [date_period] in expression [1 m]" + "EVAL does not support [date_period] in expression [1 hours]" ], "warning": [] }, { - "query": "from a_index | eval 1 m", + "query": "from a_index | eval 1 hours", "error": [ - "EVAL does not support [date_period] in expression [1 m]" + "EVAL does not support [date_period] in expression [1 hours]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 m", + "query": "from a_index | eval var = now() - 1 hours", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 m", + "query": "from a_index | eval var = dateField - 1 hours", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 M", + "query": "from a_index | eval var = dateField - 1 HOURS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 M", + "query": "from a_index | eval var = dateField - 1 Hours", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 m", + "query": "from a_index | eval var = dateField + 1 hours", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 m + 1 year", + "query": "from a_index | eval 1 hours + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 m] type [duration]" + "Argument of [+] must be [date], found value [1 hours] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 m", + "query": "from a_index | eval var = now() * 1 hours", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 m] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 hours] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 m", + "query": "from a_index | eval var = now() / 1 hours", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 m] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 hours] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 m", + "query": "from a_index | eval var = now() % 1 hours", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 m] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 hours] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 h", + "query": "from a_index | eval 1 minute", "error": [ - "EVAL does not support [date_period] in expression [1 h]" + "EVAL does not support [date_period] in expression [1 minute]" ], "warning": [] }, { - "query": "from a_index | eval 1 h", + "query": "from a_index | eval 1 minute", "error": [ - "EVAL does not support [date_period] in expression [1 h]" + "EVAL does not support [date_period] in expression [1 minute]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 h", + "query": "from a_index | eval var = now() - 1 minute", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 h", + "query": "from a_index | eval var = dateField - 1 minute", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 H", + "query": "from a_index | eval var = dateField - 1 MINUTE", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 H", + "query": "from a_index | eval var = dateField - 1 Minute", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 h", + "query": "from a_index | eval var = dateField + 1 minute", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 h + 1 year", + "query": "from a_index | eval 1 minute + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 h] type [duration]" + "Argument of [+] must be [date], found value [1 minute] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 h", + "query": "from a_index | eval var = now() * 1 minute", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 h] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 minute] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 h", + "query": "from a_index | eval var = now() / 1 minute", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 h] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 minute] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 h", + "query": "from a_index | eval var = now() % 1 minute", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 h] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 minute] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 d", + "query": "from a_index | eval 1 minutes", "error": [ - "EVAL does not support [date_period] in expression [1 d]" + "EVAL does not support [date_period] in expression [1 minutes]" ], "warning": [] }, { - "query": "from a_index | eval 1 d", + "query": "from a_index | eval 1 minutes", "error": [ - "EVAL does not support [date_period] in expression [1 d]" + "EVAL does not support [date_period] in expression [1 minutes]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 d", + "query": "from a_index | eval var = now() - 1 minutes", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 d", + "query": "from a_index | eval var = dateField - 1 minutes", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 D", + "query": "from a_index | eval var = dateField - 1 MINUTES", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 D", + "query": "from a_index | eval var = dateField - 1 Minutes", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 d", + "query": "from a_index | eval var = dateField + 1 minutes", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 d + 1 year", + "query": "from a_index | eval 1 minutes + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 d] type [duration]" + "Argument of [+] must be [date], found value [1 minutes] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 d", + "query": "from a_index | eval var = now() * 1 minutes", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 d] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 minutes] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 d", + "query": "from a_index | eval var = now() / 1 minutes", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 d] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 minutes] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 d", + "query": "from a_index | eval var = now() % 1 minutes", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 d] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 minutes] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 w", + "query": "from a_index | eval 1 second", "error": [ - "EVAL does not support [date_period] in expression [1 w]" + "EVAL does not support [date_period] in expression [1 second]" ], "warning": [] }, { - "query": "from a_index | eval 1 w", + "query": "from a_index | eval 1 second", "error": [ - "EVAL does not support [date_period] in expression [1 w]" + "EVAL does not support [date_period] in expression [1 second]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 w", + "query": "from a_index | eval var = now() - 1 second", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 w", + "query": "from a_index | eval var = dateField - 1 second", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 W", + "query": "from a_index | eval var = dateField - 1 SECOND", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 W", + "query": "from a_index | eval var = dateField - 1 Second", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 w", + "query": "from a_index | eval var = dateField + 1 second", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 w + 1 year", + "query": "from a_index | eval 1 second + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 w] type [duration]" + "Argument of [+] must be [date], found value [1 second] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 w", + "query": "from a_index | eval var = now() * 1 second", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 w] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 second] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 w", + "query": "from a_index | eval var = now() / 1 second", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 w] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 second] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 w", + "query": "from a_index | eval var = now() % 1 second", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 w] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 second] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 mo", + "query": "from a_index | eval 1 seconds", "error": [ - "EVAL does not support [date_period] in expression [1 mo]" + "EVAL does not support [date_period] in expression [1 seconds]" ], "warning": [] }, { - "query": "from a_index | eval 1 mo", + "query": "from a_index | eval 1 seconds", "error": [ - "EVAL does not support [date_period] in expression [1 mo]" + "EVAL does not support [date_period] in expression [1 seconds]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 mo", + "query": "from a_index | eval var = now() - 1 seconds", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 mo", + "query": "from a_index | eval var = dateField - 1 seconds", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 MO", + "query": "from a_index | eval var = dateField - 1 SECONDS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Mo", + "query": "from a_index | eval var = dateField - 1 Seconds", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 mo", + "query": "from a_index | eval var = dateField + 1 seconds", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 mo + 1 year", + "query": "from a_index | eval 1 seconds + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 mo] type [duration]" + "Argument of [+] must be [date], found value [1 seconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 mo", + "query": "from a_index | eval var = now() * 1 seconds", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 mo] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 seconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 mo", + "query": "from a_index | eval var = now() / 1 seconds", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 mo] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 seconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 mo", + "query": "from a_index | eval var = now() % 1 seconds", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 mo] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 seconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 q", + "query": "from a_index | eval 1 millisecond", "error": [ - "EVAL does not support [date_period] in expression [1 q]" + "EVAL does not support [date_period] in expression [1 millisecond]" ], "warning": [] }, { - "query": "from a_index | eval 1 q", + "query": "from a_index | eval 1 millisecond", "error": [ - "EVAL does not support [date_period] in expression [1 q]" + "EVAL does not support [date_period] in expression [1 millisecond]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 q", + "query": "from a_index | eval var = now() - 1 millisecond", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 q", + "query": "from a_index | eval var = dateField - 1 millisecond", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Q", + "query": "from a_index | eval var = dateField - 1 MILLISECOND", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Q", + "query": "from a_index | eval var = dateField - 1 Millisecond", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 q", + "query": "from a_index | eval var = dateField + 1 millisecond", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 q + 1 year", + "query": "from a_index | eval 1 millisecond + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 q] type [duration]" + "Argument of [+] must be [date], found value [1 millisecond] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 q", + "query": "from a_index | eval var = now() * 1 millisecond", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 q] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 millisecond] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 q", + "query": "from a_index | eval var = now() / 1 millisecond", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 q] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 millisecond] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 q", + "query": "from a_index | eval var = now() % 1 millisecond", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 q] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 millisecond] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 y", + "query": "from a_index | eval 1 milliseconds", "error": [ - "EVAL does not support [date_period] in expression [1 y]" + "EVAL does not support [date_period] in expression [1 milliseconds]" ], "warning": [] }, { - "query": "from a_index | eval 1 y", + "query": "from a_index | eval 1 milliseconds", "error": [ - "EVAL does not support [date_period] in expression [1 y]" + "EVAL does not support [date_period] in expression [1 milliseconds]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 y", + "query": "from a_index | eval var = now() - 1 milliseconds", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 y", + "query": "from a_index | eval var = dateField - 1 milliseconds", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Y", + "query": "from a_index | eval var = dateField - 1 MILLISECONDS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Y", + "query": "from a_index | eval var = dateField - 1 Milliseconds", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 y", + "query": "from a_index | eval var = dateField + 1 milliseconds", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 y + 1 year", + "query": "from a_index | eval 1 milliseconds + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 y] type [duration]" + "Argument of [+] must be [date], found value [1 milliseconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 y", + "query": "from a_index | eval var = now() * 1 milliseconds", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 y] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 milliseconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 y", + "query": "from a_index | eval var = now() / 1 milliseconds", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 y] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 milliseconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 y", + "query": "from a_index | eval var = now() % 1 milliseconds", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 y] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 milliseconds] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval 1 yr", + "query": "from a_index | eval 1 ms", "error": [ - "EVAL does not support [date_period] in expression [1 yr]" + "EVAL does not support [date_period] in expression [1 ms]" ], "warning": [] }, { - "query": "from a_index | eval 1 yr", + "query": "from a_index | eval 1 ms", "error": [ - "EVAL does not support [date_period] in expression [1 yr]" + "EVAL does not support [date_period] in expression [1 ms]" ], "warning": [] }, { - "query": "from a_index | eval var = now() - 1 yr", + "query": "from a_index | eval var = now() - 1 ms", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 yr", + "query": "from a_index | eval var = dateField - 1 ms", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 YR", + "query": "from a_index | eval var = dateField - 1 MS", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField - 1 Yr", + "query": "from a_index | eval var = dateField - 1 Ms", "error": [], "warning": [] }, { - "query": "from a_index | eval var = dateField + 1 yr", + "query": "from a_index | eval var = dateField + 1 ms", "error": [], "warning": [] }, { - "query": "from a_index | eval 1 yr + 1 year", + "query": "from a_index | eval 1 ms + 1 year", "error": [ - "Argument of [+] must be [date], found value [1 yr] type [duration]" + "Argument of [+] must be [date], found value [1 ms] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() * 1 yr", + "query": "from a_index | eval var = now() * 1 ms", "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [1 yr] type [duration]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 ms] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() / 1 yr", + "query": "from a_index | eval var = now() / 1 ms", "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [1 yr] type [duration]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 ms] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval var = now() % 1 yr", + "query": "from a_index | eval var = now() % 1 ms", "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [1 yr] type [duration]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 ms] type [duration]" ], "warning": [] }, { - "query": "from a_index | sort ", + "query": "from a_index | eval 1 s", "error": [ - "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "EVAL does not support [date_period] in expression [1 s]" ], "warning": [] }, { - "query": "from a_index | sort \"field\" ", - "error": [], + "query": "from a_index | eval 1 s", + "error": [ + "EVAL does not support [date_period] in expression [1 s]" + ], "warning": [] }, { - "query": "from a_index | sort wrongField ", - "error": [ - "Unknown column [wrongField]" - ], + "query": "from a_index | eval var = now() - 1 s", + "error": [], "warning": [] }, { - "query": "from a_index | sort numberField, ", - "error": [ - "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | eval var = dateField - 1 s", + "error": [], "warning": [] }, { - "query": "from a_index | sort numberField, stringField", + "query": "from a_index | eval var = dateField - 1 S", "error": [], "warning": [] }, { - "query": "from a_index | sort \"field\" desc ", + "query": "from a_index | eval var = dateField - 1 S", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField desc ", + "query": "from a_index | eval var = dateField + 1 s", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField desc nulls ", + "query": "from a_index | eval 1 s + 1 year", "error": [ - "SyntaxError: missing {'first', 'last'} at '<EOF>'" + "Argument of [+] must be [date], found value [1 s] type [duration]" ], "warning": [] }, { - "query": "from a_index | sort numberField desc nulls first", - "error": [], + "query": "from a_index | eval var = now() * 1 s", + "error": [ + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 s] type [duration]" + ], "warning": [] }, { - "query": "from a_index | sort numberField desc first", + "query": "from a_index | eval var = now() / 1 s", "error": [ - "SyntaxError: extraneous input 'first' expecting <EOF>" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 s] type [duration]" ], "warning": [] }, { - "query": "from a_index | sort numberField desc nulls last", - "error": [], + "query": "from a_index | eval var = now() % 1 s", + "error": [ + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 s] type [duration]" + ], "warning": [] }, { - "query": "from a_index | sort numberField desc last", + "query": "from a_index | eval 1 m", "error": [ - "SyntaxError: extraneous input 'last' expecting <EOF>" + "EVAL does not support [date_period] in expression [1 m]" ], "warning": [] }, { - "query": "from a_index | sort \"field\" asc ", - "error": [], + "query": "from a_index | eval 1 m", + "error": [ + "EVAL does not support [date_period] in expression [1 m]" + ], "warning": [] }, { - "query": "from a_index | sort numberField asc ", + "query": "from a_index | eval var = now() - 1 m", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc nulls ", - "error": [ - "SyntaxError: missing {'first', 'last'} at '<EOF>'" - ], + "query": "from a_index | eval var = dateField - 1 m", + "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc nulls first", + "query": "from a_index | eval var = dateField - 1 M", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc first", - "error": [ - "SyntaxError: extraneous input 'first' expecting <EOF>" - ], + "query": "from a_index | eval var = dateField - 1 M", + "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc nulls last", + "query": "from a_index | eval var = dateField + 1 m", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc last", + "query": "from a_index | eval 1 m + 1 year", "error": [ - "SyntaxError: extraneous input 'last' expecting <EOF>" + "Argument of [+] must be [date], found value [1 m] type [duration]" ], "warning": [] }, { - "query": "from a_index | sort numberField nulls first", - "error": [], + "query": "from a_index | eval var = now() * 1 m", + "error": [ + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 m] type [duration]" + ], "warning": [] }, { - "query": "from a_index | sort numberField first", + "query": "from a_index | eval var = now() / 1 m", "error": [ - "SyntaxError: extraneous input 'first' expecting <EOF>" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 m] type [duration]" ], "warning": [] }, { - "query": "from a_index | sort numberField nulls last", - "error": [], + "query": "from a_index | eval var = now() % 1 m", + "error": [ + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 m] type [duration]" + ], "warning": [] }, { - "query": "from a_index | sort numberField last", + "query": "from a_index | eval 1 h", "error": [ - "SyntaxError: extraneous input 'last' expecting <EOF>" + "EVAL does not support [date_period] in expression [1 h]" ], "warning": [] }, { - "query": "row a = 1 | stats COUNT(*) | sort `COUNT(*)`", - "error": [], + "query": "from a_index | eval 1 h", + "error": [ + "EVAL does not support [date_period] in expression [1 h]" + ], "warning": [] }, { - "query": "ROW a = 1 | STATS couNt(*) | SORT `couNt(*)`", + "query": "from a_index | eval var = now() - 1 h", "error": [], "warning": [] }, { - "query": "from a_index | sort abs(numberField) - to_long(stringField) desc nulls first", + "query": "from a_index | eval var = dateField - 1 h", "error": [], "warning": [] }, { - "query": "from a_index | sort sin(stringField)", - "error": [ - "Argument of [sin] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = dateField - 1 H", + "error": [], "warning": [] }, { - "query": "from a_index | sort numberField + stringField", - "error": [ - "Argument of [+] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = dateField - 1 H", + "error": [], "warning": [] }, { - "query": "from a_index | enrich", - "error": [ - "SyntaxError: missing ENRICH_POLICY_NAME at '<EOF>'" - ], + "query": "from a_index | eval var = dateField + 1 h", + "error": [], "warning": [] }, { - "query": "from a_index | enrich _", + "query": "from a_index | eval 1 h + 1 year", "error": [ - "Unknown policy [_]" + "Argument of [+] must be [date], found value [1 h] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich _:", + "query": "from a_index | eval var = now() * 1 h", "error": [ - "SyntaxError: token recognition error at: ':'", - "Unknown policy [_]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 h] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich _:policy", + "query": "from a_index | eval var = now() / 1 h", "error": [ - "Unrecognized value [_] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 h] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich :policy", + "query": "from a_index | eval var = now() % 1 h", "error": [ - "SyntaxError: token recognition error at: ':'" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 h] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich any:", + "query": "from a_index | eval 1 d", "error": [ - "SyntaxError: token recognition error at: ':'", - "Unknown policy [any]" + "EVAL does not support [date_period] in expression [1 d]" ], "warning": [] }, { - "query": "from a_index | enrich _any:", + "query": "from a_index | eval 1 d", "error": [ - "SyntaxError: token recognition error at: ':'", - "Unknown policy [_any]" + "EVAL does not support [date_period] in expression [1 d]" ], "warning": [] }, { - "query": "from a_index | enrich any:policy", - "error": [ - "Unrecognized value [any] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" - ], + "query": "from a_index | eval var = now() - 1 d", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy ", + "query": "from a_index | eval var = dateField - 1 d", "error": [], "warning": [] }, { - "query": "from a_index | enrich `this``is fine`", - "error": [ - "SyntaxError: extraneous input 'fine`' expecting <EOF>", - "Unknown policy [`this``is]" - ], + "query": "from a_index | eval var = dateField - 1 D", + "error": [], "warning": [] }, { - "query": "from a_index | enrich this is fine", - "error": [ - "SyntaxError: mismatched input 'is' expecting <EOF>", - "Unknown policy [this]" - ], + "query": "from a_index | eval var = dateField - 1 D", + "error": [], "warning": [] }, { - "query": "from a_index | enrich _any:policy ", + "query": "from a_index | eval var = dateField + 1 d", "error": [], "warning": [] }, { - "query": "from a_index | enrich _any : policy ", + "query": "from a_index | eval 1 d + 1 year", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting <EOF>", - "Unknown policy [_any]" + "Argument of [+] must be [date], found value [1 d] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich _any: policy ", + "query": "from a_index | eval var = now() * 1 d", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting <EOF>", - "Unknown policy [_any]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 d] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich _any:policy ", - "error": [], + "query": "from a_index | eval var = now() / 1 d", + "error": [ + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 d] type [duration]" + ], "warning": [] }, { - "query": "from a_index | enrich _ANY:policy ", - "error": [], + "query": "from a_index | eval var = now() % 1 d", + "error": [ + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 d] type [duration]" + ], "warning": [] }, { - "query": "from a_index | enrich _coordinator:policy ", - "error": [], + "query": "from a_index | eval 1 w", + "error": [ + "EVAL does not support [date_period] in expression [1 w]" + ], "warning": [] }, { - "query": "from a_index | enrich _coordinator : policy ", + "query": "from a_index | eval 1 w", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting <EOF>", - "Unknown policy [_coordinator]" + "EVAL does not support [date_period] in expression [1 w]" ], "warning": [] }, { - "query": "from a_index | enrich _coordinator: policy ", - "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting <EOF>", - "Unknown policy [_coordinator]" - ], + "query": "from a_index | eval var = now() - 1 w", + "error": [], "warning": [] }, { - "query": "from a_index | enrich _coordinator:policy ", + "query": "from a_index | eval var = dateField - 1 w", "error": [], "warning": [] }, { - "query": "from a_index | enrich _COORDINATOR:policy ", + "query": "from a_index | eval var = dateField - 1 W", "error": [], "warning": [] }, { - "query": "from a_index | enrich _remote:policy ", + "query": "from a_index | eval var = dateField - 1 W", "error": [], "warning": [] }, { - "query": "from a_index | enrich _remote : policy ", - "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting <EOF>", - "Unknown policy [_remote]" - ], + "query": "from a_index | eval var = dateField + 1 w", + "error": [], "warning": [] }, { - "query": "from a_index | enrich _remote: policy ", + "query": "from a_index | eval 1 w + 1 year", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting <EOF>", - "Unknown policy [_remote]" + "Argument of [+] must be [date], found value [1 w] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich _remote:policy ", - "error": [], + "query": "from a_index | eval var = now() * 1 w", + "error": [ + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 w] type [duration]" + ], "warning": [] }, { - "query": "from a_index | enrich _REMOTE:policy ", - "error": [], + "query": "from a_index | eval var = now() / 1 w", + "error": [ + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 w] type [duration]" + ], "warning": [] }, { - "query": "from a_index | enrich _unknown:policy", + "query": "from a_index | eval var = now() % 1 w", "error": [ - "Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 w] type [duration]" ], "warning": [] }, { - "query": "from a_index |enrich missing-policy ", + "query": "from a_index | eval 1 mo", "error": [ - "Unknown policy [missing-policy]" + "EVAL does not support [date_period] in expression [1 mo]" ], "warning": [] }, { - "query": "from a_index |enrich policy on ", + "query": "from a_index | eval 1 mo", "error": [ - "SyntaxError: missing ID_PATTERN at '<EOF>'" + "EVAL does not support [date_period] in expression [1 mo]" ], "warning": [] }, { - "query": "from a_index | enrich policy on b ", - "error": [ - "Unknown column [b]" - ], + "query": "from a_index | eval var = now() - 1 mo", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on `this``is fine`", - "error": [ - "Unknown column [this`is fine]" - ], + "query": "from a_index | eval var = dateField - 1 mo", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on this is fine", - "error": [ - "SyntaxError: mismatched input 'is' expecting <EOF>", - "Unknown column [this]" - ], + "query": "from a_index | eval var = dateField - 1 MO", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = dateField - 1 Mo", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = dateField + 1 mo", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with ", + "query": "from a_index | eval 1 mo + 1 year", "error": [ - "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN" + "Argument of [+] must be [date], found value [1 mo] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 ", + "query": "from a_index | eval var = now() * 1 mo", "error": [ - "Unknown column [var0]" + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 mo] type [duration]" ], "warning": [] }, { - "query": "from a_index |enrich policy on numberField with var0 = ", + "query": "from a_index | eval var = now() / 1 mo", "error": [ - "SyntaxError: missing ID_PATTERN at '<EOF>'", - "Unknown column [var0]" + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 mo] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = c ", + "query": "from a_index | eval var = now() % 1 mo", "error": [ - "Unknown column [var0]", - "Unknown column [c]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 mo] type [duration]" ], "warning": [] }, { - "query": "from a_index |enrich policy on numberField with var0 = , ", + "query": "from a_index | eval 1 q", "error": [ - "SyntaxError: missing ID_PATTERN at ','", - "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN", - "Unknown column [var0]" + "EVAL does not support [date_period] in expression [1 q]" ], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, var1 ", + "query": "from a_index | eval 1 q", "error": [ - "Unknown column [var1]" + "EVAL does not support [date_period] in expression [1 q]" ], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField ", + "query": "from a_index | eval var = now() - 1 q", "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, yetAnotherField ", + "query": "from a_index | eval var = dateField - 1 q", "error": [], "warning": [] }, { - "query": "from a_index |enrich policy on numberField with var0 = otherField, var1 = ", - "error": [ - "SyntaxError: missing ID_PATTERN at '<EOF>'", - "Unknown column [var1]" - ], + "query": "from a_index | eval var = dateField - 1 Q", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, var1 = yetAnotherField", + "query": "from a_index | eval var = dateField - 1 Q", "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, `this``is fine` = yetAnotherField", + "query": "from a_index | eval var = dateField + 1 q", "error": [], "warning": [] }, { - "query": "from a_index | enrich policy with ", + "query": "from a_index | eval 1 q + 1 year", "error": [ - "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN" + "Argument of [+] must be [date], found value [1 q] type [duration]" ], "warning": [] }, { - "query": "from a_index | enrich policy with otherField", - "error": [], + "query": "from a_index | eval var = now() * 1 q", + "error": [ + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 q] type [duration]" + ], "warning": [] }, { - "query": "from a_index | enrich policy | eval otherField", - "error": [], + "query": "from a_index | eval var = now() / 1 q", + "error": [ + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 q] type [duration]" + ], "warning": [] }, { - "query": "from a_index | enrich policy with var0 = otherField | eval var0", - "error": [], + "query": "from a_index | eval var = now() % 1 q", + "error": [ + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 q] type [duration]" + ], "warning": [] }, { - "query": "from a_index | enrich my-pol*", + "query": "from a_index | eval 1 y", "error": [ - "Using wildcards (*) in ENRICH is not allowed [my-pol*]" + "EVAL does not support [date_period] in expression [1 y]" ], "warning": [] }, { - "query": "from a_index | eval stringField = 5", - "error": [], - "warning": [ - "Column [stringField] of type string has been overwritten as new type: number" - ] - }, - { - "query": "from a_index | eval numberField = \"5\"", - "error": [], - "warning": [ - "Column [numberField] of type number has been overwritten as new type: string" - ] - }, - { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | keep ```round(numberField) + 1`` + 1`", - "error": [], + "query": "from a_index | eval 1 y", + "error": [ + "EVAL does not support [date_period] in expression [1 y]" + ], "warning": [] }, { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | keep ```````round(numberField) + 1```` + 1`` + 1`", + "query": "from a_index | eval var = now() - 1 y", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | eval ```````round(numberField) + 1```` + 1`` + 1` + 1 | keep ```````````````round(numberField) + 1```````` + 1```` + 1`` + 1`", + "query": "from a_index | eval var = dateField - 1 y", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | eval ```````round(numberField) + 1```` + 1`` + 1` + 1 | eval ```````````````round(numberField) + 1```````` + 1```` + 1`` + 1` + 1 | keep ```````````````````````````````round(numberField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`", + "query": "from a_index | eval var = dateField - 1 Y", "error": [], "warning": [] }, { - "query": "from a_index | eval 1::string", + "query": "from a_index | eval var = dateField - 1 Y", "error": [], "warning": [] }, { - "query": "from a_index | eval 1::string::long::double", + "query": "from a_index | eval var = dateField + 1 y", "error": [], "warning": [] }, { - "query": "from a_index | eval trim(\"23\"::double)", + "query": "from a_index | eval 1 y + 1 year", "error": [ - "Argument of [trim] must be [string], found value [\"23\"::double] type [double]" + "Argument of [+] must be [date], found value [1 y] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval trim(23::string)", - "error": [], + "query": "from a_index | eval var = now() * 1 y", + "error": [ + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 y] type [duration]" + ], "warning": [] }, { - "query": "from a_index | eval 1 + \"2\"::long", - "error": [], + "query": "from a_index | eval var = now() / 1 y", + "error": [ + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 y] type [duration]" + ], "warning": [] }, { - "query": "from a_index | eval 1 + \"2\"", + "query": "from a_index | eval var = now() % 1 y", "error": [ - "Argument of [+] must be [number], found value [\"2\"] type [string]" + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 y] type [duration]" ], "warning": [] }, { - "query": "from a_index | eval trim(to_double(\"23\")::string::double::long::string::double)", + "query": "from a_index | eval 1 yr", "error": [ - "Argument of [trim] must be [string], found value [to_double(\"23\")::string::double::long::string::double] type [double]" + "EVAL does not support [date_period] in expression [1 yr]" ], "warning": [] }, { - "query": "from a_index | eval CEIL(23::long)", - "error": [], + "query": "from a_index | eval 1 yr", + "error": [ + "EVAL does not support [date_period] in expression [1 yr]" + ], "warning": [] }, { - "query": "from a_index | eval CEIL(23::unsigned_long)", + "query": "from a_index | eval var = now() - 1 yr", "error": [], "warning": [] }, { - "query": "from a_index | eval CEIL(23::int)", + "query": "from a_index | eval var = dateField - 1 yr", "error": [], "warning": [] }, { - "query": "from a_index | eval CEIL(23::integer)", + "query": "from a_index | eval var = dateField - 1 YR", "error": [], "warning": [] }, { - "query": "from a_index | eval CEIL(23::double)", + "query": "from a_index | eval var = dateField - 1 Yr", "error": [], "warning": [] }, { - "query": "from a_index | eval TRIM(23::string)", + "query": "from a_index | eval var = dateField + 1 yr", "error": [], "warning": [] }, { - "query": "from a_index | eval TRIM(23::text)", - "error": [], + "query": "from a_index | eval 1 yr + 1 year", + "error": [ + "Argument of [+] must be [date], found value [1 yr] type [duration]" + ], "warning": [] }, { - "query": "from a_index | eval TRIM(23::keyword)", - "error": [], + "query": "from a_index | eval var = now() * 1 yr", + "error": [ + "Argument of [*] must be [double], found value [now()] type [date]", + "Argument of [*] must be [double], found value [1 yr] type [duration]" + ], "warning": [] }, { - "query": "from a_index | eval true AND \"false\"::boolean", - "error": [], + "query": "from a_index | eval var = now() / 1 yr", + "error": [ + "Argument of [/] must be [double], found value [now()] type [date]", + "Argument of [/] must be [double], found value [1 yr] type [duration]" + ], "warning": [] }, { - "query": "from a_index | eval true AND \"false\"::bool", - "error": [], + "query": "from a_index | eval var = now() % 1 yr", + "error": [ + "Argument of [%] must be [double], found value [now()] type [date]", + "Argument of [%] must be [double], found value [1 yr] type [duration]" + ], "warning": [] }, { - "query": "from a_index | eval true AND \"false\"", + "query": "from a_index | sort ", "error": [ - "Argument of [and] must be [boolean], found value [\"false\"] type [string]" + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | eval to_lower(trim(numberField)::string)", - "error": [ - "Argument of [trim] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | sort \"field\" ", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_upper(trim(numberField)::string::string::string::string)", + "query": "from a_index | sort wrongField ", "error": [ - "Argument of [trim] must be [string], found value [numberField] type [number]" + "Unknown column [wrongField]" ], "warning": [] }, { - "query": "from a_index | eval to_lower(to_upper(trim(numberField)::string)::string)", + "query": "from a_index | sort doubleField, ", "error": [ - "Argument of [trim] must be [string], found value [numberField] type [number]" + "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "row var = date_diff(\"month\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "query": "from a_index | sort doubleField, textField", "error": [], "warning": [] }, { - "query": "row var = date_diff(\"mm\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "query": "from a_index | sort \"field\" desc ", "error": [], "warning": [] }, { - "query": "row var = date_diff(\"bogus\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", - "error": [], - "warning": [ - "Invalid option [\"bogus\"] for date_diff. Supported options: [\"year\", \"years\", \"yy\", \"yyyy\", \"quarter\", \"quarters\", \"qq\", \"q\", \"month\", \"months\", \"mm\", \"m\", \"dayofyear\", \"dy\", \"y\", \"day\", \"days\", \"dd\", \"d\", \"week\", \"weeks\", \"wk\", \"ww\", \"weekday\", \"weekdays\", \"dw\", \"hour\", \"hours\", \"hh\", \"minute\", \"minutes\", \"mi\", \"n\", \"second\", \"seconds\", \"ss\", \"s\", \"millisecond\", \"milliseconds\", \"ms\", \"microsecond\", \"microseconds\", \"mcs\", \"nanosecond\", \"nanoseconds\", \"ns\"]." - ] - }, - { - "query": "from a_index | eval date_diff(stringField, \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "query": "from a_index | sort doubleField desc ", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", dateField, \"2023-12-02T11:00:00.000Z\")", - "error": [], + "query": "from a_index | sort doubleField desc nulls ", + "error": [ + "SyntaxError: missing {'first', 'last'} at '<EOF>'" + ], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", \"2023-12-02T11:00:00.000Z\", dateField)", + "query": "from a_index | sort doubleField desc nulls first", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", stringField, dateField)", + "query": "from a_index | sort doubleField desc first", "error": [ - "Argument of [date_diff] must be [date], found value [stringField] type [string]" + "SyntaxError: extraneous input 'first' expecting <EOF>" ], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", dateField, stringField)", - "error": [ - "Argument of [date_diff] must be [date], found value [stringField] type [string]" - ], + "query": "from a_index | sort doubleField desc nulls last", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_diff(\"year\", dateField, dateField)", - "error": [], + "query": "from a_index | sort doubleField desc last", + "error": [ + "SyntaxError: extraneous input 'last' expecting <EOF>" + ], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", dateField, dateField)", + "query": "from a_index | sort \"field\" asc ", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_diff(\"year\", to_datetime(stringField), to_datetime(stringField))", + "query": "from a_index | sort doubleField asc ", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(numberField, stringField, stringField)", + "query": "from a_index | sort doubleField asc nulls ", "error": [ - "Argument of [date_diff] must be [string], found value [numberField] type [number]", - "Argument of [date_diff] must be [date], found value [stringField] type [string]", - "Argument of [date_diff] must be [date], found value [stringField] type [string]" + "SyntaxError: missing {'first', 'last'} at '<EOF>'" ], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", dateField, dateField, extraArg)", - "error": [ - "Error: [date_diff] function expects exactly 3 arguments, got 4." - ], + "query": "from a_index | sort doubleField asc nulls first", + "error": [], "warning": [] }, { - "query": "from a_index | sort date_diff(\"year\", dateField, dateField)", - "error": [], + "query": "from a_index | sort doubleField asc first", + "error": [ + "SyntaxError: extraneous input 'first' expecting <EOF>" + ], "warning": [] }, { - "query": "from a_index | eval var = date_diff(\"year\", to_datetime(dateField), to_datetime(dateField))", + "query": "from a_index | sort doubleField asc nulls last", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(booleanField, booleanField, booleanField)", + "query": "from a_index | sort doubleField asc last", "error": [ - "Argument of [date_diff] must be [string], found value [booleanField] type [boolean]", - "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]", - "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]" + "SyntaxError: extraneous input 'last' expecting <EOF>" ], "warning": [] }, { - "query": "from a_index | eval date_diff(null, null, null)", + "query": "from a_index | sort doubleField nulls first", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_diff(nullVar, nullVar, nullVar)", - "error": [], + "query": "from a_index | sort doubleField first", + "error": [ + "SyntaxError: extraneous input 'first' expecting <EOF>" + ], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", \"2022\", \"2022\")", + "query": "from a_index | sort doubleField nulls last", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", concat(\"20\", \"22\"), concat(\"20\", \"22\"))", + "query": "from a_index | sort doubleField last", "error": [ - "Argument of [date_diff] must be [date], found value [concat(\"20\",\"22\")] type [string]", - "Argument of [date_diff] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "SyntaxError: extraneous input 'last' expecting <EOF>" ], "warning": [] }, { - "query": "row var = abs(5)", + "query": "row a = 1 | stats COUNT(*) | sort `COUNT(*)`", "error": [], "warning": [] }, { - "query": "row abs(5)", + "query": "ROW a = 1 | STATS couNt(*) | SORT `couNt(*)`", "error": [], "warning": [] }, { - "query": "row var = abs(to_integer(\"a\"))", + "query": "from a_index | sort abs(doubleField) - to_long(textField) desc nulls first", "error": [], "warning": [] }, { - "query": "row var = abs(\"a\")", + "query": "from a_index | sort sin(textField)", "error": [ - "Argument of [abs] must be [number], found value [\"a\"] type [string]" + "Argument of [sin] must be [double], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | where abs(numberField) > 0", - "error": [], + "query": "from a_index | sort doubleField + textField", + "error": [ + "Argument of [+] must be [double], found value [textField] type [text]" + ], "warning": [] }, { - "query": "from a_index | where abs(stringField) > 0", + "query": "from a_index | enrich", "error": [ - "Argument of [abs] must be [number], found value [stringField] type [string]" + "SyntaxError: missing ENRICH_POLICY_NAME at '<EOF>'" ], "warning": [] }, { - "query": "from a_index | eval var = abs(numberField)", - "error": [], + "query": "from a_index | enrich _", + "error": [ + "Unknown policy [_]" + ], "warning": [] }, { - "query": "from a_index | eval abs(numberField)", - "error": [], + "query": "from a_index | enrich _:", + "error": [ + "SyntaxError: token recognition error at: ':'", + "Unknown policy [_]" + ], "warning": [] }, { - "query": "from a_index | eval var = abs(to_integer(stringField))", - "error": [], + "query": "from a_index | enrich _:policy", + "error": [ + "Unrecognized value [_] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" + ], "warning": [] }, { - "query": "from a_index | eval abs(stringField)", + "query": "from a_index | enrich :policy", "error": [ - "Argument of [abs] must be [number], found value [stringField] type [string]" + "SyntaxError: token recognition error at: ':'" ], "warning": [] }, { - "query": "from a_index | eval abs(numberField, extraArg)", + "query": "from a_index | enrich any:", "error": [ - "Error: [abs] function expects exactly one argument, got 2." + "SyntaxError: token recognition error at: ':'", + "Unknown policy [any]" ], "warning": [] }, { - "query": "from a_index | eval var = abs(*)", + "query": "from a_index | enrich _any:", "error": [ - "Using wildcards (*) in abs is not allowed" + "SyntaxError: token recognition error at: ':'", + "Unknown policy [_any]" ], "warning": [] }, { - "query": "from a_index | sort abs(numberField)", - "error": [], + "query": "from a_index | enrich any:policy", + "error": [ + "Unrecognized value [any] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" + ], "warning": [] }, { - "query": "row var = abs(to_integer(true))", + "query": "from a_index | enrich policy ", "error": [], "warning": [] }, { - "query": "row var = abs(true)", + "query": "from a_index | enrich `this``is fine`", "error": [ - "Argument of [abs] must be [number], found value [true] type [boolean]" + "SyntaxError: extraneous input 'fine`' expecting <EOF>", + "Unknown policy [`this``is]" ], "warning": [] }, { - "query": "from a_index | where abs(booleanField) > 0", + "query": "from a_index | enrich this is fine", "error": [ - "Argument of [abs] must be [number], found value [booleanField] type [boolean]" + "SyntaxError: mismatched input 'is' expecting <EOF>", + "Unknown policy [this]" ], "warning": [] }, { - "query": "from a_index | eval var = abs(to_integer(booleanField))", + "query": "from a_index | enrich _any:policy ", "error": [], "warning": [] }, { - "query": "from a_index | eval abs(booleanField)", + "query": "from a_index | enrich _any : policy ", "error": [ - "Argument of [abs] must be [number], found value [booleanField] type [boolean]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting <EOF>", + "Unknown policy [_any]" ], "warning": [] }, { - "query": "from a_index | eval abs(null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | eval abs(nullVar)", - "error": [], + "query": "from a_index | enrich _any: policy ", + "error": [ + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting <EOF>", + "Unknown policy [_any]" + ], "warning": [] }, { - "query": "row var = acos(5)", + "query": "from a_index | enrich _any:policy ", "error": [], "warning": [] }, { - "query": "row acos(5)", + "query": "from a_index | enrich _ANY:policy ", "error": [], "warning": [] }, { - "query": "row var = acos(to_integer(\"a\"))", + "query": "from a_index | enrich _coordinator:policy ", "error": [], "warning": [] }, { - "query": "row var = acos(\"a\")", + "query": "from a_index | enrich _coordinator : policy ", "error": [ - "Argument of [acos] must be [number], found value [\"a\"] type [string]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting <EOF>", + "Unknown policy [_coordinator]" ], "warning": [] }, { - "query": "from a_index | where acos(numberField) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where acos(stringField) > 0", + "query": "from a_index | enrich _coordinator: policy ", "error": [ - "Argument of [acos] must be [number], found value [stringField] type [string]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting <EOF>", + "Unknown policy [_coordinator]" ], "warning": [] }, { - "query": "from a_index | eval var = acos(numberField)", + "query": "from a_index | enrich _coordinator:policy ", "error": [], "warning": [] }, { - "query": "from a_index | eval acos(numberField)", + "query": "from a_index | enrich _COORDINATOR:policy ", "error": [], "warning": [] }, { - "query": "from a_index | eval var = acos(to_integer(stringField))", + "query": "from a_index | enrich _remote:policy ", "error": [], "warning": [] }, { - "query": "from a_index | eval acos(stringField)", + "query": "from a_index | enrich _remote : policy ", "error": [ - "Argument of [acos] must be [number], found value [stringField] type [string]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting <EOF>", + "Unknown policy [_remote]" ], "warning": [] }, { - "query": "from a_index | eval acos(numberField, extraArg)", + "query": "from a_index | enrich _remote: policy ", "error": [ - "Error: [acos] function expects exactly one argument, got 2." + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting <EOF>", + "Unknown policy [_remote]" ], "warning": [] }, { - "query": "from a_index | eval var = acos(*)", - "error": [ - "Using wildcards (*) in acos is not allowed" - ], + "query": "from a_index | enrich _remote:policy ", + "error": [], "warning": [] }, { - "query": "from a_index | sort acos(numberField)", + "query": "from a_index | enrich _REMOTE:policy ", "error": [], "warning": [] }, { - "query": "row var = acos(to_integer(true))", - "error": [], - "warning": [] - }, - { - "query": "row var = acos(true)", + "query": "from a_index | enrich _unknown:policy", "error": [ - "Argument of [acos] must be [number], found value [true] type [boolean]" + "Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" ], "warning": [] }, { - "query": "from a_index | where acos(booleanField) > 0", + "query": "from a_index |enrich missing-policy ", "error": [ - "Argument of [acos] must be [number], found value [booleanField] type [boolean]" + "Unknown policy [missing-policy]" ], "warning": [] }, { - "query": "from a_index | eval var = acos(to_integer(booleanField))", - "error": [], + "query": "from a_index |enrich policy on ", + "error": [ + "SyntaxError: missing ID_PATTERN at '<EOF>'" + ], "warning": [] }, { - "query": "from a_index | eval acos(booleanField)", + "query": "from a_index | enrich policy on b ", "error": [ - "Argument of [acos] must be [number], found value [booleanField] type [boolean]" + "Unknown column [b]" ], "warning": [] }, { - "query": "from a_index | eval acos(null)", - "error": [], + "query": "from a_index | enrich policy on `this``is fine`", + "error": [ + "Unknown column [this`is fine]" + ], "warning": [] }, { - "query": "row nullVar = null | eval acos(nullVar)", - "error": [], + "query": "from a_index | enrich policy on this is fine", + "error": [ + "SyntaxError: mismatched input 'is' expecting <EOF>", + "Unknown column [this]" + ], "warning": [] }, { - "query": "row var = asin(5)", - "error": [], + "query": "from a_index | enrich policy on textField with ", + "error": [ + "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN" + ], "warning": [] }, { - "query": "row asin(5)", - "error": [], + "query": "from a_index | enrich policy on textField with var0 ", + "error": [ + "Unknown column [var0]" + ], "warning": [] }, { - "query": "row var = asin(to_integer(\"a\"))", - "error": [], + "query": "from a_index |enrich policy on doubleField with var0 = ", + "error": [ + "SyntaxError: missing ID_PATTERN at '<EOF>'", + "Unknown column [var0]" + ], "warning": [] }, { - "query": "row var = asin(\"a\")", + "query": "from a_index | enrich policy on textField with var0 = c ", "error": [ - "Argument of [asin] must be [number], found value [\"a\"] type [string]" + "Unknown column [var0]", + "Unknown column [c]" ], "warning": [] }, { - "query": "from a_index | where asin(numberField) > 0", - "error": [], + "query": "from a_index |enrich policy on doubleField with var0 = , ", + "error": [ + "SyntaxError: missing ID_PATTERN at ','", + "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN", + "Unknown column [var0]" + ], "warning": [] }, { - "query": "from a_index | where asin(stringField) > 0", + "query": "from a_index | enrich policy on textField with var0 = otherField, var1 ", "error": [ - "Argument of [asin] must be [number], found value [stringField] type [string]" + "Unknown column [var1]" ], "warning": [] }, { - "query": "from a_index | eval var = asin(numberField)", + "query": "from a_index | enrich policy on textField with var0 = otherField ", "error": [], "warning": [] }, { - "query": "from a_index | eval asin(numberField)", + "query": "from a_index | enrich policy on textField with var0 = otherField, yetAnotherField ", "error": [], "warning": [] }, { - "query": "from a_index | eval var = asin(to_integer(stringField))", - "error": [], + "query": "from a_index |enrich policy on doubleField with var0 = otherField, var1 = ", + "error": [ + "SyntaxError: missing ID_PATTERN at '<EOF>'", + "Unknown column [var1]" + ], "warning": [] }, { - "query": "from a_index | eval asin(stringField)", - "error": [ - "Argument of [asin] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | enrich policy on textField with var0 = otherField, var1 = yetAnotherField", + "error": [], "warning": [] }, { - "query": "from a_index | eval asin(numberField, extraArg)", - "error": [ - "Error: [asin] function expects exactly one argument, got 2." - ], + "query": "from a_index | enrich policy on textField with var0 = otherField, `this``is fine` = yetAnotherField", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = asin(*)", + "query": "from a_index | enrich policy with ", "error": [ - "Using wildcards (*) in asin is not allowed" + "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN" ], "warning": [] }, { - "query": "from a_index | sort asin(numberField)", + "query": "from a_index | enrich policy with otherField", "error": [], "warning": [] }, { - "query": "row var = asin(to_integer(true))", + "query": "from a_index | enrich policy | eval otherField", "error": [], "warning": [] }, { - "query": "row var = asin(true)", - "error": [ - "Argument of [asin] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | enrich policy with var0 = otherField | eval var0", + "error": [], "warning": [] }, { - "query": "from a_index | where asin(booleanField) > 0", + "query": "from a_index | enrich my-pol*", "error": [ - "Argument of [asin] must be [number], found value [booleanField] type [boolean]" + "Using wildcards (*) in ENRICH is not allowed [my-pol*]" ], "warning": [] }, { - "query": "from a_index | eval var = asin(to_integer(booleanField))", + "query": "from a_index | eval textField = 5", "error": [], - "warning": [] + "warning": [ + "Column [textField] of type text has been overwritten as new type: integer" + ] }, { - "query": "from a_index | eval asin(booleanField)", - "error": [ - "Argument of [asin] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval doubleField = \"5\"", + "error": [], + "warning": [ + "Column [doubleField] of type double has been overwritten as new type: string" + ] + }, + { + "query": "from a_index | eval round(doubleField) + 1 | eval `round(doubleField) + 1` + 1 | keep ```round(doubleField) + 1`` + 1`", + "error": [], "warning": [] }, { - "query": "from a_index | eval asin(null)", + "query": "from a_index | eval round(doubleField) + 1 | eval `round(doubleField) + 1` + 1 | eval ```round(doubleField) + 1`` + 1` + 1 | keep ```````round(doubleField) + 1```` + 1`` + 1`", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval asin(nullVar)", + "query": "from a_index | eval round(doubleField) + 1 | eval `round(doubleField) + 1` + 1 | eval ```round(doubleField) + 1`` + 1` + 1 | eval ```````round(doubleField) + 1```` + 1`` + 1` + 1 | keep ```````````````round(doubleField) + 1```````` + 1```` + 1`` + 1`", "error": [], "warning": [] }, { - "query": "row var = atan(5)", + "query": "from a_index | eval round(doubleField) + 1 | eval `round(doubleField) + 1` + 1 | eval ```round(doubleField) + 1`` + 1` + 1 | eval ```````round(doubleField) + 1```` + 1`` + 1` + 1 | eval ```````````````round(doubleField) + 1```````` + 1```` + 1`` + 1` + 1 | keep ```````````````````````````````round(doubleField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`", "error": [], "warning": [] }, { - "query": "row atan(5)", + "query": "from a_index | eval 1::keyword", "error": [], "warning": [] }, { - "query": "row var = atan(to_integer(\"a\"))", + "query": "from a_index | eval 1::keyword::long::double", "error": [], "warning": [] }, { - "query": "row var = atan(\"a\")", + "query": "from a_index | eval trim(\"23\"::double)", "error": [ - "Argument of [atan] must be [number], found value [\"a\"] type [string]" + "Argument of [trim] must be [keyword], found value [\"23\"::double] type [double]" ], "warning": [] }, { - "query": "from a_index | where atan(numberField) > 0", + "query": "from a_index | eval trim(23::keyword)", "error": [], "warning": [] }, { - "query": "from a_index | where atan(stringField) > 0", - "error": [ - "Argument of [atan] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval 1 + \"2\"::long", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan(numberField)", + "query": "from a_index | eval 1 + \"2\"::LONG", "error": [], "warning": [] }, { - "query": "from a_index | eval atan(numberField)", + "query": "from a_index | eval 1 + \"2\"::Long", "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan(to_integer(stringField))", + "query": "from a_index | eval 1 + \"2\"::LoNg", "error": [], "warning": [] }, { - "query": "from a_index | eval atan(stringField)", + "query": "from a_index | eval 1 + \"2\"", "error": [ - "Argument of [atan] must be [number], found value [stringField] type [string]" + "Argument of [+] must be [date_period], found value [1] type [integer]" ], "warning": [] }, { - "query": "from a_index | eval atan(numberField, extraArg)", + "query": "from a_index | eval trim(to_double(\"23\")::keyword::double::long::keyword::double)", "error": [ - "Error: [atan] function expects exactly one argument, got 2." + "Argument of [trim] must be [keyword], found value [to_double(\"23\")::keyword::double::long::keyword::double] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = atan(*)", - "error": [ - "Using wildcards (*) in atan is not allowed" - ], + "query": "from a_index | eval CEIL(23::long)", + "error": [], "warning": [] }, { - "query": "from a_index | sort atan(numberField)", + "query": "from a_index | eval CEIL(23::unsigned_long)", "error": [], "warning": [] }, { - "query": "row var = atan(to_integer(true))", + "query": "from a_index | eval CEIL(23::int)", "error": [], "warning": [] }, { - "query": "row var = atan(true)", - "error": [ - "Argument of [atan] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval CEIL(23::integer)", + "error": [], "warning": [] }, { - "query": "from a_index | where atan(booleanField) > 0", - "error": [ - "Argument of [atan] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval CEIL(23::Integer)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan(to_integer(booleanField))", + "query": "from a_index | eval CEIL(23::double)", "error": [], "warning": [] }, { - "query": "from a_index | eval atan(booleanField)", + "query": "from a_index | eval CEIL(23::DOUBLE)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval CEIL(23::doubla)", "error": [ - "Argument of [atan] must be [number], found value [booleanField] type [boolean]" + "Argument of [ceil] must be [double], found value [23::doubla] type [doubla]" ], "warning": [] }, { - "query": "from a_index | eval atan(null)", + "query": "from a_index | eval TRIM(23::keyword)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval atan(nullVar)", + "query": "from a_index | eval TRIM(23::text)", "error": [], "warning": [] }, { - "query": "row var = atan2(5, 5)", + "query": "from a_index | eval TRIM(23::keyword)", "error": [], "warning": [] }, { - "query": "row atan2(5, 5)", + "query": "from a_index | eval true AND \"false\"::boolean", "error": [], "warning": [] }, { - "query": "row var = atan2(to_integer(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval true AND \"false\"::bool", "error": [], "warning": [] }, { - "query": "row var = atan2(\"a\", \"a\")", + "query": "from a_index | eval true AND \"false\"", "error": [ - "Argument of [atan2] must be [number], found value [\"a\"] type [string]", - "Argument of [atan2] must be [number], found value [\"a\"] type [string]" + "Argument of [and] must be [boolean], found value [\"false\"] type [string]" ], "warning": [] }, { - "query": "from a_index | where atan2(numberField, numberField) > 0", - "error": [], + "query": "from a_index | eval to_lower(trim(doubleField)::keyword)", + "error": [ + "Argument of [trim] must be [keyword], found value [doubleField] type [double]" + ], "warning": [] }, { - "query": "from a_index | where atan2(stringField, stringField) > 0", + "query": "from a_index | eval to_upper(trim(doubleField)::keyword::keyword::keyword::keyword)", "error": [ - "Argument of [atan2] must be [number], found value [stringField] type [string]", - "Argument of [atan2] must be [number], found value [stringField] type [string]" + "Argument of [trim] must be [keyword], found value [doubleField] type [double]" ], "warning": [] }, { - "query": "from a_index | eval var = atan2(numberField, numberField)", - "error": [], + "query": "from a_index | eval to_lower(to_upper(trim(doubleField)::keyword)::keyword)", + "error": [ + "Argument of [trim] must be [keyword], found value [doubleField] type [double]" + ], "warning": [] }, { - "query": "from a_index | eval atan2(numberField, numberField)", + "query": "row var = abs(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan2(to_integer(stringField), to_integer(stringField))", + "query": "row abs(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval atan2(stringField, stringField)", - "error": [ - "Argument of [atan2] must be [number], found value [stringField] type [string]", - "Argument of [atan2] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval atan2(numberField, numberField, extraArg)", - "error": [ - "Error: [atan2] function expects exactly 2 arguments, got 3." - ], + "query": "row var = abs(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort atan2(numberField, numberField)", + "query": "row var = abs(5)", "error": [], "warning": [] }, { - "query": "row var = atan2(to_integer(true), to_integer(true))", + "query": "row abs(5)", "error": [], "warning": [] }, { - "query": "row var = atan2(true, true)", - "error": [ - "Argument of [atan2] must be [number], found value [true] type [boolean]", - "Argument of [atan2] must be [number], found value [true] type [boolean]" - ], + "query": "row var = abs(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where atan2(booleanField, booleanField) > 0", + "query": "row var = abs(true)", "error": [ - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]", - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]" + "Argument of [abs] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = atan2(to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | where abs(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval atan2(booleanField, booleanField)", + "query": "from a_index | where abs(booleanField) > 0", "error": [ - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]", - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]" + "Argument of [abs] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval atan2(null, null)", + "query": "from a_index | where abs(integerField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval atan2(nullVar, nullVar)", + "query": "from a_index | where abs(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = case(true, \"a\")", + "query": "from a_index | where abs(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row case(true, \"a\")", + "query": "from a_index | eval var = abs(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = case(booleanField, stringField)", + "query": "from a_index | eval abs(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval case(booleanField, stringField)", + "query": "from a_index | eval var = abs(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | sort case(booleanField, stringField)", - "error": [], + "query": "from a_index | eval abs(booleanField)", + "error": [ + "Argument of [abs] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = case(to_cartesianpoint(\"POINT (30 10)\"), true)", + "query": "from a_index | eval var = abs(*)", "error": [ - "Argument of [case] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Using wildcards (*) in abs is not allowed" ], "warning": [] }, { - "query": "from a_index | eval case(null, null)", + "query": "from a_index | eval var = abs(integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval case(nullVar, nullVar)", + "query": "from a_index | eval abs(integerField)", "error": [], "warning": [] }, { - "query": "row var = ceil(5)", + "query": "from a_index | eval var = abs(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row ceil(5)", + "query": "from a_index | eval var = abs(longField)", "error": [], "warning": [] }, { - "query": "row var = ceil(to_integer(\"a\"))", + "query": "from a_index | eval abs(longField)", "error": [], "warning": [] }, { - "query": "row var = ceil(\"a\")", - "error": [ - "Argument of [ceil] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval var = abs(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | where ceil(numberField) > 0", + "query": "from a_index | eval abs(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | where ceil(stringField) > 0", + "query": "from a_index | eval abs(doubleField, extraArg)", "error": [ - "Argument of [ceil] must be [number], found value [stringField] type [string]" + "Error: [abs] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = ceil(numberField)", + "query": "from a_index | sort abs(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(numberField)", + "query": "from a_index | eval abs(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = ceil(to_integer(stringField))", + "query": "row nullVar = null | eval abs(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(stringField)", - "error": [ - "Argument of [ceil] must be [number], found value [stringField] type [string]" - ], + "query": "row var = acos(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(numberField, extraArg)", - "error": [ - "Error: [ceil] function expects exactly one argument, got 2." - ], + "query": "row acos(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ceil(*)", - "error": [ - "Using wildcards (*) in ceil is not allowed" - ], + "query": "row var = acos(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort ceil(numberField)", + "query": "row var = acos(5)", "error": [], "warning": [] }, { - "query": "row var = ceil(to_integer(true))", + "query": "row acos(5)", "error": [], "warning": [] }, { - "query": "row var = ceil(true)", - "error": [ - "Argument of [ceil] must be [number], found value [true] type [boolean]" - ], + "query": "row var = acos(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where ceil(booleanField) > 0", + "query": "row var = acos(true)", "error": [ - "Argument of [ceil] must be [number], found value [booleanField] type [boolean]" + "Argument of [acos] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = ceil(to_integer(booleanField))", + "query": "from a_index | where acos(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(booleanField)", + "query": "from a_index | where acos(booleanField) > 0", "error": [ - "Argument of [ceil] must be [number], found value [booleanField] type [boolean]" + "Argument of [acos] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval ceil(null)", + "query": "from a_index | where acos(integerField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval ceil(nullVar)", + "query": "from a_index | where acos(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = cidr_match(to_ip(\"127.0.0.1\"), \"a\")", + "query": "from a_index | where acos(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row cidr_match(to_ip(\"127.0.0.1\"), \"a\")", + "query": "from a_index | eval var = acos(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval acos(doubleField)", "error": [], "warning": [] }, { - "query": "row var = cidr_match(to_ip(\"a\"), to_string(\"a\"))", + "query": "from a_index | eval var = acos(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = cidr_match(\"a\", 5)", + "query": "from a_index | eval acos(booleanField)", "error": [ - "Argument of [cidr_match] must be [ip], found value [\"a\"] type [string]", - "Argument of [cidr_match] must be [string], found value [5] type [number]" + "Argument of [acos] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = cidr_match(ipField, stringField)", - "error": [], + "query": "from a_index | eval var = acos(*)", + "error": [ + "Using wildcards (*) in acos is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval cidr_match(ipField, stringField)", + "query": "from a_index | eval var = acos(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = cidr_match(to_ip(stringField), to_string(stringField))", + "query": "from a_index | eval acos(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cidr_match(stringField, numberField)", - "error": [ - "Argument of [cidr_match] must be [ip], found value [stringField] type [string]", - "Argument of [cidr_match] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = acos(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort cidr_match(ipField, stringField)", + "query": "from a_index | eval var = acos(longField)", "error": [], "warning": [] }, { - "query": "row var = cidr_match(to_ip(to_ip(\"127.0.0.1\")), to_string(true))", + "query": "from a_index | eval acos(longField)", "error": [], "warning": [] }, { - "query": "row var = cidr_match(true, true)", - "error": [ - "Argument of [cidr_match] must be [ip], found value [true] type [boolean]", - "Argument of [cidr_match] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = acos(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cidr_match(to_ip(ipField), to_string(booleanField))", + "query": "from a_index | eval acos(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cidr_match(booleanField, booleanField)", + "query": "from a_index | eval acos(doubleField, extraArg)", "error": [ - "Argument of [cidr_match] must be [ip], found value [booleanField] type [boolean]", - "Argument of [cidr_match] must be [string], found value [booleanField] type [boolean]" + "Error: [acos] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval cidr_match(null, null)", + "query": "from a_index | sort acos(doubleField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval cidr_match(nullVar, nullVar)", + "query": "from a_index | eval acos(null)", "error": [], "warning": [] }, { - "query": "row var = coalesce(5)", + "query": "row nullVar = null | eval acos(nullVar)", "error": [], "warning": [] }, { - "query": "row coalesce(5)", + "query": "row var = asin(5.5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_integer(true))", + "query": "row asin(5.5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(5, 5)", + "query": "row var = asin(to_double(true))", "error": [], "warning": [] }, { - "query": "row coalesce(5, 5)", + "query": "row var = asin(5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_integer(true), to_integer(true))", + "query": "row asin(5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(now())", + "query": "row var = asin(to_integer(true))", "error": [], "warning": [] }, { - "query": "row coalesce(now())", - "error": [], + "query": "row var = asin(true)", + "error": [ + "Argument of [asin] must be [double], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_datetime(now()))", + "query": "from a_index | where asin(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = coalesce(now(), now())", - "error": [], + "query": "from a_index | where asin(booleanField) > 0", + "error": [ + "Argument of [asin] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row coalesce(now(), now())", + "query": "from a_index | where asin(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_datetime(now()), to_datetime(now()))", + "query": "from a_index | where asin(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = coalesce(\"a\")", + "query": "from a_index | where asin(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row coalesce(\"a\")", + "query": "from a_index | eval var = asin(doubleField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_string(true))", + "query": "from a_index | eval asin(doubleField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(\"a\", \"a\")", + "query": "from a_index | eval var = asin(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row coalesce(\"a\", \"a\")", - "error": [], + "query": "from a_index | eval asin(booleanField)", + "error": [ + "Argument of [asin] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_string(true), to_string(true))", - "error": [], + "query": "from a_index | eval var = asin(*)", + "error": [ + "Using wildcards (*) in asin is not allowed" + ], "warning": [] }, { - "query": "row var = coalesce(true)", + "query": "from a_index | eval var = asin(integerField)", "error": [], "warning": [] }, { - "query": "row coalesce(true)", + "query": "from a_index | eval asin(integerField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_boolean(true))", + "query": "from a_index | eval var = asin(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(true, true)", + "query": "from a_index | eval var = asin(longField)", "error": [], "warning": [] }, { - "query": "row coalesce(true, true)", + "query": "from a_index | eval asin(longField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_boolean(true), to_boolean(true))", + "query": "from a_index | eval var = asin(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval asin(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row coalesce(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "from a_index | eval asin(doubleField, extraArg)", + "error": [ + "Error: [asin] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = coalesce(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | sort asin(doubleField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval asin(null)", "error": [], "warning": [] }, { - "query": "row coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "row nullVar = null | eval asin(nullVar)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", + "query": "row var = atan(5.5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row atan(5.5)", "error": [], "warning": [] }, { - "query": "row coalesce(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = atan(to_double(true))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = atan(5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row atan(5)", "error": [], "warning": [] }, { - "query": "row coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = atan(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = atan(true)", + "error": [ + "Argument of [atan] must be [double], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | where atan(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row coalesce(to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where atan(booleanField) > 0", + "error": [ + "Argument of [atan] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | where atan(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | where atan(longField) > 0", "error": [], "warning": [] }, { - "query": "row coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | where atan(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = atan(doubleField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval atan(doubleField)", "error": [], "warning": [] }, { - "query": "row coalesce(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = atan(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval atan(booleanField)", + "error": [ + "Argument of [atan] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval var = atan(*)", + "error": [ + "Using wildcards (*) in atan is not allowed" + ], "warning": [] }, { - "query": "row coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = atan(integerField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval atan(integerField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = atan(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row coalesce(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = atan(longField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval atan(longField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = atan(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval atan(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval atan(doubleField, extraArg)", + "error": [ + "Error: [atan] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = coalesce(to_version(\"1.0.0\"))", + "query": "from a_index | sort atan(doubleField)", "error": [], "warning": [] }, { - "query": "row coalesce(to_version(\"1.0.0\"))", + "query": "from a_index | eval atan(null)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_version(\"a\"))", + "query": "row nullVar = null | eval atan(nullVar)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "row var = atan2(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "row atan2(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_version(\"a\"), to_version(\"a\"))", + "query": "row var = atan2(to_double(true), to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | where coalesce(numberField) > 0", + "query": "row var = atan2(5.5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | where coalesce(numberField, numberField) > 0", + "query": "row atan2(5.5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | where length(coalesce(stringField)) > 0", + "query": "row var = atan2(to_double(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | where length(coalesce(stringField, stringField)) > 0", + "query": "row var = atan2(to_double(true), 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(numberField)", + "query": "row var = atan2(5, 5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(numberField)", + "query": "row atan2(5, 5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_integer(booleanField))", + "query": "row var = atan2(to_integer(true), to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(numberField, numberField)", + "query": "row var = atan2(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(numberField, numberField)", + "query": "row atan2(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_integer(booleanField), to_integer(booleanField))", + "query": "row var = atan2(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(dateField)", + "query": "row var = atan2(to_integer(true), 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(dateField)", + "query": "row var = atan2(5, to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_datetime(dateField))", + "query": "row var = atan2(5, to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(dateField, dateField)", - "error": [], + "query": "row var = atan2(true, true)", + "error": [ + "Argument of [atan2] must be [double], found value [true] type [boolean]", + "Argument of [atan2] must be [double], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(dateField, dateField)", + "query": "from a_index | where atan2(doubleField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_datetime(dateField), to_datetime(dateField))", - "error": [], + "query": "from a_index | where atan2(booleanField, booleanField) > 0", + "error": [ + "Argument of [atan2] must be [double], found value [booleanField] type [boolean]", + "Argument of [atan2] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(stringField)", + "query": "from a_index | where atan2(doubleField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(stringField)", + "query": "from a_index | where atan2(doubleField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_string(booleanField))", + "query": "from a_index | where atan2(doubleField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(stringField, stringField)", + "query": "from a_index | where atan2(integerField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(stringField, stringField)", + "query": "from a_index | where atan2(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | where atan2(integerField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(booleanField)", + "query": "from a_index | where atan2(integerField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(booleanField)", + "query": "from a_index | where atan2(longField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_boolean(booleanField))", + "query": "from a_index | where atan2(longField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(booleanField, booleanField)", + "query": "from a_index | where atan2(longField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(booleanField, booleanField)", + "query": "from a_index | where atan2(longField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_boolean(booleanField), to_boolean(booleanField))", + "query": "from a_index | where atan2(unsignedLongField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(ipField)", + "query": "from a_index | where atan2(unsignedLongField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(ipField)", + "query": "from a_index | where atan2(unsignedLongField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_ip(ipField))", + "query": "from a_index | where atan2(unsignedLongField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(ipField, ipField)", + "query": "from a_index | eval var = atan2(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(ipField, ipField)", + "query": "from a_index | eval atan2(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_ip(ipField), to_ip(ipField))", + "query": "from a_index | eval var = atan2(to_double(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianPointField)", - "error": [], + "query": "from a_index | eval atan2(booleanField, booleanField)", + "error": [ + "Argument of [atan2] must be [double], found value [booleanField] type [boolean]", + "Argument of [atan2] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianPointField)", + "query": "from a_index | eval var = atan2(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval atan2(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval var = atan2(to_double(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval var = atan2(doubleField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval atan2(doubleField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianShapeField)", + "query": "from a_index | eval var = atan2(to_double(booleanField), longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianShapeField)", + "query": "from a_index | eval var = atan2(doubleField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval atan2(doubleField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianShapeField, cartesianShapeField)", + "query": "from a_index | eval var = atan2(to_double(booleanField), unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianShapeField, cartesianShapeField)", + "query": "from a_index | eval var = atan2(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval atan2(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoPointField)", + "query": "from a_index | eval var = atan2(to_integer(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoPointField)", + "query": "from a_index | eval var = atan2(integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geopoint(geoPointField))", + "query": "from a_index | eval atan2(integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoPointField, geoPointField)", + "query": "from a_index | eval var = atan2(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoPointField, geoPointField)", + "query": "from a_index | eval var = atan2(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval atan2(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoShapeField)", + "query": "from a_index | eval var = atan2(to_integer(booleanField), longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoShapeField)", + "query": "from a_index | eval var = atan2(integerField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geoshape(geoPointField))", + "query": "from a_index | eval atan2(integerField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoShapeField, geoShapeField)", + "query": "from a_index | eval var = atan2(to_integer(booleanField), unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoShapeField, geoShapeField)", + "query": "from a_index | eval var = atan2(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval atan2(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(versionField)", + "query": "from a_index | eval var = atan2(longField, to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(versionField)", + "query": "from a_index | eval var = atan2(longField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_version(stringField))", + "query": "from a_index | eval atan2(longField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(versionField, versionField)", + "query": "from a_index | eval var = atan2(longField, to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(versionField, versionField)", + "query": "from a_index | eval var = atan2(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_version(stringField), to_version(stringField))", + "query": "from a_index | eval atan2(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | sort coalesce(numberField)", + "query": "from a_index | eval var = atan2(longField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(null)", + "query": "from a_index | eval atan2(longField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval coalesce(nullVar)", + "query": "from a_index | eval var = atan2(unsignedLongField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | sort coalesce(booleanField)", + "query": "from a_index | eval atan2(unsignedLongField, doubleField)", "error": [], "warning": [] }, { - "query": "row var = concat(\"a\", \"a\")", + "query": "from a_index | eval var = atan2(unsignedLongField, to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row concat(\"a\", \"a\")", + "query": "from a_index | eval var = atan2(unsignedLongField, integerField)", "error": [], "warning": [] }, { - "query": "row var = concat(to_string(\"a\"), to_string(\"a\"))", + "query": "from a_index | eval atan2(unsignedLongField, integerField)", "error": [], "warning": [] }, { - "query": "row var = concat(5, 5)", - "error": [ - "Argument of [concat] must be [string], found value [5] type [number]", - "Argument of [concat] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | where length(concat(stringField, stringField)) > 0", + "query": "from a_index | eval var = atan2(unsignedLongField, to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(concat(numberField, numberField)) > 0", - "error": [ - "Argument of [concat] must be [string], found value [numberField] type [number]", - "Argument of [concat] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = atan2(unsignedLongField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = concat(stringField, stringField)", + "query": "from a_index | eval atan2(unsignedLongField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval concat(stringField, stringField)", + "query": "from a_index | eval var = atan2(unsignedLongField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = concat(to_string(stringField), to_string(stringField))", + "query": "from a_index | eval atan2(unsignedLongField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval concat(numberField, numberField)", + "query": "from a_index | eval atan2(doubleField, doubleField, extraArg)", "error": [ - "Argument of [concat] must be [string], found value [numberField] type [number]", - "Argument of [concat] must be [string], found value [numberField] type [number]" + "Error: [atan2] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | sort concat(stringField, stringField)", + "query": "from a_index | sort atan2(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row var = concat(to_string(true), to_string(true))", + "query": "from a_index | eval atan2(null, null)", "error": [], "warning": [] }, { - "query": "row var = concat(true, true)", - "error": [ - "Argument of [concat] must be [string], found value [true] type [boolean]", - "Argument of [concat] must be [string], found value [true] type [boolean]" - ], + "query": "row nullVar = null | eval atan2(nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(concat(booleanField, booleanField)) > 0", - "error": [ - "Argument of [concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [concat] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = cbrt(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = concat(to_string(booleanField), to_string(booleanField))", + "query": "row cbrt(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval concat(booleanField, booleanField)", - "error": [ - "Argument of [concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [concat] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = cbrt(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval concat(null, null)", + "query": "row var = cbrt(5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval concat(nullVar, nullVar)", + "query": "row cbrt(5)", "error": [], "warning": [] }, { - "query": "row var = cos(5)", + "query": "row var = cbrt(to_integer(true))", "error": [], "warning": [] }, { - "query": "row cos(5)", - "error": [], + "query": "row var = cbrt(true)", + "error": [ + "Argument of [cbrt] must be [double], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = cos(to_integer(\"a\"))", + "query": "from a_index | where cbrt(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = cos(\"a\")", + "query": "from a_index | where cbrt(booleanField) > 0", "error": [ - "Argument of [cos] must be [number], found value [\"a\"] type [string]" + "Argument of [cbrt] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where cos(numberField) > 0", + "query": "from a_index | where cbrt(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where cos(stringField) > 0", - "error": [ - "Argument of [cos] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | where cbrt(longField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cos(numberField)", + "query": "from a_index | where cbrt(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval cos(numberField)", + "query": "from a_index | eval var = cbrt(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = cos(to_integer(stringField))", + "query": "from a_index | eval cbrt(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cos(stringField)", - "error": [ - "Argument of [cos] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = cbrt(to_double(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval cos(numberField, extraArg)", + "query": "from a_index | eval cbrt(booleanField)", "error": [ - "Error: [cos] function expects exactly one argument, got 2." + "Argument of [cbrt] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = cos(*)", + "query": "from a_index | eval var = cbrt(*)", "error": [ - "Using wildcards (*) in cos is not allowed" + "Using wildcards (*) in cbrt is not allowed" ], "warning": [] }, { - "query": "from a_index | sort cos(numberField)", + "query": "from a_index | eval var = cbrt(integerField)", "error": [], "warning": [] }, { - "query": "row var = cos(to_integer(true))", + "query": "from a_index | eval cbrt(integerField)", "error": [], "warning": [] }, { - "query": "row var = cos(true)", - "error": [ - "Argument of [cos] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = cbrt(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | where cos(booleanField) > 0", - "error": [ - "Argument of [cos] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = cbrt(longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cos(to_integer(booleanField))", + "query": "from a_index | eval cbrt(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cos(booleanField)", - "error": [ - "Argument of [cos] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = cbrt(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval cos(null)", + "query": "from a_index | eval cbrt(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval cos(nullVar)", - "error": [], + "query": "from a_index | eval cbrt(doubleField, extraArg)", + "error": [ + "Error: [cbrt] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = cosh(5)", + "query": "from a_index | sort cbrt(doubleField)", "error": [], "warning": [] }, { - "query": "row cosh(5)", + "query": "from a_index | eval cbrt(null)", "error": [], "warning": [] }, { - "query": "row var = cosh(to_integer(\"a\"))", + "query": "row nullVar = null | eval cbrt(nullVar)", "error": [], "warning": [] }, { - "query": "row var = cosh(\"a\")", - "error": [ - "Argument of [cosh] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = ceil(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | where cosh(numberField) > 0", + "query": "row ceil(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | where cosh(stringField) > 0", - "error": [ - "Argument of [cosh] must be [number], found value [stringField] type [string]" - ], + "query": "row var = ceil(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cosh(numberField)", + "query": "row var = ceil(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval cosh(numberField)", + "query": "row ceil(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = cosh(to_integer(stringField))", + "query": "row var = ceil(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval cosh(stringField)", + "query": "row var = ceil(true)", "error": [ - "Argument of [cosh] must be [number], found value [stringField] type [string]" + "Argument of [ceil] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval cosh(numberField, extraArg)", - "error": [ - "Error: [cosh] function expects exactly one argument, got 2." - ], + "query": "from a_index | where ceil(doubleField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cosh(*)", + "query": "from a_index | where ceil(booleanField) > 0", "error": [ - "Using wildcards (*) in cosh is not allowed" + "Argument of [ceil] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort cosh(numberField)", + "query": "from a_index | where ceil(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = cosh(to_integer(true))", + "query": "from a_index | where ceil(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = cosh(true)", - "error": [ - "Argument of [cosh] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | where ceil(unsignedLongField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | where cosh(booleanField) > 0", - "error": [ - "Argument of [cosh] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = ceil(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cosh(to_integer(booleanField))", + "query": "from a_index | eval ceil(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cosh(booleanField)", + "query": "from a_index | eval var = ceil(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval ceil(booleanField)", "error": [ - "Argument of [cosh] must be [number], found value [booleanField] type [boolean]" + "Argument of [ceil] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval cosh(null)", - "error": [], + "query": "from a_index | eval var = ceil(*)", + "error": [ + "Using wildcards (*) in ceil is not allowed" + ], "warning": [] }, { - "query": "row nullVar = null | eval cosh(nullVar)", + "query": "from a_index | eval var = ceil(integerField)", "error": [], "warning": [] }, { - "query": "row var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", now())", + "query": "from a_index | eval ceil(integerField)", "error": [], "warning": [] }, { - "query": "row date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", now())", + "query": "from a_index | eval var = ceil(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(\"SOME_RANDOM_STRING\", now())", + "query": "from a_index | eval var = ceil(longField)", "error": [], - "warning": [ - "Invalid option [\"SOME_RANDOM_STRING\"] for date_extract. Supported options: [\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", \"ALIGNED_DAY_OF_WEEK_IN_YEAR\", \"ALIGNED_WEEK_OF_MONTH\", \"ALIGNED_WEEK_OF_YEAR\", \"AMPM_OF_DAY\", \"CLOCK_HOUR_OF_AMPM\", \"CLOCK_HOUR_OF_DAY\", \"DAY_OF_MONTH\", \"DAY_OF_WEEK\", \"DAY_OF_YEAR\", \"EPOCH_DAY\", \"ERA\", \"HOUR_OF_AMPM\", \"HOUR_OF_DAY\", \"INSTANT_SECONDS\", \"MICRO_OF_DAY\", \"MICRO_OF_SECOND\", \"MILLI_OF_DAY\", \"MILLI_OF_SECOND\", \"MINUTE_OF_DAY\", \"MINUTE_OF_HOUR\", \"MONTH_OF_YEAR\", \"NANO_OF_DAY\", \"NANO_OF_SECOND\", \"OFFSET_SECONDS\", \"PROLEPTIC_MONTH\", \"SECOND_OF_DAY\", \"SECOND_OF_MINUTE\", \"YEAR\", \"YEAR_OF_ERA\"]." - ] + "warning": [] }, { - "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", + "query": "from a_index | eval ceil(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", + "query": "from a_index | eval var = ceil(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(stringField))", + "query": "from a_index | eval ceil(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(stringField, stringField)", + "query": "from a_index | eval ceil(doubleField, extraArg)", "error": [ - "Argument of [date_extract] must be [date], found value [stringField] type [string]" + "Error: [ceil] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField, extraArg)", - "error": [ - "Error: [date_extract] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | sort date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", + "query": "from a_index | sort ceil(doubleField)", "error": [], "warning": [] }, { - "query": "row var = date_extract(true, true)", - "error": [ - "Argument of [date_extract] must be [string], found value [true] type [boolean]", - "Argument of [date_extract] must be [date], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(dateField))", + "query": "from a_index | eval ceil(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(booleanField, booleanField)", - "error": [ - "Argument of [date_extract] must be [string], found value [booleanField] type [boolean]", - "Argument of [date_extract] must be [date], found value [booleanField] type [boolean]" - ], + "query": "row nullVar = null | eval ceil(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(null, null)", + "query": "row var = cidr_match(to_ip(\"127.0.0.1\"), \"a\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_extract(nullVar, nullVar)", + "query": "row cidr_match(to_ip(\"127.0.0.1\"), \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", \"2022\")", + "query": "row var = cidr_match(to_ip(to_ip(\"127.0.0.1\")), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", concat(\"20\", \"22\"))", + "query": "row var = cidr_match(true, true)", "error": [ - "Argument of [date_extract] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [cidr_match] must be [ip], found value [true] type [boolean]", + "Argument of [cidr_match] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "row var = date_format(\"a\", now())", + "query": "from a_index | eval var = cidr_match(ipField, keywordField)", "error": [], "warning": [] }, { - "query": "row date_format(\"a\", now())", + "query": "from a_index | eval cidr_match(ipField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_format(stringField, dateField)", + "query": "from a_index | eval var = cidr_match(to_ip(ipField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, dateField)", + "query": "from a_index | eval cidr_match(booleanField, booleanField)", + "error": [ + "Argument of [cidr_match] must be [ip], found value [booleanField] type [boolean]", + "Argument of [cidr_match] must be [keyword], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = cidr_match(ipField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_format(to_string(stringField), to_datetime(stringField))", + "query": "from a_index | eval cidr_match(ipField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, numberField)", - "error": [ - "Argument of [date_format] must be [date], found value [numberField] type [number]" - ], + "query": "from a_index | sort cidr_match(ipField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, dateField, extraArg)", - "error": [ - "Error: [date_format] function expects no more than 2 arguments, got 3." - ], + "query": "from a_index | eval cidr_match(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | sort date_format(stringField, dateField)", + "query": "row nullVar = null | eval cidr_match(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = date_format(true, true)", - "error": [ - "Argument of [date_format] must be [string], found value [true] type [boolean]", - "Argument of [date_format] must be [date], found value [true] type [boolean]" - ], + "query": "row var = coalesce(true)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_format(to_string(booleanField), to_datetime(dateField))", + "query": "row coalesce(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(booleanField, booleanField)", - "error": [ - "Argument of [date_format] must be [string], found value [booleanField] type [boolean]", - "Argument of [date_format] must be [date], found value [booleanField] type [boolean]" - ], + "query": "row var = coalesce(to_boolean(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(null, null)", + "query": "row var = coalesce(true, true)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_format(nullVar, nullVar)", + "query": "row coalesce(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, \"2022\")", + "query": "row var = coalesce(to_boolean(true), to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, concat(\"20\", \"22\"))", + "query": "row var = coalesce(cartesianPointField, cartesianPointField)", "error": [ - "Argument of [date_format] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "row var = date_parse(\"a\", \"a\")", - "error": [], + "query": "row coalesce(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = date_parse(\"a\")", - "error": [], + "query": "row var = coalesce(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row date_parse(\"a\", \"a\")", + "query": "row var = coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = date_parse(to_string(\"a\"), to_string(\"a\"))", + "query": "row coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = date_parse(5, 5)", + "query": "row var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [ - "Argument of [date_parse] must be [string], found value [5] type [number]", - "Argument of [date_parse] must be [string], found value [5] type [number]" + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = date_parse(stringField)", + "query": "row var = coalesce(to_datetime(\"2021-01-01T00:00:00Z\"), to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_parse(stringField, stringField)", + "query": "row coalesce(to_datetime(\"2021-01-01T00:00:00Z\"), to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_parse(stringField, stringField)", + "query": "row var = coalesce(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")), to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_parse(to_string(stringField), to_string(stringField))", - "error": [], + "query": "row var = coalesce(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval date_parse(numberField, numberField)", + "query": "row coalesce(geoPointField, geoPointField)", "error": [ - "Argument of [date_parse] must be [string], found value [numberField] type [number]", - "Argument of [date_parse] must be [string], found value [numberField] type [number]" + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval date_parse(stringField, stringField, extraArg)", + "query": "row var = coalesce(to_geopoint(geoPointField), to_geopoint(geoPointField))", "error": [ - "Error: [date_parse] function expects no more than 2 arguments, got 3." + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | sort date_parse(stringField, stringField)", + "query": "row var = coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = date_parse(to_string(true), to_string(true))", + "query": "row coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = date_parse(true, true)", + "query": "row var = coalesce(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [ - "Argument of [date_parse] must be [string], found value [true] type [boolean]", - "Argument of [date_parse] must be [string], found value [true] type [boolean]" + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = date_parse(to_string(booleanField), to_string(booleanField))", + "query": "row var = coalesce(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_parse(booleanField, booleanField)", - "error": [ - "Argument of [date_parse] must be [string], found value [booleanField] type [boolean]", - "Argument of [date_parse] must be [string], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval date_parse(null, null)", + "query": "row coalesce(5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_parse(nullVar, nullVar)", + "query": "row var = coalesce(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = date_trunc(1 year, now())", + "query": "row var = coalesce(5, 5)", "error": [], "warning": [] }, { - "query": "row date_trunc(1 year, now())", + "query": "row coalesce(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(1 year, dateField)", + "query": "row var = coalesce(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(1 year, dateField)", + "query": "row var = coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(1 year, to_datetime(stringField))", + "query": "row coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(stringField, stringField)", - "error": [ - "Argument of [date_trunc] must be [time_literal], found value [stringField] type [string]", - "Argument of [date_trunc] must be [date], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval date_trunc(1 year, dateField, extraArg)", - "error": [ - "Error: [date_trunc] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | sort date_trunc(1 year, dateField)", + "query": "row var = coalesce(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row var = date_trunc(now(), now())", + "query": "row var = coalesce(\"a\")", "error": [], "warning": [] }, { - "query": "row date_trunc(now(), now())", + "query": "row coalesce(\"a\")", "error": [], "warning": [] }, { - "query": "row var = date_trunc(true, true)", - "error": [ - "Argument of [date_trunc] must be [time_literal], found value [true] type [boolean]", - "Argument of [date_trunc] must be [date], found value [true] type [boolean]" - ], + "query": "row var = coalesce(to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(1 year, to_datetime(dateField))", + "query": "row var = coalesce(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(booleanField, booleanField)", - "error": [ - "Argument of [date_trunc] must be [time_literal], found value [booleanField] type [boolean]", - "Argument of [date_trunc] must be [date], found value [booleanField] type [boolean]" - ], + "query": "row coalesce(\"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(dateField, dateField)", + "query": "row var = coalesce(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(dateField, dateField)", + "query": "row var = coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(to_datetime(dateField), to_datetime(dateField))", + "query": "row coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(null, null)", + "query": "row var = coalesce(to_version(\"a\"), to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_trunc(nullVar, nullVar)", + "query": "row var = coalesce(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(1 year, \"2022\")", + "query": "from a_index | where coalesce(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(1 year, concat(\"20\", \"22\"))", + "query": "from a_index | where coalesce(counterDoubleField) > 0", "error": [ - "Argument of [date_trunc] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | eval date_trunc(\"2022\", \"2022\")", + "query": "from a_index | where coalesce(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(concat(\"20\", \"22\"), concat(\"20\", \"22\"))", + "query": "from a_index | where coalesce(counterDoubleField, counterDoubleField) > 0", "error": [ - "Argument of [date_trunc] must be [time_literal], found value [concat(\"20\",\"22\")] type [string]", - "Argument of [date_trunc] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]", + "Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "row var = e()", + "query": "from a_index | where coalesce(longField) > 0", "error": [], "warning": [] }, { - "query": "row e()", + "query": "from a_index | where coalesce(longField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where e() > 0", + "query": "from a_index | eval var = coalesce(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = e()", + "query": "from a_index | eval coalesce(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval e()", + "query": "from a_index | eval var = coalesce(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval e(extraArg)", + "query": "from a_index | eval coalesce(counterDoubleField)", "error": [ - "Error: [e] function expects exactly 0 arguments, got 1." + "Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | sort e()", + "query": "from a_index | eval var = coalesce(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval e()", + "query": "from a_index | eval coalesce(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "row var = ends_with(\"a\", \"a\")", + "query": "from a_index | eval var = coalesce(to_boolean(booleanField), to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row ends_with(\"a\", \"a\")", - "error": [], + "query": "from a_index | eval coalesce(counterDoubleField, counterDoubleField)", + "error": [ + "Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]", + "Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]" + ], "warning": [] }, { - "query": "row var = ends_with(to_string(\"a\"), to_string(\"a\"))", + "query": "from a_index | eval var = coalesce(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = ends_with(5, 5)", - "error": [ - "Argument of [ends_with] must be [string], found value [5] type [number]", - "Argument of [ends_with] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval coalesce(cartesianPointField, cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ends_with(stringField, stringField)", + "query": "from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(stringField, stringField)", + "query": "from a_index | eval var = coalesce(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = ends_with(to_string(stringField), to_string(stringField))", + "query": "from a_index | eval coalesce(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(numberField, numberField)", - "error": [ - "Argument of [ends_with] must be [string], found value [numberField] type [number]", - "Argument of [ends_with] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(stringField, stringField, extraArg)", - "error": [ - "Error: [ends_with] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = coalesce(dateField, dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort ends_with(stringField, stringField)", + "query": "from a_index | eval coalesce(dateField, dateField)", "error": [], "warning": [] }, { - "query": "row var = ends_with(to_string(true), to_string(true))", + "query": "from a_index | eval var = coalesce(to_datetime(dateField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = ends_with(true, true)", - "error": [ - "Argument of [ends_with] must be [string], found value [true] type [boolean]", - "Argument of [ends_with] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = coalesce(geoPointField, geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ends_with(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval coalesce(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(booleanField, booleanField)", - "error": [ - "Argument of [ends_with] must be [string], found value [booleanField] type [boolean]", - "Argument of [ends_with] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = coalesce(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(null, null)", + "query": "from a_index | eval var = coalesce(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval ends_with(nullVar, nullVar)", + "query": "from a_index | eval coalesce(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = floor(5)", + "query": "from a_index | eval var = coalesce(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row floor(5)", + "query": "from a_index | eval var = coalesce(integerField)", "error": [], "warning": [] }, { - "query": "row var = floor(to_integer(\"a\"))", + "query": "from a_index | eval coalesce(integerField)", "error": [], "warning": [] }, { - "query": "row var = floor(\"a\")", - "error": [ - "Argument of [floor] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval var = coalesce(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | where floor(numberField) > 0", + "query": "from a_index | eval var = coalesce(integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | where floor(stringField) > 0", - "error": [ - "Argument of [floor] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval coalesce(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = floor(numberField)", + "query": "from a_index | eval var = coalesce(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval floor(numberField)", + "query": "from a_index | eval var = coalesce(ipField, ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = floor(to_integer(stringField))", + "query": "from a_index | eval coalesce(ipField, ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval floor(stringField)", - "error": [ - "Argument of [floor] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = coalesce(to_ip(ipField), to_ip(ipField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval floor(numberField, extraArg)", - "error": [ - "Error: [floor] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = coalesce(keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = floor(*)", - "error": [ - "Using wildcards (*) in floor is not allowed" - ], + "query": "from a_index | eval coalesce(keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort floor(numberField)", + "query": "from a_index | eval var = coalesce(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = floor(to_integer(true))", + "query": "from a_index | eval var = coalesce(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = floor(true)", - "error": [ - "Argument of [floor] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval coalesce(keywordField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | where floor(booleanField) > 0", - "error": [ - "Argument of [floor] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = coalesce(to_string(booleanField), to_string(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = floor(to_integer(booleanField))", + "query": "from a_index | eval var = coalesce(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval floor(booleanField)", - "error": [ - "Argument of [floor] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval coalesce(longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval floor(null)", + "query": "from a_index | eval var = coalesce(longField, longField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval floor(nullVar)", + "query": "from a_index | eval coalesce(longField, longField)", "error": [], "warning": [] }, { - "query": "row var = greatest(\"a\")", + "query": "from a_index | eval var = coalesce(textField)", "error": [], "warning": [] }, { - "query": "row greatest(\"a\")", + "query": "from a_index | eval coalesce(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(stringField)", + "query": "from a_index | eval var = coalesce(textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(stringField)", + "query": "from a_index | eval coalesce(textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | sort greatest(stringField)", + "query": "from a_index | eval var = coalesce(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row var = greatest(true)", + "query": "from a_index | eval coalesce(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row greatest(true)", + "query": "from a_index | eval var = coalesce(to_version(keywordField), to_version(keywordField))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_boolean(true))", + "query": "from a_index | sort coalesce(booleanField)", "error": [], "warning": [] }, { - "query": "row var = greatest(true, true)", + "query": "from a_index | eval coalesce(null)", "error": [], "warning": [] }, { - "query": "row greatest(true, true)", + "query": "row nullVar = null | eval coalesce(nullVar)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_boolean(true), to_boolean(true))", + "query": "from a_index | eval coalesce(\"2022\", \"2022\")", "error": [], "warning": [] }, { - "query": "row var = greatest(5, 5)", + "query": "from a_index | eval coalesce(concat(\"20\", \"22\"), concat(\"20\", \"22\"))", "error": [], "warning": [] }, { - "query": "row greatest(5, 5)", + "query": "row var = coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_integer(true), to_integer(true))", + "query": "row coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = greatest(5)", + "query": "row var = coalesce(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row greatest(5)", + "query": "row var = coalesce(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_integer(true))", + "query": "row var = coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "row coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "row var = coalesce(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", + "query": "row var = coalesce(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_string(true))", + "query": "row var = concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = greatest(\"a\", \"a\")", + "query": "row concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row greatest(\"a\", \"a\")", + "query": "row var = concat(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_string(true), to_string(true))", - "error": [], + "query": "row var = concat(true, true)", + "error": [ + "Argument of [concat] must be [keyword], found value [true] type [boolean]", + "Argument of [concat] must be [keyword], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from a_index | eval var = concat(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from a_index | eval concat(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_version(\"a\"), to_version(\"a\"))", + "query": "from a_index | eval var = concat(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval concat(booleanField, booleanField)", "error": [ - "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Argument of [concat] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [concat] must be [keyword], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where greatest(numberField, numberField) > 0", + "query": "from a_index | eval var = concat(keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | where greatest(cartesianPointField, cartesianPointField) > 0", - "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval concat(keywordField, textField)", + "error": [], "warning": [] }, { - "query": "from a_index | where greatest(numberField) > 0", + "query": "from a_index | eval var = concat(textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | where greatest(cartesianPointField) > 0", - "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval concat(textField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(greatest(stringField)) > 0", + "query": "from a_index | eval var = concat(textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(greatest(cartesianPointField)) > 0", - "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval concat(textField, textField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(greatest(stringField, stringField)) > 0", + "query": "from a_index | sort concat(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(greatest(cartesianPointField, cartesianPointField)) > 0", - "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval concat(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(booleanField)", + "query": "row nullVar = null | eval concat(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(booleanField)", + "query": "row var = cos(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_boolean(booleanField))", + "query": "row cos(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(cartesianPointField)", - "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "row var = cos(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(booleanField, booleanField)", + "query": "row var = cos(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(booleanField, booleanField)", + "query": "row cos(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_boolean(booleanField), to_boolean(booleanField))", + "query": "row var = cos(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(cartesianPointField, cartesianPointField)", + "query": "row var = cos(true)", "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [cos] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = greatest(numberField, numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval greatest(numberField, numberField)", + "query": "from a_index | where cos(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_integer(booleanField), to_integer(booleanField))", - "error": [], + "query": "from a_index | where cos(booleanField) > 0", + "error": [ + "Argument of [cos] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = greatest(numberField)", + "query": "from a_index | where cos(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(numberField)", + "query": "from a_index | where cos(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_integer(booleanField))", + "query": "from a_index | where cos(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(ipField, ipField)", + "query": "from a_index | eval var = cos(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(ipField, ipField)", + "query": "from a_index | eval cos(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_ip(ipField), to_ip(ipField))", + "query": "from a_index | eval var = cos(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_string(booleanField))", - "error": [], + "query": "from a_index | eval cos(booleanField)", + "error": [ + "Argument of [cos] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = greatest(stringField, stringField)", - "error": [], + "query": "from a_index | eval var = cos(*)", + "error": [ + "Using wildcards (*) in cos is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval greatest(stringField, stringField)", + "query": "from a_index | eval var = cos(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval cos(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(versionField, versionField)", + "query": "from a_index | eval var = cos(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(versionField, versionField)", + "query": "from a_index | eval var = cos(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_version(stringField), to_version(stringField))", + "query": "from a_index | eval cos(longField)", "error": [], "warning": [] }, { - "query": "from a_index | sort greatest(booleanField)", + "query": "from a_index | eval var = cos(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(null)", + "query": "from a_index | eval cos(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval greatest(nullVar)", - "error": [], + "query": "from a_index | eval cos(doubleField, extraArg)", + "error": [ + "Error: [cos] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = least(\"a\")", + "query": "from a_index | sort cos(doubleField)", "error": [], "warning": [] }, { - "query": "row least(\"a\")", + "query": "from a_index | eval cos(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(stringField)", + "query": "row nullVar = null | eval cos(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(stringField)", + "query": "row var = cosh(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | sort least(stringField)", + "query": "row cosh(5.5)", "error": [], "warning": [] }, { - "query": "row var = least(true)", + "query": "row var = cosh(to_double(true))", "error": [], "warning": [] }, { - "query": "row least(true)", + "query": "row var = cosh(5)", "error": [], "warning": [] }, { - "query": "row var = least(to_boolean(true))", + "query": "row cosh(5)", "error": [], "warning": [] }, { - "query": "row var = least(true, true)", + "query": "row var = cosh(to_integer(true))", "error": [], "warning": [] }, { - "query": "row least(true, true)", - "error": [], + "query": "row var = cosh(true)", + "error": [ + "Argument of [cosh] must be [double], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = least(to_boolean(true), to_boolean(true))", + "query": "from a_index | where cosh(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = least(5, 5)", - "error": [], + "query": "from a_index | where cosh(booleanField) > 0", + "error": [ + "Argument of [cosh] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row least(5, 5)", + "query": "from a_index | where cosh(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = least(to_integer(true), to_integer(true))", + "query": "from a_index | where cosh(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = least(5)", + "query": "from a_index | where cosh(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row least(5)", + "query": "from a_index | eval var = cosh(doubleField)", "error": [], "warning": [] }, { - "query": "row var = least(to_integer(true))", + "query": "from a_index | eval cosh(doubleField)", "error": [], "warning": [] }, { - "query": "row var = least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = cosh(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", - "error": [], + "query": "from a_index | eval cosh(booleanField)", + "error": [ + "Argument of [cosh] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = least(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", - "error": [], + "query": "from a_index | eval var = cosh(*)", + "error": [ + "Using wildcards (*) in cosh is not allowed" + ], "warning": [] }, { - "query": "row var = least(to_string(true))", + "query": "from a_index | eval var = cosh(integerField)", "error": [], "warning": [] }, { - "query": "row var = least(\"a\", \"a\")", + "query": "from a_index | eval cosh(integerField)", "error": [], "warning": [] }, { - "query": "row least(\"a\", \"a\")", + "query": "from a_index | eval var = cosh(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = least(to_string(true), to_string(true))", + "query": "from a_index | eval var = cosh(longField)", "error": [], "warning": [] }, { - "query": "row var = least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from a_index | eval cosh(longField)", "error": [], "warning": [] }, { - "query": "row least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from a_index | eval var = cosh(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = least(to_version(\"a\"), to_version(\"a\"))", + "query": "from a_index | eval cosh(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = least(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval cosh(doubleField, extraArg)", "error": [ - "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Error: [cosh] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | where least(numberField, numberField) > 0", + "query": "from a_index | sort cosh(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | where least(cartesianPointField, cartesianPointField) > 0", - "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | where least(numberField) > 0", + "query": "from a_index | eval cosh(null)", "error": [], "warning": [] }, { - "query": "from a_index | where least(cartesianPointField) > 0", - "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "row nullVar = null | eval cosh(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(least(stringField)) > 0", + "query": "from a_index | eval var = date_diff(\"year\", dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(least(cartesianPointField)) > 0", - "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval date_diff(\"year\", dateField, dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(least(stringField, stringField)) > 0", + "query": "from a_index | eval var = date_diff(\"year\", to_datetime(dateField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(least(cartesianPointField, cartesianPointField)) > 0", + "query": "from a_index | eval date_diff(booleanField, booleanField, booleanField)", "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [date_diff] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]", + "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = least(booleanField)", + "query": "from a_index | eval var = date_diff(textField, dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(booleanField)", + "query": "from a_index | eval date_diff(textField, dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_boolean(booleanField))", + "query": "from a_index | eval var = date_diff(to_string(booleanField), to_datetime(dateField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval least(cartesianPointField)", + "query": "from a_index | eval date_diff(\"year\", dateField, dateField, extraArg)", "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Error: [date_diff] function expects exactly 3 arguments, got 4." ], "warning": [] }, { - "query": "from a_index | eval var = least(booleanField, booleanField)", + "query": "from a_index | sort date_diff(\"year\", dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(booleanField, booleanField)", + "query": "from a_index | eval date_diff(null, null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_boolean(booleanField), to_boolean(booleanField))", + "query": "row nullVar = null | eval date_diff(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(cartesianPointField, cartesianPointField)", - "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = least(numberField, numberField)", + "query": "from a_index | eval date_diff(\"year\", \"2022\", \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | eval least(numberField, numberField)", - "error": [], + "query": "from a_index | eval date_diff(\"year\", concat(\"20\", \"22\"), concat(\"20\", \"22\"))", + "error": [ + "Argument of [date_diff] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [date_diff] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | eval var = least(to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval date_diff(textField, \"2022\", \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(numberField)", - "error": [], + "query": "from a_index | eval date_diff(textField, concat(\"20\", \"22\"), concat(\"20\", \"22\"))", + "error": [ + "Argument of [date_diff] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [date_diff] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | eval least(numberField)", + "query": "from a_index | eval var = date_diff(to_string(booleanField), dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_integer(booleanField))", + "query": "row var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(ipField, ipField)", + "query": "row date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval least(ipField, ipField)", + "query": "row var = date_extract(\"a\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_ip(ipField), to_ip(ipField))", + "query": "row date_extract(\"a\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_string(booleanField))", - "error": [], + "query": "row var = date_extract(true, true)", + "error": [ + "Argument of [date_extract] must be [keyword], found value [true] type [boolean]", + "Argument of [date_extract] must be [date], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = least(stringField, stringField)", + "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(stringField, stringField)", + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(versionField, versionField)", - "error": [], + "query": "from a_index | eval date_extract(booleanField, booleanField)", + "error": [ + "Argument of [date_extract] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [date_extract] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval least(versionField, versionField)", + "query": "from a_index | eval var = date_extract(textField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_version(stringField), to_version(stringField))", + "query": "from a_index | eval date_extract(textField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | sort least(booleanField)", + "query": "from a_index | eval var = date_extract(to_string(booleanField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval least(null)", - "error": [], + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField, extraArg)", + "error": [ + "Error: [date_extract] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row nullVar = null | eval least(nullVar)", + "query": "from a_index | sort date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", "error": [], "warning": [] }, { - "query": "row var = left(\"a\", 5)", + "query": "from a_index | eval date_extract(null, null)", "error": [], "warning": [] }, { - "query": "row left(\"a\", 5)", + "query": "row nullVar = null | eval date_extract(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = left(to_string(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", \"2022\")", "error": [], "warning": [] }, { - "query": "row var = left(5, \"a\")", + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", concat(\"20\", \"22\"))", "error": [ - "Argument of [left] must be [string], found value [5] type [number]", - "Argument of [left] must be [number], found value [\"a\"] type [string]" + "Argument of [date_extract] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" ], "warning": [] }, { - "query": "from a_index | where length(left(stringField, numberField)) > 0", + "query": "from a_index | eval date_extract(textField, \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | where length(left(numberField, stringField)) > 0", + "query": "from a_index | eval date_extract(textField, concat(\"20\", \"22\"))", "error": [ - "Argument of [left] must be [string], found value [numberField] type [number]", - "Argument of [left] must be [number], found value [stringField] type [string]" + "Argument of [date_extract] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval var = left(stringField, numberField)", + "query": "from a_index | eval var = date_extract(to_string(booleanField), dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval left(stringField, numberField)", + "query": "row var = date_format(\"a\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = left(to_string(stringField), to_integer(stringField))", + "query": "row date_format(\"a\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval left(numberField, stringField)", + "query": "row var = date_format(true, true)", "error": [ - "Argument of [left] must be [string], found value [numberField] type [number]", - "Argument of [left] must be [number], found value [stringField] type [string]" + "Argument of [date_format] must be [keyword], found value [true] type [boolean]", + "Argument of [date_format] must be [date], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval left(stringField, numberField, extraArg)", - "error": [ - "Error: [left] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = date_format(keywordField, dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort left(stringField, numberField)", + "query": "from a_index | eval date_format(keywordField, dateField)", "error": [], "warning": [] }, { - "query": "row var = left(to_string(true), to_integer(true))", + "query": "from a_index | eval var = date_format(to_string(booleanField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = left(true, true)", + "query": "from a_index | eval date_format(booleanField, booleanField)", "error": [ - "Argument of [left] must be [string], found value [true] type [boolean]", - "Argument of [left] must be [number], found value [true] type [boolean]" + "Argument of [date_format] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [date_format] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(left(booleanField, booleanField)) > 0", - "error": [ - "Argument of [left] must be [string], found value [booleanField] type [boolean]", - "Argument of [left] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = date_format(textField, dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = left(to_string(booleanField), to_integer(booleanField))", + "query": "from a_index | eval date_format(textField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval left(booleanField, booleanField)", + "query": "from a_index | eval date_format(keywordField, dateField, extraArg)", "error": [ - "Argument of [left] must be [string], found value [booleanField] type [boolean]", - "Argument of [left] must be [number], found value [booleanField] type [boolean]" + "Error: [date_format] function expects no more than 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval left(null, null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | eval left(nullVar, nullVar)", + "query": "from a_index | sort date_format(keywordField, dateField)", "error": [], "warning": [] }, { - "query": "row var = length(\"a\")", + "query": "from a_index | eval date_format(null, null)", "error": [], "warning": [] }, { - "query": "row length(\"a\")", + "query": "row nullVar = null | eval date_format(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = length(to_string(\"a\"))", + "query": "from a_index | eval date_format(keywordField, \"2022\")", "error": [], "warning": [] }, { - "query": "row var = length(5)", + "query": "from a_index | eval date_format(keywordField, concat(\"20\", \"22\"))", "error": [ - "Argument of [length] must be [string], found value [5] type [number]" + "Argument of [date_format] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" ], "warning": [] }, { - "query": "from a_index | where length(stringField) > 0", + "query": "from a_index | eval date_format(textField, \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | where length(numberField) > 0", + "query": "from a_index | eval date_format(textField, concat(\"20\", \"22\"))", "error": [ - "Argument of [length] must be [string], found value [numberField] type [number]" + "Argument of [date_format] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval var = length(stringField)", + "query": "from a_index | eval var = date_format(to_string(booleanField), dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval length(stringField)", + "query": "row var = date_parse(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = length(to_string(stringField))", + "query": "row date_parse(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval length(numberField)", - "error": [ - "Argument of [length] must be [string], found value [numberField] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | eval length(stringField, extraArg)", - "error": [ - "Error: [length] function expects exactly one argument, got 2." - ], + "query": "row var = date_parse(to_string(true), to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = length(*)", + "query": "row var = date_parse(true, true)", "error": [ - "Using wildcards (*) in length is not allowed" + "Argument of [date_parse] must be [keyword], found value [true] type [boolean]", + "Argument of [date_parse] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort length(stringField)", + "query": "from a_index | eval var = date_parse(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = length(to_string(true))", + "query": "from a_index | eval date_parse(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = length(true)", - "error": [ - "Argument of [length] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = date_parse(to_string(booleanField), to_string(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(booleanField) > 0", + "query": "from a_index | eval date_parse(booleanField, booleanField)", "error": [ - "Argument of [length] must be [string], found value [booleanField] type [boolean]" + "Argument of [date_parse] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [date_parse] must be [keyword], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = length(to_string(booleanField))", + "query": "from a_index | eval var = date_parse(keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval length(booleanField)", - "error": [ - "Argument of [length] must be [string], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval length(null)", + "query": "from a_index | eval date_parse(keywordField, textField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval length(nullVar)", + "query": "from a_index | eval var = date_parse(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = log(5, 5)", + "query": "from a_index | eval date_parse(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row log(5, 5)", + "query": "from a_index | eval var = date_parse(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = log(to_integer(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval date_parse(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = log(\"a\", \"a\")", + "query": "from a_index | eval date_parse(keywordField, keywordField, extraArg)", "error": [ - "Argument of [log] must be [number], found value [\"a\"] type [string]", - "Argument of [log] must be [number], found value [\"a\"] type [string]" + "Error: [date_parse] function expects no more than 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where log(numberField, numberField) > 0", + "query": "from a_index | sort date_parse(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | where log(stringField, stringField) > 0", - "error": [ - "Argument of [log] must be [number], found value [stringField] type [string]", - "Argument of [log] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval date_parse(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(numberField, numberField)", + "query": "row nullVar = null | eval date_parse(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval log(numberField, numberField)", + "query": "row var = date_trunc(1 year, to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(to_integer(stringField), to_integer(stringField))", + "query": "row date_trunc(1 year, to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval log(stringField, stringField)", + "query": "row var = date_trunc(\"a\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [ - "Argument of [log] must be [number], found value [stringField] type [string]", - "Argument of [log] must be [number], found value [stringField] type [string]" + "Argument of [date_trunc] must be [time_literal], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval log(numberField, numberField, extraArg)", + "query": "row date_trunc(\"a\", to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [ - "Error: [log] function expects no more than 2 arguments, got 3." + "Argument of [date_trunc] must be [time_literal], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | sort log(numberField, numberField)", - "error": [], + "query": "row var = date_trunc(true, true)", + "error": [ + "Argument of [date_trunc] must be [time_literal], found value [true] type [boolean]", + "Argument of [date_trunc] must be [date], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = log(5)", + "query": "from a_index | eval var = date_trunc(1 year, dateField)", "error": [], "warning": [] }, { - "query": "row log(5)", + "query": "from a_index | eval date_trunc(1 year, dateField)", "error": [], "warning": [] }, { - "query": "row var = log(to_integer(true))", + "query": "from a_index | eval var = date_trunc(1 year, to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = log(to_integer(true), to_integer(true))", - "error": [], + "query": "from a_index | eval date_trunc(booleanField, booleanField)", + "error": [ + "Argument of [date_trunc] must be [time_literal], found value [booleanField] type [boolean]", + "Argument of [date_trunc] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = log(true, true)", + "query": "from a_index | eval var = date_trunc(textField, dateField)", "error": [ - "Argument of [log] must be [number], found value [true] type [boolean]", - "Argument of [log] must be [number], found value [true] type [boolean]" + "Argument of [date_trunc] must be [time_literal], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | where log(numberField) > 0", - "error": [], + "query": "from a_index | eval date_trunc(textField, dateField)", + "error": [ + "Argument of [date_trunc] must be [time_literal], found value [textField] type [text]" + ], "warning": [] }, { - "query": "from a_index | where log(booleanField) > 0", + "query": "from a_index | eval var = date_trunc(textField, to_datetime(dateField))", "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]" + "Argument of [date_trunc] must be [time_literal], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | where log(booleanField, booleanField) > 0", + "query": "from a_index | eval date_trunc(1 year, dateField, extraArg)", "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]", - "Argument of [log] must be [number], found value [booleanField] type [boolean]" + "Error: [date_trunc] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = log(numberField)", + "query": "from a_index | sort date_trunc(1 year, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval log(numberField)", + "query": "from a_index | eval date_trunc(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(to_integer(booleanField))", + "query": "row nullVar = null | eval date_trunc(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval log(booleanField)", - "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval date_trunc(1 year, \"2022\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(*)", + "query": "from a_index | eval date_trunc(1 year, concat(\"20\", \"22\"))", "error": [ - "Using wildcards (*) in log is not allowed" + "Argument of [date_trunc] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" ], "warning": [] }, { - "query": "from a_index | eval var = log(to_integer(booleanField), to_integer(booleanField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval log(booleanField, booleanField)", + "query": "from a_index | eval date_trunc(textField, \"2022\")", "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]", - "Argument of [log] must be [number], found value [booleanField] type [boolean]" + "Argument of [date_trunc] must be [time_literal], found value [textField] type [text]" ], "warning": [] }, { - "query": "from a_index | sort log(numberField)", - "error": [], + "query": "from a_index | eval date_trunc(textField, concat(\"20\", \"22\"))", + "error": [ + "Argument of [date_trunc] must be [time_literal], found value [textField] type [text]", + "Argument of [date_trunc] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | eval log(null, null)", + "query": "row var = e()", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval log(nullVar, nullVar)", + "query": "row e()", "error": [], "warning": [] }, { - "query": "row var = log10(5)", + "query": "from a_index | where e() > 0", "error": [], "warning": [] }, { - "query": "row log10(5)", + "query": "from a_index | eval var = e()", "error": [], "warning": [] }, { - "query": "row var = log10(to_integer(\"a\"))", + "query": "from a_index | eval e()", "error": [], "warning": [] }, { - "query": "row var = log10(\"a\")", + "query": "from a_index | eval e(extraArg)", "error": [ - "Argument of [log10] must be [number], found value [\"a\"] type [string]" + "Error: [e] function expects exactly 0 arguments, got 1." ], "warning": [] }, { - "query": "from a_index | where log10(numberField) > 0", + "query": "from a_index | sort e()", "error": [], "warning": [] }, { - "query": "from a_index | where log10(stringField) > 0", - "error": [ - "Argument of [log10] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = log10(numberField)", + "query": "row nullVar = null | eval e()", "error": [], "warning": [] }, { - "query": "from a_index | eval log10(numberField)", + "query": "row var = ends_with(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = log10(to_integer(stringField))", + "query": "row ends_with(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval log10(stringField)", - "error": [ - "Argument of [log10] must be [number], found value [stringField] type [string]" - ], + "query": "row var = ends_with(to_string(true), to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval log10(numberField, extraArg)", + "query": "row var = ends_with(true, true)", "error": [ - "Error: [log10] function expects exactly one argument, got 2." + "Argument of [ends_with] must be [keyword], found value [true] type [boolean]", + "Argument of [ends_with] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = log10(*)", - "error": [ - "Using wildcards (*) in log10 is not allowed" - ], + "query": "from a_index | eval var = ends_with(keywordField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort log10(numberField)", + "query": "from a_index | eval ends_with(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = log10(to_integer(true))", + "query": "from a_index | eval var = ends_with(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = log10(true)", + "query": "from a_index | eval ends_with(booleanField, booleanField)", "error": [ - "Argument of [log10] must be [number], found value [true] type [boolean]" + "Argument of [ends_with] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [ends_with] must be [keyword], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where log10(booleanField) > 0", - "error": [ - "Argument of [log10] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = ends_with(textField, textField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log10(to_integer(booleanField))", + "query": "from a_index | eval ends_with(textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval log10(booleanField)", + "query": "from a_index | eval ends_with(keywordField, keywordField, extraArg)", "error": [ - "Argument of [log10] must be [number], found value [booleanField] type [boolean]" + "Error: [ends_with] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval log10(null)", + "query": "from a_index | sort ends_with(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval log10(nullVar)", + "query": "from a_index | eval ends_with(null, null)", "error": [], "warning": [] }, { - "query": "row var = ltrim(\"a\")", + "query": "row nullVar = null | eval ends_with(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row ltrim(\"a\")", + "query": "from a_index | eval var = ends_with(keywordField, textField)", "error": [], "warning": [] }, { - "query": "row var = ltrim(to_string(\"a\"))", + "query": "from a_index | eval ends_with(keywordField, textField)", "error": [], "warning": [] }, { - "query": "row var = ltrim(5)", - "error": [ - "Argument of [ltrim] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval var = ends_with(textField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(ltrim(stringField)) > 0", + "query": "from a_index | eval ends_with(textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(ltrim(numberField)) > 0", - "error": [ - "Argument of [ltrim] must be [string], found value [numberField] type [number]" - ], + "query": "row var = exp(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ltrim(stringField)", + "query": "row exp(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(stringField)", + "query": "row var = exp(to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = ltrim(to_string(stringField))", + "query": "row var = exp(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(numberField)", - "error": [ - "Argument of [ltrim] must be [string], found value [numberField] type [number]" - ], + "query": "row exp(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(stringField, extraArg)", - "error": [ - "Error: [ltrim] function expects exactly one argument, got 2." - ], + "query": "row var = exp(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ltrim(*)", + "query": "row var = exp(true)", "error": [ - "Using wildcards (*) in ltrim is not allowed" + "Argument of [exp] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort ltrim(stringField)", - "error": [], - "warning": [] - }, - { - "query": "row var = ltrim(to_string(true))", + "query": "from a_index | where exp(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = ltrim(true)", - "error": [ - "Argument of [ltrim] must be [string], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where length(ltrim(booleanField)) > 0", + "query": "from a_index | where exp(booleanField) > 0", "error": [ - "Argument of [ltrim] must be [string], found value [booleanField] type [boolean]" + "Argument of [exp] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = ltrim(to_string(booleanField))", + "query": "from a_index | where exp(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(booleanField)", - "error": [ - "Argument of [ltrim] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where exp(longField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(null)", + "query": "from a_index | where exp(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval ltrim(nullVar)", + "query": "from a_index | eval var = exp(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_avg(5)", + "query": "from a_index | eval exp(doubleField)", "error": [], "warning": [] }, { - "query": "row mv_avg(5)", + "query": "from a_index | eval var = exp(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_avg(to_integer(\"a\"))", - "error": [], + "query": "from a_index | eval exp(booleanField)", + "error": [ + "Argument of [exp] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_avg(\"a\")", + "query": "from a_index | eval var = exp(*)", "error": [ - "Argument of [mv_avg] must be [number], found value [\"a\"] type [string]" + "Using wildcards (*) in exp is not allowed" ], "warning": [] }, { - "query": "from a_index | where mv_avg(numberField) > 0", + "query": "from a_index | eval var = exp(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_avg(stringField) > 0", - "error": [ - "Argument of [mv_avg] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval exp(integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_avg(numberField)", + "query": "from a_index | eval var = exp(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(numberField)", + "query": "from a_index | eval var = exp(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_avg(to_integer(stringField))", + "query": "from a_index | eval exp(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(stringField)", - "error": [ - "Argument of [mv_avg] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = exp(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(numberField, extraArg)", - "error": [ - "Error: [mv_avg] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval exp(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_avg(*)", + "query": "from a_index | eval exp(doubleField, extraArg)", "error": [ - "Using wildcards (*) in mv_avg is not allowed" + "Error: [exp] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort mv_avg(numberField)", + "query": "from a_index | sort exp(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_avg(to_integer(true))", + "query": "from a_index | eval exp(null)", "error": [], "warning": [] }, { - "query": "row var = mv_avg(true)", - "error": [ - "Argument of [mv_avg] must be [number], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where mv_avg(booleanField) > 0", - "error": [ - "Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = mv_avg(to_integer(booleanField))", + "query": "row nullVar = null | eval exp(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(booleanField)", - "error": [ - "Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = floor(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(null)", + "query": "row floor(5.5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_avg(nullVar)", + "query": "row var = floor(to_double(true))", "error": [], "warning": [] }, { - "query": "row var = mv_concat(\"a\", \"a\")", + "query": "row var = floor(5)", "error": [], "warning": [] }, { - "query": "row mv_concat(\"a\", \"a\")", + "query": "row floor(5)", "error": [], "warning": [] }, { - "query": "row var = mv_concat(to_string(\"a\"), to_string(\"a\"))", + "query": "row var = floor(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_concat(5, 5)", + "query": "row var = floor(true)", "error": [ - "Argument of [mv_concat] must be [string], found value [5] type [number]", - "Argument of [mv_concat] must be [string], found value [5] type [number]" + "Argument of [floor] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(mv_concat(stringField, stringField)) > 0", + "query": "from a_index | where floor(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_concat(numberField, numberField)) > 0", + "query": "from a_index | where floor(booleanField) > 0", "error": [ - "Argument of [mv_concat] must be [string], found value [numberField] type [number]", - "Argument of [mv_concat] must be [string], found value [numberField] type [number]" + "Argument of [floor] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_concat(stringField, stringField)", + "query": "from a_index | where floor(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(stringField, stringField)", + "query": "from a_index | where floor(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_concat(to_string(stringField), to_string(stringField))", + "query": "from a_index | where floor(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(numberField, numberField)", - "error": [ - "Argument of [mv_concat] must be [string], found value [numberField] type [number]", - "Argument of [mv_concat] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = floor(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(stringField, stringField, extraArg)", - "error": [ - "Error: [mv_concat] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | sort mv_concat(stringField, stringField)", + "query": "from a_index | eval floor(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_concat(to_string(true), to_string(true))", + "query": "from a_index | eval var = floor(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_concat(true, true)", + "query": "from a_index | eval floor(booleanField)", "error": [ - "Argument of [mv_concat] must be [string], found value [true] type [boolean]", - "Argument of [mv_concat] must be [string], found value [true] type [boolean]" + "Argument of [floor] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(mv_concat(booleanField, booleanField)) > 0", + "query": "from a_index | eval var = floor(*)", "error": [ - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]" + "Using wildcards (*) in floor is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = mv_concat(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = floor(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(booleanField, booleanField)", - "error": [ - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval mv_concat(null, null)", + "query": "from a_index | eval floor(integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_concat(nullVar, nullVar)", + "query": "from a_index | eval var = floor(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_count(\"a\")", + "query": "from a_index | eval var = floor(longField)", "error": [], "warning": [] }, { - "query": "row mv_count(\"a\")", + "query": "from a_index | eval floor(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(stringField)", + "query": "from a_index | eval var = floor(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(stringField)", + "query": "from a_index | eval floor(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(*)", + "query": "from a_index | eval floor(doubleField, extraArg)", "error": [ - "Using wildcards (*) in mv_count is not allowed" + "Error: [floor] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort mv_count(stringField)", + "query": "from a_index | sort floor(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(true)", + "query": "from a_index | eval floor(null)", "error": [], "warning": [] }, { - "query": "row mv_count(true)", + "query": "row nullVar = null | eval floor(nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_boolean(true))", + "query": "row var = from_base64(\"a\")", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row from_base64(\"a\")", "error": [], "warning": [] }, { - "query": "row mv_count(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = from_base64(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = from_base64(true)", + "error": [ + "Argument of [from_base64] must be [keyword], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_count(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = from_base64(keywordField)", "error": [], "warning": [] }, { - "query": "row mv_count(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval from_base64(keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = from_base64(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_count(now())", - "error": [], + "query": "from a_index | eval from_base64(booleanField)", + "error": [ + "Argument of [from_base64] must be [keyword], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row mv_count(now())", - "error": [], + "query": "from a_index | eval var = from_base64(*)", + "error": [ + "Using wildcards (*) in from_base64 is not allowed" + ], "warning": [] }, { - "query": "row var = mv_count(to_datetime(now()))", + "query": "from a_index | eval var = from_base64(textField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(5)", + "query": "from a_index | eval from_base64(textField)", "error": [], "warning": [] }, { - "query": "row mv_count(5)", - "error": [], + "query": "from a_index | eval from_base64(keywordField, extraArg)", + "error": [ + "Error: [from_base64] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = mv_count(to_integer(true))", + "query": "from a_index | sort from_base64(keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval from_base64(null)", "error": [], "warning": [] }, { - "query": "row mv_count(to_geopoint(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval from_base64(nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = greatest(true)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geoshape(\"POINT (30 10)\"))", + "query": "row greatest(true)", "error": [], "warning": [] }, { - "query": "row mv_count(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = greatest(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = greatest(true, true)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_ip(\"127.0.0.1\"))", + "query": "row greatest(true, true)", "error": [], "warning": [] }, { - "query": "row mv_count(to_ip(\"127.0.0.1\"))", + "query": "row var = greatest(to_boolean(true), to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_ip(to_ip(\"127.0.0.1\")))", + "query": "row var = greatest(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_string(true))", + "query": "row greatest(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_version(\"1.0.0\"))", + "query": "row var = greatest(to_double(true), to_double(true))", "error": [], "warning": [] }, { - "query": "row mv_count(to_version(\"1.0.0\"))", + "query": "row var = greatest(5)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_version(\"a\"))", + "query": "row greatest(5)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(booleanField) > 0", + "query": "row var = greatest(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(cartesianPointField) > 0", + "query": "row var = greatest(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(cartesianShapeField) > 0", + "query": "row greatest(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(dateField) > 0", + "query": "row var = greatest(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(numberField) > 0", + "query": "row var = greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(geoPointField) > 0", + "query": "row greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(geoShapeField) > 0", + "query": "row var = greatest(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(ipField) > 0", + "query": "row var = greatest(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(stringField) > 0", + "query": "row greatest(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(versionField) > 0", + "query": "row var = greatest(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(booleanField)", + "query": "row var = greatest(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(booleanField)", + "query": "row greatest(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_boolean(booleanField))", + "query": "row var = greatest(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(cartesianPointField)", + "query": "row var = greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(cartesianPointField)", + "query": "row greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_cartesianpoint(cartesianPointField))", + "query": "row var = greatest(to_version(\"a\"), to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(cartesianShapeField)", - "error": [], + "query": "row var = greatest(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", + "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval mv_count(cartesianShapeField)", + "query": "from a_index | where greatest(doubleField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_cartesianshape(cartesianPointField))", + "query": "from a_index | where greatest(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(dateField)", - "error": [], + "query": "from a_index | where greatest(cartesianPointField) > 0", + "error": [ + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval mv_count(dateField)", + "query": "from a_index | where greatest(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_datetime(dateField))", + "query": "from a_index | where greatest(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(numberField)", + "query": "from a_index | where greatest(longField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(numberField)", + "query": "from a_index | eval var = greatest(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_integer(booleanField))", + "query": "from a_index | eval greatest(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(geoPointField)", + "query": "from a_index | eval var = greatest(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(geoPointField)", - "error": [], + "query": "from a_index | eval greatest(cartesianPointField)", + "error": [ + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_geopoint(geoPointField))", + "query": "from a_index | eval var = greatest(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(geoShapeField)", + "query": "from a_index | eval greatest(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(geoShapeField)", + "query": "from a_index | eval var = greatest(to_boolean(booleanField), to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_geoshape(geoPointField))", - "error": [], + "query": "from a_index | eval greatest(cartesianPointField, cartesianPointField)", + "error": [ + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_count(ipField)", + "query": "from a_index | eval var = greatest(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(ipField)", + "query": "from a_index | eval greatest(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_ip(ipField))", + "query": "from a_index | eval var = greatest(to_double(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_string(booleanField))", + "query": "from a_index | eval var = greatest(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(versionField)", + "query": "from a_index | eval greatest(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(versionField)", + "query": "from a_index | eval var = greatest(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_version(stringField))", + "query": "from a_index | eval var = greatest(integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(booleanField, extraArg)", - "error": [ - "Error: [mv_count] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval greatest(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_count(booleanField)", + "query": "from a_index | eval var = greatest(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(null)", + "query": "from a_index | eval var = greatest(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_count(nullVar)", + "query": "from a_index | eval greatest(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(\"a\")", + "query": "from a_index | eval var = greatest(to_ip(ipField), to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(\"a\")", + "query": "from a_index | eval var = greatest(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(stringField)", + "query": "from a_index | eval greatest(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(stringField)", + "query": "from a_index | eval var = greatest(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(*)", - "error": [ - "Using wildcards (*) in mv_dedupe is not allowed" - ], + "query": "from a_index | eval var = greatest(keywordField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_dedupe(stringField)", + "query": "from a_index | eval greatest(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(true)", + "query": "from a_index | eval var = greatest(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(true)", + "query": "from a_index | eval var = greatest(longField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_boolean(true))", + "query": "from a_index | eval greatest(longField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(now())", + "query": "from a_index | eval var = greatest(longField, longField)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(now())", + "query": "from a_index | eval greatest(longField, longField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_datetime(now()))", + "query": "from a_index | eval var = greatest(textField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(5)", + "query": "from a_index | eval greatest(textField)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(5)", + "query": "from a_index | eval var = greatest(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_integer(true))", + "query": "from a_index | eval greatest(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = greatest(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval greatest(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = greatest(to_version(keywordField), to_version(keywordField))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_string(true))", + "query": "from a_index | sort greatest(booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_version(\"1.0.0\"))", + "query": "from a_index | eval greatest(null)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_version(\"1.0.0\"))", + "query": "row nullVar = null | eval greatest(nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_version(\"a\"))", - "error": [], + "query": "from a_index | where greatest(cartesianPointField, cartesianPointField) > 0", + "error": [ + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | where mv_dedupe(numberField) > 0", + "query": "row var = ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_dedupe(stringField)) > 0", + "query": "row ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(booleanField)", + "query": "row var = ip_prefix(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(booleanField)", - "error": [], + "query": "row var = ip_prefix(true, true, true)", + "error": [ + "Argument of [ip_prefix] must be [ip], found value [true] type [boolean]", + "Argument of [ip_prefix] must be [integer], found value [true] type [boolean]", + "Argument of [ip_prefix] must be [integer], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_boolean(booleanField))", + "query": "from a_index | eval var = ip_prefix(ipField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(dateField)", + "query": "from a_index | eval ip_prefix(ipField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(dateField)", + "query": "from a_index | eval var = ip_prefix(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_datetime(dateField))", - "error": [], + "query": "from a_index | eval ip_prefix(booleanField, booleanField, booleanField)", + "error": [ + "Argument of [ip_prefix] must be [ip], found value [booleanField] type [boolean]", + "Argument of [ip_prefix] must be [integer], found value [booleanField] type [boolean]", + "Argument of [ip_prefix] must be [integer], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(numberField)", - "error": [], + "query": "from a_index | eval ip_prefix(ipField, integerField, integerField, extraArg)", + "error": [ + "Error: [ip_prefix] function expects exactly 3 arguments, got 4." + ], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(numberField)", + "query": "from a_index | sort ip_prefix(ipField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_integer(booleanField))", + "query": "from a_index | eval ip_prefix(null, null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(ipField)", + "query": "row nullVar = null | eval ip_prefix(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(ipField)", + "query": "row var = least(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_ip(ipField))", + "query": "row least(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_string(booleanField))", + "query": "row var = least(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(versionField)", + "query": "row var = least(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(versionField)", + "query": "row least(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_version(stringField))", + "query": "row var = least(to_boolean(true), to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(booleanField, extraArg)", - "error": [ - "Error: [mv_dedupe] function expects exactly one argument, got 2." - ], + "query": "row var = least(5.5, 5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_dedupe(booleanField)", + "query": "row least(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = least(to_double(true), to_double(true))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = least(5)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row least(5)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = least(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = least(5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geopoint(\"POINT (30 10)\"))", + "query": "row least(5, 5)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = least(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geoshape(\"POINT (30 10)\"))", + "query": "row least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = least(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = least(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(cartesianPointField)", + "query": "row least(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_cartesianpoint(cartesianPointField))", + "query": "row var = least(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(cartesianShapeField)", + "query": "row var = least(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(cartesianShapeField)", + "query": "row least(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_cartesianshape(cartesianPointField))", + "query": "row var = least(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(geoPointField)", + "query": "row var = least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(geoPointField)", + "query": "row least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_geopoint(geoPointField))", + "query": "row var = least(to_version(\"a\"), to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(geoShapeField)", - "error": [], + "query": "row var = least(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", + "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(geoShapeField)", + "query": "from a_index | where least(doubleField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_geoshape(geoPointField))", + "query": "from a_index | where least(cartesianPointField, cartesianPointField) > 0", + "error": [ + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where least(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(numberField, extraArg)", + "query": "from a_index | where least(cartesianPointField) > 0", "error": [ - "Error: [mv_dedupe] function expects exactly one argument, got 2." + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort mv_dedupe(numberField)", + "query": "from a_index | where least(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | where least(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(cartesianPointField)", + "query": "from a_index | where least(longField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(null)", + "query": "from a_index | eval var = least(booleanField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_dedupe(nullVar)", + "query": "from a_index | eval least(booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(\"a\")", + "query": "from a_index | eval var = least(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_first(\"a\")", + "query": "from a_index | eval least(cartesianPointField)", + "error": [ + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = least(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(stringField)", + "query": "from a_index | eval least(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(stringField)", + "query": "from a_index | eval var = least(to_boolean(booleanField), to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(*)", + "query": "from a_index | eval least(cartesianPointField, cartesianPointField)", "error": [ - "Using wildcards (*) in mv_first is not allowed" + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort mv_first(stringField)", + "query": "from a_index | eval var = least(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(true)", + "query": "from a_index | eval least(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row mv_first(true)", + "query": "from a_index | eval var = least(to_double(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_boolean(true))", + "query": "from a_index | eval var = least(integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval least(integerField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = least(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = least(integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval least(integerField, integerField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = least(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = least(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(now())", + "query": "from a_index | eval least(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row mv_first(now())", + "query": "from a_index | eval var = least(to_ip(ipField), to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_datetime(now()))", + "query": "from a_index | eval var = least(keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(5)", + "query": "from a_index | eval least(keywordField)", "error": [], "warning": [] }, { - "query": "row mv_first(5)", + "query": "from a_index | eval var = least(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_integer(true))", + "query": "from a_index | eval var = least(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval least(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = least(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = least(longField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval least(longField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = least(longField, longField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval least(longField, longField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = least(textField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval least(textField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = least(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_string(true))", + "query": "from a_index | eval least(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = least(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_version(\"1.0.0\"))", + "query": "from a_index | eval least(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_version(\"a\"))", + "query": "from a_index | eval var = least(to_version(keywordField), to_version(keywordField))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_first(numberField) > 0", + "query": "from a_index | sort least(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_first(stringField)) > 0", + "query": "from a_index | eval least(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(booleanField)", + "query": "row nullVar = null | eval least(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(booleanField)", + "query": "row var = left(\"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_boolean(booleanField))", + "query": "row left(\"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(cartesianPointField)", + "query": "row var = left(to_string(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(cartesianPointField)", - "error": [], + "query": "row var = left(true, true)", + "error": [ + "Argument of [left] must be [keyword], found value [true] type [boolean]", + "Argument of [left] must be [integer], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = left(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(cartesianShapeField)", + "query": "from a_index | eval left(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(cartesianShapeField)", + "query": "from a_index | eval var = left(to_string(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_cartesianshape(cartesianPointField))", - "error": [], + "query": "from a_index | eval left(booleanField, booleanField)", + "error": [ + "Argument of [left] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [left] must be [integer], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(dateField)", + "query": "from a_index | eval var = left(textField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(dateField)", + "query": "from a_index | eval left(textField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_datetime(dateField))", - "error": [], + "query": "from a_index | eval left(keywordField, integerField, extraArg)", + "error": [ + "Error: [left] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(numberField)", + "query": "from a_index | sort left(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(numberField)", + "query": "from a_index | eval left(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_integer(booleanField))", + "query": "row nullVar = null | eval left(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(geoPointField)", + "query": "row var = length(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(geoPointField)", + "query": "row length(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_geopoint(geoPointField))", + "query": "row var = length(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(geoShapeField)", - "error": [], + "query": "row var = length(true)", + "error": [ + "Argument of [length] must be [keyword], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval mv_first(geoShapeField)", + "query": "from a_index | eval var = length(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_geoshape(geoPointField))", + "query": "from a_index | eval length(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(ipField)", + "query": "from a_index | eval var = length(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(ipField)", - "error": [], + "query": "from a_index | eval length(booleanField)", + "error": [ + "Argument of [length] must be [keyword], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_ip(ipField))", - "error": [], + "query": "from a_index | eval var = length(*)", + "error": [ + "Using wildcards (*) in length is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_string(booleanField))", + "query": "from a_index | eval var = length(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(versionField)", + "query": "from a_index | eval length(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(versionField)", - "error": [], + "query": "from a_index | eval length(keywordField, extraArg)", + "error": [ + "Error: [length] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_version(stringField))", + "query": "from a_index | sort length(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(booleanField, extraArg)", - "error": [ - "Error: [mv_first] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval length(null)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_first(booleanField)", + "query": "row nullVar = null | eval length(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(null)", + "query": "row var = locate(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_first(nullVar)", + "query": "row locate(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = mv_last(\"a\")", + "query": "row var = locate(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row mv_last(\"a\")", + "query": "row var = locate(\"a\", \"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(stringField)", + "query": "row locate(\"a\", \"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(stringField)", + "query": "row var = locate(to_string(true), to_string(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(*)", + "query": "row var = locate(true, true, true)", "error": [ - "Using wildcards (*) in mv_last is not allowed" + "Argument of [locate] must be [keyword], found value [true] type [boolean]", + "Argument of [locate] must be [keyword], found value [true] type [boolean]", + "Argument of [locate] must be [integer], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort mv_last(stringField)", + "query": "from a_index | eval var = locate(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(true)", + "query": "from a_index | eval locate(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row mv_last(true)", + "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_boolean(true))", - "error": [], + "query": "from a_index | eval locate(booleanField, booleanField)", + "error": [ + "Argument of [locate] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [locate] must be [keyword], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_last(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = locate(keywordField, keywordField, integerField)", "error": [], "warning": [] }, { - "query": "row mv_last(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval locate(keywordField, keywordField, integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval locate(booleanField, booleanField, booleanField)", + "error": [ + "Argument of [locate] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [locate] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [locate] must be [integer], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row mv_last(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = locate(keywordField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval locate(keywordField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(now())", + "query": "from a_index | eval var = locate(keywordField, textField, integerField)", "error": [], "warning": [] }, { - "query": "row mv_last(now())", + "query": "from a_index | eval locate(keywordField, textField, integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_datetime(now()))", + "query": "from a_index | eval var = locate(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(5)", + "query": "from a_index | eval locate(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row mv_last(5)", + "query": "from a_index | eval var = locate(textField, keywordField, integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_integer(true))", + "query": "from a_index | eval locate(textField, keywordField, integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = locate(textField, textField)", "error": [], "warning": [] }, { - "query": "row mv_last(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval locate(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = locate(textField, textField, integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval locate(textField, textField, integerField)", "error": [], "warning": [] }, { - "query": "row mv_last(to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval locate(keywordField, keywordField, integerField, extraArg)", + "error": [ + "Error: [locate] function expects no more than 3 arguments, got 4." + ], "warning": [] }, { - "query": "row var = mv_last(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | sort locate(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval locate(null, null, null)", "error": [], "warning": [] }, { - "query": "row mv_last(to_ip(\"127.0.0.1\"))", + "query": "row nullVar = null | eval locate(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_ip(to_ip(\"127.0.0.1\")))", + "query": "row var = log(5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_string(true))", + "query": "row log(5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_version(\"1.0.0\"))", + "query": "row var = log(to_double(true))", "error": [], "warning": [] }, { - "query": "row mv_last(to_version(\"1.0.0\"))", + "query": "row var = log(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_version(\"a\"))", + "query": "row log(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_last(numberField) > 0", + "query": "row var = log(to_double(true), to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_last(stringField)) > 0", + "query": "row var = log(5.5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(booleanField)", + "query": "row log(5.5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(booleanField)", + "query": "row var = log(to_double(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_boolean(booleanField))", + "query": "row var = log(to_double(true), 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(cartesianPointField)", + "query": "row var = log(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(cartesianPointField)", + "query": "row log(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_cartesianpoint(cartesianPointField))", + "query": "row var = log(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(cartesianShapeField)", + "query": "row var = log(5, 5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(cartesianShapeField)", + "query": "row log(5, 5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_cartesianshape(cartesianPointField))", + "query": "row var = log(to_integer(true), to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(dateField)", + "query": "row var = log(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(dateField)", + "query": "row log(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_datetime(dateField))", + "query": "row var = log(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(numberField)", + "query": "row var = log(to_integer(true), 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(numberField)", + "query": "row var = log(5, to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_integer(booleanField))", + "query": "row var = log(5, to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(geoPointField)", - "error": [], + "query": "row var = log(true, true)", + "error": [ + "Argument of [log] must be [double], found value [true] type [boolean]", + "Argument of [log] must be [double], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval mv_last(geoPointField)", + "query": "from a_index | where log(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_geopoint(geoPointField))", - "error": [], + "query": "from a_index | where log(booleanField) > 0", + "error": [ + "Argument of [log] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_last(geoShapeField)", + "query": "from a_index | where log(doubleField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(geoShapeField)", - "error": [], + "query": "from a_index | where log(booleanField, booleanField) > 0", + "error": [ + "Argument of [log] must be [double], found value [booleanField] type [boolean]", + "Argument of [log] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_geoshape(geoPointField))", + "query": "from a_index | where log(doubleField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(ipField)", + "query": "from a_index | where log(doubleField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(ipField)", + "query": "from a_index | where log(doubleField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_ip(ipField))", + "query": "from a_index | where log(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_string(booleanField))", + "query": "from a_index | where log(integerField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(versionField)", + "query": "from a_index | where log(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(versionField)", + "query": "from a_index | where log(integerField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_version(stringField))", + "query": "from a_index | where log(integerField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(booleanField, extraArg)", - "error": [ - "Error: [mv_last] function expects exactly one argument, got 2." - ], + "query": "from a_index | where log(longField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_last(booleanField)", + "query": "from a_index | where log(longField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(null)", + "query": "from a_index | where log(longField, integerField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_last(nullVar)", + "query": "from a_index | where log(longField, longField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_max(\"a\")", + "query": "from a_index | where log(longField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row mv_max(\"a\")", + "query": "from a_index | where log(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(stringField)", + "query": "from a_index | where log(unsignedLongField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(stringField)", + "query": "from a_index | where log(unsignedLongField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(*)", - "error": [ - "Using wildcards (*) in mv_max is not allowed" - ], + "query": "from a_index | where log(unsignedLongField, longField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_max(stringField)", + "query": "from a_index | where log(unsignedLongField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_max(true)", + "query": "from a_index | eval var = log(doubleField)", "error": [], "warning": [] }, { - "query": "row mv_max(true)", + "query": "from a_index | eval log(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_boolean(true))", + "query": "from a_index | eval var = log(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_max(now())", - "error": [], + "query": "from a_index | eval log(booleanField)", + "error": [ + "Argument of [log] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row mv_max(now())", - "error": [], + "query": "from a_index | eval var = log(*)", + "error": [ + "Using wildcards (*) in log is not allowed" + ], "warning": [] }, { - "query": "row var = mv_max(to_datetime(now()))", + "query": "from a_index | eval var = log(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(5)", + "query": "from a_index | eval log(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row mv_max(5)", + "query": "from a_index | eval var = log(to_double(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_integer(true))", + "query": "from a_index | eval log(booleanField, booleanField)", + "error": [ + "Argument of [log] must be [double], found value [booleanField] type [boolean]", + "Argument of [log] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = log(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval log(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "row mv_max(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = log(to_double(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = log(doubleField, longField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_string(true))", + "query": "from a_index | eval log(doubleField, longField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = log(to_double(booleanField), longField)", "error": [], "warning": [] }, { - "query": "row mv_max(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = log(doubleField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_version(\"a\"))", + "query": "from a_index | eval log(doubleField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [ - "Argument of [mv_max] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" - ], + "query": "from a_index | eval var = log(to_double(booleanField), unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | where mv_max(numberField) > 0", + "query": "from a_index | eval var = log(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_max(cartesianPointField) > 0", - "error": [ - "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval log(integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_max(stringField)) > 0", + "query": "from a_index | eval var = log(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_max(cartesianPointField)) > 0", - "error": [ - "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval var = log(integerField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(booleanField)", + "query": "from a_index | eval log(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(booleanField)", + "query": "from a_index | eval var = log(to_integer(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_boolean(booleanField))", + "query": "from a_index | eval var = log(integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(cartesianPointField)", - "error": [ - "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval log(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(dateField)", + "query": "from a_index | eval var = log(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(dateField)", + "query": "from a_index | eval var = log(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_datetime(dateField))", + "query": "from a_index | eval log(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(numberField)", + "query": "from a_index | eval var = log(to_integer(booleanField), longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(numberField)", + "query": "from a_index | eval var = log(integerField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_integer(booleanField))", + "query": "from a_index | eval log(integerField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(ipField)", + "query": "from a_index | eval var = log(to_integer(booleanField), unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(ipField)", + "query": "from a_index | eval var = log(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_ip(ipField))", + "query": "from a_index | eval log(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_string(booleanField))", + "query": "from a_index | eval var = log(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(versionField)", + "query": "from a_index | eval log(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(versionField)", + "query": "from a_index | eval var = log(longField, to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_version(stringField))", + "query": "from a_index | eval var = log(longField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(booleanField, extraArg)", - "error": [ - "Error: [mv_max] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval log(longField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_max(booleanField)", + "query": "from a_index | eval var = log(longField, to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(null)", + "query": "from a_index | eval var = log(longField, longField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_max(nullVar)", + "query": "from a_index | eval log(longField, longField)", "error": [], "warning": [] }, { - "query": "row var = mv_median(5)", + "query": "from a_index | eval var = log(longField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "row mv_median(5)", + "query": "from a_index | eval log(longField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_median(to_integer(\"a\"))", + "query": "from a_index | eval var = log(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_median(\"a\")", - "error": [ - "Argument of [mv_median] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval log(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | where mv_median(numberField) > 0", + "query": "from a_index | eval var = log(unsignedLongField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_median(stringField) > 0", - "error": [ - "Argument of [mv_median] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval log(unsignedLongField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_median(numberField)", + "query": "from a_index | eval var = log(unsignedLongField, to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(numberField)", + "query": "from a_index | eval var = log(unsignedLongField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_median(to_integer(stringField))", + "query": "from a_index | eval log(unsignedLongField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(stringField)", - "error": [ - "Argument of [mv_median] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = log(unsignedLongField, to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(numberField, extraArg)", - "error": [ - "Error: [mv_median] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = log(unsignedLongField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_median(*)", - "error": [ - "Using wildcards (*) in mv_median is not allowed" - ], + "query": "from a_index | eval log(unsignedLongField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_median(numberField)", + "query": "from a_index | eval var = log(unsignedLongField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_median(to_integer(true))", + "query": "from a_index | eval log(unsignedLongField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_median(true)", + "query": "from a_index | eval log(doubleField, doubleField, extraArg)", "error": [ - "Argument of [mv_median] must be [number], found value [true] type [boolean]" + "Error: [log] function expects no more than 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where mv_median(booleanField) > 0", - "error": [ - "Argument of [mv_median] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | sort log(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_median(to_integer(booleanField))", + "query": "from a_index | eval log(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(booleanField)", - "error": [ - "Argument of [mv_median] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row nullVar = null | eval log(nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(null)", + "query": "row var = log10(5.5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_median(nullVar)", + "query": "row log10(5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_min(\"a\")", + "query": "row var = log10(to_double(true))", "error": [], "warning": [] }, { - "query": "row mv_min(\"a\")", + "query": "row var = log10(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(stringField)", + "query": "row log10(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(stringField)", + "query": "row var = log10(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(*)", + "query": "row var = log10(true)", "error": [ - "Using wildcards (*) in mv_min is not allowed" + "Argument of [log10] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort mv_min(stringField)", + "query": "from a_index | where log10(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_min(true)", - "error": [], + "query": "from a_index | where log10(booleanField) > 0", + "error": [ + "Argument of [log10] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row mv_min(true)", + "query": "from a_index | where log10(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_boolean(true))", + "query": "from a_index | where log10(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_min(now())", + "query": "from a_index | where log10(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row mv_min(now())", + "query": "from a_index | eval var = log10(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_datetime(now()))", + "query": "from a_index | eval log10(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(5)", + "query": "from a_index | eval var = log10(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_min(5)", - "error": [], + "query": "from a_index | eval log10(booleanField)", + "error": [ + "Argument of [log10] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_min(to_integer(true))", - "error": [], + "query": "from a_index | eval var = log10(*)", + "error": [ + "Using wildcards (*) in log10 is not allowed" + ], "warning": [] }, { - "query": "row var = mv_min(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = log10(integerField)", "error": [], "warning": [] }, { - "query": "row mv_min(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval log10(integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = log10(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_string(true))", + "query": "from a_index | eval var = log10(longField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_version(\"1.0.0\"))", + "query": "from a_index | eval log10(longField)", "error": [], "warning": [] }, { - "query": "row mv_min(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = log10(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_version(\"a\"))", + "query": "from a_index | eval log10(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval log10(doubleField, extraArg)", "error": [ - "Argument of [mv_min] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Error: [log10] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | where mv_min(numberField) > 0", + "query": "from a_index | sort log10(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_min(cartesianPointField) > 0", - "error": [ - "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | where length(mv_min(stringField)) > 0", + "query": "from a_index | eval log10(null)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_min(cartesianPointField)) > 0", - "error": [ - "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "row nullVar = null | eval log10(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(booleanField)", + "query": "row var = ltrim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(booleanField)", + "query": "row ltrim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_boolean(booleanField))", + "query": "row var = ltrim(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(cartesianPointField)", + "query": "row var = ltrim(true)", "error": [ - "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [ltrim] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_min(dateField)", + "query": "from a_index | eval var = ltrim(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(dateField)", + "query": "from a_index | eval ltrim(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_datetime(dateField))", + "query": "from a_index | eval var = ltrim(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(numberField)", - "error": [], + "query": "from a_index | eval ltrim(booleanField)", + "error": [ + "Argument of [ltrim] must be [keyword], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval mv_min(numberField)", - "error": [], + "query": "from a_index | eval var = ltrim(*)", + "error": [ + "Using wildcards (*) in ltrim is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_integer(booleanField))", + "query": "from a_index | eval var = ltrim(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(ipField)", + "query": "from a_index | eval ltrim(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(ipField)", + "query": "from a_index | eval ltrim(keywordField, extraArg)", + "error": [ + "Error: [ltrim] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort ltrim(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_ip(ipField))", + "query": "from a_index | eval ltrim(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_string(booleanField))", + "query": "row nullVar = null | eval ltrim(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(versionField)", + "query": "row var = mv_append(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(versionField)", + "query": "row mv_append(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_version(stringField))", + "query": "row var = mv_append(to_boolean(true), to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(booleanField, extraArg)", + "query": "row var = mv_append(cartesianPointField, cartesianPointField)", "error": [ - "Error: [mv_min] function expects exactly one argument, got 2." + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | sort mv_min(booleanField)", - "error": [], + "query": "row mv_append(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval mv_min(null)", - "error": [], + "query": "row var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row nullVar = null | eval mv_min(nullVar)", + "query": "row var = mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(\"a\", 5, 5)", + "query": "row mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row mv_slice(\"a\", 5, 5)", - "error": [], + "query": "row var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(stringField, numberField, numberField)", + "query": "row var = mv_append(to_datetime(\"2021-01-01T00:00:00Z\"), to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(stringField, numberField, numberField)", + "query": "row mv_append(to_datetime(\"2021-01-01T00:00:00Z\"), to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | sort mv_slice(stringField, numberField, numberField)", + "query": "row var = mv_append(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")), to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(true, 5, 5)", + "query": "row var = mv_append(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row mv_slice(true, 5, 5)", + "query": "row mv_append(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_boolean(true), to_integer(true), to_integer(true))", + "query": "row var = mv_append(to_double(true), to_double(true))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", - "error": [], + "query": "row var = mv_append(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", - "error": [], + "query": "row mv_append(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", - "error": [], + "query": "row var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", + "query": "row var = mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", + "query": "row mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", - "error": [], + "query": "row var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = mv_slice(now(), 5, 5)", + "query": "row var = mv_append(5, 5)", "error": [], "warning": [] }, { - "query": "row mv_slice(now(), 5, 5)", + "query": "row mv_append(5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_datetime(now()), to_integer(true), to_integer(true))", + "query": "row var = mv_append(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(5, 5, 5)", + "query": "row var = mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row mv_slice(5, 5, 5)", + "query": "row mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_integer(true), to_integer(true), to_integer(true))", + "query": "row var = mv_append(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", + "query": "row var = mv_append(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", + "query": "row mv_append(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", + "query": "row var = mv_append(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", + "query": "row var = mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", + "query": "row mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", + "query": "row var = mv_append(to_version(\"a\"), to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", + "query": "from a_index | where mv_append(doubleField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "row mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", - "error": [], + "query": "from a_index | where mv_append(counterDoubleField, counterDoubleField) > 0", + "error": [ + "Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]", + "Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]" + ], "warning": [] }, { - "query": "row var = mv_slice(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", + "query": "from a_index | where mv_append(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_string(true), to_integer(true), to_integer(true))", + "query": "from a_index | where mv_append(longField, longField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_version(\"1.0.0\"), 5, 5)", + "query": "from a_index | eval var = mv_append(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "row mv_slice(to_version(\"1.0.0\"), 5, 5)", + "query": "from a_index | eval mv_append(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_version(\"a\"), to_integer(true), to_integer(true))", + "query": "from a_index | eval var = mv_append(to_boolean(booleanField), to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_version(\"1.0.0\"), true, true)", + "query": "from a_index | eval mv_append(counterDoubleField, counterDoubleField)", "error": [ - "Argument of [mv_slice] must be [number], found value [true] type [boolean]", - "Argument of [mv_slice] must be [number], found value [true] type [boolean]" + "Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]", + "Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | where mv_slice(numberField, numberField, numberField) > 0", + "query": "from a_index | eval var = mv_append(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_slice(numberField, booleanField, booleanField) > 0", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_append(cartesianPointField, cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_slice(stringField, numberField, numberField)) > 0", + "query": "from a_index | eval var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_slice(stringField, booleanField, booleanField)) > 0", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_append(cartesianShapeField, cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(booleanField, numberField, numberField)", + "query": "from a_index | eval mv_append(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(booleanField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_boolean(booleanField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_append(dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(booleanField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_append(dateField, dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(cartesianPointField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(to_datetime(dateField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianPointField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval mv_append(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianPointField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_append(to_double(booleanField), to_double(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(cartesianShapeField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianShapeField, numberField, numberField)", + "query": "from a_index | eval mv_append(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianShapeField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_append(geoShapeField, geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(dateField, numberField, numberField)", + "query": "from a_index | eval mv_append(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(dateField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_datetime(dateField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_append(integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(dateField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_append(integerField, integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = mv_append(to_integer(booleanField), to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(numberField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(ipField, ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(numberField, numberField, numberField)", + "query": "from a_index | eval mv_append(ipField, ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_integer(booleanField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_append(to_ip(ipField), to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(numberField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_append(keywordField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(geoPointField, numberField, numberField)", + "query": "from a_index | eval mv_append(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(geoPointField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_geopoint(geoPointField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_append(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(geoPointField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_append(longField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(geoShapeField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(geoShapeField, numberField, numberField)", + "query": "from a_index | eval mv_append(textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_geoshape(geoPointField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_append(versionField, versionField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval mv_append(versionField, versionField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = mv_append(to_version(keywordField), to_version(keywordField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(geoShapeField, booleanField, booleanField)", + "query": "from a_index | eval mv_append(booleanField, booleanField, extraArg)", "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + "Error: [mv_append] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(ipField, numberField, numberField)", + "query": "from a_index | sort mv_append(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(ipField, numberField, numberField)", + "query": "from a_index | eval mv_append(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", + "query": "row nullVar = null | eval mv_append(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(ipField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_append(\"2022\", \"2022\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval mv_append(concat(\"20\", \"22\"), concat(\"20\", \"22\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(stringField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(versionField, numberField, numberField)", + "query": "row mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(versionField, numberField, numberField)", + "query": "row var = mv_append(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_version(stringField), to_integer(booleanField), to_integer(booleanField))", + "query": "row var = mv_append(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(versionField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(booleanField, numberField, numberField, extraArg)", - "error": [ - "Error: [mv_slice] function expects no more than 3 arguments, got 4." - ], + "query": "row mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_slice(booleanField, numberField, numberField)", + "query": "row var = mv_append(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(null, null, null)", + "query": "row var = mv_append(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_slice(nullVar, nullVar, nullVar)", + "query": "row var = mv_avg(5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_sort(\"a\", \"asc\")", + "query": "row mv_avg(5.5)", "error": [], "warning": [] }, { - "query": "row mv_sort(\"a\", \"asc\")", + "query": "row var = mv_avg(to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(stringField, \"asc\")", + "query": "row var = mv_avg(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(stringField, \"asc\")", + "query": "row mv_avg(5)", "error": [], "warning": [] }, { - "query": "from a_index | sort mv_sort(stringField, \"asc\")", + "query": "row var = mv_avg(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_sort(true, \"asc\")", + "query": "row var = mv_avg(true)", + "error": [ + "Argument of [mv_avg] must be [double], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where mv_avg(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row mv_sort(true, \"asc\")", + "query": "from a_index | where mv_avg(booleanField) > 0", + "error": [ + "Argument of [mv_avg] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where mv_avg(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_sort(now(), \"asc\")", + "query": "from a_index | where mv_avg(longField) > 0", "error": [], "warning": [] }, { - "query": "row mv_sort(now(), \"asc\")", + "query": "from a_index | where mv_avg(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_sort(5, \"asc\")", + "query": "from a_index | eval var = mv_avg(doubleField)", "error": [], "warning": [] }, { - "query": "row mv_sort(5, \"asc\")", + "query": "from a_index | eval mv_avg(doubleField)", "error": [], "warning": [] }, { - "query": "row var = mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", + "query": "from a_index | eval var = mv_avg(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", + "query": "from a_index | eval mv_avg(booleanField)", + "error": [ + "Argument of [mv_avg] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = mv_avg(*)", + "error": [ + "Using wildcards (*) in mv_avg is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = mv_avg(integerField)", "error": [], "warning": [] }, { - "query": "row var = mv_sort(to_version(\"1.0.0\"), \"asc\")", + "query": "from a_index | eval mv_avg(integerField)", "error": [], "warning": [] }, { - "query": "row mv_sort(to_version(\"1.0.0\"), \"asc\")", + "query": "from a_index | eval var = mv_avg(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_sort(to_cartesianpoint(\"POINT (30 10)\"), true)", - "error": [ - "Argument of [mv_sort] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", - "Argument of [mv_sort] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = mv_avg(longField)", + "error": [], "warning": [] }, { - "query": "from a_index | where mv_sort(numberField, \"asc\") > 0", + "query": "from a_index | eval mv_avg(longField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_sort(cartesianPointField, booleanField) > 0", - "error": [ - "Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_avg(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_sort(stringField, \"asc\")) > 0", + "query": "from a_index | eval mv_avg(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_sort(cartesianPointField, booleanField)) > 0", + "query": "from a_index | eval mv_avg(doubleField, extraArg)", "error": [ - "Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]" + "Error: [mv_avg] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(booleanField, \"asc\")", + "query": "from a_index | sort mv_avg(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(booleanField, \"asc\")", + "query": "from a_index | eval mv_avg(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(dateField, \"asc\")", + "query": "row nullVar = null | eval mv_avg(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(dateField, \"asc\")", + "query": "row var = mv_concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(numberField, \"asc\")", + "query": "row mv_concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(numberField, \"asc\")", + "query": "row var = mv_concat(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(ipField, \"asc\")", - "error": [], + "query": "row var = mv_concat(true, true)", + "error": [ + "Argument of [mv_concat] must be [keyword], found value [true] type [boolean]", + "Argument of [mv_concat] must be [keyword], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval mv_sort(ipField, \"asc\")", + "query": "from a_index | eval var = mv_concat(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(versionField, \"asc\")", + "query": "from a_index | eval mv_concat(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(versionField, \"asc\")", + "query": "from a_index | eval var = mv_concat(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(booleanField, \"asc\", extraArg)", + "query": "from a_index | eval mv_concat(booleanField, booleanField)", "error": [ - "Error: [mv_sort] function expects no more than 2 arguments, got 3." + "Argument of [mv_concat] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [mv_concat] must be [keyword], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort mv_sort(booleanField, \"asc\")", + "query": "from a_index | eval var = mv_concat(keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(null, null)", + "query": "from a_index | eval mv_concat(keywordField, textField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_sort(nullVar, nullVar)", + "query": "from a_index | eval var = mv_concat(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = mv_sum(5)", + "query": "from a_index | eval mv_concat(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row mv_sum(5)", + "query": "from a_index | eval var = mv_concat(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_sum(to_integer(\"a\"))", + "query": "from a_index | eval mv_concat(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = mv_sum(\"a\")", + "query": "from a_index | eval mv_concat(keywordField, keywordField, extraArg)", "error": [ - "Argument of [mv_sum] must be [number], found value [\"a\"] type [string]" + "Error: [mv_concat] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where mv_sum(numberField) > 0", + "query": "from a_index | sort mv_concat(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_sum(stringField) > 0", - "error": [ - "Argument of [mv_sum] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval mv_concat(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval mv_concat(nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(numberField)", + "query": "row var = mv_count(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sum(numberField)", + "query": "row mv_count(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(to_integer(stringField))", + "query": "row var = mv_count(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sum(stringField)", + "query": "row var = mv_count(cartesianPointField)", "error": [ - "Argument of [mv_sum] must be [number], found value [stringField] type [string]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval mv_sum(numberField, extraArg)", + "query": "row mv_count(cartesianPointField)", "error": [ - "Error: [mv_sum] function expects exactly one argument, got 2." + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(*)", + "query": "row var = mv_count(to_cartesianpoint(cartesianPointField))", "error": [ - "Using wildcards (*) in mv_sum is not allowed" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | sort mv_sum(numberField)", + "query": "row var = mv_count(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_sum(to_integer(true))", + "query": "row mv_count(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_sum(true)", + "query": "row var = mv_count(to_cartesianshape(cartesianPointField))", "error": [ - "Argument of [mv_sum] must be [number], found value [true] type [boolean]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | where mv_sum(booleanField) > 0", - "error": [ - "Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_count(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(to_integer(booleanField))", + "query": "row mv_count(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sum(booleanField)", - "error": [ - "Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_count(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sum(null)", + "query": "row var = mv_count(5.5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_sum(nullVar)", + "query": "row mv_count(5.5)", "error": [], "warning": [] }, { - "query": "row var = mv_zip(\"a\", \"a\", \"a\")", + "query": "row var = mv_count(to_double(true))", "error": [], "warning": [] }, { - "query": "row var = mv_zip(\"a\", \"a\")", - "error": [], + "query": "row var = mv_count(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row mv_zip(\"a\", \"a\", \"a\")", - "error": [], + "query": "row mv_count(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row mv_zip(\"a\", \"a\")", + "query": "row var = mv_count(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = mv_count(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_zip(to_string(\"a\"), to_string(\"a\"), to_string(\"a\"))", + "query": "row mv_count(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_zip(5, 5, 5)", + "query": "row var = mv_count(to_geoshape(geoPointField))", "error": [ - "Argument of [mv_zip] must be [string], found value [5] type [number]", - "Argument of [mv_zip] must be [string], found value [5] type [number]", - "Argument of [mv_zip] must be [string], found value [5] type [number]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | where length(mv_zip(stringField, stringField, stringField)) > 0", + "query": "row var = mv_count(5)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(numberField, numberField, numberField)) > 0", - "error": [ - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]" - ], + "query": "row mv_count(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(stringField, stringField, stringField)", + "query": "row var = mv_count(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(stringField, stringField)", + "query": "row var = mv_count(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(stringField, stringField, stringField)", + "query": "row mv_count(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(to_string(stringField), to_string(stringField), to_string(stringField))", + "query": "row var = mv_count(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(numberField, numberField, numberField)", - "error": [ - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]" - ], + "query": "row var = mv_count(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(stringField, stringField, stringField, extraArg)", - "error": [ - "Error: [mv_zip] function expects no more than 3 arguments, got 4." - ], + "query": "row mv_count(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_zip(stringField, stringField, stringField)", + "query": "row var = mv_count(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = mv_zip(to_string(true), to_string(true), to_string(true))", + "query": "row var = mv_count(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = mv_zip(true, true, true)", - "error": [ - "Argument of [mv_zip] must be [string], found value [true] type [boolean]", - "Argument of [mv_zip] must be [string], found value [true] type [boolean]", - "Argument of [mv_zip] must be [string], found value [true] type [boolean]" - ], + "query": "row mv_count(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(booleanField, booleanField, booleanField)) > 0", - "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_count(to_version(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField), to_string(booleanField))", + "query": "from a_index | where mv_count(booleanField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(booleanField, booleanField, booleanField)", + "query": "from a_index | where mv_count(counterDoubleField) > 0", "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" + "Argument of [mv_count] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | eval mv_zip(null, null, null)", + "query": "from a_index | where mv_count(cartesianPointField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_zip(nullVar, nullVar, nullVar)", + "query": "from a_index | where mv_count(cartesianShapeField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_zip(to_string(true), to_string(true))", + "query": "from a_index | where mv_count(dateField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(stringField, stringField)) > 0", + "query": "from a_index | where mv_count(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(booleanField, booleanField)) > 0", - "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where mv_count(geoPointField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(stringField, stringField)", + "query": "from a_index | where mv_count(geoShapeField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | where mv_count(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(booleanField, booleanField)", - "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where mv_count(ipField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_zip(stringField, stringField)", + "query": "from a_index | where mv_count(keywordField) > 0", "error": [], "warning": [] }, { - "query": "row var = now()", + "query": "from a_index | where mv_count(longField) > 0", "error": [], "warning": [] }, { - "query": "row now()", + "query": "from a_index | where mv_count(textField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = now()", + "query": "from a_index | where mv_count(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval now()", + "query": "from a_index | where mv_count(versionField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval now(extraArg)", - "error": [ - "Error: [now] function expects exactly 0 arguments, got 1." - ], + "query": "from a_index | eval var = mv_count(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort now()", + "query": "from a_index | eval mv_count(booleanField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval now()", + "query": "from a_index | eval var = mv_count(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row var = pi()", - "error": [], + "query": "from a_index | eval mv_count(counterDoubleField)", + "error": [ + "Argument of [mv_count] must be [boolean], found value [counterDoubleField] type [counter_double]" + ], "warning": [] }, { - "query": "row pi()", + "query": "from a_index | eval var = mv_count(*)", + "error": [ + "Using wildcards (*) in mv_count is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = mv_count(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | where pi() > 0", + "query": "from a_index | eval mv_count(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = pi()", + "query": "from a_index | eval var = mv_count(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval pi()", + "query": "from a_index | eval var = mv_count(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval pi(extraArg)", - "error": [ - "Error: [pi] function expects exactly 0 arguments, got 1." - ], + "query": "from a_index | eval mv_count(cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort pi()", + "query": "from a_index | eval var = mv_count(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval pi()", + "query": "from a_index | eval var = mv_count(dateField)", "error": [], "warning": [] }, { - "query": "row var = pow(5, 5)", + "query": "from a_index | eval mv_count(dateField)", "error": [], "warning": [] }, { - "query": "row pow(5, 5)", + "query": "from a_index | eval var = mv_count(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = pow(to_integer(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval var = mv_count(doubleField)", "error": [], "warning": [] }, { - "query": "row var = pow(\"a\", \"a\")", - "error": [ - "Argument of [pow] must be [number], found value [\"a\"] type [string]", - "Argument of [pow] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval mv_count(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | where pow(numberField, numberField) > 0", + "query": "from a_index | eval var = mv_count(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where pow(stringField, stringField) > 0", - "error": [ - "Argument of [pow] must be [number], found value [stringField] type [string]", - "Argument of [pow] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = mv_count(geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = pow(numberField, numberField)", + "query": "from a_index | eval mv_count(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval pow(numberField, numberField)", + "query": "from a_index | eval var = mv_count(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = pow(to_integer(stringField), to_integer(stringField))", + "query": "from a_index | eval var = mv_count(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval pow(stringField, stringField)", - "error": [ - "Argument of [pow] must be [number], found value [stringField] type [string]", - "Argument of [pow] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval mv_count(geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval pow(numberField, numberField, extraArg)", - "error": [ - "Error: [pow] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = mv_count(to_geoshape(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort pow(numberField, numberField)", + "query": "from a_index | eval var = mv_count(integerField)", "error": [], "warning": [] }, { - "query": "row var = pow(to_integer(true), to_integer(true))", + "query": "from a_index | eval mv_count(integerField)", "error": [], "warning": [] }, { - "query": "row var = pow(true, true)", - "error": [ - "Argument of [pow] must be [number], found value [true] type [boolean]", - "Argument of [pow] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = mv_count(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | where pow(booleanField, booleanField) > 0", - "error": [ - "Argument of [pow] must be [number], found value [booleanField] type [boolean]", - "Argument of [pow] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_count(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = pow(to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval mv_count(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval pow(booleanField, booleanField)", - "error": [ - "Argument of [pow] must be [number], found value [booleanField] type [boolean]", - "Argument of [pow] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_count(to_ip(ipField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval pow(null, null)", + "query": "from a_index | eval var = mv_count(keywordField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval pow(nullVar, nullVar)", + "query": "from a_index | eval mv_count(keywordField)", "error": [], "warning": [] }, { - "query": "row var = replace(\"a\", \"a\", \"a\")", + "query": "from a_index | eval var = mv_count(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row replace(\"a\", \"a\", \"a\")", + "query": "from a_index | eval var = mv_count(longField)", "error": [], "warning": [] }, { - "query": "row var = replace(to_string(\"a\"), to_string(\"a\"), to_string(\"a\"))", + "query": "from a_index | eval mv_count(longField)", "error": [], "warning": [] }, { - "query": "row var = replace(5, 5, 5)", - "error": [ - "Argument of [replace] must be [string], found value [5] type [number]", - "Argument of [replace] must be [string], found value [5] type [number]", - "Argument of [replace] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval var = mv_count(textField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(replace(stringField, stringField, stringField)) > 0", + "query": "from a_index | eval mv_count(textField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(replace(numberField, numberField, numberField)) > 0", - "error": [ - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = mv_count(unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = replace(stringField, stringField, stringField)", + "query": "from a_index | eval mv_count(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval replace(stringField, stringField, stringField)", + "query": "from a_index | eval var = mv_count(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = replace(to_string(stringField), to_string(stringField), to_string(stringField))", + "query": "from a_index | eval mv_count(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval replace(numberField, numberField, numberField)", - "error": [ - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = mv_count(to_version(keywordField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval replace(stringField, stringField, stringField, extraArg)", + "query": "from a_index | eval mv_count(booleanField, extraArg)", "error": [ - "Error: [replace] function expects exactly 3 arguments, got 4." + "Error: [mv_count] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort replace(stringField, stringField, stringField)", + "query": "from a_index | sort mv_count(booleanField)", "error": [], "warning": [] }, { - "query": "row var = replace(to_string(true), to_string(true), to_string(true))", + "query": "from a_index | eval mv_count(null)", "error": [], "warning": [] }, { - "query": "row var = replace(true, true, true)", - "error": [ - "Argument of [replace] must be [string], found value [true] type [boolean]", - "Argument of [replace] must be [string], found value [true] type [boolean]", - "Argument of [replace] must be [string], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where length(replace(booleanField, booleanField, booleanField)) > 0", - "error": [ - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row nullVar = null | eval mv_count(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = replace(to_string(booleanField), to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval mv_count(\"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | eval replace(booleanField, booleanField, booleanField)", - "error": [ - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_count(concat(\"20\", \"22\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval replace(null, null, null)", + "query": "row var = mv_count(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval replace(nullVar, nullVar, nullVar)", + "query": "row mv_count(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = right(\"a\", 5)", + "query": "row var = mv_count(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row right(\"a\", 5)", + "query": "row var = mv_count(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = right(to_string(\"a\"), to_integer(\"a\"))", + "query": "row var = mv_count(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = right(5, \"a\")", - "error": [ - "Argument of [right] must be [string], found value [5] type [number]", - "Argument of [right] must be [number], found value [\"a\"] type [string]" - ], + "query": "row mv_count(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(right(stringField, numberField)) > 0", + "query": "row var = mv_count(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | where length(right(numberField, stringField)) > 0", - "error": [ - "Argument of [right] must be [string], found value [numberField] type [number]", - "Argument of [right] must be [number], found value [stringField] type [string]" - ], + "query": "row var = mv_count(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = right(stringField, numberField)", + "query": "row var = mv_dedupe(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval right(stringField, numberField)", + "query": "row mv_dedupe(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = right(to_string(stringField), to_integer(stringField))", + "query": "row var = mv_dedupe(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval right(numberField, stringField)", + "query": "row var = mv_dedupe(cartesianPointField)", "error": [ - "Argument of [right] must be [string], found value [numberField] type [number]", - "Argument of [right] must be [number], found value [stringField] type [string]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval right(stringField, numberField, extraArg)", + "query": "row mv_dedupe(cartesianPointField)", "error": [ - "Error: [right] function expects exactly 2 arguments, got 3." + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | sort right(stringField, numberField)", - "error": [], + "query": "row var = mv_dedupe(to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = right(to_string(true), to_integer(true))", + "query": "row var = mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = right(true, true)", - "error": [ - "Argument of [right] must be [string], found value [true] type [boolean]", - "Argument of [right] must be [number], found value [true] type [boolean]" - ], + "query": "row mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(right(booleanField, booleanField)) > 0", + "query": "row var = mv_dedupe(to_cartesianshape(cartesianPointField))", "error": [ - "Argument of [right] must be [string], found value [booleanField] type [boolean]", - "Argument of [right] must be [number], found value [booleanField] type [boolean]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = right(to_string(booleanField), to_integer(booleanField))", + "query": "row var = mv_dedupe(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval right(booleanField, booleanField)", - "error": [ - "Argument of [right] must be [string], found value [booleanField] type [boolean]", - "Argument of [right] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval right(null, null)", + "query": "row mv_dedupe(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval right(nullVar, nullVar)", + "query": "row var = mv_dedupe(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "row var = round(5, 5)", + "query": "row var = mv_dedupe(5.5)", "error": [], "warning": [] }, { - "query": "row round(5, 5)", + "query": "row mv_dedupe(5.5)", "error": [], "warning": [] }, { - "query": "row var = round(to_integer(\"a\"), to_integer(\"a\"))", + "query": "row var = mv_dedupe(to_double(true))", "error": [], "warning": [] }, { - "query": "row var = round(\"a\", \"a\")", + "query": "row var = mv_dedupe(geoPointField)", "error": [ - "Argument of [round] must be [number], found value [\"a\"] type [string]", - "Argument of [round] must be [number], found value [\"a\"] type [string]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | where round(numberField, numberField) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where round(stringField, stringField) > 0", + "query": "row mv_dedupe(geoPointField)", "error": [ - "Argument of [round] must be [number], found value [stringField] type [string]", - "Argument of [round] must be [number], found value [stringField] type [string]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = round(numberField, numberField)", - "error": [], + "query": "row var = mv_dedupe(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval round(numberField, numberField)", + "query": "row var = mv_dedupe(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = round(to_integer(stringField), to_integer(stringField))", + "query": "row mv_dedupe(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval round(stringField, stringField)", + "query": "row var = mv_dedupe(to_geoshape(geoPointField))", "error": [ - "Argument of [round] must be [number], found value [stringField] type [string]", - "Argument of [round] must be [number], found value [stringField] type [string]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval round(numberField, numberField, extraArg)", - "error": [ - "Error: [round] function expects no more than 2 arguments, got 3." - ], + "query": "row var = mv_dedupe(5)", + "error": [], "warning": [] }, { - "query": "from a_index | sort round(numberField, numberField)", + "query": "row mv_dedupe(5)", "error": [], "warning": [] }, { - "query": "row var = round(5)", + "query": "row var = mv_dedupe(to_integer(true))", "error": [], "warning": [] }, { - "query": "row round(5)", + "query": "row var = mv_dedupe(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = round(to_integer(true))", + "query": "row mv_dedupe(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = round(to_integer(true), to_integer(true))", + "query": "row var = mv_dedupe(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row var = round(true, true)", - "error": [ - "Argument of [round] must be [number], found value [true] type [boolean]", - "Argument of [round] must be [number], found value [true] type [boolean]" - ], + "query": "row var = mv_dedupe(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | where round(numberField) > 0", + "query": "row mv_dedupe(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where round(booleanField) > 0", - "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_dedupe(to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where round(booleanField, booleanField) > 0", - "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]", - "Argument of [round] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_dedupe(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = round(numberField)", + "query": "row mv_dedupe(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField)", + "query": "row var = mv_dedupe(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = round(to_integer(booleanField))", + "query": "from a_index | where mv_dedupe(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval round(booleanField)", + "query": "from a_index | where mv_dedupe(counterDoubleField) > 0", "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_dedupe] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | eval var = round(*)", - "error": [ - "Using wildcards (*) in round is not allowed" - ], + "query": "from a_index | where mv_dedupe(integerField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = round(to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | where mv_dedupe(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval round(booleanField, booleanField)", - "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]", - "Argument of [round] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_dedupe(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort round(numberField)", + "query": "from a_index | eval mv_dedupe(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval round(null, null)", + "query": "from a_index | eval var = mv_dedupe(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval round(nullVar, nullVar)", - "error": [], + "query": "from a_index | eval mv_dedupe(counterDoubleField)", + "error": [ + "Argument of [mv_dedupe] must be [boolean], found value [counterDoubleField] type [counter_double]" + ], "warning": [] }, { - "query": "row var = rtrim(\"a\")", - "error": [], + "query": "from a_index | eval var = mv_dedupe(*)", + "error": [ + "Using wildcards (*) in mv_dedupe is not allowed" + ], "warning": [] }, { - "query": "row rtrim(\"a\")", + "query": "from a_index | eval var = mv_dedupe(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = rtrim(to_string(\"a\"))", + "query": "from a_index | eval mv_dedupe(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = rtrim(5)", - "error": [ - "Argument of [rtrim] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval var = mv_dedupe(to_cartesianpoint(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(rtrim(stringField)) > 0", + "query": "from a_index | eval var = mv_dedupe(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(rtrim(numberField)) > 0", - "error": [ - "Argument of [rtrim] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval mv_dedupe(cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = rtrim(stringField)", + "query": "from a_index | eval var = mv_dedupe(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(stringField)", + "query": "from a_index | eval var = mv_dedupe(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = rtrim(to_string(stringField))", + "query": "from a_index | eval mv_dedupe(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(numberField)", - "error": [ - "Argument of [rtrim] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = mv_dedupe(to_datetime(dateField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(stringField, extraArg)", - "error": [ - "Error: [rtrim] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = mv_dedupe(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = rtrim(*)", - "error": [ - "Using wildcards (*) in rtrim is not allowed" - ], + "query": "from a_index | eval mv_dedupe(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort rtrim(stringField)", + "query": "from a_index | eval var = mv_dedupe(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = rtrim(to_string(true))", + "query": "from a_index | eval var = mv_dedupe(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = rtrim(true)", - "error": [ - "Argument of [rtrim] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval mv_dedupe(geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(rtrim(booleanField)) > 0", - "error": [ - "Argument of [rtrim] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_dedupe(to_geopoint(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = rtrim(to_string(booleanField))", + "query": "from a_index | eval var = mv_dedupe(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(booleanField)", - "error": [ - "Argument of [rtrim] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_dedupe(geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(null)", + "query": "from a_index | eval var = mv_dedupe(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval rtrim(nullVar)", + "query": "from a_index | eval var = mv_dedupe(integerField)", "error": [], "warning": [] }, { - "query": "row var = signum(5)", + "query": "from a_index | eval mv_dedupe(integerField)", "error": [], "warning": [] }, { - "query": "row signum(5)", + "query": "from a_index | eval var = mv_dedupe(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = signum(to_integer(\"a\"))", + "query": "from a_index | eval var = mv_dedupe(ipField)", "error": [], "warning": [] }, { - "query": "row var = signum(\"a\")", - "error": [ - "Argument of [signum] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval mv_dedupe(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | where signum(numberField) > 0", + "query": "from a_index | eval var = mv_dedupe(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | where signum(stringField) > 0", - "error": [ - "Argument of [signum] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = mv_dedupe(keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = signum(numberField)", + "query": "from a_index | eval mv_dedupe(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval signum(numberField)", + "query": "from a_index | eval var = mv_dedupe(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = signum(to_integer(stringField))", + "query": "from a_index | eval var = mv_dedupe(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval signum(stringField)", - "error": [ - "Argument of [signum] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval mv_dedupe(longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval signum(numberField, extraArg)", - "error": [ - "Error: [signum] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = mv_dedupe(textField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = signum(*)", - "error": [ - "Using wildcards (*) in signum is not allowed" - ], + "query": "from a_index | eval mv_dedupe(textField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort signum(numberField)", + "query": "from a_index | eval var = mv_dedupe(versionField)", "error": [], "warning": [] }, { - "query": "row var = signum(to_integer(true))", + "query": "from a_index | eval mv_dedupe(versionField)", "error": [], "warning": [] }, { - "query": "row var = signum(true)", - "error": [ - "Argument of [signum] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = mv_dedupe(to_version(keywordField))", + "error": [], "warning": [] }, { - "query": "from a_index | where signum(booleanField) > 0", + "query": "from a_index | eval mv_dedupe(booleanField, extraArg)", "error": [ - "Argument of [signum] must be [number], found value [booleanField] type [boolean]" + "Error: [mv_dedupe] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = signum(to_integer(booleanField))", + "query": "from a_index | sort mv_dedupe(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval signum(booleanField)", - "error": [ - "Argument of [signum] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval signum(null)", + "query": "from a_index | eval mv_dedupe(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval signum(nullVar)", + "query": "row nullVar = null | eval mv_dedupe(nullVar)", "error": [], "warning": [] }, { - "query": "row var = sin(5)", + "query": "from a_index | eval mv_dedupe(\"2022\")", "error": [], "warning": [] }, { - "query": "row sin(5)", + "query": "from a_index | eval mv_dedupe(concat(\"20\", \"22\"))", "error": [], "warning": [] }, { - "query": "row var = sin(to_integer(\"a\"))", + "query": "row var = mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = sin(\"a\")", - "error": [ - "Argument of [sin] must be [number], found value [\"a\"] type [string]" - ], + "query": "row mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where sin(numberField) > 0", + "query": "row var = mv_dedupe(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | where sin(stringField) > 0", - "error": [ - "Argument of [sin] must be [number], found value [stringField] type [string]" - ], + "query": "row var = mv_dedupe(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sin(numberField)", + "query": "row var = mv_dedupe(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval sin(numberField)", + "query": "row mv_dedupe(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = sin(to_integer(stringField))", + "query": "row var = mv_dedupe(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval sin(stringField)", - "error": [ - "Argument of [sin] must be [number], found value [stringField] type [string]" - ], + "query": "row var = mv_dedupe(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval sin(numberField, extraArg)", - "error": [ - "Error: [sin] function expects exactly one argument, got 2." - ], + "query": "row var = mv_first(true)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sin(*)", - "error": [ - "Using wildcards (*) in sin is not allowed" - ], + "query": "row mv_first(true)", + "error": [], "warning": [] }, { - "query": "from a_index | sort sin(numberField)", + "query": "row var = mv_first(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = sin(to_integer(true))", - "error": [], + "query": "row var = mv_first(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = sin(true)", + "query": "row mv_first(cartesianPointField)", "error": [ - "Argument of [sin] must be [number], found value [true] type [boolean]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | where sin(booleanField) > 0", + "query": "row var = mv_first(to_cartesianpoint(cartesianPointField))", "error": [ - "Argument of [sin] must be [number], found value [booleanField] type [boolean]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = sin(to_integer(booleanField))", + "query": "row var = mv_first(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval sin(booleanField)", + "query": "row mv_first(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = mv_first(to_cartesianshape(cartesianPointField))", "error": [ - "Argument of [sin] must be [number], found value [booleanField] type [boolean]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval sin(null)", + "query": "row var = mv_first(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval sin(nullVar)", + "query": "row mv_first(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "row var = sinh(5)", + "query": "row var = mv_first(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "row sinh(5)", + "query": "row var = mv_first(5.5)", "error": [], "warning": [] }, { - "query": "row var = sinh(to_integer(\"a\"))", + "query": "row mv_first(5.5)", "error": [], "warning": [] }, { - "query": "row var = sinh(\"a\")", - "error": [ - "Argument of [sinh] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = mv_first(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where sinh(numberField) > 0", - "error": [], + "query": "row var = mv_first(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where sinh(stringField) > 0", + "query": "row mv_first(geoPointField)", "error": [ - "Argument of [sinh] must be [number], found value [stringField] type [string]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = sinh(numberField)", - "error": [], + "query": "row var = mv_first(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval sinh(numberField)", + "query": "row var = mv_first(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = sinh(to_integer(stringField))", + "query": "row mv_first(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval sinh(stringField)", + "query": "row var = mv_first(to_geoshape(geoPointField))", "error": [ - "Argument of [sinh] must be [number], found value [stringField] type [string]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval sinh(numberField, extraArg)", - "error": [ - "Error: [sinh] function expects exactly one argument, got 2." - ], + "query": "row var = mv_first(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sinh(*)", - "error": [ - "Using wildcards (*) in sinh is not allowed" - ], + "query": "row mv_first(5)", + "error": [], "warning": [] }, { - "query": "from a_index | sort sinh(numberField)", + "query": "row var = mv_first(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = sinh(to_integer(true))", + "query": "row var = mv_first(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = sinh(true)", - "error": [ - "Argument of [sinh] must be [number], found value [true] type [boolean]" - ], + "query": "row mv_first(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where sinh(booleanField) > 0", - "error": [ - "Argument of [sinh] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_first(to_ip(to_ip(\"127.0.0.1\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sinh(to_integer(booleanField))", + "query": "row var = mv_first(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval sinh(booleanField)", - "error": [ - "Argument of [sinh] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row mv_first(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval sinh(null)", + "query": "row var = mv_first(to_string(true))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval sinh(nullVar)", + "query": "row var = mv_first(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = split(\"a\", \"a\")", + "query": "row mv_first(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row split(\"a\", \"a\")", + "query": "row var = mv_first(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = split(to_string(\"a\"), to_string(\"a\"))", + "query": "from a_index | where mv_first(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = split(5, 5)", + "query": "from a_index | where mv_first(counterDoubleField) > 0", "error": [ - "Argument of [split] must be [string], found value [5] type [number]", - "Argument of [split] must be [string], found value [5] type [number]" + "Argument of [mv_first] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | where length(split(stringField, stringField)) > 0", + "query": "from a_index | where mv_first(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(split(numberField, numberField)) > 0", - "error": [ - "Argument of [split] must be [string], found value [numberField] type [number]", - "Argument of [split] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | where mv_first(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where mv_first(unsignedLongField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = split(stringField, stringField)", + "query": "from a_index | eval var = mv_first(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval split(stringField, stringField)", + "query": "from a_index | eval mv_first(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = split(to_string(stringField), to_string(stringField))", + "query": "from a_index | eval var = mv_first(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval split(numberField, numberField)", + "query": "from a_index | eval mv_first(counterDoubleField)", "error": [ - "Argument of [split] must be [string], found value [numberField] type [number]", - "Argument of [split] must be [string], found value [numberField] type [number]" + "Argument of [mv_first] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | eval split(stringField, stringField, extraArg)", + "query": "from a_index | eval var = mv_first(*)", "error": [ - "Error: [split] function expects exactly 2 arguments, got 3." + "Using wildcards (*) in mv_first is not allowed" ], "warning": [] }, { - "query": "from a_index | sort split(stringField, stringField)", + "query": "from a_index | eval var = mv_first(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = split(to_string(true), to_string(true))", + "query": "from a_index | eval mv_first(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = split(true, true)", - "error": [ - "Argument of [split] must be [string], found value [true] type [boolean]", - "Argument of [split] must be [string], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where length(split(booleanField, booleanField)) > 0", - "error": [ - "Argument of [split] must be [string], found value [booleanField] type [boolean]", - "Argument of [split] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_first(to_cartesianpoint(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = split(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = mv_first(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval split(booleanField, booleanField)", - "error": [ - "Argument of [split] must be [string], found value [booleanField] type [boolean]", - "Argument of [split] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_first(cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval split(null, null)", + "query": "from a_index | eval var = mv_first(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval split(nullVar, nullVar)", + "query": "from a_index | eval var = mv_first(dateField)", "error": [], "warning": [] }, { - "query": "row var = sqrt(5)", + "query": "from a_index | eval mv_first(dateField)", "error": [], "warning": [] }, { - "query": "row sqrt(5)", + "query": "from a_index | eval var = mv_first(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = sqrt(to_integer(\"a\"))", + "query": "from a_index | eval var = mv_first(doubleField)", "error": [], "warning": [] }, { - "query": "row var = sqrt(\"a\")", - "error": [ - "Argument of [sqrt] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval mv_first(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | where sqrt(numberField) > 0", + "query": "from a_index | eval var = mv_first(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where sqrt(stringField) > 0", - "error": [ - "Argument of [sqrt] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = mv_first(geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sqrt(numberField)", + "query": "from a_index | eval mv_first(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(numberField)", + "query": "from a_index | eval var = mv_first(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = sqrt(to_integer(stringField))", + "query": "from a_index | eval var = mv_first(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(stringField)", - "error": [ - "Argument of [sqrt] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval mv_first(geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(numberField, extraArg)", - "error": [ - "Error: [sqrt] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = mv_first(to_geoshape(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sqrt(*)", - "error": [ - "Using wildcards (*) in sqrt is not allowed" - ], + "query": "from a_index | eval var = mv_first(integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort sqrt(numberField)", + "query": "from a_index | eval mv_first(integerField)", "error": [], "warning": [] }, { - "query": "row var = sqrt(to_integer(true))", + "query": "from a_index | eval var = mv_first(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = sqrt(true)", - "error": [ - "Argument of [sqrt] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = mv_first(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | where sqrt(booleanField) > 0", - "error": [ - "Argument of [sqrt] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_first(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sqrt(to_integer(booleanField))", + "query": "from a_index | eval var = mv_first(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(booleanField)", - "error": [ - "Argument of [sqrt] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_first(keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(null)", + "query": "from a_index | eval mv_first(keywordField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval sqrt(nullVar)", + "query": "from a_index | eval var = mv_first(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_first(longField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_first(longField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "query": "from a_index | eval var = mv_first(textField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(\"a\", \"a\")", - "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_contains] must be [cartesian_point], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval mv_first(textField)", + "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_first(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_first(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_first(versionField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_first(versionField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_first(to_version(keywordField))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", - "error": [], + "query": "from a_index | eval mv_first(booleanField, extraArg)", + "error": [ + "Error: [mv_first] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | sort mv_first(booleanField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_first(null)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval mv_first(nullVar)", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_first(\"2022\")", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", + "query": "from a_index | eval mv_first(concat(\"20\", \"22\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = mv_first(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row mv_first(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = mv_first(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = mv_first(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = mv_first(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "row mv_first(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = mv_first(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = mv_first(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoPointField, geoPointField)", + "query": "row var = mv_last(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoPointField)", + "query": "row mv_last(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(stringField), to_geopoint(stringField))", + "query": "row var = mv_last(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(stringField, stringField)", + "query": "row var = mv_last(cartesianPointField)", "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]" + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoPointField, extraArg)", + "query": "row mv_last(cartesianPointField)", "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoPointField, geoShapeField)", - "error": [], + "query": "row var = mv_last(to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoShapeField)", + "query": "row var = mv_last(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(stringField), geoShapeField)", + "query": "row mv_last(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoShapeField, extraArg)", + "query": "row var = mv_last(to_cartesianshape(cartesianPointField))", "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoShapeField, geoPointField)", + "query": "row var = mv_last(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoPointField)", + "query": "row mv_last(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoShapeField, to_geopoint(stringField))", + "query": "row var = mv_last(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoPointField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_last(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoShapeField, geoShapeField)", + "query": "row mv_last(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoShapeField)", + "query": "row var = mv_last(to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoShapeField, extraArg)", + "query": "row var = mv_last(geoPointField)", "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianPointField)", - "error": [], + "query": "row mv_last(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField)", + "query": "row var = mv_last(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = mv_last(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "row mv_last(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField, extraArg)", + "query": "row var = mv_last(to_geoshape(geoPointField))", "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianShapeField)", + "query": "row var = mv_last(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianShapeField)", + "query": "row mv_last(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(stringField), cartesianShapeField)", + "query": "row var = mv_last(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_last(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianPointField)", + "query": "row mv_last(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianPointField)", + "query": "row var = mv_last(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianShapeField, to_cartesianpoint(stringField))", + "query": "row var = mv_last(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row mv_last(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianShapeField)", + "query": "row var = mv_last(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianShapeField)", + "query": "row var = mv_last(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row mv_last(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort st_contains(geoPointField, geoPointField)", + "query": "row var = mv_last(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | where mv_last(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | where mv_last(counterDoubleField) > 0", + "error": [ + "Argument of [mv_last] must be [boolean], found value [counterDoubleField] type [counter_double]" + ], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | where mv_last(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | where mv_last(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | where mv_last(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_last(booleanField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval mv_last(booleanField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_last(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_contains(true, true)", + "query": "from a_index | eval mv_last(counterDoubleField)", "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]" + "Argument of [mv_last] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval st_contains(booleanField, booleanField)", + "query": "from a_index | eval var = mv_last(*)", "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]" + "Using wildcards (*) in mv_last is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = mv_last(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval mv_last(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = mv_last(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval var = mv_last(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval mv_last(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval var = mv_last(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval var = mv_last(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | sort st_contains(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval mv_last(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(null, null)", + "query": "from a_index | eval var = mv_last(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_contains(nullVar, nullVar)", + "query": "from a_index | eval var = mv_last(doubleField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(doubleField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "query": "from a_index | eval var = mv_last(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(\"a\", \"a\")", - "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_disjoint] must be [cartesian_point], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval mv_last(geoPointField)", + "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(integerField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "query": "from a_index | eval mv_last(integerField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(ipField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(ipField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", + "query": "from a_index | eval var = mv_last(keywordField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(keywordField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(longField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(longField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(textField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "from a_index | eval mv_last(textField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoPointField, geoPointField)", + "query": "from a_index | eval var = mv_last(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoPointField)", + "query": "from a_index | eval mv_last(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(stringField), to_geopoint(stringField))", + "query": "from a_index | eval var = mv_last(to_version(keywordField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(stringField, stringField)", + "query": "from a_index | eval mv_last(booleanField, extraArg)", "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]" + "Error: [mv_last] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_disjoint(geoPointField, geoShapeField)", + "query": "from a_index | sort mv_last(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoShapeField)", + "query": "from a_index | eval mv_last(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(stringField), geoShapeField)", + "query": "row nullVar = null | eval mv_last(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_disjoint(geoShapeField, geoPointField)", + "query": "from a_index | eval mv_last(\"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoPointField)", + "query": "from a_index | eval mv_last(concat(\"20\", \"22\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoShapeField, to_geopoint(stringField))", + "query": "row var = mv_last(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row mv_last(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoShapeField, geoShapeField)", + "query": "row var = mv_last(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoShapeField)", + "query": "row var = mv_last(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_last(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianPointField)", + "query": "row mv_last(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField)", + "query": "row var = mv_last(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "row var = mv_last(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_max(true)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianShapeField)", + "query": "row mv_max(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField)", + "query": "row var = mv_max(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), cartesianShapeField)", + "query": "row var = mv_max(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row mv_max(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianPointField)", + "query": "row var = mv_max(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField)", + "query": "row var = mv_max(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianShapeField, to_cartesianpoint(stringField))", + "query": "row mv_max(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_max(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianShapeField)", + "query": "row var = mv_max(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField)", + "query": "row mv_max(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_max(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort st_disjoint(geoPointField, geoPointField)", + "query": "row var = mv_max(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row mv_max(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = mv_max(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = mv_max(\"a\")", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row mv_max(\"a\")", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_max(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_max(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row mv_max(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_max(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(true, true)", + "query": "row var = mv_max(to_cartesianpoint(\"POINT (30 10)\"))", "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]" + "Argument of [mv_max] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | where mv_max(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(booleanField, booleanField)", + "query": "from a_index | where mv_max(cartesianPointField) > 0", "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]" + "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | where mv_max(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | where mv_max(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | where mv_max(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval var = mv_max(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval mv_max(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval var = mv_max(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))", - "error": [], + "query": "from a_index | eval mv_max(cartesianPointField)", + "error": [ + "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | sort st_disjoint(cartesianPointField, cartesianPointField)", - "error": [], + "query": "from a_index | eval var = mv_max(*)", + "error": [ + "Using wildcards (*) in mv_max is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval st_disjoint(null, null)", + "query": "from a_index | eval var = mv_max(dateField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_disjoint(nullVar, nullVar)", + "query": "from a_index | eval mv_max(dateField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(doubleField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "query": "from a_index | eval mv_max(doubleField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(\"a\", \"a\")", - "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_intersects] must be [cartesian_point], found value [\"a\"] type [string]" - ], - "warning": [] - }, - { - "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(integerField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_max(integerField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(ipField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "query": "from a_index | eval mv_max(ipField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(keywordField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_max(keywordField)", "error": [], "warning": [] }, { - "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", + "query": "from a_index | eval var = mv_max(longField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_max(longField)", "error": [], "warning": [] }, { - "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(textField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_max(textField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_max(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "from a_index | eval var = mv_max(versionField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_max(versionField)", "error": [], "warning": [] }, { - "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(to_version(keywordField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoPointField, geoPointField)", - "error": [], + "query": "from a_index | eval mv_max(booleanField, extraArg)", + "error": [ + "Error: [mv_max] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoPointField)", + "query": "from a_index | sort mv_max(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(stringField), to_geopoint(stringField))", + "query": "from a_index | eval mv_max(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(stringField, stringField)", - "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]" - ], + "query": "row nullVar = null | eval mv_max(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoPointField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval mv_max(\"2022\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoPointField, geoShapeField)", + "query": "from a_index | eval mv_max(concat(\"20\", \"22\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoShapeField)", + "query": "row var = mv_median(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(stringField), geoShapeField)", + "query": "row mv_median(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoShapeField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_median(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoShapeField, geoPointField)", + "query": "row var = mv_median(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoShapeField, geoPointField)", + "query": "row mv_median(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoShapeField, to_geopoint(stringField))", + "query": "row var = mv_median(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoShapeField, geoPointField, extraArg)", + "query": "row var = mv_median(true)", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Argument of [mv_median] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoShapeField, geoShapeField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval st_intersects(geoShapeField, geoShapeField)", + "query": "from a_index | where mv_median(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoShapeField, geoShapeField, extraArg)", + "query": "from a_index | where mv_median(booleanField) > 0", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Argument of [mv_median] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianPointField)", + "query": "from a_index | where mv_median(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField)", + "query": "from a_index | where mv_median(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "from a_index | where mv_median(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = mv_median(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianShapeField)", + "query": "from a_index | eval mv_median(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianShapeField)", + "query": "from a_index | eval var = mv_median(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(stringField), cartesianShapeField)", - "error": [], + "query": "from a_index | eval mv_median(booleanField)", + "error": [ + "Argument of [mv_median] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianShapeField, extraArg)", + "query": "from a_index | eval var = mv_median(*)", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Using wildcards (*) in mv_median is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianPointField)", + "query": "from a_index | eval var = mv_median(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianPointField)", + "query": "from a_index | eval mv_median(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianShapeField, to_cartesianpoint(stringField))", + "query": "from a_index | eval var = mv_median(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = mv_median(longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianShapeField)", + "query": "from a_index | eval mv_median(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField)", + "query": "from a_index | eval var = mv_median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval mv_median(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField, extraArg)", + "query": "from a_index | eval mv_median(doubleField, extraArg)", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Error: [mv_median] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort st_intersects(geoPointField, geoPointField)", + "query": "from a_index | sort mv_median(doubleField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval mv_median(null)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row nullVar = null | eval mv_median(nullVar)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = mv_min(true)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row mv_min(true)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_min(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_min(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row mv_min(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_min(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(true, true)", - "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "row var = mv_min(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "row mv_min(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(booleanField, booleanField)", - "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_min(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "row var = mv_min(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "row mv_min(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "row var = mv_min(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "row var = mv_min(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "query": "row mv_min(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "query": "row var = mv_min(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | sort st_intersects(cartesianPointField, cartesianPointField)", + "query": "row var = mv_min(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(null, null)", + "query": "row mv_min(\"a\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_intersects(nullVar, nullVar)", + "query": "row var = mv_min(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "row var = mv_min(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "row mv_min(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "query": "row var = mv_min(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = st_within(\"a\", \"a\")", + "query": "row var = mv_min(to_cartesianpoint(\"POINT (30 10)\"))", "error": [ - "Argument of [st_within] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_within] must be [cartesian_point], found value [\"a\"] type [string]" + "Argument of [mv_min] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" ], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | where mv_min(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where mv_min(cartesianPointField) > 0", + "error": [ + "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | where mv_min(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | where mv_min(longField) > 0", "error": [], "warning": [] }, { - "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | where mv_min(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "query": "from a_index | eval var = mv_min(booleanField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_min(booleanField)", "error": [], "warning": [] }, { - "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_min(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval mv_min(cartesianPointField)", + "error": [ + "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval var = mv_min(*)", + "error": [ + "Using wildcards (*) in mv_min is not allowed" + ], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", + "query": "from a_index | eval var = mv_min(dateField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_min(dateField)", "error": [], "warning": [] }, { - "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_min(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_min(doubleField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_min(doubleField)", "error": [], "warning": [] }, { - "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_min(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "from a_index | eval var = mv_min(integerField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_min(integerField)", "error": [], "warning": [] }, { - "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_min(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoPointField, geoPointField)", + "query": "from a_index | eval var = mv_min(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoPointField)", + "query": "from a_index | eval mv_min(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(stringField), to_geopoint(stringField))", + "query": "from a_index | eval var = mv_min(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(stringField, stringField)", - "error": [ - "Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = mv_min(keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoPointField, extraArg)", - "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval mv_min(keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoPointField, geoShapeField)", + "query": "from a_index | eval var = mv_min(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoShapeField)", + "query": "from a_index | eval var = mv_min(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(stringField), geoShapeField)", + "query": "from a_index | eval mv_min(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoShapeField, extraArg)", - "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = mv_min(textField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoShapeField, geoPointField)", + "query": "from a_index | eval mv_min(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoPointField)", + "query": "from a_index | eval var = mv_min(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoShapeField, to_geopoint(stringField))", + "query": "from a_index | eval mv_min(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoPointField, extraArg)", - "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = mv_min(versionField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoShapeField, geoShapeField)", + "query": "from a_index | eval mv_min(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoShapeField)", + "query": "from a_index | eval var = mv_min(to_version(keywordField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoShapeField, extraArg)", + "query": "from a_index | eval mv_min(booleanField, extraArg)", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Error: [mv_min] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianPointField, cartesianPointField)", + "query": "from a_index | sort mv_min(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval mv_min(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "row nullVar = null | eval mv_min(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_within(cartesianPointField, cartesianShapeField)", + "query": "from a_index | eval mv_min(\"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianShapeField)", + "query": "from a_index | eval mv_min(concat(\"20\", \"22\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(stringField), cartesianShapeField)", + "query": "row var = mv_slice(true, 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." - ], + "query": "row mv_slice(true, 5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianPointField)", + "query": "row var = mv_slice(to_boolean(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianPointField)", - "error": [], + "query": "row var = mv_slice(cartesianPointField, 5, 5)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianShapeField, to_cartesianpoint(stringField))", - "error": [], + "query": "row mv_slice(cartesianPointField, 5, 5)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianPointField, extraArg)", + "query": "row var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(true), to_integer(true))", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianShapeField)", + "query": "row var = mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianShapeField)", + "query": "row mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianShapeField, extraArg)", + "query": "row var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(true), to_integer(true))", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Unknown column [cartesianPointField]" ], "warning": [] }, { - "query": "from a_index | sort st_within(geoPointField, geoPointField)", + "query": "row var = mv_slice(to_datetime(\"2021-01-01T00:00:00Z\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row mv_slice(to_datetime(\"2021-01-01T00:00:00Z\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = mv_slice(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = mv_slice(5.5, 5, 5)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row mv_slice(5.5, 5, 5)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_slice(to_double(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = mv_slice(geoPointField, 5, 5)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row mv_slice(geoPointField, 5, 5)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = mv_slice(to_geopoint(geoPointField), to_integer(true), to_integer(true))", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = st_within(true, true)", - "error": [ - "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "row var = mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "row mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(booleanField, booleanField)", + "query": "row var = mv_slice(to_geoshape(geoPointField), to_integer(true), to_integer(true))", "error": [ - "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "row var = mv_slice(5, 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "row mv_slice(5, 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "row var = mv_slice(to_integer(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "row var = mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "query": "row mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "query": "row var = mv_slice(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "row var = mv_slice(\"a\", 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | sort st_within(cartesianPointField, cartesianPointField)", + "query": "row mv_slice(\"a\", 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(null, null)", + "query": "row var = mv_slice(to_string(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_within(nullVar, nullVar)", + "query": "row var = mv_slice(5, to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = st_x(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = mv_slice(to_version(\"1.0.0\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row st_x(to_geopoint(\"POINT (30 10)\"))", + "query": "row mv_slice(to_version(\"1.0.0\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row var = st_x(to_geopoint(\"a\"))", + "query": "row var = mv_slice(to_version(\"a\"), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = st_x(\"a\")", + "query": "row var = mv_slice(5.5, true, true)", "error": [ - "Argument of [st_x] must be [cartesian_point], found value [\"a\"] type [string]" + "Argument of [mv_slice] must be [integer], found value [true] type [boolean]", + "Argument of [mv_slice] must be [integer], found value [true] type [boolean]" ], "warning": [] }, { - "query": "row var = st_x(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | where mv_slice(doubleField, integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "row st_x(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where mv_slice(counterDoubleField, booleanField, booleanField) > 0", + "error": [ + "Argument of [mv_slice] must be [boolean], found value [counterDoubleField] type [counter_double]", + "Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = st_x(to_cartesianpoint(\"a\"))", + "query": "from a_index | where mv_slice(integerField, integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(geoPointField)", + "query": "from a_index | where mv_slice(longField, integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(geoPointField)", + "query": "from a_index | eval var = mv_slice(booleanField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_geopoint(stringField))", + "query": "from a_index | eval mv_slice(booleanField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(stringField)", - "error": [ - "Argument of [st_x] must be [cartesian_point], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = mv_slice(to_boolean(booleanField), to_integer(booleanField), to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(geoPointField, extraArg)", + "query": "from a_index | eval mv_slice(counterDoubleField, booleanField, booleanField)", "error": [ - "Error: [st_x] function expects exactly one argument, got 2." + "Argument of [mv_slice] must be [boolean], found value [counterDoubleField] type [counter_double]", + "Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_x(*)", - "error": [ - "Using wildcards (*) in st_x is not allowed" - ], + "query": "from a_index | eval var = mv_slice(cartesianPointField, integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(cartesianPointField)", + "query": "from a_index | eval mv_slice(cartesianPointField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(cartesianPointField)", + "query": "from a_index | eval var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_cartesianpoint(stringField))", + "query": "from a_index | eval var = mv_slice(cartesianShapeField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(cartesianPointField, extraArg)", - "error": [ - "Error: [st_x] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval mv_slice(cartesianShapeField, integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort st_x(geoPointField)", + "query": "from a_index | eval var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_x(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_slice(dateField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = st_x(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval mv_slice(dateField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = st_x(true)", - "error": [ - "Argument of [st_x] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = mv_slice(to_datetime(dateField), to_integer(booleanField), to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = mv_slice(doubleField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(booleanField)", - "error": [ - "Argument of [st_x] must be [cartesian_point], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_slice(doubleField, integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_geopoint(geoPointField))", + "query": "from a_index | eval var = mv_slice(to_double(booleanField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | sort st_x(cartesianPointField)", + "query": "from a_index | eval var = mv_slice(geoPointField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(null)", + "query": "from a_index | eval mv_slice(geoPointField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_x(nullVar)", + "query": "from a_index | eval var = mv_slice(to_geopoint(geoPointField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_y(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_slice(geoShapeField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row st_y(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_slice(geoShapeField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = st_y(to_geopoint(\"a\"))", + "query": "from a_index | eval var = mv_slice(to_geoshape(geoPointField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_y(\"a\")", - "error": [ - "Argument of [st_y] must be [cartesian_point], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval var = mv_slice(integerField, integerField, integerField)", + "error": [], "warning": [] }, { - "query": "row var = st_y(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_slice(integerField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row st_y(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_slice(to_integer(booleanField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_y(to_cartesianpoint(\"a\"))", + "query": "from a_index | eval var = mv_slice(ipField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(geoPointField)", + "query": "from a_index | eval mv_slice(ipField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(geoPointField)", + "query": "from a_index | eval var = mv_slice(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_geopoint(stringField))", + "query": "from a_index | eval var = mv_slice(keywordField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(stringField)", - "error": [ - "Argument of [st_y] must be [cartesian_point], found value [stringField] type [string]" - ], + "query": "from a_index | eval mv_slice(keywordField, integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(geoPointField, extraArg)", - "error": [ - "Error: [st_y] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = mv_slice(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(*)", - "error": [ - "Using wildcards (*) in st_y is not allowed" - ], + "query": "from a_index | eval var = mv_slice(longField, integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(cartesianPointField)", + "query": "from a_index | eval mv_slice(longField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(cartesianPointField)", + "query": "from a_index | eval var = mv_slice(longField, to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_cartesianpoint(stringField))", + "query": "from a_index | eval var = mv_slice(textField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(cartesianPointField, extraArg)", - "error": [ - "Error: [st_y] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval mv_slice(textField, integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort st_y(geoPointField)", + "query": "from a_index | eval var = mv_slice(versionField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = st_y(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval mv_slice(versionField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = st_y(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_slice(to_version(keywordField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = st_y(true)", + "query": "from a_index | eval mv_slice(booleanField, integerField, integerField, extraArg)", "error": [ - "Argument of [st_y] must be [cartesian_point], found value [true] type [boolean]" + "Error: [mv_slice] function expects no more than 3 arguments, got 4." ], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | sort mv_slice(booleanField, integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(booleanField)", - "error": [ - "Argument of [st_y] must be [cartesian_point], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_slice(null, null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_geopoint(geoPointField))", + "query": "row nullVar = null | eval mv_slice(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | sort st_y(cartesianPointField)", + "query": "from a_index | eval mv_slice(\"2022\", integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(null)", + "query": "from a_index | eval mv_slice(concat(\"20\", \"22\"), integerField, integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_y(nullVar)", + "query": "row var = mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row var = starts_with(\"a\", \"a\")", + "query": "row mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row starts_with(\"a\", \"a\")", + "query": "row var = mv_slice(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = starts_with(to_string(\"a\"), to_string(\"a\"))", + "query": "row var = mv_slice(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = starts_with(5, 5)", - "error": [ - "Argument of [starts_with] must be [string], found value [5] type [number]", - "Argument of [starts_with] must be [string], found value [5] type [number]" - ], + "query": "row var = mv_slice(to_datetime(\"2021-01-01T00:00:00Z\"), to_integer(true), to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = starts_with(stringField, stringField)", + "query": "row var = mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(stringField, stringField)", + "query": "row mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = starts_with(to_string(stringField), to_string(stringField))", + "query": "row var = mv_slice(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(numberField, numberField)", - "error": [ - "Argument of [starts_with] must be [string], found value [numberField] type [number]", - "Argument of [starts_with] must be [string], found value [numberField] type [number]" - ], + "query": "row var = mv_slice(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(stringField, stringField, extraArg)", - "error": [ - "Error: [starts_with] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = mv_slice(dateField, to_integer(booleanField), to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort starts_with(stringField, stringField)", + "query": "row var = mv_sort(true, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = starts_with(to_string(true), to_string(true))", + "query": "row mv_sort(true, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = starts_with(true, true)", - "error": [ - "Argument of [starts_with] must be [string], found value [true] type [boolean]", - "Argument of [starts_with] must be [string], found value [true] type [boolean]" - ], + "query": "row var = mv_sort(to_datetime(\"2021-01-01T00:00:00Z\"), \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = starts_with(to_string(booleanField), to_string(booleanField))", + "query": "row mv_sort(to_datetime(\"2021-01-01T00:00:00Z\"), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(booleanField, booleanField)", - "error": [ - "Argument of [starts_with] must be [string], found value [booleanField] type [boolean]", - "Argument of [starts_with] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_sort(5.5, \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(null, null)", + "query": "row mv_sort(5.5, \"asc\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval starts_with(nullVar, nullVar)", + "query": "row var = mv_sort(5, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = substring(\"a\", 5, 5)", + "query": "row mv_sort(5, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = substring(\"a\", 5)", + "query": "row var = mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", "error": [], "warning": [] }, { - "query": "row substring(\"a\", 5, 5)", + "query": "row mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", "error": [], "warning": [] }, { - "query": "row substring(\"a\", 5)", + "query": "row var = mv_sort(\"a\", \"asc\")", "error": [], "warning": [] }, { - "query": "row var = substring(to_string(\"a\"), to_integer(\"a\"), to_integer(\"a\"))", + "query": "row mv_sort(\"a\", \"asc\")", "error": [], "warning": [] }, { - "query": "row var = substring(5, \"a\", \"a\")", - "error": [ - "Argument of [substring] must be [string], found value [5] type [number]", - "Argument of [substring] must be [number], found value [\"a\"] type [string]", - "Argument of [substring] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = mv_sort(to_version(\"1.0.0\"), \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | where length(substring(stringField, numberField, numberField)) > 0", + "query": "row mv_sort(to_version(\"1.0.0\"), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | where length(substring(numberField, stringField, stringField)) > 0", + "query": "row var = mv_sort(to_cartesianpoint(\"POINT (30 10)\"), true)", "error": [ - "Argument of [substring] must be [string], found value [numberField] type [number]", - "Argument of [substring] must be [number], found value [stringField] type [string]", - "Argument of [substring] must be [number], found value [stringField] type [string]" + "Argument of [mv_sort] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", + "Argument of [mv_sort] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = substring(stringField, numberField, numberField)", + "query": "from a_index | eval var = mv_sort(booleanField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval substring(stringField, numberField, numberField)", + "query": "from a_index | eval mv_sort(booleanField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = substring(to_string(stringField), to_integer(stringField), to_integer(stringField))", + "query": "from a_index | eval var = mv_sort(dateField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval substring(numberField, stringField, stringField)", - "error": [ - "Argument of [substring] must be [string], found value [numberField] type [number]", - "Argument of [substring] must be [number], found value [stringField] type [string]", - "Argument of [substring] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval substring(stringField, numberField, numberField, extraArg)", - "error": [ - "Error: [substring] function expects no more than 3 arguments, got 4." - ], + "query": "from a_index | eval mv_sort(dateField, \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | sort substring(stringField, numberField, numberField)", + "query": "from a_index | eval var = mv_sort(doubleField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | sort substring(stringField, numberField)", + "query": "from a_index | eval mv_sort(doubleField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = substring(to_string(true), to_integer(true), to_integer(true))", + "query": "from a_index | eval var = mv_sort(integerField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = substring(true, true, true)", - "error": [ - "Argument of [substring] must be [string], found value [true] type [boolean]", - "Argument of [substring] must be [number], found value [true] type [boolean]", - "Argument of [substring] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval mv_sort(integerField, \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | where length(substring(booleanField, booleanField, booleanField)) > 0", - "error": [ - "Argument of [substring] must be [string], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_sort(ipField, \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = substring(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval mv_sort(ipField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval substring(booleanField, booleanField, booleanField)", - "error": [ - "Argument of [substring] must be [string], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_sort(keywordField, \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval substring(null, null, null)", + "query": "from a_index | eval mv_sort(keywordField, \"asc\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval substring(nullVar, nullVar, nullVar)", + "query": "from a_index | eval var = mv_sort(longField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = tan(5)", + "query": "from a_index | eval mv_sort(longField, \"asc\")", "error": [], "warning": [] }, { - "query": "row tan(5)", + "query": "from a_index | eval var = mv_sort(textField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = tan(to_integer(\"a\"))", + "query": "from a_index | eval mv_sort(textField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = tan(\"a\")", - "error": [ - "Argument of [tan] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval var = mv_sort(versionField, \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | where tan(numberField) > 0", + "query": "from a_index | eval mv_sort(versionField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | where tan(stringField) > 0", + "query": "from a_index | eval mv_sort(booleanField, \"asc\", extraArg)", "error": [ - "Argument of [tan] must be [number], found value [stringField] type [string]" + "Error: [mv_sort] function expects no more than 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = tan(numberField)", + "query": "from a_index | sort mv_sort(booleanField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval tan(numberField)", + "query": "from a_index | eval mv_sort(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = tan(to_integer(stringField))", + "query": "row nullVar = null | eval mv_sort(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval tan(stringField)", - "error": [ - "Argument of [tan] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval mv_sort(\"2022\", \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval tan(numberField, extraArg)", - "error": [ - "Error: [tan] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval mv_sort(concat(\"20\", \"22\"), \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tan(*)", - "error": [ - "Using wildcards (*) in tan is not allowed" - ], - "warning": [] + "query": "row var = mv_sort(5, \"a\")", + "error": [], + "warning": [ + "Invalid option [\"a\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] }, { - "query": "from a_index | sort tan(numberField)", + "query": "row mv_sort(5, \"a\")", "error": [], - "warning": [] + "warning": [ + "Invalid option [\"a\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] }, { - "query": "row var = tan(to_integer(true))", + "query": "row var = mv_sort(\"a\", \"a\")", "error": [], - "warning": [] + "warning": [ + "Invalid option [\"a\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] }, { - "query": "row var = tan(true)", - "error": [ - "Argument of [tan] must be [number], found value [true] type [boolean]" - ], - "warning": [] + "query": "row mv_sort(\"a\", \"a\")", + "error": [], + "warning": [ + "Invalid option [\"a\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] }, { - "query": "from a_index | where tan(booleanField) > 0", - "error": [ - "Argument of [tan] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] + "query": "row var = mv_sort(to_version(\"1.0.0\"), \"a\")", + "error": [], + "warning": [ + "Invalid option [\"a\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] }, { - "query": "from a_index | eval var = tan(to_integer(booleanField))", + "query": "row mv_sort(to_version(\"1.0.0\"), \"a\")", "error": [], - "warning": [] + "warning": [ + "Invalid option [\"a\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] }, { - "query": "from a_index | eval tan(booleanField)", - "error": [ - "Argument of [tan] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_sort(longField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval tan(null)", + "query": "from a_index | eval mv_sort(longField, keywordField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval tan(nullVar)", + "query": "from a_index | eval var = mv_sort(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = tanh(5)", + "query": "from a_index | eval mv_sort(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row tanh(5)", + "query": "from a_index | eval var = mv_sort(versionField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = tanh(to_integer(\"a\"))", + "query": "from a_index | eval mv_sort(versionField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = tanh(\"a\")", - "error": [ - "Argument of [tanh] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = mv_sum(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | where tanh(numberField) > 0", + "query": "row mv_sum(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | where tanh(stringField) > 0", - "error": [ - "Argument of [tanh] must be [number], found value [stringField] type [string]" - ], + "query": "row var = mv_sum(to_double(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tanh(numberField)", + "query": "row var = mv_sum(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval tanh(numberField)", + "query": "row mv_sum(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = tanh(to_integer(stringField))", + "query": "row var = mv_sum(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval tanh(stringField)", + "query": "row var = mv_sum(true)", "error": [ - "Argument of [tanh] must be [number], found value [stringField] type [string]" + "Argument of [mv_sum] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval tanh(numberField, extraArg)", - "error": [ - "Error: [tanh] function expects exactly one argument, got 2." - ], + "query": "from a_index | where mv_sum(doubleField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tanh(*)", + "query": "from a_index | where mv_sum(booleanField) > 0", "error": [ - "Using wildcards (*) in tanh is not allowed" + "Argument of [mv_sum] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort tanh(numberField)", + "query": "from a_index | where mv_sum(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = tanh(to_integer(true))", + "query": "from a_index | where mv_sum(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = tanh(true)", - "error": [ - "Argument of [tanh] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | where mv_sum(unsignedLongField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | where tanh(booleanField) > 0", - "error": [ - "Argument of [tanh] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_sum(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tanh(to_integer(booleanField))", + "query": "from a_index | eval mv_sum(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval tanh(booleanField)", + "query": "from a_index | eval var = mv_sum(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval mv_sum(booleanField)", "error": [ - "Argument of [tanh] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_sum] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval tanh(null)", + "query": "from a_index | eval var = mv_sum(*)", + "error": [ + "Using wildcards (*) in mv_sum is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = mv_sum(integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval tanh(nullVar)", + "query": "from a_index | eval mv_sum(integerField)", "error": [], "warning": [] }, { - "query": "row var = tau()", + "query": "from a_index | eval var = mv_sum(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row tau()", + "query": "from a_index | eval var = mv_sum(longField)", "error": [], "warning": [] }, { - "query": "from a_index | where tau() > 0", + "query": "from a_index | eval mv_sum(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = tau()", + "query": "from a_index | eval var = mv_sum(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval tau()", + "query": "from a_index | eval mv_sum(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval tau(extraArg)", + "query": "from a_index | eval mv_sum(doubleField, extraArg)", "error": [ - "Error: [tau] function expects exactly 0 arguments, got 1." + "Error: [mv_sum] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort tau()", + "query": "from a_index | sort mv_sum(doubleField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval tau()", + "query": "from a_index | eval mv_sum(null)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(\"a\")", + "query": "row nullVar = null | eval mv_sum(nullVar)", "error": [], "warning": [] }, { - "query": "row to_boolean(\"a\")", + "query": "row var = mv_zip(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = to_bool(\"a\")", + "query": "row mv_zip(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(stringField)", + "query": "row var = mv_zip(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(stringField)", + "query": "row var = mv_zip(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_bool(stringField)", + "query": "row mv_zip(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(*)", + "query": "row var = mv_zip(to_string(true), to_string(true), to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = mv_zip(true, true, true)", "error": [ - "Using wildcards (*) in to_boolean is not allowed" + "Argument of [mv_zip] must be [keyword], found value [true] type [boolean]", + "Argument of [mv_zip] must be [keyword], found value [true] type [boolean]", + "Argument of [mv_zip] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort to_boolean(stringField)", + "query": "from a_index | eval var = mv_zip(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(true)", + "query": "from a_index | eval mv_zip(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row to_boolean(true)", + "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_bool(true)", - "error": [], + "query": "from a_index | eval mv_zip(booleanField, booleanField)", + "error": [ + "Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_boolean(to_boolean(true))", + "query": "from a_index | eval var = mv_zip(keywordField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(5)", + "query": "from a_index | eval mv_zip(keywordField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row to_boolean(5)", + "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_bool(5)", - "error": [], + "query": "from a_index | eval mv_zip(booleanField, booleanField, booleanField)", + "error": [ + "Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_boolean(to_integer(true))", + "query": "from a_index | eval var = mv_zip(keywordField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(to_string(true))", + "query": "from a_index | eval mv_zip(keywordField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [ - "Argument of [to_boolean] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" - ], + "query": "from a_index | eval var = mv_zip(keywordField, textField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(booleanField)", + "query": "from a_index | eval mv_zip(keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(booleanField)", + "query": "from a_index | eval var = mv_zip(keywordField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_bool(booleanField)", + "query": "from a_index | eval mv_zip(keywordField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(to_boolean(booleanField))", + "query": "from a_index | eval var = mv_zip(keywordField, textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(cartesianPointField)", - "error": [ - "Argument of [to_boolean] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval mv_zip(keywordField, textField, textField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(numberField)", + "query": "from a_index | eval var = mv_zip(textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(numberField)", + "query": "from a_index | eval mv_zip(textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_bool(numberField)", + "query": "from a_index | eval var = mv_zip(textField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(to_integer(booleanField))", + "query": "from a_index | eval mv_zip(textField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(to_string(booleanField))", + "query": "from a_index | eval var = mv_zip(textField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(booleanField, extraArg)", - "error": [ - "Error: [to_boolean] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort to_boolean(booleanField)", + "query": "from a_index | eval mv_zip(textField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(null)", + "query": "from a_index | eval var = mv_zip(textField, textField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_boolean(nullVar)", + "query": "from a_index | eval mv_zip(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(\"a\")", + "query": "from a_index | eval var = mv_zip(textField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "row to_cartesianpoint(\"a\")", + "query": "from a_index | eval mv_zip(textField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(stringField)", + "query": "from a_index | eval var = mv_zip(textField, textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(stringField)", + "query": "from a_index | eval mv_zip(textField, textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(*)", + "query": "from a_index | eval mv_zip(keywordField, keywordField, keywordField, extraArg)", "error": [ - "Using wildcards (*) in to_cartesianpoint is not allowed" + "Error: [mv_zip] function expects no more than 3 arguments, got 4." ], "warning": [] }, { - "query": "from a_index | sort to_cartesianpoint(stringField)", + "query": "from a_index | sort mv_zip(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], - "warning": [] - }, - { - "query": "row to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_zip(null, null, null)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row nullVar = null | eval mv_zip(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(to_string(true))", + "query": "row var = now()", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(true)", - "error": [ - "Argument of [to_cartesianpoint] must be [cartesian_point], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_cartesianpoint(cartesianPointField)", + "query": "row now()", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(cartesianPointField)", + "query": "from a_index | eval var = now()", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval now()", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(booleanField)", + "query": "from a_index | eval now(extraArg)", "error": [ - "Argument of [to_cartesianpoint] must be [cartesian_point], found value [booleanField] type [boolean]" + "Error: [now] function expects exactly 0 arguments, got 1." ], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(to_string(booleanField))", + "query": "from a_index | sort now()", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(cartesianPointField, extraArg)", - "error": [ - "Error: [to_cartesianpoint] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort to_cartesianpoint(cartesianPointField)", + "query": "row nullVar = null | eval now()", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(null)", + "query": "row var = pi()", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_cartesianpoint(nullVar)", + "query": "row pi()", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(\"a\")", + "query": "from a_index | where pi() > 0", "error": [], "warning": [] }, { - "query": "row to_cartesianshape(\"a\")", + "query": "from a_index | eval var = pi()", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(stringField)", + "query": "from a_index | eval pi()", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(stringField)", - "error": [], + "query": "from a_index | eval pi(extraArg)", + "error": [ + "Error: [pi] function expects exactly 0 arguments, got 1." + ], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(*)", - "error": [ - "Using wildcards (*) in to_cartesianshape is not allowed" - ], + "query": "from a_index | sort pi()", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_cartesianshape(stringField)", + "query": "row nullVar = null | eval pi()", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = pow(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row pow(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = pow(to_double(true), to_double(true))", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = pow(5.5, 5)", "error": [], "warning": [] }, { - "query": "row to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row pow(5.5, 5)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = pow(to_double(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_string(true))", + "query": "row var = pow(to_double(true), 5)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(true)", - "error": [ - "Argument of [to_cartesianshape] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "row var = pow(5, 5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(cartesianPointField)", + "query": "row pow(5, 5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(cartesianPointField)", + "query": "row var = pow(to_integer(true), to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(to_cartesianpoint(cartesianPointField))", + "query": "row var = pow(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(booleanField)", - "error": [ - "Argument of [to_cartesianshape] must be [cartesian_point], found value [booleanField] type [boolean]" - ], + "query": "row pow(5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(cartesianShapeField)", + "query": "row var = pow(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(cartesianShapeField)", + "query": "row var = pow(to_integer(true), 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(to_cartesianshape(cartesianPointField))", + "query": "row var = pow(5, to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(to_string(booleanField))", + "query": "row var = pow(5, to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(cartesianPointField, extraArg)", + "query": "row var = pow(true, true)", "error": [ - "Error: [to_cartesianshape] function expects exactly one argument, got 2." + "Argument of [pow] must be [double], found value [true] type [boolean]", + "Argument of [pow] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort to_cartesianshape(cartesianPointField)", + "query": "from a_index | where pow(doubleField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(null)", - "error": [], + "query": "from a_index | where pow(booleanField, booleanField) > 0", + "error": [ + "Argument of [pow] must be [double], found value [booleanField] type [boolean]", + "Argument of [pow] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row nullVar = null | eval to_cartesianshape(nullVar)", + "query": "from a_index | where pow(doubleField, integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_datetime(\"a\")", + "query": "from a_index | where pow(doubleField, longField) > 0", "error": [], "warning": [] }, { - "query": "row to_datetime(\"a\")", + "query": "from a_index | where pow(doubleField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_dt(\"a\")", + "query": "from a_index | where pow(integerField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(stringField)", + "query": "from a_index | where pow(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(stringField)", + "query": "from a_index | where pow(integerField, longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dt(stringField)", + "query": "from a_index | where pow(integerField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(*)", - "error": [ - "Using wildcards (*) in to_datetime is not allowed" - ], + "query": "from a_index | where pow(longField, doubleField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_datetime(stringField)", + "query": "from a_index | where pow(longField, integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_datetime(now())", + "query": "from a_index | where pow(longField, longField) > 0", "error": [], "warning": [] }, { - "query": "row to_datetime(now())", + "query": "from a_index | where pow(longField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_dt(now())", + "query": "from a_index | where pow(unsignedLongField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_datetime(to_datetime(now()))", + "query": "from a_index | where pow(unsignedLongField, integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_datetime(5)", + "query": "from a_index | where pow(unsignedLongField, longField) > 0", "error": [], "warning": [] }, { - "query": "row to_datetime(5)", + "query": "from a_index | where pow(unsignedLongField, unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_dt(5)", + "query": "from a_index | eval var = pow(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_datetime(to_integer(true))", + "query": "from a_index | eval pow(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_datetime(to_string(true))", + "query": "from a_index | eval var = pow(to_double(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_datetime(true)", + "query": "from a_index | eval pow(booleanField, booleanField)", "error": [ - "Argument of [to_datetime] must be [date], found value [true] type [boolean]" + "Argument of [pow] must be [double], found value [booleanField] type [boolean]", + "Argument of [pow] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(dateField)", + "query": "from a_index | eval var = pow(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(dateField)", + "query": "from a_index | eval pow(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dt(dateField)", + "query": "from a_index | eval var = pow(to_double(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(to_datetime(dateField))", + "query": "from a_index | eval var = pow(doubleField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(booleanField)", - "error": [ - "Argument of [to_datetime] must be [date], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval pow(doubleField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(numberField)", + "query": "from a_index | eval var = pow(to_double(booleanField), longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(numberField)", + "query": "from a_index | eval var = pow(doubleField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dt(numberField)", + "query": "from a_index | eval pow(doubleField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(to_integer(booleanField))", + "query": "from a_index | eval var = pow(to_double(booleanField), unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(to_string(booleanField))", + "query": "from a_index | eval var = pow(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(dateField, extraArg)", - "error": [ - "Error: [to_datetime] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval pow(integerField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_datetime(dateField)", + "query": "from a_index | eval var = pow(to_integer(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(null)", + "query": "from a_index | eval var = pow(integerField, integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_datetime(nullVar)", + "query": "from a_index | eval pow(integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_degrees(5)", + "query": "from a_index | eval var = pow(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row to_degrees(5)", + "query": "from a_index | eval var = pow(integerField, longField)", "error": [], "warning": [] }, { - "query": "row var = to_degrees(to_integer(\"a\"))", + "query": "from a_index | eval pow(integerField, longField)", "error": [], "warning": [] }, { - "query": "row var = to_degrees(\"a\")", - "error": [ - "Argument of [to_degrees] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval var = pow(to_integer(booleanField), longField)", + "error": [], "warning": [] }, { - "query": "from a_index | where to_degrees(numberField) > 0", + "query": "from a_index | eval var = pow(integerField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_degrees(stringField) > 0", - "error": [ - "Argument of [to_degrees] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval pow(integerField, unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(numberField)", + "query": "from a_index | eval var = pow(to_integer(booleanField), unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(numberField)", + "query": "from a_index | eval var = pow(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(to_integer(stringField))", + "query": "from a_index | eval pow(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(stringField)", - "error": [ - "Argument of [to_degrees] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = pow(longField, to_double(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(numberField, extraArg)", - "error": [ - "Error: [to_degrees] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = pow(longField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(*)", - "error": [ - "Using wildcards (*) in to_degrees is not allowed" - ], + "query": "from a_index | eval pow(longField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_degrees(numberField)", + "query": "from a_index | eval var = pow(longField, to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_degrees(to_integer(true))", + "query": "from a_index | eval var = pow(longField, longField)", "error": [], "warning": [] }, { - "query": "row var = to_degrees(true)", - "error": [ - "Argument of [to_degrees] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval pow(longField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | where to_degrees(booleanField) > 0", - "error": [ - "Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = pow(longField, unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(to_integer(booleanField))", + "query": "from a_index | eval pow(longField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(booleanField)", - "error": [ - "Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = pow(unsignedLongField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(null)", + "query": "from a_index | eval pow(unsignedLongField, doubleField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_degrees(nullVar)", + "query": "from a_index | eval var = pow(unsignedLongField, to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_double(\"a\")", + "query": "from a_index | eval var = pow(unsignedLongField, integerField)", "error": [], "warning": [] }, { - "query": "row to_double(\"a\")", + "query": "from a_index | eval pow(unsignedLongField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_dbl(\"a\")", + "query": "from a_index | eval var = pow(unsignedLongField, to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(stringField)", + "query": "from a_index | eval var = pow(unsignedLongField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(stringField)", + "query": "from a_index | eval pow(unsignedLongField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(stringField)", + "query": "from a_index | eval var = pow(unsignedLongField, unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(*)", - "error": [ - "Using wildcards (*) in to_double is not allowed" - ], + "query": "from a_index | eval pow(unsignedLongField, unsignedLongField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_double(stringField)", - "error": [], + "query": "from a_index | eval pow(doubleField, doubleField, extraArg)", + "error": [ + "Error: [pow] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = to_double(true)", + "query": "from a_index | sort pow(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "row to_double(true)", + "query": "from a_index | eval pow(null, null)", "error": [], "warning": [] }, { - "query": "row var = to_dbl(true)", + "query": "row nullVar = null | eval pow(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_double(to_boolean(true))", + "query": "row var = repeat(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row var = to_double(5)", + "query": "row repeat(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row to_double(5)", + "query": "row var = repeat(to_string(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_dbl(5)", - "error": [], + "query": "row var = repeat(true, true)", + "error": [ + "Argument of [repeat] must be [keyword], found value [true] type [boolean]", + "Argument of [repeat] must be [integer], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_double(to_integer(true))", + "query": "from a_index | eval var = repeat(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_double(now())", + "query": "from a_index | eval repeat(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "row to_double(now())", + "query": "from a_index | eval var = repeat(to_string(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_dbl(now())", - "error": [], + "query": "from a_index | eval repeat(booleanField, booleanField)", + "error": [ + "Argument of [repeat] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [repeat] must be [integer], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_double(to_datetime(now()))", + "query": "from a_index | eval var = repeat(textField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_double(to_string(true))", + "query": "from a_index | eval repeat(textField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_double(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval repeat(keywordField, integerField, extraArg)", "error": [ - "Argument of [to_double] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Error: [repeat] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where to_double(booleanField) > 0", + "query": "from a_index | sort repeat(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_double(cartesianPointField) > 0", - "error": [ - "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval repeat(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | where to_double(numberField) > 0", + "query": "row nullVar = null | eval repeat(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | where to_double(dateField) > 0", + "query": "row var = replace(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where to_double(stringField) > 0", + "query": "row replace(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(booleanField)", + "query": "row var = replace(to_string(true), to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(booleanField)", + "query": "row var = replace(true, true, true)", + "error": [ + "Argument of [replace] must be [keyword], found value [true] type [boolean]", + "Argument of [replace] must be [keyword], found value [true] type [boolean]", + "Argument of [replace] must be [keyword], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = replace(keywordField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(booleanField)", + "query": "from a_index | eval replace(keywordField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_boolean(booleanField))", + "query": "from a_index | eval var = replace(to_string(booleanField), to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(cartesianPointField)", + "query": "from a_index | eval replace(booleanField, booleanField, booleanField)", "error": [ - "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [replace] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [replace] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [replace] must be [keyword], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_double(numberField)", + "query": "from a_index | eval var = replace(keywordField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(numberField)", + "query": "from a_index | eval replace(keywordField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(numberField)", + "query": "from a_index | eval var = replace(keywordField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_integer(booleanField))", + "query": "from a_index | eval replace(keywordField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(dateField)", + "query": "from a_index | eval var = replace(keywordField, textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(dateField)", + "query": "from a_index | eval replace(keywordField, textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(dateField)", + "query": "from a_index | eval var = replace(textField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_datetime(dateField))", + "query": "from a_index | eval replace(textField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_string(booleanField))", + "query": "from a_index | eval var = replace(textField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(booleanField, extraArg)", - "error": [ - "Error: [to_double] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort to_double(booleanField)", + "query": "from a_index | eval replace(textField, keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(null)", + "query": "from a_index | eval var = replace(textField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_double(nullVar)", + "query": "from a_index | eval replace(textField, textField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(\"a\")", + "query": "from a_index | eval var = replace(textField, textField, textField)", "error": [], "warning": [] }, { - "query": "row to_geopoint(\"a\")", + "query": "from a_index | eval replace(textField, textField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(stringField)", - "error": [], + "query": "from a_index | eval replace(keywordField, keywordField, keywordField, extraArg)", + "error": [ + "Error: [replace] function expects exactly 3 arguments, got 4." + ], "warning": [] }, { - "query": "from a_index | eval to_geopoint(stringField)", + "query": "from a_index | sort replace(keywordField, keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(*)", - "error": [ - "Using wildcards (*) in to_geopoint is not allowed" - ], - "warning": [] - }, - { - "query": "from a_index | sort to_geopoint(stringField)", + "query": "from a_index | eval replace(null, null, null)", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(to_geopoint(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval replace(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row to_geopoint(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = right(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row right(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(to_string(true))", + "query": "row var = right(to_string(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(true)", + "query": "row var = right(true, true)", "error": [ - "Argument of [to_geopoint] must be [geo_point], found value [true] type [boolean]" + "Argument of [right] must be [keyword], found value [true] type [boolean]", + "Argument of [right] must be [integer], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(geoPointField)", + "query": "from a_index | eval var = right(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(geoPointField)", + "query": "from a_index | eval right(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(to_geopoint(geoPointField))", + "query": "from a_index | eval var = right(to_string(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(booleanField)", + "query": "from a_index | eval right(booleanField, booleanField)", "error": [ - "Argument of [to_geopoint] must be [geo_point], found value [booleanField] type [boolean]" + "Argument of [right] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [right] must be [integer], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(to_string(booleanField))", + "query": "from a_index | eval var = right(textField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(geoPointField, extraArg)", + "query": "from a_index | eval right(textField, integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval right(keywordField, integerField, extraArg)", "error": [ - "Error: [to_geopoint] function expects exactly one argument, got 2." + "Error: [right] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | sort to_geopoint(geoPointField)", + "query": "from a_index | sort right(keywordField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(null)", + "query": "from a_index | eval right(null, null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_geopoint(nullVar)", + "query": "row nullVar = null | eval right(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(\"a\")", + "query": "row var = round(5.5)", "error": [], "warning": [] }, { - "query": "row to_geoshape(\"a\")", + "query": "row round(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(stringField)", + "query": "row var = round(to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(stringField)", + "query": "row var = round(5.5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(*)", - "error": [ - "Using wildcards (*) in to_geoshape is not allowed" - ], + "query": "row round(5.5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_geoshape(stringField)", + "query": "row var = round(to_double(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = round(5)", "error": [], "warning": [] }, { - "query": "row to_geoshape(to_geopoint(\"POINT (30 10)\"))", + "query": "row round(5)", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = round(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = round(5, 5)", "error": [], "warning": [] }, { - "query": "row to_geoshape(to_geoshape(\"POINT (30 10)\"))", + "query": "row round(5, 5)", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = round(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_string(true))", + "query": "row var = round(5, to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(true)", + "query": "row var = round(true, true)", "error": [ - "Argument of [to_geoshape] must be [geo_point], found value [true] type [boolean]" + "Argument of [round] must be [double], found value [true] type [boolean]", + "Argument of [round] must be [integer], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(geoPointField)", + "query": "from a_index | where round(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(geoPointField)", - "error": [], + "query": "from a_index | where round(booleanField) > 0", + "error": [ + "Argument of [round] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(to_geopoint(geoPointField))", + "query": "from a_index | where round(doubleField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(booleanField)", + "query": "from a_index | where round(booleanField, booleanField) > 0", "error": [ - "Argument of [to_geoshape] must be [geo_point], found value [booleanField] type [boolean]" + "Argument of [round] must be [double], found value [booleanField] type [boolean]", + "Argument of [round] must be [integer], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(geoShapeField)", + "query": "from a_index | where round(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(geoShapeField)", + "query": "from a_index | where round(integerField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(to_geoshape(geoPointField))", + "query": "from a_index | where round(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(to_string(booleanField))", + "query": "from a_index | where round(longField, integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(geoPointField, extraArg)", - "error": [ - "Error: [to_geoshape] function expects exactly one argument, got 2." - ], + "query": "from a_index | where round(unsignedLongField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_geoshape(geoPointField)", + "query": "from a_index | eval var = round(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(null)", + "query": "from a_index | eval round(doubleField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_geoshape(nullVar)", + "query": "from a_index | eval var = round(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_integer(\"a\")", - "error": [], + "query": "from a_index | eval round(booleanField)", + "error": [ + "Argument of [round] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row to_integer(\"a\")", + "query": "from a_index | eval var = round(*)", + "error": [ + "Using wildcards (*) in round is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = round(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_int(\"a\")", + "query": "from a_index | eval round(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(stringField)", + "query": "from a_index | eval var = round(to_double(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(stringField)", + "query": "from a_index | eval round(booleanField, booleanField)", + "error": [ + "Argument of [round] must be [double], found value [booleanField] type [boolean]", + "Argument of [round] must be [integer], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = round(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_int(stringField)", + "query": "from a_index | eval round(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(*)", - "error": [ - "Using wildcards (*) in to_integer is not allowed" - ], + "query": "from a_index | eval var = round(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_integer(stringField)", + "query": "from a_index | eval var = round(integerField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_integer(true)", + "query": "from a_index | eval round(integerField, integerField)", "error": [], "warning": [] }, { - "query": "row to_integer(true)", + "query": "from a_index | eval var = round(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_int(true)", + "query": "from a_index | eval var = round(longField)", "error": [], "warning": [] }, { - "query": "row var = to_integer(to_boolean(true))", + "query": "from a_index | eval round(longField)", "error": [], "warning": [] }, { - "query": "row var = to_integer(5)", + "query": "from a_index | eval var = round(longField, integerField)", "error": [], "warning": [] }, { - "query": "row to_integer(5)", + "query": "from a_index | eval round(longField, integerField)", "error": [], "warning": [] }, { - "query": "row var = to_int(5)", + "query": "from a_index | eval var = round(longField, to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_integer(to_integer(true))", + "query": "from a_index | eval var = round(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = to_integer(now())", + "query": "from a_index | eval round(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row to_integer(now())", + "query": "from a_index | eval round(doubleField, integerField, extraArg)", + "error": [ + "Error: [round] function expects no more than 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort round(doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_int(now())", + "query": "from a_index | eval round(null, null)", "error": [], "warning": [] }, { - "query": "row var = to_integer(to_datetime(now()))", + "query": "row nullVar = null | eval round(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_integer(to_string(true))", + "query": "row var = rtrim(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_integer(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [ - "Argument of [to_integer] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" - ], + "query": "row rtrim(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | where to_integer(booleanField) > 0", + "query": "row var = rtrim(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | where to_integer(cartesianPointField) > 0", + "query": "row var = rtrim(true)", "error": [ - "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [rtrim] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where to_integer(numberField) > 0", + "query": "from a_index | eval var = rtrim(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_integer(dateField) > 0", + "query": "from a_index | eval rtrim(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_integer(stringField) > 0", + "query": "from a_index | eval var = rtrim(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(booleanField)", - "error": [], + "query": "from a_index | eval rtrim(booleanField)", + "error": [ + "Argument of [rtrim] must be [keyword], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval to_integer(booleanField)", - "error": [], + "query": "from a_index | eval var = rtrim(*)", + "error": [ + "Using wildcards (*) in rtrim is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = to_int(booleanField)", + "query": "from a_index | eval var = rtrim(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_boolean(booleanField))", + "query": "from a_index | eval rtrim(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(cartesianPointField)", + "query": "from a_index | eval rtrim(keywordField, extraArg)", "error": [ - "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Error: [rtrim] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = to_integer(numberField)", + "query": "from a_index | sort rtrim(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(numberField)", + "query": "from a_index | eval rtrim(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_int(numberField)", + "query": "row nullVar = null | eval rtrim(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_integer(booleanField))", + "query": "row var = signum(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(dateField)", + "query": "row signum(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(dateField)", + "query": "row var = signum(to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_int(dateField)", + "query": "row var = signum(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_datetime(dateField))", + "query": "row signum(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_string(booleanField))", + "query": "row var = signum(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(booleanField, extraArg)", + "query": "row var = signum(true)", "error": [ - "Error: [to_integer] function expects exactly one argument, got 2." + "Argument of [signum] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort to_integer(booleanField)", + "query": "from a_index | where signum(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(null)", + "query": "from a_index | where signum(booleanField) > 0", + "error": [ + "Argument of [signum] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where signum(integerField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_integer(nullVar)", + "query": "from a_index | where signum(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_ip(\"a\")", + "query": "from a_index | where signum(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row to_ip(\"a\")", + "query": "from a_index | eval var = signum(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ip(stringField)", + "query": "from a_index | eval signum(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(stringField)", + "query": "from a_index | eval var = signum(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ip(*)", + "query": "from a_index | eval signum(booleanField)", "error": [ - "Using wildcards (*) in to_ip is not allowed" + "Argument of [signum] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort to_ip(stringField)", - "error": [], + "query": "from a_index | eval var = signum(*)", + "error": [ + "Using wildcards (*) in signum is not allowed" + ], "warning": [] }, { - "query": "row var = to_ip(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = signum(integerField)", "error": [], "warning": [] }, { - "query": "row to_ip(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval signum(integerField)", "error": [], "warning": [] }, { - "query": "row var = to_ip(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = signum(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_ip(to_string(true))", + "query": "from a_index | eval var = signum(longField)", "error": [], "warning": [] }, { - "query": "row var = to_ip(true)", - "error": [ - "Argument of [to_ip] must be [ip], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_ip(ipField)", + "query": "from a_index | eval signum(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(ipField)", + "query": "from a_index | eval var = signum(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ip(to_ip(ipField))", + "query": "from a_index | eval signum(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(booleanField)", + "query": "from a_index | eval signum(doubleField, extraArg)", "error": [ - "Argument of [to_ip] must be [ip], found value [booleanField] type [boolean]" + "Error: [signum] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = to_ip(to_string(booleanField))", + "query": "from a_index | sort signum(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(ipField, extraArg)", - "error": [ - "Error: [to_ip] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval signum(null)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_ip(ipField)", + "query": "row nullVar = null | eval signum(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(null)", + "query": "row var = sin(5.5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_ip(nullVar)", + "query": "row sin(5.5)", "error": [], "warning": [] }, { - "query": "row var = to_long(\"a\")", + "query": "row var = sin(to_double(true))", "error": [], "warning": [] }, { - "query": "row to_long(\"a\")", + "query": "row var = sin(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(stringField)", + "query": "row sin(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(stringField)", + "query": "row var = sin(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(*)", + "query": "row var = sin(true)", "error": [ - "Using wildcards (*) in to_long is not allowed" + "Argument of [sin] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort to_long(stringField)", + "query": "from a_index | where sin(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(true)", - "error": [], + "query": "from a_index | where sin(booleanField) > 0", + "error": [ + "Argument of [sin] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row to_long(true)", + "query": "from a_index | where sin(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(to_boolean(true))", + "query": "from a_index | where sin(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(5)", + "query": "from a_index | where sin(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row to_long(5)", + "query": "from a_index | eval var = sin(doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_long(to_integer(true))", + "query": "from a_index | eval sin(doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_long(now())", + "query": "from a_index | eval var = sin(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row to_long(now())", - "error": [], - "warning": [] - }, - { - "query": "row var = to_long(to_datetime(now()))", - "error": [], - "warning": [] - }, - { - "query": "row var = to_long(to_string(true))", - "error": [], - "warning": [] - }, - { - "query": "row var = to_long(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval sin(booleanField)", "error": [ - "Argument of [to_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Argument of [sin] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where to_long(booleanField) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where to_long(cartesianPointField) > 0", + "query": "from a_index | eval var = sin(*)", "error": [ - "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Using wildcards (*) in sin is not allowed" ], "warning": [] }, { - "query": "from a_index | where to_long(numberField) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where to_long(dateField) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where to_long(stringField) > 0", + "query": "from a_index | eval var = sin(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(booleanField)", + "query": "from a_index | eval sin(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(booleanField)", + "query": "from a_index | eval var = sin(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_boolean(booleanField))", + "query": "from a_index | eval var = sin(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(cartesianPointField)", - "error": [ - "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_long(numberField)", + "query": "from a_index | eval sin(longField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(numberField)", + "query": "from a_index | eval var = sin(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_integer(booleanField))", + "query": "from a_index | eval sin(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(dateField)", - "error": [], + "query": "from a_index | eval sin(doubleField, extraArg)", + "error": [ + "Error: [sin] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval to_long(dateField)", + "query": "from a_index | sort sin(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_datetime(dateField))", + "query": "from a_index | eval sin(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_string(booleanField))", + "query": "row nullVar = null | eval sin(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(booleanField, extraArg)", - "error": [ - "Error: [to_long] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort to_long(booleanField)", + "query": "row var = sinh(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(null)", + "query": "row sinh(5.5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_long(nullVar)", + "query": "row var = sinh(to_double(true))", "error": [], "warning": [] }, { - "query": "row var = to_lower(\"a\")", + "query": "row var = sinh(5)", "error": [], "warning": [] }, { - "query": "row to_lower(\"a\")", + "query": "row sinh(5)", "error": [], "warning": [] }, { - "query": "row var = to_lower(to_string(\"a\"))", + "query": "row var = sinh(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_lower(5)", + "query": "row var = sinh(true)", "error": [ - "Argument of [to_lower] must be [string], found value [5] type [number]" + "Argument of [sinh] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(to_lower(stringField)) > 0", + "query": "from a_index | where sinh(doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_lower(numberField)) > 0", + "query": "from a_index | where sinh(booleanField) > 0", "error": [ - "Argument of [to_lower] must be [string], found value [numberField] type [number]" + "Argument of [sinh] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_lower(stringField)", + "query": "from a_index | where sinh(integerField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(stringField)", + "query": "from a_index | where sinh(longField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_lower(to_string(stringField))", + "query": "from a_index | where sinh(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(numberField)", - "error": [ - "Argument of [to_lower] must be [string], found value [numberField] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | eval to_lower(stringField, extraArg)", - "error": [ - "Error: [to_lower] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_lower(*)", - "error": [ - "Using wildcards (*) in to_lower is not allowed" - ], + "query": "from a_index | eval var = sinh(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_lower(stringField)", + "query": "from a_index | eval sinh(doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_lower(to_string(true))", + "query": "from a_index | eval var = sinh(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_lower(true)", + "query": "from a_index | eval sinh(booleanField)", "error": [ - "Argument of [to_lower] must be [string], found value [true] type [boolean]" + "Argument of [sinh] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(to_lower(booleanField)) > 0", + "query": "from a_index | eval var = sinh(*)", "error": [ - "Argument of [to_lower] must be [string], found value [booleanField] type [boolean]" + "Using wildcards (*) in sinh is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = to_lower(to_string(booleanField))", + "query": "from a_index | eval var = sinh(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(booleanField)", - "error": [ - "Argument of [to_lower] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval sinh(integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(null)", + "query": "from a_index | eval var = sinh(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_lower(nullVar)", + "query": "from a_index | eval var = sinh(longField)", "error": [], "warning": [] }, { - "query": "row var = to_radians(5)", + "query": "from a_index | eval sinh(longField)", "error": [], "warning": [] }, { - "query": "row to_radians(5)", + "query": "from a_index | eval var = sinh(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = to_radians(to_integer(\"a\"))", + "query": "from a_index | eval sinh(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = to_radians(\"a\")", + "query": "from a_index | eval sinh(doubleField, extraArg)", "error": [ - "Argument of [to_radians] must be [number], found value [\"a\"] type [string]" + "Error: [sinh] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | where to_radians(numberField) > 0", + "query": "from a_index | sort sinh(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_radians(stringField) > 0", - "error": [ - "Argument of [to_radians] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval sinh(null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_radians(numberField)", + "query": "row nullVar = null | eval sinh(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(numberField)", + "query": "row var = split(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_radians(to_integer(stringField))", + "query": "row split(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(stringField)", - "error": [ - "Argument of [to_radians] must be [number], found value [stringField] type [string]" - ], + "query": "row var = split(to_string(true), to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(numberField, extraArg)", + "query": "row var = split(true, true)", "error": [ - "Error: [to_radians] function expects exactly one argument, got 2." + "Argument of [split] must be [keyword], found value [true] type [boolean]", + "Argument of [split] must be [keyword], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_radians(*)", - "error": [ - "Using wildcards (*) in to_radians is not allowed" - ], + "query": "from a_index | eval var = split(keywordField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_radians(numberField)", + "query": "from a_index | eval split(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "row var = to_radians(to_integer(true))", + "query": "from a_index | eval var = split(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_radians(true)", + "query": "from a_index | eval split(booleanField, booleanField)", "error": [ - "Argument of [to_radians] must be [number], found value [true] type [boolean]" + "Argument of [split] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [split] must be [keyword], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where to_radians(booleanField) > 0", - "error": [ - "Argument of [to_radians] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = split(keywordField, textField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_radians(to_integer(booleanField))", + "query": "from a_index | eval split(keywordField, textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(booleanField)", - "error": [ - "Argument of [to_radians] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = split(textField, keywordField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(null)", + "query": "from a_index | eval split(textField, keywordField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_radians(nullVar)", + "query": "from a_index | eval var = split(textField, textField)", "error": [], "warning": [] }, { - "query": "row var = to_string(\"a\")", + "query": "from a_index | eval split(textField, textField)", "error": [], "warning": [] }, { - "query": "row to_string(\"a\")", - "error": [], + "query": "from a_index | eval split(keywordField, keywordField, extraArg)", + "error": [ + "Error: [split] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = to_str(\"a\")", + "query": "from a_index | sort split(keywordField, keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(stringField)", + "query": "from a_index | eval split(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(stringField)", + "query": "row nullVar = null | eval split(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(stringField)", + "query": "row var = sqrt(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(*)", - "error": [ - "Using wildcards (*) in to_string is not allowed" - ], + "query": "row sqrt(5.5)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_string(stringField)", + "query": "row var = sqrt(to_double(true))", "error": [], "warning": [] }, { - "query": "row var = to_string(true)", + "query": "row var = sqrt(5)", "error": [], "warning": [] }, { - "query": "row to_string(true)", + "query": "row sqrt(5)", "error": [], "warning": [] }, { - "query": "row var = to_str(true)", + "query": "row var = sqrt(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_string(to_boolean(true))", - "error": [], + "query": "row var = sqrt(true)", + "error": [ + "Argument of [sqrt] must be [double], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_string(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | where sqrt(doubleField) > 0", "error": [], "warning": [] }, { - "query": "row to_string(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where sqrt(booleanField) > 0", + "error": [ + "Argument of [sqrt] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_str(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | where sqrt(integerField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_string(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | where sqrt(longField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_string(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | where sqrt(unsignedLongField) > 0", "error": [], "warning": [] }, { - "query": "row to_string(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = sqrt(doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_str(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval sqrt(doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = sqrt(to_double(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_string(now())", - "error": [], + "query": "from a_index | eval sqrt(booleanField)", + "error": [ + "Argument of [sqrt] must be [double], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row to_string(now())", - "error": [], + "query": "from a_index | eval var = sqrt(*)", + "error": [ + "Using wildcards (*) in sqrt is not allowed" + ], "warning": [] }, { - "query": "row var = to_str(now())", + "query": "from a_index | eval var = sqrt(integerField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_datetime(now()))", + "query": "from a_index | eval sqrt(integerField)", "error": [], "warning": [] }, { - "query": "row var = to_string(5)", + "query": "from a_index | eval var = sqrt(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row to_string(5)", + "query": "from a_index | eval var = sqrt(longField)", "error": [], "warning": [] }, { - "query": "row var = to_str(5)", + "query": "from a_index | eval sqrt(longField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_integer(true))", + "query": "from a_index | eval var = sqrt(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval sqrt(unsignedLongField)", "error": [], "warning": [] }, { - "query": "row to_string(to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval sqrt(doubleField, extraArg)", + "error": [ + "Error: [sqrt] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = to_str(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | sort sqrt(doubleField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval sqrt(null)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_geoshape(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval sqrt(nullVar)", "error": [], "warning": [] }, { - "query": "row to_string(to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "row var = st_contains(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_str(to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "row st_contains(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_string(to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_string(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "row var = st_contains(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row to_string(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "row st_contains(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_str(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "row var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_string(to_ip(to_ip(\"127.0.0.1\")))", - "error": [], + "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_string(to_string(true))", - "error": [], + "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_string(to_version(\"1.0.0\"))", - "error": [], + "query": "row var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row to_string(to_version(\"1.0.0\"))", + "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_str(to_version(\"1.0.0\"))", + "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_string(to_version(\"a\"))", - "error": [], + "query": "row var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(booleanField)) > 0", - "error": [], + "query": "row var = st_contains(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(cartesianPointField)) > 0", - "error": [], + "query": "row st_contains(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(cartesianShapeField)) > 0", - "error": [], + "query": "row var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(dateField)) > 0", - "error": [], + "query": "row var = st_contains(geoPointField, to_geoshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(numberField)) > 0", - "error": [], + "query": "row st_contains(geoPointField, to_geoshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(geoPointField)) > 0", - "error": [], + "query": "row var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(geoShapeField)) > 0", - "error": [], + "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(ipField)) > 0", - "error": [], + "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(stringField)) > 0", - "error": [], + "query": "row var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(versionField)) > 0", + "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(booleanField)", + "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(booleanField)", - "error": [], + "query": "row var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_str(booleanField)", - "error": [], + "query": "row var = st_contains(true, true)", + "error": [ + "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_boolean(booleanField))", + "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(cartesianPointField)", + "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(cartesianPointField)", + "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(cartesianPointField)", - "error": [], + "query": "from a_index | eval st_contains(booleanField, booleanField)", + "error": [ + "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(cartesianShapeField)", + "query": "from a_index | eval st_contains(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(cartesianShapeField)", + "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(cartesianShapeField)", + "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval st_contains(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(dateField)", + "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(dateField)", + "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(dateField)", + "query": "from a_index | eval st_contains(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_datetime(dateField))", + "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(numberField)", + "query": "from a_index | eval var = st_contains(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(numberField)", + "query": "from a_index | eval st_contains(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(numberField)", + "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_integer(booleanField))", + "query": "from a_index | eval var = st_contains(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(geoPointField)", + "query": "from a_index | eval st_contains(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(geoPointField)", + "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(geoPointField)", + "query": "from a_index | eval var = st_contains(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_geopoint(geoPointField))", + "query": "from a_index | eval st_contains(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(geoShapeField)", + "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(geoShapeField)", + "query": "from a_index | eval var = st_contains(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(geoShapeField)", + "query": "from a_index | eval st_contains(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_geoshape(geoPointField))", + "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(ipField)", - "error": [], + "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_contains] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval to_string(ipField)", + "query": "from a_index | sort st_contains(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(ipField)", + "query": "from a_index | eval st_contains(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_ip(ipField))", + "query": "row nullVar = null | eval st_contains(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_string(booleanField))", + "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(versionField)", + "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(versionField)", + "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(versionField)", + "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_version(stringField))", + "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(booleanField, extraArg)", - "error": [ - "Error: [to_string] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort to_string(booleanField)", + "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(null)", + "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_string(nullVar)", + "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_unsigned_long(\"a\")", + "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row to_unsigned_long(\"a\")", + "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = to_ul(\"a\")", + "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_ulong(\"a\")", + "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(stringField)", + "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(stringField)", + "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ul(stringField)", + "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(stringField)", + "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(*)", - "error": [ - "Using wildcards (*) in to_unsigned_long is not allowed" - ], - "warning": [] - }, - { - "query": "from a_index | sort to_unsigned_long(stringField)", + "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_unsigned_long(true)", + "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row to_unsigned_long(true)", + "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = to_ul(true)", + "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = to_ulong(true)", - "error": [], + "query": "row var = st_disjoint(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_boolean(true))", - "error": [], + "query": "row st_disjoint(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(now())", - "error": [], + "query": "row var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row to_unsigned_long(now())", - "error": [], + "query": "row var = st_disjoint(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_ul(now())", - "error": [], + "query": "row st_disjoint(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_ulong(now())", - "error": [], + "query": "row var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_datetime(now()))", - "error": [], + "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(5)", - "error": [], + "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row to_unsigned_long(5)", - "error": [], + "query": "row var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_ul(5)", + "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_ulong(5)", + "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_unsigned_long(to_integer(true))", - "error": [], + "query": "row var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_string(true))", - "error": [], + "query": "row var = st_disjoint(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row st_disjoint(geoPointField, geoPointField)", "error": [ - "Argument of [to_unsigned_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(booleanField) > 0", - "error": [], + "query": "row var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(cartesianPointField) > 0", + "query": "row var = st_disjoint(geoPointField, to_geoshape(\"POINT (30 10)\"))", "error": [ - "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(dateField) > 0", - "error": [], + "query": "row st_disjoint(geoPointField, to_geoshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(numberField) > 0", - "error": [], + "query": "row var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(stringField) > 0", - "error": [], + "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(booleanField)", - "error": [], + "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(booleanField)", - "error": [], + "query": "row var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_ul(booleanField)", + "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(booleanField)", + "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_boolean(booleanField))", - "error": [], + "query": "row var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(cartesianPointField)", + "query": "row var = st_disjoint(true, true)", "error": [ - "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(dateField)", + "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(dateField)", + "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ul(dateField)", + "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(dateField)", - "error": [], + "query": "from a_index | eval st_disjoint(booleanField, booleanField)", + "error": [ + "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_datetime(dateField))", + "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(numberField)", + "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(numberField)", + "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ul(numberField)", + "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(numberField)", + "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_integer(booleanField))", + "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_string(booleanField))", + "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(booleanField, extraArg)", - "error": [ - "Error: [to_unsigned_long] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_unsigned_long(booleanField)", + "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(null)", + "query": "from a_index | eval var = st_disjoint(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_unsigned_long(nullVar)", + "query": "from a_index | eval st_disjoint(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = to_upper(\"a\")", + "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_disjoint(geoPointField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_disjoint(geoPointField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_disjoint(geoShapeField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_disjoint(geoShapeField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_disjoint(geoShapeField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_disjoint(geoShapeField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_disjoint(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_disjoint(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval st_disjoint(nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_distance(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_distance(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_distance(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_distance(true, true)", + "error": [ + "Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(booleanField, booleanField)", + "error": [ + "Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(cartesianPointField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_distance] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_distance(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_distance(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval st_distance(nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_distance(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_distance(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_intersects(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_intersects(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_intersects(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(geoPointField, to_geoshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_intersects(geoPointField, to_geoshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_intersects(true, true)", + "error": [ + "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(booleanField, booleanField)", + "error": [ + "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(cartesianPointField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(geoPointField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(geoPointField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(geoShapeField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(geoShapeField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(geoShapeField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(geoShapeField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_intersects] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_intersects(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_intersects(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval st_intersects(nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_within(cartesianPointField, cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_within(cartesianPointField, to_cartesianshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]", + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_within(geoPointField, geoPointField)", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(geoPointField, to_geoshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_within(geoPointField, to_geoshape(\"POINT (30 10)\"))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_within(to_geoshape(\"POINT (30 10)\"), geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]", + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_within(true, true)", + "error": [ + "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(booleanField, booleanField)", + "error": [ + "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(cartesianPointField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(cartesianPointField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(cartesianShapeField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(cartesianShapeField, cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(geoPointField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(geoPointField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(geoPointField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(geoShapeField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(geoShapeField, geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(geoShapeField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(geoShapeField, geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_within] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_within(cartesianPointField, cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_within(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval st_within(nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_x(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_x(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_x(to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_x(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_x(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_x(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_x(true)", + "error": [ + "Argument of [st_x] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_x(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_x(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_x(to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_x(booleanField)", + "error": [ + "Argument of [st_x] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_x(*)", + "error": [ + "Using wildcards (*) in st_x is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_x(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_x(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_x(to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_x(cartesianPointField, extraArg)", + "error": [ + "Error: [st_x] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_x(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_x(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval st_x(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = st_x(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_x(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_x(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_x(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_x(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_x(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_y(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row st_y(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_y(to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_y(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row st_y(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_y(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = st_y(true)", + "error": [ + "Argument of [st_y] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_y(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_y(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_y(to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_y(booleanField)", + "error": [ + "Argument of [st_y] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_y(*)", + "error": [ + "Using wildcards (*) in st_y is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_y(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_y(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = st_y(to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_y(cartesianPointField, extraArg)", + "error": [ + "Error: [st_y] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_y(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_y(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval st_y(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = st_y(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_y(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_y(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_y(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row st_y(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = st_y(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = starts_with(\"a\", \"a\")", + "error": [], + "warning": [] + }, + { + "query": "row starts_with(\"a\", \"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = starts_with(to_string(true), to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = starts_with(true, true)", + "error": [ + "Argument of [starts_with] must be [keyword], found value [true] type [boolean]", + "Argument of [starts_with] must be [keyword], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = starts_with(keywordField, keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval starts_with(keywordField, keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = starts_with(to_string(booleanField), to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval starts_with(booleanField, booleanField)", + "error": [ + "Argument of [starts_with] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [starts_with] must be [keyword], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = starts_with(textField, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval starts_with(textField, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval starts_with(keywordField, keywordField, extraArg)", + "error": [ + "Error: [starts_with] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort starts_with(keywordField, keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval starts_with(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval starts_with(nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = starts_with(keywordField, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval starts_with(keywordField, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = starts_with(textField, keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval starts_with(textField, keywordField)", + "error": [], + "warning": [] + }, + { + "query": "row var = substring(\"a\", 5, 5)", + "error": [], + "warning": [] + }, + { + "query": "row substring(\"a\", 5, 5)", + "error": [], + "warning": [] + }, + { + "query": "row var = substring(to_string(true), to_integer(true), to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = substring(true, true, true)", + "error": [ + "Argument of [substring] must be [keyword], found value [true] type [boolean]", + "Argument of [substring] must be [integer], found value [true] type [boolean]", + "Argument of [substring] must be [integer], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = substring(keywordField, integerField, integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval substring(keywordField, integerField, integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = substring(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval substring(booleanField, booleanField, booleanField)", + "error": [ + "Argument of [substring] must be [keyword], found value [booleanField] type [boolean]", + "Argument of [substring] must be [integer], found value [booleanField] type [boolean]", + "Argument of [substring] must be [integer], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = substring(textField, integerField, integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval substring(textField, integerField, integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval substring(keywordField, integerField, integerField, extraArg)", + "error": [ + "Error: [substring] function expects no more than 3 arguments, got 4." + ], + "warning": [] + }, + { + "query": "from a_index | sort substring(keywordField, integerField, integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval substring(null, null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval substring(nullVar, nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = tan(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row tan(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = tan(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = tan(5)", + "error": [], + "warning": [] + }, + { + "query": "row tan(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = tan(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = tan(true)", + "error": [ + "Argument of [tan] must be [double], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where tan(doubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where tan(booleanField) > 0", + "error": [ + "Argument of [tan] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where tan(integerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where tan(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where tan(unsignedLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tan(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tan(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tan(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tan(booleanField)", + "error": [ + "Argument of [tan] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = tan(*)", + "error": [ + "Using wildcards (*) in tan is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = tan(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tan(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tan(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tan(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tan(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tan(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tan(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tan(doubleField, extraArg)", + "error": [ + "Error: [tan] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort tan(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tan(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval tan(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = tanh(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row tanh(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = tanh(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = tanh(5)", + "error": [], + "warning": [] + }, + { + "query": "row tanh(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = tanh(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = tanh(true)", + "error": [ + "Argument of [tanh] must be [double], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where tanh(doubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where tanh(booleanField) > 0", + "error": [ + "Argument of [tanh] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where tanh(integerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where tanh(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where tanh(unsignedLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tanh(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tanh(booleanField)", + "error": [ + "Argument of [tanh] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(*)", + "error": [ + "Using wildcards (*) in tanh is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tanh(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tanh(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tanh(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tanh(doubleField, extraArg)", + "error": [ + "Error: [tanh] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort tanh(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tanh(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval tanh(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = tau()", + "error": [], + "warning": [] + }, + { + "query": "row tau()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where tau() > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = tau()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tau()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tau(extraArg)", + "error": [ + "Error: [tau] function expects exactly 0 arguments, got 1." + ], + "warning": [] + }, + { + "query": "from a_index | sort tau()", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval tau()", + "error": [], + "warning": [] + }, + { + "query": "row var = to_base64(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_base64(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_base64(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_base64(true)", + "error": [ + "Argument of [to_base64] must be [keyword], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_base64(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_base64(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_base64(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_base64(booleanField)", + "error": [ + "Argument of [to_base64] must be [keyword], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_base64(*)", + "error": [ + "Using wildcards (*) in to_base64 is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_base64(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_base64(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_base64(keywordField, extraArg)", + "error": [ + "Error: [to_base64] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_base64(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_base64(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_base64(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(true)", + "error": [], + "warning": [] + }, + { + "query": "row to_boolean(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_bool(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(to_boolean(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_boolean(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_bool(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_boolean(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_bool(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_boolean(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_bool(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [to_boolean] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_bool(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(to_boolean(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(cartesianPointField)", + "error": [ + "Argument of [to_boolean] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(*)", + "error": [ + "Using wildcards (*) in to_boolean is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_bool(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_bool(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_bool(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_bool(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_bool(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_bool(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(booleanField, extraArg)", + "error": [ + "Error: [to_boolean] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_boolean(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_boolean(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row to_cartesianpoint(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_cartesianpoint(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(true)", + "error": [ + "Argument of [to_cartesianpoint] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianpoint(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianpoint(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianpoint(booleanField)", + "error": [ + "Argument of [to_cartesianpoint] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianpoint(*)", + "error": [ + "Using wildcards (*) in to_cartesianpoint is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianpoint(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianpoint(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianpoint(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianpoint(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianpoint(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianpoint(cartesianPointField, extraArg)", + "error": [ + "Error: [to_cartesianpoint] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_cartesianpoint(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianpoint(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_cartesianpoint(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row to_cartesianshape(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_cartesianshape(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(true)", + "error": [ + "Argument of [to_cartesianshape] must be [cartesian_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianshape(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianshape(booleanField)", + "error": [ + "Argument of [to_cartesianshape] must be [cartesian_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(*)", + "error": [ + "Using wildcards (*) in to_cartesianshape is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianshape(cartesianShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(to_cartesianshape(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianshape(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_cartesianshape(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianshape(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianshape(cartesianPointField, extraArg)", + "error": [ + "Error: [to_cartesianshape] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_cartesianshape(cartesianPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianshape(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_cartesianshape(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianshape(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_datetime(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dt(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_datetime(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dt(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_datetime(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dt(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_datetime(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dt(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_datetime(true)", + "error": [ + "Argument of [to_datetime] must be [date], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dt(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(to_datetime(dateField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(booleanField)", + "error": [ + "Argument of [to_datetime] must be [date], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(*)", + "error": [ + "Using wildcards (*) in to_datetime is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dt(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dt(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dt(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dt(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dt(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_datetime(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dt(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(dateField, extraArg)", + "error": [ + "Error: [to_datetime] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_datetime(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_datetime(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(\"2022\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_datetime(concat(\"20\", \"22\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_degrees(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_degrees(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_degrees(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_degrees(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_degrees(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_degrees(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_degrees(true)", + "error": [ + "Argument of [to_degrees] must be [double], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_degrees(doubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_degrees(booleanField) > 0", + "error": [ + "Argument of [to_degrees] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_degrees(integerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_degrees(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_degrees(unsignedLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_degrees(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_degrees(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_degrees(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_degrees(booleanField)", + "error": [ + "Argument of [to_degrees] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_degrees(*)", + "error": [ + "Using wildcards (*) in to_degrees is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_degrees(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_degrees(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_degrees(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_degrees(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_degrees(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_degrees(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_degrees(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_degrees(doubleField, extraArg)", + "error": [ + "Error: [to_degrees] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_degrees(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_degrees(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_degrees(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(true)", + "error": [], + "warning": [] + }, + { + "query": "row to_double(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dbl(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(to_boolean(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_double(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dbl(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_double(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dbl(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_double(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dbl(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_double(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_dbl(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_double(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [to_double] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_double(booleanField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(cartesianPointField) > 0", + "error": [ + "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_double(counterDoubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(counterIntegerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(counterLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(dateField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(doubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(integerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(keywordField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(textField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_double(unsignedLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(to_boolean(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(cartesianPointField)", + "error": [ + "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(*)", + "error": [ + "Using wildcards (*) in to_double is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(to_datetime(dateField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_double(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_dbl(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(booleanField, extraArg)", + "error": [ + "Error: [to_double] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_double(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_double(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(\"2022\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_double(concat(\"20\", \"22\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geopoint(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row to_geopoint(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_geopoint(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_geopoint(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_geopoint(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geopoint(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geopoint(true)", + "error": [ + "Argument of [to_geopoint] must be [geo_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geopoint(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geopoint(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geopoint(to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geopoint(booleanField)", + "error": [ + "Argument of [to_geopoint] must be [geo_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geopoint(*)", + "error": [ + "Using wildcards (*) in to_geopoint is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geopoint(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geopoint(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geopoint(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geopoint(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geopoint(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geopoint(geoPointField, extraArg)", + "error": [ + "Error: [to_geopoint] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_geopoint(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geopoint(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_geopoint(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geopoint(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_geopoint(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geopoint(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geoshape(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row to_geoshape(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_geoshape(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_geoshape(to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_geoshape(to_geoshape(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geoshape(to_geoshape(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], + "warning": [] + }, + { + "query": "row var = to_geoshape(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_geoshape(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geoshape(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geoshape(true)", + "error": [ + "Argument of [to_geoshape] must be [geo_point], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geoshape(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(to_geopoint(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geoshape(booleanField)", + "error": [ + "Argument of [to_geoshape] must be [geo_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(*)", + "error": [ + "Using wildcards (*) in to_geoshape is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geoshape(geoShapeField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(to_geoshape(geoPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geoshape(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geoshape(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geoshape(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geoshape(geoPointField, extraArg)", + "error": [ + "Error: [to_geoshape] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_geoshape(geoPointField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_geoshape(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_geoshape(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geoshape(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_geoshape(to_geopoint(\"POINT (30 10)\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geoshape(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_geoshape(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(true)", + "error": [], + "warning": [] + }, + { + "query": "row to_integer(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_int(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(to_boolean(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_integer(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_int(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_integer(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_int(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_integer(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_int(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_integer(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_int(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_integer(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [to_integer] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_integer(booleanField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(cartesianPointField) > 0", + "error": [ + "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_integer(counterIntegerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(dateField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(doubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(integerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(keywordField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(textField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_integer(unsignedLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(to_boolean(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(cartesianPointField)", + "error": [ + "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(*)", + "error": [ + "Using wildcards (*) in to_integer is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(to_datetime(dateField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_int(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(booleanField, extraArg)", + "error": [ + "Error: [to_integer] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_integer(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_integer(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(\"2022\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_integer(concat(\"20\", \"22\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ip(to_ip(\"127.0.0.1\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_ip(to_ip(\"127.0.0.1\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ip(to_ip(to_ip(\"127.0.0.1\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ip(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_ip(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ip(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ip(true)", + "error": [ + "Argument of [to_ip] must be [ip], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ip(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_ip(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ip(to_ip(ipField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_ip(booleanField)", + "error": [ + "Argument of [to_ip] must be [ip], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ip(*)", + "error": [ + "Using wildcards (*) in to_ip is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ip(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_ip(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ip(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ip(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_ip(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_ip(ipField, extraArg)", + "error": [ + "Error: [to_ip] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_ip(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_ip(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_ip(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(true)", + "error": [], + "warning": [] + }, + { + "query": "row to_long(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(to_boolean(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_long(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_long(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_long(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_long(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_long(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [to_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_long(booleanField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(cartesianPointField) > 0", + "error": [ + "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_long(counterIntegerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(counterLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(dateField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(doubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(integerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(keywordField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(textField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_long(unsignedLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(to_boolean(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(cartesianPointField)", + "error": [ + "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(*)", + "error": [ + "Using wildcards (*) in to_long is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(to_datetime(dateField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_long(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(booleanField, extraArg)", + "error": [ + "Error: [to_long] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_long(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_long(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(\"2022\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(concat(\"20\", \"22\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_lower(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_lower(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_lower(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_lower(true)", + "error": [ + "Argument of [to_lower] must be [keyword], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_lower(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_lower(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_lower(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_lower(booleanField)", + "error": [ + "Argument of [to_lower] must be [keyword], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_lower(*)", + "error": [ + "Using wildcards (*) in to_lower is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_lower(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_lower(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_lower(keywordField, extraArg)", + "error": [ + "Error: [to_lower] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_lower(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_lower(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_lower(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_radians(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_radians(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_radians(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_radians(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_radians(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_radians(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_radians(true)", + "error": [ + "Argument of [to_radians] must be [double], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_radians(doubleField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_radians(booleanField) > 0", + "error": [ + "Argument of [to_radians] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_radians(integerField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_radians(longField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where to_radians(unsignedLongField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_radians(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_radians(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_radians(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_radians(booleanField)", + "error": [ + "Argument of [to_radians] must be [double], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_radians(*)", + "error": [ + "Using wildcards (*) in to_radians is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_radians(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_radians(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_radians(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_radians(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_radians(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_radians(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_radians(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_radians(doubleField, extraArg)", + "error": [ + "Error: [to_radians] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_radians(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_radians(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_radians(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(true)", + "error": [], + "warning": [] + }, + { + "query": "row to_unsigned_long(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ul(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ulong(true)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(to_boolean(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_unsigned_long(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ul(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ulong(to_datetime(\"2021-01-01T00:00:00Z\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row to_unsigned_long(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ul(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ulong(5.5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(to_double(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(5)", + "error": [], + "warning": [] + }, + { + "query": "row to_unsigned_long(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ul(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ulong(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_unsigned_long(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ul(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ulong(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_unsigned_long(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [to_unsigned_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(booleanField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(booleanField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(cartesianPointField) > 0", + "error": [ + "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [>] must be [double], found value [to_unsigned_long(cartesianPointField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(dateField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(dateField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(doubleField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(doubleField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(integerField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(integerField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(keywordField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(keywordField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(longField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(longField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(textField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(textField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_unsigned_long(unsignedLongField) > 0", + "error": [ + "Argument of [>] must be [double], found value [to_unsigned_long(unsignedLongField)] type [unsigned_long]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(to_boolean(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(cartesianPointField)", + "error": [ + "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(*)", + "error": [ + "Using wildcards (*) in to_unsigned_long is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(to_datetime(dateField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(to_double(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_unsigned_long(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ul(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ulong(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(booleanField, extraArg)", + "error": [ + "Error: [to_unsigned_long] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_unsigned_long(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_unsigned_long(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(\"2022\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_unsigned_long(concat(\"20\", \"22\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_upper(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_upper(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_upper(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_upper(true)", + "error": [ + "Argument of [to_upper] must be [keyword], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_upper(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_upper(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_upper(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_upper(booleanField)", + "error": [ + "Argument of [to_upper] must be [keyword], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_upper(*)", + "error": [ + "Using wildcards (*) in to_upper is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_upper(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_upper(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_upper(keywordField, extraArg)", + "error": [ + "Error: [to_upper] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_upper(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_upper(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_upper(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = to_version(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row to_version(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ver(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = to_version(to_version(\"1.0.0\"))", + "error": [], + "warning": [] + }, + { + "query": "row to_version(to_version(\"1.0.0\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_ver(to_version(\"1.0.0\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_version(true)", + "error": [ + "Argument of [to_version] must be [keyword], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_version(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_version(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ver(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_version(*)", + "error": [ + "Using wildcards (*) in to_version is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_version(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_version(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ver(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_version(versionField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_version(versionField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_ver(versionField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_version(keywordField, extraArg)", + "error": [ + "Error: [to_version] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_version(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_version(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval to_version(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = trim(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row trim(\"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = trim(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = trim(true)", + "error": [ + "Argument of [trim] must be [keyword], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = trim(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval trim(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = trim(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval trim(booleanField)", + "error": [ + "Argument of [trim] must be [keyword], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = trim(*)", + "error": [ + "Using wildcards (*) in trim is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = trim(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval trim(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval trim(keywordField, extraArg)", + "error": [ + "Error: [trim] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort trim(keywordField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval trim(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval trim(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "row var = case(true, \"a\")", + "error": [], + "warning": [] + }, + { + "query": "row case(true, \"a\")", + "error": [], + "warning": [] + }, + { + "query": "row var = case(to_cartesianpoint(\"POINT (30 10)\"), true)", + "error": [ + "Argument of [case] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = case(booleanField, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval case(booleanField, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort case(booleanField, textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval case(null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | eval case(nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(integerField)) + avg(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(integerField)) + avg(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(integerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(integerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(integerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(integerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(integerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats avg(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats avg(booleanField)", + "error": [ + "Argument of [avg] must be [integer], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(*)", + "error": [ + "Using wildcards (*) in avg is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(counterIntegerField)) + avg(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(counterIntegerField)) + avg(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(counterIntegerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(counterIntegerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterIntegerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterIntegerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterIntegerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(doubleField)) + avg(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(doubleField)) + avg(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(doubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(doubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(doubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(doubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(unsignedLongField)) + avg(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(unsignedLongField)) + avg(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(unsignedLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(unsignedLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(unsignedLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(unsignedLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(unsignedLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(longField)) + avg(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(longField)) + avg(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(longField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(longField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(longField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(longField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(longField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(counterLongField)) + avg(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(counterLongField)) + avg(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(counterLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(counterLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = avg(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(avg(counterDoubleField)) + avg(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(avg(counterDoubleField)) + avg(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(counterDoubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = avg(counterDoubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterDoubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterDoubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), avg(counterDoubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = avg(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort avg(integerField)", + "error": [ + "SORT does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(integerField)", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(integerField) > 0", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(counterIntegerField)", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(counterIntegerField) > 0", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(doubleField)", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(doubleField) > 0", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(unsignedLongField)", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(unsignedLongField) > 0", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(longField)", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(longField) > 0", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(counterLongField)", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(counterLongField) > 0", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(counterDoubleField)", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | where avg(counterDoubleField) > 0", + "error": [ + "WHERE does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(integerField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(integerField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(integerField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(integerField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(counterIntegerField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(counterIntegerField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(counterIntegerField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(counterIntegerField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(doubleField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(doubleField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(doubleField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(doubleField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(unsignedLongField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(unsignedLongField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(unsignedLongField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(unsignedLongField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(longField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(longField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(longField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(longField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(counterLongField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(counterLongField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(counterLongField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(counterLongField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(counterDoubleField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = avg(counterDoubleField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(counterDoubleField)", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | eval avg(counterDoubleField) > 0", + "error": [ + "EVAL does not support function avg" + ], + "warning": [] + }, + { + "query": "from a_index | stats avg(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats avg(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(integerField)) + sum(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(integerField)) + sum(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(integerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(integerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(integerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(integerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(integerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats sum(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats sum(booleanField)", + "error": [ + "Argument of [sum] must be [integer], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(*)", + "error": [ + "Using wildcards (*) in sum is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(counterIntegerField)) + sum(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(counterIntegerField)) + sum(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(counterIntegerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(counterIntegerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterIntegerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterIntegerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterIntegerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(doubleField)) + sum(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(doubleField)) + sum(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(doubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(doubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(doubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(doubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(doubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(unsignedLongField)) + sum(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(unsignedLongField)) + sum(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(unsignedLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(unsignedLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(unsignedLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(unsignedLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(unsignedLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(longField)) + sum(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(longField)) + sum(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(longField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(longField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(longField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(longField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(longField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(counterLongField)) + sum(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(counterLongField)) + sum(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(counterLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(counterLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = sum(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(sum(counterDoubleField)) + sum(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(sum(counterDoubleField)) + sum(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats sum(counterDoubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = sum(counterDoubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterDoubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterDoubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), sum(counterDoubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = sum(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort sum(integerField)", + "error": [ + "SORT does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(integerField)", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(integerField) > 0", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(counterIntegerField)", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(counterIntegerField) > 0", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(doubleField)", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(doubleField) > 0", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(unsignedLongField)", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(unsignedLongField) > 0", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(longField)", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(longField) > 0", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(counterLongField)", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(counterLongField) > 0", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(counterDoubleField)", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | where sum(counterDoubleField) > 0", + "error": [ + "WHERE does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(integerField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(integerField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(integerField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(integerField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(counterIntegerField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(counterIntegerField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(counterIntegerField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(counterIntegerField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(doubleField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(doubleField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(doubleField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(doubleField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(unsignedLongField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(unsignedLongField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(unsignedLongField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(unsignedLongField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(longField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(longField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(longField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(longField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(counterLongField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(counterLongField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(counterLongField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(counterLongField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(counterDoubleField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sum(counterDoubleField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(counterDoubleField)", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | eval sum(counterDoubleField) > 0", + "error": [ + "EVAL does not support function sum" + ], + "warning": [] + }, + { + "query": "from a_index | stats sum(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats sum(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(integerField)) + median(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(integerField)) + median(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(integerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(integerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(integerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(integerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(integerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats median(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats median(booleanField)", + "error": [ + "Argument of [median] must be [integer], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = median(*)", + "error": [ + "Using wildcards (*) in median is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = median(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(counterIntegerField)) + median(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(counterIntegerField)) + median(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(counterIntegerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(counterIntegerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterIntegerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterIntegerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterIntegerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(doubleField)) + median(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(doubleField)) + median(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(doubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(doubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(doubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(doubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(doubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(unsignedLongField)) + median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(unsignedLongField)) + median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(unsignedLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(unsignedLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(unsignedLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(unsignedLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(unsignedLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(longField)) + median(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(longField)) + median(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(longField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(longField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(longField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(longField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(longField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(counterLongField)) + median(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(counterLongField)) + median(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(counterLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(counterLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median(counterDoubleField)) + median(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median(counterDoubleField)) + median(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median(counterDoubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median(counterDoubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterDoubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterDoubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median(counterDoubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort median(integerField)", + "error": [ + "SORT does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(integerField)", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(integerField) > 0", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(counterIntegerField)", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(counterIntegerField) > 0", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(doubleField)", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(doubleField) > 0", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(unsignedLongField)", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(unsignedLongField) > 0", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(longField)", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(longField) > 0", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(counterLongField)", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(counterLongField) > 0", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(counterDoubleField)", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | where median(counterDoubleField) > 0", + "error": [ + "WHERE does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(integerField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(integerField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(integerField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(integerField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(counterIntegerField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(counterIntegerField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(counterIntegerField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(counterIntegerField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(doubleField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(doubleField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(doubleField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(doubleField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(unsignedLongField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(unsignedLongField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(unsignedLongField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(unsignedLongField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(longField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(longField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(longField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(longField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(counterLongField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(counterLongField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(counterLongField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(counterLongField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(counterDoubleField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median(counterDoubleField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(counterDoubleField)", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | eval median(counterDoubleField) > 0", + "error": [ + "EVAL does not support function median" + ], + "warning": [] + }, + { + "query": "from a_index | stats median(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats median(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(integerField)) + median_absolute_deviation(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(integerField)) + median_absolute_deviation(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(integerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(integerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(integerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(integerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(integerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(booleanField)", + "error": [ + "Argument of [median_absolute_deviation] must be [integer], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(*)", + "error": [ + "Using wildcards (*) in median_absolute_deviation is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(counterIntegerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(counterIntegerField)) + median_absolute_deviation(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(counterIntegerField)) + median_absolute_deviation(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterIntegerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(counterIntegerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(counterIntegerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterIntegerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterIntegerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterIntegerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(doubleField)) + median_absolute_deviation(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(doubleField)) + median_absolute_deviation(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(doubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(doubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(doubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(doubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(unsignedLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(unsignedLongField)) + median_absolute_deviation(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(unsignedLongField)) + median_absolute_deviation(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(unsignedLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(unsignedLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(unsignedLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(unsignedLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(unsignedLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(unsignedLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(longField)) + median_absolute_deviation(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(longField)) + median_absolute_deviation(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(longField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(longField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(longField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(longField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(longField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(counterLongField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(counterLongField)) + median_absolute_deviation(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(counterLongField)) + median_absolute_deviation(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterLongField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(counterLongField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(counterLongField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterLongField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterLongField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterLongField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterLongField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = median_absolute_deviation(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(median_absolute_deviation(counterDoubleField)) + median_absolute_deviation(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(median_absolute_deviation(counterDoubleField)) + median_absolute_deviation(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(counterDoubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = median_absolute_deviation(counterDoubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterDoubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterDoubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), median_absolute_deviation(counterDoubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort median_absolute_deviation(integerField)", + "error": [ + "SORT does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(integerField)", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(integerField) > 0", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(counterIntegerField)", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(counterIntegerField) > 0", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(doubleField)", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(doubleField) > 0", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(unsignedLongField)", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(unsignedLongField) > 0", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(longField)", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(longField) > 0", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(counterLongField)", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(counterLongField) > 0", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(counterDoubleField)", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | where median_absolute_deviation(counterDoubleField) > 0", + "error": [ + "WHERE does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(integerField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(integerField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(integerField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(integerField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(counterIntegerField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(counterIntegerField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(counterIntegerField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(counterIntegerField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(doubleField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(doubleField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(doubleField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(doubleField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(unsignedLongField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(unsignedLongField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(unsignedLongField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(unsignedLongField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(longField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(longField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(longField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(longField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(counterLongField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(counterLongField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(counterLongField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(counterLongField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(counterDoubleField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = median_absolute_deviation(counterDoubleField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(counterDoubleField)", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | eval median_absolute_deviation(counterDoubleField) > 0", + "error": [ + "EVAL does not support function median_absolute_deviation" + ], + "warning": [] + }, + { + "query": "from a_index | stats median_absolute_deviation(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats median_absolute_deviation(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = max(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(doubleField)) + max(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(doubleField)) + max(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = max(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = max(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(doubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = max(doubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(doubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(doubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(doubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = max(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats max(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats max(cartesianPointField)", + "error": [ + "Argument of [max] must be [double], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = max(*)", + "error": [ + "Using wildcards (*) in max is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = max(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(longField)) + max(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(longField)) + max(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = max(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(longField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = max(longField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(longField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(longField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(longField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = max(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(integerField)) + max(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(integerField)) + max(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = max(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(integerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = max(integerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(integerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(integerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), max(integerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = max(integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = max(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = max(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = max(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort max(doubleField)", + "error": [ + "SORT does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(doubleField)", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(doubleField) > 0", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(longField)", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(longField) > 0", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(integerField)", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(integerField) > 0", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(dateField)", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(dateField) > 0", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(booleanField)", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(booleanField) > 0", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(ipField)", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | where max(ipField) > 0", + "error": [ + "WHERE does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(doubleField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(doubleField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(doubleField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(doubleField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(longField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(longField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(longField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(longField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(integerField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(integerField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(integerField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(integerField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(dateField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(dateField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(dateField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(dateField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(booleanField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(booleanField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(booleanField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(booleanField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(ipField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = max(ipField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(ipField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(ipField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | stats max(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats max(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(\"2022\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats max(concat(\"20\", \"22\"))", + "error": [ + "Argument of [max] must be [double], found value [concat(\"20\",\"22\")] type [keyword]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = min(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(min(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(min(doubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(min(doubleField)) + min(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(min(doubleField)) + min(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = min(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = min(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(doubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(doubleField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = min(doubleField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(doubleField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(doubleField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(doubleField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = min(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats min(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], + "warning": [] + }, + { + "query": "from a_index | stats min(cartesianPointField)", + "error": [ + "Argument of [min] must be [double], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = min(*)", + "error": [ + "Using wildcards (*) in min is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = min(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(min(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(min(longField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(min(longField)) + min(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(min(longField)) + min(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = min(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(longField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(longField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = min(longField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(longField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(longField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(longField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = min(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(min(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(min(integerField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(min(integerField)) + min(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(min(integerField)) + min(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = min(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(integerField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(integerField) by round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var0 = min(integerField) by var1 = round(doubleField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(integerField) by round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(integerField) by var1 = round(doubleField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), min(integerField) by round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(doubleField), var0 = min(integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = min(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = min(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(booleanField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = min(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(ipField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort min(doubleField)", + "error": [ + "SORT does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(doubleField)", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(doubleField) > 0", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(longField)", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(longField) > 0", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(integerField)", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(integerField) > 0", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(dateField)", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(dateField) > 0", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(booleanField)", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(booleanField) > 0", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(ipField)", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | where min(ipField) > 0", + "error": [ + "WHERE does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(doubleField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(doubleField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(doubleField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(doubleField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(longField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(longField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(longField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(longField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(integerField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(integerField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(integerField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(integerField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(dateField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(dateField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(dateField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(dateField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(booleanField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(booleanField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(booleanField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(booleanField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(ipField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = min(ipField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(ipField)", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | eval min(ipField) > 0", + "error": [ + "EVAL does not support function min" + ], + "warning": [] + }, + { + "query": "from a_index | stats min(null)", "error": [], "warning": [] }, { - "query": "row to_upper(\"a\")", + "query": "row nullVar = null | stats min(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(\"2022\")", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats min(concat(\"20\", \"22\"))", + "error": [ + "Argument of [min] must be [double], found value [concat(\"20\",\"22\")] type [keyword]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = count(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats count(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(count(textField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(count(textField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(count(textField)) + count(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(count(textField)) + count(textField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort count(textField)", + "error": [ + "SORT does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | where count(textField)", + "error": [ + "WHERE does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | where count(textField) > 0", + "error": [ + "WHERE does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = count(textField)", + "error": [ + "EVAL does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = count(textField) > 0", + "error": [ + "EVAL does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | eval count(textField)", + "error": [ + "EVAL does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | eval count(textField) > 0", + "error": [ + "EVAL does not support function count" + ], + "warning": [] + }, + { + "query": "from a_index | stats count(null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats count(nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats count_distinct(null, null, null, null, null, null, null, null)", + "error": [], + "warning": [] + }, + { + "query": "row nullVar = null | stats count_distinct(nullVar, nullVar, nullVar, nullVar, nullVar, nullVar, nullVar, nullVar)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)) + count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)) + count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [ + "SORT does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | where count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [ + "WHERE does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | where count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField) > 0", + "error": [ + "WHERE does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [ + "EVAL does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField) > 0", + "error": [ + "EVAL does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | eval count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)", + "error": [ + "EVAL does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | eval count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField) > 0", + "error": [ + "EVAL does not support function count_distinct" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = st_centroid_agg(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = to_upper(to_string(\"a\"))", + "query": "from a_index | stats st_centroid_agg(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = to_upper(5)", + "query": "from a_index | stats var = st_centroid_agg(avg(integerField))", "error": [ - "Argument of [to_upper] must be [string], found value [5] type [number]" + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" ], "warning": [] }, { - "query": "from a_index | where length(to_upper(stringField)) > 0", - "error": [], + "query": "from a_index | stats st_centroid_agg(avg(integerField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" + ], "warning": [] }, { - "query": "from a_index | where length(to_upper(numberField)) > 0", + "query": "from a_index | stats st_centroid_agg(booleanField)", "error": [ - "Argument of [to_upper] must be [string], found value [numberField] type [number]" + "Argument of [st_centroid_agg] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_upper(stringField)", - "error": [], + "query": "from a_index | stats var = st_centroid_agg(*)", + "error": [ + "Using wildcards (*) in st_centroid_agg is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval to_upper(stringField)", + "query": "from a_index | stats var = st_centroid_agg(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_upper(to_string(stringField))", + "query": "from a_index | stats st_centroid_agg(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_upper(numberField)", + "query": "from a_index | sort st_centroid_agg(cartesianPointField)", "error": [ - "Argument of [to_upper] must be [string], found value [numberField] type [number]" + "SORT does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval to_upper(stringField, extraArg)", + "query": "from a_index | where st_centroid_agg(cartesianPointField)", "error": [ - "Error: [to_upper] function expects exactly one argument, got 2." + "WHERE does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval var = to_upper(*)", + "query": "from a_index | where st_centroid_agg(cartesianPointField) > 0", "error": [ - "Using wildcards (*) in to_upper is not allowed" + "WHERE does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | sort to_upper(stringField)", - "error": [], + "query": "from a_index | where st_centroid_agg(geoPointField)", + "error": [ + "WHERE does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "row var = to_upper(to_string(true))", - "error": [], + "query": "from a_index | where st_centroid_agg(geoPointField) > 0", + "error": [ + "WHERE does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "row var = to_upper(true)", + "query": "from a_index | eval var = st_centroid_agg(cartesianPointField)", "error": [ - "Argument of [to_upper] must be [string], found value [true] type [boolean]" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | where length(to_upper(booleanField)) > 0", + "query": "from a_index | eval var = st_centroid_agg(cartesianPointField) > 0", "error": [ - "Argument of [to_upper] must be [string], found value [booleanField] type [boolean]" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval var = to_upper(to_string(booleanField))", - "error": [], + "query": "from a_index | eval st_centroid_agg(cartesianPointField)", + "error": [ + "EVAL does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "from a_index | eval to_upper(booleanField)", + "query": "from a_index | eval st_centroid_agg(cartesianPointField) > 0", "error": [ - "Argument of [to_upper] must be [string], found value [booleanField] type [boolean]" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval to_upper(null)", - "error": [], + "query": "from a_index | eval var = st_centroid_agg(geoPointField)", + "error": [ + "EVAL does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "row nullVar = null | eval to_upper(nullVar)", - "error": [], + "query": "from a_index | eval var = st_centroid_agg(geoPointField) > 0", + "error": [ + "EVAL does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "row var = to_version(\"a\")", - "error": [], + "query": "from a_index | eval st_centroid_agg(geoPointField)", + "error": [ + "EVAL does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "row to_version(\"a\")", - "error": [], + "query": "from a_index | eval st_centroid_agg(geoPointField) > 0", + "error": [ + "EVAL does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "row var = to_ver(\"a\")", + "query": "from a_index | stats st_centroid_agg(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_version(stringField)", + "query": "row nullVar = null | stats st_centroid_agg(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_version(stringField)", + "query": "from a_index | stats var = values(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ver(stringField)", + "query": "from a_index | stats values(textField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_version(*)", + "query": "from a_index | sort values(textField)", "error": [ - "Using wildcards (*) in to_version is not allowed" + "SORT does not support function values" ], "warning": [] }, { - "query": "from a_index | sort to_version(stringField)", - "error": [], + "query": "from a_index | where values(textField)", + "error": [ + "WHERE does not support function values" + ], "warning": [] }, { - "query": "row var = to_version(to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | where values(textField) > 0", + "error": [ + "WHERE does not support function values" + ], "warning": [] }, { - "query": "row to_version(to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | eval var = values(textField)", + "error": [ + "EVAL does not support function values" + ], "warning": [] }, { - "query": "row var = to_ver(to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | eval var = values(textField) > 0", + "error": [ + "EVAL does not support function values" + ], "warning": [] }, { - "query": "row var = to_version(true)", + "query": "from a_index | eval values(textField)", "error": [ - "Argument of [to_version] must be [string], found value [true] type [boolean]" + "EVAL does not support function values" ], "warning": [] }, { - "query": "from a_index | eval var = to_version(versionField)", - "error": [], + "query": "from a_index | eval values(textField) > 0", + "error": [ + "EVAL does not support function values" + ], "warning": [] }, { - "query": "from a_index | eval to_version(versionField)", + "query": "from a_index | stats values(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ver(versionField)", + "query": "row nullVar = null | stats values(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_version(stringField, extraArg)", + "query": "from a_index | stats var = top(textField, integerField, textField)", "error": [ - "Error: [to_version] function expects exactly one argument, got 2." + "Argument of [=] must be a constant, received [top(textField,integerField,textField)]" ], "warning": [] }, { - "query": "from a_index | eval to_version(null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | eval to_version(nullVar)", - "error": [], - "warning": [] - }, - { - "query": "row var = trim(\"a\")", - "error": [], + "query": "from a_index | stats top(textField, integerField, textField)", + "error": [ + "Argument of [top] must be a constant, received [integerField]", + "Argument of [top] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "row trim(\"a\")", - "error": [], + "query": "from a_index | sort top(textField, integerField, textField)", + "error": [ + "SORT does not support function top" + ], "warning": [] }, { - "query": "row var = trim(to_string(\"a\"))", - "error": [], + "query": "from a_index | where top(textField, integerField, textField)", + "error": [ + "WHERE does not support function top" + ], "warning": [] }, { - "query": "row var = trim(5)", + "query": "from a_index | where top(textField, integerField, textField) > 0", "error": [ - "Argument of [trim] must be [string], found value [5] type [number]" + "WHERE does not support function top" ], "warning": [] }, { - "query": "from a_index | where length(trim(stringField)) > 0", - "error": [], + "query": "from a_index | eval var = top(textField, integerField, textField)", + "error": [ + "EVAL does not support function top" + ], "warning": [] }, { - "query": "from a_index | where length(trim(numberField)) > 0", + "query": "from a_index | eval var = top(textField, integerField, textField) > 0", "error": [ - "Argument of [trim] must be [string], found value [numberField] type [number]" + "EVAL does not support function top" ], "warning": [] }, { - "query": "from a_index | eval var = trim(stringField)", - "error": [], + "query": "from a_index | eval top(textField, integerField, textField)", + "error": [ + "EVAL does not support function top" + ], "warning": [] }, { - "query": "from a_index | eval trim(stringField)", - "error": [], + "query": "from a_index | eval top(textField, integerField, textField) > 0", + "error": [ + "EVAL does not support function top" + ], "warning": [] }, { - "query": "from a_index | eval var = trim(to_string(stringField))", + "query": "from a_index | stats top(null, null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval trim(numberField)", + "query": "row nullVar = null | stats top(nullVar, nullVar, nullVar)", "error": [ - "Argument of [trim] must be [string], found value [numberField] type [number]" + "Argument of [top] must be a constant, received [nullVar]", + "Argument of [top] must be a constant, received [nullVar]" ], "warning": [] }, { - "query": "from a_index | eval trim(stringField, extraArg)", + "query": "from a_index | stats var = top(textField, integerField, \"asc\")", "error": [ - "Error: [trim] function expects exactly one argument, got 2." + "Argument of [=] must be a constant, received [top(textField,integerField,\"asc\")]" ], "warning": [] }, { - "query": "from a_index | eval var = trim(*)", + "query": "from a_index | stats top(textField, integerField, \"asc\")", "error": [ - "Using wildcards (*) in trim is not allowed" + "Argument of [top] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | sort trim(stringField)", - "error": [], - "warning": [] - }, - { - "query": "row var = trim(to_string(true))", - "error": [], + "query": "from a_index | sort top(textField, integerField, \"asc\")", + "error": [ + "SORT does not support function top" + ], "warning": [] }, { - "query": "row var = trim(true)", + "query": "from a_index | where top(textField, integerField, \"asc\")", "error": [ - "Argument of [trim] must be [string], found value [true] type [boolean]" + "WHERE does not support function top" ], "warning": [] }, { - "query": "from a_index | where length(trim(booleanField)) > 0", + "query": "from a_index | where top(textField, integerField, \"asc\") > 0", "error": [ - "Argument of [trim] must be [string], found value [booleanField] type [boolean]" + "WHERE does not support function top" ], "warning": [] }, { - "query": "from a_index | eval var = trim(to_string(booleanField))", - "error": [], + "query": "from a_index | eval var = top(textField, integerField, \"asc\")", + "error": [ + "EVAL does not support function top" + ], "warning": [] }, { - "query": "from a_index | eval trim(booleanField)", + "query": "from a_index | eval var = top(textField, integerField, \"asc\") > 0", "error": [ - "Argument of [trim] must be [string], found value [booleanField] type [boolean]" + "EVAL does not support function top" ], "warning": [] }, { - "query": "from a_index | eval trim(null)", - "error": [], + "query": "from a_index | eval top(textField, integerField, \"asc\")", + "error": [ + "EVAL does not support function top" + ], "warning": [] }, { - "query": "row nullVar = null | eval trim(nullVar)", - "error": [], + "query": "from a_index | eval top(textField, integerField, \"asc\") > 0", + "error": [ + "EVAL does not support function top" + ], "warning": [] }, { - "query": "from a_index | stats var = avg(numberField)", + "query": "from a_index | stats var = weighted_avg(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField)", + "query": "from a_index | stats weighted_avg(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(avg(numberField))", + "query": "from a_index | stats var = round(weighted_avg(doubleField, doubleField))", "error": [], "warning": [] }, { - "query": "from a_index | stats round(avg(numberField))", + "query": "from a_index | stats round(weighted_avg(doubleField, doubleField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(avg(numberField)) + avg(numberField)", + "query": "from a_index | stats var = round(weighted_avg(doubleField, doubleField)) + weighted_avg(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(avg(numberField)) + avg(numberField)", + "query": "from a_index | stats round(weighted_avg(doubleField, doubleField)) + weighted_avg(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField / 2)", + "query": "from a_index | stats weighted_avg(doubleField / 2, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = avg(numberField / 2)", + "query": "from a_index | stats var0 = weighted_avg(doubleField / 2, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField / 2)", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField / 2, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField / 2)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField / 2, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = avg(numberField)", + "query": "from a_index | stats var0 = weighted_avg(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField)", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by round(numberField / 2)", + "query": "from a_index | stats weighted_avg(doubleField, doubleField) by round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = avg(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats var0 = weighted_avg(doubleField, doubleField) by var1 = round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, doubleField) by round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, doubleField) by var1 = round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, doubleField) by round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, doubleField) by var1 = round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats var = avg(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | stats avg(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | stats avg(stringField)", + "query": "from a_index | stats var = weighted_avg(avg(integerField), avg(integerField))", "error": [ - "Argument of [avg] must be [number], found value [stringField] type [string]" + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" ], "warning": [] }, { - "query": "from a_index | stats var = avg(*)", + "query": "from a_index | stats weighted_avg(avg(integerField), avg(integerField))", "error": [ - "Using wildcards (*) in avg is not allowed" + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" ], "warning": [] }, { - "query": "from a_index | sort avg(numberField)", + "query": "from a_index | stats weighted_avg(booleanField, booleanField)", "error": [ - "SORT does not support function avg" + "Argument of [weighted_avg] must be [double], found value [booleanField] type [boolean]", + "Argument of [weighted_avg] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where avg(numberField)", + "query": "from a_index | sort weighted_avg(doubleField, doubleField)", "error": [ - "WHERE does not support function avg" + "SORT does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | where avg(numberField) > 0", + "query": "from a_index | where weighted_avg(doubleField, doubleField)", "error": [ - "WHERE does not support function avg" + "WHERE does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = avg(numberField)", + "query": "from a_index | where weighted_avg(doubleField, doubleField) > 0", "error": [ - "EVAL does not support function avg" + "WHERE does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = avg(numberField) > 0", + "query": "from a_index | eval var = weighted_avg(doubleField, doubleField)", "error": [ - "EVAL does not support function avg" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval avg(numberField)", + "query": "from a_index | eval var = weighted_avg(doubleField, doubleField) > 0", "error": [ - "EVAL does not support function avg" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval avg(numberField) > 0", + "query": "from a_index | eval weighted_avg(doubleField, doubleField)", "error": [ - "EVAL does not support function avg" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats avg(booleanField)", + "query": "from a_index | eval weighted_avg(doubleField, doubleField) > 0", "error": [ - "Argument of [avg] must be [number], found value [booleanField] type [boolean]" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats avg(null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | stats avg(nullVar)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = sum(numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats sum(numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(sum(numberField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats round(sum(numberField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(sum(numberField)) + sum(numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats round(sum(numberField)) + sum(numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats sum(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var0 = sum(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats avg(numberField), sum(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField / 2)", + "query": "from a_index | stats weighted_avg(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = sum(numberField)", + "query": "row nullVar = null | stats weighted_avg(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), sum(numberField)", + "query": "from a_index | stats var = weighted_avg(doubleField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField)", + "query": "from a_index | stats weighted_avg(doubleField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(numberField) by round(numberField / 2)", + "query": "from a_index | stats var = round(weighted_avg(doubleField, longField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = sum(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats round(weighted_avg(doubleField, longField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats var = round(weighted_avg(doubleField, longField)) + weighted_avg(doubleField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats round(weighted_avg(doubleField, longField)) + weighted_avg(doubleField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats weighted_avg(doubleField / 2, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats var0 = weighted_avg(doubleField / 2, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = sum(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField / 2, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats sum(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField / 2, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats sum(stringField)", - "error": [ - "Argument of [sum] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | stats var0 = weighted_avg(doubleField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = sum(*)", - "error": [ - "Using wildcards (*) in sum is not allowed" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort sum(numberField)", - "error": [ - "SORT does not support function sum" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, longField)", + "error": [], "warning": [] }, { - "query": "from a_index | where sum(numberField)", - "error": [ - "WHERE does not support function sum" - ], + "query": "from a_index | stats weighted_avg(doubleField, longField) by round(doubleField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | where sum(numberField) > 0", - "error": [ - "WHERE does not support function sum" - ], + "query": "from a_index | stats var0 = weighted_avg(doubleField, longField) by var1 = round(doubleField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sum(numberField)", - "error": [ - "EVAL does not support function sum" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, longField) by round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sum(numberField) > 0", - "error": [ - "EVAL does not support function sum" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, longField) by var1 = round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | eval sum(numberField)", - "error": [ - "EVAL does not support function sum" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, longField) by round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | eval sum(numberField) > 0", - "error": [ - "EVAL does not support function sum" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | stats sum(booleanField)", - "error": [ - "Argument of [sum] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | stats var = weighted_avg(doubleField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats sum(null)", + "query": "from a_index | stats weighted_avg(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats sum(nullVar)", + "query": "from a_index | stats var = round(weighted_avg(doubleField, integerField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = median(numberField)", + "query": "from a_index | stats round(weighted_avg(doubleField, integerField))", "error": [], "warning": [] }, { - "query": "from a_index | stats median(numberField)", + "query": "from a_index | stats var = round(weighted_avg(doubleField, integerField)) + weighted_avg(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(median(numberField))", + "query": "from a_index | stats round(weighted_avg(doubleField, integerField)) + weighted_avg(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median(numberField))", + "query": "from a_index | stats weighted_avg(doubleField / 2, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(median(numberField)) + median(numberField)", + "query": "from a_index | stats var0 = weighted_avg(doubleField / 2, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median(numberField)) + median(numberField)", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField / 2, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats median(numberField / 2)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField / 2, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median(numberField / 2)", + "query": "from a_index | stats var0 = weighted_avg(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField / 2)", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField / 2)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median(numberField)", + "query": "from a_index | stats weighted_avg(doubleField, integerField) by round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField)", + "query": "from a_index | stats var0 = weighted_avg(doubleField, integerField) by var1 = round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField)", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, integerField) by round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats median(numberField) by round(numberField / 2)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, integerField) by var1 = round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats avg(doubleField), weighted_avg(doubleField, integerField) by round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, integerField) by var1 = round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats var = weighted_avg(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats weighted_avg(longField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats var = round(weighted_avg(longField, doubleField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = median(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats round(weighted_avg(longField, doubleField))", + "error": [], "warning": [] }, { - "query": "from a_index | stats median(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats var = round(weighted_avg(longField, doubleField)) + weighted_avg(longField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats median(stringField)", - "error": [ - "Argument of [median] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | stats round(weighted_avg(longField, doubleField)) + weighted_avg(longField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = median(*)", - "error": [ - "Using wildcards (*) in median is not allowed" - ], + "query": "from a_index | stats var0 = weighted_avg(longField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort median(numberField)", - "error": [ - "SORT does not support function median" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | where median(numberField)", - "error": [ - "WHERE does not support function median" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | where median(numberField) > 0", - "error": [ - "WHERE does not support function median" - ], + "query": "from a_index | stats weighted_avg(longField, doubleField) by round(doubleField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = median(numberField)", - "error": [ - "EVAL does not support function median" - ], + "query": "from a_index | stats var0 = weighted_avg(longField, doubleField) by var1 = round(doubleField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = median(numberField) > 0", - "error": [ - "EVAL does not support function median" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, doubleField) by round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | eval median(numberField)", - "error": [ - "EVAL does not support function median" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, doubleField) by var1 = round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | eval median(numberField) > 0", - "error": [ - "EVAL does not support function median" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, doubleField) by round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | stats median(booleanField)", - "error": [ - "Argument of [median] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | stats median(null)", + "query": "from a_index | stats var = weighted_avg(longField, longField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats median(nullVar)", + "query": "from a_index | stats weighted_avg(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = median_absolute_deviation(numberField)", + "query": "from a_index | stats var = round(weighted_avg(longField, longField))", "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(numberField)", + "query": "from a_index | stats round(weighted_avg(longField, longField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(median_absolute_deviation(numberField))", + "query": "from a_index | stats var = round(weighted_avg(longField, longField)) + weighted_avg(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median_absolute_deviation(numberField))", + "query": "from a_index | stats round(weighted_avg(longField, longField)) + weighted_avg(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)", + "query": "from a_index | stats var0 = weighted_avg(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)", + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats weighted_avg(longField, longField) by round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats var0 = weighted_avg(longField, longField) by var1 = round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, longField) by round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median_absolute_deviation(numberField)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, longField) by var1 = round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField)", + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, longField) by round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, longField) by var1 = round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(numberField) by round(numberField / 2)", + "query": "from a_index | stats var = weighted_avg(longField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats weighted_avg(longField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats var = round(weighted_avg(longField, integerField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats round(weighted_avg(longField, integerField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats var = round(weighted_avg(longField, integerField)) + weighted_avg(longField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats round(weighted_avg(longField, integerField)) + weighted_avg(longField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = median_absolute_deviation(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats var0 = weighted_avg(longField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(stringField)", - "error": [ - "Argument of [median_absolute_deviation] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = median_absolute_deviation(*)", - "error": [ - "Using wildcards (*) in median_absolute_deviation is not allowed" - ], + "query": "from a_index | stats weighted_avg(longField, integerField) by round(doubleField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | sort median_absolute_deviation(numberField)", - "error": [ - "SORT does not support function median_absolute_deviation" - ], + "query": "from a_index | stats var0 = weighted_avg(longField, integerField) by var1 = round(doubleField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | where median_absolute_deviation(numberField)", - "error": [ - "WHERE does not support function median_absolute_deviation" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, integerField) by round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | where median_absolute_deviation(numberField) > 0", - "error": [ - "WHERE does not support function median_absolute_deviation" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, integerField) by var1 = round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = median_absolute_deviation(numberField)", - "error": [ - "EVAL does not support function median_absolute_deviation" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(longField, integerField) by round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = median_absolute_deviation(numberField) > 0", - "error": [ - "EVAL does not support function median_absolute_deviation" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(longField, integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | eval median_absolute_deviation(numberField)", - "error": [ - "EVAL does not support function median_absolute_deviation" - ], + "query": "from a_index | stats var = weighted_avg(integerField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval median_absolute_deviation(numberField) > 0", - "error": [ - "EVAL does not support function median_absolute_deviation" - ], + "query": "from a_index | stats weighted_avg(integerField, doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(booleanField)", - "error": [ - "Argument of [median_absolute_deviation] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | stats var = round(weighted_avg(integerField, doubleField))", + "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(null)", + "query": "from a_index | stats round(weighted_avg(integerField, doubleField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats median_absolute_deviation(nullVar)", + "query": "from a_index | stats var = round(weighted_avg(integerField, doubleField)) + weighted_avg(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = percentile(numberField, 5)", + "query": "from a_index | stats round(weighted_avg(integerField, doubleField)) + weighted_avg(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(numberField, 5)", + "query": "from a_index | stats var0 = weighted_avg(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(percentile(numberField, 5))", + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(percentile(numberField, 5))", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(percentile(numberField, 5)) + percentile(numberField, 5)", + "query": "from a_index | stats weighted_avg(integerField, doubleField) by round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(percentile(numberField, 5)) + percentile(numberField, 5)", + "query": "from a_index | stats var0 = weighted_avg(integerField, doubleField) by var1 = round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(numberField, numberField)", - "error": [ - "Argument of [percentile] must be a constant, received [numberField]" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, doubleField) by round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(numberField / 2, 5)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, doubleField) by var1 = round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = percentile(numberField / 2, 5)", + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, doubleField) by round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField / 2, 5)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, doubleField) by var1 = round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField / 2, 5)", + "query": "from a_index | stats var = weighted_avg(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = percentile(numberField, 5)", + "query": "from a_index | stats weighted_avg(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 5)", + "query": "from a_index | stats var = round(weighted_avg(integerField, longField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5)", + "query": "from a_index | stats round(weighted_avg(integerField, longField))", "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(numberField, 5) by round(numberField / 2)", + "query": "from a_index | stats var = round(weighted_avg(integerField, longField)) + weighted_avg(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = percentile(numberField, 5) by var1 = round(numberField / 2)", + "query": "from a_index | stats round(weighted_avg(integerField, longField)) + weighted_avg(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), ipField", + "query": "from a_index | stats var0 = weighted_avg(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats weighted_avg(integerField, longField) by round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = percentile(avg(numberField), 5)", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats var0 = weighted_avg(integerField, longField) by var1 = round(doubleField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(avg(numberField), 5)", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, longField) by round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(stringField, 5)", - "error": [ - "Argument of [percentile] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, longField) by var1 = round(doubleField / 2), ipField", + "error": [], "warning": [] }, { - "query": "from a_index | sort percentile(numberField, 5)", - "error": [ - "SORT does not support function percentile" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, longField) by round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | where percentile(numberField, 5)", - "error": [ - "WHERE does not support function percentile" - ], + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | where percentile(numberField, 5) > 0", - "error": [ - "WHERE does not support function percentile" - ], + "query": "from a_index | stats var = weighted_avg(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = percentile(numberField, 5)", - "error": [ - "EVAL does not support function percentile" - ], + "query": "from a_index | stats weighted_avg(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = percentile(numberField, 5) > 0", - "error": [ - "EVAL does not support function percentile" - ], + "query": "from a_index | stats var = round(weighted_avg(integerField, integerField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval percentile(numberField, 5)", - "error": [ - "EVAL does not support function percentile" - ], + "query": "from a_index | stats round(weighted_avg(integerField, integerField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval percentile(numberField, 5) > 0", - "error": [ - "EVAL does not support function percentile" - ], + "query": "from a_index | stats var = round(weighted_avg(integerField, integerField)) + weighted_avg(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(booleanField, 5)", - "error": [ - "Argument of [percentile] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | stats round(weighted_avg(integerField, integerField)) + weighted_avg(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats percentile(null, null)", + "query": "from a_index | stats var0 = weighted_avg(integerField, integerField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats percentile(nullVar, nullVar)", - "error": [ - "Argument of [percentile] must be a constant, received [nullVar]" - ], + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = max(numberField)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, integerField)", "error": [], "warning": [] }, { - "query": "from a_index | stats max(numberField)", + "query": "from a_index | stats weighted_avg(integerField, integerField) by round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(max(numberField))", + "query": "from a_index | stats var0 = weighted_avg(integerField, integerField) by var1 = round(doubleField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(max(numberField))", + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, integerField) by round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(max(numberField)) + max(numberField)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, integerField) by var1 = round(doubleField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats round(max(numberField)) + max(numberField)", + "query": "from a_index | stats avg(doubleField), weighted_avg(integerField, integerField) by round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats max(numberField / 2)", + "query": "from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, integerField) by var1 = round(doubleField / 2), doubleField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = max(numberField / 2)", - "error": [], + "query": "from a_index | where weighted_avg(doubleField, longField)", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField / 2)", - "error": [], + "query": "from a_index | where weighted_avg(doubleField, longField) > 0", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField / 2)", - "error": [], + "query": "from a_index | where weighted_avg(doubleField, integerField)", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats var0 = max(numberField)", - "error": [], + "query": "from a_index | where weighted_avg(doubleField, integerField) > 0", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField)", - "error": [], + "query": "from a_index | where weighted_avg(longField, doubleField)", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField)", - "error": [], + "query": "from a_index | where weighted_avg(longField, doubleField) > 0", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats max(numberField) by round(numberField / 2)", - "error": [], + "query": "from a_index | where weighted_avg(longField, longField)", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats var0 = max(numberField) by var1 = round(numberField / 2)", - "error": [], + "query": "from a_index | where weighted_avg(longField, longField) > 0", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), ipField", - "error": [], + "query": "from a_index | where weighted_avg(longField, integerField)", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), ipField", - "error": [], + "query": "from a_index | where weighted_avg(longField, integerField) > 0", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), numberField / 2", - "error": [], + "query": "from a_index | where weighted_avg(integerField, doubleField)", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), numberField / 2", - "error": [], + "query": "from a_index | where weighted_avg(integerField, doubleField) > 0", + "error": [ + "WHERE does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats var = max(avg(numberField))", + "query": "from a_index | where weighted_avg(integerField, longField)", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "WHERE does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats max(avg(numberField))", + "query": "from a_index | where weighted_avg(integerField, longField) > 0", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "WHERE does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats max(stringField)", + "query": "from a_index | where weighted_avg(integerField, integerField)", "error": [ - "Argument of [max] must be [number], found value [stringField] type [string]" + "WHERE does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats var = max(*)", + "query": "from a_index | where weighted_avg(integerField, integerField) > 0", "error": [ - "Using wildcards (*) in max is not allowed" + "WHERE does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats var = max(dateField)", - "error": [], + "query": "from a_index | eval var = weighted_avg(doubleField, longField)", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats max(dateField)", - "error": [], + "query": "from a_index | eval var = weighted_avg(doubleField, longField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats var = round(max(dateField))", - "error": [], + "query": "from a_index | eval weighted_avg(doubleField, longField)", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats round(max(dateField))", - "error": [], + "query": "from a_index | eval weighted_avg(doubleField, longField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats var = round(max(dateField)) + max(dateField)", - "error": [], + "query": "from a_index | eval var = weighted_avg(doubleField, integerField)", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats round(max(dateField)) + max(dateField)", - "error": [], + "query": "from a_index | eval var = weighted_avg(doubleField, integerField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | sort max(numberField)", + "query": "from a_index | eval weighted_avg(doubleField, integerField)", "error": [ - "SORT does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | where max(numberField)", + "query": "from a_index | eval weighted_avg(doubleField, integerField) > 0", "error": [ - "WHERE does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | where max(numberField) > 0", + "query": "from a_index | eval var = weighted_avg(longField, doubleField)", "error": [ - "WHERE does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | where max(dateField)", + "query": "from a_index | eval var = weighted_avg(longField, doubleField) > 0", "error": [ - "WHERE does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | where max(dateField) > 0", + "query": "from a_index | eval weighted_avg(longField, doubleField)", "error": [ - "WHERE does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = max(numberField)", + "query": "from a_index | eval weighted_avg(longField, doubleField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = max(numberField) > 0", + "query": "from a_index | eval var = weighted_avg(longField, longField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval max(numberField)", + "query": "from a_index | eval var = weighted_avg(longField, longField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval max(numberField) > 0", + "query": "from a_index | eval weighted_avg(longField, longField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = max(dateField)", + "query": "from a_index | eval weighted_avg(longField, longField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = max(dateField) > 0", + "query": "from a_index | eval var = weighted_avg(longField, integerField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval max(dateField)", + "query": "from a_index | eval var = weighted_avg(longField, integerField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval max(dateField) > 0", + "query": "from a_index | eval weighted_avg(longField, integerField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats max(booleanField)", - "error": [], + "query": "from a_index | eval weighted_avg(longField, integerField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats max(null)", - "error": [], + "query": "from a_index | eval var = weighted_avg(integerField, doubleField)", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "row nullVar = null | stats max(nullVar)", - "error": [], + "query": "from a_index | eval var = weighted_avg(integerField, doubleField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats max(\"2022\")", - "error": [], + "query": "from a_index | eval weighted_avg(integerField, doubleField)", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | stats max(concat(\"20\", \"22\"))", + "query": "from a_index | eval weighted_avg(integerField, doubleField) > 0", "error": [ - "Argument of [max] must be [number], found value [concat(\"20\",\"22\")] type [string]" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats max(cartesianPointField)", + "query": "from a_index | eval var = weighted_avg(integerField, longField)", "error": [ - "Argument of [max] must be [number], found value [cartesianPointField] type [cartesian_point]" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats var = max(booleanField)", - "error": [], + "query": "from a_index | eval var = weighted_avg(integerField, longField) > 0", + "error": [ + "EVAL does not support function weighted_avg" + ], "warning": [] }, { - "query": "from a_index | where max(booleanField)", + "query": "from a_index | eval weighted_avg(integerField, longField)", "error": [ - "WHERE does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | where max(booleanField) > 0", + "query": "from a_index | eval weighted_avg(integerField, longField) > 0", "error": [ - "WHERE does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = max(booleanField)", + "query": "from a_index | eval var = weighted_avg(integerField, integerField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval var = max(booleanField) > 0", + "query": "from a_index | eval var = weighted_avg(integerField, integerField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval max(booleanField)", + "query": "from a_index | eval weighted_avg(integerField, integerField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | eval max(booleanField) > 0", + "query": "from a_index | eval weighted_avg(integerField, integerField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function weighted_avg" ], "warning": [] }, { - "query": "from a_index | stats var = max(ipField)", + "query": "from a_index | stats by bucket(dateField, 1 year)", "error": [], "warning": [] }, { - "query": "from a_index | stats max(ipField)", + "query": "from a_index | stats by bin(dateField, 1 year)", "error": [], "warning": [] }, { - "query": "from a_index | where max(ipField)", + "query": "from a_index | stats by bucket(integerField, integerField)", "error": [ - "WHERE does not support function max" + "Argument of [bucket] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | where max(ipField) > 0", + "query": "from a_index | stats by bin(integerField, integerField)", "error": [ - "WHERE does not support function max" + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval var = max(ipField)", + "query": "from a_index | stats by bucket(dateField, integerField, textField, textField)", "error": [ - "EVAL does not support function max" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [textField]", + "Argument of [bucket] must be a constant, received [textField]" ], "warning": [] }, { - "query": "from a_index | eval var = max(ipField) > 0", + "query": "from a_index | stats by bin(dateField, integerField, textField, textField)", "error": [ - "EVAL does not support function max" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [textField]", + "Argument of [bin] must be a constant, received [textField]" ], "warning": [] }, { - "query": "from a_index | eval max(ipField)", + "query": "from a_index | stats by bucket(dateField, integerField, dateField, dateField)", "error": [ - "EVAL does not support function max" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [dateField]", + "Argument of [bucket] must be a constant, received [dateField]" ], "warning": [] }, { - "query": "from a_index | eval max(ipField) > 0", + "query": "from a_index | stats by bin(dateField, integerField, dateField, dateField)", "error": [ - "EVAL does not support function max" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [dateField]", + "Argument of [bin] must be a constant, received [dateField]" ], "warning": [] }, { - "query": "from a_index | stats var = min(numberField)", - "error": [], + "query": "from a_index | stats by bucket(dateField, integerField, textField, dateField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [textField]", + "Argument of [bucket] must be a constant, received [dateField]" + ], "warning": [] }, { - "query": "from a_index | stats min(numberField)", - "error": [], + "query": "from a_index | stats by bin(dateField, integerField, textField, dateField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [textField]", + "Argument of [bin] must be a constant, received [dateField]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(min(numberField))", - "error": [], + "query": "from a_index | stats by bucket(dateField, integerField, dateField, textField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [dateField]", + "Argument of [bucket] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "from a_index | stats round(min(numberField))", - "error": [], + "query": "from a_index | stats by bin(dateField, integerField, dateField, textField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [dateField]", + "Argument of [bin] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(min(numberField)) + min(numberField)", - "error": [], + "query": "from a_index | stats by bucket(integerField, integerField, integerField, integerField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats round(min(numberField)) + min(numberField)", - "error": [], + "query": "from a_index | stats by bin(integerField, integerField, integerField, integerField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats min(numberField / 2)", - "error": [], + "query": "from a_index | sort bucket(dateField, 1 year)", + "error": [ + "SORT does not support function bucket" + ], "warning": [] }, { - "query": "from a_index | stats var0 = min(numberField / 2)", + "query": "from a_index | stats bucket(null, null, null, null)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), min(numberField / 2)", - "error": [], + "query": "row nullVar = null | stats bucket(nullVar, nullVar, nullVar, nullVar)", + "error": [ + "Argument of [bucket] must be a constant, received [nullVar]", + "Argument of [bucket] must be a constant, received [nullVar]", + "Argument of [bucket] must be a constant, received [nullVar]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = min(numberField / 2)", + "query": "from a_index | stats bucket(\"2022\", 1 year)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = min(numberField)", - "error": [], + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 1 year)", + "error": [ + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), min(numberField)", - "error": [], + "query": "from a_index | stats bucket(\"2022\", integerField, textField, textField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [textField]", + "Argument of [bucket] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = min(numberField)", - "error": [], + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), integerField, textField, textField)", + "error": [ + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [textField]", + "Argument of [bucket] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "from a_index | stats min(numberField) by round(numberField / 2)", - "error": [], + "query": "from a_index | stats bucket(\"2022\", integerField, \"2022\", \"2022\")", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats var0 = min(numberField) by var1 = round(numberField / 2)", - "error": [], + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), integerField, concat(\"20\", \"22\"), concat(\"20\", \"22\"))", + "error": [ + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), ipField", - "error": [], + "query": "from a_index | stats bucket(\"2022\", integerField, textField, \"2022\")", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), ipField", - "error": [], + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), integerField, textField, concat(\"20\", \"22\"))", + "error": [ + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [textField]", + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), numberField / 2", - "error": [], + "query": "from a_index | stats bucket(\"2022\", integerField, \"2022\", textField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), numberField / 2", - "error": [], + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), integerField, concat(\"20\", \"22\"), textField)", + "error": [ + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [keyword]", + "Argument of [bucket] must be a constant, received [textField]" + ], "warning": [] }, { - "query": "from a_index | stats var = min(avg(numberField))", + "query": "from a_index | stats by bucket(dateField, integerField, now(), now())", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "Argument of [bucket] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | stats min(avg(numberField))", + "query": "from a_index | stats by bin(dateField, integerField, now(), now())", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | stats min(stringField)", + "query": "from a_index | stats by bucket(doubleField, doubleField)", "error": [ - "Argument of [min] must be [number], found value [stringField] type [string]" + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | stats var = min(*)", + "query": "from a_index | stats by bin(doubleField, doubleField)", "error": [ - "Using wildcards (*) in min is not allowed" + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | stats var = min(dateField)", - "error": [], + "query": "from a_index | stats by bucket(doubleField, integerField, doubleField, doubleField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats min(dateField)", - "error": [], + "query": "from a_index | stats by bin(doubleField, integerField, doubleField, doubleField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(min(dateField))", - "error": [], + "query": "from a_index | stats by bucket(doubleField, integerField, doubleField, integerField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats round(min(dateField))", - "error": [], + "query": "from a_index | stats by bin(doubleField, integerField, doubleField, integerField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(min(dateField)) + min(dateField)", - "error": [], + "query": "from a_index | stats by bucket(doubleField, integerField, doubleField, longField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | stats round(min(dateField)) + min(dateField)", - "error": [], + "query": "from a_index | stats by bin(doubleField, integerField, doubleField, longField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | sort min(numberField)", + "query": "from a_index | stats by bucket(doubleField, integerField, integerField, doubleField)", "error": [ - "SORT does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | where min(numberField)", + "query": "from a_index | stats by bin(doubleField, integerField, integerField, doubleField)", "error": [ - "WHERE does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | where min(numberField) > 0", + "query": "from a_index | stats by bucket(doubleField, integerField, integerField, integerField)", "error": [ - "WHERE does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | where min(dateField)", + "query": "from a_index | stats by bin(doubleField, integerField, integerField, integerField)", "error": [ - "WHERE does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | where min(dateField) > 0", + "query": "from a_index | stats by bucket(doubleField, integerField, integerField, longField)", "error": [ - "WHERE does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(numberField)", + "query": "from a_index | stats by bin(doubleField, integerField, integerField, longField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(numberField) > 0", + "query": "from a_index | stats by bucket(doubleField, integerField, longField, doubleField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval min(numberField)", + "query": "from a_index | stats by bin(doubleField, integerField, longField, doubleField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval min(numberField) > 0", + "query": "from a_index | stats by bucket(doubleField, integerField, longField, integerField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(dateField)", + "query": "from a_index | stats by bin(doubleField, integerField, longField, integerField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(dateField) > 0", + "query": "from a_index | stats by bucket(doubleField, integerField, longField, longField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval min(dateField)", + "query": "from a_index | stats by bin(doubleField, integerField, longField, longField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval min(dateField) > 0", + "query": "from a_index | stats by bucket(integerField, doubleField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | stats min(booleanField)", - "error": [], + "query": "from a_index | stats by bin(integerField, doubleField)", + "error": [ + "Argument of [bin] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats min(null)", - "error": [], + "query": "from a_index | stats by bucket(integerField, integerField, doubleField, doubleField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "row nullVar = null | stats min(nullVar)", - "error": [], + "query": "from a_index | stats by bin(integerField, integerField, doubleField, doubleField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats min(\"2022\")", - "error": [], + "query": "from a_index | stats by bucket(integerField, integerField, doubleField, integerField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats min(concat(\"20\", \"22\"))", + "query": "from a_index | stats by bin(integerField, integerField, doubleField, integerField)", "error": [ - "Argument of [min] must be [number], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | stats min(cartesianPointField)", + "query": "from a_index | stats by bucket(integerField, integerField, doubleField, longField)", "error": [ - "Argument of [min] must be [number], found value [cartesianPointField] type [cartesian_point]" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | stats var = min(booleanField)", - "error": [], + "query": "from a_index | stats by bin(integerField, integerField, doubleField, longField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | where min(booleanField)", + "query": "from a_index | stats by bucket(integerField, integerField, integerField, doubleField)", "error": [ - "WHERE does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | where min(booleanField) > 0", + "query": "from a_index | stats by bin(integerField, integerField, integerField, doubleField)", "error": [ - "WHERE does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(booleanField)", + "query": "from a_index | stats by bucket(integerField, integerField, integerField, longField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]" + ], + "warning": [] + }, + { + "query": "from a_index | stats by bin(integerField, integerField, integerField, longField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]" + ], + "warning": [] + }, + { + "query": "from a_index | stats by bucket(integerField, integerField, longField, doubleField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(booleanField) > 0", + "query": "from a_index | stats by bin(integerField, integerField, longField, doubleField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval min(booleanField)", + "query": "from a_index | stats by bucket(integerField, integerField, longField, integerField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval min(booleanField) > 0", + "query": "from a_index | stats by bin(integerField, integerField, longField, integerField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | stats var = min(ipField)", - "error": [], + "query": "from a_index | stats by bucket(integerField, integerField, longField, longField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | stats min(ipField)", - "error": [], + "query": "from a_index | stats by bin(integerField, integerField, longField, longField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | where min(ipField)", + "query": "from a_index | stats by bucket(longField, doubleField)", "error": [ - "WHERE does not support function min" + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | where min(ipField) > 0", + "query": "from a_index | stats by bin(longField, doubleField)", "error": [ - "WHERE does not support function min" + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(ipField)", + "query": "from a_index | stats by bucket(longField, integerField, doubleField, doubleField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval var = min(ipField) > 0", + "query": "from a_index | stats by bin(longField, integerField, doubleField, doubleField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval min(ipField)", + "query": "from a_index | stats by bucket(longField, integerField, doubleField, integerField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval min(ipField) > 0", + "query": "from a_index | stats by bin(longField, integerField, doubleField, integerField)", "error": [ - "EVAL does not support function min" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | stats var = count(stringField)", - "error": [], + "query": "from a_index | stats by bucket(longField, integerField, doubleField, longField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]", + "Argument of [bucket] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | stats count(stringField)", - "error": [], + "query": "from a_index | stats by bin(longField, integerField, doubleField, longField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]", + "Argument of [bin] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(count(stringField))", - "error": [], + "query": "from a_index | stats by bucket(longField, integerField, integerField, doubleField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats round(count(stringField))", - "error": [], + "query": "from a_index | stats by bin(longField, integerField, integerField, doubleField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(count(stringField)) + count(stringField)", - "error": [], + "query": "from a_index | stats by bucket(longField, integerField, integerField, integerField)", + "error": [ + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats round(count(stringField)) + count(stringField)", - "error": [], + "query": "from a_index | stats by bin(longField, integerField, integerField, integerField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | sort count(stringField)", + "query": "from a_index | stats by bucket(longField, integerField, integerField, longField)", "error": [ - "SORT does not support function count" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | where count(stringField)", + "query": "from a_index | stats by bin(longField, integerField, integerField, longField)", "error": [ - "WHERE does not support function count" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | where count(stringField) > 0", + "query": "from a_index | stats by bucket(longField, integerField, longField, doubleField)", "error": [ - "WHERE does not support function count" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval var = count(stringField)", + "query": "from a_index | stats by bin(longField, integerField, longField, doubleField)", "error": [ - "EVAL does not support function count" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval var = count(stringField) > 0", + "query": "from a_index | stats by bucket(longField, integerField, longField, integerField)", "error": [ - "EVAL does not support function count" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval count(stringField)", + "query": "from a_index | stats by bin(longField, integerField, longField, integerField)", "error": [ - "EVAL does not support function count" + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval count(stringField) > 0", + "query": "from a_index | stats by bucket(longField, integerField, longField, longField)", "error": [ - "EVAL does not support function count" + "Argument of [bucket] must be a constant, received [integerField]", + "Argument of [bucket] must be a constant, received [longField]", + "Argument of [bucket] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | stats count(null)", - "error": [], + "query": "from a_index | stats by bin(longField, integerField, longField, longField)", + "error": [ + "Argument of [bin] must be a constant, received [integerField]", + "Argument of [bin] must be a constant, received [longField]", + "Argument of [bin] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "row nullVar = null | stats count(nullVar)", - "error": [], + "query": "from a_index | stats var = percentile(doubleField, doubleField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats var = count_distinct(stringField, numberField)", - "error": [], + "query": "from a_index | stats percentile(doubleField, doubleField)", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats count_distinct(stringField, numberField)", - "error": [], + "query": "from a_index | stats var = round(percentile(doubleField, doubleField))", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(doubleField,doubleField))]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(count_distinct(stringField, numberField))", - "error": [], + "query": "from a_index | stats round(percentile(doubleField, doubleField))", + "error": [ + "Argument of [round] must be a constant, received [percentile(doubleField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats round(count_distinct(stringField, numberField))", - "error": [], + "query": "from a_index | stats var = round(percentile(doubleField, doubleField)) + percentile(doubleField, doubleField)", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(doubleField,doubleField))+percentile(doubleField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats var = round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)", - "error": [], + "query": "from a_index | stats round(percentile(doubleField, doubleField)) + percentile(doubleField, doubleField)", + "error": [ + "Argument of [+] must be a constant, received [round(percentile(doubleField,doubleField))]", + "Argument of [+] must be a constant, received [percentile(doubleField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)", - "error": [], + "query": "from a_index | stats percentile(doubleField / 2, doubleField)", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | sort count_distinct(stringField, numberField)", + "query": "from a_index | stats var0 = percentile(doubleField / 2, doubleField)", "error": [ - "SORT does not support function count_distinct" + "Argument of [=] must be a constant, received [percentile(doubleField/2,doubleField)]" ], "warning": [] }, { - "query": "from a_index | where count_distinct(stringField, numberField)", + "query": "from a_index | stats avg(doubleField), percentile(doubleField / 2, doubleField)", "error": [ - "WHERE does not support function count_distinct" + "Argument of [percentile] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | where count_distinct(stringField, numberField) > 0", + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField / 2, doubleField)", "error": [ - "WHERE does not support function count_distinct" + "Argument of [=] must be a constant, received [percentile(doubleField/2,doubleField)]" ], "warning": [] }, { - "query": "from a_index | eval var = count_distinct(stringField, numberField)", + "query": "from a_index | stats var0 = percentile(doubleField, doubleField)", "error": [ - "EVAL does not support function count_distinct" + "Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | eval var = count_distinct(stringField, numberField) > 0", + "query": "from a_index | stats avg(doubleField), percentile(doubleField, doubleField)", "error": [ - "EVAL does not support function count_distinct" + "Argument of [percentile] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | eval count_distinct(stringField, numberField)", + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, doubleField)", "error": [ - "EVAL does not support function count_distinct" + "Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | eval count_distinct(stringField, numberField) > 0", + "query": "from a_index | stats percentile(doubleField, doubleField) by round(doubleField / 2)", "error": [ - "EVAL does not support function count_distinct" + "Argument of [percentile] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | stats count_distinct(null, null)", - "error": [], + "query": "from a_index | stats var0 = percentile(doubleField, doubleField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]" + ], "warning": [] }, { - "query": "row nullVar = null | stats count_distinct(nullVar, nullVar)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(doubleField, doubleField) by round(doubleField / 2), ipField", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(cartesianPointField)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, doubleField) by var1 = round(doubleField / 2), ipField", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(cartesianPointField)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(doubleField, doubleField) by round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(avg(numberField))", + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, doubleField) by var1 = round(doubleField / 2), doubleField / 2", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(avg(numberField))", + "query": "from a_index | stats var = percentile(avg(integerField), avg(integerField))", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "Argument of [=] must be a constant, received [percentile(avg(integerField),avg(integerField))]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" ], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(stringField)", + "query": "from a_index | stats percentile(avg(integerField), avg(integerField))", "error": [ - "Argument of [st_centroid_agg] must be [cartesian_point], found value [stringField] type [string]" + "Argument of [percentile] must be a constant, received [avg(integerField)]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]" ], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(*)", + "query": "from a_index | stats percentile(booleanField, )", "error": [ - "Using wildcards (*) in st_centroid_agg is not allowed" + "SyntaxError: no viable alternative at input 'percentile(booleanField, )'", + "SyntaxError: mismatched input ')' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "At least one aggregation or grouping expression required in [STATS]" ], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(geoPointField)", - "error": [], + "query": "from a_index | stats var = percentile(doubleField, longField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,longField)]" + ], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(geoPointField)", - "error": [], + "query": "from a_index | stats percentile(doubleField, longField)", + "error": [ + "Argument of [percentile] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | sort st_centroid_agg(cartesianPointField)", + "query": "from a_index | stats var = round(percentile(doubleField, longField))", "error": [ - "SORT does not support function st_centroid_agg" + "Argument of [=] must be a constant, received [round(percentile(doubleField,longField))]" ], "warning": [] }, { - "query": "from a_index | where st_centroid_agg(cartesianPointField)", + "query": "from a_index | stats round(percentile(doubleField, longField))", "error": [ - "WHERE does not support function st_centroid_agg" + "Argument of [round] must be a constant, received [percentile(doubleField,longField)]" ], "warning": [] }, { - "query": "from a_index | where st_centroid_agg(cartesianPointField) > 0", + "query": "from a_index | stats var = round(percentile(doubleField, longField)) + percentile(doubleField, longField)", "error": [ - "WHERE does not support function st_centroid_agg" + "Argument of [=] must be a constant, received [round(percentile(doubleField,longField))+percentile(doubleField,longField)]" ], "warning": [] }, { - "query": "from a_index | where st_centroid_agg(geoPointField)", + "query": "from a_index | stats round(percentile(doubleField, longField)) + percentile(doubleField, longField)", "error": [ - "WHERE does not support function st_centroid_agg" + "Argument of [+] must be a constant, received [round(percentile(doubleField,longField))]", + "Argument of [+] must be a constant, received [percentile(doubleField,longField)]" ], "warning": [] }, { - "query": "from a_index | where st_centroid_agg(geoPointField) > 0", + "query": "from a_index | stats percentile(doubleField / 2, longField)", "error": [ - "WHERE does not support function st_centroid_agg" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_centroid_agg(cartesianPointField)", + "query": "from a_index | stats var0 = percentile(doubleField / 2, longField)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [=] must be a constant, received [percentile(doubleField/2,longField)]" ], "warning": [] }, { - "query": "from a_index | eval var = st_centroid_agg(cartesianPointField) > 0", + "query": "from a_index | stats avg(doubleField), percentile(doubleField / 2, longField)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval st_centroid_agg(cartesianPointField)", + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField / 2, longField)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [=] must be a constant, received [percentile(doubleField/2,longField)]" ], "warning": [] }, { - "query": "from a_index | eval st_centroid_agg(cartesianPointField) > 0", + "query": "from a_index | stats var0 = percentile(doubleField, longField)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [=] must be a constant, received [percentile(doubleField,longField)]" ], "warning": [] }, { - "query": "from a_index | eval var = st_centroid_agg(geoPointField)", + "query": "from a_index | stats avg(doubleField), percentile(doubleField, longField)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval var = st_centroid_agg(geoPointField) > 0", + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, longField)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [=] must be a constant, received [percentile(doubleField,longField)]" ], "warning": [] }, { - "query": "from a_index | eval st_centroid_agg(geoPointField)", + "query": "from a_index | stats percentile(doubleField, longField) by round(doubleField / 2)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval st_centroid_agg(geoPointField) > 0", + "query": "from a_index | stats var0 = percentile(doubleField, longField) by var1 = round(doubleField / 2)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [=] must be a constant, received [percentile(doubleField,longField)]" ], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(booleanField)", + "query": "from a_index | stats avg(doubleField), percentile(doubleField, longField) by round(doubleField / 2), ipField", "error": [ - "Argument of [st_centroid_agg] must be [cartesian_point], found value [booleanField] type [boolean]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(null)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, longField) by var1 = round(doubleField / 2), ipField", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,longField)]" + ], "warning": [] }, { - "query": "row nullVar = null | stats st_centroid_agg(nullVar)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(doubleField, longField) by round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [percentile] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | stats var = values(stringField)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,longField)]" + ], "warning": [] }, { - "query": "from a_index | stats values(stringField)", - "error": [], + "query": "from a_index | stats var = percentile(doubleField, integerField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | sort values(stringField)", + "query": "from a_index | stats percentile(doubleField, integerField)", "error": [ - "SORT does not support function values" + "Argument of [percentile] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | where values(stringField)", + "query": "from a_index | stats var = round(percentile(doubleField, integerField))", "error": [ - "WHERE does not support function values" + "Argument of [=] must be a constant, received [round(percentile(doubleField,integerField))]" ], "warning": [] }, { - "query": "from a_index | where values(stringField) > 0", + "query": "from a_index | stats round(percentile(doubleField, integerField))", "error": [ - "WHERE does not support function values" + "Argument of [round] must be a constant, received [percentile(doubleField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval var = values(stringField)", + "query": "from a_index | stats var = round(percentile(doubleField, integerField)) + percentile(doubleField, integerField)", "error": [ - "EVAL does not support function values" + "Argument of [=] must be a constant, received [round(percentile(doubleField,integerField))+percentile(doubleField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval var = values(stringField) > 0", + "query": "from a_index | stats round(percentile(doubleField, integerField)) + percentile(doubleField, integerField)", "error": [ - "EVAL does not support function values" + "Argument of [+] must be a constant, received [round(percentile(doubleField,integerField))]", + "Argument of [+] must be a constant, received [percentile(doubleField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval values(stringField)", + "query": "from a_index | stats percentile(doubleField / 2, integerField)", "error": [ - "EVAL does not support function values" + "Argument of [percentile] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval values(stringField) > 0", + "query": "from a_index | stats var0 = percentile(doubleField / 2, integerField)", "error": [ - "EVAL does not support function values" + "Argument of [=] must be a constant, received [percentile(doubleField/2,integerField)]" ], "warning": [] }, { - "query": "from a_index | stats values(null)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(doubleField / 2, integerField)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "row nullVar = null | stats values(nullVar)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField / 2, integerField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField/2,integerField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 1 year)", - "error": [], + "query": "from a_index | stats var0 = percentile(doubleField, integerField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 1 year)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(doubleField, integerField)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, 5)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, integerField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, numberField)", + "query": "from a_index | stats percentile(doubleField, integerField) by round(doubleField / 2)", "error": [ - "Argument of [bucket] must be a constant, received [numberField]" + "Argument of [percentile] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | stats by bin(numberField, 5)", - "error": [], + "query": "from a_index | stats var0 = percentile(doubleField, integerField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, \"a\", \"a\")", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(doubleField, integerField) by round(doubleField / 2), ipField", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, stringField, stringField)", + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, integerField) by var1 = round(doubleField / 2), ipField", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [stringField]", - "Argument of [bucket] must be a constant, received [stringField]" + "Argument of [=] must be a constant, received [percentile(doubleField,integerField)]" ], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, \"a\", \"a\")", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(doubleField, integerField) by round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, now(), now())", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(doubleField, integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [=] must be a constant, received [percentile(doubleField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, dateField, dateField)", + "query": "from a_index | stats var = percentile(longField, doubleField)", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [dateField]", - "Argument of [bucket] must be a constant, received [dateField]" + "Argument of [=] must be a constant, received [percentile(longField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, now(), now())", - "error": [], + "query": "from a_index | stats percentile(longField, doubleField)", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, \"a\", now())", - "error": [], + "query": "from a_index | stats var = round(percentile(longField, doubleField))", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(longField,doubleField))]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, stringField, dateField)", + "query": "from a_index | stats round(percentile(longField, doubleField))", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [stringField]", - "Argument of [bucket] must be a constant, received [dateField]" + "Argument of [round] must be a constant, received [percentile(longField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, \"a\", now())", - "error": [], + "query": "from a_index | stats var = round(percentile(longField, doubleField)) + percentile(longField, doubleField)", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(longField,doubleField))+percentile(longField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, now(), \"a\")", - "error": [], + "query": "from a_index | stats round(percentile(longField, doubleField)) + percentile(longField, doubleField)", + "error": [ + "Argument of [+] must be a constant, received [round(percentile(longField,doubleField))]", + "Argument of [+] must be a constant, received [percentile(longField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, dateField, stringField)", + "query": "from a_index | stats var0 = percentile(longField, doubleField)", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [dateField]", - "Argument of [bucket] must be a constant, received [stringField]" + "Argument of [=] must be a constant, received [percentile(longField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, now(), \"a\")", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(longField, doubleField)", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, 5, 5, 5)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, doubleField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, numberField, numberField, numberField)", + "query": "from a_index | stats percentile(longField, doubleField) by round(doubleField / 2)", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [numberField]" + "Argument of [percentile] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | stats by bin(numberField, 5, 5, 5)", - "error": [], + "query": "from a_index | stats var0 = percentile(longField, doubleField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | sort bucket(dateField, 1 year)", + "query": "from a_index | stats avg(doubleField), percentile(longField, doubleField) by round(doubleField / 2), ipField", "error": [ - "SORT does not support function bucket" + "Argument of [percentile] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | stats bucket(null, null, null, null)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, doubleField) by var1 = round(doubleField / 2), ipField", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,doubleField)]" + ], "warning": [] }, { - "query": "row nullVar = null | stats bucket(nullVar, nullVar, nullVar, nullVar)", + "query": "from a_index | stats avg(doubleField), percentile(longField, doubleField) by round(doubleField / 2), doubleField / 2", "error": [ - "Argument of [bucket] must be a constant, received [nullVar]", - "Argument of [bucket] must be a constant, received [nullVar]", - "Argument of [bucket] must be a constant, received [nullVar]" + "Argument of [percentile] must be a constant, received [doubleField]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 1 year)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 1 year)", + "query": "from a_index | stats var = percentile(longField, longField)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [=] must be a constant, received [percentile(longField,longField)]" ], "warning": [] }, { - "query": "from a_index | stats by bucket(concat(\"20\", \"22\"), 1 year)", + "query": "from a_index | stats percentile(longField, longField)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"a\", \"a\")", - "error": [], + "query": "from a_index | stats var = round(percentile(longField, longField))", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(longField,longField))]" + ], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, \"a\", \"a\")", + "query": "from a_index | stats round(percentile(longField, longField))", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [round] must be a constant, received [percentile(longField,longField)]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"2022\", \"2022\")", - "error": [], + "query": "from a_index | stats var = round(percentile(longField, longField)) + percentile(longField, longField)", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(longField,longField))+percentile(longField,longField)]" + ], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, concat(\"20\", \"22\"), concat(\"20\", \"22\"))", + "query": "from a_index | stats round(percentile(longField, longField)) + percentile(longField, longField)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [+] must be a constant, received [round(percentile(longField,longField))]", + "Argument of [+] must be a constant, received [percentile(longField,longField)]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"a\", \"2022\")", - "error": [], + "query": "from a_index | stats var0 = percentile(longField, longField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,longField)]" + ], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, \"a\", concat(\"20\", \"22\"))", + "query": "from a_index | stats avg(doubleField), percentile(longField, longField)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"2022\", \"a\")", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, longField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,longField)]" + ], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, concat(\"20\", \"22\"), \"a\")", + "query": "from a_index | stats percentile(longField, longField) by round(doubleField / 2)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\",\"22\")] type [string]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "row var = cbrt(5)", - "error": [], + "query": "from a_index | stats var0 = percentile(longField, longField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,longField)]" + ], "warning": [] }, { - "query": "row cbrt(5)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(longField, longField) by round(doubleField / 2), ipField", + "error": [ + "Argument of [percentile] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "row var = cbrt(to_integer(true))", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, longField) by var1 = round(doubleField / 2), ipField", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,longField)]" + ], "warning": [] }, { - "query": "row var = cbrt(true)", + "query": "from a_index | stats avg(doubleField), percentile(longField, longField) by round(doubleField / 2), doubleField / 2", "error": [ - "Argument of [cbrt] must be [number], found value [true] type [boolean]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | where cbrt(numberField) > 0", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, longField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,longField)]" + ], "warning": [] }, { - "query": "from a_index | where cbrt(booleanField) > 0", + "query": "from a_index | stats var = percentile(longField, integerField)", "error": [ - "Argument of [cbrt] must be [number], found value [booleanField] type [boolean]" + "Argument of [=] must be a constant, received [percentile(longField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval var = cbrt(numberField)", - "error": [], + "query": "from a_index | stats percentile(longField, integerField)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | eval cbrt(numberField)", - "error": [], + "query": "from a_index | stats var = round(percentile(longField, integerField))", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(longField,integerField))]" + ], "warning": [] }, { - "query": "from a_index | eval var = cbrt(to_integer(booleanField))", - "error": [], + "query": "from a_index | stats round(percentile(longField, integerField))", + "error": [ + "Argument of [round] must be a constant, received [percentile(longField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | eval cbrt(booleanField)", + "query": "from a_index | stats var = round(percentile(longField, integerField)) + percentile(longField, integerField)", "error": [ - "Argument of [cbrt] must be [number], found value [booleanField] type [boolean]" + "Argument of [=] must be a constant, received [round(percentile(longField,integerField))+percentile(longField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval var = cbrt(*)", + "query": "from a_index | stats round(percentile(longField, integerField)) + percentile(longField, integerField)", "error": [ - "Using wildcards (*) in cbrt is not allowed" + "Argument of [+] must be a constant, received [round(percentile(longField,integerField))]", + "Argument of [+] must be a constant, received [percentile(longField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval cbrt(numberField, extraArg)", + "query": "from a_index | stats var0 = percentile(longField, integerField)", "error": [ - "Error: [cbrt] function expects exactly one argument, got 2." + "Argument of [=] must be a constant, received [percentile(longField,integerField)]" ], "warning": [] }, { - "query": "from a_index | sort cbrt(numberField)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(longField, integerField)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | eval cbrt(null)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, integerField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,integerField)]" + ], "warning": [] }, { - "query": "row nullVar = null | eval cbrt(nullVar)", - "error": [], + "query": "from a_index | stats percentile(longField, integerField) by round(doubleField / 2)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "row var = from_base64(\"a\")", - "error": [], + "query": "from a_index | stats var0 = percentile(longField, integerField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,integerField)]" + ], "warning": [] }, { - "query": "row from_base64(\"a\")", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(longField, integerField) by round(doubleField / 2), ipField", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "row var = from_base64(to_string(true))", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, integerField) by var1 = round(doubleField / 2), ipField", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,integerField)]" + ], "warning": [] }, { - "query": "row var = from_base64(true)", + "query": "from a_index | stats avg(doubleField), percentile(longField, integerField) by round(doubleField / 2), doubleField / 2", "error": [ - "Argument of [from_base64] must be [string], found value [true] type [boolean]" + "Argument of [percentile] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | where length(from_base64(stringField)) > 0", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(longField, integerField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [=] must be a constant, received [percentile(longField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | where length(from_base64(booleanField)) > 0", + "query": "from a_index | stats var = percentile(integerField, doubleField)", "error": [ - "Argument of [from_base64] must be [string], found value [booleanField] type [boolean]" + "Argument of [=] must be a constant, received [percentile(integerField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | eval var = from_base64(stringField)", - "error": [], + "query": "from a_index | stats percentile(integerField, doubleField)", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | eval from_base64(stringField)", - "error": [], + "query": "from a_index | stats var = round(percentile(integerField, doubleField))", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(integerField,doubleField))]" + ], "warning": [] }, { - "query": "from a_index | eval var = from_base64(to_string(booleanField))", - "error": [], + "query": "from a_index | stats round(percentile(integerField, doubleField))", + "error": [ + "Argument of [round] must be a constant, received [percentile(integerField,doubleField)]" + ], "warning": [] }, { - "query": "from a_index | eval from_base64(booleanField)", + "query": "from a_index | stats var = round(percentile(integerField, doubleField)) + percentile(integerField, doubleField)", "error": [ - "Argument of [from_base64] must be [string], found value [booleanField] type [boolean]" + "Argument of [=] must be a constant, received [round(percentile(integerField,doubleField))+percentile(integerField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | eval var = from_base64(*)", + "query": "from a_index | stats round(percentile(integerField, doubleField)) + percentile(integerField, doubleField)", "error": [ - "Using wildcards (*) in from_base64 is not allowed" + "Argument of [+] must be a constant, received [round(percentile(integerField,doubleField))]", + "Argument of [+] must be a constant, received [percentile(integerField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | eval from_base64(stringField, extraArg)", + "query": "from a_index | stats var0 = percentile(integerField, doubleField)", "error": [ - "Error: [from_base64] function expects exactly one argument, got 2." + "Argument of [=] must be a constant, received [percentile(integerField,doubleField)]" ], "warning": [] }, { - "query": "from a_index | sort from_base64(stringField)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(integerField, doubleField)", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "from a_index | eval from_base64(null)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, doubleField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,doubleField)]" + ], "warning": [] }, { - "query": "row nullVar = null | eval from_base64(nullVar)", - "error": [], + "query": "from a_index | stats percentile(integerField, doubleField) by round(doubleField / 2)", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "row var = locate(\"a\", \"a\")", - "error": [], + "query": "from a_index | stats var0 = percentile(integerField, doubleField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,doubleField)]" + ], "warning": [] }, { - "query": "row locate(\"a\", \"a\")", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(integerField, doubleField) by round(doubleField / 2), ipField", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "row var = locate(to_string(true), to_string(true))", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, doubleField) by var1 = round(doubleField / 2), ipField", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,doubleField)]" + ], "warning": [] }, { - "query": "row var = locate(\"a\", \"a\", 5)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(integerField, doubleField) by round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [percentile] must be a constant, received [doubleField]" + ], "warning": [] }, { - "query": "row locate(\"a\", \"a\", 5)", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, doubleField) by var1 = round(doubleField / 2), doubleField / 2", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,doubleField)]" + ], "warning": [] }, { - "query": "row var = locate(to_string(true), to_string(true), to_integer(true))", - "error": [], + "query": "from a_index | stats var = percentile(integerField, longField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,longField)]" + ], "warning": [] }, { - "query": "row var = locate(true, true, true)", + "query": "from a_index | stats percentile(integerField, longField)", "error": [ - "Argument of [locate] must be [string], found value [true] type [boolean]", - "Argument of [locate] must be [string], found value [true] type [boolean]", - "Argument of [locate] must be [number], found value [true] type [boolean]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | where locate(stringField, stringField) > 0", - "error": [], + "query": "from a_index | stats var = round(percentile(integerField, longField))", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(integerField,longField))]" + ], "warning": [] }, { - "query": "from a_index | where locate(booleanField, booleanField) > 0", + "query": "from a_index | stats round(percentile(integerField, longField))", "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]" + "Argument of [round] must be a constant, received [percentile(integerField,longField)]" ], "warning": [] }, { - "query": "from a_index | where locate(stringField, stringField, numberField) > 0", - "error": [], + "query": "from a_index | stats var = round(percentile(integerField, longField)) + percentile(integerField, longField)", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(integerField,longField))+percentile(integerField,longField)]" + ], "warning": [] }, { - "query": "from a_index | where locate(booleanField, booleanField, booleanField) > 0", + "query": "from a_index | stats round(percentile(integerField, longField)) + percentile(integerField, longField)", "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [number], found value [booleanField] type [boolean]" + "Argument of [+] must be a constant, received [round(percentile(integerField,longField))]", + "Argument of [+] must be a constant, received [percentile(integerField,longField)]" ], "warning": [] }, { - "query": "from a_index | eval var = locate(stringField, stringField)", - "error": [], + "query": "from a_index | stats var0 = percentile(integerField, longField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,longField)]" + ], "warning": [] }, { - "query": "from a_index | eval locate(stringField, stringField)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(integerField, longField)", + "error": [ + "Argument of [percentile] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField))", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, longField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,longField)]" + ], "warning": [] }, { - "query": "from a_index | eval locate(booleanField, booleanField)", + "query": "from a_index | stats percentile(integerField, longField) by round(doubleField / 2)", "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval var = locate(stringField, stringField, numberField)", - "error": [], + "query": "from a_index | stats var0 = percentile(integerField, longField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,longField)]" + ], "warning": [] }, { - "query": "from a_index | eval locate(stringField, stringField, numberField)", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(integerField, longField) by round(doubleField / 2), ipField", + "error": [ + "Argument of [percentile] must be a constant, received [longField]" + ], "warning": [] }, { - "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField), to_integer(booleanField))", - "error": [], + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, longField) by var1 = round(doubleField / 2), ipField", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,longField)]" + ], "warning": [] }, { - "query": "from a_index | eval locate(booleanField, booleanField, booleanField)", + "query": "from a_index | stats avg(doubleField), percentile(integerField, longField) by round(doubleField / 2), doubleField / 2", "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [number], found value [booleanField] type [boolean]" + "Argument of [percentile] must be a constant, received [longField]" ], "warning": [] }, { - "query": "from a_index | eval locate(stringField, stringField, numberField, extraArg)", + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, longField) by var1 = round(doubleField / 2), doubleField / 2", "error": [ - "Error: [locate] function expects no more than 3 arguments, got 4." + "Argument of [=] must be a constant, received [percentile(integerField,longField)]" ], "warning": [] }, { - "query": "from a_index | sort locate(stringField, stringField)", - "error": [], + "query": "from a_index | stats var = percentile(integerField, integerField)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | eval locate(null, null, null)", - "error": [], + "query": "from a_index | stats percentile(integerField, integerField)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "row nullVar = null | eval locate(nullVar, nullVar, nullVar)", - "error": [], + "query": "from a_index | stats var = round(percentile(integerField, integerField))", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(integerField,integerField))]" + ], "warning": [] }, { - "query": "row var = to_base64(\"a\")", - "error": [], + "query": "from a_index | stats round(percentile(integerField, integerField))", + "error": [ + "Argument of [round] must be a constant, received [percentile(integerField,integerField)]" + ], "warning": [] }, { - "query": "row to_base64(\"a\")", - "error": [], + "query": "from a_index | stats var = round(percentile(integerField, integerField)) + percentile(integerField, integerField)", + "error": [ + "Argument of [=] must be a constant, received [round(percentile(integerField,integerField))+percentile(integerField,integerField)]" + ], "warning": [] }, { - "query": "row var = to_base64(to_string(true))", - "error": [], + "query": "from a_index | stats round(percentile(integerField, integerField)) + percentile(integerField, integerField)", + "error": [ + "Argument of [+] must be a constant, received [round(percentile(integerField,integerField))]", + "Argument of [+] must be a constant, received [percentile(integerField,integerField)]" + ], "warning": [] }, { - "query": "row var = to_base64(true)", + "query": "from a_index | stats var0 = percentile(integerField, integerField)", "error": [ - "Argument of [to_base64] must be [string], found value [true] type [boolean]" + "Argument of [=] must be a constant, received [percentile(integerField,integerField)]" ], "warning": [] }, { - "query": "from a_index | where length(to_base64(stringField)) > 0", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(integerField, integerField)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | where length(to_base64(booleanField)) > 0", + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, integerField)", "error": [ - "Argument of [to_base64] must be [string], found value [booleanField] type [boolean]" + "Argument of [=] must be a constant, received [percentile(integerField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval var = to_base64(stringField)", - "error": [], + "query": "from a_index | stats percentile(integerField, integerField) by round(doubleField / 2)", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | eval to_base64(stringField)", - "error": [], + "query": "from a_index | stats var0 = percentile(integerField, integerField) by var1 = round(doubleField / 2)", + "error": [ + "Argument of [=] must be a constant, received [percentile(integerField,integerField)]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_base64(to_string(booleanField))", - "error": [], + "query": "from a_index | stats avg(doubleField), percentile(integerField, integerField) by round(doubleField / 2), ipField", + "error": [ + "Argument of [percentile] must be a constant, received [integerField]" + ], "warning": [] }, { - "query": "from a_index | eval to_base64(booleanField)", + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, integerField) by var1 = round(doubleField / 2), ipField", "error": [ - "Argument of [to_base64] must be [string], found value [booleanField] type [boolean]" + "Argument of [=] must be a constant, received [percentile(integerField,integerField)]" ], "warning": [] }, { - "query": "from a_index | eval var = to_base64(*)", + "query": "from a_index | stats avg(doubleField), percentile(integerField, integerField) by round(doubleField / 2), doubleField / 2", "error": [ - "Using wildcards (*) in to_base64 is not allowed" + "Argument of [percentile] must be a constant, received [integerField]" ], "warning": [] }, { - "query": "from a_index | eval to_base64(stringField, extraArg)", + "query": "from a_index | stats avg(doubleField), var0 = percentile(integerField, integerField) by var1 = round(doubleField / 2), doubleField / 2", "error": [ - "Error: [to_base64] function expects exactly one argument, got 2." + "Argument of [=] must be a constant, received [percentile(integerField,integerField)]" ], "warning": [] }, { - "query": "from a_index | sort to_base64(stringField)", - "error": [], + "query": "from a_index | sort percentile(doubleField, doubleField)", + "error": [ + "SORT does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval to_base64(null)", - "error": [], + "query": "from a_index | where percentile(doubleField, doubleField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row nullVar = null | eval to_base64(nullVar)", - "error": [], + "query": "from a_index | where percentile(doubleField, doubleField) > 0", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row var = ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", - "error": [], + "query": "from a_index | where percentile(doubleField, longField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", - "error": [], + "query": "from a_index | where percentile(doubleField, longField) > 0", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row var = ip_prefix(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", - "error": [], + "query": "from a_index | where percentile(doubleField, integerField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row var = ip_prefix(true, true, true)", + "query": "from a_index | where percentile(doubleField, integerField) > 0", "error": [ - "Argument of [ip_prefix] must be [ip], found value [true] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [true] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [true] type [boolean]" + "WHERE does not support function percentile" ], "warning": [] }, { - "query": "from a_index | eval var = ip_prefix(ipField, numberField, numberField)", - "error": [], + "query": "from a_index | where percentile(longField, doubleField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval ip_prefix(ipField, numberField, numberField)", - "error": [], + "query": "from a_index | where percentile(longField, doubleField) > 0", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval var = ip_prefix(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", - "error": [], + "query": "from a_index | where percentile(longField, longField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval ip_prefix(booleanField, booleanField, booleanField)", + "query": "from a_index | where percentile(longField, longField) > 0", "error": [ - "Argument of [ip_prefix] must be [ip], found value [booleanField] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]" + "WHERE does not support function percentile" ], "warning": [] }, { - "query": "from a_index | eval ip_prefix(ipField, numberField, numberField, extraArg)", + "query": "from a_index | where percentile(longField, integerField)", "error": [ - "Error: [ip_prefix] function expects exactly 3 arguments, got 4." + "WHERE does not support function percentile" ], "warning": [] }, { - "query": "from a_index | sort ip_prefix(ipField, numberField, numberField)", - "error": [], + "query": "from a_index | where percentile(longField, integerField) > 0", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval ip_prefix(null, null, null)", - "error": [], + "query": "from a_index | where percentile(integerField, doubleField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row nullVar = null | eval ip_prefix(nullVar, nullVar, nullVar)", - "error": [], + "query": "from a_index | where percentile(integerField, doubleField) > 0", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(true, true)", - "error": [], + "query": "from a_index | where percentile(integerField, longField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(true, true)", - "error": [], + "query": "from a_index | where percentile(integerField, longField) > 0", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_boolean(true), to_boolean(true))", - "error": [], + "query": "from a_index | where percentile(integerField, integerField)", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where percentile(integerField, integerField) > 0", + "error": [ + "WHERE does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval var = percentile(doubleField, doubleField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval var = percentile(doubleField, doubleField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval percentile(doubleField, doubleField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval percentile(doubleField, doubleField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval var = percentile(doubleField, longField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(now(), now())", - "error": [], + "query": "from a_index | eval var = percentile(doubleField, longField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(now(), now())", - "error": [], + "query": "from a_index | eval percentile(doubleField, longField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_datetime(now()), to_datetime(now()))", - "error": [], + "query": "from a_index | eval percentile(doubleField, longField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(5, 5)", - "error": [], + "query": "from a_index | eval var = percentile(doubleField, integerField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(5, 5)", - "error": [], + "query": "from a_index | eval var = percentile(doubleField, integerField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_integer(true), to_integer(true))", - "error": [], + "query": "from a_index | eval percentile(doubleField, integerField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval percentile(doubleField, integerField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval var = percentile(longField, doubleField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval var = percentile(longField, doubleField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval percentile(longField, doubleField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval percentile(longField, doubleField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval var = percentile(longField, longField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", - "error": [], + "query": "from a_index | eval var = percentile(longField, longField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", - "error": [], + "query": "from a_index | eval percentile(longField, longField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", - "error": [], + "query": "from a_index | eval percentile(longField, longField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(\"a\", \"a\")", - "error": [], + "query": "from a_index | eval var = percentile(longField, integerField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(\"a\", \"a\")", - "error": [], + "query": "from a_index | eval var = percentile(longField, integerField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_string(true), to_string(true))", - "error": [], + "query": "from a_index | eval percentile(longField, integerField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | eval percentile(longField, integerField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | eval var = percentile(integerField, doubleField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "row var = mv_append(to_version(\"a\"), to_version(\"a\"))", - "error": [], + "query": "from a_index | eval var = percentile(integerField, doubleField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | where mv_append(numberField, numberField) > 0", - "error": [], + "query": "from a_index | eval percentile(integerField, doubleField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | where length(mv_append(stringField, stringField)) > 0", - "error": [], + "query": "from a_index | eval percentile(integerField, doubleField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(booleanField, booleanField)", - "error": [], + "query": "from a_index | eval var = percentile(integerField, longField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(booleanField, booleanField)", - "error": [], + "query": "from a_index | eval var = percentile(integerField, longField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_boolean(booleanField), to_boolean(booleanField))", - "error": [], + "query": "from a_index | eval percentile(integerField, longField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(cartesianPointField, cartesianPointField)", - "error": [], + "query": "from a_index | eval percentile(integerField, longField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(cartesianPointField, cartesianPointField)", - "error": [], + "query": "from a_index | eval var = percentile(integerField, integerField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", - "error": [], + "query": "from a_index | eval var = percentile(integerField, integerField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(cartesianShapeField, cartesianShapeField)", - "error": [], + "query": "from a_index | eval percentile(integerField, integerField)", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(cartesianShapeField, cartesianShapeField)", - "error": [], + "query": "from a_index | eval percentile(integerField, integerField) > 0", + "error": [ + "EVAL does not support function percentile" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | stats percentile(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(dateField, dateField)", - "error": [], + "query": "row nullVar = null | stats percentile(nullVar, nullVar)", + "error": [ + "Argument of [percentile] must be a constant, received [nullVar]" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(dateField, dateField)", + "query": "row var = to_string(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_datetime(dateField), to_datetime(dateField))", + "query": "row to_string(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(numberField, numberField)", + "query": "row var = to_str(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(numberField, numberField)", + "query": "row var = to_string(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_integer(booleanField), to_integer(booleanField))", - "error": [], + "query": "row var = to_string(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(geoPointField, geoPointField)", - "error": [], + "query": "row to_string(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(geoPointField, geoPointField)", - "error": [], + "query": "row var = to_str(cartesianPointField)", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))", - "error": [], + "query": "row var = to_string(to_cartesianpoint(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(geoShapeField, geoShapeField)", + "query": "row var = to_string(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(geoShapeField, geoShapeField)", + "query": "row to_string(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "row var = to_str(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(ipField, ipField)", - "error": [], + "query": "row var = to_string(to_cartesianshape(cartesianPointField))", + "error": [ + "Unknown column [cartesianPointField]" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(ipField, ipField)", + "query": "row var = to_string(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_ip(ipField), to_ip(ipField))", + "query": "row to_string(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(stringField, stringField)", + "query": "row var = to_str(to_datetime(\"2021-01-01T00:00:00Z\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(stringField, stringField)", + "query": "row var = to_string(to_datetime(to_datetime(\"2021-01-01T00:00:00Z\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_string(booleanField), to_string(booleanField))", + "query": "row var = to_string(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(versionField, versionField)", + "query": "row to_string(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(versionField, versionField)", + "query": "row var = to_str(5.5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_version(stringField), to_version(stringField))", + "query": "row var = to_string(to_double(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(booleanField, booleanField, extraArg)", + "query": "row var = to_string(geoPointField)", "error": [ - "Error: [mv_append] function expects exactly 2 arguments, got 3." + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | sort mv_append(booleanField, booleanField)", - "error": [], + "query": "row to_string(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(null, null)", - "error": [], + "query": "row var = to_str(geoPointField)", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row nullVar = null | eval mv_append(nullVar, nullVar)", - "error": [], + "query": "row var = to_string(to_geopoint(geoPointField))", + "error": [ + "Unknown column [geoPointField]" + ], "warning": [] }, { - "query": "row var = repeat(\"a\", 5)", + "query": "row var = to_string(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row repeat(\"a\", 5)", + "query": "row to_string(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = repeat(to_string(true), to_integer(true))", + "query": "row var = to_str(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = repeat(true, true)", + "query": "row var = to_string(to_geoshape(geoPointField))", "error": [ - "Argument of [repeat] must be [string], found value [true] type [boolean]", - "Argument of [repeat] must be [number], found value [true] type [boolean]" + "Unknown column [geoPointField]" ], "warning": [] }, { - "query": "from a_index | where length(repeat(stringField, numberField)) > 0", + "query": "row var = to_string(5)", "error": [], "warning": [] }, { - "query": "from a_index | where length(repeat(booleanField, booleanField)) > 0", - "error": [ - "Argument of [repeat] must be [string], found value [booleanField] type [boolean]", - "Argument of [repeat] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row to_string(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = repeat(stringField, numberField)", + "query": "row var = to_str(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(stringField, numberField)", + "query": "row var = to_string(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = repeat(to_string(booleanField), to_integer(booleanField))", + "query": "row var = to_string(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(booleanField, booleanField)", - "error": [ - "Argument of [repeat] must be [string], found value [booleanField] type [boolean]", - "Argument of [repeat] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row to_string(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(stringField, numberField, extraArg)", - "error": [ - "Error: [repeat] function expects exactly 2 arguments, got 3." - ], + "query": "row var = to_str(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort repeat(stringField, numberField)", + "query": "row var = to_string(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(null, null)", + "query": "row var = to_string(\"a\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval repeat(nullVar, nullVar)", + "query": "row to_string(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, 3, \"asc\")", + "query": "row var = to_str(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats top(stringField, 1, \"desc\")", + "query": "row var = to_string(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, 5, \"asc\")", + "query": "row var = to_string(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | stats top(stringField, 5, \"asc\")", + "query": "row to_string(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, 3)", - "error": [ - "Error: [top] function expects exactly 3 arguments, got 2." - ], + "query": "row var = to_str(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField)", - "error": [ - "Error: [top] function expects exactly 3 arguments, got 1." - ], + "query": "row var = to_string(to_version(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, numberField, \"asc\")", - "error": [ - "Argument of [=] must be a constant, received [top(stringField,numberField,\"asc\")]" - ], + "query": "from a_index | eval var = to_string(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, 100 + numberField, \"asc\")", - "error": [ - "Argument of [=] must be a constant, received [top(stringField,100+numberField,\"asc\")]" - ], + "query": "from a_index | eval to_string(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, 1, stringField)", - "error": [ - "Argument of [=] must be a constant, received [top(stringField,1,stringField)]" - ], + "query": "from a_index | eval var = to_str(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, 1, \"asdf\")", + "query": "from a_index | eval var = to_string(to_boolean(booleanField))", "error": [], - "warning": [ - "Invalid option [\"asdf\"] for top. Supported options: [\"asc\", \"desc\"]." - ] - }, - { - "query": "from a_index | sort top(stringField, numberField, \"asc\")", - "error": [ - "SORT does not support function top" - ], "warning": [] }, { - "query": "from a_index | where top(stringField, numberField, \"asc\")", + "query": "from a_index | eval to_string(counterDoubleField)", "error": [ - "WHERE does not support function top" + "Argument of [to_string] must be [boolean], found value [counterDoubleField] type [counter_double]" ], "warning": [] }, { - "query": "from a_index | where top(stringField, numberField, \"asc\") > 0", + "query": "from a_index | eval var = to_string(*)", "error": [ - "WHERE does not support function top" + "Using wildcards (*) in to_string is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = top(stringField, numberField, \"asc\")", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval var = to_string(cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = top(stringField, numberField, \"asc\") > 0", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval to_string(cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval top(stringField, numberField, \"asc\")", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval var = to_str(cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval top(stringField, numberField, \"asc\") > 0", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval var = to_string(to_cartesianpoint(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort top(stringField, 5, \"asc\")", - "error": [ - "SORT does not support function top" - ], + "query": "from a_index | eval var = to_string(cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | where top(stringField, 5, \"asc\")", - "error": [ - "WHERE does not support function top" - ], + "query": "from a_index | eval to_string(cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | where top(stringField, 5, \"asc\") > 0", - "error": [ - "WHERE does not support function top" - ], + "query": "from a_index | eval var = to_str(cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = top(stringField, 5, \"asc\")", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval var = to_string(to_cartesianshape(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = top(stringField, 5, \"asc\") > 0", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval var = to_string(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval top(stringField, 5, \"asc\")", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval to_string(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval top(stringField, 5, \"asc\") > 0", - "error": [ - "EVAL does not support function top" - ], + "query": "from a_index | eval var = to_str(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = top(stringField, 5, \"asc\")", + "query": "from a_index | eval var = to_string(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | stats top(stringField, 5, \"asc\")", + "query": "from a_index | eval var = to_string(doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | stats top(stringField, numberField, \"asc\")", - "error": [ - "Argument of [top] must be a constant, received [numberField]" - ], + "query": "from a_index | eval to_string(doubleField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats top(null, null, null)", + "query": "from a_index | eval var = to_str(doubleField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats top(nullVar, nullVar, nullVar)", - "error": [ - "Argument of [top] must be a constant, received [nullVar]", - "Argument of [top] must be a constant, received [nullVar]" - ], + "query": "from a_index | eval var = to_string(to_double(booleanField))", + "error": [], "warning": [] }, { - "query": "row var = st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_string(geoPointField)", "error": [], "warning": [] }, { - "query": "row st_distance(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval to_string(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_distance(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = to_str(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_distance(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_string(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row st_distance(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_string(geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_distance(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval to_string(geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_distance(true, true)", - "error": [ - "Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = to_str(geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_distance(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval var = to_string(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_distance(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval var = to_string(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval to_string(integerField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_distance(booleanField, booleanField)", - "error": [ - "Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = to_str(integerField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_distance(geoPointField, geoPointField)", + "query": "from a_index | eval var = to_string(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_distance(geoPointField, geoPointField)", + "query": "from a_index | eval var = to_string(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval to_string(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_distance(cartesianPointField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_distance] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = to_str(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort st_distance(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval var = to_string(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_distance(null, null)", + "query": "from a_index | eval var = to_string(keywordField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_distance(nullVar, nullVar)", + "query": "from a_index | eval to_string(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = weighted_avg(numberField, numberField)", + "query": "from a_index | eval var = to_str(keywordField)", "error": [], "warning": [] }, { - "query": "from a_index | stats weighted_avg(numberField, numberField)", + "query": "from a_index | eval var = to_string(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(weighted_avg(numberField, numberField))", + "query": "from a_index | eval var = to_string(longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(weighted_avg(numberField, numberField))", + "query": "from a_index | eval to_string(longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)", + "query": "from a_index | eval var = to_str(longField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)", + "query": "from a_index | eval var = to_string(textField)", "error": [], "warning": [] }, { - "query": "from a_index | stats weighted_avg(numberField / 2, numberField)", + "query": "from a_index | eval to_string(textField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = weighted_avg(numberField / 2, numberField)", + "query": "from a_index | eval var = to_str(textField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), weighted_avg(numberField / 2, numberField)", + "query": "from a_index | eval var = to_string(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField / 2, numberField)", + "query": "from a_index | eval to_string(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = weighted_avg(numberField, numberField)", + "query": "from a_index | eval var = to_str(unsignedLongField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), weighted_avg(numberField, numberField)", + "query": "from a_index | eval var = to_string(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField)", + "query": "from a_index | eval to_string(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | stats weighted_avg(numberField, numberField) by round(numberField / 2)", + "query": "from a_index | eval var = to_str(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2)", + "query": "from a_index | eval var = to_string(to_version(keywordField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), ipField", - "error": [], + "query": "from a_index | eval to_string(booleanField, extraArg)", + "error": [ + "Error: [to_string] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | sort to_string(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | eval to_string(null)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "row nullVar = null | eval to_string(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = weighted_avg(avg(numberField), avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | eval to_string(\"2022\")", + "error": [], "warning": [] }, { - "query": "from a_index | stats weighted_avg(avg(numberField), avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | eval to_string(concat(\"20\", \"22\"))", + "error": [], "warning": [] }, { - "query": "from a_index | stats weighted_avg(booleanField, booleanField)", - "error": [ - "Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]", - "Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = to_string(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort weighted_avg(numberField, numberField)", - "error": [ - "SORT does not support function weighted_avg" - ], + "query": "row to_string(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where weighted_avg(numberField, numberField)", - "error": [ - "WHERE does not support function weighted_avg" - ], + "query": "row var = to_str(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where weighted_avg(numberField, numberField) > 0", - "error": [ - "WHERE does not support function weighted_avg" - ], + "query": "row var = to_string(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = weighted_avg(numberField, numberField)", - "error": [ - "EVAL does not support function weighted_avg" - ], + "query": "row var = to_string(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = weighted_avg(numberField, numberField) > 0", - "error": [ - "EVAL does not support function weighted_avg" - ], + "query": "row var = to_string(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval weighted_avg(numberField, numberField)", - "error": [ - "EVAL does not support function weighted_avg" - ], + "query": "row to_string(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval weighted_avg(numberField, numberField) > 0", - "error": [ - "EVAL does not support function weighted_avg" - ], + "query": "row var = to_str(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | stats weighted_avg(null, null)", + "query": "row var = to_string(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats weighted_avg(nullVar, nullVar)", + "query": "row var = to_string(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = exp(5)", + "query": "row var = mv_pseries_weighted_sum(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row exp(5)", + "query": "row mv_pseries_weighted_sum(5.5, 5.5)", "error": [], "warning": [] }, { - "query": "row var = exp(to_integer(true))", + "query": "row var = mv_pseries_weighted_sum(to_double(true), to_double(true))", "error": [], "warning": [] }, { - "query": "row var = exp(true)", + "query": "row var = mv_pseries_weighted_sum(true, true)", "error": [ - "Argument of [exp] must be [number], found value [true] type [boolean]" + "Argument of [mv_pseries_weighted_sum] must be [double], found value [true] type [boolean]", + "Argument of [mv_pseries_weighted_sum] must be [double], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where exp(numberField) > 0", + "query": "from a_index | where mv_pseries_weighted_sum(doubleField, doubleField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where exp(booleanField) > 0", + "query": "from a_index | where mv_pseries_weighted_sum(booleanField, booleanField) > 0", "error": [ - "Argument of [exp] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]", + "Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = exp(numberField)", + "query": "from a_index | eval var = mv_pseries_weighted_sum(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval exp(numberField)", + "query": "from a_index | eval mv_pseries_weighted_sum(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = exp(to_integer(booleanField))", + "query": "from a_index | eval var = mv_pseries_weighted_sum(to_double(booleanField), to_double(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval exp(booleanField)", - "error": [ - "Argument of [exp] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = exp(*)", + "query": "from a_index | eval mv_pseries_weighted_sum(booleanField, booleanField)", "error": [ - "Using wildcards (*) in exp is not allowed" + "Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]", + "Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval exp(numberField, extraArg)", + "query": "from a_index | eval mv_pseries_weighted_sum(doubleField, doubleField, extraArg)", "error": [ - "Error: [exp] function expects exactly one argument, got 2." + "Error: [mv_pseries_weighted_sum] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | sort exp(numberField)", + "query": "from a_index | sort mv_pseries_weighted_sum(doubleField, doubleField)", "error": [], "warning": [] }, { - "query": "from a_index | eval exp(null)", + "query": "from a_index | eval mv_pseries_weighted_sum(null, null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval exp(nullVar)", + "query": "row nullVar = null | eval mv_pseries_weighted_sum(nullVar, nullVar)", "error": [], "warning": [] }, @@ -26843,48 +37151,6 @@ ], "warning": [] }, - { - "query": "FROM index, missingIndex", - "error": [ - "Unknown index [missingIndex]" - ], - "warning": [] - }, - { - "query": "from average()", - "error": [ - "Unknown index [average()]" - ], - "warning": [] - }, - { - "query": "fRom custom_function()", - "error": [ - "Unknown index [custom_function()]" - ], - "warning": [] - }, - { - "query": "FROM indexes*", - "error": [ - "Unknown index [indexes*]" - ], - "warning": [] - }, - { - "query": "from numberField", - "error": [ - "Unknown index [numberField]" - ], - "warning": [] - }, - { - "query": "FROM policy", - "error": [ - "Unknown index [policy]" - ], - "warning": [] - }, { "query": "from index metadata _id", "error": [], diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/types.ts b/packages/kbn-esql-validation-autocomplete/src/validation/types.ts index 41a0c9b2dfd6f..f6654540bcf86 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/types.ts @@ -7,6 +7,7 @@ */ import type { ESQLMessage, ESQLLocation } from '@kbn/esql-ast'; +import { FieldType } from '../definitions/types'; import type { EditorError } from '../types'; export interface ESQLVariable { @@ -17,10 +18,9 @@ export interface ESQLVariable { export interface ESQLRealField { name: string; - type: string; + type: FieldType; metadata?: { description?: string; - type?: string; }; } diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index daeff58923f5c..5e61c2631d9e1 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -11,7 +11,13 @@ import { writeFile, readFile } from 'fs/promises'; import { ignoreErrorsMap, validateQuery } from './validation'; import { evalFunctionDefinitions } from '../definitions/functions'; import { getFunctionSignatures } from '../definitions/helpers'; -import { FunctionDefinition, SupportedFieldType, supportedFieldTypes } from '../definitions/types'; +import { + FieldType, + FunctionDefinition, + SupportedDataType, + dataTypes, + fieldTypes as _fieldTypes, +} from '../definitions/types'; import { timeUnits, timeUnitsToSuggest } from '../definitions/literals'; import { statsAggregationFunctionDefinitions } from '../definitions/aggs'; import capitalize from 'lodash/capitalize'; @@ -30,31 +36,45 @@ import { import { validationFromCommandTestSuite as runFromTestSuite } from './__tests__/test_suites/validation.command.from'; import { Setup, setup } from './__tests__/helpers'; +const fieldTypes = _fieldTypes.filter((type) => type !== 'unsupported'); + const NESTING_LEVELS = 4; const NESTED_DEPTHS = Array(NESTING_LEVELS) .fill(0) .map((_, i) => i + 1); +const toAvgSignature = statsAggregationFunctionDefinitions.find(({ name }) => name === 'avg')!; const toInteger = evalFunctionDefinitions.find(({ name }) => name === 'to_integer')!; +const toDoubleSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_double')!; const toStringSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_string')!; const toDateSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_datetime')!; const toBooleanSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_boolean')!; const toIpSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_ip')!; const toGeoPointSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_geopoint')!; +const toGeoShapeSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_geoshape')!; const toCartesianPointSignature = evalFunctionDefinitions.find( ({ name }) => name === 'to_cartesianpoint' )!; - -const toAvgSignature = statsAggregationFunctionDefinitions.find(({ name }) => name === 'avg')!; +const toCartesianShapeSignature = evalFunctionDefinitions.find( + ({ name }) => name === 'to_cartesianshape' +)!; +const toVersionSignature = evalFunctionDefinitions.find(({ name }) => name === 'to_version')!; const nestedFunctions = { - number: prepareNestedFunction(toInteger), + double: prepareNestedFunction(toDoubleSignature), + integer: prepareNestedFunction(toInteger), string: prepareNestedFunction(toStringSignature), + text: prepareNestedFunction(toStringSignature), + keyword: prepareNestedFunction(toStringSignature), date: prepareNestedFunction(toDateSignature), boolean: prepareNestedFunction(toBooleanSignature), ip: prepareNestedFunction(toIpSignature), + version: prepareNestedFunction(toVersionSignature), geo_point: prepareNestedFunction(toGeoPointSignature), + geo_shape: prepareNestedFunction(toGeoShapeSignature), cartesian_point: prepareNestedFunction(toCartesianPointSignature), + cartesian_shape: prepareNestedFunction(toCartesianShapeSignature), + datetime: prepareNestedFunction(toDateSignature), }; const literals = { @@ -64,7 +84,7 @@ function getLiteralType(typeString: 'time_literal') { return `1 ${literals[typeString]}`; } -export const fieldNameFromType = (type: SupportedFieldType) => `${camelCase(type)}Field`; +export const fieldNameFromType = (type: FieldType) => `${camelCase(type)}Field`; function getFieldName( typeString: string, @@ -115,8 +135,8 @@ function getFieldMapping( date: 'now()', }; return params.map(({ name: _name, type, constantOnly, literalOptions, ...rest }) => { - const typeString: string = type; - if (supportedFieldTypes.includes(typeString as SupportedFieldType)) { + const typeString: string = type as string; + if (dataTypes.includes(typeString as SupportedDataType)) { if (useLiterals && literalOptions) { return { name: `"${literalOptions[0]}"`, @@ -152,7 +172,7 @@ function getFieldMapping( ...rest, }; } - return { name: 'stringField', type, ...rest }; + return { name: 'textField', type, ...rest }; }); } @@ -351,7 +371,7 @@ describe('validation logic', () => { for (const op of ['>', '>=', '<', '<=', '==', '!=']) { testErrorsAndWarnings(`row var = 5 ${op} 0`, []); testErrorsAndWarnings(`row var = NOT 5 ${op} 0`, []); - testErrorsAndWarnings(`row var = (numberField ${op} 0)`, ['Unknown column [numberField]']); + testErrorsAndWarnings(`row var = (doubleField ${op} 0)`, ['Unknown column [doubleField]']); testErrorsAndWarnings(`row var = (NOT (5 ${op} 0))`, []); testErrorsAndWarnings(`row var = to_ip("127.0.0.1") ${op} to_ip("127.0.0.1")`, []); testErrorsAndWarnings(`row var = now() ${op} now()`, []); @@ -360,8 +380,8 @@ describe('validation logic', () => { ['==', '!='].includes(op) ? [] : [ - `Argument of [${op}] must be [number], found value [false] type [boolean]`, - `Argument of [${op}] must be [number], found value [false] type [boolean]`, + `Argument of [${op}] must be [date], found value [false] type [boolean]`, + `Argument of [${op}] must be [date], found value [false] type [boolean]`, ] ); for (const [valueTypeA, valueTypeB] of [['now()', '"2022"']]) { @@ -375,10 +395,10 @@ describe('validation logic', () => { testErrorsAndWarnings( `row var = now() ${op} now()`, ['+', '-'].includes(op) - ? [`Argument of [${op}] must be [time_literal], found value [now()] type [date]`] + ? [`Argument of [${op}] must be [date_period], found value [now()] type [date]`] : [ - `Argument of [${op}] must be [number], found value [now()] type [date]`, - `Argument of [${op}] must be [number], found value [now()] type [date]`, + `Argument of [${op}] must be [double], found value [now()] type [date]`, + `Argument of [${op}] must be [double], found value [now()] type [date]`, ] ); } @@ -389,16 +409,16 @@ describe('validation logic', () => { testErrorsAndWarnings(`row var = NOT "a" ${op} "?a"`, []); testErrorsAndWarnings(`row var = NOT "a" NOT ${op} "?a"`, []); testErrorsAndWarnings(`row var = 5 ${op} "?a"`, [ - `Argument of [${op}] must be [string], found value [5] type [number]`, + `Argument of [${op}] must be [text], found value [5] type [integer]`, ]); testErrorsAndWarnings(`row var = 5 NOT ${op} "?a"`, [ - `Argument of [not_${op}] must be [string], found value [5] type [number]`, + `Argument of [not_${op}] must be [text], found value [5] type [integer]`, ]); testErrorsAndWarnings(`row var = NOT 5 ${op} "?a"`, [ - `Argument of [${op}] must be [string], found value [5] type [number]`, + `Argument of [${op}] must be [text], found value [5] type [integer]`, ]); testErrorsAndWarnings(`row var = NOT 5 NOT ${op} "?a"`, [ - `Argument of [not_${op}] must be [string], found value [5] type [number]`, + `Argument of [not_${op}] must be [text], found value [5] type [integer]`, ]); } @@ -438,8 +458,8 @@ describe('validation logic', () => { ]); for (const op of ['*', '/', '%']) { testErrorsAndWarnings(`row var = now() ${op} 1 ${timeLiteral.name}`, [ - `Argument of [${op}] must be [number], found value [now()] type [date]`, - `Argument of [${op}] must be [number], found value [1 ${timeLiteral.name}] type [duration]`, + `Argument of [${op}] must be [double], found value [now()] type [date]`, + `Argument of [${op}] must be [double], found value [1 ${timeLiteral.name}] type [duration]`, ]); } } @@ -449,13 +469,13 @@ describe('validation logic', () => { describe('show', () => { testErrorsAndWarnings('show', ["SyntaxError: missing 'info' at '<EOF>'"]); testErrorsAndWarnings('show info', []); - testErrorsAndWarnings('show numberField', [ - "SyntaxError: token recognition error at: 'n'", + testErrorsAndWarnings('show doubleField', [ + "SyntaxError: token recognition error at: 'd'", + "SyntaxError: token recognition error at: 'o'", "SyntaxError: token recognition error at: 'u'", - "SyntaxError: token recognition error at: 'm'", "SyntaxError: token recognition error at: 'b'", + "SyntaxError: token recognition error at: 'l'", "SyntaxError: token recognition error at: 'e'", - "SyntaxError: token recognition error at: 'r'", "SyntaxError: token recognition error at: 'F'", "SyntaxError: token recognition error at: 'ie'", "SyntaxError: token recognition error at: 'l'", @@ -475,11 +495,11 @@ describe('validation logic', () => { testErrorsAndWarnings('from index | limit a', [ "SyntaxError: mismatched input 'a' expecting INTEGER_LITERAL", ]); - testErrorsAndWarnings('from index | limit numberField', [ - "SyntaxError: mismatched input 'numberField' expecting INTEGER_LITERAL", + testErrorsAndWarnings('from index | limit doubleField', [ + "SyntaxError: mismatched input 'doubleField' expecting INTEGER_LITERAL", ]); - testErrorsAndWarnings('from index | limit stringField', [ - "SyntaxError: mismatched input 'stringField' expecting INTEGER_LITERAL", + testErrorsAndWarnings('from index | limit textField', [ + "SyntaxError: mismatched input 'textField' expecting INTEGER_LITERAL", ]); testErrorsAndWarnings('from index | limit 4', []); }); @@ -490,8 +510,14 @@ describe('validation logic', () => { describe('keep', () => { testErrorsAndWarnings('from index | keep ', ["SyntaxError: missing ID_PATTERN at '<EOF>'"]); - testErrorsAndWarnings('from index | keep stringField, numberField, dateField', []); - testErrorsAndWarnings('from index | keep `stringField`, `numberField`, `dateField`', []); + testErrorsAndWarnings( + 'from index | keep keywordField, doubleField, integerField, dateField', + [] + ); + testErrorsAndWarnings( + 'from index | keep `keywordField`, `doubleField`, `integerField`, `dateField`', + [] + ); testErrorsAndWarnings('from index | keep 4.5', [ "SyntaxError: token recognition error at: '4'", "SyntaxError: token recognition error at: '5'", @@ -499,74 +525,67 @@ describe('validation logic', () => { "SyntaxError: missing ID_PATTERN at '<EOF>'", ]); testErrorsAndWarnings('from index | keep `4.5`', ['Unknown column [4.5]']); - testErrorsAndWarnings('from index | keep missingField, numberField, dateField', [ + testErrorsAndWarnings('from index | keep missingField, doubleField, dateField', [ 'Unknown column [missingField]', ]); testErrorsAndWarnings('from index | keep `any#Char$Field`', []); testErrorsAndWarnings('from index | project ', [ "SyntaxError: mismatched input 'project' expecting {'dissect', 'drop', 'enrich', 'eval', 'grok', 'inlinestats', 'keep', 'limit', 'lookup', 'mv_expand', 'rename', 'sort', 'stats', 'where'}", ]); - testErrorsAndWarnings('from index | project stringField, numberField, dateField', [ + testErrorsAndWarnings('from index | project textField, doubleField, dateField', [ "SyntaxError: mismatched input 'project' expecting {'dissect', 'drop', 'enrich', 'eval', 'grok', 'inlinestats', 'keep', 'limit', 'lookup', 'mv_expand', 'rename', 'sort', 'stats', 'where'}", ]); - testErrorsAndWarnings('from index | PROJECT stringField, numberField, dateField', [ + testErrorsAndWarnings('from index | PROJECT textField, doubleField, dateField', [ "SyntaxError: mismatched input 'PROJECT' expecting {'dissect', 'drop', 'enrich', 'eval', 'grok', 'inlinestats', 'keep', 'limit', 'lookup', 'mv_expand', 'rename', 'sort', 'stats', 'where'}", ]); - testErrorsAndWarnings('from index | project missingField, numberField, dateField', [ + testErrorsAndWarnings('from index | project missingField, doubleField, dateField', [ "SyntaxError: mismatched input 'project' expecting {'dissect', 'drop', 'enrich', 'eval', 'grok', 'inlinestats', 'keep', 'limit', 'lookup', 'mv_expand', 'rename', 'sort', 'stats', 'where'}", ]); - testErrorsAndWarnings('from index | keep s*', []); + testErrorsAndWarnings('from index | keep k*', []); testErrorsAndWarnings('from index | keep *Field', []); - testErrorsAndWarnings('from index | keep s*Field', []); - testErrorsAndWarnings('from index | keep string*Field', []); - testErrorsAndWarnings('from index | keep s*, n*', []); + testErrorsAndWarnings('from index | keep k*Field', []); + testErrorsAndWarnings('from index | keep key*Field', []); + testErrorsAndWarnings('from index | keep k*, i*', []); testErrorsAndWarnings('from index | keep m*', ['Unknown column [m*]']); testErrorsAndWarnings('from index | keep *m', ['Unknown column [*m]']); testErrorsAndWarnings('from index | keep d*m', ['Unknown column [d*m]']); - testErrorsAndWarnings( - 'from unsupported_index | keep unsupported_field', - [], - [ - 'Field [unsupported_field] cannot be retrieved, it is unsupported or not indexed; returning null', - ] - ); testErrorsAndWarnings( - `FROM index | STATS ROUND(AVG(numberField * 1.5)), COUNT(*), MIN(numberField * 10) | KEEP \`MIN(numberField * 10)\``, + `FROM index | STATS ROUND(AVG(doubleField * 1.5)), COUNT(*), MIN(doubleField * 10) | KEEP \`MIN(doubleField * 10)\``, [] ); testErrorsAndWarnings( - `FROM index | STATS COUNT(*), MIN(numberField * 10), MAX(numberField)| KEEP \`COUNT(*)\``, + `FROM index | STATS COUNT(*), MIN(doubleField * 10), MAX(doubleField)| KEEP \`COUNT(*)\``, [] ); }); describe('drop', () => { testErrorsAndWarnings('from index | drop ', ["SyntaxError: missing ID_PATTERN at '<EOF>'"]); - testErrorsAndWarnings('from index | drop stringField, numberField, dateField', []); + testErrorsAndWarnings('from index | drop textField, doubleField, dateField', []); testErrorsAndWarnings('from index | drop 4.5', [ "SyntaxError: token recognition error at: '4'", "SyntaxError: token recognition error at: '5'", "SyntaxError: missing ID_PATTERN at '.'", "SyntaxError: missing ID_PATTERN at '<EOF>'", ]); - testErrorsAndWarnings('from index | drop missingField, numberField, dateField', [ + testErrorsAndWarnings('from index | drop missingField, doubleField, dateField', [ 'Unknown column [missingField]', ]); testErrorsAndWarnings('from index | drop `any#Char$Field`', []); - testErrorsAndWarnings('from index | drop s*', []); - testErrorsAndWarnings('from index | drop s**Field', []); + testErrorsAndWarnings('from index | drop t*', []); + testErrorsAndWarnings('from index | drop t**Field', []); testErrorsAndWarnings('from index | drop *Field*', []); - testErrorsAndWarnings('from index | drop s*F*d', []); + testErrorsAndWarnings('from index | drop t*F*d', []); testErrorsAndWarnings('from index | drop *Field', []); - testErrorsAndWarnings('from index | drop s*Field', []); - testErrorsAndWarnings('from index | drop string*Field', []); - testErrorsAndWarnings('from index | drop s*, n*', []); + testErrorsAndWarnings('from index | drop t*Field', []); + testErrorsAndWarnings('from index | drop textField', []); + testErrorsAndWarnings('from index | drop s*, d*', ['Unknown column [s*]']); testErrorsAndWarnings('from index | drop m*', ['Unknown column [m*]']); testErrorsAndWarnings('from index | drop *m', ['Unknown column [*m]']); testErrorsAndWarnings('from index | drop d*m', ['Unknown column [d*m]']); testErrorsAndWarnings('from index | drop *', ['Removing all fields is not allowed [*]']); - testErrorsAndWarnings('from index | drop stringField, *', [ + testErrorsAndWarnings('from index | drop textField, *', [ 'Removing all fields is not allowed [*]', ]); testErrorsAndWarnings( @@ -575,16 +594,16 @@ describe('validation logic', () => { ['Drop [@timestamp] will remove all time filters to the search results'] ); testErrorsAndWarnings( - 'from index | drop stringField, @timestamp', + 'from index | drop textField, @timestamp', [], ['Drop [@timestamp] will remove all time filters to the search results'] ); testErrorsAndWarnings( - `FROM index | STATS ROUND(AVG(numberField * 1.5)), COUNT(*), MIN(numberField * 10) | DROP \`MIN(numberField * 10)\``, + `FROM index | STATS ROUND(AVG(doubleField * 1.5)), COUNT(*), MIN(doubleField * 10) | DROP \`MIN(doubleField * 10)\``, [] ); testErrorsAndWarnings( - `FROM index | STATS COUNT(*), MIN(numberField * 10), MAX(numberField)| DROP \`COUNT(*)\``, + `FROM index | STATS COUNT(*), MIN(doubleField * 10), MAX(doubleField)| DROP \`COUNT(*)\``, [] ); }); @@ -593,11 +612,11 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | mv_expand ', [ "SyntaxError: missing {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER} at '<EOF>'", ]); - for (const type of ['string', 'number', 'date', 'boolean', 'ip']) { + for (const type of ['text', 'integer', 'date', 'boolean', 'ip']) { testErrorsAndWarnings(`from a_index | mv_expand ${type}Field`, []); } - testErrorsAndWarnings('from a_index | mv_expand numberField, b', [ + testErrorsAndWarnings('from a_index | mv_expand doubleField, b', [ "SyntaxError: token recognition error at: ','", "SyntaxError: extraneous input 'b' expecting <EOF>", ]); @@ -612,24 +631,24 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | rename', [ "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN", ]); - testErrorsAndWarnings('from a_index | rename stringField', [ + testErrorsAndWarnings('from a_index | rename textField', [ "SyntaxError: mismatched input '<EOF>' expecting 'as'", ]); testErrorsAndWarnings('from a_index | rename a', [ "SyntaxError: mismatched input '<EOF>' expecting 'as'", 'Unknown column [a]', ]); - testErrorsAndWarnings('from a_index | rename stringField as', [ + testErrorsAndWarnings('from a_index | rename textField as', [ "SyntaxError: missing ID_PATTERN at '<EOF>'", ]); testErrorsAndWarnings('from a_index | rename missingField as', [ "SyntaxError: missing ID_PATTERN at '<EOF>'", 'Unknown column [missingField]', ]); - testErrorsAndWarnings('from a_index | rename stringField as b', []); - testErrorsAndWarnings('from a_index | rename stringField AS b', []); - testErrorsAndWarnings('from a_index | rename stringField As b', []); - testErrorsAndWarnings('from a_index | rename stringField As b, b AS c', []); + testErrorsAndWarnings('from a_index | rename textField as b', []); + testErrorsAndWarnings('from a_index | rename textField AS b', []); + testErrorsAndWarnings('from a_index | rename textField As b', []); + testErrorsAndWarnings('from a_index | rename textField As b, b AS c', []); testErrorsAndWarnings('from a_index | rename fn() as a', [ "SyntaxError: token recognition error at: '('", "SyntaxError: token recognition error at: ')'", @@ -637,18 +656,22 @@ describe('validation logic', () => { 'Unknown column [a]', ]); testErrorsAndWarnings( - 'from a_index | eval numberField + 1 | rename `numberField + 1` as a', + 'from a_index | eval doubleField + 1 | rename `doubleField + 1` as a', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField) | rename `avg(numberField)` as avg0', + 'from a_index | stats avg(doubleField) | rename `avg(doubleField)` as avg0', [] ); - testErrorsAndWarnings('from a_index |eval numberField + 1 | rename `numberField + 1` as ', [ + testErrorsAndWarnings('from a_index |eval doubleField + 1 | rename `doubleField + 1` as ', [ "SyntaxError: missing ID_PATTERN at '<EOF>'", ]); + testErrorsAndWarnings('from a_index | rename key* as keywords', [ + 'Using wildcards (*) in RENAME is not allowed [key*]', + 'Unknown column [keywords]', + ]); testErrorsAndWarnings('from a_index | rename s* as strings', [ - 'Using wildcards (*) in RENAME is not allowed [s*]', + 'Unknown column [s*]', 'Unknown column [strings]', ]); testErrorsAndWarnings('row a = 10 | rename a as `this``is fine`', []); @@ -661,51 +684,48 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | dissect', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - testErrorsAndWarnings('from a_index | dissect stringField', [ + testErrorsAndWarnings('from a_index | dissect textField', [ "SyntaxError: missing QUOTED_STRING at '<EOF>'", ]); - testErrorsAndWarnings('from a_index | dissect stringField 2', [ + testErrorsAndWarnings('from a_index | dissect textField 2', [ "SyntaxError: mismatched input '2' expecting QUOTED_STRING", ]); - testErrorsAndWarnings('from a_index | dissect stringField .', [ + testErrorsAndWarnings('from a_index | dissect textField .', [ "SyntaxError: mismatched input '<EOF>' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - 'Unknown column [stringField.]', + 'Unknown column [textField.]', ]); - testErrorsAndWarnings('from a_index | dissect stringField %a', [ + testErrorsAndWarnings('from a_index | dissect textField %a', [ "SyntaxError: mismatched input '%' expecting QUOTED_STRING", "SyntaxError: mismatched input '<EOF>' expecting '='", ]); // Do not try to validate the dissect pattern string - testErrorsAndWarnings('from a_index | dissect stringField "%{firstWord}"', []); - testErrorsAndWarnings('from a_index | dissect numberField "%{firstWord}"', [ - 'DISSECT only supports string type values, found [numberField] of type [number]', + testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}"', []); + testErrorsAndWarnings('from a_index | dissect doubleField "%{firstWord}"', [ + 'DISSECT only supports string type values, found [doubleField] of type [double]', ]); - testErrorsAndWarnings('from a_index | dissect stringField "%{firstWord}" option ', [ + testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" option ', [ "SyntaxError: mismatched input '<EOF>' expecting '='", ]); - testErrorsAndWarnings('from a_index | dissect stringField "%{firstWord}" option = ', [ + testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" option = ', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET}", 'Invalid option for DISSECT: [option]', ]); - testErrorsAndWarnings('from a_index | dissect stringField "%{firstWord}" option = 1', [ + testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" option = 1', [ 'Invalid option for DISSECT: [option]', ]); testErrorsAndWarnings( - 'from a_index | dissect stringField "%{firstWord}" append_separator = "-"', + 'from a_index | dissect textField "%{firstWord}" append_separator = "-"', [] ); testErrorsAndWarnings( - 'from a_index | dissect stringField "%{firstWord}" ignore_missing = true', + 'from a_index | dissect textField "%{firstWord}" ignore_missing = true', ['Invalid option for DISSECT: [ignore_missing]'] ); testErrorsAndWarnings( - 'from a_index | dissect stringField "%{firstWord}" append_separator = true', + 'from a_index | dissect textField "%{firstWord}" append_separator = true', ['Invalid value for DISSECT append_separator: expected a string, but was [true]'] ); - testErrorsAndWarnings( - 'from a_index | dissect stringField "%{firstWord}" | keep firstWord', - [] - ); + testErrorsAndWarnings('from a_index | dissect textField "%{firstWord}" | keep firstWord', []); // testErrorsAndWarnings('from a_index | dissect s* "%{a}"', [ // 'Using wildcards (*) in dissect is not allowed [s*]', // ]); @@ -715,25 +735,26 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | grok', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - testErrorsAndWarnings('from a_index | grok stringField', [ + testErrorsAndWarnings('from a_index | grok textField', [ "SyntaxError: missing QUOTED_STRING at '<EOF>'", ]); - testErrorsAndWarnings('from a_index | grok stringField 2', [ + testErrorsAndWarnings('from a_index | grok textField 2', [ "SyntaxError: mismatched input '2' expecting QUOTED_STRING", ]); - testErrorsAndWarnings('from a_index | grok stringField .', [ + testErrorsAndWarnings('from a_index | grok textField .', [ "SyntaxError: mismatched input '<EOF>' expecting {UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - 'Unknown column [stringField.]', + 'Unknown column [textField.]', ]); - testErrorsAndWarnings('from a_index | grok stringField %a', [ + testErrorsAndWarnings('from a_index | grok textField %a', [ "SyntaxError: mismatched input '%' expecting QUOTED_STRING", ]); + // @TODO: investigate // Do not try to validate the grok pattern string - testErrorsAndWarnings('from a_index | grok stringField "%{firstWord}"', []); - testErrorsAndWarnings('from a_index | grok numberField "%{firstWord}"', [ - 'GROK only supports string type values, found [numberField] of type [number]', + testErrorsAndWarnings('from a_index | grok textField "%{firstWord}"', []); + testErrorsAndWarnings('from a_index | grok doubleField "%{firstWord}"', [ + 'GROK only supports string type values, found [doubleField] of type [double]', ]); - testErrorsAndWarnings('from a_index | grok stringField "%{firstWord}" | keep firstWord', []); + testErrorsAndWarnings('from a_index | grok textField "%{firstWord}" | keep firstWord', []); // testErrorsAndWarnings('from a_index | grok s* "%{a}"', [ // 'Using wildcards (*) in grok is not allowed [s*]', // ]); @@ -750,20 +771,20 @@ describe('validation logic', () => { testErrorsAndWarnings(`from a_index | where NOT ${nValue} > 0`, []); } for (const op of ['>', '>=', '<', '<=', '==', '!=']) { - testErrorsAndWarnings(`from a_index | where numberField ${op} 0`, []); - testErrorsAndWarnings(`from a_index | where NOT numberField ${op} 0`, []); - testErrorsAndWarnings(`from a_index | where (numberField ${op} 0)`, []); - testErrorsAndWarnings(`from a_index | where (NOT (numberField ${op} 0))`, []); + testErrorsAndWarnings(`from a_index | where doubleField ${op} 0`, []); + testErrorsAndWarnings(`from a_index | where NOT doubleField ${op} 0`, []); + testErrorsAndWarnings(`from a_index | where (doubleField ${op} 0)`, []); + testErrorsAndWarnings(`from a_index | where (NOT (doubleField ${op} 0))`, []); testErrorsAndWarnings(`from a_index | where 1 ${op} 0`, []); - for (const type of ['string', 'number', 'date', 'boolean', 'ip']) { + for (const type of ['text', 'double', 'date', 'boolean', 'ip']) { testErrorsAndWarnings( `from a_index | where ${type}Field ${op} ${type}Field`, type !== 'boolean' || ['==', '!='].includes(op) ? [] : [ - `Argument of [${op}] must be [number], found value [${type}Field] type [${type}]`, - `Argument of [${op}] must be [number], found value [${type}Field] type [${type}]`, + `Argument of [${op}] must be [date], found value [${type}Field] type [${type}]`, + `Argument of [${op}] must be [date], found value [${type}Field] type [${type}]`, ] ); } @@ -778,17 +799,17 @@ describe('validation logic', () => { .fill('- ') .map((_, i) => (i % 2 ? oddOp : evenOp)) .join(''); - testErrorsAndWarnings(`from a_index | where ${unaryCombination} numberField > 0`, []); + testErrorsAndWarnings(`from a_index | where ${unaryCombination} doubleField > 0`, []); testErrorsAndWarnings( - `from a_index | where ${unaryCombination} round(numberField) > 0`, + `from a_index | where ${unaryCombination} round(doubleField) > 0`, [] ); testErrorsAndWarnings( - `from a_index | where 1 + ${unaryCombination} numberField > 0`, + `from a_index | where 1 + ${unaryCombination} doubleField > 0`, [] ); // still valid - testErrorsAndWarnings(`from a_index | where 1 ${unaryCombination} numberField > 0`, []); + testErrorsAndWarnings(`from a_index | where 1 ${unaryCombination} doubleField > 0`, []); } } testErrorsAndWarnings( @@ -797,52 +818,52 @@ describe('validation logic', () => { ); } for (const wrongOp of ['*', '/', '%']) { - testErrorsAndWarnings(`from a_index | where ${wrongOp}+ numberField`, [ + testErrorsAndWarnings(`from a_index | where ${wrongOp}+ doubleField`, [ `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, ]); } // Skip these tests until the insensitive case equality gets restored back - testErrorsAndWarnings.skip(`from a_index | where numberField =~ 0`, [ - 'Argument of [=~] must be [string], found value [numberField] type [number]', - 'Argument of [=~] must be [string], found value [0] type [number]', + testErrorsAndWarnings.skip(`from a_index | where doubleField =~ 0`, [ + 'Argument of [=~] must be [text], found value [doubleField] type [double]', + 'Argument of [=~] must be [text], found value [0] type [number]', ]); - testErrorsAndWarnings.skip(`from a_index | where NOT numberField =~ 0`, [ - 'Argument of [=~] must be [string], found value [numberField] type [number]', - 'Argument of [=~] must be [string], found value [0] type [number]', + testErrorsAndWarnings.skip(`from a_index | where NOT doubleField =~ 0`, [ + 'Argument of [=~] must be [text], found value [doubleField] type [double]', + 'Argument of [=~] must be [text], found value [0] type [number]', ]); - testErrorsAndWarnings.skip(`from a_index | where (numberField =~ 0)`, [ - 'Argument of [=~] must be [string], found value [numberField] type [number]', - 'Argument of [=~] must be [string], found value [0] type [number]', + testErrorsAndWarnings.skip(`from a_index | where (doubleField =~ 0)`, [ + 'Argument of [=~] must be [text], found value [doubleField] type [double]', + 'Argument of [=~] must be [text], found value [0] type [number]', ]); - testErrorsAndWarnings.skip(`from a_index | where (NOT (numberField =~ 0))`, [ - 'Argument of [=~] must be [string], found value [numberField] type [number]', - 'Argument of [=~] must be [string], found value [0] type [number]', + testErrorsAndWarnings.skip(`from a_index | where (NOT (doubleField =~ 0))`, [ + 'Argument of [=~] must be [text], found value [doubleField] type [double]', + 'Argument of [=~] must be [text], found value [0] type [number]', ]); testErrorsAndWarnings.skip(`from a_index | where 1 =~ 0`, [ - 'Argument of [=~] must be [string], found value [1] type [number]', - 'Argument of [=~] must be [string], found value [0] type [number]', + 'Argument of [=~] must be [text], found value [1] type [number]', + 'Argument of [=~] must be [text], found value [0] type [number]', ]); - testErrorsAndWarnings.skip(`from a_index | eval stringField =~ 0`, [ - `Argument of [=~] must be [string], found value [0] type [number]`, + testErrorsAndWarnings.skip(`from a_index | eval textField =~ 0`, [ + `Argument of [=~] must be [text], found value [0] type [number]`, ]); for (const op of ['like', 'rlike']) { - testErrorsAndWarnings(`from a_index | where stringField ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | where stringField NOT ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | where NOT stringField ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | where NOT stringField NOT ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | where numberField ${op} "?a"`, [ - `Argument of [${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | where textField ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | where textField NOT ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | where NOT textField ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | where NOT textField NOT ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | where doubleField ${op} "?a"`, [ + `Argument of [${op}] must be [text], found value [doubleField] type [double]`, ]); - testErrorsAndWarnings(`from a_index | where numberField NOT ${op} "?a"`, [ - `Argument of [not_${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | where doubleField NOT ${op} "?a"`, [ + `Argument of [not_${op}] must be [text], found value [doubleField] type [double]`, ]); - testErrorsAndWarnings(`from a_index | where NOT numberField ${op} "?a"`, [ - `Argument of [${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | where NOT doubleField ${op} "?a"`, [ + `Argument of [${op}] must be [text], found value [doubleField] type [double]`, ]); - testErrorsAndWarnings(`from a_index | where NOT numberField NOT ${op} "?a"`, [ - `Argument of [not_${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | where NOT doubleField NOT ${op} "?a"`, [ + `Argument of [not_${op}] must be [text], found value [doubleField] type [double]`, ]); } @@ -854,7 +875,7 @@ describe('validation logic', () => { [] ); - for (const field of supportedFieldTypes) { + for (const field of fieldTypes) { testErrorsAndWarnings(`from a_index | where ${fieldNameFromType(field)} IS NULL`, []); testErrorsAndWarnings(`from a_index | where ${fieldNameFromType(field)} IS null`, []); testErrorsAndWarnings(`from a_index | where ${fieldNameFromType(field)} is null`, []); @@ -866,21 +887,21 @@ describe('validation logic', () => { } // this is a scenario that was failing because "or" didn't accept "null" - testErrorsAndWarnings('from a_index | where stringField == "a" or null', []); + testErrorsAndWarnings('from a_index | where textField == "a" or null', []); }); describe('eval', () => { testErrorsAndWarnings('from a_index | eval ', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - testErrorsAndWarnings('from a_index | eval stringField ', []); - testErrorsAndWarnings('from a_index | eval b = stringField', []); - testErrorsAndWarnings('from a_index | eval numberField + 1', []); - testErrorsAndWarnings('from a_index | eval numberField + ', [ - "SyntaxError: no viable alternative at input 'numberField + '", + testErrorsAndWarnings('from a_index | eval textField ', []); + testErrorsAndWarnings('from a_index | eval b = textField', []); + testErrorsAndWarnings('from a_index | eval doubleField + 1', []); + testErrorsAndWarnings('from a_index | eval doubleField + ', [ + "SyntaxError: no viable alternative at input 'doubleField + '", ]); - testErrorsAndWarnings('from a_index | eval stringField + 1', [ - 'Argument of [+] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval textField + 1', [ + 'Argument of [+] must be [double], found value [textField] type [text]', ]); testErrorsAndWarnings('from a_index | eval a=b', ['Unknown column [b]']); testErrorsAndWarnings('from a_index | eval a=b, ', [ @@ -891,24 +912,24 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval a=round(', [ "SyntaxError: no viable alternative at input 'round('", ]); - testErrorsAndWarnings('from a_index | eval a=round(numberField) ', []); - testErrorsAndWarnings('from a_index | eval a=round(numberField), ', [ + testErrorsAndWarnings('from a_index | eval a=round(doubleField) ', []); + testErrorsAndWarnings('from a_index | eval a=round(doubleField), ', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - testErrorsAndWarnings('from a_index | eval a=round(numberField) + round(numberField) ', []); - testErrorsAndWarnings('from a_index | eval a=round(numberField) + round(stringField) ', [ - 'Argument of [round] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval a=round(doubleField) + round(doubleField) ', []); + testErrorsAndWarnings('from a_index | eval a=round(doubleField) + round(textField) ', [ + 'Argument of [round] must be [double], found value [textField] type [text]', ]); testErrorsAndWarnings( - 'from a_index | eval a=round(numberField) + round(stringField), numberField ', - ['Argument of [round] must be [number], found value [stringField] type [string]'] + 'from a_index | eval a=round(doubleField) + round(textField), doubleField ', + ['Argument of [round] must be [double], found value [textField] type [text]'] ); testErrorsAndWarnings( - 'from a_index | eval a=round(numberField) + round(numberField), numberField ', + 'from a_index | eval a=round(doubleField) + round(doubleField), doubleField ', [] ); testErrorsAndWarnings( - 'from a_index | eval a=round(numberField) + round(numberField), b = numberField ', + 'from a_index | eval a=round(doubleField) + round(doubleField), b = doubleField ', [] ); @@ -917,7 +938,7 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval a=["a", "b"]', []); testErrorsAndWarnings('from a_index | eval a=null', []); - for (const field of supportedFieldTypes) { + for (const field of fieldTypes) { testErrorsAndWarnings(`from a_index | eval ${fieldNameFromType(field)} IS NULL`, []); testErrorsAndWarnings(`from a_index | eval ${fieldNameFromType(field)} IS null`, []); testErrorsAndWarnings(`from a_index | eval ${fieldNameFromType(field)} is null`, []); @@ -936,15 +957,15 @@ describe('validation logic', () => { .fill('- ') .map((_, i) => (i % 2 ? oddOp : evenOp)) .join(''); - testErrorsAndWarnings(`from a_index | eval ${unaryCombination} numberField`, []); - testErrorsAndWarnings(`from a_index | eval a=${unaryCombination} numberField`, []); + testErrorsAndWarnings(`from a_index | eval ${unaryCombination} doubleField`, []); + testErrorsAndWarnings(`from a_index | eval a=${unaryCombination} doubleField`, []); testErrorsAndWarnings( - `from a_index | eval a=${unaryCombination} round(numberField)`, + `from a_index | eval a=${unaryCombination} round(doubleField)`, [] ); - testErrorsAndWarnings(`from a_index | eval 1 + ${unaryCombination} numberField`, []); + testErrorsAndWarnings(`from a_index | eval 1 + ${unaryCombination} doubleField`, []); // still valid - testErrorsAndWarnings(`from a_index | eval 1 ${unaryCombination} numberField`, []); + testErrorsAndWarnings(`from a_index | eval 1 ${unaryCombination} doubleField`, []); } } @@ -955,7 +976,7 @@ describe('validation logic', () => { } for (const wrongOp of ['*', '/', '%']) { - testErrorsAndWarnings(`from a_index | eval ${wrongOp}+ numberField`, [ + testErrorsAndWarnings(`from a_index | eval ${wrongOp}+ doubleField`, [ `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, ]); } @@ -991,46 +1012,58 @@ describe('validation logic', () => { ] ); for (const op of ['>', '>=', '<', '<=', '==', '!=']) { - testErrorsAndWarnings(`from a_index | eval numberField ${op} 0`, []); - testErrorsAndWarnings(`from a_index | eval NOT numberField ${op} 0`, []); - testErrorsAndWarnings(`from a_index | eval (numberField ${op} 0)`, []); - testErrorsAndWarnings(`from a_index | eval (NOT (numberField ${op} 0))`, []); + testErrorsAndWarnings(`from a_index | eval doubleField ${op} 0`, []); + testErrorsAndWarnings(`from a_index | eval NOT doubleField ${op} 0`, []); + testErrorsAndWarnings(`from a_index | eval (doubleField ${op} 0)`, []); + testErrorsAndWarnings(`from a_index | eval (NOT (doubleField ${op} 0))`, []); testErrorsAndWarnings(`from a_index | eval 1 ${op} 0`, []); - for (const type of ['string', 'number', 'date', 'boolean', 'ip']) { - testErrorsAndWarnings( - `from a_index | eval ${type}Field ${op} ${type}Field`, - type !== 'boolean' || ['==', '!='].includes(op) - ? [] - : [ - `Argument of [${op}] must be [number], found value [${type}Field] type [${type}]`, - `Argument of [${op}] must be [number], found value [${type}Field] type [${type}]`, - ] - ); + for (const type of ['text', 'double', 'date', 'boolean', 'ip']) { + if (type === 'boolean') { + testErrorsAndWarnings( + `from a_index | eval ${type}Field ${op} ${type}Field`, + type !== 'boolean' || ['==', '!='].includes(op) + ? [] + : [ + `Argument of [${op}] must be [date], found value [${type}Field] type [${type}]`, + `Argument of [${op}] must be [date], found value [${type}Field] type [${type}]`, + ] + ); + } else { + testErrorsAndWarnings( + `from a_index | eval ${type}Field ${op} ${type}Field`, + type !== 'boolean' || ['==', '!='].includes(op) + ? [] + : [ + `Argument of [${op}] must be [double], found value [${type}Field] type [${type}]`, + `Argument of [${op}] must be [double], found value [${type}Field] type [${type}]`, + ] + ); + } } // Implicit casting of literal values tests - testErrorsAndWarnings(`from a_index | eval numberField ${op} stringField`, [ - `Argument of [${op}] must be [number], found value [stringField] type [string]`, + testErrorsAndWarnings(`from a_index | eval doubleField ${op} textField`, [ + `Argument of [${op}] must be [double], found value [textField] type [text]`, ]); - testErrorsAndWarnings(`from a_index | eval stringField ${op} numberField`, [ - `Argument of [${op}] must be [number], found value [stringField] type [string]`, + testErrorsAndWarnings(`from a_index | eval keywordField ${op} doubleField`, [ + `Argument of [${op}] must be [double], found value [keywordField] type [keyword]`, ]); - testErrorsAndWarnings(`from a_index | eval numberField ${op} "2022"`, [ - `Argument of [${op}] must be [number], found value ["2022"] type [string]`, + testErrorsAndWarnings(`from a_index | eval doubleField ${op} "2022"`, [ + `Argument of [${op}] must be [date], found value [doubleField] type [double]`, ]); - testErrorsAndWarnings(`from a_index | eval dateField ${op} stringField`, [ - `Argument of [${op}] must be [string], found value [dateField] type [date]`, + testErrorsAndWarnings(`from a_index | eval dateField ${op} keywordField`, [ + `Argument of [${op}] must be [date], found value [keywordField] type [keyword]`, ]); - testErrorsAndWarnings(`from a_index | eval stringField ${op} dateField`, [ - `Argument of [${op}] must be [string], found value [dateField] type [date]`, + testErrorsAndWarnings(`from a_index | eval keywordField ${op} dateField`, [ + `Argument of [${op}] must be [date], found value [keywordField] type [keyword]`, ]); // Check that the implicit cast doesn't apply for fields - testErrorsAndWarnings(`from a_index | eval stringField ${op} 0`, [ - `Argument of [${op}] must be [number], found value [stringField] type [string]`, + testErrorsAndWarnings(`from a_index | eval textField ${op} 0`, [ + `Argument of [${op}] must be [double], found value [textField] type [text]`, ]); - testErrorsAndWarnings(`from a_index | eval stringField ${op} now()`, [ - `Argument of [${op}] must be [string], found value [now()] type [date]`, + testErrorsAndWarnings(`from a_index | eval textField ${op} now()`, [ + `Argument of [${op}] must be [date], found value [textField] type [text]`, ]); testErrorsAndWarnings(`from a_index | eval dateField ${op} "2022"`, []); @@ -1043,13 +1076,13 @@ describe('validation logic', () => { `from a_index | eval booleanField ${op} "true"`, ['==', '!='].includes(op) ? [] - : [`Argument of [${op}] must be [string], found value [booleanField] type [boolean]`] + : [`Argument of [${op}] must be [date], found value [booleanField] type [boolean]`] ); testErrorsAndWarnings( `from a_index | eval "true" ${op} booleanField`, ['==', '!='].includes(op) ? [] - : [`Argument of [${op}] must be [string], found value [booleanField] type [boolean]`] + : [`Argument of [${op}] must be [date], found value [booleanField] type [boolean]`] ); testErrorsAndWarnings(`from a_index | eval ipField ${op} "136.36.3.205"`, []); @@ -1072,25 +1105,31 @@ describe('validation logic', () => { ); for (const op of ['+', '-', '*', '/', '%']) { - testErrorsAndWarnings(`from a_index | eval numberField ${op} 1`, []); - testErrorsAndWarnings(`from a_index | eval (numberField ${op} 1)`, []); + testErrorsAndWarnings(`from a_index | eval doubleField ${op} 1`, []); + testErrorsAndWarnings(`from a_index | eval (doubleField ${op} 1)`, []); testErrorsAndWarnings(`from a_index | eval 1 ${op} 1`, []); testErrorsAndWarnings( `from a_index | eval now() ${op} now()`, ['+', '-'].includes(op) - ? [`Argument of [${op}] must be [time_literal], found value [now()] type [date]`] + ? [`Argument of [${op}] must be [date_period], found value [now()] type [date]`] : [ - `Argument of [${op}] must be [number], found value [now()] type [date]`, - `Argument of [${op}] must be [number], found value [now()] type [date]`, + `Argument of [${op}] must be [double], found value [now()] type [date]`, + `Argument of [${op}] must be [double], found value [now()] type [date]`, ] ); - testErrorsAndWarnings(`from a_index | eval 1 ${op} "1"`, [ - `Argument of [${op}] must be [number], found value [\"1\"] type [string]`, - ]); - testErrorsAndWarnings(`from a_index | eval "1" ${op} 1`, [ - `Argument of [${op}] must be [number], found value [\"1\"] type [string]`, - ]); + testErrorsAndWarnings( + `from a_index | eval 1 ${op} "1"`, + ['+', '-'].includes(op) + ? [`Argument of [${op}] must be [date_period], found value [1] type [integer]`] + : [`Argument of [${op}] must be [double], found value [\"1\"] type [string]`] + ); + testErrorsAndWarnings( + `from a_index | eval "1" ${op} 1`, + ['+', '-'].includes(op) + ? [`Argument of [${op}] must be [date_period], found value [1] type [integer]`] + : [`Argument of [${op}] must be [double], found value [\"1\"] type [string]`] + ); // TODO: enable when https://github.com/elastic/elasticsearch/issues/108432 is complete // testErrorsAndWarnings(`from a_index | eval "2022" ${op} 1 day`, []); } @@ -1109,54 +1148,51 @@ describe('validation logic', () => { ); } for (const op of ['like', 'rlike']) { - testErrorsAndWarnings(`from a_index | eval stringField ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | eval stringField NOT ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | eval NOT stringField ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | eval NOT stringField NOT ${op} "?a"`, []); - testErrorsAndWarnings(`from a_index | eval numberField ${op} "?a"`, [ - `Argument of [${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | eval textField ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | eval textField NOT ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | eval NOT textField ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | eval NOT textField NOT ${op} "?a"`, []); + testErrorsAndWarnings(`from a_index | eval doubleField ${op} "?a"`, [ + `Argument of [${op}] must be [text], found value [doubleField] type [double]`, ]); - testErrorsAndWarnings(`from a_index | eval numberField NOT ${op} "?a"`, [ - `Argument of [not_${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | eval doubleField NOT ${op} "?a"`, [ + `Argument of [not_${op}] must be [text], found value [doubleField] type [double]`, ]); - testErrorsAndWarnings(`from a_index | eval NOT numberField ${op} "?a"`, [ - `Argument of [${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | eval NOT doubleField ${op} "?a"`, [ + `Argument of [${op}] must be [text], found value [doubleField] type [double]`, ]); - testErrorsAndWarnings(`from a_index | eval NOT numberField NOT ${op} "?a"`, [ - `Argument of [not_${op}] must be [string], found value [numberField] type [number]`, + testErrorsAndWarnings(`from a_index | eval NOT doubleField NOT ${op} "?a"`, [ + `Argument of [not_${op}] must be [text], found value [doubleField] type [double]`, ]); } // test lists testErrorsAndWarnings('from a_index | eval 1 in (1, 2, 3)', []); - testErrorsAndWarnings('from a_index | eval numberField in (1, 2, 3)', []); - testErrorsAndWarnings('from a_index | eval numberField not in (1, 2, 3)', []); - testErrorsAndWarnings('from a_index | eval numberField not in (1, 2, 3, numberField)', []); - testErrorsAndWarnings('from a_index | eval 1 in (1, 2, 3, round(numberField))', []); + testErrorsAndWarnings('from a_index | eval doubleField in (1, 2, 3)', []); + testErrorsAndWarnings('from a_index | eval doubleField not in (1, 2, 3)', []); + testErrorsAndWarnings('from a_index | eval doubleField not in (1, 2, 3, doubleField)', []); + testErrorsAndWarnings('from a_index | eval 1 in (1, 2, 3, round(doubleField))', []); testErrorsAndWarnings('from a_index | eval "a" in ("a", "b", "c")', []); - testErrorsAndWarnings('from a_index | eval stringField in ("a", "b", "c")', []); - testErrorsAndWarnings('from a_index | eval stringField not in ("a", "b", "c")', []); - testErrorsAndWarnings( - 'from a_index | eval stringField not in ("a", "b", "c", stringField)', - [] - ); + testErrorsAndWarnings('from a_index | eval textField in ("a", "b", "c")', []); + testErrorsAndWarnings('from a_index | eval textField not in ("a", "b", "c")', []); + testErrorsAndWarnings('from a_index | eval textField not in ("a", "b", "c", textField)', []); testErrorsAndWarnings('from a_index | eval 1 in ("a", "b", "c")', [ // 'Argument of [in] must be [number[]], found value [("a", "b", "c")] type [(string, string, string)]', ]); - testErrorsAndWarnings('from a_index | eval numberField in ("a", "b", "c")', [ + testErrorsAndWarnings('from a_index | eval doubleField in ("a", "b", "c")', [ // 'Argument of [in] must be [number[]], found value [("a", "b", "c")] type [(string, string, string)]', ]); - testErrorsAndWarnings('from a_index | eval numberField not in ("a", "b", "c")', [ + testErrorsAndWarnings('from a_index | eval doubleField not in ("a", "b", "c")', [ // 'Argument of [not_in] must be [number[]], found value [("a", "b", "c")] type [(string, string, string)]', ]); - testErrorsAndWarnings('from a_index | eval numberField not in (1, 2, 3, stringField)', [ - // 'Argument of [not_in] must be [number[]], found value [(1, 2, 3, stringField)] type [(number, number, number, string)]', + testErrorsAndWarnings('from a_index | eval doubleField not in (1, 2, 3, textField)', [ + // 'Argument of [not_in] must be [number[]], found value [(1, 2, 3, textField)] type [(number, number, number, string)]', ]); - testErrorsAndWarnings('from a_index | eval avg(numberField)', [ + testErrorsAndWarnings('from a_index | eval avg(doubleField)', [ 'EVAL does not support function avg', ]); testErrorsAndWarnings( - 'from a_index | stats avg(numberField) | eval `avg(numberField)` + 1', + 'from a_index | stats avg(doubleField) | eval `avg(doubleField)` + 1', [] ); testErrorsAndWarnings('from a_index | eval not', [ @@ -1167,17 +1203,17 @@ describe('validation logic', () => { "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - testErrorsAndWarnings('from a_index | eval stringField in stringField', [ - "SyntaxError: missing '(' at 'stringField'", + testErrorsAndWarnings('from a_index | eval textField in textField', [ + "SyntaxError: missing '(' at 'textField'", "SyntaxError: mismatched input '<EOF>' expecting {',', ')'}", ]); - testErrorsAndWarnings('from a_index | eval stringField in stringField)', [ - "SyntaxError: missing '(' at 'stringField'", + testErrorsAndWarnings('from a_index | eval textField in textField)', [ + "SyntaxError: missing '(' at 'textField'", 'Error: [in] function expects exactly 2 arguments, got 1.', ]); - testErrorsAndWarnings('from a_index | eval stringField not in stringField', [ - "SyntaxError: missing '(' at 'stringField'", + testErrorsAndWarnings('from a_index | eval textField not in textField', [ + "SyntaxError: missing '(' at 'textField'", "SyntaxError: mismatched input '<EOF>' expecting {',', ')'}", ]); @@ -1236,8 +1272,8 @@ describe('validation logic', () => { ]); for (const op of ['*', '/', '%']) { testErrorsAndWarnings(`from a_index | eval var = now() ${op} 1 ${unit}`, [ - `Argument of [${op}] must be [number], found value [now()] type [date]`, - `Argument of [${op}] must be [number], found value [1 ${unit}] type [duration]`, + `Argument of [${op}] must be [double], found value [now()] type [date]`, + `Argument of [${op}] must be [double], found value [1 ${unit}] type [duration]`, ]); } } @@ -1250,26 +1286,26 @@ describe('validation logic', () => { ]); testErrorsAndWarnings('from a_index | sort "field" ', []); testErrorsAndWarnings('from a_index | sort wrongField ', ['Unknown column [wrongField]']); - testErrorsAndWarnings('from a_index | sort numberField, ', [ + testErrorsAndWarnings('from a_index | sort doubleField, ', [ "SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); - testErrorsAndWarnings('from a_index | sort numberField, stringField', []); + testErrorsAndWarnings('from a_index | sort doubleField, textField', []); for (const dir of ['desc', 'asc']) { testErrorsAndWarnings(`from a_index | sort "field" ${dir} `, []); - testErrorsAndWarnings(`from a_index | sort numberField ${dir} `, []); - testErrorsAndWarnings(`from a_index | sort numberField ${dir} nulls `, [ + testErrorsAndWarnings(`from a_index | sort doubleField ${dir} `, []); + testErrorsAndWarnings(`from a_index | sort doubleField ${dir} nulls `, [ "SyntaxError: missing {'first', 'last'} at '<EOF>'", ]); for (const nullDir of ['first', 'last']) { - testErrorsAndWarnings(`from a_index | sort numberField ${dir} nulls ${nullDir}`, []); - testErrorsAndWarnings(`from a_index | sort numberField ${dir} ${nullDir}`, [ + testErrorsAndWarnings(`from a_index | sort doubleField ${dir} nulls ${nullDir}`, []); + testErrorsAndWarnings(`from a_index | sort doubleField ${dir} ${nullDir}`, [ `SyntaxError: extraneous input '${nullDir}' expecting <EOF>`, ]); } } for (const nullDir of ['first', 'last']) { - testErrorsAndWarnings(`from a_index | sort numberField nulls ${nullDir}`, []); - testErrorsAndWarnings(`from a_index | sort numberField ${nullDir}`, [ + testErrorsAndWarnings(`from a_index | sort doubleField nulls ${nullDir}`, []); + testErrorsAndWarnings(`from a_index | sort doubleField ${nullDir}`, [ `SyntaxError: extraneous input '${nullDir}' expecting <EOF>`, ]); } @@ -1279,18 +1315,18 @@ describe('validation logic', () => { describe('sorting by expressions', () => { // SORT accepts complex expressions testErrorsAndWarnings( - 'from a_index | sort abs(numberField) - to_long(stringField) desc nulls first', + 'from a_index | sort abs(doubleField) - to_long(textField) desc nulls first', [] ); // Expression parts are also validated - testErrorsAndWarnings('from a_index | sort sin(stringField)', [ - 'Argument of [sin] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | sort sin(textField)', [ + 'Argument of [sin] must be [double], found value [textField] type [text]', ]); // Expression parts are also validated - testErrorsAndWarnings('from a_index | sort numberField + stringField', [ - 'Argument of [+] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | sort doubleField + textField', [ + 'Argument of [+] must be [double], found value [textField] type [text]', ]); }); }); @@ -1364,52 +1400,52 @@ describe('validation logic', () => { "SyntaxError: mismatched input 'is' expecting <EOF>", 'Unknown column [this]', ]); - testErrorsAndWarnings(`from a_index | enrich policy on stringField with `, [ + testErrorsAndWarnings(`from a_index | enrich policy on textField with `, [ "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN", ]); - testErrorsAndWarnings(`from a_index | enrich policy on stringField with var0 `, [ + testErrorsAndWarnings(`from a_index | enrich policy on textField with var0 `, [ 'Unknown column [var0]', ]); - testErrorsAndWarnings(`from a_index |enrich policy on numberField with var0 = `, [ + testErrorsAndWarnings(`from a_index |enrich policy on doubleField with var0 = `, [ "SyntaxError: missing ID_PATTERN at '<EOF>'", 'Unknown column [var0]', ]); - testErrorsAndWarnings(`from a_index | enrich policy on stringField with var0 = c `, [ + testErrorsAndWarnings(`from a_index | enrich policy on textField with var0 = c `, [ 'Unknown column [var0]', `Unknown column [c]`, ]); // need to re-enable once the fields/variables become location aware - // testErrorsAndWarnings(`from a_index | enrich policy on stringField with var0 = stringField `, [ - // `Unknown column [stringField]`, + // testErrorsAndWarnings(`from a_index | enrich policy on textField with var0 = textField `, [ + // `Unknown column [textField]`, // ]); - testErrorsAndWarnings(`from a_index |enrich policy on numberField with var0 = , `, [ + testErrorsAndWarnings(`from a_index |enrich policy on doubleField with var0 = , `, [ "SyntaxError: missing ID_PATTERN at ','", "SyntaxError: mismatched input '<EOF>' expecting ID_PATTERN", 'Unknown column [var0]', ]); testErrorsAndWarnings( - `from a_index | enrich policy on stringField with var0 = otherField, var1 `, + `from a_index | enrich policy on textField with var0 = otherField, var1 `, ['Unknown column [var1]'] ); testErrorsAndWarnings( - `from a_index | enrich policy on stringField with var0 = otherField `, + `from a_index | enrich policy on textField with var0 = otherField `, [] ); testErrorsAndWarnings( - `from a_index | enrich policy on stringField with var0 = otherField, yetAnotherField `, + `from a_index | enrich policy on textField with var0 = otherField, yetAnotherField `, [] ); testErrorsAndWarnings( - `from a_index |enrich policy on numberField with var0 = otherField, var1 = `, + `from a_index |enrich policy on doubleField with var0 = otherField, var1 = `, ["SyntaxError: missing ID_PATTERN at '<EOF>'", 'Unknown column [var1]'] ); testErrorsAndWarnings( - `from a_index | enrich policy on stringField with var0 = otherField, var1 = yetAnotherField`, + `from a_index | enrich policy on textField with var0 = otherField, var1 = yetAnotherField`, [] ); testErrorsAndWarnings( - 'from a_index | enrich policy on stringField with var0 = otherField, `this``is fine` = yetAnotherField', + 'from a_index | enrich policy on textField with var0 = otherField, `this``is fine` = yetAnotherField', [] ); testErrorsAndWarnings(`from a_index | enrich policy with `, [ @@ -1425,14 +1461,14 @@ describe('validation logic', () => { describe('shadowing', () => { testErrorsAndWarnings( - 'from a_index | eval stringField = 5', + 'from a_index | eval textField = 5', [], - ['Column [stringField] of type string has been overwritten as new type: number'] + ['Column [textField] of type text has been overwritten as new type: integer'] ); testErrorsAndWarnings( - 'from a_index | eval numberField = "5"', + 'from a_index | eval doubleField = "5"', [], - ['Column [numberField] of type number has been overwritten as new type: string'] + ['Column [doubleField] of type double has been overwritten as new type: string'] ); }); @@ -1469,7 +1505,7 @@ describe('validation logic', () => { for (const nesting of NESTED_DEPTHS) { // start with a quotable expression - const expr = 'round(numberField) + 1'; + const expr = 'round(doubleField) + 1'; const startingQuery = `from a_index | eval ${expr}`; // now pipe for each nesting level a new eval command that appends a +1 to the previous quoted expression const finalQuery = `${startingQuery} | ${Array(nesting) @@ -1554,7 +1590,7 @@ describe('validation logic', () => { it(`should not crash if no callbacks are available`, async () => { try { await validateQuery( - `from a_index | eval b = a | enrich policy | dissect stringField "%{firstWord}"`, + `from a_index | eval b = a | enrich policy | dissect textField "%{firstWord}"`, getAstAndSyntaxErrors, undefined, { @@ -1571,7 +1607,7 @@ describe('validation logic', () => { it(`should not crash if no callbacks are passed`, async () => { try { await validateQuery( - `from a_index | eval b = a | enrich policy | dissect stringField "%{firstWord}"`, + `from a_index | eval b = a | enrich policy | dissect textField "%{firstWord}"`, getAstAndSyntaxErrors ); } catch { @@ -1582,40 +1618,47 @@ describe('validation logic', () => { describe('inline casting', () => { // accepts casting - testErrorsAndWarnings('from a_index | eval 1::string', []); + testErrorsAndWarnings('from a_index | eval 1::keyword', []); // errors if the cast type is invalid // testErrorsAndWarnings('from a_index | eval 1::foo', ['Invalid type [foo] for casting']); // accepts casting with multiple types - testErrorsAndWarnings('from a_index | eval 1::string::long::double', []); + testErrorsAndWarnings('from a_index | eval 1::keyword::long::double', []); // takes into account casting in function arguments testErrorsAndWarnings('from a_index | eval trim("23"::double)', [ - 'Argument of [trim] must be [string], found value ["23"::double] type [double]', + 'Argument of [trim] must be [keyword], found value ["23"::double] type [double]', ]); - testErrorsAndWarnings('from a_index | eval trim(23::string)', []); + testErrorsAndWarnings('from a_index | eval trim(23::keyword)', []); testErrorsAndWarnings('from a_index | eval 1 + "2"::long', []); + testErrorsAndWarnings('from a_index | eval 1 + "2"::LONG', []); + testErrorsAndWarnings('from a_index | eval 1 + "2"::Long', []); + testErrorsAndWarnings('from a_index | eval 1 + "2"::LoNg', []); + testErrorsAndWarnings('from a_index | eval 1 + "2"', [ // just a counter-case to make sure the previous test is meaningful - 'Argument of [+] must be [number], found value ["2"] type [string]', + 'Argument of [+] must be [date_period], found value [1] type [integer]', ]); testErrorsAndWarnings( - 'from a_index | eval trim(to_double("23")::string::double::long::string::double)', + 'from a_index | eval trim(to_double("23")::keyword::double::long::keyword::double)', [ - 'Argument of [trim] must be [string], found value [to_double("23")::string::double::long::string::double] type [double]', + 'Argument of [trim] must be [keyword], found value [to_double("23")::keyword::double::long::keyword::double] type [double]', ] ); - // accepts elasticsearch subtypes and type aliases like int and keyword - // (once https://github.com/elastic/kibana/issues/174710 is done this won't be a special case anymore) testErrorsAndWarnings('from a_index | eval CEIL(23::long)', []); testErrorsAndWarnings('from a_index | eval CEIL(23::unsigned_long)', []); testErrorsAndWarnings('from a_index | eval CEIL(23::int)', []); testErrorsAndWarnings('from a_index | eval CEIL(23::integer)', []); + testErrorsAndWarnings('from a_index | eval CEIL(23::Integer)', []); testErrorsAndWarnings('from a_index | eval CEIL(23::double)', []); + testErrorsAndWarnings('from a_index | eval CEIL(23::DOUBLE)', []); + testErrorsAndWarnings('from a_index | eval CEIL(23::doubla)', [ + 'Argument of [ceil] must be [double], found value [23::doubla] type [doubla]', + ]); - testErrorsAndWarnings('from a_index | eval TRIM(23::string)', []); + testErrorsAndWarnings('from a_index | eval TRIM(23::keyword)', []); testErrorsAndWarnings('from a_index | eval TRIM(23::text)', []); testErrorsAndWarnings('from a_index | eval TRIM(23::keyword)', []); @@ -1630,439 +1673,501 @@ describe('validation logic', () => { // testErrorsAndWarnings('from a_index | eval 23::cartesian_point', ['wrong type!']); // still validates nested functions when they are casted - testErrorsAndWarnings('from a_index | eval to_lower(trim(numberField)::string)', [ - 'Argument of [trim] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | eval to_lower(trim(doubleField)::keyword)', [ + 'Argument of [trim] must be [keyword], found value [doubleField] type [double]', ]); testErrorsAndWarnings( - 'from a_index | eval to_upper(trim(numberField)::string::string::string::string)', - ['Argument of [trim] must be [string], found value [numberField] type [number]'] + 'from a_index | eval to_upper(trim(doubleField)::keyword::keyword::keyword::keyword)', + ['Argument of [trim] must be [keyword], found value [doubleField] type [double]'] ); testErrorsAndWarnings( - 'from a_index | eval to_lower(to_upper(trim(numberField)::string)::string)', - ['Argument of [trim] must be [string], found value [numberField] type [number]'] + 'from a_index | eval to_lower(to_upper(trim(doubleField)::keyword)::keyword)', + ['Argument of [trim] must be [keyword], found value [doubleField] type [double]'] ); }); describe(FUNCTION_DESCRIBE_BLOCK_NAME, () => { - describe('date_diff', () => { - testErrorsAndWarnings( - `row var = date_diff("month", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")`, - [] - ); - - testErrorsAndWarnings( - `row var = date_diff("mm", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")`, - [] - ); - - testErrorsAndWarnings( - `row var = date_diff("bogus", "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")`, - [], - [ - 'Invalid option ["bogus"] for date_diff. Supported options: ["year", "years", "yy", "yyyy", "quarter", "quarters", "qq", "q", "month", "months", "mm", "m", "dayofyear", "dy", "y", "day", "days", "dd", "d", "week", "weeks", "wk", "ww", "weekday", "weekdays", "dw", "hour", "hours", "hh", "minute", "minutes", "mi", "n", "second", "seconds", "ss", "s", "millisecond", "milliseconds", "ms", "microsecond", "microseconds", "mcs", "nanosecond", "nanoseconds", "ns"].', - ] - ); - - testErrorsAndWarnings( - `from a_index | eval date_diff(stringField, "2023-12-02T11:00:00.000Z", "2023-12-02T11:00:00.000Z")`, - [] - ); - - testErrorsAndWarnings( - `from a_index | eval date_diff("month", dateField, "2023-12-02T11:00:00.000Z")`, - [] - ); - - testErrorsAndWarnings( - `from a_index | eval date_diff("month", "2023-12-02T11:00:00.000Z", dateField)`, - [] - ); - - testErrorsAndWarnings(`from a_index | eval date_diff("month", stringField, dateField)`, [ - 'Argument of [date_diff] must be [date], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings(`from a_index | eval date_diff("month", dateField, stringField)`, [ - 'Argument of [date_diff] must be [date], found value [stringField] type [string]', - ]); - testErrorsAndWarnings( - 'from a_index | eval var = date_diff("year", dateField, dateField)', - [] - ); - testErrorsAndWarnings('from a_index | eval date_diff("year", dateField, dateField)', []); - - testErrorsAndWarnings( - 'from a_index | eval var = date_diff("year", to_datetime(stringField), to_datetime(stringField))', - [] - ); - - testErrorsAndWarnings( - 'from a_index | eval date_diff(numberField, stringField, stringField)', - [ - 'Argument of [date_diff] must be [string], found value [numberField] type [number]', - 'Argument of [date_diff] must be [date], found value [stringField] type [string]', - 'Argument of [date_diff] must be [date], found value [stringField] type [string]', - ] - ); - - testErrorsAndWarnings( - 'from a_index | eval date_diff("year", dateField, dateField, extraArg)', - ['Error: [date_diff] function expects exactly 3 arguments, got 4.'] - ); - - testErrorsAndWarnings('from a_index | sort date_diff("year", dateField, dateField)', []); - - testErrorsAndWarnings( - 'from a_index | eval var = date_diff("year", to_datetime(dateField), to_datetime(dateField))', - [] - ); - - testErrorsAndWarnings( - 'from a_index | eval date_diff(booleanField, booleanField, booleanField)', - [ - 'Argument of [date_diff] must be [string], found value [booleanField] type [boolean]', - 'Argument of [date_diff] must be [date], found value [booleanField] type [boolean]', - 'Argument of [date_diff] must be [date], found value [booleanField] type [boolean]', - ] - ); - testErrorsAndWarnings('from a_index | eval date_diff(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval date_diff(nullVar, nullVar, nullVar)', []); - - testErrorsAndWarnings('from a_index | eval date_diff("year", "2022", "2022")', []); - testErrorsAndWarnings( - 'from a_index | eval date_diff("year", concat("20", "22"), concat("20", "22"))', - [ - 'Argument of [date_diff] must be [date], found value [concat("20","22")] type [string]', - 'Argument of [date_diff] must be [date], found value [concat("20","22")] type [string]', - ] - ); - }); - describe('abs', () => { + testErrorsAndWarnings('row var = abs(5.5)', []); + testErrorsAndWarnings('row abs(5.5)', []); + testErrorsAndWarnings('row var = abs(to_double(true))', []); testErrorsAndWarnings('row var = abs(5)', []); testErrorsAndWarnings('row abs(5)', []); - testErrorsAndWarnings('row var = abs(to_integer("a"))', []); + testErrorsAndWarnings('row var = abs(to_integer(true))', []); - testErrorsAndWarnings('row var = abs("a")', [ - 'Argument of [abs] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = abs(true)', [ + 'Argument of [abs] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where abs(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where abs(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where abs(stringField) > 0', [ - 'Argument of [abs] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where abs(booleanField) > 0', [ + 'Argument of [abs] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = abs(numberField)', []); - testErrorsAndWarnings('from a_index | eval abs(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = abs(to_integer(stringField))', []); - - testErrorsAndWarnings('from a_index | eval abs(stringField)', [ - 'Argument of [abs] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings('from a_index | where abs(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where abs(longField) > 0', []); + testErrorsAndWarnings('from a_index | where abs(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = abs(doubleField)', []); + testErrorsAndWarnings('from a_index | eval abs(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = abs(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval abs(numberField, extraArg)', [ - 'Error: [abs] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval abs(booleanField)', [ + 'Argument of [abs] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = abs(*)', [ 'Using wildcards (*) in abs is not allowed', ]); - testErrorsAndWarnings('from a_index | sort abs(numberField)', []); - testErrorsAndWarnings('row var = abs(to_integer(true))', []); - - testErrorsAndWarnings('row var = abs(true)', [ - 'Argument of [abs] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where abs(booleanField) > 0', [ - 'Argument of [abs] must be [number], found value [booleanField] type [boolean]', - ]); - + testErrorsAndWarnings('from a_index | eval var = abs(integerField)', []); + testErrorsAndWarnings('from a_index | eval abs(integerField)', []); testErrorsAndWarnings('from a_index | eval var = abs(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = abs(longField)', []); + testErrorsAndWarnings('from a_index | eval abs(longField)', []); + testErrorsAndWarnings('from a_index | eval var = abs(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval abs(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval abs(booleanField)', [ - 'Argument of [abs] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval abs(doubleField, extraArg)', [ + 'Error: [abs] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort abs(doubleField)', []); testErrorsAndWarnings('from a_index | eval abs(null)', []); testErrorsAndWarnings('row nullVar = null | eval abs(nullVar)', []); }); describe('acos', () => { + testErrorsAndWarnings('row var = acos(5.5)', []); + testErrorsAndWarnings('row acos(5.5)', []); + testErrorsAndWarnings('row var = acos(to_double(true))', []); testErrorsAndWarnings('row var = acos(5)', []); testErrorsAndWarnings('row acos(5)', []); - testErrorsAndWarnings('row var = acos(to_integer("a"))', []); + testErrorsAndWarnings('row var = acos(to_integer(true))', []); - testErrorsAndWarnings('row var = acos("a")', [ - 'Argument of [acos] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = acos(true)', [ + 'Argument of [acos] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where acos(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where acos(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where acos(stringField) > 0', [ - 'Argument of [acos] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where acos(booleanField) > 0', [ + 'Argument of [acos] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = acos(numberField)', []); - testErrorsAndWarnings('from a_index | eval acos(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = acos(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | where acos(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where acos(longField) > 0', []); + testErrorsAndWarnings('from a_index | where acos(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = acos(doubleField)', []); + testErrorsAndWarnings('from a_index | eval acos(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = acos(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval acos(stringField)', [ - 'Argument of [acos] must be [number], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings('from a_index | eval acos(numberField, extraArg)', [ - 'Error: [acos] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval acos(booleanField)', [ + 'Argument of [acos] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = acos(*)', [ 'Using wildcards (*) in acos is not allowed', ]); - testErrorsAndWarnings('from a_index | sort acos(numberField)', []); - testErrorsAndWarnings('row var = acos(to_integer(true))', []); - - testErrorsAndWarnings('row var = acos(true)', [ - 'Argument of [acos] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where acos(booleanField) > 0', [ - 'Argument of [acos] must be [number], found value [booleanField] type [boolean]', - ]); - + testErrorsAndWarnings('from a_index | eval var = acos(integerField)', []); + testErrorsAndWarnings('from a_index | eval acos(integerField)', []); testErrorsAndWarnings('from a_index | eval var = acos(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = acos(longField)', []); + testErrorsAndWarnings('from a_index | eval acos(longField)', []); + testErrorsAndWarnings('from a_index | eval var = acos(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval acos(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval acos(booleanField)', [ - 'Argument of [acos] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval acos(doubleField, extraArg)', [ + 'Error: [acos] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort acos(doubleField)', []); testErrorsAndWarnings('from a_index | eval acos(null)', []); testErrorsAndWarnings('row nullVar = null | eval acos(nullVar)', []); }); describe('asin', () => { + testErrorsAndWarnings('row var = asin(5.5)', []); + testErrorsAndWarnings('row asin(5.5)', []); + testErrorsAndWarnings('row var = asin(to_double(true))', []); testErrorsAndWarnings('row var = asin(5)', []); testErrorsAndWarnings('row asin(5)', []); - testErrorsAndWarnings('row var = asin(to_integer("a"))', []); + testErrorsAndWarnings('row var = asin(to_integer(true))', []); - testErrorsAndWarnings('row var = asin("a")', [ - 'Argument of [asin] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = asin(true)', [ + 'Argument of [asin] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where asin(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where asin(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where asin(stringField) > 0', [ - 'Argument of [asin] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where asin(booleanField) > 0', [ + 'Argument of [asin] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = asin(numberField)', []); - testErrorsAndWarnings('from a_index | eval asin(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = asin(to_integer(stringField))', []); - - testErrorsAndWarnings('from a_index | eval asin(stringField)', [ - 'Argument of [asin] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings('from a_index | where asin(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where asin(longField) > 0', []); + testErrorsAndWarnings('from a_index | where asin(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = asin(doubleField)', []); + testErrorsAndWarnings('from a_index | eval asin(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = asin(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval asin(numberField, extraArg)', [ - 'Error: [asin] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval asin(booleanField)', [ + 'Argument of [asin] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = asin(*)', [ 'Using wildcards (*) in asin is not allowed', ]); - testErrorsAndWarnings('from a_index | sort asin(numberField)', []); - testErrorsAndWarnings('row var = asin(to_integer(true))', []); - - testErrorsAndWarnings('row var = asin(true)', [ - 'Argument of [asin] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where asin(booleanField) > 0', [ - 'Argument of [asin] must be [number], found value [booleanField] type [boolean]', - ]); - + testErrorsAndWarnings('from a_index | eval var = asin(integerField)', []); + testErrorsAndWarnings('from a_index | eval asin(integerField)', []); testErrorsAndWarnings('from a_index | eval var = asin(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = asin(longField)', []); + testErrorsAndWarnings('from a_index | eval asin(longField)', []); + testErrorsAndWarnings('from a_index | eval var = asin(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval asin(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval asin(booleanField)', [ - 'Argument of [asin] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval asin(doubleField, extraArg)', [ + 'Error: [asin] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort asin(doubleField)', []); testErrorsAndWarnings('from a_index | eval asin(null)', []); testErrorsAndWarnings('row nullVar = null | eval asin(nullVar)', []); }); describe('atan', () => { + testErrorsAndWarnings('row var = atan(5.5)', []); + testErrorsAndWarnings('row atan(5.5)', []); + testErrorsAndWarnings('row var = atan(to_double(true))', []); testErrorsAndWarnings('row var = atan(5)', []); testErrorsAndWarnings('row atan(5)', []); - testErrorsAndWarnings('row var = atan(to_integer("a"))', []); + testErrorsAndWarnings('row var = atan(to_integer(true))', []); - testErrorsAndWarnings('row var = atan("a")', [ - 'Argument of [atan] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = atan(true)', [ + 'Argument of [atan] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where atan(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where atan(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where atan(stringField) > 0', [ - 'Argument of [atan] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where atan(booleanField) > 0', [ + 'Argument of [atan] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = atan(numberField)', []); - testErrorsAndWarnings('from a_index | eval atan(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = atan(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | where atan(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where atan(longField) > 0', []); + testErrorsAndWarnings('from a_index | where atan(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = atan(doubleField)', []); + testErrorsAndWarnings('from a_index | eval atan(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = atan(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval atan(stringField)', [ - 'Argument of [atan] must be [number], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings('from a_index | eval atan(numberField, extraArg)', [ - 'Error: [atan] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval atan(booleanField)', [ + 'Argument of [atan] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = atan(*)', [ 'Using wildcards (*) in atan is not allowed', ]); - testErrorsAndWarnings('from a_index | sort atan(numberField)', []); - testErrorsAndWarnings('row var = atan(to_integer(true))', []); - - testErrorsAndWarnings('row var = atan(true)', [ - 'Argument of [atan] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where atan(booleanField) > 0', [ - 'Argument of [atan] must be [number], found value [booleanField] type [boolean]', - ]); - + testErrorsAndWarnings('from a_index | eval var = atan(integerField)', []); + testErrorsAndWarnings('from a_index | eval atan(integerField)', []); testErrorsAndWarnings('from a_index | eval var = atan(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = atan(longField)', []); + testErrorsAndWarnings('from a_index | eval atan(longField)', []); + testErrorsAndWarnings('from a_index | eval var = atan(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval atan(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval atan(booleanField)', [ - 'Argument of [atan] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval atan(doubleField, extraArg)', [ + 'Error: [atan] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort atan(doubleField)', []); testErrorsAndWarnings('from a_index | eval atan(null)', []); testErrorsAndWarnings('row nullVar = null | eval atan(nullVar)', []); }); describe('atan2', () => { + testErrorsAndWarnings('row var = atan2(5.5, 5.5)', []); + testErrorsAndWarnings('row atan2(5.5, 5.5)', []); + testErrorsAndWarnings('row var = atan2(to_double(true), to_double(true))', []); + testErrorsAndWarnings('row var = atan2(5.5, 5)', []); + testErrorsAndWarnings('row atan2(5.5, 5)', []); + testErrorsAndWarnings('row var = atan2(to_double(true), to_integer(true))', []); + testErrorsAndWarnings('row var = atan2(to_double(true), 5)', []); + testErrorsAndWarnings('row var = atan2(5, 5.5)', []); + testErrorsAndWarnings('row atan2(5, 5.5)', []); + testErrorsAndWarnings('row var = atan2(to_integer(true), to_double(true))', []); testErrorsAndWarnings('row var = atan2(5, 5)', []); testErrorsAndWarnings('row atan2(5, 5)', []); - testErrorsAndWarnings('row var = atan2(to_integer("a"), to_integer("a"))', []); + testErrorsAndWarnings('row var = atan2(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = atan2(to_integer(true), 5)', []); + testErrorsAndWarnings('row var = atan2(5, to_double(true))', []); + testErrorsAndWarnings('row var = atan2(5, to_integer(true))', []); - testErrorsAndWarnings('row var = atan2("a", "a")', [ - 'Argument of [atan2] must be [number], found value ["a"] type [string]', - 'Argument of [atan2] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = atan2(true, true)', [ + 'Argument of [atan2] must be [double], found value [true] type [boolean]', + 'Argument of [atan2] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where atan2(numberField, numberField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(doubleField, doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where atan2(stringField, stringField) > 0', [ - 'Argument of [atan2] must be [number], found value [stringField] type [string]', - 'Argument of [atan2] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where atan2(booleanField, booleanField) > 0', [ + 'Argument of [atan2] must be [double], found value [booleanField] type [boolean]', + 'Argument of [atan2] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = atan2(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval atan2(numberField, numberField)', []); - + testErrorsAndWarnings('from a_index | where atan2(doubleField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(doubleField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(doubleField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(integerField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(integerField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(integerField, longField) > 0', []); testErrorsAndWarnings( - 'from a_index | eval var = atan2(to_integer(stringField), to_integer(stringField))', + 'from a_index | where atan2(integerField, unsignedLongField) > 0', [] ); - - testErrorsAndWarnings('from a_index | eval atan2(stringField, stringField)', [ - 'Argument of [atan2] must be [number], found value [stringField] type [string]', - 'Argument of [atan2] must be [number], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings('from a_index | eval atan2(numberField, numberField, extraArg)', [ - 'Error: [atan2] function expects exactly 2 arguments, got 3.', - ]); - - testErrorsAndWarnings('from a_index | sort atan2(numberField, numberField)', []); - testErrorsAndWarnings('row var = atan2(to_integer(true), to_integer(true))', []); - - testErrorsAndWarnings('row var = atan2(true, true)', [ - 'Argument of [atan2] must be [number], found value [true] type [boolean]', - 'Argument of [atan2] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where atan2(booleanField, booleanField) > 0', [ - 'Argument of [atan2] must be [number], found value [booleanField] type [boolean]', - 'Argument of [atan2] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | where atan2(longField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(longField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(longField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(longField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where atan2(unsignedLongField, doubleField) > 0', []); + testErrorsAndWarnings( + 'from a_index | where atan2(unsignedLongField, integerField) > 0', + [] + ); + testErrorsAndWarnings('from a_index | where atan2(unsignedLongField, longField) > 0', []); + testErrorsAndWarnings( + 'from a_index | where atan2(unsignedLongField, unsignedLongField) > 0', + [] + ); + testErrorsAndWarnings('from a_index | eval var = atan2(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval atan2(doubleField, doubleField)', []); testErrorsAndWarnings( - 'from a_index | eval var = atan2(to_integer(booleanField), to_integer(booleanField))', + 'from a_index | eval var = atan2(to_double(booleanField), to_double(booleanField))', [] ); testErrorsAndWarnings('from a_index | eval atan2(booleanField, booleanField)', [ - 'Argument of [atan2] must be [number], found value [booleanField] type [boolean]', - 'Argument of [atan2] must be [number], found value [booleanField] type [boolean]', + 'Argument of [atan2] must be [double], found value [booleanField] type [boolean]', + 'Argument of [atan2] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval atan2(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval atan2(nullVar, nullVar)', []); - }); - describe('case', () => { - testErrorsAndWarnings('row var = case(true, "a")', []); - testErrorsAndWarnings('row case(true, "a")', []); - testErrorsAndWarnings('from a_index | eval var = case(booleanField, stringField)', []); - testErrorsAndWarnings('from a_index | eval case(booleanField, stringField)', []); - testErrorsAndWarnings('from a_index | sort case(booleanField, stringField)', []); + testErrorsAndWarnings('from a_index | eval var = atan2(doubleField, integerField)', []); + testErrorsAndWarnings('from a_index | eval atan2(doubleField, integerField)', []); - testErrorsAndWarnings('row var = case(to_cartesianpoint("POINT (30 10)"), true)', [ - 'Argument of [case] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', - ]); - testErrorsAndWarnings('from a_index | eval case(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval case(nullVar, nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(to_double(booleanField), to_integer(booleanField))', + [] + ); - describe('ceil', () => { - testErrorsAndWarnings('row var = ceil(5)', []); - testErrorsAndWarnings('row ceil(5)', []); - testErrorsAndWarnings('row var = ceil(to_integer("a"))', []); + testErrorsAndWarnings('from a_index | eval var = atan2(doubleField, longField)', []); + testErrorsAndWarnings('from a_index | eval atan2(doubleField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(to_double(booleanField), longField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(doubleField, unsignedLongField)', + [] + ); + testErrorsAndWarnings('from a_index | eval atan2(doubleField, unsignedLongField)', []); - testErrorsAndWarnings('row var = ceil("a")', [ - 'Argument of [ceil] must be [number], found value ["a"] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(to_double(booleanField), unsignedLongField)', + [] + ); - testErrorsAndWarnings('from a_index | where ceil(numberField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = atan2(integerField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval atan2(integerField, doubleField)', []); - testErrorsAndWarnings('from a_index | where ceil(stringField) > 0', [ - 'Argument of [ceil] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(to_integer(booleanField), to_double(booleanField))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = ceil(numberField)', []); - testErrorsAndWarnings('from a_index | eval ceil(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = ceil(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | eval var = atan2(integerField, integerField)', []); + testErrorsAndWarnings('from a_index | eval atan2(integerField, integerField)', []); - testErrorsAndWarnings('from a_index | eval ceil(stringField)', [ - 'Argument of [ceil] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(to_integer(booleanField), to_integer(booleanField))', + [] + ); - testErrorsAndWarnings('from a_index | eval ceil(numberField, extraArg)', [ - 'Error: [ceil] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval var = atan2(integerField, longField)', []); + testErrorsAndWarnings('from a_index | eval atan2(integerField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(to_integer(booleanField), longField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(integerField, unsignedLongField)', + [] + ); + testErrorsAndWarnings('from a_index | eval atan2(integerField, unsignedLongField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = atan2(to_integer(booleanField), unsignedLongField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = atan2(longField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval atan2(longField, doubleField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(longField, to_double(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = atan2(longField, integerField)', []); + testErrorsAndWarnings('from a_index | eval atan2(longField, integerField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(longField, to_integer(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = atan2(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval atan2(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval var = atan2(longField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval atan2(longField, unsignedLongField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = atan2(unsignedLongField, doubleField)', + [] + ); + testErrorsAndWarnings('from a_index | eval atan2(unsignedLongField, doubleField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = atan2(unsignedLongField, to_double(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = atan2(unsignedLongField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | eval atan2(unsignedLongField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = atan2(unsignedLongField, to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = atan2(unsignedLongField, longField)', []); + testErrorsAndWarnings('from a_index | eval atan2(unsignedLongField, longField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = atan2(unsignedLongField, unsignedLongField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval atan2(unsignedLongField, unsignedLongField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval atan2(doubleField, doubleField, extraArg)', [ + 'Error: [atan2] function expects exactly 2 arguments, got 3.', ]); - testErrorsAndWarnings('from a_index | eval var = ceil(*)', [ - 'Using wildcards (*) in ceil is not allowed', + testErrorsAndWarnings('from a_index | sort atan2(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval atan2(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval atan2(nullVar, nullVar)', []); + }); + + describe('cbrt', () => { + testErrorsAndWarnings('row var = cbrt(5.5)', []); + testErrorsAndWarnings('row cbrt(5.5)', []); + testErrorsAndWarnings('row var = cbrt(to_double(true))', []); + testErrorsAndWarnings('row var = cbrt(5)', []); + testErrorsAndWarnings('row cbrt(5)', []); + testErrorsAndWarnings('row var = cbrt(to_integer(true))', []); + + testErrorsAndWarnings('row var = cbrt(true)', [ + 'Argument of [cbrt] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where cbrt(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where cbrt(booleanField) > 0', [ + 'Argument of [cbrt] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where cbrt(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where cbrt(longField) > 0', []); + testErrorsAndWarnings('from a_index | where cbrt(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = cbrt(doubleField)', []); + testErrorsAndWarnings('from a_index | eval cbrt(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = cbrt(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval cbrt(booleanField)', [ + 'Argument of [cbrt] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort ceil(numberField)', []); + testErrorsAndWarnings('from a_index | eval var = cbrt(*)', [ + 'Using wildcards (*) in cbrt is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = cbrt(integerField)', []); + testErrorsAndWarnings('from a_index | eval cbrt(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = cbrt(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = cbrt(longField)', []); + testErrorsAndWarnings('from a_index | eval cbrt(longField)', []); + testErrorsAndWarnings('from a_index | eval var = cbrt(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval cbrt(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval cbrt(doubleField, extraArg)', [ + 'Error: [cbrt] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort cbrt(doubleField)', []); + testErrorsAndWarnings('from a_index | eval cbrt(null)', []); + testErrorsAndWarnings('row nullVar = null | eval cbrt(nullVar)', []); + }); + + describe('ceil', () => { + testErrorsAndWarnings('row var = ceil(5.5)', []); + testErrorsAndWarnings('row ceil(5.5)', []); + testErrorsAndWarnings('row var = ceil(to_double(true))', []); + testErrorsAndWarnings('row var = ceil(5)', []); + testErrorsAndWarnings('row ceil(5)', []); testErrorsAndWarnings('row var = ceil(to_integer(true))', []); testErrorsAndWarnings('row var = ceil(true)', [ - 'Argument of [ceil] must be [number], found value [true] type [boolean]', + 'Argument of [ceil] must be [double], found value [true] type [boolean]', ]); + testErrorsAndWarnings('from a_index | where ceil(doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where ceil(booleanField) > 0', [ - 'Argument of [ceil] must be [number], found value [booleanField] type [boolean]', + 'Argument of [ceil] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = ceil(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | where ceil(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where ceil(longField) > 0', []); + testErrorsAndWarnings('from a_index | where ceil(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = ceil(doubleField)', []); + testErrorsAndWarnings('from a_index | eval ceil(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = ceil(to_double(booleanField))', []); testErrorsAndWarnings('from a_index | eval ceil(booleanField)', [ - 'Argument of [ceil] must be [number], found value [booleanField] type [boolean]', + 'Argument of [ceil] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = ceil(*)', [ + 'Using wildcards (*) in ceil is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = ceil(integerField)', []); + testErrorsAndWarnings('from a_index | eval ceil(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = ceil(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = ceil(longField)', []); + testErrorsAndWarnings('from a_index | eval ceil(longField)', []); + testErrorsAndWarnings('from a_index | eval var = ceil(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval ceil(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval ceil(doubleField, extraArg)', [ + 'Error: [ceil] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort ceil(doubleField)', []); testErrorsAndWarnings('from a_index | eval ceil(null)', []); testErrorsAndWarnings('row nullVar = null | eval ceil(nullVar)', []); }); @@ -2070,27 +2175,6 @@ describe('validation logic', () => { describe('cidr_match', () => { testErrorsAndWarnings('row var = cidr_match(to_ip("127.0.0.1"), "a")', []); testErrorsAndWarnings('row cidr_match(to_ip("127.0.0.1"), "a")', []); - testErrorsAndWarnings('row var = cidr_match(to_ip("a"), to_string("a"))', []); - - testErrorsAndWarnings('row var = cidr_match("a", 5)', [ - 'Argument of [cidr_match] must be [ip], found value ["a"] type [string]', - 'Argument of [cidr_match] must be [string], found value [5] type [number]', - ]); - - testErrorsAndWarnings('from a_index | eval var = cidr_match(ipField, stringField)', []); - testErrorsAndWarnings('from a_index | eval cidr_match(ipField, stringField)', []); - - testErrorsAndWarnings( - 'from a_index | eval var = cidr_match(to_ip(stringField), to_string(stringField))', - [] - ); - - testErrorsAndWarnings('from a_index | eval cidr_match(stringField, numberField)', [ - 'Argument of [cidr_match] must be [ip], found value [stringField] type [string]', - 'Argument of [cidr_match] must be [string], found value [numberField] type [number]', - ]); - - testErrorsAndWarnings('from a_index | sort cidr_match(ipField, stringField)', []); testErrorsAndWarnings( 'row var = cidr_match(to_ip(to_ip("127.0.0.1")), to_string(true))', [] @@ -2098,9 +2182,12 @@ describe('validation logic', () => { testErrorsAndWarnings('row var = cidr_match(true, true)', [ 'Argument of [cidr_match] must be [ip], found value [true] type [boolean]', - 'Argument of [cidr_match] must be [string], found value [true] type [boolean]', + 'Argument of [cidr_match] must be [keyword], found value [true] type [boolean]', ]); + testErrorsAndWarnings('from a_index | eval var = cidr_match(ipField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval cidr_match(ipField, keywordField)', []); + testErrorsAndWarnings( 'from a_index | eval var = cidr_match(to_ip(ipField), to_string(booleanField))', [] @@ -2108,181 +2195,146 @@ describe('validation logic', () => { testErrorsAndWarnings('from a_index | eval cidr_match(booleanField, booleanField)', [ 'Argument of [cidr_match] must be [ip], found value [booleanField] type [boolean]', - 'Argument of [cidr_match] must be [string], found value [booleanField] type [boolean]', + 'Argument of [cidr_match] must be [keyword], found value [booleanField] type [boolean]', ]); + + testErrorsAndWarnings('from a_index | eval var = cidr_match(ipField, textField)', []); + testErrorsAndWarnings('from a_index | eval cidr_match(ipField, textField)', []); + testErrorsAndWarnings('from a_index | sort cidr_match(ipField, keywordField)', []); testErrorsAndWarnings('from a_index | eval cidr_match(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval cidr_match(nullVar, nullVar)', []); }); describe('coalesce', () => { - testErrorsAndWarnings('row var = coalesce(5)', []); - testErrorsAndWarnings('row coalesce(5)', []); - testErrorsAndWarnings('row var = coalesce(to_integer(true))', []); - testErrorsAndWarnings('row var = coalesce(5, 5)', []); - testErrorsAndWarnings('row coalesce(5, 5)', []); - testErrorsAndWarnings('row var = coalesce(to_integer(true), to_integer(true))', []); - testErrorsAndWarnings('row var = coalesce(now())', []); - testErrorsAndWarnings('row coalesce(now())', []); - testErrorsAndWarnings('row var = coalesce(to_datetime(now()))', []); - testErrorsAndWarnings('row var = coalesce(now(), now())', []); - testErrorsAndWarnings('row coalesce(now(), now())', []); - testErrorsAndWarnings('row var = coalesce(to_datetime(now()), to_datetime(now()))', []); - testErrorsAndWarnings('row var = coalesce("a")', []); - testErrorsAndWarnings('row coalesce("a")', []); - testErrorsAndWarnings('row var = coalesce(to_string(true))', []); - testErrorsAndWarnings('row var = coalesce("a", "a")', []); - testErrorsAndWarnings('row coalesce("a", "a")', []); - testErrorsAndWarnings('row var = coalesce(to_string(true), to_string(true))', []); testErrorsAndWarnings('row var = coalesce(true)', []); testErrorsAndWarnings('row coalesce(true)', []); testErrorsAndWarnings('row var = coalesce(to_boolean(true))', []); testErrorsAndWarnings('row var = coalesce(true, true)', []); testErrorsAndWarnings('row coalesce(true, true)', []); testErrorsAndWarnings('row var = coalesce(to_boolean(true), to_boolean(true))', []); - testErrorsAndWarnings('row var = coalesce(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row coalesce(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = coalesce(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = coalesce(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row coalesce(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); - - testErrorsAndWarnings( - 'row var = coalesce(to_ip(to_ip("127.0.0.1")), to_ip(to_ip("127.0.0.1")))', - [] - ); - - testErrorsAndWarnings('row var = coalesce(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row coalesce(to_cartesianpoint("POINT (30 10)"))', []); - - testErrorsAndWarnings( - 'row var = coalesce(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', - [] - ); - - testErrorsAndWarnings( - 'row var = coalesce(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', - [] - ); + testErrorsAndWarnings('row var = coalesce(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row coalesce(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); testErrorsAndWarnings( - 'row coalesce(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', - [] + 'row var = coalesce(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] ); testErrorsAndWarnings( - 'row var = coalesce(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'row var = coalesce(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', [] ); - testErrorsAndWarnings('row var = coalesce(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row coalesce(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings( - 'row var = coalesce(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'row coalesce(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'row var = coalesce(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', - [] + 'row var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] ); testErrorsAndWarnings( - 'row coalesce(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'row var = coalesce(to_datetime("2021-01-01T00:00:00Z"), to_datetime("2021-01-01T00:00:00Z"))', [] ); testErrorsAndWarnings( - 'row var = coalesce(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'row coalesce(to_datetime("2021-01-01T00:00:00Z"), to_datetime("2021-01-01T00:00:00Z"))', [] ); - testErrorsAndWarnings('row var = coalesce(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row coalesce(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = coalesce(to_geopoint(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings( - 'row var = coalesce(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'row var = coalesce(to_datetime(to_datetime("2021-01-01T00:00:00Z")), to_datetime(to_datetime("2021-01-01T00:00:00Z")))', [] ); + testErrorsAndWarnings('row var = coalesce(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row coalesce(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); testErrorsAndWarnings( - 'row coalesce(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', - [] + 'row var = coalesce(to_geopoint(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] ); testErrorsAndWarnings( - 'row var = coalesce(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'row var = coalesce(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', [] ); - testErrorsAndWarnings('row var = coalesce(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row coalesce(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = coalesce(to_geoshape(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings( - 'row var = coalesce(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'row coalesce(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'row coalesce(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', - [] + 'row var = coalesce(to_geoshape(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] ); + testErrorsAndWarnings('row var = coalesce(5)', []); + testErrorsAndWarnings('row coalesce(5)', []); + testErrorsAndWarnings('row var = coalesce(to_integer(true))', []); + testErrorsAndWarnings('row var = coalesce(5, 5)', []); + testErrorsAndWarnings('row coalesce(5, 5)', []); + testErrorsAndWarnings('row var = coalesce(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = coalesce(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row coalesce(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); testErrorsAndWarnings( - 'row var = coalesce(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'row var = coalesce(to_ip(to_ip("127.0.0.1")), to_ip(to_ip("127.0.0.1")))', [] ); - testErrorsAndWarnings('row var = coalesce(to_version("1.0.0"))', []); - testErrorsAndWarnings('row coalesce(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = coalesce(to_version("a"))', []); + testErrorsAndWarnings('row var = coalesce("a")', []); + testErrorsAndWarnings('row coalesce("a")', []); + testErrorsAndWarnings('row var = coalesce(to_string(true))', []); + testErrorsAndWarnings('row var = coalesce("a", "a")', []); + testErrorsAndWarnings('row coalesce("a", "a")', []); + testErrorsAndWarnings('row var = coalesce(to_string(true), to_string(true))', []); testErrorsAndWarnings('row var = coalesce(to_version("1.0.0"), to_version("1.0.0"))', []); testErrorsAndWarnings('row coalesce(to_version("1.0.0"), to_version("1.0.0"))', []); testErrorsAndWarnings('row var = coalesce(to_version("a"), to_version("a"))', []); - testErrorsAndWarnings('from a_index | where coalesce(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where coalesce(numberField, numberField) > 0', []); - testErrorsAndWarnings('from a_index | where length(coalesce(stringField)) > 0', []); - testErrorsAndWarnings( - 'from a_index | where length(coalesce(stringField, stringField)) > 0', - [] - ); - testErrorsAndWarnings('from a_index | eval var = coalesce(numberField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(numberField, numberField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_integer(booleanField), to_integer(booleanField))', - [] - ); + testErrorsAndWarnings('row var = coalesce(5.5, 5.5)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(dateField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(dateField, dateField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(dateField, dateField)', []); + testErrorsAndWarnings('from a_index | where coalesce(integerField) > 0', []); - testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_datetime(dateField), to_datetime(dateField))', - [] - ); + testErrorsAndWarnings('from a_index | where coalesce(counterDoubleField) > 0', [ + 'Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); - testErrorsAndWarnings('from a_index | eval var = coalesce(stringField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(stringField, stringField)', []); + testErrorsAndWarnings('from a_index | where coalesce(integerField, integerField) > 0', []); testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_string(booleanField), to_string(booleanField))', - [] + 'from a_index | where coalesce(counterDoubleField, counterDoubleField) > 0', + [ + 'Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]', + 'Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]', + ] ); + testErrorsAndWarnings('from a_index | where coalesce(longField) > 0', []); + testErrorsAndWarnings('from a_index | where coalesce(longField, longField) > 0', []); testErrorsAndWarnings('from a_index | eval var = coalesce(booleanField)', []); testErrorsAndWarnings('from a_index | eval coalesce(booleanField)', []); testErrorsAndWarnings('from a_index | eval var = coalesce(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval coalesce(counterDoubleField)', [ + 'Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + testErrorsAndWarnings('from a_index | eval var = coalesce(booleanField, booleanField)', []); testErrorsAndWarnings('from a_index | eval coalesce(booleanField, booleanField)', []); @@ -2291,21 +2343,12 @@ describe('validation logic', () => { [] ); - testErrorsAndWarnings('from a_index | eval var = coalesce(ipField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(ipField, ipField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(ipField, ipField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_ip(ipField), to_ip(ipField))', - [] - ); - testErrorsAndWarnings('from a_index | eval var = coalesce(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(cartesianPointField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField))', - [] + 'from a_index | eval coalesce(counterDoubleField, counterDoubleField)', + [ + 'Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]', + 'Argument of [coalesce] must be [boolean], found value [counterDoubleField] type [counter_double]', + ] ); testErrorsAndWarnings( @@ -2323,32 +2366,29 @@ describe('validation logic', () => { [] ); - testErrorsAndWarnings('from a_index | eval var = coalesce(cartesianShapeField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(cartesianShapeField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField))', + 'from a_index | eval var = coalesce(cartesianShapeField, cartesianShapeField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = coalesce(cartesianShapeField, cartesianShapeField)', + 'from a_index | eval coalesce(cartesianShapeField, cartesianShapeField)', [] ); testErrorsAndWarnings( - 'from a_index | eval coalesce(cartesianShapeField, cartesianShapeField)', + 'from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', [] ); + testErrorsAndWarnings('from a_index | eval var = coalesce(dateField, dateField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(dateField, dateField)', []); + testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | eval var = coalesce(to_datetime(dateField), to_datetime(dateField))', [] ); - testErrorsAndWarnings('from a_index | eval var = coalesce(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(to_geopoint(geoPointField))', []); testErrorsAndWarnings( 'from a_index | eval var = coalesce(geoPointField, geoPointField)', [] @@ -2360,9 +2400,6 @@ describe('validation logic', () => { [] ); - testErrorsAndWarnings('from a_index | eval var = coalesce(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(to_geoshape(geoPointField))', []); testErrorsAndWarnings( 'from a_index | eval var = coalesce(geoShapeField, geoShapeField)', [] @@ -2374,241 +2411,364 @@ describe('validation logic', () => { [] ); - testErrorsAndWarnings('from a_index | eval var = coalesce(versionField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(to_version(stringField))', []); - testErrorsAndWarnings('from a_index | eval var = coalesce(versionField, versionField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(versionField, versionField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(integerField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(integerField, integerField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(integerField, integerField)', []); testErrorsAndWarnings( - 'from a_index | eval var = coalesce(to_version(stringField), to_version(stringField))', + 'from a_index | eval var = coalesce(to_integer(booleanField), to_integer(booleanField))', [] ); - testErrorsAndWarnings('from a_index | sort coalesce(numberField)', []); - testErrorsAndWarnings('from a_index | eval coalesce(null)', []); - testErrorsAndWarnings('row nullVar = null | eval coalesce(nullVar)', []); - testErrorsAndWarnings('from a_index | sort coalesce(booleanField)', []); - }); - - describe('concat', () => { - testErrorsAndWarnings('row var = concat("a", "a")', []); - testErrorsAndWarnings('row concat("a", "a")', []); - testErrorsAndWarnings('row var = concat(to_string("a"), to_string("a"))', []); - - testErrorsAndWarnings('row var = concat(5, 5)', [ - 'Argument of [concat] must be [string], found value [5] type [number]', - 'Argument of [concat] must be [string], found value [5] type [number]', - ]); - + testErrorsAndWarnings('from a_index | eval var = coalesce(ipField, ipField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(ipField, ipField)', []); testErrorsAndWarnings( - 'from a_index | where length(concat(stringField, stringField)) > 0', + 'from a_index | eval var = coalesce(to_ip(ipField), to_ip(ipField))', [] ); - - testErrorsAndWarnings('from a_index | where length(concat(numberField, numberField)) > 0', [ - 'Argument of [concat] must be [string], found value [numberField] type [number]', - 'Argument of [concat] must be [string], found value [numberField] type [number]', - ]); - - testErrorsAndWarnings('from a_index | eval var = concat(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval concat(stringField, stringField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(keywordField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(keywordField, keywordField)', []); testErrorsAndWarnings( - 'from a_index | eval var = concat(to_string(stringField), to_string(stringField))', + 'from a_index | eval var = coalesce(to_string(booleanField), to_string(booleanField))', [] ); - testErrorsAndWarnings('from a_index | eval concat(numberField, numberField)', [ - 'Argument of [concat] must be [string], found value [numberField] type [number]', - 'Argument of [concat] must be [string], found value [numberField] type [number]', - ]); + testErrorsAndWarnings('from a_index | eval var = coalesce(longField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(longField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(textField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(textField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = coalesce(versionField, versionField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(versionField, versionField)', []); - testErrorsAndWarnings('from a_index | sort concat(stringField, stringField)', []); - testErrorsAndWarnings('row var = concat(to_string(true), to_string(true))', []); + testErrorsAndWarnings( + 'from a_index | eval var = coalesce(to_version(keywordField), to_version(keywordField))', + [] + ); - testErrorsAndWarnings('row var = concat(true, true)', [ - 'Argument of [concat] must be [string], found value [true] type [boolean]', - 'Argument of [concat] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | sort coalesce(booleanField)', []); + testErrorsAndWarnings('from a_index | eval coalesce(null)', []); + testErrorsAndWarnings('row nullVar = null | eval coalesce(nullVar)', []); + testErrorsAndWarnings('from a_index | eval coalesce("2022", "2022")', []); testErrorsAndWarnings( - 'from a_index | where length(concat(booleanField, booleanField)) > 0', - [ - 'Argument of [concat] must be [string], found value [booleanField] type [boolean]', - 'Argument of [concat] must be [string], found value [booleanField] type [boolean]', - ] + 'from a_index | eval coalesce(concat("20", "22"), concat("20", "22"))', + [] ); + testErrorsAndWarnings( + 'row var = coalesce(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row coalesce(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = coalesce(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = coalesce(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = coalesce(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row coalesce(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = coalesce(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = coalesce(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); + }); + + describe('concat', () => { + testErrorsAndWarnings('row var = concat("a", "a")', []); + testErrorsAndWarnings('row concat("a", "a")', []); + testErrorsAndWarnings('row var = concat(to_string(true), to_string(true))', []); + + testErrorsAndWarnings('row var = concat(true, true)', [ + 'Argument of [concat] must be [keyword], found value [true] type [boolean]', + 'Argument of [concat] must be [keyword], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = concat(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval concat(keywordField, keywordField)', []); + testErrorsAndWarnings( 'from a_index | eval var = concat(to_string(booleanField), to_string(booleanField))', [] ); testErrorsAndWarnings('from a_index | eval concat(booleanField, booleanField)', [ - 'Argument of [concat] must be [string], found value [booleanField] type [boolean]', - 'Argument of [concat] must be [string], found value [booleanField] type [boolean]', + 'Argument of [concat] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [concat] must be [keyword], found value [booleanField] type [boolean]', ]); + + testErrorsAndWarnings('from a_index | eval var = concat(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval concat(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = concat(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval concat(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = concat(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval concat(textField, textField)', []); + testErrorsAndWarnings('from a_index | sort concat(keywordField, keywordField)', []); testErrorsAndWarnings('from a_index | eval concat(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval concat(nullVar, nullVar)', []); }); describe('cos', () => { + testErrorsAndWarnings('row var = cos(5.5)', []); + testErrorsAndWarnings('row cos(5.5)', []); + testErrorsAndWarnings('row var = cos(to_double(true))', []); testErrorsAndWarnings('row var = cos(5)', []); testErrorsAndWarnings('row cos(5)', []); - testErrorsAndWarnings('row var = cos(to_integer("a"))', []); + testErrorsAndWarnings('row var = cos(to_integer(true))', []); - testErrorsAndWarnings('row var = cos("a")', [ - 'Argument of [cos] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = cos(true)', [ + 'Argument of [cos] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where cos(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where cos(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where cos(stringField) > 0', [ - 'Argument of [cos] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where cos(booleanField) > 0', [ + 'Argument of [cos] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = cos(numberField)', []); - testErrorsAndWarnings('from a_index | eval cos(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = cos(to_integer(stringField))', []); - - testErrorsAndWarnings('from a_index | eval cos(stringField)', [ - 'Argument of [cos] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings('from a_index | where cos(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where cos(longField) > 0', []); + testErrorsAndWarnings('from a_index | where cos(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = cos(doubleField)', []); + testErrorsAndWarnings('from a_index | eval cos(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = cos(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval cos(numberField, extraArg)', [ - 'Error: [cos] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval cos(booleanField)', [ + 'Argument of [cos] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = cos(*)', [ 'Using wildcards (*) in cos is not allowed', ]); - testErrorsAndWarnings('from a_index | sort cos(numberField)', []); - testErrorsAndWarnings('row var = cos(to_integer(true))', []); - - testErrorsAndWarnings('row var = cos(true)', [ - 'Argument of [cos] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where cos(booleanField) > 0', [ - 'Argument of [cos] must be [number], found value [booleanField] type [boolean]', - ]); - + testErrorsAndWarnings('from a_index | eval var = cos(integerField)', []); + testErrorsAndWarnings('from a_index | eval cos(integerField)', []); testErrorsAndWarnings('from a_index | eval var = cos(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = cos(longField)', []); + testErrorsAndWarnings('from a_index | eval cos(longField)', []); + testErrorsAndWarnings('from a_index | eval var = cos(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval cos(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval cos(booleanField)', [ - 'Argument of [cos] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval cos(doubleField, extraArg)', [ + 'Error: [cos] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort cos(doubleField)', []); testErrorsAndWarnings('from a_index | eval cos(null)', []); testErrorsAndWarnings('row nullVar = null | eval cos(nullVar)', []); }); describe('cosh', () => { + testErrorsAndWarnings('row var = cosh(5.5)', []); + testErrorsAndWarnings('row cosh(5.5)', []); + testErrorsAndWarnings('row var = cosh(to_double(true))', []); testErrorsAndWarnings('row var = cosh(5)', []); testErrorsAndWarnings('row cosh(5)', []); - testErrorsAndWarnings('row var = cosh(to_integer("a"))', []); + testErrorsAndWarnings('row var = cosh(to_integer(true))', []); - testErrorsAndWarnings('row var = cosh("a")', [ - 'Argument of [cosh] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = cosh(true)', [ + 'Argument of [cosh] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where cosh(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where cosh(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where cosh(stringField) > 0', [ - 'Argument of [cosh] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where cosh(booleanField) > 0', [ + 'Argument of [cosh] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = cosh(numberField)', []); - testErrorsAndWarnings('from a_index | eval cosh(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = cosh(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | where cosh(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where cosh(longField) > 0', []); + testErrorsAndWarnings('from a_index | where cosh(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = cosh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval cosh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = cosh(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval cosh(stringField)', [ - 'Argument of [cosh] must be [number], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings('from a_index | eval cosh(numberField, extraArg)', [ - 'Error: [cosh] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval cosh(booleanField)', [ + 'Argument of [cosh] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = cosh(*)', [ 'Using wildcards (*) in cosh is not allowed', ]); - testErrorsAndWarnings('from a_index | sort cosh(numberField)', []); - testErrorsAndWarnings('row var = cosh(to_integer(true))', []); - - testErrorsAndWarnings('row var = cosh(true)', [ - 'Argument of [cosh] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where cosh(booleanField) > 0', [ - 'Argument of [cosh] must be [number], found value [booleanField] type [boolean]', - ]); - + testErrorsAndWarnings('from a_index | eval var = cosh(integerField)', []); + testErrorsAndWarnings('from a_index | eval cosh(integerField)', []); testErrorsAndWarnings('from a_index | eval var = cosh(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = cosh(longField)', []); + testErrorsAndWarnings('from a_index | eval cosh(longField)', []); + testErrorsAndWarnings('from a_index | eval var = cosh(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval cosh(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval cosh(booleanField)', [ - 'Argument of [cosh] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval cosh(doubleField, extraArg)', [ + 'Error: [cosh] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort cosh(doubleField)', []); testErrorsAndWarnings('from a_index | eval cosh(null)', []); testErrorsAndWarnings('row nullVar = null | eval cosh(nullVar)', []); }); - describe('date_extract', () => { - testErrorsAndWarnings('row var = date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", now())', []); - testErrorsAndWarnings('row date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", now())', []); + describe('date_diff', () => { testErrorsAndWarnings( - 'from a_index | eval date_extract("SOME_RANDOM_STRING", now())', - [], + 'from a_index | eval var = date_diff("year", dateField, dateField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval date_diff("year", dateField, dateField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = date_diff("year", to_datetime(dateField), to_datetime(dateField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval date_diff(booleanField, booleanField, booleanField)', [ - 'Invalid option ["SOME_RANDOM_STRING"] for date_extract. Supported options: ["ALIGNED_DAY_OF_WEEK_IN_MONTH", "ALIGNED_DAY_OF_WEEK_IN_YEAR", "ALIGNED_WEEK_OF_MONTH", "ALIGNED_WEEK_OF_YEAR", "AMPM_OF_DAY", "CLOCK_HOUR_OF_AMPM", "CLOCK_HOUR_OF_DAY", "DAY_OF_MONTH", "DAY_OF_WEEK", "DAY_OF_YEAR", "EPOCH_DAY", "ERA", "HOUR_OF_AMPM", "HOUR_OF_DAY", "INSTANT_SECONDS", "MICRO_OF_DAY", "MICRO_OF_SECOND", "MILLI_OF_DAY", "MILLI_OF_SECOND", "MINUTE_OF_DAY", "MINUTE_OF_HOUR", "MONTH_OF_YEAR", "NANO_OF_DAY", "NANO_OF_SECOND", "OFFSET_SECONDS", "PROLEPTIC_MONTH", "SECOND_OF_DAY", "SECOND_OF_MINUTE", "YEAR", "YEAR_OF_ERA"].', + 'Argument of [date_diff] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [date_diff] must be [date], found value [booleanField] type [boolean]', + 'Argument of [date_diff] must be [date], found value [booleanField] type [boolean]', ] ); testErrorsAndWarnings( - 'from a_index | eval var = date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField)', + 'from a_index | eval var = date_diff(textField, dateField, dateField)', [] ); + testErrorsAndWarnings('from a_index | eval date_diff(textField, dateField, dateField)', []); + testErrorsAndWarnings( - 'from a_index | eval date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField)', + 'from a_index | eval var = date_diff(to_string(booleanField), to_datetime(dateField), to_datetime(dateField))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", to_datetime(stringField))', + 'from a_index | eval date_diff("year", dateField, dateField, extraArg)', + ['Error: [date_diff] function expects exactly 3 arguments, got 4.'] + ); + + testErrorsAndWarnings('from a_index | sort date_diff("year", dateField, dateField)', []); + + testErrorsAndWarnings('from a_index | eval date_diff(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval date_diff(nullVar, nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | eval date_diff("year", "2022", "2022")', []); + + testErrorsAndWarnings( + 'from a_index | eval date_diff("year", concat("20", "22"), concat("20", "22"))', + [ + 'Argument of [date_diff] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [date_diff] must be [date], found value [concat("20","22")] type [keyword]', + ] + ); + + testErrorsAndWarnings('from a_index | eval date_diff(textField, "2022", "2022")', []); + + testErrorsAndWarnings( + 'from a_index | eval date_diff(textField, concat("20", "22"), concat("20", "22"))', + [ + 'Argument of [date_diff] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [date_diff] must be [date], found value [concat("20","22")] type [keyword]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = date_diff(to_string(booleanField), dateField, dateField)', [] ); + }); - testErrorsAndWarnings('from a_index | eval date_extract(stringField, stringField)', [ - 'Argument of [date_extract] must be [date], found value [stringField] type [string]', - ]); + describe('date_extract', () => { + testErrorsAndWarnings( + 'row var = date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", to_datetime("2021-01-01T00:00:00Z"))', + [] + ); testErrorsAndWarnings( - 'from a_index | eval date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField, extraArg)', - ['Error: [date_extract] function expects exactly 2 arguments, got 3.'] + 'row date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", to_datetime("2021-01-01T00:00:00Z"))', + [] ); testErrorsAndWarnings( - 'from a_index | sort date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField)', + 'row var = date_extract("a", to_datetime("2021-01-01T00:00:00Z"))', [] ); + testErrorsAndWarnings('row date_extract("a", to_datetime("2021-01-01T00:00:00Z"))', []); testErrorsAndWarnings('row var = date_extract(true, true)', [ - 'Argument of [date_extract] must be [string], found value [true] type [boolean]', + 'Argument of [date_extract] must be [keyword], found value [true] type [boolean]', 'Argument of [date_extract] must be [date], found value [true] type [boolean]', ]); + testErrorsAndWarnings( + 'from a_index | eval var = date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField)', + [] + ); + testErrorsAndWarnings( 'from a_index | eval var = date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", to_datetime(dateField))', [] ); testErrorsAndWarnings('from a_index | eval date_extract(booleanField, booleanField)', [ - 'Argument of [date_extract] must be [string], found value [booleanField] type [boolean]', + 'Argument of [date_extract] must be [keyword], found value [booleanField] type [boolean]', 'Argument of [date_extract] must be [date], found value [booleanField] type [boolean]', ]); + + testErrorsAndWarnings('from a_index | eval var = date_extract(textField, dateField)', []); + testErrorsAndWarnings('from a_index | eval date_extract(textField, dateField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = date_extract(to_string(booleanField), to_datetime(dateField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField, extraArg)', + ['Error: [date_extract] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", dateField)', + [] + ); + testErrorsAndWarnings('from a_index | eval date_extract(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval date_extract(nullVar, nullVar)', []); @@ -2620,134 +2780,138 @@ describe('validation logic', () => { testErrorsAndWarnings( 'from a_index | eval date_extract("ALIGNED_DAY_OF_WEEK_IN_MONTH", concat("20", "22"))', [ - 'Argument of [date_extract] must be [date], found value [concat("20","22")] type [string]', + 'Argument of [date_extract] must be [date], found value [concat("20","22")] type [keyword]', ] ); - }); - describe('date_format', () => { - testErrorsAndWarnings('row var = date_format("a", now())', []); - testErrorsAndWarnings('row date_format("a", now())', []); - testErrorsAndWarnings('from a_index | eval var = date_format(stringField, dateField)', []); - testErrorsAndWarnings('from a_index | eval date_format(stringField, dateField)', []); + testErrorsAndWarnings('from a_index | eval date_extract(textField, "2022")', []); + testErrorsAndWarnings('from a_index | eval date_extract(textField, concat("20", "22"))', [ + 'Argument of [date_extract] must be [date], found value [concat("20","22")] type [keyword]', + ]); testErrorsAndWarnings( - 'from a_index | eval var = date_format(to_string(stringField), to_datetime(stringField))', + 'from a_index | eval var = date_extract(to_string(booleanField), dateField)', [] ); + }); - testErrorsAndWarnings('from a_index | eval date_format(stringField, numberField)', [ - 'Argument of [date_format] must be [date], found value [numberField] type [number]', - ]); - - testErrorsAndWarnings('from a_index | eval date_format(stringField, dateField, extraArg)', [ - 'Error: [date_format] function expects no more than 2 arguments, got 3.', - ]); - - testErrorsAndWarnings('from a_index | sort date_format(stringField, dateField)', []); + describe('date_format', () => { + testErrorsAndWarnings( + 'row var = date_format("a", to_datetime("2021-01-01T00:00:00Z"))', + [] + ); + testErrorsAndWarnings('row date_format("a", to_datetime("2021-01-01T00:00:00Z"))', []); testErrorsAndWarnings('row var = date_format(true, true)', [ - 'Argument of [date_format] must be [string], found value [true] type [boolean]', + 'Argument of [date_format] must be [keyword], found value [true] type [boolean]', 'Argument of [date_format] must be [date], found value [true] type [boolean]', ]); + testErrorsAndWarnings('from a_index | eval var = date_format(keywordField, dateField)', []); + testErrorsAndWarnings('from a_index | eval date_format(keywordField, dateField)', []); + testErrorsAndWarnings( 'from a_index | eval var = date_format(to_string(booleanField), to_datetime(dateField))', [] ); testErrorsAndWarnings('from a_index | eval date_format(booleanField, booleanField)', [ - 'Argument of [date_format] must be [string], found value [booleanField] type [boolean]', + 'Argument of [date_format] must be [keyword], found value [booleanField] type [boolean]', 'Argument of [date_format] must be [date], found value [booleanField] type [boolean]', ]); + + testErrorsAndWarnings('from a_index | eval var = date_format(textField, dateField)', []); + testErrorsAndWarnings('from a_index | eval date_format(textField, dateField)', []); + + testErrorsAndWarnings( + 'from a_index | eval date_format(keywordField, dateField, extraArg)', + ['Error: [date_format] function expects no more than 2 arguments, got 3.'] + ); + + testErrorsAndWarnings('from a_index | sort date_format(keywordField, dateField)', []); testErrorsAndWarnings('from a_index | eval date_format(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval date_format(nullVar, nullVar)', []); - testErrorsAndWarnings('from a_index | eval date_format(stringField, "2022")', []); - testErrorsAndWarnings('from a_index | eval date_format(stringField, concat("20", "22"))', [ - 'Argument of [date_format] must be [date], found value [concat("20","22")] type [string]', + testErrorsAndWarnings('from a_index | eval date_format(keywordField, "2022")', []); + + testErrorsAndWarnings('from a_index | eval date_format(keywordField, concat("20", "22"))', [ + 'Argument of [date_format] must be [date], found value [concat("20","22")] type [keyword]', + ]); + + testErrorsAndWarnings('from a_index | eval date_format(textField, "2022")', []); + testErrorsAndWarnings('from a_index | eval date_format(textField, concat("20", "22"))', [ + 'Argument of [date_format] must be [date], found value [concat("20","22")] type [keyword]', ]); + + testErrorsAndWarnings( + 'from a_index | eval var = date_format(to_string(booleanField), dateField)', + [] + ); }); describe('date_parse', () => { testErrorsAndWarnings('row var = date_parse("a", "a")', []); - testErrorsAndWarnings('row var = date_parse("a")', []); testErrorsAndWarnings('row date_parse("a", "a")', []); - testErrorsAndWarnings('row var = date_parse(to_string("a"), to_string("a"))', []); + testErrorsAndWarnings('row var = date_parse(to_string(true), to_string(true))', []); - testErrorsAndWarnings('row var = date_parse(5, 5)', [ - 'Argument of [date_parse] must be [string], found value [5] type [number]', - 'Argument of [date_parse] must be [string], found value [5] type [number]', + testErrorsAndWarnings('row var = date_parse(true, true)', [ + 'Argument of [date_parse] must be [keyword], found value [true] type [boolean]', + 'Argument of [date_parse] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = date_parse(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = date_parse(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval date_parse(stringField, stringField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = date_parse(to_string(stringField), to_string(stringField))', + 'from a_index | eval var = date_parse(keywordField, keywordField)', [] ); - - testErrorsAndWarnings('from a_index | eval date_parse(numberField, numberField)', [ - 'Argument of [date_parse] must be [string], found value [numberField] type [number]', - 'Argument of [date_parse] must be [string], found value [numberField] type [number]', - ]); + testErrorsAndWarnings('from a_index | eval date_parse(keywordField, keywordField)', []); testErrorsAndWarnings( - 'from a_index | eval date_parse(stringField, stringField, extraArg)', - ['Error: [date_parse] function expects no more than 2 arguments, got 3.'] + 'from a_index | eval var = date_parse(to_string(booleanField), to_string(booleanField))', + [] ); - testErrorsAndWarnings('from a_index | sort date_parse(stringField, stringField)', []); - testErrorsAndWarnings('row var = date_parse(to_string(true), to_string(true))', []); - - testErrorsAndWarnings('row var = date_parse(true, true)', [ - 'Argument of [date_parse] must be [string], found value [true] type [boolean]', - 'Argument of [date_parse] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval date_parse(booleanField, booleanField)', [ + 'Argument of [date_parse] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [date_parse] must be [keyword], found value [booleanField] type [boolean]', ]); + testErrorsAndWarnings('from a_index | eval var = date_parse(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval date_parse(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = date_parse(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval date_parse(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = date_parse(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval date_parse(textField, textField)', []); + testErrorsAndWarnings( - 'from a_index | eval var = date_parse(to_string(booleanField), to_string(booleanField))', - [] + 'from a_index | eval date_parse(keywordField, keywordField, extraArg)', + ['Error: [date_parse] function expects no more than 2 arguments, got 3.'] ); - testErrorsAndWarnings('from a_index | eval date_parse(booleanField, booleanField)', [ - 'Argument of [date_parse] must be [string], found value [booleanField] type [boolean]', - 'Argument of [date_parse] must be [string], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | sort date_parse(keywordField, keywordField)', []); testErrorsAndWarnings('from a_index | eval date_parse(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval date_parse(nullVar, nullVar)', []); }); describe('date_trunc', () => { - testErrorsAndWarnings('row var = date_trunc(1 year, now())', []); - testErrorsAndWarnings('row date_trunc(1 year, now())', []); - testErrorsAndWarnings('from a_index | eval var = date_trunc(1 year, dateField)', []); - testErrorsAndWarnings('from a_index | eval date_trunc(1 year, dateField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = date_trunc(1 year, to_datetime(stringField))', + 'row var = date_trunc(1 year, to_datetime("2021-01-01T00:00:00Z"))', [] ); - - testErrorsAndWarnings('from a_index | eval date_trunc(stringField, stringField)', [ - 'Argument of [date_trunc] must be [time_literal], found value [stringField] type [string]', - 'Argument of [date_trunc] must be [date], found value [stringField] type [string]', + testErrorsAndWarnings('row date_trunc(1 year, to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row var = date_trunc("a", to_datetime("2021-01-01T00:00:00Z"))', [ + 'Argument of [date_trunc] must be [time_literal], found value ["a"] type [string]', ]); - - testErrorsAndWarnings('from a_index | eval date_trunc(1 year, dateField, extraArg)', [ - 'Error: [date_trunc] function expects exactly 2 arguments, got 3.', + testErrorsAndWarnings('row date_trunc("a", to_datetime("2021-01-01T00:00:00Z"))', [ + 'Argument of [date_trunc] must be [time_literal], found value ["a"] type [string]', ]); - testErrorsAndWarnings('from a_index | sort date_trunc(1 year, dateField)', []); - testErrorsAndWarnings('row var = date_trunc(now(), now())', []); - testErrorsAndWarnings('row date_trunc(now(), now())', []); - testErrorsAndWarnings('row var = date_trunc(true, true)', [ 'Argument of [date_trunc] must be [time_literal], found value [true] type [boolean]', 'Argument of [date_trunc] must be [date], found value [true] type [boolean]', ]); + testErrorsAndWarnings('from a_index | eval var = date_trunc(1 year, dateField)', []); + testErrorsAndWarnings('from a_index | eval date_trunc(1 year, dateField)', []); + testErrorsAndWarnings( 'from a_index | eval var = date_trunc(1 year, to_datetime(dateField))', [] @@ -2758,28 +2922,36 @@ describe('validation logic', () => { 'Argument of [date_trunc] must be [date], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = date_trunc(dateField, dateField)', []); - testErrorsAndWarnings('from a_index | eval date_trunc(dateField, dateField)', []); + testErrorsAndWarnings('from a_index | eval var = date_trunc(textField, dateField)', [ + 'Argument of [date_trunc] must be [time_literal], found value [textField] type [text]', + ]); + testErrorsAndWarnings('from a_index | eval date_trunc(textField, dateField)', [ + 'Argument of [date_trunc] must be [time_literal], found value [textField] type [text]', + ]); testErrorsAndWarnings( - 'from a_index | eval var = date_trunc(to_datetime(dateField), to_datetime(dateField))', - [] + 'from a_index | eval var = date_trunc(textField, to_datetime(dateField))', + ['Argument of [date_trunc] must be [time_literal], found value [textField] type [text]'] ); + + testErrorsAndWarnings('from a_index | eval date_trunc(1 year, dateField, extraArg)', [ + 'Error: [date_trunc] function expects exactly 2 arguments, got 3.', + ]); + + testErrorsAndWarnings('from a_index | sort date_trunc(1 year, dateField)', []); testErrorsAndWarnings('from a_index | eval date_trunc(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval date_trunc(nullVar, nullVar)', []); testErrorsAndWarnings('from a_index | eval date_trunc(1 year, "2022")', []); testErrorsAndWarnings('from a_index | eval date_trunc(1 year, concat("20", "22"))', [ - 'Argument of [date_trunc] must be [date], found value [concat("20","22")] type [string]', + 'Argument of [date_trunc] must be [date], found value [concat("20","22")] type [keyword]', + ]); + testErrorsAndWarnings('from a_index | eval date_trunc(textField, "2022")', [ + 'Argument of [date_trunc] must be [time_literal], found value [textField] type [text]', + ]); + testErrorsAndWarnings('from a_index | eval date_trunc(textField, concat("20", "22"))', [ + 'Argument of [date_trunc] must be [time_literal], found value [textField] type [text]', + 'Argument of [date_trunc] must be [date], found value [concat("20","22")] type [keyword]', ]); - testErrorsAndWarnings('from a_index | eval date_trunc("2022", "2022")', []); - - testErrorsAndWarnings( - 'from a_index | eval date_trunc(concat("20", "22"), concat("20", "22"))', - [ - 'Argument of [date_trunc] must be [time_literal], found value [concat("20","22")] type [string]', - 'Argument of [date_trunc] must be [date], found value [concat("20","22")] type [string]', - ] - ); }); describe('e', () => { @@ -2800,37 +2972,18 @@ describe('validation logic', () => { describe('ends_with', () => { testErrorsAndWarnings('row var = ends_with("a", "a")', []); testErrorsAndWarnings('row ends_with("a", "a")', []); - testErrorsAndWarnings('row var = ends_with(to_string("a"), to_string("a"))', []); + testErrorsAndWarnings('row var = ends_with(to_string(true), to_string(true))', []); - testErrorsAndWarnings('row var = ends_with(5, 5)', [ - 'Argument of [ends_with] must be [string], found value [5] type [number]', - 'Argument of [ends_with] must be [string], found value [5] type [number]', + testErrorsAndWarnings('row var = ends_with(true, true)', [ + 'Argument of [ends_with] must be [keyword], found value [true] type [boolean]', + 'Argument of [ends_with] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = ends_with(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval ends_with(stringField, stringField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = ends_with(to_string(stringField), to_string(stringField))', + 'from a_index | eval var = ends_with(keywordField, keywordField)', [] ); - - testErrorsAndWarnings('from a_index | eval ends_with(numberField, numberField)', [ - 'Argument of [ends_with] must be [string], found value [numberField] type [number]', - 'Argument of [ends_with] must be [string], found value [numberField] type [number]', - ]); - - testErrorsAndWarnings('from a_index | eval ends_with(stringField, stringField, extraArg)', [ - 'Error: [ends_with] function expects exactly 2 arguments, got 3.', - ]); - - testErrorsAndWarnings('from a_index | sort ends_with(stringField, stringField)', []); - testErrorsAndWarnings('row var = ends_with(to_string(true), to_string(true))', []); - - testErrorsAndWarnings('row var = ends_with(true, true)', [ - 'Argument of [ends_with] must be [string], found value [true] type [boolean]', - 'Argument of [ends_with] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | eval ends_with(keywordField, keywordField)', []); testErrorsAndWarnings( 'from a_index | eval var = ends_with(to_string(booleanField), to_string(booleanField))', @@ -2838,82 +2991,176 @@ describe('validation logic', () => { ); testErrorsAndWarnings('from a_index | eval ends_with(booleanField, booleanField)', [ - 'Argument of [ends_with] must be [string], found value [booleanField] type [boolean]', - 'Argument of [ends_with] must be [string], found value [booleanField] type [boolean]', + 'Argument of [ends_with] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [ends_with] must be [keyword], found value [booleanField] type [boolean]', ]); + + testErrorsAndWarnings('from a_index | eval var = ends_with(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval ends_with(textField, textField)', []); + + testErrorsAndWarnings( + 'from a_index | eval ends_with(keywordField, keywordField, extraArg)', + ['Error: [ends_with] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings('from a_index | sort ends_with(keywordField, keywordField)', []); testErrorsAndWarnings('from a_index | eval ends_with(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval ends_with(nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | eval var = ends_with(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval ends_with(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = ends_with(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval ends_with(textField, keywordField)', []); }); - describe('floor', () => { - testErrorsAndWarnings('row var = floor(5)', []); - testErrorsAndWarnings('row floor(5)', []); - testErrorsAndWarnings('row var = floor(to_integer("a"))', []); + describe('exp', () => { + testErrorsAndWarnings('row var = exp(5.5)', []); + testErrorsAndWarnings('row exp(5.5)', []); + testErrorsAndWarnings('row var = exp(to_double(true))', []); + testErrorsAndWarnings('row var = exp(5)', []); + testErrorsAndWarnings('row exp(5)', []); + testErrorsAndWarnings('row var = exp(to_integer(true))', []); - testErrorsAndWarnings('row var = floor("a")', [ - 'Argument of [floor] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = exp(true)', [ + 'Argument of [exp] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where floor(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where exp(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where floor(stringField) > 0', [ - 'Argument of [floor] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where exp(booleanField) > 0', [ + 'Argument of [exp] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = floor(numberField)', []); - testErrorsAndWarnings('from a_index | eval floor(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = floor(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | where exp(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where exp(longField) > 0', []); + testErrorsAndWarnings('from a_index | where exp(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = exp(doubleField)', []); + testErrorsAndWarnings('from a_index | eval exp(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = exp(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval floor(stringField)', [ - 'Argument of [floor] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval exp(booleanField)', [ + 'Argument of [exp] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval floor(numberField, extraArg)', [ - 'Error: [floor] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval var = exp(*)', [ + 'Using wildcards (*) in exp is not allowed', ]); - testErrorsAndWarnings('from a_index | eval var = floor(*)', [ - 'Using wildcards (*) in floor is not allowed', + testErrorsAndWarnings('from a_index | eval var = exp(integerField)', []); + testErrorsAndWarnings('from a_index | eval exp(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = exp(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = exp(longField)', []); + testErrorsAndWarnings('from a_index | eval exp(longField)', []); + testErrorsAndWarnings('from a_index | eval var = exp(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval exp(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval exp(doubleField, extraArg)', [ + 'Error: [exp] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | sort floor(numberField)', []); + testErrorsAndWarnings('from a_index | sort exp(doubleField)', []); + testErrorsAndWarnings('from a_index | eval exp(null)', []); + testErrorsAndWarnings('row nullVar = null | eval exp(nullVar)', []); + }); + + describe('floor', () => { + testErrorsAndWarnings('row var = floor(5.5)', []); + testErrorsAndWarnings('row floor(5.5)', []); + testErrorsAndWarnings('row var = floor(to_double(true))', []); + testErrorsAndWarnings('row var = floor(5)', []); + testErrorsAndWarnings('row floor(5)', []); testErrorsAndWarnings('row var = floor(to_integer(true))', []); testErrorsAndWarnings('row var = floor(true)', [ - 'Argument of [floor] must be [number], found value [true] type [boolean]', + 'Argument of [floor] must be [double], found value [true] type [boolean]', ]); + testErrorsAndWarnings('from a_index | where floor(doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where floor(booleanField) > 0', [ - 'Argument of [floor] must be [number], found value [booleanField] type [boolean]', + 'Argument of [floor] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = floor(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | where floor(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where floor(longField) > 0', []); + testErrorsAndWarnings('from a_index | where floor(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = floor(doubleField)', []); + testErrorsAndWarnings('from a_index | eval floor(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = floor(to_double(booleanField))', []); testErrorsAndWarnings('from a_index | eval floor(booleanField)', [ - 'Argument of [floor] must be [number], found value [booleanField] type [boolean]', + 'Argument of [floor] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = floor(*)', [ + 'Using wildcards (*) in floor is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = floor(integerField)', []); + testErrorsAndWarnings('from a_index | eval floor(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = floor(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = floor(longField)', []); + testErrorsAndWarnings('from a_index | eval floor(longField)', []); + testErrorsAndWarnings('from a_index | eval var = floor(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval floor(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval floor(doubleField, extraArg)', [ + 'Error: [floor] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort floor(doubleField)', []); testErrorsAndWarnings('from a_index | eval floor(null)', []); testErrorsAndWarnings('row nullVar = null | eval floor(nullVar)', []); }); + describe('from_base64', () => { + testErrorsAndWarnings('row var = from_base64("a")', []); + testErrorsAndWarnings('row from_base64("a")', []); + testErrorsAndWarnings('row var = from_base64(to_string(true))', []); + + testErrorsAndWarnings('row var = from_base64(true)', [ + 'Argument of [from_base64] must be [keyword], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = from_base64(keywordField)', []); + testErrorsAndWarnings('from a_index | eval from_base64(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = from_base64(to_string(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval from_base64(booleanField)', [ + 'Argument of [from_base64] must be [keyword], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = from_base64(*)', [ + 'Using wildcards (*) in from_base64 is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = from_base64(textField)', []); + testErrorsAndWarnings('from a_index | eval from_base64(textField)', []); + + testErrorsAndWarnings('from a_index | eval from_base64(keywordField, extraArg)', [ + 'Error: [from_base64] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort from_base64(keywordField)', []); + testErrorsAndWarnings('from a_index | eval from_base64(null)', []); + testErrorsAndWarnings('row nullVar = null | eval from_base64(nullVar)', []); + }); + describe('greatest', () => { - testErrorsAndWarnings('row var = greatest("a")', []); - testErrorsAndWarnings('row greatest("a")', []); - testErrorsAndWarnings('from a_index | eval var = greatest(stringField)', []); - testErrorsAndWarnings('from a_index | eval greatest(stringField)', []); - testErrorsAndWarnings('from a_index | sort greatest(stringField)', []); testErrorsAndWarnings('row var = greatest(true)', []); testErrorsAndWarnings('row greatest(true)', []); testErrorsAndWarnings('row var = greatest(to_boolean(true))', []); testErrorsAndWarnings('row var = greatest(true, true)', []); testErrorsAndWarnings('row greatest(true, true)', []); testErrorsAndWarnings('row var = greatest(to_boolean(true), to_boolean(true))', []); - testErrorsAndWarnings('row var = greatest(5, 5)', []); - testErrorsAndWarnings('row greatest(5, 5)', []); - testErrorsAndWarnings('row var = greatest(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = greatest(5.5, 5.5)', []); + testErrorsAndWarnings('row greatest(5.5, 5.5)', []); + testErrorsAndWarnings('row var = greatest(to_double(true), to_double(true))', []); testErrorsAndWarnings('row var = greatest(5)', []); testErrorsAndWarnings('row greatest(5)', []); testErrorsAndWarnings('row var = greatest(to_integer(true))', []); + testErrorsAndWarnings('row var = greatest(5, 5)', []); + testErrorsAndWarnings('row greatest(5, 5)', []); + testErrorsAndWarnings('row var = greatest(to_integer(true), to_integer(true))', []); testErrorsAndWarnings('row var = greatest(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); testErrorsAndWarnings('row greatest(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); @@ -2922,6 +3169,8 @@ describe('validation logic', () => { [] ); + testErrorsAndWarnings('row var = greatest("a")', []); + testErrorsAndWarnings('row greatest("a")', []); testErrorsAndWarnings('row var = greatest(to_string(true))', []); testErrorsAndWarnings('row var = greatest("a", "a")', []); testErrorsAndWarnings('row greatest("a", "a")', []); @@ -2938,41 +3187,17 @@ describe('validation logic', () => { ] ); - testErrorsAndWarnings('from a_index | where greatest(numberField, numberField) > 0', []); - - testErrorsAndWarnings( - 'from a_index | where greatest(cartesianPointField, cartesianPointField) > 0', - [ - 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ] - ); + testErrorsAndWarnings('from a_index | where greatest(doubleField, doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where greatest(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where greatest(integerField) > 0', []); testErrorsAndWarnings('from a_index | where greatest(cartesianPointField) > 0', [ 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | where length(greatest(stringField)) > 0', []); - - testErrorsAndWarnings('from a_index | where length(greatest(cartesianPointField)) > 0', [ - 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); - - testErrorsAndWarnings( - 'from a_index | where length(greatest(stringField, stringField)) > 0', - [] - ); - - testErrorsAndWarnings( - 'from a_index | where length(greatest(cartesianPointField, cartesianPointField)) > 0', - [ - 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ] - ); - + testErrorsAndWarnings('from a_index | where greatest(integerField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where greatest(longField) > 0', []); + testErrorsAndWarnings('from a_index | where greatest(longField, longField) > 0', []); testErrorsAndWarnings('from a_index | eval var = greatest(booleanField)', []); testErrorsAndWarnings('from a_index | eval greatest(booleanField)', []); testErrorsAndWarnings('from a_index | eval var = greatest(to_boolean(booleanField))', []); @@ -2997,63 +3222,139 @@ describe('validation logic', () => { ] ); - testErrorsAndWarnings('from a_index | eval var = greatest(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval greatest(numberField, numberField)', []); + testErrorsAndWarnings('from a_index | eval var = greatest(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval greatest(doubleField, doubleField)', []); testErrorsAndWarnings( - 'from a_index | eval var = greatest(to_integer(booleanField), to_integer(booleanField))', + 'from a_index | eval var = greatest(to_double(booleanField), to_double(booleanField))', [] ); - testErrorsAndWarnings('from a_index | eval var = greatest(numberField)', []); - testErrorsAndWarnings('from a_index | eval greatest(numberField)', []); + testErrorsAndWarnings('from a_index | eval var = greatest(integerField)', []); + testErrorsAndWarnings('from a_index | eval greatest(integerField)', []); testErrorsAndWarnings('from a_index | eval var = greatest(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = greatest(integerField, integerField)', []); + testErrorsAndWarnings('from a_index | eval greatest(integerField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = greatest(to_integer(booleanField), to_integer(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = greatest(ipField, ipField)', []); testErrorsAndWarnings('from a_index | eval greatest(ipField, ipField)', []); testErrorsAndWarnings( 'from a_index | eval var = greatest(to_ip(ipField), to_ip(ipField))', [] ); + testErrorsAndWarnings('from a_index | eval var = greatest(keywordField)', []); + testErrorsAndWarnings('from a_index | eval greatest(keywordField)', []); testErrorsAndWarnings('from a_index | eval var = greatest(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = greatest(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval greatest(stringField, stringField)', []); + testErrorsAndWarnings('from a_index | eval var = greatest(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval greatest(keywordField, keywordField)', []); testErrorsAndWarnings( 'from a_index | eval var = greatest(to_string(booleanField), to_string(booleanField))', [] ); + testErrorsAndWarnings('from a_index | eval var = greatest(longField)', []); + testErrorsAndWarnings('from a_index | eval greatest(longField)', []); + testErrorsAndWarnings('from a_index | eval var = greatest(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval greatest(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval var = greatest(textField)', []); + testErrorsAndWarnings('from a_index | eval greatest(textField)', []); + testErrorsAndWarnings('from a_index | eval var = greatest(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval greatest(textField, textField)', []); testErrorsAndWarnings('from a_index | eval var = greatest(versionField, versionField)', []); testErrorsAndWarnings('from a_index | eval greatest(versionField, versionField)', []); testErrorsAndWarnings( - 'from a_index | eval var = greatest(to_version(stringField), to_version(stringField))', + 'from a_index | eval var = greatest(to_version(keywordField), to_version(keywordField))', [] ); testErrorsAndWarnings('from a_index | sort greatest(booleanField)', []); testErrorsAndWarnings('from a_index | eval greatest(null)', []); testErrorsAndWarnings('row nullVar = null | eval greatest(nullVar)', []); + + testErrorsAndWarnings( + 'from a_index | where greatest(cartesianPointField, cartesianPointField) > 0', + [ + 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + 'Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + ] + ); + }); + + describe('ip_prefix', () => { + testErrorsAndWarnings('row var = ip_prefix(to_ip("127.0.0.1"), 5, 5)', []); + testErrorsAndWarnings('row ip_prefix(to_ip("127.0.0.1"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = ip_prefix(to_ip(to_ip("127.0.0.1")), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = ip_prefix(true, true, true)', [ + 'Argument of [ip_prefix] must be [ip], found value [true] type [boolean]', + 'Argument of [ip_prefix] must be [integer], found value [true] type [boolean]', + 'Argument of [ip_prefix] must be [integer], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = ip_prefix(ipField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval ip_prefix(ipField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = ip_prefix(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval ip_prefix(booleanField, booleanField, booleanField)', + [ + 'Argument of [ip_prefix] must be [ip], found value [booleanField] type [boolean]', + 'Argument of [ip_prefix] must be [integer], found value [booleanField] type [boolean]', + 'Argument of [ip_prefix] must be [integer], found value [booleanField] type [boolean]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | eval ip_prefix(ipField, integerField, integerField, extraArg)', + ['Error: [ip_prefix] function expects exactly 3 arguments, got 4.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort ip_prefix(ipField, integerField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | eval ip_prefix(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval ip_prefix(nullVar, nullVar, nullVar)', []); }); describe('least', () => { - testErrorsAndWarnings('row var = least("a")', []); - testErrorsAndWarnings('row least("a")', []); - testErrorsAndWarnings('from a_index | eval var = least(stringField)', []); - testErrorsAndWarnings('from a_index | eval least(stringField)', []); - testErrorsAndWarnings('from a_index | sort least(stringField)', []); testErrorsAndWarnings('row var = least(true)', []); testErrorsAndWarnings('row least(true)', []); testErrorsAndWarnings('row var = least(to_boolean(true))', []); testErrorsAndWarnings('row var = least(true, true)', []); testErrorsAndWarnings('row least(true, true)', []); testErrorsAndWarnings('row var = least(to_boolean(true), to_boolean(true))', []); - testErrorsAndWarnings('row var = least(5, 5)', []); - testErrorsAndWarnings('row least(5, 5)', []); - testErrorsAndWarnings('row var = least(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = least(5.5, 5.5)', []); + testErrorsAndWarnings('row least(5.5, 5.5)', []); + testErrorsAndWarnings('row var = least(to_double(true), to_double(true))', []); testErrorsAndWarnings('row var = least(5)', []); testErrorsAndWarnings('row least(5)', []); testErrorsAndWarnings('row var = least(to_integer(true))', []); + testErrorsAndWarnings('row var = least(5, 5)', []); + testErrorsAndWarnings('row least(5, 5)', []); + testErrorsAndWarnings('row var = least(to_integer(true), to_integer(true))', []); testErrorsAndWarnings('row var = least(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); testErrorsAndWarnings('row least(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); @@ -3062,6 +3363,8 @@ describe('validation logic', () => { [] ); + testErrorsAndWarnings('row var = least("a")', []); + testErrorsAndWarnings('row least("a")', []); testErrorsAndWarnings('row var = least(to_string(true))', []); testErrorsAndWarnings('row var = least("a", "a")', []); testErrorsAndWarnings('row least("a", "a")', []); @@ -3078,7 +3381,7 @@ describe('validation logic', () => { ] ); - testErrorsAndWarnings('from a_index | where least(numberField, numberField) > 0', []); + testErrorsAndWarnings('from a_index | where least(doubleField, doubleField) > 0', []); testErrorsAndWarnings( 'from a_index | where least(cartesianPointField, cartesianPointField) > 0', @@ -3088,31 +3391,15 @@ describe('validation logic', () => { ] ); - testErrorsAndWarnings('from a_index | where least(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where least(integerField) > 0', []); testErrorsAndWarnings('from a_index | where least(cartesianPointField) > 0', [ 'Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | where length(least(stringField)) > 0', []); - - testErrorsAndWarnings('from a_index | where length(least(cartesianPointField)) > 0', [ - 'Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); - - testErrorsAndWarnings( - 'from a_index | where length(least(stringField, stringField)) > 0', - [] - ); - - testErrorsAndWarnings( - 'from a_index | where length(least(cartesianPointField, cartesianPointField)) > 0', - [ - 'Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - 'Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ] - ); - + testErrorsAndWarnings('from a_index | where least(integerField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where least(longField) > 0', []); + testErrorsAndWarnings('from a_index | where least(longField, longField) > 0', []); testErrorsAndWarnings('from a_index | eval var = least(booleanField)', []); testErrorsAndWarnings('from a_index | eval least(booleanField)', []); testErrorsAndWarnings('from a_index | eval var = least(to_boolean(booleanField))', []); @@ -3137,37 +3424,55 @@ describe('validation logic', () => { ] ); - testErrorsAndWarnings('from a_index | eval var = least(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval least(numberField, numberField)', []); + testErrorsAndWarnings('from a_index | eval var = least(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval least(doubleField, doubleField)', []); testErrorsAndWarnings( - 'from a_index | eval var = least(to_integer(booleanField), to_integer(booleanField))', + 'from a_index | eval var = least(to_double(booleanField), to_double(booleanField))', [] ); - testErrorsAndWarnings('from a_index | eval var = least(numberField)', []); - testErrorsAndWarnings('from a_index | eval least(numberField)', []); + testErrorsAndWarnings('from a_index | eval var = least(integerField)', []); + testErrorsAndWarnings('from a_index | eval least(integerField)', []); testErrorsAndWarnings('from a_index | eval var = least(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = least(integerField, integerField)', []); + testErrorsAndWarnings('from a_index | eval least(integerField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = least(to_integer(booleanField), to_integer(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = least(ipField, ipField)', []); testErrorsAndWarnings('from a_index | eval least(ipField, ipField)', []); testErrorsAndWarnings( 'from a_index | eval var = least(to_ip(ipField), to_ip(ipField))', [] ); + testErrorsAndWarnings('from a_index | eval var = least(keywordField)', []); + testErrorsAndWarnings('from a_index | eval least(keywordField)', []); testErrorsAndWarnings('from a_index | eval var = least(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = least(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval least(stringField, stringField)', []); + testErrorsAndWarnings('from a_index | eval var = least(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval least(keywordField, keywordField)', []); testErrorsAndWarnings( 'from a_index | eval var = least(to_string(booleanField), to_string(booleanField))', [] ); + testErrorsAndWarnings('from a_index | eval var = least(longField)', []); + testErrorsAndWarnings('from a_index | eval least(longField)', []); + testErrorsAndWarnings('from a_index | eval var = least(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval least(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval var = least(textField)', []); + testErrorsAndWarnings('from a_index | eval least(textField)', []); + testErrorsAndWarnings('from a_index | eval var = least(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval least(textField, textField)', []); testErrorsAndWarnings('from a_index | eval var = least(versionField, versionField)', []); testErrorsAndWarnings('from a_index | eval least(versionField, versionField)', []); testErrorsAndWarnings( - 'from a_index | eval var = least(to_version(stringField), to_version(stringField))', + 'from a_index | eval var = least(to_version(keywordField), to_version(keywordField))', [] ); @@ -3179,52 +3484,15 @@ describe('validation logic', () => { describe('left', () => { testErrorsAndWarnings('row var = left("a", 5)', []); testErrorsAndWarnings('row left("a", 5)', []); - testErrorsAndWarnings('row var = left(to_string("a"), to_integer("a"))', []); - - testErrorsAndWarnings('row var = left(5, "a")', [ - 'Argument of [left] must be [string], found value [5] type [number]', - 'Argument of [left] must be [number], found value ["a"] type [string]', - ]); - - testErrorsAndWarnings( - 'from a_index | where length(left(stringField, numberField)) > 0', - [] - ); - - testErrorsAndWarnings('from a_index | where length(left(numberField, stringField)) > 0', [ - 'Argument of [left] must be [string], found value [numberField] type [number]', - 'Argument of [left] must be [number], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings('from a_index | eval var = left(stringField, numberField)', []); - testErrorsAndWarnings('from a_index | eval left(stringField, numberField)', []); - - testErrorsAndWarnings( - 'from a_index | eval var = left(to_string(stringField), to_integer(stringField))', - [] - ); - - testErrorsAndWarnings('from a_index | eval left(numberField, stringField)', [ - 'Argument of [left] must be [string], found value [numberField] type [number]', - 'Argument of [left] must be [number], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings('from a_index | eval left(stringField, numberField, extraArg)', [ - 'Error: [left] function expects exactly 2 arguments, got 3.', - ]); - - testErrorsAndWarnings('from a_index | sort left(stringField, numberField)', []); testErrorsAndWarnings('row var = left(to_string(true), to_integer(true))', []); testErrorsAndWarnings('row var = left(true, true)', [ - 'Argument of [left] must be [string], found value [true] type [boolean]', - 'Argument of [left] must be [number], found value [true] type [boolean]', + 'Argument of [left] must be [keyword], found value [true] type [boolean]', + 'Argument of [left] must be [integer], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where length(left(booleanField, booleanField)) > 0', [ - 'Argument of [left] must be [string], found value [booleanField] type [boolean]', - 'Argument of [left] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | eval var = left(keywordField, integerField)', []); + testErrorsAndWarnings('from a_index | eval left(keywordField, integerField)', []); testErrorsAndWarnings( 'from a_index | eval var = left(to_string(booleanField), to_integer(booleanField))', @@ -3232,9 +3500,18 @@ describe('validation logic', () => { ); testErrorsAndWarnings('from a_index | eval left(booleanField, booleanField)', [ - 'Argument of [left] must be [string], found value [booleanField] type [boolean]', - 'Argument of [left] must be [number], found value [booleanField] type [boolean]', + 'Argument of [left] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [left] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = left(textField, integerField)', []); + testErrorsAndWarnings('from a_index | eval left(textField, integerField)', []); + + testErrorsAndWarnings('from a_index | eval left(keywordField, integerField, extraArg)', [ + 'Error: [left] function expects exactly 2 arguments, got 3.', ]); + + testErrorsAndWarnings('from a_index | sort left(keywordField, integerField)', []); testErrorsAndWarnings('from a_index | eval left(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval left(nullVar, nullVar)', []); }); @@ -3242,184 +3519,374 @@ describe('validation logic', () => { describe('length', () => { testErrorsAndWarnings('row var = length("a")', []); testErrorsAndWarnings('row length("a")', []); - testErrorsAndWarnings('row var = length(to_string("a"))', []); - - testErrorsAndWarnings('row var = length(5)', [ - 'Argument of [length] must be [string], found value [5] type [number]', - ]); - - testErrorsAndWarnings('from a_index | where length(stringField) > 0', []); + testErrorsAndWarnings('row var = length(to_string(true))', []); - testErrorsAndWarnings('from a_index | where length(numberField) > 0', [ - 'Argument of [length] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('row var = length(true)', [ + 'Argument of [length] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = length(stringField)', []); - testErrorsAndWarnings('from a_index | eval length(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = length(to_string(stringField))', []); - - testErrorsAndWarnings('from a_index | eval length(numberField)', [ - 'Argument of [length] must be [string], found value [numberField] type [number]', - ]); + testErrorsAndWarnings('from a_index | eval var = length(keywordField)', []); + testErrorsAndWarnings('from a_index | eval length(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = length(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval length(stringField, extraArg)', [ - 'Error: [length] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval length(booleanField)', [ + 'Argument of [length] must be [keyword], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = length(*)', [ 'Using wildcards (*) in length is not allowed', ]); - testErrorsAndWarnings('from a_index | sort length(stringField)', []); - testErrorsAndWarnings('row var = length(to_string(true))', []); - - testErrorsAndWarnings('row var = length(true)', [ - 'Argument of [length] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | eval var = length(textField)', []); + testErrorsAndWarnings('from a_index | eval length(textField)', []); - testErrorsAndWarnings('from a_index | where length(booleanField) > 0', [ - 'Argument of [length] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval length(keywordField, extraArg)', [ + 'Error: [length] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | eval var = length(to_string(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval length(booleanField)', [ - 'Argument of [length] must be [string], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | sort length(keywordField)', []); testErrorsAndWarnings('from a_index | eval length(null)', []); testErrorsAndWarnings('row nullVar = null | eval length(nullVar)', []); }); - describe('log', () => { - testErrorsAndWarnings('row var = log(5, 5)', []); - testErrorsAndWarnings('row log(5, 5)', []); - testErrorsAndWarnings('row var = log(to_integer("a"), to_integer("a"))', []); - - testErrorsAndWarnings('row var = log("a", "a")', [ - 'Argument of [log] must be [number], found value ["a"] type [string]', - 'Argument of [log] must be [number], found value ["a"] type [string]', - ]); - - testErrorsAndWarnings('from a_index | where log(numberField, numberField) > 0', []); + describe('locate', () => { + testErrorsAndWarnings('row var = locate("a", "a")', []); + testErrorsAndWarnings('row locate("a", "a")', []); + testErrorsAndWarnings('row var = locate(to_string(true), to_string(true))', []); + testErrorsAndWarnings('row var = locate("a", "a", 5)', []); + testErrorsAndWarnings('row locate("a", "a", 5)', []); + testErrorsAndWarnings( + 'row var = locate(to_string(true), to_string(true), to_integer(true))', + [] + ); - testErrorsAndWarnings('from a_index | where log(stringField, stringField) > 0', [ - 'Argument of [log] must be [number], found value [stringField] type [string]', - 'Argument of [log] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('row var = locate(true, true, true)', [ + 'Argument of [locate] must be [keyword], found value [true] type [boolean]', + 'Argument of [locate] must be [keyword], found value [true] type [boolean]', + 'Argument of [locate] must be [integer], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = log(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval log(numberField, numberField)', []); + testErrorsAndWarnings('from a_index | eval var = locate(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval locate(keywordField, keywordField)', []); testErrorsAndWarnings( - 'from a_index | eval var = log(to_integer(stringField), to_integer(stringField))', + 'from a_index | eval var = locate(to_string(booleanField), to_string(booleanField))', [] ); - testErrorsAndWarnings('from a_index | eval log(stringField, stringField)', [ - 'Argument of [log] must be [number], found value [stringField] type [string]', - 'Argument of [log] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval locate(booleanField, booleanField)', [ + 'Argument of [locate] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [locate] must be [keyword], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval log(numberField, numberField, extraArg)', [ - 'Error: [log] function expects no more than 2 arguments, got 3.', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = locate(keywordField, keywordField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | sort log(numberField, numberField)', []); - testErrorsAndWarnings('row var = log(5)', []); - testErrorsAndWarnings('row log(5)', []); - testErrorsAndWarnings('row var = log(to_integer(true))', []); + testErrorsAndWarnings( + 'from a_index | eval locate(keywordField, keywordField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = locate(to_string(booleanField), to_string(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval locate(booleanField, booleanField, booleanField)', + [ + 'Argument of [locate] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [locate] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [locate] must be [integer], found value [booleanField] type [boolean]', + ] + ); + + testErrorsAndWarnings('from a_index | eval var = locate(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval locate(keywordField, textField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = locate(keywordField, textField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval locate(keywordField, textField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | eval var = locate(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval locate(textField, keywordField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = locate(textField, keywordField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval locate(textField, keywordField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | eval var = locate(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval locate(textField, textField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = locate(textField, textField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | eval locate(textField, textField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval locate(keywordField, keywordField, integerField, extraArg)', + ['Error: [locate] function expects no more than 3 arguments, got 4.'] + ); + + testErrorsAndWarnings('from a_index | sort locate(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval locate(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval locate(nullVar, nullVar, nullVar)', []); + }); + + describe('log', () => { + testErrorsAndWarnings('row var = log(5.5)', []); + testErrorsAndWarnings('row log(5.5)', []); + testErrorsAndWarnings('row var = log(to_double(true))', []); + testErrorsAndWarnings('row var = log(5.5, 5.5)', []); + testErrorsAndWarnings('row log(5.5, 5.5)', []); + testErrorsAndWarnings('row var = log(to_double(true), to_double(true))', []); + testErrorsAndWarnings('row var = log(5.5, 5)', []); + testErrorsAndWarnings('row log(5.5, 5)', []); + testErrorsAndWarnings('row var = log(to_double(true), to_integer(true))', []); + testErrorsAndWarnings('row var = log(to_double(true), 5)', []); + testErrorsAndWarnings('row var = log(5)', []); + testErrorsAndWarnings('row log(5)', []); + testErrorsAndWarnings('row var = log(to_integer(true))', []); + testErrorsAndWarnings('row var = log(5, 5.5)', []); + testErrorsAndWarnings('row log(5, 5.5)', []); + testErrorsAndWarnings('row var = log(to_integer(true), to_double(true))', []); + testErrorsAndWarnings('row var = log(5, 5)', []); + testErrorsAndWarnings('row log(5, 5)', []); testErrorsAndWarnings('row var = log(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = log(to_integer(true), 5)', []); + testErrorsAndWarnings('row var = log(5, to_double(true))', []); + testErrorsAndWarnings('row var = log(5, to_integer(true))', []); testErrorsAndWarnings('row var = log(true, true)', [ - 'Argument of [log] must be [number], found value [true] type [boolean]', - 'Argument of [log] must be [number], found value [true] type [boolean]', + 'Argument of [log] must be [double], found value [true] type [boolean]', + 'Argument of [log] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where log(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where log(doubleField) > 0', []); testErrorsAndWarnings('from a_index | where log(booleanField) > 0', [ - 'Argument of [log] must be [number], found value [booleanField] type [boolean]', + 'Argument of [log] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where log(booleanField, booleanField) > 0', [ - 'Argument of [log] must be [number], found value [booleanField] type [boolean]', - 'Argument of [log] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | where log(doubleField, doubleField) > 0', []); - testErrorsAndWarnings('from a_index | eval var = log(numberField)', []); - testErrorsAndWarnings('from a_index | eval log(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = log(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | where log(booleanField, booleanField) > 0', [ + 'Argument of [log] must be [double], found value [booleanField] type [boolean]', + 'Argument of [log] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where log(doubleField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where log(doubleField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where log(doubleField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where log(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where log(integerField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where log(integerField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where log(integerField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where log(integerField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where log(longField) > 0', []); + testErrorsAndWarnings('from a_index | where log(longField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where log(longField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where log(longField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where log(longField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where log(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where log(unsignedLongField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where log(unsignedLongField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where log(unsignedLongField, longField) > 0', []); + testErrorsAndWarnings( + 'from a_index | where log(unsignedLongField, unsignedLongField) > 0', + [] + ); + testErrorsAndWarnings('from a_index | eval var = log(doubleField)', []); + testErrorsAndWarnings('from a_index | eval log(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = log(to_double(booleanField))', []); testErrorsAndWarnings('from a_index | eval log(booleanField)', [ - 'Argument of [log] must be [number], found value [booleanField] type [boolean]', + 'Argument of [log] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = log(*)', [ 'Using wildcards (*) in log is not allowed', ]); + testErrorsAndWarnings('from a_index | eval var = log(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval log(doubleField, doubleField)', []); + testErrorsAndWarnings( - 'from a_index | eval var = log(to_integer(booleanField), to_integer(booleanField))', + 'from a_index | eval var = log(to_double(booleanField), to_double(booleanField))', [] ); testErrorsAndWarnings('from a_index | eval log(booleanField, booleanField)', [ - 'Argument of [log] must be [number], found value [booleanField] type [boolean]', - 'Argument of [log] must be [number], found value [booleanField] type [boolean]', + 'Argument of [log] must be [double], found value [booleanField] type [boolean]', + 'Argument of [log] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = log(doubleField, integerField)', []); + testErrorsAndWarnings('from a_index | eval log(doubleField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = log(to_double(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = log(doubleField, longField)', []); + testErrorsAndWarnings('from a_index | eval log(doubleField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = log(to_double(booleanField), longField)', + [] + ); + testErrorsAndWarnings('from a_index | eval var = log(doubleField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval log(doubleField, unsignedLongField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = log(to_double(booleanField), unsignedLongField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = log(integerField)', []); + testErrorsAndWarnings('from a_index | eval log(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = log(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = log(integerField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval log(integerField, doubleField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = log(to_integer(booleanField), to_double(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = log(integerField, integerField)', []); + testErrorsAndWarnings('from a_index | eval log(integerField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = log(to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = log(integerField, longField)', []); + testErrorsAndWarnings('from a_index | eval log(integerField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = log(to_integer(booleanField), longField)', + [] + ); + testErrorsAndWarnings('from a_index | eval var = log(integerField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval log(integerField, unsignedLongField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = log(to_integer(booleanField), unsignedLongField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = log(longField)', []); + testErrorsAndWarnings('from a_index | eval log(longField)', []); + testErrorsAndWarnings('from a_index | eval var = log(longField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval log(longField, doubleField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = log(longField, to_double(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = log(longField, integerField)', []); + testErrorsAndWarnings('from a_index | eval log(longField, integerField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = log(longField, to_integer(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = log(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval log(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval var = log(longField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval log(longField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = log(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval log(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = log(unsignedLongField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval log(unsignedLongField, doubleField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = log(unsignedLongField, to_double(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = log(unsignedLongField, integerField)', []); + testErrorsAndWarnings('from a_index | eval log(unsignedLongField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = log(unsignedLongField, to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = log(unsignedLongField, longField)', []); + testErrorsAndWarnings('from a_index | eval log(unsignedLongField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = log(unsignedLongField, unsignedLongField)', + [] + ); + testErrorsAndWarnings('from a_index | eval log(unsignedLongField, unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval log(doubleField, doubleField, extraArg)', [ + 'Error: [log] function expects no more than 2 arguments, got 3.', ]); - testErrorsAndWarnings('from a_index | sort log(numberField)', []); + testErrorsAndWarnings('from a_index | sort log(doubleField)', []); testErrorsAndWarnings('from a_index | eval log(null, null)', []); testErrorsAndWarnings('row nullVar = null | eval log(nullVar, nullVar)', []); }); describe('log10', () => { + testErrorsAndWarnings('row var = log10(5.5)', []); + testErrorsAndWarnings('row log10(5.5)', []); + testErrorsAndWarnings('row var = log10(to_double(true))', []); testErrorsAndWarnings('row var = log10(5)', []); testErrorsAndWarnings('row log10(5)', []); - testErrorsAndWarnings('row var = log10(to_integer("a"))', []); + testErrorsAndWarnings('row var = log10(to_integer(true))', []); - testErrorsAndWarnings('row var = log10("a")', [ - 'Argument of [log10] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = log10(true)', [ + 'Argument of [log10] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where log10(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where log10(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | where log10(stringField) > 0', [ - 'Argument of [log10] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where log10(booleanField) > 0', [ + 'Argument of [log10] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = log10(numberField)', []); - testErrorsAndWarnings('from a_index | eval log10(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = log10(to_integer(stringField))', []); - - testErrorsAndWarnings('from a_index | eval log10(stringField)', [ - 'Argument of [log10] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings('from a_index | where log10(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where log10(longField) > 0', []); + testErrorsAndWarnings('from a_index | where log10(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = log10(doubleField)', []); + testErrorsAndWarnings('from a_index | eval log10(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = log10(to_double(booleanField))', []); - testErrorsAndWarnings('from a_index | eval log10(numberField, extraArg)', [ - 'Error: [log10] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval log10(booleanField)', [ + 'Argument of [log10] must be [double], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = log10(*)', [ 'Using wildcards (*) in log10 is not allowed', ]); - testErrorsAndWarnings('from a_index | sort log10(numberField)', []); - testErrorsAndWarnings('row var = log10(to_integer(true))', []); - - testErrorsAndWarnings('row var = log10(true)', [ - 'Argument of [log10] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where log10(booleanField) > 0', [ - 'Argument of [log10] must be [number], found value [booleanField] type [boolean]', - ]); - + testErrorsAndWarnings('from a_index | eval var = log10(integerField)', []); + testErrorsAndWarnings('from a_index | eval log10(integerField)', []); testErrorsAndWarnings('from a_index | eval var = log10(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = log10(longField)', []); + testErrorsAndWarnings('from a_index | eval log10(longField)', []); + testErrorsAndWarnings('from a_index | eval var = log10(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval log10(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval log10(booleanField)', [ - 'Argument of [log10] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval log10(doubleField, extraArg)', [ + 'Error: [log10] function expects exactly one argument, got 2.', ]); + + testErrorsAndWarnings('from a_index | sort log10(doubleField)', []); testErrorsAndWarnings('from a_index | eval log10(null)', []); testErrorsAndWarnings('row nullVar = null | eval log10(nullVar)', []); }); @@ -3427,7170 +3894,11902 @@ describe('validation logic', () => { describe('ltrim', () => { testErrorsAndWarnings('row var = ltrim("a")', []); testErrorsAndWarnings('row ltrim("a")', []); - testErrorsAndWarnings('row var = ltrim(to_string("a"))', []); - - testErrorsAndWarnings('row var = ltrim(5)', [ - 'Argument of [ltrim] must be [string], found value [5] type [number]', - ]); - - testErrorsAndWarnings('from a_index | where length(ltrim(stringField)) > 0', []); + testErrorsAndWarnings('row var = ltrim(to_string(true))', []); - testErrorsAndWarnings('from a_index | where length(ltrim(numberField)) > 0', [ - 'Argument of [ltrim] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('row var = ltrim(true)', [ + 'Argument of [ltrim] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = ltrim(stringField)', []); - testErrorsAndWarnings('from a_index | eval ltrim(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = ltrim(to_string(stringField))', []); - - testErrorsAndWarnings('from a_index | eval ltrim(numberField)', [ - 'Argument of [ltrim] must be [string], found value [numberField] type [number]', - ]); + testErrorsAndWarnings('from a_index | eval var = ltrim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval ltrim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = ltrim(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval ltrim(stringField, extraArg)', [ - 'Error: [ltrim] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval ltrim(booleanField)', [ + 'Argument of [ltrim] must be [keyword], found value [booleanField] type [boolean]', ]); testErrorsAndWarnings('from a_index | eval var = ltrim(*)', [ 'Using wildcards (*) in ltrim is not allowed', ]); - testErrorsAndWarnings('from a_index | sort ltrim(stringField)', []); - testErrorsAndWarnings('row var = ltrim(to_string(true))', []); - - testErrorsAndWarnings('row var = ltrim(true)', [ - 'Argument of [ltrim] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | eval var = ltrim(textField)', []); + testErrorsAndWarnings('from a_index | eval ltrim(textField)', []); - testErrorsAndWarnings('from a_index | where length(ltrim(booleanField)) > 0', [ - 'Argument of [ltrim] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval ltrim(keywordField, extraArg)', [ + 'Error: [ltrim] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | eval var = ltrim(to_string(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval ltrim(booleanField)', [ - 'Argument of [ltrim] must be [string], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | sort ltrim(keywordField)', []); testErrorsAndWarnings('from a_index | eval ltrim(null)', []); testErrorsAndWarnings('row nullVar = null | eval ltrim(nullVar)', []); }); - describe('mv_avg', () => { - testErrorsAndWarnings('row var = mv_avg(5)', []); - testErrorsAndWarnings('row mv_avg(5)', []); - testErrorsAndWarnings('row var = mv_avg(to_integer("a"))', []); - - testErrorsAndWarnings('row var = mv_avg("a")', [ - 'Argument of [mv_avg] must be [number], found value ["a"] type [string]', + describe('mv_append', () => { + testErrorsAndWarnings('row var = mv_append(true, true)', []); + testErrorsAndWarnings('row mv_append(true, true)', []); + testErrorsAndWarnings('row var = mv_append(to_boolean(true), to_boolean(true))', []); + testErrorsAndWarnings('row var = mv_append(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', ]); - - testErrorsAndWarnings('from a_index | where mv_avg(numberField) > 0', []); - - testErrorsAndWarnings('from a_index | where mv_avg(stringField) > 0', [ - 'Argument of [mv_avg] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('row mv_append(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', ]); - testErrorsAndWarnings('from a_index | eval var = mv_avg(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_avg(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_avg(to_integer(stringField))', []); + testErrorsAndWarnings( + 'row var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | eval mv_avg(stringField)', [ - 'Argument of [mv_avg] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'row var = mv_append(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | eval mv_avg(numberField, extraArg)', [ - 'Error: [mv_avg] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'row mv_append(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_avg(*)', [ - 'Using wildcards (*) in mv_avg is not allowed', - ]); + testErrorsAndWarnings( + 'row var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | sort mv_avg(numberField)', []); - testErrorsAndWarnings('row var = mv_avg(to_integer(true))', []); + testErrorsAndWarnings( + 'row var = mv_append(to_datetime("2021-01-01T00:00:00Z"), to_datetime("2021-01-01T00:00:00Z"))', + [] + ); - testErrorsAndWarnings('row var = mv_avg(true)', [ - 'Argument of [mv_avg] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'row mv_append(to_datetime("2021-01-01T00:00:00Z"), to_datetime("2021-01-01T00:00:00Z"))', + [] + ); - testErrorsAndWarnings('from a_index | where mv_avg(booleanField) > 0', [ - 'Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'row var = mv_append(to_datetime(to_datetime("2021-01-01T00:00:00Z")), to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_avg(to_integer(booleanField))', []); + testErrorsAndWarnings('row var = mv_append(5.5, 5.5)', []); - testErrorsAndWarnings('from a_index | eval mv_avg(booleanField)', [ - 'Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('row mv_append(5.5, 5.5)', []); + testErrorsAndWarnings('row var = mv_append(to_double(true), to_double(true))', []); + testErrorsAndWarnings('row var = mv_append(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', ]); - testErrorsAndWarnings('from a_index | eval mv_avg(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_avg(nullVar)', []); - }); - - describe('mv_concat', () => { - testErrorsAndWarnings('row var = mv_concat("a", "a")', []); - testErrorsAndWarnings('row mv_concat("a", "a")', []); - testErrorsAndWarnings('row var = mv_concat(to_string("a"), to_string("a"))', []); - - testErrorsAndWarnings('row var = mv_concat(5, 5)', [ - 'Argument of [mv_concat] must be [string], found value [5] type [number]', - 'Argument of [mv_concat] must be [string], found value [5] type [number]', + testErrorsAndWarnings('row mv_append(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', ]); + testErrorsAndWarnings( + 'row var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); testErrorsAndWarnings( - 'from a_index | where length(mv_concat(stringField, stringField)) > 0', + 'row var = mv_append(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | where length(mv_concat(numberField, numberField)) > 0', - [ - 'Argument of [mv_concat] must be [string], found value [numberField] type [number]', - 'Argument of [mv_concat] must be [string], found value [numberField] type [number]', - ] + 'row mv_append(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] ); - testErrorsAndWarnings('from a_index | eval var = mv_concat(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_concat(stringField, stringField)', []); + testErrorsAndWarnings( + 'row var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row var = mv_append(5, 5)', []); + testErrorsAndWarnings('row mv_append(5, 5)', []); + testErrorsAndWarnings('row var = mv_append(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = mv_append(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row mv_append(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_concat(to_string(stringField), to_string(stringField))', + 'row var = mv_append(to_ip(to_ip("127.0.0.1")), to_ip(to_ip("127.0.0.1")))', [] ); - testErrorsAndWarnings('from a_index | eval mv_concat(numberField, numberField)', [ - 'Argument of [mv_concat] must be [string], found value [numberField] type [number]', - 'Argument of [mv_concat] must be [string], found value [numberField] type [number]', - ]); - - testErrorsAndWarnings('from a_index | eval mv_concat(stringField, stringField, extraArg)', [ - 'Error: [mv_concat] function expects exactly 2 arguments, got 3.', - ]); - - testErrorsAndWarnings('from a_index | sort mv_concat(stringField, stringField)', []); - testErrorsAndWarnings('row var = mv_concat(to_string(true), to_string(true))', []); - - testErrorsAndWarnings('row var = mv_concat(true, true)', [ - 'Argument of [mv_concat] must be [string], found value [true] type [boolean]', - 'Argument of [mv_concat] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('row var = mv_append("a", "a")', []); + testErrorsAndWarnings('row mv_append("a", "a")', []); + testErrorsAndWarnings('row var = mv_append(to_string(true), to_string(true))', []); + testErrorsAndWarnings('row var = mv_append(to_version("1.0.0"), to_version("1.0.0"))', []); + testErrorsAndWarnings('row mv_append(to_version("1.0.0"), to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = mv_append(to_version("a"), to_version("a"))', []); + testErrorsAndWarnings('from a_index | where mv_append(doubleField, doubleField) > 0', []); testErrorsAndWarnings( - 'from a_index | where length(mv_concat(booleanField, booleanField)) > 0', + 'from a_index | where mv_append(counterDoubleField, counterDoubleField) > 0', [ - 'Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]', + 'Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]', + 'Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]', ] ); + testErrorsAndWarnings('from a_index | where mv_append(integerField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_append(longField, longField) > 0', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_concat(to_string(booleanField), to_string(booleanField))', + 'from a_index | eval var = mv_append(booleanField, booleanField)', [] ); - - testErrorsAndWarnings('from a_index | eval mv_concat(booleanField, booleanField)', [ - 'Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | eval mv_concat(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_concat(nullVar, nullVar)', []); - }); - - describe('mv_count', () => { - testErrorsAndWarnings('row var = mv_count("a")', []); - testErrorsAndWarnings('row mv_count("a")', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = mv_count(*)', [ - 'Using wildcards (*) in mv_count is not allowed', - ]); - - testErrorsAndWarnings('from a_index | sort mv_count(stringField)', []); - testErrorsAndWarnings('row var = mv_count(true)', []); - testErrorsAndWarnings('row mv_count(true)', []); - testErrorsAndWarnings('row var = mv_count(to_boolean(true))', []); - testErrorsAndWarnings('row var = mv_count(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_count(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('from a_index | eval mv_append(booleanField, booleanField)', []); testErrorsAndWarnings( - 'row var = mv_count(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | eval var = mv_append(to_boolean(booleanField), to_boolean(booleanField))', [] ); - testErrorsAndWarnings('row var = mv_count(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_count(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings( + 'from a_index | eval mv_append(counterDoubleField, counterDoubleField)', + [ + 'Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]', + 'Argument of [mv_append] must be [boolean], found value [counterDoubleField] type [counter_double]', + ] + ); testErrorsAndWarnings( - 'row var = mv_count(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | eval var = mv_append(cartesianPointField, cartesianPointField)', [] ); - testErrorsAndWarnings('row var = mv_count(now())', []); - testErrorsAndWarnings('row mv_count(now())', []); - testErrorsAndWarnings('row var = mv_count(to_datetime(now()))', []); - testErrorsAndWarnings('row var = mv_count(5)', []); - testErrorsAndWarnings('row mv_count(5)', []); - testErrorsAndWarnings('row var = mv_count(to_integer(true))', []); - testErrorsAndWarnings('row var = mv_count(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_count(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_count(to_geopoint(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = mv_count(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_count(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_count(to_geoshape(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = mv_count(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row mv_count(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = mv_count(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = mv_count(to_string(true))', []); - testErrorsAndWarnings('row var = mv_count(to_version("1.0.0"))', []); - testErrorsAndWarnings('row mv_count(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = mv_count(to_version("a"))', []); - testErrorsAndWarnings('from a_index | where mv_count(booleanField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(cartesianPointField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(cartesianShapeField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(dateField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(geoPointField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(geoShapeField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(ipField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(stringField) > 0', []); - testErrorsAndWarnings('from a_index | where mv_count(versionField) > 0', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_boolean(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(cartesianPointField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = mv_count(to_cartesianpoint(cartesianPointField))', + 'from a_index | eval mv_append(cartesianPointField, cartesianPointField)', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_count(cartesianShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(cartesianShapeField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = mv_count(to_cartesianshape(cartesianPointField))', + 'from a_index | eval var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_count(dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_geopoint(geoPointField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_geoshape(geoPointField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(ipField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(versionField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_count(to_version(stringField))', []); - - testErrorsAndWarnings('from a_index | eval mv_count(booleanField, extraArg)', [ - 'Error: [mv_count] function expects exactly one argument, got 2.', - ]); - - testErrorsAndWarnings('from a_index | sort mv_count(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_count(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_count(nullVar)', []); - }); - - describe('mv_dedupe', () => { - testErrorsAndWarnings('row var = mv_dedupe("a")', []); - testErrorsAndWarnings('row mv_dedupe("a")', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(*)', [ - 'Using wildcards (*) in mv_dedupe is not allowed', - ]); - - testErrorsAndWarnings('from a_index | sort mv_dedupe(stringField)', []); - testErrorsAndWarnings('row var = mv_dedupe(true)', []); - testErrorsAndWarnings('row mv_dedupe(true)', []); - testErrorsAndWarnings('row var = mv_dedupe(to_boolean(true))', []); - testErrorsAndWarnings('row var = mv_dedupe(now())', []); - testErrorsAndWarnings('row mv_dedupe(now())', []); - testErrorsAndWarnings('row var = mv_dedupe(to_datetime(now()))', []); - testErrorsAndWarnings('row var = mv_dedupe(5)', []); - testErrorsAndWarnings('row mv_dedupe(5)', []); - testErrorsAndWarnings('row var = mv_dedupe(to_integer(true))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row mv_dedupe(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_string(true))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_version("1.0.0"))', []); - testErrorsAndWarnings('row mv_dedupe(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_version("a"))', []); - - testErrorsAndWarnings('from a_index | where mv_dedupe(numberField) > 0', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(cartesianShapeField, cartesianShapeField)', + [] + ); - testErrorsAndWarnings('from a_index | where length(mv_dedupe(stringField)) > 0', []); + testErrorsAndWarnings( + 'from a_index | eval mv_append(cartesianShapeField, cartesianShapeField)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_boolean(booleanField))', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(ipField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(versionField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_version(stringField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_append(dateField, dateField)', []); + testErrorsAndWarnings('from a_index | eval mv_append(dateField, dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(booleanField, extraArg)', [ - 'Error: [mv_dedupe] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(to_datetime(dateField), to_datetime(dateField))', + [] + ); - testErrorsAndWarnings('from a_index | sort mv_dedupe(booleanField)', []); - testErrorsAndWarnings('row mv_dedupe(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('from a_index | eval var = mv_append(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_append(doubleField, doubleField)', []); testErrorsAndWarnings( - 'row var = mv_dedupe(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | eval var = mv_append(to_double(booleanField), to_double(booleanField))', [] ); - testErrorsAndWarnings('row var = mv_dedupe(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_dedupe(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(geoPointField, geoPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_append(geoPointField, geoPointField)', []); testErrorsAndWarnings( - 'row var = mv_dedupe(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | eval var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))', [] ); - testErrorsAndWarnings('row var = mv_dedupe(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_dedupe(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_geopoint(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_dedupe(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_dedupe(to_geoshape(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(cartesianPointField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(geoShapeField, geoShapeField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_append(geoShapeField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_dedupe(to_cartesianpoint(cartesianPointField))', + 'from a_index | eval var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(cartesianShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(cartesianShapeField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(integerField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_append(integerField, integerField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_dedupe(to_cartesianshape(cartesianPointField))', + 'from a_index | eval var = mv_append(to_integer(booleanField), to_integer(booleanField))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_append(ipField, ipField)', []); + testErrorsAndWarnings('from a_index | eval mv_append(ipField, ipField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_dedupe(to_geopoint(geoPointField))', + 'from a_index | eval var = mv_append(to_ip(ipField), to_ip(ipField))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_dedupe(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_dedupe(to_geoshape(geoPointField))', + 'from a_index | eval var = mv_append(keywordField, keywordField)', [] ); + testErrorsAndWarnings('from a_index | eval mv_append(keywordField, keywordField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(numberField, extraArg)', [ - 'Error: [mv_dedupe] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(to_string(booleanField), to_string(booleanField))', + [] + ); - testErrorsAndWarnings('from a_index | sort mv_dedupe(numberField)', []); - testErrorsAndWarnings('row var = mv_dedupe(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_dedupe(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_dedupe(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | eval var = mv_append(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval mv_append(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_append(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval mv_append(textField, textField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(versionField, versionField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_append(versionField, versionField)', []); - describe('mv_first', () => { - testErrorsAndWarnings('row var = mv_first("a")', []); - testErrorsAndWarnings('row mv_first("a")', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(stringField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_append(to_version(keywordField), to_version(keywordField))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_first(*)', [ - 'Using wildcards (*) in mv_first is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | eval mv_append(booleanField, booleanField, extraArg)', + ['Error: [mv_append] function expects exactly 2 arguments, got 3.'] + ); - testErrorsAndWarnings('from a_index | sort mv_first(stringField)', []); - testErrorsAndWarnings('row var = mv_first(true)', []); - testErrorsAndWarnings('row mv_first(true)', []); - testErrorsAndWarnings('row var = mv_first(to_boolean(true))', []); - testErrorsAndWarnings('row var = mv_first(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_first(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('from a_index | sort mv_append(booleanField, booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_append(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_append(nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_append("2022", "2022")', []); testErrorsAndWarnings( - 'row var = mv_first(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | eval mv_append(concat("20", "22"), concat("20", "22"))', [] ); - testErrorsAndWarnings('row var = mv_first(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_first(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings( + 'row var = mv_append(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); testErrorsAndWarnings( - 'row var = mv_first(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'row mv_append(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', [] ); - testErrorsAndWarnings('row var = mv_first(now())', []); - testErrorsAndWarnings('row mv_first(now())', []); - testErrorsAndWarnings('row var = mv_first(to_datetime(now()))', []); - testErrorsAndWarnings('row var = mv_first(5)', []); - testErrorsAndWarnings('row mv_first(5)', []); - testErrorsAndWarnings('row var = mv_first(to_integer(true))', []); - testErrorsAndWarnings('row var = mv_first(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_first(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_first(to_geopoint(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = mv_first(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_first(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_first(to_geoshape(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = mv_first(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row mv_first(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = mv_first(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = mv_first(to_string(true))', []); - testErrorsAndWarnings('row var = mv_first(to_version("1.0.0"))', []); - testErrorsAndWarnings('row mv_first(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = mv_first(to_version("a"))', []); - testErrorsAndWarnings('from a_index | where mv_first(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where length(mv_first(stringField)) > 0', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_boolean(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(cartesianPointField)', []); + testErrorsAndWarnings( + 'row var = mv_append(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = mv_first(to_cartesianpoint(cartesianPointField))', + 'row var = mv_append(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_first(cartesianShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(cartesianShapeField)', []); + testErrorsAndWarnings( + 'row var = mv_append(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = mv_first(to_cartesianshape(cartesianPointField))', + 'row mv_append(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_first(dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_geopoint(geoPointField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_geoshape(geoPointField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(ipField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(versionField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_first(to_version(stringField))', []); + testErrorsAndWarnings( + 'row var = mv_append(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); - testErrorsAndWarnings('from a_index | eval mv_first(booleanField, extraArg)', [ - 'Error: [mv_first] function expects exactly one argument, got 2.', + testErrorsAndWarnings( + 'row var = mv_append(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); + }); + + describe('mv_avg', () => { + testErrorsAndWarnings('row var = mv_avg(5.5)', []); + testErrorsAndWarnings('row mv_avg(5.5)', []); + testErrorsAndWarnings('row var = mv_avg(to_double(true))', []); + testErrorsAndWarnings('row var = mv_avg(5)', []); + testErrorsAndWarnings('row mv_avg(5)', []); + testErrorsAndWarnings('row var = mv_avg(to_integer(true))', []); + + testErrorsAndWarnings('row var = mv_avg(true)', [ + 'Argument of [mv_avg] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort mv_first(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_first(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_first(nullVar)', []); + testErrorsAndWarnings('from a_index | where mv_avg(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_avg(booleanField) > 0', [ + 'Argument of [mv_avg] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where mv_avg(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_avg(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_avg(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_avg(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_avg(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_avg(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_avg(booleanField)', [ + 'Argument of [mv_avg] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_avg(*)', [ + 'Using wildcards (*) in mv_avg is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_avg(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_avg(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_avg(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_avg(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_avg(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_avg(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_avg(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval mv_avg(doubleField, extraArg)', [ + 'Error: [mv_avg] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_avg(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_avg(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_avg(nullVar)', []); }); - describe('mv_last', () => { - testErrorsAndWarnings('row var = mv_last("a")', []); - testErrorsAndWarnings('row mv_last("a")', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(stringField)', []); + describe('mv_concat', () => { + testErrorsAndWarnings('row var = mv_concat("a", "a")', []); + testErrorsAndWarnings('row mv_concat("a", "a")', []); + testErrorsAndWarnings('row var = mv_concat(to_string(true), to_string(true))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(*)', [ - 'Using wildcards (*) in mv_last is not allowed', + testErrorsAndWarnings('row var = mv_concat(true, true)', [ + 'Argument of [mv_concat] must be [keyword], found value [true] type [boolean]', + 'Argument of [mv_concat] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort mv_last(stringField)', []); - testErrorsAndWarnings('row var = mv_last(true)', []); - testErrorsAndWarnings('row mv_last(true)', []); - testErrorsAndWarnings('row var = mv_last(to_boolean(true))', []); - testErrorsAndWarnings('row var = mv_last(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_last(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_concat(keywordField, keywordField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_concat(keywordField, keywordField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_concat(to_string(booleanField), to_string(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval mv_concat(booleanField, booleanField)', [ + 'Argument of [mv_concat] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [mv_concat] must be [keyword], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_concat(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval mv_concat(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_concat(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_concat(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_concat(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval mv_concat(textField, textField)', []); + + testErrorsAndWarnings( + 'from a_index | eval mv_concat(keywordField, keywordField, extraArg)', + ['Error: [mv_concat] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings('from a_index | sort mv_concat(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_concat(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_concat(nullVar, nullVar)', []); + }); + + describe('mv_count', () => { + testErrorsAndWarnings('row var = mv_count(true)', []); + testErrorsAndWarnings('row mv_count(true)', []); + testErrorsAndWarnings('row var = mv_count(to_boolean(true))', []); + testErrorsAndWarnings('row var = mv_count(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row mv_count(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_count(to_cartesianpoint(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_count(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_count(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_count(to_cartesianshape(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_count(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row mv_count(to_datetime("2021-01-01T00:00:00Z"))', []); + + testErrorsAndWarnings( + 'row var = mv_count(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); + + testErrorsAndWarnings('row var = mv_count(5.5)', []); + + testErrorsAndWarnings('row mv_count(5.5)', []); + testErrorsAndWarnings('row var = mv_count(to_double(true))', []); + testErrorsAndWarnings('row var = mv_count(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row mv_count(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = mv_count(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_count(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_count(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_count(to_geoshape(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_count(5)', []); + testErrorsAndWarnings('row mv_count(5)', []); + testErrorsAndWarnings('row var = mv_count(to_integer(true))', []); + testErrorsAndWarnings('row var = mv_count(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row mv_count(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = mv_count(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = mv_count("a")', []); + testErrorsAndWarnings('row mv_count("a")', []); + testErrorsAndWarnings('row var = mv_count(to_string(true))', []); + testErrorsAndWarnings('row var = mv_count(to_version("1.0.0"))', []); + testErrorsAndWarnings('row mv_count(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = mv_count(to_version("a"))', []); + testErrorsAndWarnings('from a_index | where mv_count(booleanField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_count(counterDoubleField) > 0', [ + 'Argument of [mv_count] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | where mv_count(cartesianPointField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(cartesianShapeField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(dateField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(geoPointField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(geoShapeField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(ipField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(keywordField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(textField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_count(versionField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_count(counterDoubleField)', [ + 'Argument of [mv_count] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_count(*)', [ + 'Using wildcards (*) in mv_count is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_count(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(cartesianPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_count(to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_count(cartesianShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(cartesianShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_count(to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_count(dateField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_geopoint(geoPointField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_geoshape(geoPointField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(ipField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(textField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(versionField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_count(to_version(keywordField))', []); + + testErrorsAndWarnings('from a_index | eval mv_count(booleanField, extraArg)', [ + 'Error: [mv_count] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_count(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_count(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_count(nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_count("2022")', []); + testErrorsAndWarnings('from a_index | eval mv_count(concat("20", "22"))', []); + testErrorsAndWarnings('row var = mv_count(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_count(to_cartesianpoint("POINT (30 10)"))', []); + + testErrorsAndWarnings( + 'row var = mv_count(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = mv_count(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings('row var = mv_count(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_count(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_count(to_geopoint(to_geopoint("POINT (30 10)")))', []); + testErrorsAndWarnings('row var = mv_count(to_geoshape(to_geopoint("POINT (30 10)")))', []); + }); + + describe('mv_dedupe', () => { + testErrorsAndWarnings('row var = mv_dedupe(true)', []); + testErrorsAndWarnings('row mv_dedupe(true)', []); + testErrorsAndWarnings('row var = mv_dedupe(to_boolean(true))', []); + testErrorsAndWarnings('row var = mv_dedupe(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row mv_dedupe(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_dedupe(to_cartesianpoint(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_dedupe(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_dedupe(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_cartesianshape(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_dedupe(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row mv_dedupe(to_datetime("2021-01-01T00:00:00Z"))', []); + + testErrorsAndWarnings( + 'row var = mv_dedupe(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); + + testErrorsAndWarnings('row var = mv_dedupe(5.5)', []); + + testErrorsAndWarnings('row mv_dedupe(5.5)', []); + testErrorsAndWarnings('row var = mv_dedupe(to_double(true))', []); + testErrorsAndWarnings('row var = mv_dedupe(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row mv_dedupe(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = mv_dedupe(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_dedupe(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_dedupe(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_geoshape(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_dedupe(5)', []); + testErrorsAndWarnings('row mv_dedupe(5)', []); + testErrorsAndWarnings('row var = mv_dedupe(to_integer(true))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row mv_dedupe(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = mv_dedupe("a")', []); + testErrorsAndWarnings('row mv_dedupe("a")', []); + testErrorsAndWarnings('row var = mv_dedupe(to_string(true))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_version("1.0.0"))', []); + testErrorsAndWarnings('row mv_dedupe(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_version("a"))', []); + testErrorsAndWarnings('from a_index | where mv_dedupe(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_dedupe(counterDoubleField) > 0', [ + 'Argument of [mv_dedupe] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | where mv_dedupe(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_dedupe(longField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_dedupe(counterDoubleField)', [ + 'Argument of [mv_dedupe] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(*)', [ + 'Using wildcards (*) in mv_dedupe is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(cartesianPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_dedupe(to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(cartesianShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(cartesianShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_dedupe(to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(dateField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(geoPointField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_dedupe(to_geopoint(geoPointField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(geoShapeField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_dedupe(to_geoshape(geoPointField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(ipField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(textField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(versionField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_dedupe(to_version(keywordField))', []); + + testErrorsAndWarnings('from a_index | eval mv_dedupe(booleanField, extraArg)', [ + 'Error: [mv_dedupe] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_dedupe(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_dedupe(nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe("2022")', []); + testErrorsAndWarnings('from a_index | eval mv_dedupe(concat("20", "22"))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_dedupe(to_cartesianpoint("POINT (30 10)"))', []); + + testErrorsAndWarnings( + 'row var = mv_dedupe(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = mv_dedupe(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings('row var = mv_dedupe(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_dedupe(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_geopoint(to_geopoint("POINT (30 10)")))', []); + testErrorsAndWarnings('row var = mv_dedupe(to_geoshape(to_geopoint("POINT (30 10)")))', []); + }); + + describe('mv_first', () => { + testErrorsAndWarnings('row var = mv_first(true)', []); + testErrorsAndWarnings('row mv_first(true)', []); + testErrorsAndWarnings('row var = mv_first(to_boolean(true))', []); + testErrorsAndWarnings('row var = mv_first(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row mv_first(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_first(to_cartesianpoint(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_first(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_first(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_first(to_cartesianshape(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_first(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row mv_first(to_datetime("2021-01-01T00:00:00Z"))', []); + + testErrorsAndWarnings( + 'row var = mv_first(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); + + testErrorsAndWarnings('row var = mv_first(5.5)', []); + + testErrorsAndWarnings('row mv_first(5.5)', []); + testErrorsAndWarnings('row var = mv_first(to_double(true))', []); + testErrorsAndWarnings('row var = mv_first(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row mv_first(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = mv_first(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_first(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_first(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_first(to_geoshape(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_first(5)', []); + testErrorsAndWarnings('row mv_first(5)', []); + testErrorsAndWarnings('row var = mv_first(to_integer(true))', []); + testErrorsAndWarnings('row var = mv_first(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row mv_first(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = mv_first(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = mv_first("a")', []); + testErrorsAndWarnings('row mv_first("a")', []); + testErrorsAndWarnings('row var = mv_first(to_string(true))', []); + testErrorsAndWarnings('row var = mv_first(to_version("1.0.0"))', []); + testErrorsAndWarnings('row mv_first(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = mv_first(to_version("a"))', []); + testErrorsAndWarnings('from a_index | where mv_first(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_first(counterDoubleField) > 0', [ + 'Argument of [mv_first] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | where mv_first(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_first(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_first(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_first(counterDoubleField)', [ + 'Argument of [mv_first] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_first(*)', [ + 'Using wildcards (*) in mv_first is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_first(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(cartesianPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_first(to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_first(cartesianShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(cartesianShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_first(to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_first(dateField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_geopoint(geoPointField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_geoshape(geoPointField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(ipField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(textField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(versionField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_first(to_version(keywordField))', []); + + testErrorsAndWarnings('from a_index | eval mv_first(booleanField, extraArg)', [ + 'Error: [mv_first] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_first(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_first(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_first(nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_first("2022")', []); + testErrorsAndWarnings('from a_index | eval mv_first(concat("20", "22"))', []); + testErrorsAndWarnings('row var = mv_first(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_first(to_cartesianpoint("POINT (30 10)"))', []); + + testErrorsAndWarnings( + 'row var = mv_first(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = mv_first(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings('row var = mv_first(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_first(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_first(to_geopoint(to_geopoint("POINT (30 10)")))', []); + testErrorsAndWarnings('row var = mv_first(to_geoshape(to_geopoint("POINT (30 10)")))', []); + }); + + describe('mv_last', () => { + testErrorsAndWarnings('row var = mv_last(true)', []); + testErrorsAndWarnings('row mv_last(true)', []); + testErrorsAndWarnings('row var = mv_last(to_boolean(true))', []); + testErrorsAndWarnings('row var = mv_last(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row mv_last(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_last(to_cartesianpoint(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_last(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_last(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_last(to_cartesianshape(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = mv_last(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row mv_last(to_datetime("2021-01-01T00:00:00Z"))', []); + + testErrorsAndWarnings( + 'row var = mv_last(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); + + testErrorsAndWarnings('row var = mv_last(5.5)', []); + + testErrorsAndWarnings('row mv_last(5.5)', []); + testErrorsAndWarnings('row var = mv_last(to_double(true))', []); + testErrorsAndWarnings('row var = mv_last(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row mv_last(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = mv_last(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_last(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_last(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_last(to_geoshape(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = mv_last(5)', []); + testErrorsAndWarnings('row mv_last(5)', []); + testErrorsAndWarnings('row var = mv_last(to_integer(true))', []); + testErrorsAndWarnings('row var = mv_last(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row mv_last(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = mv_last(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = mv_last("a")', []); + testErrorsAndWarnings('row mv_last("a")', []); + testErrorsAndWarnings('row var = mv_last(to_string(true))', []); + testErrorsAndWarnings('row var = mv_last(to_version("1.0.0"))', []); + testErrorsAndWarnings('row mv_last(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = mv_last(to_version("a"))', []); + testErrorsAndWarnings('from a_index | where mv_last(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_last(counterDoubleField) > 0', [ + 'Argument of [mv_last] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | where mv_last(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_last(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_last(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_last(counterDoubleField)', [ + 'Argument of [mv_last] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_last(*)', [ + 'Using wildcards (*) in mv_last is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_last(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(cartesianPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_last(to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_last(cartesianShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(cartesianShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_last(to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = mv_last(dateField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_geopoint(geoPointField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_geoshape(geoPointField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(ipField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(textField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(versionField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_last(to_version(keywordField))', []); + + testErrorsAndWarnings('from a_index | eval mv_last(booleanField, extraArg)', [ + 'Error: [mv_last] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_last(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_last(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_last(nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_last("2022")', []); + testErrorsAndWarnings('from a_index | eval mv_last(concat("20", "22"))', []); + testErrorsAndWarnings('row var = mv_last(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_last(to_cartesianpoint("POINT (30 10)"))', []); + + testErrorsAndWarnings( + 'row var = mv_last(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = mv_last(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings('row var = mv_last(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row mv_last(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = mv_last(to_geopoint(to_geopoint("POINT (30 10)")))', []); + testErrorsAndWarnings('row var = mv_last(to_geoshape(to_geopoint("POINT (30 10)")))', []); + }); + + describe('mv_max', () => { + testErrorsAndWarnings('row var = mv_max(true)', []); + testErrorsAndWarnings('row mv_max(true)', []); + testErrorsAndWarnings('row var = mv_max(to_boolean(true))', []); + testErrorsAndWarnings('row var = mv_max(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row mv_max(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings( + 'row var = mv_max(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); + testErrorsAndWarnings('row var = mv_max(5.5)', []); + testErrorsAndWarnings('row mv_max(5.5)', []); + testErrorsAndWarnings('row var = mv_max(to_double(true))', []); + testErrorsAndWarnings('row var = mv_max(5)', []); + testErrorsAndWarnings('row mv_max(5)', []); + testErrorsAndWarnings('row var = mv_max(to_integer(true))', []); + testErrorsAndWarnings('row var = mv_max(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row mv_max(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = mv_max(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = mv_max("a")', []); + testErrorsAndWarnings('row mv_max("a")', []); + testErrorsAndWarnings('row var = mv_max(to_string(true))', []); + testErrorsAndWarnings('row var = mv_max(to_version("1.0.0"))', []); + testErrorsAndWarnings('row mv_max(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = mv_max(to_version("a"))', []); + + testErrorsAndWarnings('row var = mv_max(to_cartesianpoint("POINT (30 10)"))', [ + 'Argument of [mv_max] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + ]); + + testErrorsAndWarnings('from a_index | where mv_max(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_max(cartesianPointField) > 0', [ + 'Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + ]); + + testErrorsAndWarnings('from a_index | where mv_max(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_max(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_max(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_max(cartesianPointField)', [ + 'Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_max(*)', [ + 'Using wildcards (*) in mv_max is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_max(dateField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(ipField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(textField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(versionField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_max(to_version(keywordField))', []); + + testErrorsAndWarnings('from a_index | eval mv_max(booleanField, extraArg)', [ + 'Error: [mv_max] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_max(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_max(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_max(nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_max("2022")', []); + testErrorsAndWarnings('from a_index | eval mv_max(concat("20", "22"))', []); + }); + + describe('mv_median', () => { + testErrorsAndWarnings('row var = mv_median(5.5)', []); + testErrorsAndWarnings('row mv_median(5.5)', []); + testErrorsAndWarnings('row var = mv_median(to_double(true))', []); + testErrorsAndWarnings('row var = mv_median(5)', []); + testErrorsAndWarnings('row mv_median(5)', []); + testErrorsAndWarnings('row var = mv_median(to_integer(true))', []); + + testErrorsAndWarnings('row var = mv_median(true)', [ + 'Argument of [mv_median] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where mv_median(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_median(booleanField) > 0', [ + 'Argument of [mv_median] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where mv_median(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_median(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_median(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_median(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_median(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_median(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_median(booleanField)', [ + 'Argument of [mv_median] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_median(*)', [ + 'Using wildcards (*) in mv_median is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_median(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_median(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_median(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_median(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_median(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_median(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_median(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval mv_median(doubleField, extraArg)', [ + 'Error: [mv_median] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_median(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_median(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_median(nullVar)', []); + }); + + describe('mv_min', () => { + testErrorsAndWarnings('row var = mv_min(true)', []); + testErrorsAndWarnings('row mv_min(true)', []); + testErrorsAndWarnings('row var = mv_min(to_boolean(true))', []); + testErrorsAndWarnings('row var = mv_min(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row mv_min(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings( + 'row var = mv_min(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); + testErrorsAndWarnings('row var = mv_min(5.5)', []); + testErrorsAndWarnings('row mv_min(5.5)', []); + testErrorsAndWarnings('row var = mv_min(to_double(true))', []); + testErrorsAndWarnings('row var = mv_min(5)', []); + testErrorsAndWarnings('row mv_min(5)', []); + testErrorsAndWarnings('row var = mv_min(to_integer(true))', []); + testErrorsAndWarnings('row var = mv_min(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row mv_min(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = mv_min(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = mv_min("a")', []); + testErrorsAndWarnings('row mv_min("a")', []); + testErrorsAndWarnings('row var = mv_min(to_string(true))', []); + testErrorsAndWarnings('row var = mv_min(to_version("1.0.0"))', []); + testErrorsAndWarnings('row mv_min(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = mv_min(to_version("a"))', []); + + testErrorsAndWarnings('row var = mv_min(to_cartesianpoint("POINT (30 10)"))', [ + 'Argument of [mv_min] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + ]); + + testErrorsAndWarnings('from a_index | where mv_min(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_min(cartesianPointField) > 0', [ + 'Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + ]); + + testErrorsAndWarnings('from a_index | where mv_min(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_min(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_min(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_min(cartesianPointField)', [ + 'Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_min(*)', [ + 'Using wildcards (*) in mv_min is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_min(dateField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(ipField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(textField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(versionField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_min(to_version(keywordField))', []); + + testErrorsAndWarnings('from a_index | eval mv_min(booleanField, extraArg)', [ + 'Error: [mv_min] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_min(booleanField)', []); + testErrorsAndWarnings('from a_index | eval mv_min(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_min(nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_min("2022")', []); + testErrorsAndWarnings('from a_index | eval mv_min(concat("20", "22"))', []); + }); + + describe('mv_slice', () => { + testErrorsAndWarnings('row var = mv_slice(true, 5, 5)', []); + testErrorsAndWarnings('row mv_slice(true, 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_boolean(true), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice(cartesianPointField, 5, 5)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row mv_slice(cartesianPointField, 5, 5)', [ + 'Unknown column [cartesianPointField]', + ]); + + testErrorsAndWarnings( + 'row var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(true), to_integer(true))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings('row var = mv_slice(to_cartesianshape("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings('row mv_slice(to_cartesianshape("POINT (30 10)"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(true), to_integer(true))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings('row var = mv_slice(to_datetime("2021-01-01T00:00:00Z"), 5, 5)', []); + testErrorsAndWarnings('row mv_slice(to_datetime("2021-01-01T00:00:00Z"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_datetime(to_datetime("2021-01-01T00:00:00Z")), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice(5.5, 5, 5)', []); + testErrorsAndWarnings('row mv_slice(5.5, 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_double(true), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice(geoPointField, 5, 5)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row mv_slice(geoPointField, 5, 5)', [ + 'Unknown column [geoPointField]', + ]); + + testErrorsAndWarnings( + 'row var = mv_slice(to_geopoint(geoPointField), to_integer(true), to_integer(true))', + ['Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings('row var = mv_slice(to_geoshape("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings('row mv_slice(to_geoshape("POINT (30 10)"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_geoshape(geoPointField), to_integer(true), to_integer(true))', + ['Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings('row var = mv_slice(5, 5, 5)', []); + testErrorsAndWarnings('row mv_slice(5, 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_integer(true), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice(to_ip("127.0.0.1"), 5, 5)', []); + testErrorsAndWarnings('row mv_slice(to_ip("127.0.0.1"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_ip(to_ip("127.0.0.1")), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice("a", 5, 5)', []); + testErrorsAndWarnings('row mv_slice("a", 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_string(true), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice(5, to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = mv_slice(to_version("1.0.0"), 5, 5)', []); + testErrorsAndWarnings('row mv_slice(to_version("1.0.0"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_version("a"), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice(5.5, true, true)', [ + 'Argument of [mv_slice] must be [integer], found value [true] type [boolean]', + 'Argument of [mv_slice] must be [integer], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | where mv_slice(doubleField, integerField, integerField) > 0', + [] + ); + + testErrorsAndWarnings( + 'from a_index | where mv_slice(counterDoubleField, booleanField, booleanField) > 0', + [ + 'Argument of [mv_slice] must be [boolean], found value [counterDoubleField] type [counter_double]', + 'Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]', + 'Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | where mv_slice(integerField, integerField, integerField) > 0', + [] + ); + + testErrorsAndWarnings( + 'from a_index | where mv_slice(longField, integerField, integerField) > 0', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(booleanField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(booleanField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_boolean(booleanField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(counterDoubleField, booleanField, booleanField)', + [ + 'Argument of [mv_slice] must be [boolean], found value [counterDoubleField] type [counter_double]', + 'Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]', + 'Argument of [mv_slice] must be [integer], found value [booleanField] type [boolean]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(cartesianPointField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(cartesianPointField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(cartesianShapeField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(cartesianShapeField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(dateField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(dateField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_datetime(dateField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(doubleField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(doubleField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_double(booleanField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(geoPointField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(geoPointField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_geopoint(geoPointField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(geoShapeField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(geoShapeField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_geoshape(geoPointField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(integerField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(integerField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_integer(booleanField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(ipField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(ipField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(keywordField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(keywordField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(longField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(longField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(longField, to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(textField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(textField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(versionField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(versionField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(to_version(keywordField), to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(booleanField, integerField, integerField, extraArg)', + ['Error: [mv_slice] function expects no more than 3 arguments, got 4.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort mv_slice(booleanField, integerField, integerField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval mv_slice(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_slice(nullVar, nullVar, nullVar)', []); + testErrorsAndWarnings( + 'from a_index | eval mv_slice("2022", integerField, integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_slice(concat("20", "22"), integerField, integerField)', + [] + ); + testErrorsAndWarnings('row var = mv_slice(to_cartesianpoint("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings('row mv_slice(to_cartesianpoint("POINT (30 10)"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings( + 'row var = mv_slice(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings( + 'row var = mv_slice(to_datetime("2021-01-01T00:00:00Z"), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_slice(to_geopoint("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings('row mv_slice(to_geopoint("POINT (30 10)"), 5, 5)', []); + + testErrorsAndWarnings( + 'row var = mv_slice(to_geopoint(to_geopoint("POINT (30 10)")), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings( + 'row var = mv_slice(to_geoshape(to_geopoint("POINT (30 10)")), to_integer(true), to_integer(true))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_slice(dateField, to_integer(booleanField), to_integer(booleanField))', + [] + ); + }); + + describe('mv_sort', () => { + testErrorsAndWarnings('row var = mv_sort(true, "asc")', []); + testErrorsAndWarnings('row mv_sort(true, "asc")', []); + testErrorsAndWarnings('row var = mv_sort(to_datetime("2021-01-01T00:00:00Z"), "asc")', []); + testErrorsAndWarnings('row mv_sort(to_datetime("2021-01-01T00:00:00Z"), "asc")', []); + testErrorsAndWarnings('row var = mv_sort(5.5, "asc")', []); + testErrorsAndWarnings('row mv_sort(5.5, "asc")', []); + testErrorsAndWarnings('row var = mv_sort(5, "asc")', []); + testErrorsAndWarnings('row mv_sort(5, "asc")', []); + testErrorsAndWarnings('row var = mv_sort(to_ip("127.0.0.1"), "asc")', []); + testErrorsAndWarnings('row mv_sort(to_ip("127.0.0.1"), "asc")', []); + testErrorsAndWarnings('row var = mv_sort("a", "asc")', []); + testErrorsAndWarnings('row mv_sort("a", "asc")', []); + testErrorsAndWarnings('row var = mv_sort(to_version("1.0.0"), "asc")', []); + testErrorsAndWarnings('row mv_sort(to_version("1.0.0"), "asc")', []); + + testErrorsAndWarnings('row var = mv_sort(to_cartesianpoint("POINT (30 10)"), true)', [ + 'Argument of [mv_sort] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + 'Argument of [mv_sort] must be [keyword], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_sort(booleanField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(booleanField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(dateField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(dateField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(doubleField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(doubleField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(integerField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(integerField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(ipField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(ipField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(keywordField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(keywordField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(longField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(longField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(textField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(textField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(versionField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(versionField, "asc")', []); + + testErrorsAndWarnings('from a_index | eval mv_sort(booleanField, "asc", extraArg)', [ + 'Error: [mv_sort] function expects no more than 2 arguments, got 3.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_sort(booleanField, "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_sort(nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | eval mv_sort("2022", "asc")', []); + testErrorsAndWarnings('from a_index | eval mv_sort(concat("20", "22"), "asc")', []); + testErrorsAndWarnings( + 'row var = mv_sort(5, "a")', + [], + ['Invalid option ["a"] for mv_sort. Supported options: ["asc", "desc"].'] + ); + testErrorsAndWarnings( + 'row mv_sort(5, "a")', + [], + ['Invalid option ["a"] for mv_sort. Supported options: ["asc", "desc"].'] + ); + testErrorsAndWarnings( + 'row var = mv_sort("a", "a")', + [], + ['Invalid option ["a"] for mv_sort. Supported options: ["asc", "desc"].'] + ); + testErrorsAndWarnings( + 'row mv_sort("a", "a")', + [], + ['Invalid option ["a"] for mv_sort. Supported options: ["asc", "desc"].'] + ); + testErrorsAndWarnings( + 'row var = mv_sort(to_version("1.0.0"), "a")', + [], + ['Invalid option ["a"] for mv_sort. Supported options: ["asc", "desc"].'] + ); + testErrorsAndWarnings( + 'row mv_sort(to_version("1.0.0"), "a")', + [], + ['Invalid option ["a"] for mv_sort. Supported options: ["asc", "desc"].'] + ); + testErrorsAndWarnings('from a_index | eval var = mv_sort(longField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_sort(longField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_sort(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_sort(versionField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_sort(versionField, keywordField)', []); + }); + + describe('mv_sum', () => { + testErrorsAndWarnings('row var = mv_sum(5.5)', []); + testErrorsAndWarnings('row mv_sum(5.5)', []); + testErrorsAndWarnings('row var = mv_sum(to_double(true))', []); + testErrorsAndWarnings('row var = mv_sum(5)', []); + testErrorsAndWarnings('row mv_sum(5)', []); + testErrorsAndWarnings('row var = mv_sum(to_integer(true))', []); + + testErrorsAndWarnings('row var = mv_sum(true)', [ + 'Argument of [mv_sum] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where mv_sum(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where mv_sum(booleanField) > 0', [ + 'Argument of [mv_sum] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where mv_sum(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_sum(longField) > 0', []); + testErrorsAndWarnings('from a_index | where mv_sum(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = mv_sum(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_sum(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_sum(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval mv_sum(booleanField)', [ + 'Argument of [mv_sum] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_sum(*)', [ + 'Using wildcards (*) in mv_sum is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_sum(integerField)', []); + testErrorsAndWarnings('from a_index | eval mv_sum(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_sum(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = mv_sum(longField)', []); + testErrorsAndWarnings('from a_index | eval mv_sum(longField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_sum(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval mv_sum(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval mv_sum(doubleField, extraArg)', [ + 'Error: [mv_sum] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort mv_sum(doubleField)', []); + testErrorsAndWarnings('from a_index | eval mv_sum(null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_sum(nullVar)', []); + }); + + describe('mv_zip', () => { + testErrorsAndWarnings('row var = mv_zip("a", "a")', []); + testErrorsAndWarnings('row mv_zip("a", "a")', []); + testErrorsAndWarnings('row var = mv_zip(to_string(true), to_string(true))', []); + testErrorsAndWarnings('row var = mv_zip("a", "a", "a")', []); + testErrorsAndWarnings('row mv_zip("a", "a", "a")', []); + testErrorsAndWarnings( + 'row var = mv_zip(to_string(true), to_string(true), to_string(true))', + [] + ); + + testErrorsAndWarnings('row var = mv_zip(true, true, true)', [ + 'Argument of [mv_zip] must be [keyword], found value [true] type [boolean]', + 'Argument of [mv_zip] must be [keyword], found value [true] type [boolean]', + 'Argument of [mv_zip] must be [keyword], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = mv_zip(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_zip(keywordField, keywordField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval mv_zip(booleanField, booleanField)', [ + 'Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(keywordField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_zip(keywordField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField), to_string(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_zip(booleanField, booleanField, booleanField)', + [ + 'Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [mv_zip] must be [keyword], found value [booleanField] type [boolean]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(keywordField, keywordField, textField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_zip(keywordField, keywordField, textField)', + [] + ); + testErrorsAndWarnings('from a_index | eval var = mv_zip(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval mv_zip(keywordField, textField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(keywordField, textField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_zip(keywordField, textField, keywordField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(keywordField, textField, textField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_zip(keywordField, textField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_zip(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_zip(textField, keywordField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(textField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval mv_zip(textField, keywordField, keywordField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(textField, keywordField, textField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_zip(textField, keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = mv_zip(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval mv_zip(textField, textField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(textField, textField, keywordField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_zip(textField, textField, keywordField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = mv_zip(textField, textField, textField)', + [] + ); + testErrorsAndWarnings('from a_index | eval mv_zip(textField, textField, textField)', []); + + testErrorsAndWarnings( + 'from a_index | eval mv_zip(keywordField, keywordField, keywordField, extraArg)', + ['Error: [mv_zip] function expects no more than 3 arguments, got 4.'] + ); + + testErrorsAndWarnings('from a_index | sort mv_zip(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval mv_zip(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval mv_zip(nullVar, nullVar, nullVar)', []); + }); + + describe('now', () => { + testErrorsAndWarnings('row var = now()', []); + testErrorsAndWarnings('row now()', []); + testErrorsAndWarnings('from a_index | eval var = now()', []); + testErrorsAndWarnings('from a_index | eval now()', []); + + testErrorsAndWarnings('from a_index | eval now(extraArg)', [ + 'Error: [now] function expects exactly 0 arguments, got 1.', + ]); + + testErrorsAndWarnings('from a_index | sort now()', []); + testErrorsAndWarnings('row nullVar = null | eval now()', []); + }); + + describe('pi', () => { + testErrorsAndWarnings('row var = pi()', []); + testErrorsAndWarnings('row pi()', []); + testErrorsAndWarnings('from a_index | where pi() > 0', []); + testErrorsAndWarnings('from a_index | eval var = pi()', []); + testErrorsAndWarnings('from a_index | eval pi()', []); + + testErrorsAndWarnings('from a_index | eval pi(extraArg)', [ + 'Error: [pi] function expects exactly 0 arguments, got 1.', + ]); + + testErrorsAndWarnings('from a_index | sort pi()', []); + testErrorsAndWarnings('row nullVar = null | eval pi()', []); + }); + + describe('pow', () => { + testErrorsAndWarnings('row var = pow(5.5, 5.5)', []); + testErrorsAndWarnings('row pow(5.5, 5.5)', []); + testErrorsAndWarnings('row var = pow(to_double(true), to_double(true))', []); + testErrorsAndWarnings('row var = pow(5.5, 5)', []); + testErrorsAndWarnings('row pow(5.5, 5)', []); + testErrorsAndWarnings('row var = pow(to_double(true), to_integer(true))', []); + testErrorsAndWarnings('row var = pow(to_double(true), 5)', []); + testErrorsAndWarnings('row var = pow(5, 5.5)', []); + testErrorsAndWarnings('row pow(5, 5.5)', []); + testErrorsAndWarnings('row var = pow(to_integer(true), to_double(true))', []); + testErrorsAndWarnings('row var = pow(5, 5)', []); + testErrorsAndWarnings('row pow(5, 5)', []); + testErrorsAndWarnings('row var = pow(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = pow(to_integer(true), 5)', []); + testErrorsAndWarnings('row var = pow(5, to_double(true))', []); + testErrorsAndWarnings('row var = pow(5, to_integer(true))', []); + + testErrorsAndWarnings('row var = pow(true, true)', [ + 'Argument of [pow] must be [double], found value [true] type [boolean]', + 'Argument of [pow] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where pow(doubleField, doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where pow(booleanField, booleanField) > 0', [ + 'Argument of [pow] must be [double], found value [booleanField] type [boolean]', + 'Argument of [pow] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where pow(doubleField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(doubleField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(doubleField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(integerField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(integerField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(integerField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(integerField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(longField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(longField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(longField, longField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(longField, unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(unsignedLongField, doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(unsignedLongField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where pow(unsignedLongField, longField) > 0', []); + testErrorsAndWarnings( + 'from a_index | where pow(unsignedLongField, unsignedLongField) > 0', + [] + ); + testErrorsAndWarnings('from a_index | eval var = pow(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval pow(doubleField, doubleField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_double(booleanField), to_double(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval pow(booleanField, booleanField)', [ + 'Argument of [pow] must be [double], found value [booleanField] type [boolean]', + 'Argument of [pow] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = pow(doubleField, integerField)', []); + testErrorsAndWarnings('from a_index | eval pow(doubleField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_double(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = pow(doubleField, longField)', []); + testErrorsAndWarnings('from a_index | eval pow(doubleField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_double(booleanField), longField)', + [] + ); + testErrorsAndWarnings('from a_index | eval var = pow(doubleField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval pow(doubleField, unsignedLongField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_double(booleanField), unsignedLongField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = pow(integerField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval pow(integerField, doubleField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_integer(booleanField), to_double(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = pow(integerField, integerField)', []); + testErrorsAndWarnings('from a_index | eval pow(integerField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = pow(integerField, longField)', []); + testErrorsAndWarnings('from a_index | eval pow(integerField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_integer(booleanField), longField)', + [] + ); + testErrorsAndWarnings('from a_index | eval var = pow(integerField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval pow(integerField, unsignedLongField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(to_integer(booleanField), unsignedLongField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = pow(longField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval pow(longField, doubleField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = pow(longField, to_double(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = pow(longField, integerField)', []); + testErrorsAndWarnings('from a_index | eval pow(longField, integerField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = pow(longField, to_integer(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = pow(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval pow(longField, longField)', []); + testErrorsAndWarnings('from a_index | eval var = pow(longField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval pow(longField, unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = pow(unsignedLongField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval pow(unsignedLongField, doubleField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(unsignedLongField, to_double(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = pow(unsignedLongField, integerField)', []); + testErrorsAndWarnings('from a_index | eval pow(unsignedLongField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = pow(unsignedLongField, to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = pow(unsignedLongField, longField)', []); + testErrorsAndWarnings('from a_index | eval pow(unsignedLongField, longField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = pow(unsignedLongField, unsignedLongField)', + [] + ); + testErrorsAndWarnings('from a_index | eval pow(unsignedLongField, unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval pow(doubleField, doubleField, extraArg)', [ + 'Error: [pow] function expects exactly 2 arguments, got 3.', + ]); + + testErrorsAndWarnings('from a_index | sort pow(doubleField, doubleField)', []); + testErrorsAndWarnings('from a_index | eval pow(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval pow(nullVar, nullVar)', []); + }); + + describe('repeat', () => { + testErrorsAndWarnings('row var = repeat("a", 5)', []); + testErrorsAndWarnings('row repeat("a", 5)', []); + testErrorsAndWarnings('row var = repeat(to_string(true), to_integer(true))', []); + + testErrorsAndWarnings('row var = repeat(true, true)', [ + 'Argument of [repeat] must be [keyword], found value [true] type [boolean]', + 'Argument of [repeat] must be [integer], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = repeat(keywordField, integerField)', []); + testErrorsAndWarnings('from a_index | eval repeat(keywordField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = repeat(to_string(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval repeat(booleanField, booleanField)', [ + 'Argument of [repeat] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [repeat] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = repeat(textField, integerField)', []); + testErrorsAndWarnings('from a_index | eval repeat(textField, integerField)', []); + + testErrorsAndWarnings('from a_index | eval repeat(keywordField, integerField, extraArg)', [ + 'Error: [repeat] function expects exactly 2 arguments, got 3.', + ]); + + testErrorsAndWarnings('from a_index | sort repeat(keywordField, integerField)', []); + testErrorsAndWarnings('from a_index | eval repeat(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval repeat(nullVar, nullVar)', []); + }); + + describe('replace', () => { + testErrorsAndWarnings('row var = replace("a", "a", "a")', []); + testErrorsAndWarnings('row replace("a", "a", "a")', []); + testErrorsAndWarnings( + 'row var = replace(to_string(true), to_string(true), to_string(true))', + [] + ); + + testErrorsAndWarnings('row var = replace(true, true, true)', [ + 'Argument of [replace] must be [keyword], found value [true] type [boolean]', + 'Argument of [replace] must be [keyword], found value [true] type [boolean]', + 'Argument of [replace] must be [keyword], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(keywordField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(keywordField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(to_string(booleanField), to_string(booleanField), to_string(booleanField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(booleanField, booleanField, booleanField)', + [ + 'Argument of [replace] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [replace] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [replace] must be [keyword], found value [booleanField] type [boolean]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(keywordField, keywordField, textField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(keywordField, keywordField, textField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(keywordField, textField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(keywordField, textField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(keywordField, textField, textField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(keywordField, textField, textField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(textField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(textField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(textField, keywordField, textField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(textField, keywordField, textField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = replace(textField, textField, keywordField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval replace(textField, textField, keywordField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | eval var = replace(textField, textField, textField)', + [] + ); + testErrorsAndWarnings('from a_index | eval replace(textField, textField, textField)', []); + + testErrorsAndWarnings( + 'from a_index | eval replace(keywordField, keywordField, keywordField, extraArg)', + ['Error: [replace] function expects exactly 3 arguments, got 4.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort replace(keywordField, keywordField, keywordField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval replace(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval replace(nullVar, nullVar, nullVar)', []); + }); + + describe('right', () => { + testErrorsAndWarnings('row var = right("a", 5)', []); + testErrorsAndWarnings('row right("a", 5)', []); + testErrorsAndWarnings('row var = right(to_string(true), to_integer(true))', []); + + testErrorsAndWarnings('row var = right(true, true)', [ + 'Argument of [right] must be [keyword], found value [true] type [boolean]', + 'Argument of [right] must be [integer], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = right(keywordField, integerField)', []); + testErrorsAndWarnings('from a_index | eval right(keywordField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = right(to_string(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval right(booleanField, booleanField)', [ + 'Argument of [right] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [right] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = right(textField, integerField)', []); + testErrorsAndWarnings('from a_index | eval right(textField, integerField)', []); + + testErrorsAndWarnings('from a_index | eval right(keywordField, integerField, extraArg)', [ + 'Error: [right] function expects exactly 2 arguments, got 3.', + ]); + + testErrorsAndWarnings('from a_index | sort right(keywordField, integerField)', []); + testErrorsAndWarnings('from a_index | eval right(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval right(nullVar, nullVar)', []); + }); + + describe('round', () => { + testErrorsAndWarnings('row var = round(5.5)', []); + testErrorsAndWarnings('row round(5.5)', []); + testErrorsAndWarnings('row var = round(to_double(true))', []); + testErrorsAndWarnings('row var = round(5.5, 5)', []); + testErrorsAndWarnings('row round(5.5, 5)', []); + testErrorsAndWarnings('row var = round(to_double(true), to_integer(true))', []); + testErrorsAndWarnings('row var = round(5)', []); + testErrorsAndWarnings('row round(5)', []); + testErrorsAndWarnings('row var = round(to_integer(true))', []); + testErrorsAndWarnings('row var = round(5, 5)', []); + testErrorsAndWarnings('row round(5, 5)', []); + testErrorsAndWarnings('row var = round(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('row var = round(5, to_integer(true))', []); + + testErrorsAndWarnings('row var = round(true, true)', [ + 'Argument of [round] must be [double], found value [true] type [boolean]', + 'Argument of [round] must be [integer], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where round(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where round(booleanField) > 0', [ + 'Argument of [round] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where round(doubleField, integerField) > 0', []); + + testErrorsAndWarnings('from a_index | where round(booleanField, booleanField) > 0', [ + 'Argument of [round] must be [double], found value [booleanField] type [boolean]', + 'Argument of [round] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where round(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where round(integerField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where round(longField) > 0', []); + testErrorsAndWarnings('from a_index | where round(longField, integerField) > 0', []); + testErrorsAndWarnings('from a_index | where round(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = round(doubleField)', []); + testErrorsAndWarnings('from a_index | eval round(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = round(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval round(booleanField)', [ + 'Argument of [round] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = round(*)', [ + 'Using wildcards (*) in round is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = round(doubleField, integerField)', []); + testErrorsAndWarnings('from a_index | eval round(doubleField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = round(to_double(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval round(booleanField, booleanField)', [ + 'Argument of [round] must be [double], found value [booleanField] type [boolean]', + 'Argument of [round] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = round(integerField)', []); + testErrorsAndWarnings('from a_index | eval round(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = round(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = round(integerField, integerField)', []); + testErrorsAndWarnings('from a_index | eval round(integerField, integerField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = round(to_integer(booleanField), to_integer(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval var = round(longField)', []); + testErrorsAndWarnings('from a_index | eval round(longField)', []); + testErrorsAndWarnings('from a_index | eval var = round(longField, integerField)', []); + testErrorsAndWarnings('from a_index | eval round(longField, integerField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = round(longField, to_integer(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = round(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval round(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval round(doubleField, integerField, extraArg)', [ + 'Error: [round] function expects no more than 2 arguments, got 3.', + ]); + + testErrorsAndWarnings('from a_index | sort round(doubleField)', []); + testErrorsAndWarnings('from a_index | eval round(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval round(nullVar, nullVar)', []); + }); + + describe('rtrim', () => { + testErrorsAndWarnings('row var = rtrim("a")', []); + testErrorsAndWarnings('row rtrim("a")', []); + testErrorsAndWarnings('row var = rtrim(to_string(true))', []); + + testErrorsAndWarnings('row var = rtrim(true)', [ + 'Argument of [rtrim] must be [keyword], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = rtrim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval rtrim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = rtrim(to_string(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval rtrim(booleanField)', [ + 'Argument of [rtrim] must be [keyword], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = rtrim(*)', [ + 'Using wildcards (*) in rtrim is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = rtrim(textField)', []); + testErrorsAndWarnings('from a_index | eval rtrim(textField)', []); + + testErrorsAndWarnings('from a_index | eval rtrim(keywordField, extraArg)', [ + 'Error: [rtrim] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort rtrim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval rtrim(null)', []); + testErrorsAndWarnings('row nullVar = null | eval rtrim(nullVar)', []); + }); + + describe('signum', () => { + testErrorsAndWarnings('row var = signum(5.5)', []); + testErrorsAndWarnings('row signum(5.5)', []); + testErrorsAndWarnings('row var = signum(to_double(true))', []); + testErrorsAndWarnings('row var = signum(5)', []); + testErrorsAndWarnings('row signum(5)', []); + testErrorsAndWarnings('row var = signum(to_integer(true))', []); + + testErrorsAndWarnings('row var = signum(true)', [ + 'Argument of [signum] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where signum(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where signum(booleanField) > 0', [ + 'Argument of [signum] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where signum(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where signum(longField) > 0', []); + testErrorsAndWarnings('from a_index | where signum(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = signum(doubleField)', []); + testErrorsAndWarnings('from a_index | eval signum(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = signum(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval signum(booleanField)', [ + 'Argument of [signum] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = signum(*)', [ + 'Using wildcards (*) in signum is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = signum(integerField)', []); + testErrorsAndWarnings('from a_index | eval signum(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = signum(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = signum(longField)', []); + testErrorsAndWarnings('from a_index | eval signum(longField)', []); + testErrorsAndWarnings('from a_index | eval var = signum(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval signum(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval signum(doubleField, extraArg)', [ + 'Error: [signum] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort signum(doubleField)', []); + testErrorsAndWarnings('from a_index | eval signum(null)', []); + testErrorsAndWarnings('row nullVar = null | eval signum(nullVar)', []); + }); + + describe('sin', () => { + testErrorsAndWarnings('row var = sin(5.5)', []); + testErrorsAndWarnings('row sin(5.5)', []); + testErrorsAndWarnings('row var = sin(to_double(true))', []); + testErrorsAndWarnings('row var = sin(5)', []); + testErrorsAndWarnings('row sin(5)', []); + testErrorsAndWarnings('row var = sin(to_integer(true))', []); + + testErrorsAndWarnings('row var = sin(true)', [ + 'Argument of [sin] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where sin(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where sin(booleanField) > 0', [ + 'Argument of [sin] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where sin(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where sin(longField) > 0', []); + testErrorsAndWarnings('from a_index | where sin(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = sin(doubleField)', []); + testErrorsAndWarnings('from a_index | eval sin(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = sin(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval sin(booleanField)', [ + 'Argument of [sin] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = sin(*)', [ + 'Using wildcards (*) in sin is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = sin(integerField)', []); + testErrorsAndWarnings('from a_index | eval sin(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = sin(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = sin(longField)', []); + testErrorsAndWarnings('from a_index | eval sin(longField)', []); + testErrorsAndWarnings('from a_index | eval var = sin(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval sin(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval sin(doubleField, extraArg)', [ + 'Error: [sin] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort sin(doubleField)', []); + testErrorsAndWarnings('from a_index | eval sin(null)', []); + testErrorsAndWarnings('row nullVar = null | eval sin(nullVar)', []); + }); + + describe('sinh', () => { + testErrorsAndWarnings('row var = sinh(5.5)', []); + testErrorsAndWarnings('row sinh(5.5)', []); + testErrorsAndWarnings('row var = sinh(to_double(true))', []); + testErrorsAndWarnings('row var = sinh(5)', []); + testErrorsAndWarnings('row sinh(5)', []); + testErrorsAndWarnings('row var = sinh(to_integer(true))', []); + + testErrorsAndWarnings('row var = sinh(true)', [ + 'Argument of [sinh] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where sinh(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where sinh(booleanField) > 0', [ + 'Argument of [sinh] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where sinh(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where sinh(longField) > 0', []); + testErrorsAndWarnings('from a_index | where sinh(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = sinh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval sinh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = sinh(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval sinh(booleanField)', [ + 'Argument of [sinh] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = sinh(*)', [ + 'Using wildcards (*) in sinh is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = sinh(integerField)', []); + testErrorsAndWarnings('from a_index | eval sinh(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = sinh(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = sinh(longField)', []); + testErrorsAndWarnings('from a_index | eval sinh(longField)', []); + testErrorsAndWarnings('from a_index | eval var = sinh(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval sinh(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval sinh(doubleField, extraArg)', [ + 'Error: [sinh] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort sinh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval sinh(null)', []); + testErrorsAndWarnings('row nullVar = null | eval sinh(nullVar)', []); + }); + + describe('split', () => { + testErrorsAndWarnings('row var = split("a", "a")', []); + testErrorsAndWarnings('row split("a", "a")', []); + testErrorsAndWarnings('row var = split(to_string(true), to_string(true))', []); + + testErrorsAndWarnings('row var = split(true, true)', [ + 'Argument of [split] must be [keyword], found value [true] type [boolean]', + 'Argument of [split] must be [keyword], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = split(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval split(keywordField, keywordField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = split(to_string(booleanField), to_string(booleanField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval split(booleanField, booleanField)', [ + 'Argument of [split] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [split] must be [keyword], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = split(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval split(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = split(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval split(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = split(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval split(textField, textField)', []); + + testErrorsAndWarnings('from a_index | eval split(keywordField, keywordField, extraArg)', [ + 'Error: [split] function expects exactly 2 arguments, got 3.', + ]); + + testErrorsAndWarnings('from a_index | sort split(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval split(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval split(nullVar, nullVar)', []); + }); + + describe('sqrt', () => { + testErrorsAndWarnings('row var = sqrt(5.5)', []); + testErrorsAndWarnings('row sqrt(5.5)', []); + testErrorsAndWarnings('row var = sqrt(to_double(true))', []); + testErrorsAndWarnings('row var = sqrt(5)', []); + testErrorsAndWarnings('row sqrt(5)', []); + testErrorsAndWarnings('row var = sqrt(to_integer(true))', []); + + testErrorsAndWarnings('row var = sqrt(true)', [ + 'Argument of [sqrt] must be [double], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where sqrt(doubleField) > 0', []); + + testErrorsAndWarnings('from a_index | where sqrt(booleanField) > 0', [ + 'Argument of [sqrt] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | where sqrt(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where sqrt(longField) > 0', []); + testErrorsAndWarnings('from a_index | where sqrt(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = sqrt(doubleField)', []); + testErrorsAndWarnings('from a_index | eval sqrt(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = sqrt(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval sqrt(booleanField)', [ + 'Argument of [sqrt] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = sqrt(*)', [ + 'Using wildcards (*) in sqrt is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = sqrt(integerField)', []); + testErrorsAndWarnings('from a_index | eval sqrt(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = sqrt(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = sqrt(longField)', []); + testErrorsAndWarnings('from a_index | eval sqrt(longField)', []); + testErrorsAndWarnings('from a_index | eval var = sqrt(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval sqrt(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval sqrt(doubleField, extraArg)', [ + 'Error: [sqrt] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort sqrt(doubleField)', []); + testErrorsAndWarnings('from a_index | eval sqrt(null)', []); + testErrorsAndWarnings('row nullVar = null | eval sqrt(nullVar)', []); + }); + + describe('st_contains', () => { + testErrorsAndWarnings('row var = st_contains(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row st_contains(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_contains(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row st_contains(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row st_contains(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings('row var = st_contains(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row st_contains(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings( + 'row var = st_contains(geoPointField, to_geoshape("POINT (30 10)"))', + ['Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row st_contains(geoPointField, to_geoshape("POINT (30 10)"))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings( + 'row var = st_contains(to_geoshape("POINT (30 10)"), geoPointField)', + ['Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row st_contains(to_geoshape("POINT (30 10)"), geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings('row var = st_contains(true, true)', [ + 'Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]', + 'Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_contains(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_contains(booleanField, booleanField)', [ + 'Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]', + 'Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(cartesianPointField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_contains(cartesianPointField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(cartesianShapeField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_contains(cartesianShapeField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(cartesianShapeField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_contains(cartesianShapeField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(geoPointField, geoPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_contains(geoPointField, geoPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(geoPointField, geoShapeField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_contains(geoPointField, geoShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(geoShapeField, geoPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_contains(geoShapeField, geoPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(geoShapeField, geoShapeField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_contains(geoShapeField, geoShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_contains(cartesianPointField, cartesianPointField, extraArg)', + ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort st_contains(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_contains(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_contains(nullVar, nullVar)', []); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_contains(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_contains(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); + }); + + describe('st_disjoint', () => { + testErrorsAndWarnings('row var = st_disjoint(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row st_disjoint(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row st_disjoint(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings('row var = st_disjoint(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row st_disjoint(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings( + 'row var = st_disjoint(geoPointField, to_geoshape("POINT (30 10)"))', + ['Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row st_disjoint(geoPointField, to_geoshape("POINT (30 10)"))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings( + 'row var = st_disjoint(to_geoshape("POINT (30 10)"), geoPointField)', + ['Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row st_disjoint(to_geoshape("POINT (30 10)"), geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings('row var = st_disjoint(true, true)', [ + 'Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]', + 'Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_disjoint(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_disjoint(booleanField, booleanField)', [ + 'Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]', + 'Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(cartesianPointField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(cartesianShapeField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(cartesianShapeField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(geoPointField, geoPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_disjoint(geoPointField, geoPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(geoPointField, geoShapeField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_disjoint(geoPointField, geoShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(geoShapeField, geoPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_disjoint(geoShapeField, geoPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(geoShapeField, geoShapeField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_disjoint(geoShapeField, geoShapeField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_disjoint(cartesianPointField, cartesianPointField, extraArg)', + ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort st_disjoint(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_disjoint(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_disjoint(nullVar, nullVar)', []); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_disjoint(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_disjoint(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); + }); + + describe('st_distance', () => { + testErrorsAndWarnings('row var = st_distance(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row st_distance(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + + testErrorsAndWarnings( + 'row var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings('row var = st_distance(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row st_distance(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings('row var = st_distance(true, true)', [ + 'Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]', + 'Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_distance(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_distance(booleanField, booleanField)', [ + 'Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]', + 'Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(geoPointField, geoPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval st_distance(geoPointField, geoPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_distance(cartesianPointField, cartesianPointField, extraArg)', + ['Error: [st_distance] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort st_distance(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_distance(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_distance(nullVar, nullVar)', []); + + testErrorsAndWarnings( + 'row var = st_distance(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_distance(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_distance(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_distance(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_distance(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_distance(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); + }); + + describe('st_intersects', () => { + testErrorsAndWarnings('row var = st_intersects(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row st_intersects(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', + ]); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row st_intersects(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row st_intersects(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); + + testErrorsAndWarnings('row var = st_intersects(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row st_intersects(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings( + 'row var = st_intersects(geoPointField, to_geoshape("POINT (30 10)"))', + ['Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row st_intersects(geoPointField, to_geoshape("POINT (30 10)"))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings( + 'row var = st_intersects(to_geoshape("POINT (30 10)"), geoPointField)', + ['Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row st_intersects(to_geoshape("POINT (30 10)"), geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings( + 'row var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_intersects(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + + testErrorsAndWarnings('row var = st_intersects(true, true)', [ + 'Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]', + 'Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_intersects(booleanField, booleanField)', [ + 'Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]', + 'Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(cartesianPointField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(cartesianPointField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(cartesianShapeField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(cartesianShapeField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(cartesianShapeField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(geoPointField, geoPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(geoPointField, geoPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(geoPointField, geoShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(geoPointField, geoShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(geoShapeField, geoPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(geoShapeField, geoPointField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(geoShapeField, geoShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(geoShapeField, geoShapeField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))', + [] + ); + + testErrorsAndWarnings( + 'from a_index | eval st_intersects(cartesianPointField, cartesianPointField, extraArg)', + ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + ); + + testErrorsAndWarnings( + 'from a_index | sort st_intersects(cartesianPointField, cartesianPointField)', + [] + ); + + testErrorsAndWarnings('from a_index | eval st_intersects(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_intersects(nullVar, nullVar)', []); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); testErrorsAndWarnings( - 'row var = mv_last(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'row var = st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', [] ); - testErrorsAndWarnings('row var = mv_last(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_last(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings( - 'row var = mv_last(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'row st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', [] ); - testErrorsAndWarnings('row var = mv_last(now())', []); - testErrorsAndWarnings('row mv_last(now())', []); - testErrorsAndWarnings('row var = mv_last(to_datetime(now()))', []); - testErrorsAndWarnings('row var = mv_last(5)', []); - testErrorsAndWarnings('row mv_last(5)', []); - testErrorsAndWarnings('row var = mv_last(to_integer(true))', []); - testErrorsAndWarnings('row var = mv_last(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_last(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_last(to_geopoint(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = mv_last(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row mv_last(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = mv_last(to_geoshape(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = mv_last(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row mv_last(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = mv_last(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = mv_last(to_string(true))', []); - testErrorsAndWarnings('row var = mv_last(to_version("1.0.0"))', []); - testErrorsAndWarnings('row mv_last(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = mv_last(to_version("a"))', []); - testErrorsAndWarnings('from a_index | where mv_last(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where length(mv_last(stringField)) > 0', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_boolean(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(cartesianPointField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = mv_last(to_cartesianpoint(cartesianPointField))', + 'row var = st_intersects(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_last(cartesianShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(cartesianShapeField)', []); + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = mv_last(to_cartesianshape(cartesianPointField))', + 'row st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', [] ); - testErrorsAndWarnings('from a_index | eval var = mv_last(dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_geopoint(geoPointField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_geoshape(geoPointField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(ipField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(versionField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_last(to_version(stringField))', []); + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] + ); - testErrorsAndWarnings('from a_index | eval mv_last(booleanField, extraArg)', [ - 'Error: [mv_last] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'row var = st_intersects(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + [] + ); - testErrorsAndWarnings('from a_index | sort mv_last(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_last(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_last(nullVar)', []); - }); + testErrorsAndWarnings( + 'row var = st_intersects(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); - describe('mv_max', () => { - testErrorsAndWarnings('row var = mv_max("a")', []); - testErrorsAndWarnings('row mv_max("a")', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_max(stringField)', []); + testErrorsAndWarnings( + 'row st_intersects(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_max(*)', [ - 'Using wildcards (*) in mv_max is not allowed', - ]); + testErrorsAndWarnings( + 'row var = st_intersects(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); - testErrorsAndWarnings('from a_index | sort mv_max(stringField)', []); - testErrorsAndWarnings('row var = mv_max(true)', []); - testErrorsAndWarnings('row mv_max(true)', []); - testErrorsAndWarnings('row var = mv_max(to_boolean(true))', []); - testErrorsAndWarnings('row var = mv_max(now())', []); - testErrorsAndWarnings('row mv_max(now())', []); - testErrorsAndWarnings('row var = mv_max(to_datetime(now()))', []); - testErrorsAndWarnings('row var = mv_max(5)', []); - testErrorsAndWarnings('row mv_max(5)', []); - testErrorsAndWarnings('row var = mv_max(to_integer(true))', []); - testErrorsAndWarnings('row var = mv_max(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row mv_max(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = mv_max(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = mv_max(to_string(true))', []); - testErrorsAndWarnings('row var = mv_max(to_version("1.0.0"))', []); - testErrorsAndWarnings('row mv_max(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = mv_max(to_version("a"))', []); + testErrorsAndWarnings( + 'row var = st_intersects(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('row var = mv_max(to_cartesianpoint("POINT (30 10)"))', [ - 'Argument of [mv_max] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', - ]); + testErrorsAndWarnings( + 'row st_intersects(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | where mv_max(numberField) > 0', []); + testErrorsAndWarnings( + 'row var = st_intersects(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); - testErrorsAndWarnings('from a_index | where mv_max(cartesianPointField) > 0', [ - 'Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); + testErrorsAndWarnings( + 'row var = st_intersects(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | where length(mv_max(stringField)) > 0', []); + testErrorsAndWarnings( + 'row st_intersects(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | where length(mv_max(cartesianPointField)) > 0', [ - 'Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); + testErrorsAndWarnings( + 'row var = st_intersects(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_max(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_max(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(to_boolean(booleanField))', []); + testErrorsAndWarnings( + 'row var = st_intersects(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + [] + ); + }); - testErrorsAndWarnings('from a_index | eval mv_max(cartesianPointField)', [ - 'Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + describe('st_within', () => { + testErrorsAndWarnings('row var = st_within(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', ]); - - testErrorsAndWarnings('from a_index | eval var = mv_max(dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_max(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_max(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(ipField)', []); - testErrorsAndWarnings('from a_index | eval mv_max(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(versionField)', []); - testErrorsAndWarnings('from a_index | eval mv_max(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_max(to_version(stringField))', []); - - testErrorsAndWarnings('from a_index | eval mv_max(booleanField, extraArg)', [ - 'Error: [mv_max] function expects exactly one argument, got 2.', + testErrorsAndWarnings('row st_within(cartesianPointField, cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + 'Unknown column [cartesianPointField]', ]); - testErrorsAndWarnings('from a_index | sort mv_max(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_max(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_max(nullVar)', []); - }); - - describe('mv_median', () => { - testErrorsAndWarnings('row var = mv_median(5)', []); - testErrorsAndWarnings('row mv_median(5)', []); - testErrorsAndWarnings('row var = mv_median(to_integer("a"))', []); + testErrorsAndWarnings( + 'row var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('row var = mv_median("a")', [ - 'Argument of [mv_median] must be [number], found value ["a"] type [string]', - ]); + testErrorsAndWarnings( + 'row var = st_within(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | where mv_median(numberField) > 0', []); + testErrorsAndWarnings( + 'row st_within(cartesianPointField, to_cartesianshape("POINT (30 10)"))', + ['Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | where mv_median(stringField) > 0', [ - 'Argument of [mv_median] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'row var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | eval var = mv_median(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_median(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_median(to_integer(stringField))', []); + testErrorsAndWarnings( + 'row var = st_within(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | eval mv_median(stringField)', [ - 'Argument of [mv_median] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'row st_within(to_cartesianshape("POINT (30 10)"), cartesianPointField)', + ['Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | eval mv_median(numberField, extraArg)', [ - 'Error: [mv_median] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'row var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | eval var = mv_median(*)', [ - 'Using wildcards (*) in mv_median is not allowed', - ]); + testErrorsAndWarnings( + 'row var = st_within(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | sort mv_median(numberField)', []); - testErrorsAndWarnings('row var = mv_median(to_integer(true))', []); + testErrorsAndWarnings( + 'row st_within(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('row var = mv_median(true)', [ - 'Argument of [mv_median] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'row var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]', 'Unknown column [cartesianPointField]'] + ); - testErrorsAndWarnings('from a_index | where mv_median(booleanField) > 0', [ - 'Argument of [mv_median] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('row var = st_within(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', ]); - - testErrorsAndWarnings('from a_index | eval var = mv_median(to_integer(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval mv_median(booleanField)', [ - 'Argument of [mv_median] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('row st_within(geoPointField, geoPointField)', [ + 'Unknown column [geoPointField]', + 'Unknown column [geoPointField]', ]); - testErrorsAndWarnings('from a_index | eval mv_median(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_median(nullVar)', []); - }); - - describe('mv_min', () => { - testErrorsAndWarnings('row var = mv_min("a")', []); - testErrorsAndWarnings('row mv_min("a")', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_min(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = mv_min(*)', [ - 'Using wildcards (*) in mv_min is not allowed', + testErrorsAndWarnings( + 'row var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row var = st_within(geoPointField, to_geoshape("POINT (30 10)"))', [ + 'Unknown column [geoPointField]', ]); - - testErrorsAndWarnings('from a_index | sort mv_min(stringField)', []); - testErrorsAndWarnings('row var = mv_min(true)', []); - testErrorsAndWarnings('row mv_min(true)', []); - testErrorsAndWarnings('row var = mv_min(to_boolean(true))', []); - testErrorsAndWarnings('row var = mv_min(now())', []); - testErrorsAndWarnings('row mv_min(now())', []); - testErrorsAndWarnings('row var = mv_min(to_datetime(now()))', []); - testErrorsAndWarnings('row var = mv_min(5)', []); - testErrorsAndWarnings('row mv_min(5)', []); - testErrorsAndWarnings('row var = mv_min(to_integer(true))', []); - testErrorsAndWarnings('row var = mv_min(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row mv_min(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = mv_min(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = mv_min(to_string(true))', []); - testErrorsAndWarnings('row var = mv_min(to_version("1.0.0"))', []); - testErrorsAndWarnings('row mv_min(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = mv_min(to_version("a"))', []); - - testErrorsAndWarnings('row var = mv_min(to_cartesianpoint("POINT (30 10)"))', [ - 'Argument of [mv_min] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + testErrorsAndWarnings('row st_within(geoPointField, to_geoshape("POINT (30 10)"))', [ + 'Unknown column [geoPointField]', ]); - - testErrorsAndWarnings('from a_index | where mv_min(numberField) > 0', []); - - testErrorsAndWarnings('from a_index | where mv_min(cartesianPointField) > 0', [ - 'Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings( + 'row var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); + testErrorsAndWarnings('row var = st_within(to_geoshape("POINT (30 10)"), geoPointField)', [ + 'Unknown column [geoPointField]', ]); - - testErrorsAndWarnings('from a_index | where length(mv_min(stringField)) > 0', []); - - testErrorsAndWarnings('from a_index | where length(mv_min(cartesianPointField)) > 0', [ - 'Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings('row st_within(to_geoshape("POINT (30 10)"), geoPointField)', [ + 'Unknown column [geoPointField]', ]); + testErrorsAndWarnings( + 'row var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); - testErrorsAndWarnings('from a_index | eval var = mv_min(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_min(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(to_boolean(booleanField))', []); + testErrorsAndWarnings( + 'row var = st_within(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | eval mv_min(cartesianPointField)', [ - 'Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); + testErrorsAndWarnings( + 'row st_within(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = mv_min(dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_min(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_min(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(ipField)', []); - testErrorsAndWarnings('from a_index | eval mv_min(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(versionField)', []); - testErrorsAndWarnings('from a_index | eval mv_min(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_min(to_version(stringField))', []); + testErrorsAndWarnings( + 'row var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))', + ['Unknown column [geoPointField]', 'Unknown column [geoPointField]'] + ); - testErrorsAndWarnings('from a_index | eval mv_min(booleanField, extraArg)', [ - 'Error: [mv_min] function expects exactly one argument, got 2.', + testErrorsAndWarnings('row var = st_within(true, true)', [ + 'Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]', + 'Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort mv_min(booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_min(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_min(nullVar)', []); - }); - - describe('mv_slice', () => { - testErrorsAndWarnings('row var = mv_slice("a", 5, 5)', []); - testErrorsAndWarnings('row mv_slice("a", 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_within(cartesianPointField, cartesianPointField)', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(stringField, numberField, numberField)', + 'from a_index | eval st_within(cartesianPointField, cartesianPointField)', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(stringField, numberField, numberField)', + 'from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', [] ); + + testErrorsAndWarnings('from a_index | eval st_within(booleanField, booleanField)', [ + 'Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]', + 'Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + testErrorsAndWarnings( - 'from a_index | sort mv_slice(stringField, numberField, numberField)', + 'from a_index | eval var = st_within(cartesianPointField, cartesianShapeField)', [] ); - testErrorsAndWarnings('row var = mv_slice(true, 5, 5)', []); - testErrorsAndWarnings('row mv_slice(true, 5, 5)', []); testErrorsAndWarnings( - 'row var = mv_slice(to_boolean(true), to_integer(true), to_integer(true))', + 'from a_index | eval st_within(cartesianPointField, cartesianShapeField)', [] ); - testErrorsAndWarnings('row var = mv_slice(to_cartesianpoint("POINT (30 10)"), 5, 5)', []); - testErrorsAndWarnings('row mv_slice(to_cartesianpoint("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_integer(true), to_integer(true))', + 'from a_index | eval var = st_within(cartesianShapeField, cartesianPointField)', [] ); - testErrorsAndWarnings('row var = mv_slice(to_cartesianshape("POINT (30 10)"), 5, 5)', []); - testErrorsAndWarnings('row mv_slice(to_cartesianshape("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval st_within(cartesianShapeField, cartesianPointField)', + [] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_integer(true), to_integer(true))', + 'from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', [] ); - testErrorsAndWarnings('row var = mv_slice(now(), 5, 5)', []); - testErrorsAndWarnings('row mv_slice(now(), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_within(cartesianShapeField, cartesianShapeField)', + [] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_datetime(now()), to_integer(true), to_integer(true))', + 'from a_index | eval st_within(cartesianShapeField, cartesianShapeField)', [] ); - testErrorsAndWarnings('row var = mv_slice(5, 5, 5)', []); - testErrorsAndWarnings('row mv_slice(5, 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + [] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_integer(true), to_integer(true), to_integer(true))', + 'from a_index | eval var = st_within(geoPointField, geoPointField)', [] ); + testErrorsAndWarnings('from a_index | eval st_within(geoPointField, geoPointField)', []); - testErrorsAndWarnings('row var = mv_slice(to_geopoint("POINT (30 10)"), 5, 5)', []); - testErrorsAndWarnings('row mv_slice(to_geopoint("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))', + [] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_geopoint(to_geopoint("POINT (30 10)")), to_integer(true), to_integer(true))', + 'from a_index | eval var = st_within(geoPointField, geoShapeField)', [] ); + testErrorsAndWarnings('from a_index | eval st_within(geoPointField, geoShapeField)', []); - testErrorsAndWarnings('row var = mv_slice(to_geoshape("POINT (30 10)"), 5, 5)', []); - testErrorsAndWarnings('row mv_slice(to_geoshape("POINT (30 10)"), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))', + [] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_geoshape(to_geopoint("POINT (30 10)")), to_integer(true), to_integer(true))', + 'from a_index | eval var = st_within(geoShapeField, geoPointField)', [] ); + testErrorsAndWarnings('from a_index | eval st_within(geoShapeField, geoPointField)', []); - testErrorsAndWarnings('row var = mv_slice(to_ip("127.0.0.1"), 5, 5)', []); - testErrorsAndWarnings('row mv_slice(to_ip("127.0.0.1"), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))', + [] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_ip(to_ip("127.0.0.1")), to_integer(true), to_integer(true))', + 'from a_index | eval var = st_within(geoShapeField, geoShapeField)', [] ); + testErrorsAndWarnings('from a_index | eval st_within(geoShapeField, geoShapeField)', []); testErrorsAndWarnings( - 'row var = mv_slice(to_string(true), to_integer(true), to_integer(true))', + 'from a_index | eval var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))', [] ); - testErrorsAndWarnings('row var = mv_slice(to_version("1.0.0"), 5, 5)', []); - testErrorsAndWarnings('row mv_slice(to_version("1.0.0"), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | eval st_within(cartesianPointField, cartesianPointField, extraArg)', + ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + ); testErrorsAndWarnings( - 'row var = mv_slice(to_version("a"), to_integer(true), to_integer(true))', + 'from a_index | sort st_within(cartesianPointField, cartesianPointField)', [] ); - testErrorsAndWarnings('row var = mv_slice(to_version("1.0.0"), true, true)', [ - 'Argument of [mv_slice] must be [number], found value [true] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | eval st_within(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_within(nullVar, nullVar)', []); testErrorsAndWarnings( - 'from a_index | where mv_slice(numberField, numberField, numberField) > 0', + 'row var = st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | where mv_slice(numberField, booleanField, booleanField) > 0', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] + 'row st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + [] ); testErrorsAndWarnings( - 'from a_index | where length(mv_slice(stringField, numberField, numberField)) > 0', + 'row var = st_within(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', [] ); testErrorsAndWarnings( - 'from a_index | where length(mv_slice(stringField, booleanField, booleanField)) > 0', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] + 'row var = st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] + ); + + testErrorsAndWarnings( + 'row st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(booleanField, numberField, numberField)', + 'row var = st_within(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(booleanField, numberField, numberField)', + 'row var = st_within(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_boolean(booleanField), to_integer(booleanField), to_integer(booleanField))', + 'row st_within(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(booleanField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] + 'row var = st_within(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(cartesianPointField, numberField, numberField)', + 'row var = st_within(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(cartesianPointField, numberField, numberField)', + 'row var = st_within(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(booleanField), to_integer(booleanField))', + 'row st_within(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(cartesianPointField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] + 'row var = st_within(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(cartesianShapeField, numberField, numberField)', + 'row var = st_within(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(cartesianShapeField, numberField, numberField)', + 'row st_within(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(booleanField), to_integer(booleanField))', + 'row var = st_within(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(cartesianShapeField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] + 'row var = st_within(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(dateField, numberField, numberField)', + 'row st_within(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(dateField, numberField, numberField)', + 'row var = st_within(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_datetime(dateField), to_integer(booleanField), to_integer(booleanField))', + 'row var = st_within(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', [] ); + }); + + describe('st_x', () => { + testErrorsAndWarnings('row var = st_x(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row st_x(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = st_x(to_cartesianpoint(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = st_x(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row st_x(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = st_x(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + + testErrorsAndWarnings('row var = st_x(true)', [ + 'Argument of [st_x] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = st_x(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval st_x(cartesianPointField)', []); testErrorsAndWarnings( - 'from a_index | eval mv_slice(dateField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] + 'from a_index | eval var = st_x(to_cartesianpoint(cartesianPointField))', + [] ); + testErrorsAndWarnings('from a_index | eval st_x(booleanField)', [ + 'Argument of [st_x] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = st_x(*)', [ + 'Using wildcards (*) in st_x is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = st_x(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval st_x(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval var = st_x(to_geopoint(geoPointField))', []); + + testErrorsAndWarnings('from a_index | eval st_x(cartesianPointField, extraArg)', [ + 'Error: [st_x] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort st_x(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval st_x(null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_x(nullVar)', []); + testErrorsAndWarnings('row var = st_x(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row st_x(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(numberField, numberField, numberField)', + 'row var = st_x(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', [] ); + testErrorsAndWarnings('row var = st_x(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row st_x(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = st_x(to_geopoint(to_geopoint("POINT (30 10)")))', []); + }); + + describe('st_y', () => { + testErrorsAndWarnings('row var = st_y(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row st_y(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = st_y(to_cartesianpoint(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = st_y(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row st_y(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = st_y(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + + testErrorsAndWarnings('row var = st_y(true)', [ + 'Argument of [st_y] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = st_y(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval st_y(cartesianPointField)', []); + testErrorsAndWarnings( - 'from a_index | eval mv_slice(numberField, numberField, numberField)', + 'from a_index | eval var = st_y(to_cartesianpoint(cartesianPointField))', [] ); + testErrorsAndWarnings('from a_index | eval st_y(booleanField)', [ + 'Argument of [st_y] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = st_y(*)', [ + 'Using wildcards (*) in st_y is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = st_y(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval st_y(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval var = st_y(to_geopoint(geoPointField))', []); + + testErrorsAndWarnings('from a_index | eval st_y(cartesianPointField, extraArg)', [ + 'Error: [st_y] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort st_y(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval st_y(null)', []); + testErrorsAndWarnings('row nullVar = null | eval st_y(nullVar)', []); + testErrorsAndWarnings('row var = st_y(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row st_y(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_integer(booleanField), to_integer(booleanField), to_integer(booleanField))', + 'row var = st_y(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', [] ); + testErrorsAndWarnings('row var = st_y(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row st_y(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = st_y(to_geopoint(to_geopoint("POINT (30 10)")))', []); + }); + + describe('starts_with', () => { + testErrorsAndWarnings('row var = starts_with("a", "a")', []); + testErrorsAndWarnings('row starts_with("a", "a")', []); + testErrorsAndWarnings('row var = starts_with(to_string(true), to_string(true))', []); + + testErrorsAndWarnings('row var = starts_with(true, true)', [ + 'Argument of [starts_with] must be [keyword], found value [true] type [boolean]', + 'Argument of [starts_with] must be [keyword], found value [true] type [boolean]', + ]); + testErrorsAndWarnings( - 'from a_index | eval mv_slice(numberField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] + 'from a_index | eval var = starts_with(keywordField, keywordField)', + [] ); + testErrorsAndWarnings('from a_index | eval starts_with(keywordField, keywordField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(geoPointField, numberField, numberField)', + 'from a_index | eval var = starts_with(to_string(booleanField), to_string(booleanField))', [] ); + testErrorsAndWarnings('from a_index | eval starts_with(booleanField, booleanField)', [ + 'Argument of [starts_with] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [starts_with] must be [keyword], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = starts_with(textField, textField)', []); + testErrorsAndWarnings('from a_index | eval starts_with(textField, textField)', []); + testErrorsAndWarnings( - 'from a_index | eval mv_slice(geoPointField, numberField, numberField)', - [] + 'from a_index | eval starts_with(keywordField, keywordField, extraArg)', + ['Error: [starts_with] function expects exactly 2 arguments, got 3.'] ); + testErrorsAndWarnings('from a_index | sort starts_with(keywordField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval starts_with(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval starts_with(nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | eval var = starts_with(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval starts_with(keywordField, textField)', []); + testErrorsAndWarnings('from a_index | eval var = starts_with(textField, keywordField)', []); + testErrorsAndWarnings('from a_index | eval starts_with(textField, keywordField)', []); + }); + + describe('substring', () => { + testErrorsAndWarnings('row var = substring("a", 5, 5)', []); + testErrorsAndWarnings('row substring("a", 5, 5)', []); + testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_geopoint(geoPointField), to_integer(booleanField), to_integer(booleanField))', + 'row var = substring(to_string(true), to_integer(true), to_integer(true))', [] ); - testErrorsAndWarnings( - 'from a_index | eval mv_slice(geoPointField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] - ); + testErrorsAndWarnings('row var = substring(true, true, true)', [ + 'Argument of [substring] must be [keyword], found value [true] type [boolean]', + 'Argument of [substring] must be [integer], found value [true] type [boolean]', + 'Argument of [substring] must be [integer], found value [true] type [boolean]', + ]); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(geoShapeField, numberField, numberField)', + 'from a_index | eval var = substring(keywordField, integerField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(geoShapeField, numberField, numberField)', + 'from a_index | eval substring(keywordField, integerField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_geoshape(geoPointField), to_integer(booleanField), to_integer(booleanField))', + 'from a_index | eval var = substring(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(geoShapeField, booleanField, booleanField)', + 'from a_index | eval substring(booleanField, booleanField, booleanField)', [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', + 'Argument of [substring] must be [keyword], found value [booleanField] type [boolean]', + 'Argument of [substring] must be [integer], found value [booleanField] type [boolean]', + 'Argument of [substring] must be [integer], found value [booleanField] type [boolean]', ] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(ipField, numberField, numberField)', + 'from a_index | eval var = substring(textField, integerField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval mv_slice(ipField, numberField, numberField)', + 'from a_index | eval substring(textField, integerField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))', - [] + 'from a_index | eval substring(keywordField, integerField, integerField, extraArg)', + ['Error: [substring] function expects no more than 3 arguments, got 4.'] ); - testErrorsAndWarnings('from a_index | eval mv_slice(ipField, booleanField, booleanField)', [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))', + 'from a_index | sort substring(keywordField, integerField, integerField)', [] ); - testErrorsAndWarnings( - 'from a_index | eval mv_slice(stringField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] - ); + testErrorsAndWarnings('from a_index | eval substring(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval substring(nullVar, nullVar, nullVar)', []); + }); - testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(versionField, numberField, numberField)', - [] - ); + describe('tan', () => { + testErrorsAndWarnings('row var = tan(5.5)', []); + testErrorsAndWarnings('row tan(5.5)', []); + testErrorsAndWarnings('row var = tan(to_double(true))', []); + testErrorsAndWarnings('row var = tan(5)', []); + testErrorsAndWarnings('row tan(5)', []); + testErrorsAndWarnings('row var = tan(to_integer(true))', []); - testErrorsAndWarnings( - 'from a_index | eval mv_slice(versionField, numberField, numberField)', - [] - ); + testErrorsAndWarnings('row var = tan(true)', [ + 'Argument of [tan] must be [double], found value [true] type [boolean]', + ]); - testErrorsAndWarnings( - 'from a_index | eval var = mv_slice(to_version(stringField), to_integer(booleanField), to_integer(booleanField))', - [] - ); + testErrorsAndWarnings('from a_index | where tan(doubleField) > 0', []); - testErrorsAndWarnings( - 'from a_index | eval mv_slice(versionField, booleanField, booleanField)', - [ - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - 'Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]', - ] - ); + testErrorsAndWarnings('from a_index | where tan(booleanField) > 0', [ + 'Argument of [tan] must be [double], found value [booleanField] type [boolean]', + ]); - testErrorsAndWarnings( - 'from a_index | eval mv_slice(booleanField, numberField, numberField, extraArg)', - ['Error: [mv_slice] function expects no more than 3 arguments, got 4.'] - ); + testErrorsAndWarnings('from a_index | where tan(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where tan(longField) > 0', []); + testErrorsAndWarnings('from a_index | where tan(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = tan(doubleField)', []); + testErrorsAndWarnings('from a_index | eval tan(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = tan(to_double(booleanField))', []); - testErrorsAndWarnings( - 'from a_index | sort mv_slice(booleanField, numberField, numberField)', - [] - ); - testErrorsAndWarnings('from a_index | eval mv_slice(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_slice(nullVar, nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | eval tan(booleanField)', [ + 'Argument of [tan] must be [double], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = tan(*)', [ + 'Using wildcards (*) in tan is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = tan(integerField)', []); + testErrorsAndWarnings('from a_index | eval tan(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = tan(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = tan(longField)', []); + testErrorsAndWarnings('from a_index | eval tan(longField)', []); + testErrorsAndWarnings('from a_index | eval var = tan(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval tan(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval tan(doubleField, extraArg)', [ + 'Error: [tan] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort tan(doubleField)', []); + testErrorsAndWarnings('from a_index | eval tan(null)', []); + testErrorsAndWarnings('row nullVar = null | eval tan(nullVar)', []); }); - describe('mv_sort', () => { - testErrorsAndWarnings('row var = mv_sort("a", "asc")', []); - testErrorsAndWarnings('row mv_sort("a", "asc")', []); - testErrorsAndWarnings('from a_index | eval var = mv_sort(stringField, "asc")', []); - testErrorsAndWarnings('from a_index | eval mv_sort(stringField, "asc")', []); - testErrorsAndWarnings('from a_index | sort mv_sort(stringField, "asc")', []); - testErrorsAndWarnings('row var = mv_sort(true, "asc")', []); - testErrorsAndWarnings('row mv_sort(true, "asc")', []); - testErrorsAndWarnings('row var = mv_sort(now(), "asc")', []); - testErrorsAndWarnings('row mv_sort(now(), "asc")', []); - testErrorsAndWarnings('row var = mv_sort(5, "asc")', []); - testErrorsAndWarnings('row mv_sort(5, "asc")', []); - testErrorsAndWarnings('row var = mv_sort(to_ip("127.0.0.1"), "asc")', []); - testErrorsAndWarnings('row mv_sort(to_ip("127.0.0.1"), "asc")', []); - testErrorsAndWarnings('row var = mv_sort(to_version("1.0.0"), "asc")', []); - testErrorsAndWarnings('row mv_sort(to_version("1.0.0"), "asc")', []); + describe('tanh', () => { + testErrorsAndWarnings('row var = tanh(5.5)', []); + testErrorsAndWarnings('row tanh(5.5)', []); + testErrorsAndWarnings('row var = tanh(to_double(true))', []); + testErrorsAndWarnings('row var = tanh(5)', []); + testErrorsAndWarnings('row tanh(5)', []); + testErrorsAndWarnings('row var = tanh(to_integer(true))', []); - testErrorsAndWarnings('row var = mv_sort(to_cartesianpoint("POINT (30 10)"), true)', [ - 'Argument of [mv_sort] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', - 'Argument of [mv_sort] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('row var = tanh(true)', [ + 'Argument of [tanh] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where mv_sort(numberField, "asc") > 0', []); + testErrorsAndWarnings('from a_index | where tanh(doubleField) > 0', []); - testErrorsAndWarnings( - 'from a_index | where mv_sort(cartesianPointField, booleanField) > 0', - [ - 'Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - 'Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]', - ] - ); + testErrorsAndWarnings('from a_index | where tanh(booleanField) > 0', [ + 'Argument of [tanh] must be [double], found value [booleanField] type [boolean]', + ]); - testErrorsAndWarnings('from a_index | where length(mv_sort(stringField, "asc")) > 0', []); + testErrorsAndWarnings('from a_index | where tanh(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where tanh(longField) > 0', []); + testErrorsAndWarnings('from a_index | where tanh(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = tanh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval tanh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = tanh(to_double(booleanField))', []); - testErrorsAndWarnings( - 'from a_index | where length(mv_sort(cartesianPointField, booleanField)) > 0', - [ - 'Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - 'Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]', - ] - ); + testErrorsAndWarnings('from a_index | eval tanh(booleanField)', [ + 'Argument of [tanh] must be [double], found value [booleanField] type [boolean]', + ]); - testErrorsAndWarnings('from a_index | eval var = mv_sort(booleanField, "asc")', []); - testErrorsAndWarnings('from a_index | eval mv_sort(booleanField, "asc")', []); - testErrorsAndWarnings('from a_index | eval var = mv_sort(dateField, "asc")', []); - testErrorsAndWarnings('from a_index | eval mv_sort(dateField, "asc")', []); - testErrorsAndWarnings('from a_index | eval var = mv_sort(numberField, "asc")', []); - testErrorsAndWarnings('from a_index | eval mv_sort(numberField, "asc")', []); - testErrorsAndWarnings('from a_index | eval var = mv_sort(ipField, "asc")', []); - testErrorsAndWarnings('from a_index | eval mv_sort(ipField, "asc")', []); - testErrorsAndWarnings('from a_index | eval var = mv_sort(versionField, "asc")', []); - testErrorsAndWarnings('from a_index | eval mv_sort(versionField, "asc")', []); + testErrorsAndWarnings('from a_index | eval var = tanh(*)', [ + 'Using wildcards (*) in tanh is not allowed', + ]); - testErrorsAndWarnings('from a_index | eval mv_sort(booleanField, "asc", extraArg)', [ - 'Error: [mv_sort] function expects no more than 2 arguments, got 3.', + testErrorsAndWarnings('from a_index | eval var = tanh(integerField)', []); + testErrorsAndWarnings('from a_index | eval tanh(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = tanh(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = tanh(longField)', []); + testErrorsAndWarnings('from a_index | eval tanh(longField)', []); + testErrorsAndWarnings('from a_index | eval var = tanh(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval tanh(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval tanh(doubleField, extraArg)', [ + 'Error: [tanh] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | sort mv_sort(booleanField, "asc")', []); - testErrorsAndWarnings('from a_index | eval mv_sort(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_sort(nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | sort tanh(doubleField)', []); + testErrorsAndWarnings('from a_index | eval tanh(null)', []); + testErrorsAndWarnings('row nullVar = null | eval tanh(nullVar)', []); }); - describe('mv_sum', () => { - testErrorsAndWarnings('row var = mv_sum(5)', []); - testErrorsAndWarnings('row mv_sum(5)', []); - testErrorsAndWarnings('row var = mv_sum(to_integer("a"))', []); + describe('tau', () => { + testErrorsAndWarnings('row var = tau()', []); + testErrorsAndWarnings('row tau()', []); + testErrorsAndWarnings('from a_index | where tau() > 0', []); + testErrorsAndWarnings('from a_index | eval var = tau()', []); + testErrorsAndWarnings('from a_index | eval tau()', []); - testErrorsAndWarnings('row var = mv_sum("a")', [ - 'Argument of [mv_sum] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | eval tau(extraArg)', [ + 'Error: [tau] function expects exactly 0 arguments, got 1.', ]); - testErrorsAndWarnings('from a_index | where mv_sum(numberField) > 0', []); + testErrorsAndWarnings('from a_index | sort tau()', []); + testErrorsAndWarnings('row nullVar = null | eval tau()', []); + }); + + describe('to_base64', () => { + testErrorsAndWarnings('row var = to_base64("a")', []); + testErrorsAndWarnings('row to_base64("a")', []); + testErrorsAndWarnings('row var = to_base64(to_string(true))', []); - testErrorsAndWarnings('from a_index | where mv_sum(stringField) > 0', [ - 'Argument of [mv_sum] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('row var = to_base64(true)', [ + 'Argument of [to_base64] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = mv_sum(numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_sum(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = mv_sum(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | eval var = to_base64(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_base64(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_base64(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval mv_sum(stringField)', [ - 'Argument of [mv_sum] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval to_base64(booleanField)', [ + 'Argument of [to_base64] must be [keyword], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval mv_sum(numberField, extraArg)', [ - 'Error: [mv_sum] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval var = to_base64(*)', [ + 'Using wildcards (*) in to_base64 is not allowed', ]); - testErrorsAndWarnings('from a_index | eval var = mv_sum(*)', [ - 'Using wildcards (*) in mv_sum is not allowed', + testErrorsAndWarnings('from a_index | eval var = to_base64(textField)', []); + testErrorsAndWarnings('from a_index | eval to_base64(textField)', []); + + testErrorsAndWarnings('from a_index | eval to_base64(keywordField, extraArg)', [ + 'Error: [to_base64] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | sort mv_sum(numberField)', []); - testErrorsAndWarnings('row var = mv_sum(to_integer(true))', []); + testErrorsAndWarnings('from a_index | sort to_base64(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_base64(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_base64(nullVar)', []); + }); - testErrorsAndWarnings('row var = mv_sum(true)', [ - 'Argument of [mv_sum] must be [number], found value [true] type [boolean]', + describe('to_boolean', () => { + testErrorsAndWarnings('row var = to_boolean(true)', []); + testErrorsAndWarnings('row to_boolean(true)', []); + testErrorsAndWarnings('row var = to_bool(true)', []); + testErrorsAndWarnings('row var = to_boolean(to_boolean(true))', []); + testErrorsAndWarnings('row var = to_boolean(5.5)', []); + testErrorsAndWarnings('row to_boolean(5.5)', []); + testErrorsAndWarnings('row var = to_bool(5.5)', []); + testErrorsAndWarnings('row var = to_boolean(to_double(true))', []); + testErrorsAndWarnings('row var = to_boolean(5)', []); + testErrorsAndWarnings('row to_boolean(5)', []); + testErrorsAndWarnings('row var = to_bool(5)', []); + testErrorsAndWarnings('row var = to_boolean(to_integer(true))', []); + testErrorsAndWarnings('row var = to_boolean("a")', []); + testErrorsAndWarnings('row to_boolean("a")', []); + testErrorsAndWarnings('row var = to_bool("a")', []); + testErrorsAndWarnings('row var = to_boolean(to_string(true))', []); + + testErrorsAndWarnings('row var = to_boolean(to_cartesianpoint("POINT (30 10)"))', [ + 'Argument of [to_boolean] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | where mv_sum(booleanField) > 0', [ - 'Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = to_boolean(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_bool(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(to_boolean(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval to_boolean(cartesianPointField)', [ + 'Argument of [to_boolean] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | eval var = mv_sum(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(*)', [ + 'Using wildcards (*) in to_boolean is not allowed', + ]); - testErrorsAndWarnings('from a_index | eval mv_sum(booleanField)', [ - 'Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = to_boolean(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_bool(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_bool(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_bool(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(longField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_bool(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(textField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_bool(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_boolean(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_bool(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval to_boolean(booleanField, extraArg)', [ + 'Error: [to_boolean] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | eval mv_sum(null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_sum(nullVar)', []); + + testErrorsAndWarnings('from a_index | sort to_boolean(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_boolean(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_boolean(nullVar)', []); }); - describe('mv_zip', () => { - testErrorsAndWarnings('row var = mv_zip("a", "a", "a")', []); - testErrorsAndWarnings('row var = mv_zip("a", "a")', []); - testErrorsAndWarnings('row mv_zip("a", "a", "a")', []); - testErrorsAndWarnings('row mv_zip("a", "a")', []); + describe('to_cartesianpoint', () => { + testErrorsAndWarnings('row var = to_cartesianpoint(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row to_cartesianpoint(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings( + 'row var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]'] + ); + testErrorsAndWarnings('row var = to_cartesianpoint("a")', []); + testErrorsAndWarnings('row to_cartesianpoint("a")', []); + testErrorsAndWarnings('row var = to_cartesianpoint(to_string(true))', []); + + testErrorsAndWarnings('row var = to_cartesianpoint(true)', [ + 'Argument of [to_cartesianpoint] must be [cartesian_point], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = to_cartesianpoint(cartesianPointField)', + [] + ); + testErrorsAndWarnings('from a_index | eval to_cartesianpoint(cartesianPointField)', []); + + testErrorsAndWarnings( + 'from a_index | eval var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))', + [] + ); + + testErrorsAndWarnings('from a_index | eval to_cartesianpoint(booleanField)', [ + 'Argument of [to_cartesianpoint] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_cartesianpoint(*)', [ + 'Using wildcards (*) in to_cartesianpoint is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_cartesianpoint(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_cartesianpoint(keywordField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = to_cartesianpoint(to_string(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = to_cartesianpoint(textField)', []); + testErrorsAndWarnings('from a_index | eval to_cartesianpoint(textField)', []); + + testErrorsAndWarnings( + 'from a_index | eval to_cartesianpoint(cartesianPointField, extraArg)', + ['Error: [to_cartesianpoint] function expects exactly one argument, got 2.'] + ); + testErrorsAndWarnings('from a_index | sort to_cartesianpoint(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval to_cartesianpoint(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_cartesianpoint(nullVar)', []); testErrorsAndWarnings( - 'row var = mv_zip(to_string("a"), to_string("a"), to_string("a"))', + 'row var = to_cartesianpoint(to_cartesianpoint("POINT (30 10)"))', [] ); - - testErrorsAndWarnings('row var = mv_zip(5, 5, 5)', [ - 'Argument of [mv_zip] must be [string], found value [5] type [number]', - 'Argument of [mv_zip] must be [string], found value [5] type [number]', - 'Argument of [mv_zip] must be [string], found value [5] type [number]', - ]); + testErrorsAndWarnings('row to_cartesianpoint(to_cartesianpoint("POINT (30 10)"))', []); testErrorsAndWarnings( - 'from a_index | where length(mv_zip(stringField, stringField, stringField)) > 0', + 'row var = to_cartesianpoint(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', [] ); + }); + describe('to_cartesianshape', () => { + testErrorsAndWarnings('row var = to_cartesianshape(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row to_cartesianshape(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); testErrorsAndWarnings( - 'from a_index | where length(mv_zip(numberField, numberField, numberField)) > 0', - [ - 'Argument of [mv_zip] must be [string], found value [numberField] type [number]', - 'Argument of [mv_zip] must be [string], found value [numberField] type [number]', - 'Argument of [mv_zip] must be [string], found value [numberField] type [number]', - ] + 'row var = to_cartesianshape(to_cartesianpoint(cartesianPointField))', + ['Unknown column [cartesianPointField]'] ); - testErrorsAndWarnings( - 'from a_index | eval var = mv_zip(stringField, stringField, stringField)', + 'row var = to_cartesianshape(to_cartesianshape("POINT (30 10)"))', [] ); + testErrorsAndWarnings('row to_cartesianshape(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings( + 'row var = to_cartesianshape(to_cartesianshape(cartesianPointField))', + ['Unknown column [cartesianPointField]'] + ); + testErrorsAndWarnings('row var = to_cartesianshape("a")', []); + testErrorsAndWarnings('row to_cartesianshape("a")', []); + testErrorsAndWarnings('row var = to_cartesianshape(to_string(true))', []); - testErrorsAndWarnings('from a_index | eval mv_zip(stringField, stringField)', []); + testErrorsAndWarnings('row var = to_cartesianshape(true)', [ + 'Argument of [to_cartesianshape] must be [cartesian_point], found value [true] type [boolean]', + ]); testErrorsAndWarnings( - 'from a_index | eval mv_zip(stringField, stringField, stringField)', + 'from a_index | eval var = to_cartesianshape(cartesianPointField)', [] ); + testErrorsAndWarnings('from a_index | eval to_cartesianshape(cartesianPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_zip(to_string(stringField), to_string(stringField), to_string(stringField))', + 'from a_index | eval var = to_cartesianshape(to_cartesianpoint(cartesianPointField))', [] ); - testErrorsAndWarnings('from a_index | eval mv_zip(numberField, numberField, numberField)', [ - 'Argument of [mv_zip] must be [string], found value [numberField] type [number]', - 'Argument of [mv_zip] must be [string], found value [numberField] type [number]', - 'Argument of [mv_zip] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | eval to_cartesianshape(booleanField)', [ + 'Argument of [to_cartesianshape] must be [cartesian_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_cartesianshape(*)', [ + 'Using wildcards (*) in to_cartesianshape is not allowed', ]); testErrorsAndWarnings( - 'from a_index | eval mv_zip(stringField, stringField, stringField, extraArg)', - ['Error: [mv_zip] function expects no more than 3 arguments, got 4.'] + 'from a_index | eval var = to_cartesianshape(cartesianShapeField)', + [] ); + testErrorsAndWarnings('from a_index | eval to_cartesianshape(cartesianShapeField)', []); testErrorsAndWarnings( - 'from a_index | sort mv_zip(stringField, stringField, stringField)', + 'from a_index | eval var = to_cartesianshape(to_cartesianshape(cartesianPointField))', [] ); + + testErrorsAndWarnings('from a_index | eval var = to_cartesianshape(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_cartesianshape(keywordField)', []); testErrorsAndWarnings( - 'row var = mv_zip(to_string(true), to_string(true), to_string(true))', + 'from a_index | eval var = to_cartesianshape(to_string(booleanField))', [] ); - - testErrorsAndWarnings('row var = mv_zip(true, true, true)', [ - 'Argument of [mv_zip] must be [string], found value [true] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [true] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | eval var = to_cartesianshape(textField)', []); + testErrorsAndWarnings('from a_index | eval to_cartesianshape(textField)', []); testErrorsAndWarnings( - 'from a_index | where length(mv_zip(booleanField, booleanField, booleanField)) > 0', - [ - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - ] + 'from a_index | eval to_cartesianshape(cartesianPointField, extraArg)', + ['Error: [to_cartesianshape] function expects exactly one argument, got 2.'] ); + testErrorsAndWarnings('from a_index | sort to_cartesianshape(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval to_cartesianshape(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_cartesianshape(nullVar)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField), to_string(booleanField))', + 'row var = to_cartesianshape(to_cartesianpoint("POINT (30 10)"))', [] ); + testErrorsAndWarnings('row to_cartesianshape(to_cartesianpoint("POINT (30 10)"))', []); testErrorsAndWarnings( - 'from a_index | eval mv_zip(booleanField, booleanField, booleanField)', - [ - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - ] + 'row var = to_cartesianshape(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + [] ); - testErrorsAndWarnings('from a_index | eval mv_zip(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_zip(nullVar, nullVar, nullVar)', []); - testErrorsAndWarnings('row var = mv_zip(to_string(true), to_string(true))', []); + testErrorsAndWarnings( - 'from a_index | where length(mv_zip(stringField, stringField)) > 0', + 'row var = to_cartesianshape(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', [] ); + }); + + describe('to_datetime', () => { + testErrorsAndWarnings('row var = to_datetime(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row to_datetime(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row var = to_dt(to_datetime("2021-01-01T00:00:00Z"))', []); testErrorsAndWarnings( - 'from a_index | where length(mv_zip(booleanField, booleanField)) > 0', - [ - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - ] + 'row var = to_datetime(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] ); - testErrorsAndWarnings('from a_index | eval var = mv_zip(stringField, stringField)', []); + testErrorsAndWarnings('row var = to_datetime(5.5)', []); + testErrorsAndWarnings('row to_datetime(5.5)', []); + testErrorsAndWarnings('row var = to_dt(5.5)', []); + testErrorsAndWarnings('row var = to_datetime(to_double(true))', []); + testErrorsAndWarnings('row var = to_datetime(5)', []); + testErrorsAndWarnings('row to_datetime(5)', []); + testErrorsAndWarnings('row var = to_dt(5)', []); + testErrorsAndWarnings('row var = to_datetime(to_integer(true))', []); + testErrorsAndWarnings('row var = to_datetime("a")', []); + testErrorsAndWarnings('row to_datetime("a")', []); + testErrorsAndWarnings('row var = to_dt("a")', []); + testErrorsAndWarnings('row var = to_datetime(to_string(true))', []); + + testErrorsAndWarnings('row var = to_datetime(true)', [ + 'Argument of [to_datetime] must be [date], found value [true] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_datetime(dateField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dt(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_datetime(to_datetime(dateField))', []); + + testErrorsAndWarnings('from a_index | eval to_datetime(booleanField)', [ + 'Argument of [to_datetime] must be [date], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_datetime(*)', [ + 'Using wildcards (*) in to_datetime is not allowed', + ]); + testErrorsAndWarnings('from a_index | eval var = to_datetime(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dt(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_datetime(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_datetime(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dt(integerField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField))', + 'from a_index | eval var = to_datetime(to_integer(booleanField))', [] ); + testErrorsAndWarnings('from a_index | eval var = to_datetime(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dt(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_datetime(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_datetime(longField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dt(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_datetime(textField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dt(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_datetime(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dt(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval mv_zip(booleanField, booleanField)', [ - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', - 'Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval to_datetime(dateField, extraArg)', [ + 'Error: [to_datetime] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | sort mv_zip(stringField, stringField)', []); + testErrorsAndWarnings('from a_index | sort to_datetime(dateField)', []); + testErrorsAndWarnings('from a_index | eval to_datetime(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_datetime(nullVar)', []); + testErrorsAndWarnings('from a_index | eval to_datetime("2022")', []); + testErrorsAndWarnings('from a_index | eval to_datetime(concat("20", "22"))', []); }); - describe('now', () => { - testErrorsAndWarnings('row var = now()', []); - testErrorsAndWarnings('row now()', []); - testErrorsAndWarnings('from a_index | eval var = now()', []); - testErrorsAndWarnings('from a_index | eval now()', []); + describe('to_degrees', () => { + testErrorsAndWarnings('row var = to_degrees(5.5)', []); + testErrorsAndWarnings('row to_degrees(5.5)', []); + testErrorsAndWarnings('row var = to_degrees(to_double(true))', []); + testErrorsAndWarnings('row var = to_degrees(5)', []); + testErrorsAndWarnings('row to_degrees(5)', []); + testErrorsAndWarnings('row var = to_degrees(to_integer(true))', []); - testErrorsAndWarnings('from a_index | eval now(extraArg)', [ - 'Error: [now] function expects exactly 0 arguments, got 1.', + testErrorsAndWarnings('row var = to_degrees(true)', [ + 'Argument of [to_degrees] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort now()', []); - testErrorsAndWarnings('row nullVar = null | eval now()', []); - }); - - describe('pi', () => { - testErrorsAndWarnings('row var = pi()', []); - testErrorsAndWarnings('row pi()', []); - testErrorsAndWarnings('from a_index | where pi() > 0', []); - testErrorsAndWarnings('from a_index | eval var = pi()', []); - testErrorsAndWarnings('from a_index | eval pi()', []); + testErrorsAndWarnings('from a_index | where to_degrees(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | eval pi(extraArg)', [ - 'Error: [pi] function expects exactly 0 arguments, got 1.', + testErrorsAndWarnings('from a_index | where to_degrees(booleanField) > 0', [ + 'Argument of [to_degrees] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort pi()', []); - testErrorsAndWarnings('row nullVar = null | eval pi()', []); - }); + testErrorsAndWarnings('from a_index | where to_degrees(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_degrees(longField) > 0', []); + testErrorsAndWarnings('from a_index | where to_degrees(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_degrees(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_degrees(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_degrees(to_double(booleanField))', []); - describe('pow', () => { - testErrorsAndWarnings('row var = pow(5, 5)', []); - testErrorsAndWarnings('row pow(5, 5)', []); - testErrorsAndWarnings('row var = pow(to_integer("a"), to_integer("a"))', []); + testErrorsAndWarnings('from a_index | eval to_degrees(booleanField)', [ + 'Argument of [to_degrees] must be [double], found value [booleanField] type [boolean]', + ]); - testErrorsAndWarnings('row var = pow("a", "a")', [ - 'Argument of [pow] must be [number], found value ["a"] type [string]', - 'Argument of [pow] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | eval var = to_degrees(*)', [ + 'Using wildcards (*) in to_degrees is not allowed', ]); - testErrorsAndWarnings('from a_index | where pow(numberField, numberField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_degrees(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_degrees(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_degrees(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_degrees(longField)', []); + testErrorsAndWarnings('from a_index | eval to_degrees(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_degrees(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_degrees(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | where pow(stringField, stringField) > 0', [ - 'Argument of [pow] must be [number], found value [stringField] type [string]', - 'Argument of [pow] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval to_degrees(doubleField, extraArg)', [ + 'Error: [to_degrees] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | eval var = pow(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval pow(numberField, numberField)', []); + testErrorsAndWarnings('from a_index | sort to_degrees(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_degrees(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_degrees(nullVar)', []); + }); + + describe('to_double', () => { + testErrorsAndWarnings('row var = to_double(true)', []); + testErrorsAndWarnings('row to_double(true)', []); + testErrorsAndWarnings('row var = to_dbl(true)', []); + testErrorsAndWarnings('row var = to_double(to_boolean(true))', []); + testErrorsAndWarnings('row var = to_double(5.5)', []); + testErrorsAndWarnings('row to_double(5.5)', []); + testErrorsAndWarnings('row var = to_dbl(5.5)', []); + testErrorsAndWarnings('row var = to_double(5)', []); + testErrorsAndWarnings('row to_double(5)', []); + testErrorsAndWarnings('row var = to_dbl(5)', []); + testErrorsAndWarnings('row var = to_double(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row to_double(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row var = to_dbl(to_datetime("2021-01-01T00:00:00Z"))', []); testErrorsAndWarnings( - 'from a_index | eval var = pow(to_integer(stringField), to_integer(stringField))', + 'row var = to_double(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', [] ); - testErrorsAndWarnings('from a_index | eval pow(stringField, stringField)', [ - 'Argument of [pow] must be [number], found value [stringField] type [string]', - 'Argument of [pow] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('row var = to_double(to_double(true))', []); + testErrorsAndWarnings('row var = to_double(to_integer(true))', []); + testErrorsAndWarnings('row var = to_double("a")', []); + testErrorsAndWarnings('row to_double("a")', []); + testErrorsAndWarnings('row var = to_dbl("a")', []); + testErrorsAndWarnings('row var = to_double(to_string(true))', []); + + testErrorsAndWarnings('row var = to_double(to_cartesianpoint("POINT (30 10)"))', [ + 'Argument of [to_double] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | eval pow(numberField, numberField, extraArg)', [ - 'Error: [pow] function expects exactly 2 arguments, got 3.', + testErrorsAndWarnings('from a_index | where to_double(booleanField) > 0', []); + + testErrorsAndWarnings('from a_index | where to_double(cartesianPointField) > 0', [ + 'Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | sort pow(numberField, numberField)', []); - testErrorsAndWarnings('row var = pow(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('from a_index | where to_double(counterDoubleField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(counterIntegerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(counterLongField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(dateField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(keywordField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(longField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(textField) > 0', []); + testErrorsAndWarnings('from a_index | where to_double(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_double(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_double(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(to_boolean(booleanField))', []); - testErrorsAndWarnings('row var = pow(true, true)', [ - 'Argument of [pow] must be [number], found value [true] type [boolean]', - 'Argument of [pow] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval to_double(cartesianPointField)', [ + 'Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | where pow(booleanField, booleanField) > 0', [ - 'Argument of [pow] must be [number], found value [booleanField] type [boolean]', - 'Argument of [pow] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = to_double(*)', [ + 'Using wildcards (*) in to_double is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_double(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | eval to_double(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval to_double(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(counterLongField)', []); + testErrorsAndWarnings('from a_index | eval to_double(counterLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(counterLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(dateField)', []); + testErrorsAndWarnings('from a_index | eval to_double(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = to_double(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_double(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_double(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_double(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_double(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_double(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_double(longField)', []); + testErrorsAndWarnings('from a_index | eval to_double(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(textField)', []); + testErrorsAndWarnings('from a_index | eval to_double(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_double(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_double(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_dbl(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval to_double(booleanField, extraArg)', [ + 'Error: [to_double] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort to_double(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_double(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_double(nullVar)', []); + testErrorsAndWarnings('from a_index | eval to_double("2022")', []); + testErrorsAndWarnings('from a_index | eval to_double(concat("20", "22"))', []); + }); + + describe('to_geopoint', () => { + testErrorsAndWarnings('row var = to_geopoint(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row to_geopoint(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = to_geopoint(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = to_geopoint("a")', []); + testErrorsAndWarnings('row to_geopoint("a")', []); + testErrorsAndWarnings('row var = to_geopoint(to_string(true))', []); + + testErrorsAndWarnings('row var = to_geopoint(true)', [ + 'Argument of [to_geopoint] must be [geo_point], found value [true] type [boolean]', ]); + testErrorsAndWarnings('from a_index | eval var = to_geopoint(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval to_geopoint(geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = pow(to_integer(booleanField), to_integer(booleanField))', + 'from a_index | eval var = to_geopoint(to_geopoint(geoPointField))', [] ); - testErrorsAndWarnings('from a_index | eval pow(booleanField, booleanField)', [ - 'Argument of [pow] must be [number], found value [booleanField] type [boolean]', - 'Argument of [pow] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval to_geopoint(booleanField)', [ + 'Argument of [to_geopoint] must be [geo_point], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval pow(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval pow(nullVar, nullVar)', []); + + testErrorsAndWarnings('from a_index | eval var = to_geopoint(*)', [ + 'Using wildcards (*) in to_geopoint is not allowed', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_geopoint(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_geopoint(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_geopoint(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_geopoint(textField)', []); + testErrorsAndWarnings('from a_index | eval to_geopoint(textField)', []); + + testErrorsAndWarnings('from a_index | eval to_geopoint(geoPointField, extraArg)', [ + 'Error: [to_geopoint] function expects exactly one argument, got 2.', + ]); + + testErrorsAndWarnings('from a_index | sort to_geopoint(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval to_geopoint(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_geopoint(nullVar)', []); + testErrorsAndWarnings('row var = to_geopoint(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row to_geopoint(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings( + 'row var = to_geopoint(to_geopoint(to_geopoint("POINT (30 10)")))', + [] + ); }); - describe('replace', () => { - testErrorsAndWarnings('row var = replace("a", "a", "a")', []); - testErrorsAndWarnings('row replace("a", "a", "a")', []); + describe('to_geoshape', () => { + testErrorsAndWarnings('row var = to_geoshape(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row to_geoshape(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = to_geoshape(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = to_geoshape(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row to_geoshape(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_geoshape(to_geoshape(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = to_geoshape("a")', []); + testErrorsAndWarnings('row to_geoshape("a")', []); + testErrorsAndWarnings('row var = to_geoshape(to_string(true))', []); + + testErrorsAndWarnings('row var = to_geoshape(true)', [ + 'Argument of [to_geoshape] must be [geo_point], found value [true] type [boolean]', + ]); + testErrorsAndWarnings('from a_index | eval var = to_geoshape(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval to_geoshape(geoPointField)', []); testErrorsAndWarnings( - 'row var = replace(to_string("a"), to_string("a"), to_string("a"))', + 'from a_index | eval var = to_geoshape(to_geopoint(geoPointField))', [] ); - testErrorsAndWarnings('row var = replace(5, 5, 5)', [ - 'Argument of [replace] must be [string], found value [5] type [number]', - 'Argument of [replace] must be [string], found value [5] type [number]', - 'Argument of [replace] must be [string], found value [5] type [number]', + testErrorsAndWarnings('from a_index | eval to_geoshape(booleanField)', [ + 'Argument of [to_geoshape] must be [geo_point], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_geoshape(*)', [ + 'Using wildcards (*) in to_geoshape is not allowed', ]); + testErrorsAndWarnings('from a_index | eval var = to_geoshape(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval to_geoshape(geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | where length(replace(stringField, stringField, stringField)) > 0', + 'from a_index | eval var = to_geoshape(to_geoshape(geoPointField))', [] ); + testErrorsAndWarnings('from a_index | eval var = to_geoshape(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_geoshape(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_geoshape(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_geoshape(textField)', []); + testErrorsAndWarnings('from a_index | eval to_geoshape(textField)', []); - testErrorsAndWarnings( - 'from a_index | where length(replace(numberField, numberField, numberField)) > 0', - [ - 'Argument of [replace] must be [string], found value [numberField] type [number]', - 'Argument of [replace] must be [string], found value [numberField] type [number]', - 'Argument of [replace] must be [string], found value [numberField] type [number]', - ] - ); + testErrorsAndWarnings('from a_index | eval to_geoshape(geoPointField, extraArg)', [ + 'Error: [to_geoshape] function expects exactly one argument, got 2.', + ]); + testErrorsAndWarnings('from a_index | sort to_geoshape(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval to_geoshape(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_geoshape(nullVar)', []); + testErrorsAndWarnings('row var = to_geoshape(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row to_geoshape(to_geopoint("POINT (30 10)"))', []); testErrorsAndWarnings( - 'from a_index | eval var = replace(stringField, stringField, stringField)', + 'row var = to_geoshape(to_geopoint(to_geopoint("POINT (30 10)")))', [] ); - testErrorsAndWarnings( - 'from a_index | eval replace(stringField, stringField, stringField)', + 'row var = to_geoshape(to_geoshape(to_geopoint("POINT (30 10)")))', [] ); + }); + + describe('to_integer', () => { + testErrorsAndWarnings('row var = to_integer(true)', []); + testErrorsAndWarnings('row to_integer(true)', []); + testErrorsAndWarnings('row var = to_int(true)', []); + testErrorsAndWarnings('row var = to_integer(to_boolean(true))', []); + testErrorsAndWarnings('row var = to_integer(5)', []); + testErrorsAndWarnings('row to_integer(5)', []); + testErrorsAndWarnings('row var = to_int(5)', []); + testErrorsAndWarnings('row var = to_integer(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row to_integer(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row var = to_int(to_datetime("2021-01-01T00:00:00Z"))', []); testErrorsAndWarnings( - 'from a_index | eval var = replace(to_string(stringField), to_string(stringField), to_string(stringField))', + 'row var = to_integer(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', [] ); - testErrorsAndWarnings( - 'from a_index | eval replace(numberField, numberField, numberField)', - [ - 'Argument of [replace] must be [string], found value [numberField] type [number]', - 'Argument of [replace] must be [string], found value [numberField] type [number]', - 'Argument of [replace] must be [string], found value [numberField] type [number]', - ] - ); + testErrorsAndWarnings('row var = to_integer(5.5)', []); + testErrorsAndWarnings('row to_integer(5.5)', []); + testErrorsAndWarnings('row var = to_int(5.5)', []); + testErrorsAndWarnings('row var = to_integer(to_double(true))', []); + testErrorsAndWarnings('row var = to_integer(to_integer(true))', []); + testErrorsAndWarnings('row var = to_integer("a")', []); + testErrorsAndWarnings('row to_integer("a")', []); + testErrorsAndWarnings('row var = to_int("a")', []); + testErrorsAndWarnings('row var = to_integer(to_string(true))', []); - testErrorsAndWarnings( - 'from a_index | eval replace(stringField, stringField, stringField, extraArg)', - ['Error: [replace] function expects exactly 3 arguments, got 4.'] - ); + testErrorsAndWarnings('row var = to_integer(to_cartesianpoint("POINT (30 10)"))', [ + 'Argument of [to_integer] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + ]); - testErrorsAndWarnings( - 'from a_index | sort replace(stringField, stringField, stringField)', - [] - ); - testErrorsAndWarnings( - 'row var = replace(to_string(true), to_string(true), to_string(true))', - [] - ); + testErrorsAndWarnings('from a_index | where to_integer(booleanField) > 0', []); - testErrorsAndWarnings('row var = replace(true, true, true)', [ - 'Argument of [replace] must be [string], found value [true] type [boolean]', - 'Argument of [replace] must be [string], found value [true] type [boolean]', - 'Argument of [replace] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | where to_integer(cartesianPointField) > 0', [ + 'Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings( - 'from a_index | where length(replace(booleanField, booleanField, booleanField)) > 0', - [ - 'Argument of [replace] must be [string], found value [booleanField] type [boolean]', - 'Argument of [replace] must be [string], found value [booleanField] type [boolean]', - 'Argument of [replace] must be [string], found value [booleanField] type [boolean]', - ] - ); + testErrorsAndWarnings('from a_index | where to_integer(counterIntegerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_integer(dateField) > 0', []); + testErrorsAndWarnings('from a_index | where to_integer(doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where to_integer(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_integer(keywordField) > 0', []); + testErrorsAndWarnings('from a_index | where to_integer(longField) > 0', []); + testErrorsAndWarnings('from a_index | where to_integer(textField) > 0', []); + testErrorsAndWarnings('from a_index | where to_integer(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(to_boolean(booleanField))', []); - testErrorsAndWarnings( - 'from a_index | eval var = replace(to_string(booleanField), to_string(booleanField), to_string(booleanField))', - [] - ); + testErrorsAndWarnings('from a_index | eval to_integer(cartesianPointField)', [ + 'Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + ]); - testErrorsAndWarnings( - 'from a_index | eval replace(booleanField, booleanField, booleanField)', - [ - 'Argument of [replace] must be [string], found value [booleanField] type [boolean]', - 'Argument of [replace] must be [string], found value [booleanField] type [boolean]', - 'Argument of [replace] must be [string], found value [booleanField] type [boolean]', - ] - ); - testErrorsAndWarnings('from a_index | eval replace(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval replace(nullVar, nullVar, nullVar)', []); - }); + testErrorsAndWarnings('from a_index | eval var = to_integer(*)', [ + 'Using wildcards (*) in to_integer is not allowed', + ]); - describe('right', () => { - testErrorsAndWarnings('row var = right("a", 5)', []); - testErrorsAndWarnings('row right("a", 5)', []); - testErrorsAndWarnings('row var = right(to_string("a"), to_integer("a"))', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(dateField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(longField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(textField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_integer(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_int(unsignedLongField)', []); - testErrorsAndWarnings('row var = right(5, "a")', [ - 'Argument of [right] must be [string], found value [5] type [number]', - 'Argument of [right] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | eval to_integer(booleanField, extraArg)', [ + 'Error: [to_integer] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings( - 'from a_index | where length(right(stringField, numberField)) > 0', - [] - ); + testErrorsAndWarnings('from a_index | sort to_integer(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_integer(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_integer(nullVar)', []); + testErrorsAndWarnings('from a_index | eval to_integer("2022")', []); + testErrorsAndWarnings('from a_index | eval to_integer(concat("20", "22"))', []); + }); - testErrorsAndWarnings('from a_index | where length(right(numberField, stringField)) > 0', [ - 'Argument of [right] must be [string], found value [numberField] type [number]', - 'Argument of [right] must be [number], found value [stringField] type [string]', - ]); + describe('to_ip', () => { + testErrorsAndWarnings('row var = to_ip(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row to_ip(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = to_ip(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = to_ip("a")', []); + testErrorsAndWarnings('row to_ip("a")', []); + testErrorsAndWarnings('row var = to_ip(to_string(true))', []); - testErrorsAndWarnings('from a_index | eval var = right(stringField, numberField)', []); - testErrorsAndWarnings('from a_index | eval right(stringField, numberField)', []); + testErrorsAndWarnings('row var = to_ip(true)', [ + 'Argument of [to_ip] must be [ip], found value [true] type [boolean]', + ]); - testErrorsAndWarnings( - 'from a_index | eval var = right(to_string(stringField), to_integer(stringField))', - [] - ); + testErrorsAndWarnings('from a_index | eval var = to_ip(ipField)', []); + testErrorsAndWarnings('from a_index | eval to_ip(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ip(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval right(numberField, stringField)', [ - 'Argument of [right] must be [string], found value [numberField] type [number]', - 'Argument of [right] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval to_ip(booleanField)', [ + 'Argument of [to_ip] must be [ip], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval right(stringField, numberField, extraArg)', [ - 'Error: [right] function expects exactly 2 arguments, got 3.', + testErrorsAndWarnings('from a_index | eval var = to_ip(*)', [ + 'Using wildcards (*) in to_ip is not allowed', ]); - testErrorsAndWarnings('from a_index | sort right(stringField, numberField)', []); - testErrorsAndWarnings('row var = right(to_string(true), to_integer(true))', []); + testErrorsAndWarnings('from a_index | eval var = to_ip(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_ip(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ip(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_ip(textField)', []); + testErrorsAndWarnings('from a_index | eval to_ip(textField)', []); - testErrorsAndWarnings('row var = right(true, true)', [ - 'Argument of [right] must be [string], found value [true] type [boolean]', - 'Argument of [right] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval to_ip(ipField, extraArg)', [ + 'Error: [to_ip] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings( - 'from a_index | where length(right(booleanField, booleanField)) > 0', - [ - 'Argument of [right] must be [string], found value [booleanField] type [boolean]', - 'Argument of [right] must be [number], found value [booleanField] type [boolean]', - ] - ); + testErrorsAndWarnings('from a_index | sort to_ip(ipField)', []); + testErrorsAndWarnings('from a_index | eval to_ip(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_ip(nullVar)', []); + }); + + describe('to_long', () => { + testErrorsAndWarnings('row var = to_long(true)', []); + testErrorsAndWarnings('row to_long(true)', []); + testErrorsAndWarnings('row var = to_long(to_boolean(true))', []); + testErrorsAndWarnings('row var = to_long(5)', []); + testErrorsAndWarnings('row to_long(5)', []); + testErrorsAndWarnings('row var = to_long(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row to_long(to_datetime("2021-01-01T00:00:00Z"))', []); testErrorsAndWarnings( - 'from a_index | eval var = right(to_string(booleanField), to_integer(booleanField))', + 'row var = to_long(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', [] ); - testErrorsAndWarnings('from a_index | eval right(booleanField, booleanField)', [ - 'Argument of [right] must be [string], found value [booleanField] type [boolean]', - 'Argument of [right] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('row var = to_long(5.5)', []); + testErrorsAndWarnings('row to_long(5.5)', []); + testErrorsAndWarnings('row var = to_long(to_double(true))', []); + testErrorsAndWarnings('row var = to_long(to_integer(true))', []); + testErrorsAndWarnings('row var = to_long("a")', []); + testErrorsAndWarnings('row to_long("a")', []); + testErrorsAndWarnings('row var = to_long(to_string(true))', []); + + testErrorsAndWarnings('row var = to_long(to_cartesianpoint("POINT (30 10)"))', [ + 'Argument of [to_long] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | eval right(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval right(nullVar, nullVar)', []); - }); - describe('round', () => { - testErrorsAndWarnings('row var = round(5, 5)', []); - testErrorsAndWarnings('row round(5, 5)', []); - testErrorsAndWarnings('row var = round(to_integer("a"), to_integer("a"))', []); + testErrorsAndWarnings('from a_index | where to_long(booleanField) > 0', []); - testErrorsAndWarnings('row var = round("a", "a")', [ - 'Argument of [round] must be [number], found value ["a"] type [string]', - 'Argument of [round] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | where to_long(cartesianPointField) > 0', [ + 'Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | where round(numberField, numberField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(counterIntegerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(counterLongField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(dateField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(doubleField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(keywordField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(longField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(textField) > 0', []); + testErrorsAndWarnings('from a_index | where to_long(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_long(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_long(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(to_boolean(booleanField))', []); - testErrorsAndWarnings('from a_index | where round(stringField, stringField) > 0', [ - 'Argument of [round] must be [number], found value [stringField] type [string]', - 'Argument of [round] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval to_long(cartesianPointField)', [ + 'Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | eval var = round(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval round(numberField, numberField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(*)', [ + 'Using wildcards (*) in to_long is not allowed', + ]); - testErrorsAndWarnings( - 'from a_index | eval var = round(to_integer(stringField), to_integer(stringField))', - [] - ); + testErrorsAndWarnings('from a_index | eval var = to_long(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval to_long(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(counterLongField)', []); + testErrorsAndWarnings('from a_index | eval to_long(counterLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(dateField)', []); + testErrorsAndWarnings('from a_index | eval to_long(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = to_long(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_long(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_long(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_long(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_long(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_long(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_long(longField)', []); + testErrorsAndWarnings('from a_index | eval to_long(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(textField)', []); + testErrorsAndWarnings('from a_index | eval to_long(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_long(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_long(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval round(stringField, stringField)', [ - 'Argument of [round] must be [number], found value [stringField] type [string]', - 'Argument of [round] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval to_long(booleanField, extraArg)', [ + 'Error: [to_long] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | eval round(numberField, numberField, extraArg)', [ - 'Error: [round] function expects no more than 2 arguments, got 3.', + testErrorsAndWarnings('from a_index | sort to_long(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_long(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_long(nullVar)', []); + testErrorsAndWarnings('from a_index | eval to_long("2022")', []); + testErrorsAndWarnings('from a_index | eval to_long(concat("20", "22"))', []); + }); + + describe('to_lower', () => { + testErrorsAndWarnings('row var = to_lower("a")', []); + testErrorsAndWarnings('row to_lower("a")', []); + testErrorsAndWarnings('row var = to_lower(to_string(true))', []); + + testErrorsAndWarnings('row var = to_lower(true)', [ + 'Argument of [to_lower] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort round(numberField, numberField)', []); - testErrorsAndWarnings('row var = round(5)', []); - testErrorsAndWarnings('row round(5)', []); - testErrorsAndWarnings('row var = round(to_integer(true))', []); - testErrorsAndWarnings('row var = round(to_integer(true), to_integer(true))', []); + testErrorsAndWarnings('from a_index | eval var = to_lower(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_lower(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_lower(to_string(booleanField))', []); - testErrorsAndWarnings('row var = round(true, true)', [ - 'Argument of [round] must be [number], found value [true] type [boolean]', - 'Argument of [round] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval to_lower(booleanField)', [ + 'Argument of [to_lower] must be [keyword], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | eval var = to_lower(*)', [ + 'Using wildcards (*) in to_lower is not allowed', ]); - testErrorsAndWarnings('from a_index | where round(numberField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_lower(textField)', []); + testErrorsAndWarnings('from a_index | eval to_lower(textField)', []); - testErrorsAndWarnings('from a_index | where round(booleanField) > 0', [ - 'Argument of [round] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval to_lower(keywordField, extraArg)', [ + 'Error: [to_lower] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | where round(booleanField, booleanField) > 0', [ - 'Argument of [round] must be [number], found value [booleanField] type [boolean]', - 'Argument of [round] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | sort to_lower(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_lower(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_lower(nullVar)', []); + }); + + describe('to_radians', () => { + testErrorsAndWarnings('row var = to_radians(5.5)', []); + testErrorsAndWarnings('row to_radians(5.5)', []); + testErrorsAndWarnings('row var = to_radians(to_double(true))', []); + testErrorsAndWarnings('row var = to_radians(5)', []); + testErrorsAndWarnings('row to_radians(5)', []); + testErrorsAndWarnings('row var = to_radians(to_integer(true))', []); + + testErrorsAndWarnings('row var = to_radians(true)', [ + 'Argument of [to_radians] must be [double], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = round(numberField)', []); - testErrorsAndWarnings('from a_index | eval round(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = round(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | where to_radians(doubleField) > 0', []); - testErrorsAndWarnings('from a_index | eval round(booleanField)', [ - 'Argument of [round] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where to_radians(booleanField) > 0', [ + 'Argument of [to_radians] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = round(*)', [ - 'Using wildcards (*) in round is not allowed', + testErrorsAndWarnings('from a_index | where to_radians(integerField) > 0', []); + testErrorsAndWarnings('from a_index | where to_radians(longField) > 0', []); + testErrorsAndWarnings('from a_index | where to_radians(unsignedLongField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_radians(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_radians(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_radians(to_double(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval to_radians(booleanField)', [ + 'Argument of [to_radians] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings( - 'from a_index | eval var = round(to_integer(booleanField), to_integer(booleanField))', - [] - ); + testErrorsAndWarnings('from a_index | eval var = to_radians(*)', [ + 'Using wildcards (*) in to_radians is not allowed', + ]); - testErrorsAndWarnings('from a_index | eval round(booleanField, booleanField)', [ - 'Argument of [round] must be [number], found value [booleanField] type [boolean]', - 'Argument of [round] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = to_radians(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_radians(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_radians(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_radians(longField)', []); + testErrorsAndWarnings('from a_index | eval to_radians(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_radians(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_radians(unsignedLongField)', []); + + testErrorsAndWarnings('from a_index | eval to_radians(doubleField, extraArg)', [ + 'Error: [to_radians] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | sort round(numberField)', []); - testErrorsAndWarnings('from a_index | eval round(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval round(nullVar, nullVar)', []); + testErrorsAndWarnings('from a_index | sort to_radians(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_radians(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_radians(nullVar)', []); }); - describe('rtrim', () => { - testErrorsAndWarnings('row var = rtrim("a")', []); - testErrorsAndWarnings('row rtrim("a")', []); - testErrorsAndWarnings('row var = rtrim(to_string("a"))', []); + describe('to_unsigned_long', () => { + testErrorsAndWarnings('row var = to_unsigned_long(true)', []); + testErrorsAndWarnings('row to_unsigned_long(true)', []); + testErrorsAndWarnings('row var = to_ul(true)', []); + testErrorsAndWarnings('row var = to_ulong(true)', []); + testErrorsAndWarnings('row var = to_unsigned_long(to_boolean(true))', []); + testErrorsAndWarnings( + 'row var = to_unsigned_long(to_datetime("2021-01-01T00:00:00Z"))', + [] + ); + testErrorsAndWarnings('row to_unsigned_long(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row var = to_ul(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row var = to_ulong(to_datetime("2021-01-01T00:00:00Z"))', []); - testErrorsAndWarnings('row var = rtrim(5)', [ - 'Argument of [rtrim] must be [string], found value [5] type [number]', - ]); + testErrorsAndWarnings( + 'row var = to_unsigned_long(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', + [] + ); - testErrorsAndWarnings('from a_index | where length(rtrim(stringField)) > 0', []); + testErrorsAndWarnings('row var = to_unsigned_long(5.5)', []); + testErrorsAndWarnings('row to_unsigned_long(5.5)', []); + testErrorsAndWarnings('row var = to_ul(5.5)', []); + testErrorsAndWarnings('row var = to_ulong(5.5)', []); + testErrorsAndWarnings('row var = to_unsigned_long(to_double(true))', []); + testErrorsAndWarnings('row var = to_unsigned_long(5)', []); + testErrorsAndWarnings('row to_unsigned_long(5)', []); + testErrorsAndWarnings('row var = to_ul(5)', []); + testErrorsAndWarnings('row var = to_ulong(5)', []); + testErrorsAndWarnings('row var = to_unsigned_long(to_integer(true))', []); + testErrorsAndWarnings('row var = to_unsigned_long("a")', []); + testErrorsAndWarnings('row to_unsigned_long("a")', []); + testErrorsAndWarnings('row var = to_ul("a")', []); + testErrorsAndWarnings('row var = to_ulong("a")', []); + testErrorsAndWarnings('row var = to_unsigned_long(to_string(true))', []); - testErrorsAndWarnings('from a_index | where length(rtrim(numberField)) > 0', [ - 'Argument of [rtrim] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('row var = to_unsigned_long(to_cartesianpoint("POINT (30 10)"))', [ + 'Argument of [to_unsigned_long] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | eval var = rtrim(stringField)', []); - testErrorsAndWarnings('from a_index | eval rtrim(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = rtrim(to_string(stringField))', []); - - testErrorsAndWarnings('from a_index | eval rtrim(numberField)', [ - 'Argument of [rtrim] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | where to_unsigned_long(booleanField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(booleanField)] type [unsigned_long]', ]); - testErrorsAndWarnings('from a_index | eval rtrim(stringField, extraArg)', [ - 'Error: [rtrim] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | where to_unsigned_long(cartesianPointField) > 0', [ + 'Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + 'Argument of [>] must be [double], found value [to_unsigned_long(cartesianPointField)] type [unsigned_long]', ]); - testErrorsAndWarnings('from a_index | eval var = rtrim(*)', [ - 'Using wildcards (*) in rtrim is not allowed', + testErrorsAndWarnings('from a_index | where to_unsigned_long(dateField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(dateField)] type [unsigned_long]', ]); - - testErrorsAndWarnings('from a_index | sort rtrim(stringField)', []); - testErrorsAndWarnings('row var = rtrim(to_string(true))', []); - - testErrorsAndWarnings('row var = rtrim(true)', [ - 'Argument of [rtrim] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | where to_unsigned_long(doubleField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(doubleField)] type [unsigned_long]', ]); - - testErrorsAndWarnings('from a_index | where length(rtrim(booleanField)) > 0', [ - 'Argument of [rtrim] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where to_unsigned_long(integerField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(integerField)] type [unsigned_long]', ]); - - testErrorsAndWarnings('from a_index | eval var = rtrim(to_string(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval rtrim(booleanField)', [ - 'Argument of [rtrim] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where to_unsigned_long(keywordField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(keywordField)] type [unsigned_long]', ]); - testErrorsAndWarnings('from a_index | eval rtrim(null)', []); - testErrorsAndWarnings('row nullVar = null | eval rtrim(nullVar)', []); - }); - - describe('signum', () => { - testErrorsAndWarnings('row var = signum(5)', []); - testErrorsAndWarnings('row signum(5)', []); - testErrorsAndWarnings('row var = signum(to_integer("a"))', []); - - testErrorsAndWarnings('row var = signum("a")', [ - 'Argument of [signum] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | where to_unsigned_long(longField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(longField)] type [unsigned_long]', ]); - - testErrorsAndWarnings('from a_index | where signum(numberField) > 0', []); - - testErrorsAndWarnings('from a_index | where signum(stringField) > 0', [ - 'Argument of [signum] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where to_unsigned_long(textField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(textField)] type [unsigned_long]', ]); - - testErrorsAndWarnings('from a_index | eval var = signum(numberField)', []); - testErrorsAndWarnings('from a_index | eval signum(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = signum(to_integer(stringField))', []); - - testErrorsAndWarnings('from a_index | eval signum(stringField)', [ - 'Argument of [signum] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where to_unsigned_long(unsignedLongField) > 0', [ + 'Argument of [>] must be [double], found value [to_unsigned_long(unsignedLongField)] type [unsigned_long]', ]); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(booleanField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = to_unsigned_long(to_boolean(booleanField))', + [] + ); - testErrorsAndWarnings('from a_index | eval signum(numberField, extraArg)', [ - 'Error: [signum] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval to_unsigned_long(cartesianPointField)', [ + 'Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | eval var = signum(*)', [ - 'Using wildcards (*) in signum is not allowed', + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(*)', [ + 'Using wildcards (*) in to_unsigned_long is not allowed', ]); - testErrorsAndWarnings('from a_index | sort signum(numberField)', []); - testErrorsAndWarnings('row var = signum(to_integer(true))', []); - - testErrorsAndWarnings('row var = signum(true)', [ - 'Argument of [signum] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(dateField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(dateField)', []); - testErrorsAndWarnings('from a_index | where signum(booleanField) > 0', [ - 'Argument of [signum] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = to_unsigned_long(to_datetime(dateField))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = signum(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(doubleField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = to_unsigned_long(to_double(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(integerField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = to_unsigned_long(to_integer(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(keywordField)', []); + testErrorsAndWarnings( + 'from a_index | eval var = to_unsigned_long(to_string(booleanField))', + [] + ); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(longField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(textField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ul(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ulong(unsignedLongField)', []); - testErrorsAndWarnings('from a_index | eval signum(booleanField)', [ - 'Argument of [signum] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval to_unsigned_long(booleanField, extraArg)', [ + 'Error: [to_unsigned_long] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | eval signum(null)', []); - testErrorsAndWarnings('row nullVar = null | eval signum(nullVar)', []); + + testErrorsAndWarnings('from a_index | sort to_unsigned_long(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_unsigned_long(nullVar)', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long("2022")', []); + testErrorsAndWarnings('from a_index | eval to_unsigned_long(concat("20", "22"))', []); }); - describe('sin', () => { - testErrorsAndWarnings('row var = sin(5)', []); - testErrorsAndWarnings('row sin(5)', []); - testErrorsAndWarnings('row var = sin(to_integer("a"))', []); + describe('to_upper', () => { + testErrorsAndWarnings('row var = to_upper("a")', []); + testErrorsAndWarnings('row to_upper("a")', []); + testErrorsAndWarnings('row var = to_upper(to_string(true))', []); - testErrorsAndWarnings('row var = sin("a")', [ - 'Argument of [sin] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('row var = to_upper(true)', [ + 'Argument of [to_upper] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | where sin(numberField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = to_upper(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_upper(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_upper(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | where sin(stringField) > 0', [ - 'Argument of [sin] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval to_upper(booleanField)', [ + 'Argument of [to_upper] must be [keyword], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = sin(numberField)', []); - testErrorsAndWarnings('from a_index | eval sin(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = sin(to_integer(stringField))', []); - - testErrorsAndWarnings('from a_index | eval sin(stringField)', [ - 'Argument of [sin] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval var = to_upper(*)', [ + 'Using wildcards (*) in to_upper is not allowed', ]); - testErrorsAndWarnings('from a_index | eval sin(numberField, extraArg)', [ - 'Error: [sin] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings('from a_index | eval var = to_upper(textField)', []); + testErrorsAndWarnings('from a_index | eval to_upper(textField)', []); - testErrorsAndWarnings('from a_index | eval var = sin(*)', [ - 'Using wildcards (*) in sin is not allowed', + testErrorsAndWarnings('from a_index | eval to_upper(keywordField, extraArg)', [ + 'Error: [to_upper] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | sort sin(numberField)', []); - testErrorsAndWarnings('row var = sin(to_integer(true))', []); + testErrorsAndWarnings('from a_index | sort to_upper(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_upper(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_upper(nullVar)', []); + }); - testErrorsAndWarnings('row var = sin(true)', [ - 'Argument of [sin] must be [number], found value [true] type [boolean]', - ]); + describe('to_version', () => { + testErrorsAndWarnings('row var = to_version("a")', []); + testErrorsAndWarnings('row to_version("a")', []); + testErrorsAndWarnings('row var = to_ver("a")', []); + testErrorsAndWarnings('row var = to_version(to_version("1.0.0"))', []); + testErrorsAndWarnings('row to_version(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = to_ver(to_version("1.0.0"))', []); - testErrorsAndWarnings('from a_index | where sin(booleanField) > 0', [ - 'Argument of [sin] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('row var = to_version(true)', [ + 'Argument of [to_version] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = sin(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_version(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_version(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ver(keywordField)', []); - testErrorsAndWarnings('from a_index | eval sin(booleanField)', [ - 'Argument of [sin] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = to_version(*)', [ + 'Using wildcards (*) in to_version is not allowed', ]); - testErrorsAndWarnings('from a_index | eval sin(null)', []); - testErrorsAndWarnings('row nullVar = null | eval sin(nullVar)', []); - }); - describe('sinh', () => { - testErrorsAndWarnings('row var = sinh(5)', []); - testErrorsAndWarnings('row sinh(5)', []); - testErrorsAndWarnings('row var = sinh(to_integer("a"))', []); + testErrorsAndWarnings('from a_index | eval var = to_version(textField)', []); + testErrorsAndWarnings('from a_index | eval to_version(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ver(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_version(versionField)', []); + testErrorsAndWarnings('from a_index | eval to_version(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = to_ver(versionField)', []); - testErrorsAndWarnings('row var = sinh("a")', [ - 'Argument of [sinh] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | eval to_version(keywordField, extraArg)', [ + 'Error: [to_version] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | where sinh(numberField) > 0', []); - - testErrorsAndWarnings('from a_index | where sinh(stringField) > 0', [ - 'Argument of [sinh] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings('from a_index | sort to_version(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_version(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_version(nullVar)', []); + }); - testErrorsAndWarnings('from a_index | eval var = sinh(numberField)', []); - testErrorsAndWarnings('from a_index | eval sinh(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = sinh(to_integer(stringField))', []); + describe('trim', () => { + testErrorsAndWarnings('row var = trim("a")', []); + testErrorsAndWarnings('row trim("a")', []); + testErrorsAndWarnings('row var = trim(to_string(true))', []); - testErrorsAndWarnings('from a_index | eval sinh(stringField)', [ - 'Argument of [sinh] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('row var = trim(true)', [ + 'Argument of [trim] must be [keyword], found value [true] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval sinh(numberField, extraArg)', [ - 'Error: [sinh] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval var = trim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval trim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = trim(to_string(booleanField))', []); + + testErrorsAndWarnings('from a_index | eval trim(booleanField)', [ + 'Argument of [trim] must be [keyword], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = sinh(*)', [ - 'Using wildcards (*) in sinh is not allowed', + testErrorsAndWarnings('from a_index | eval var = trim(*)', [ + 'Using wildcards (*) in trim is not allowed', ]); - testErrorsAndWarnings('from a_index | sort sinh(numberField)', []); - testErrorsAndWarnings('row var = sinh(to_integer(true))', []); + testErrorsAndWarnings('from a_index | eval var = trim(textField)', []); + testErrorsAndWarnings('from a_index | eval trim(textField)', []); - testErrorsAndWarnings('row var = sinh(true)', [ - 'Argument of [sinh] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval trim(keywordField, extraArg)', [ + 'Error: [trim] function expects exactly one argument, got 2.', ]); - testErrorsAndWarnings('from a_index | where sinh(booleanField) > 0', [ - 'Argument of [sinh] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | sort trim(keywordField)', []); + testErrorsAndWarnings('from a_index | eval trim(null)', []); + testErrorsAndWarnings('row nullVar = null | eval trim(nullVar)', []); + }); - testErrorsAndWarnings('from a_index | eval var = sinh(to_integer(booleanField))', []); + describe('case', () => { + testErrorsAndWarnings('row var = case(true, "a")', []); + testErrorsAndWarnings('row case(true, "a")', []); - testErrorsAndWarnings('from a_index | eval sinh(booleanField)', [ - 'Argument of [sinh] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('row var = case(to_cartesianpoint("POINT (30 10)"), true)', [ + 'Argument of [case] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', ]); - testErrorsAndWarnings('from a_index | eval sinh(null)', []); - testErrorsAndWarnings('row nullVar = null | eval sinh(nullVar)', []); + + testErrorsAndWarnings('from a_index | eval var = case(booleanField, textField)', []); + testErrorsAndWarnings('from a_index | eval case(booleanField, textField)', []); + testErrorsAndWarnings('from a_index | sort case(booleanField, textField)', []); + testErrorsAndWarnings('from a_index | eval case(null, null)', []); + testErrorsAndWarnings('row nullVar = null | eval case(nullVar, nullVar)', []); }); - describe('split', () => { - testErrorsAndWarnings('row var = split("a", "a")', []); - testErrorsAndWarnings('row split("a", "a")', []); - testErrorsAndWarnings('row var = split(to_string("a"), to_string("a"))', []); + describe('avg', () => { + testErrorsAndWarnings('from a_index | stats var = avg(integerField)', []); + testErrorsAndWarnings('from a_index | stats avg(integerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(avg(integerField))', []); + testErrorsAndWarnings('from a_index | stats round(avg(integerField))', []); - testErrorsAndWarnings('row var = split(5, 5)', [ - 'Argument of [split] must be [string], found value [5] type [number]', - 'Argument of [split] must be [string], found value [5] type [number]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(avg(integerField)) + avg(integerField)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats round(avg(integerField)) + avg(integerField)', + [] + ); + testErrorsAndWarnings('from a_index | stats avg(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats var0 = avg(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), avg(doubleField / 2)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(doubleField / 2)', + [] + ); + testErrorsAndWarnings('from a_index | stats var0 = avg(integerField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), avg(integerField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(integerField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | stats avg(integerField) by round(doubleField / 2)', + [] + ); testErrorsAndWarnings( - 'from a_index | where length(split(stringField, stringField)) > 0', + 'from a_index | stats var0 = avg(integerField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | where length(split(numberField, numberField)) > 0', [ - 'Argument of [split] must be [string], found value [numberField] type [number]', - 'Argument of [split] must be [string], found value [numberField] type [number]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), avg(integerField) by round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | eval var = split(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval split(stringField, stringField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(integerField) by var1 = round(doubleField / 2), ipField', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), avg(integerField) by round(doubleField / 2), doubleField / 2', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = split(to_string(stringField), to_string(stringField))', + 'from a_index | stats avg(doubleField), var0 = avg(integerField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval split(numberField, numberField)', [ - 'Argument of [split] must be [string], found value [numberField] type [number]', - 'Argument of [split] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | stats var = avg(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", ]); - testErrorsAndWarnings('from a_index | eval split(stringField, stringField, extraArg)', [ - 'Error: [split] function expects exactly 2 arguments, got 3.', + testErrorsAndWarnings('from a_index | stats avg(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", ]); - testErrorsAndWarnings('from a_index | sort split(stringField, stringField)', []); - testErrorsAndWarnings('row var = split(to_string(true), to_string(true))', []); + testErrorsAndWarnings('from a_index | stats avg(booleanField)', [ + 'Argument of [avg] must be [integer], found value [booleanField] type [boolean]', + ]); - testErrorsAndWarnings('row var = split(true, true)', [ - 'Argument of [split] must be [string], found value [true] type [boolean]', - 'Argument of [split] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | stats var = avg(*)', [ + 'Using wildcards (*) in avg is not allowed', ]); + testErrorsAndWarnings('from a_index | stats var = avg(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | stats avg(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(avg(counterIntegerField))', []); + testErrorsAndWarnings('from a_index | stats round(avg(counterIntegerField))', []); + testErrorsAndWarnings( - 'from a_index | where length(split(booleanField, booleanField)) > 0', - [ - 'Argument of [split] must be [string], found value [booleanField] type [boolean]', - 'Argument of [split] must be [string], found value [booleanField] type [boolean]', - ] + 'from a_index | stats var = round(avg(counterIntegerField)) + avg(counterIntegerField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = split(to_string(booleanField), to_string(booleanField))', + 'from a_index | stats round(avg(counterIntegerField)) + avg(counterIntegerField)', [] ); - testErrorsAndWarnings('from a_index | eval split(booleanField, booleanField)', [ - 'Argument of [split] must be [string], found value [booleanField] type [boolean]', - 'Argument of [split] must be [string], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | eval split(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval split(nullVar, nullVar)', []); - }); + testErrorsAndWarnings('from a_index | stats var0 = avg(counterIntegerField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), avg(counterIntegerField)', + [] + ); - describe('sqrt', () => { - testErrorsAndWarnings('row var = sqrt(5)', []); - testErrorsAndWarnings('row sqrt(5)', []); - testErrorsAndWarnings('row var = sqrt(to_integer("a"))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(counterIntegerField)', + [] + ); - testErrorsAndWarnings('row var = sqrt("a")', [ - 'Argument of [sqrt] must be [number], found value ["a"] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(counterIntegerField) by round(doubleField / 2)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats var0 = avg(counterIntegerField) by var1 = round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | where sqrt(numberField) > 0', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), avg(counterIntegerField) by round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | where sqrt(stringField) > 0', [ - 'Argument of [sqrt] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(counterIntegerField) by var1 = round(doubleField / 2), ipField', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), avg(counterIntegerField) by round(doubleField / 2), doubleField / 2', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | eval var = sqrt(numberField)', []); - testErrorsAndWarnings('from a_index | eval sqrt(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = sqrt(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | stats var = avg(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(avg(doubleField))', []); + testErrorsAndWarnings('from a_index | stats round(avg(doubleField))', []); - testErrorsAndWarnings('from a_index | eval sqrt(stringField)', [ - 'Argument of [sqrt] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(avg(doubleField)) + avg(doubleField)', + [] + ); - testErrorsAndWarnings('from a_index | eval sqrt(numberField, extraArg)', [ - 'Error: [sqrt] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(avg(doubleField)) + avg(doubleField)', + [] + ); + testErrorsAndWarnings('from a_index | stats var0 = avg(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), avg(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = avg(doubleField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField) by round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = sqrt(*)', [ - 'Using wildcards (*) in sqrt is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = avg(doubleField) by var1 = round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | sort sqrt(numberField)', []); - testErrorsAndWarnings('row var = sqrt(to_integer(true))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), avg(doubleField) by round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('row var = sqrt(true)', [ - 'Argument of [sqrt] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(doubleField) by var1 = round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | where sqrt(booleanField) > 0', [ - 'Argument of [sqrt] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), avg(doubleField) by round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | eval var = sqrt(to_integer(booleanField))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(doubleField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | eval sqrt(booleanField)', [ - 'Argument of [sqrt] must be [number], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | eval sqrt(null)', []); - testErrorsAndWarnings('row nullVar = null | eval sqrt(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | stats var = avg(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats avg(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats var = round(avg(unsignedLongField))', []); + testErrorsAndWarnings('from a_index | stats round(avg(unsignedLongField))', []); - describe('st_contains', () => { testErrorsAndWarnings( - 'row var = st_contains(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats var = round(avg(unsignedLongField)) + avg(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row st_contains(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats round(avg(unsignedLongField)) + avg(unsignedLongField)', [] ); - testErrorsAndWarnings('row var = st_contains(to_geopoint("a"), to_geopoint("a"))', []); + testErrorsAndWarnings('from a_index | stats var0 = avg(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), avg(unsignedLongField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(unsignedLongField)', + [] + ); - testErrorsAndWarnings('row var = st_contains("a", "a")', [ - 'Argument of [st_contains] must be [cartesian_point], found value ["a"] type [string]', - 'Argument of [st_contains] must be [cartesian_point], found value ["a"] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(unsignedLongField) by round(doubleField / 2)', + [] + ); testErrorsAndWarnings( - 'row var = st_contains(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats var0 = avg(unsignedLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row st_contains(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), avg(unsignedLongField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_geopoint("a"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = avg(unsignedLongField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), avg(unsignedLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row st_contains(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = avg(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = avg(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(longField)', []); + testErrorsAndWarnings('from a_index | stats var = round(avg(longField))', []); + testErrorsAndWarnings('from a_index | stats round(avg(longField))', []); testErrorsAndWarnings( - 'row var = st_contains(to_geoshape("POINT (30 10)"), to_geopoint("a"))', + 'from a_index | stats var = round(avg(longField)) + avg(longField)', [] ); + testErrorsAndWarnings('from a_index | stats round(avg(longField)) + avg(longField)', []); + testErrorsAndWarnings('from a_index | stats var0 = avg(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), avg(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = avg(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(longField) by round(doubleField / 2)', []); testErrorsAndWarnings( - 'row var = st_contains(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats var0 = avg(longField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row st_contains(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), avg(longField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = avg(longField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), avg(longField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianpoint("a"), to_cartesianpoint("a"))', + 'from a_index | stats avg(doubleField), var0 = avg(longField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = avg(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats avg(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats var = round(avg(counterLongField))', []); + testErrorsAndWarnings('from a_index | stats round(avg(counterLongField))', []); + testErrorsAndWarnings( - 'row var = st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats var = round(avg(counterLongField)) + avg(counterLongField)', [] ); testErrorsAndWarnings( - 'row st_contains(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats round(avg(counterLongField)) + avg(counterLongField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = avg(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), avg(counterLongField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = avg(counterLongField)', + [] + ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianpoint("a"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(counterLongField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats var0 = avg(counterLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), avg(counterLongField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("a"))', + 'from a_index | stats avg(doubleField), var0 = avg(counterLongField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), avg(counterLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row st_contains(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = avg(counterLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = avg(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(avg(counterDoubleField))', []); + testErrorsAndWarnings('from a_index | stats round(avg(counterDoubleField))', []); + testErrorsAndWarnings( - 'from a_index | eval var = st_contains(geoPointField, geoPointField)', + 'from a_index | stats var = round(avg(counterDoubleField)) + avg(counterDoubleField)', [] ); - testErrorsAndWarnings('from a_index | eval st_contains(geoPointField, geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_geopoint(stringField), to_geopoint(stringField))', + 'from a_index | stats round(avg(counterDoubleField)) + avg(counterDoubleField)', [] ); - testErrorsAndWarnings('from a_index | eval st_contains(stringField, stringField)', [ - 'Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]', - 'Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]', - ]); + testErrorsAndWarnings('from a_index | stats var0 = avg(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), avg(counterDoubleField)', []); testErrorsAndWarnings( - 'from a_index | eval st_contains(geoPointField, geoPointField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = avg(counterDoubleField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(geoPointField, geoShapeField)', + 'from a_index | stats avg(counterDoubleField) by round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | eval st_contains(geoPointField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_geopoint(stringField), geoShapeField)', + 'from a_index | stats var0 = avg(counterDoubleField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(geoPointField, geoShapeField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), avg(counterDoubleField) by round(doubleField / 2), ipField', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(geoShapeField, geoPointField)', + 'from a_index | stats avg(doubleField), var0 = avg(counterDoubleField) by var1 = round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('from a_index | eval st_contains(geoShapeField, geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(geoShapeField, to_geopoint(stringField))', + 'from a_index | stats avg(doubleField), avg(counterDoubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(geoShapeField, geoPointField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = avg(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); + testErrorsAndWarnings('from a_index | sort avg(integerField)', [ + 'SORT does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(integerField)', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(integerField) > 0', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(counterIntegerField)', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(counterIntegerField) > 0', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(doubleField)', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(doubleField) > 0', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(unsignedLongField)', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(unsignedLongField) > 0', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(longField)', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(longField) > 0', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(counterLongField)', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(counterLongField) > 0', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(counterDoubleField)', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | where avg(counterDoubleField) > 0', [ + 'WHERE does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(integerField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(integerField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(integerField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(integerField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(counterIntegerField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(counterIntegerField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(counterIntegerField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(counterIntegerField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(doubleField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(doubleField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(doubleField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(doubleField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(unsignedLongField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(unsignedLongField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(unsignedLongField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(unsignedLongField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(longField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(longField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(longField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(longField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(counterLongField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(counterLongField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(counterLongField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(counterLongField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(counterDoubleField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = avg(counterDoubleField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(counterDoubleField)', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | eval avg(counterDoubleField) > 0', [ + 'EVAL does not support function avg', + ]); + + testErrorsAndWarnings('from a_index | stats avg(null)', []); + testErrorsAndWarnings('row nullVar = null | stats avg(nullVar)', []); + }); + + describe('sum', () => { + testErrorsAndWarnings('from a_index | stats var = sum(integerField)', []); + testErrorsAndWarnings('from a_index | stats sum(integerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(sum(integerField))', []); + testErrorsAndWarnings('from a_index | stats round(sum(integerField))', []); + testErrorsAndWarnings( - 'from a_index | eval var = st_contains(geoShapeField, geoShapeField)', + 'from a_index | stats var = round(sum(integerField)) + sum(integerField)', [] ); - testErrorsAndWarnings('from a_index | eval st_contains(geoShapeField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval st_contains(geoShapeField, geoShapeField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats round(sum(integerField)) + sum(integerField)', + [] + ); + testErrorsAndWarnings('from a_index | stats sum(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats var0 = sum(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), sum(doubleField / 2)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = sum(doubleField / 2)', + [] + ); + testErrorsAndWarnings('from a_index | stats var0 = sum(integerField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), sum(integerField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = sum(integerField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | stats sum(integerField) by round(doubleField / 2)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(cartesianPointField, cartesianPointField)', + 'from a_index | stats var0 = sum(integerField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), sum(integerField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_cartesianpoint(stringField), to_cartesianpoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = sum(integerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianPointField, cartesianPointField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), sum(integerField) by round(doubleField / 2), doubleField / 2', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(cartesianPointField, cartesianShapeField)', + 'from a_index | stats avg(doubleField), var0 = sum(integerField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = sum(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); + + testErrorsAndWarnings('from a_index | stats sum(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); + + testErrorsAndWarnings('from a_index | stats sum(booleanField)', [ + 'Argument of [sum] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | stats var = sum(*)', [ + 'Using wildcards (*) in sum is not allowed', + ]); + + testErrorsAndWarnings('from a_index | stats var = sum(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | stats sum(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(sum(counterIntegerField))', []); + testErrorsAndWarnings('from a_index | stats round(sum(counterIntegerField))', []); + testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianPointField, cartesianShapeField)', + 'from a_index | stats var = round(sum(counterIntegerField)) + sum(counterIntegerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_cartesianpoint(stringField), cartesianShapeField)', + 'from a_index | stats round(sum(counterIntegerField)) + sum(counterIntegerField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = sum(counterIntegerField)', []); testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianPointField, cartesianShapeField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), sum(counterIntegerField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(cartesianShapeField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = sum(counterIntegerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianShapeField, cartesianPointField)', + 'from a_index | stats sum(counterIntegerField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(cartesianShapeField, to_cartesianpoint(stringField))', + 'from a_index | stats var0 = sum(counterIntegerField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianShapeField, cartesianPointField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), sum(counterIntegerField) by round(doubleField / 2), ipField', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats avg(doubleField), var0 = sum(counterIntegerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats avg(doubleField), sum(counterIntegerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval st_contains(cartesianShapeField, cartesianShapeField, extraArg)', - ['Error: [st_contains] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = sum(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); - testErrorsAndWarnings('from a_index | sort st_contains(geoPointField, geoPointField)', []); + testErrorsAndWarnings('from a_index | stats var = sum(doubleField)', []); + testErrorsAndWarnings('from a_index | stats sum(doubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(sum(doubleField))', []); + testErrorsAndWarnings('from a_index | stats round(sum(doubleField))', []); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var = round(sum(doubleField)) + sum(doubleField)', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats round(sum(doubleField)) + sum(doubleField)', [] ); - + testErrorsAndWarnings('from a_index | stats var0 = sum(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), sum(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = sum(doubleField)', []); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats sum(doubleField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var0 = sum(doubleField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), sum(doubleField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = sum(doubleField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), sum(doubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_contains(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = sum(doubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('row var = st_contains(true, true)', [ - 'Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]', - 'Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | stats var = sum(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats sum(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats var = round(sum(unsignedLongField))', []); + testErrorsAndWarnings('from a_index | stats round(sum(unsignedLongField))', []); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats var = round(sum(unsignedLongField)) + sum(unsignedLongField)', [] ); - testErrorsAndWarnings('from a_index | eval st_contains(booleanField, booleanField)', [ - 'Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]', - 'Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats round(sum(unsignedLongField)) + sum(unsignedLongField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = sum(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), sum(unsignedLongField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats avg(doubleField), var0 = sum(unsignedLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats sum(unsignedLongField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats var0 = sum(unsignedLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats avg(doubleField), sum(unsignedLongField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats avg(doubleField), var0 = sum(unsignedLongField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats avg(doubleField), sum(unsignedLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | sort st_contains(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = sum(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval st_contains(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval st_contains(nullVar, nullVar)', []); - }); - describe('st_disjoint', () => { + testErrorsAndWarnings('from a_index | stats var = sum(longField)', []); + testErrorsAndWarnings('from a_index | stats sum(longField)', []); + testErrorsAndWarnings('from a_index | stats var = round(sum(longField))', []); + testErrorsAndWarnings('from a_index | stats round(sum(longField))', []); testErrorsAndWarnings( - 'row var = st_disjoint(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats var = round(sum(longField)) + sum(longField)', [] ); + testErrorsAndWarnings('from a_index | stats round(sum(longField)) + sum(longField)', []); + testErrorsAndWarnings('from a_index | stats var0 = sum(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), sum(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = sum(longField)', []); + testErrorsAndWarnings('from a_index | stats sum(longField) by round(doubleField / 2)', []); testErrorsAndWarnings( - 'row st_disjoint(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats var0 = sum(longField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('row var = st_disjoint(to_geopoint("a"), to_geopoint("a"))', []); - - testErrorsAndWarnings('row var = st_disjoint("a", "a")', [ - 'Argument of [st_disjoint] must be [cartesian_point], found value ["a"] type [string]', - 'Argument of [st_disjoint] must be [cartesian_point], found value ["a"] type [string]', - ]); - testErrorsAndWarnings( - 'row var = st_disjoint(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), sum(longField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row st_disjoint(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = sum(longField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_geopoint("a"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), sum(longField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = sum(longField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = sum(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats sum(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats var = round(sum(counterLongField))', []); + testErrorsAndWarnings('from a_index | stats round(sum(counterLongField))', []); + testErrorsAndWarnings( - 'row st_disjoint(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats var = round(sum(counterLongField)) + sum(counterLongField)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_geoshape("POINT (30 10)"), to_geopoint("a"))', + 'from a_index | stats round(sum(counterLongField)) + sum(counterLongField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = sum(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), sum(counterLongField)', []); testErrorsAndWarnings( - 'row var = st_disjoint(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = sum(counterLongField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | stats sum(counterLongField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row st_disjoint(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats var0 = sum(counterLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), sum(counterLongField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = sum(counterLongField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianpoint("a"), to_cartesianpoint("a"))', + 'from a_index | stats avg(doubleField), sum(counterLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = sum(counterLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = sum(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats sum(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(sum(counterDoubleField))', []); + testErrorsAndWarnings('from a_index | stats round(sum(counterDoubleField))', []); + testErrorsAndWarnings( - 'row st_disjoint(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats var = round(sum(counterDoubleField)) + sum(counterDoubleField)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianpoint("a"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats round(sum(counterDoubleField)) + sum(counterDoubleField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = sum(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), sum(counterDoubleField)', []); + testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = sum(counterDoubleField)', [] ); testErrorsAndWarnings( - 'row st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats sum(counterDoubleField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("a"))', + 'from a_index | stats var0 = sum(counterDoubleField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), sum(counterDoubleField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row st_disjoint(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = sum(counterDoubleField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(geoPointField, geoPointField)', + 'from a_index | stats avg(doubleField), sum(counterDoubleField) by round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval st_disjoint(geoPointField, geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_geopoint(stringField), to_geopoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = sum(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval st_disjoint(stringField, stringField)', [ - 'Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]', - 'Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | sort sum(integerField)', [ + 'SORT does not support function sum', ]); - testErrorsAndWarnings( - 'from a_index | eval st_disjoint(geoPointField, geoPointField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] - ); + testErrorsAndWarnings('from a_index | where sum(integerField)', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(integerField) > 0', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(counterIntegerField)', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(counterIntegerField) > 0', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(doubleField)', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(doubleField) > 0', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(unsignedLongField)', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(unsignedLongField) > 0', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(longField)', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(longField) > 0', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(counterLongField)', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(counterLongField) > 0', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(counterDoubleField)', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | where sum(counterDoubleField) > 0', [ + 'WHERE does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(integerField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(integerField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(integerField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(integerField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(counterIntegerField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(counterIntegerField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(counterIntegerField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(counterIntegerField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(doubleField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(doubleField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(doubleField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(doubleField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(unsignedLongField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(unsignedLongField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(unsignedLongField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(unsignedLongField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(longField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(longField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(longField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(longField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(counterLongField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(counterLongField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(counterLongField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(counterLongField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(counterDoubleField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval var = sum(counterDoubleField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(counterDoubleField)', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | eval sum(counterDoubleField) > 0', [ + 'EVAL does not support function sum', + ]); + + testErrorsAndWarnings('from a_index | stats sum(null)', []); + testErrorsAndWarnings('row nullVar = null | stats sum(nullVar)', []); + }); + + describe('median', () => { + testErrorsAndWarnings('from a_index | stats var = median(integerField)', []); + testErrorsAndWarnings('from a_index | stats median(integerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(median(integerField))', []); + testErrorsAndWarnings('from a_index | stats round(median(integerField))', []); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(geoPointField, geoShapeField)', + 'from a_index | stats var = round(median(integerField)) + median(integerField)', [] ); - testErrorsAndWarnings('from a_index | eval st_disjoint(geoPointField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_geopoint(stringField), geoShapeField)', + 'from a_index | stats round(median(integerField)) + median(integerField)', [] ); - testErrorsAndWarnings( - 'from a_index | eval st_disjoint(geoPointField, geoShapeField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] - ); + testErrorsAndWarnings('from a_index | stats median(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats var0 = median(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), median(doubleField / 2)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(geoShapeField, geoPointField)', + 'from a_index | stats avg(doubleField), var0 = median(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | eval st_disjoint(geoShapeField, geoPointField)', []); + testErrorsAndWarnings('from a_index | stats var0 = median(integerField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), median(integerField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(geoShapeField, to_geopoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = median(integerField)', [] ); - testErrorsAndWarnings( - 'from a_index | eval st_disjoint(geoShapeField, geoPointField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats median(integerField) by round(doubleField / 2)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(geoShapeField, geoShapeField)', + 'from a_index | stats var0 = median(integerField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | eval st_disjoint(geoShapeField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(geoShapeField, geoShapeField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), median(integerField) by round(doubleField / 2), ipField', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median(integerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), median(integerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), to_cartesianpoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = median(integerField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianPointField, cartesianPointField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] - ); + testErrorsAndWarnings('from a_index | stats var = median(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); - testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(cartesianPointField, cartesianShapeField)', - [] - ); + testErrorsAndWarnings('from a_index | stats median(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); + + testErrorsAndWarnings('from a_index | stats median(booleanField)', [ + 'Argument of [median] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | stats var = median(*)', [ + 'Using wildcards (*) in median is not allowed', + ]); + + testErrorsAndWarnings('from a_index | stats var = median(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | stats median(counterIntegerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(median(counterIntegerField))', []); + testErrorsAndWarnings('from a_index | stats round(median(counterIntegerField))', []); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField)', + 'from a_index | stats var = round(median(counterIntegerField)) + median(counterIntegerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), cartesianShapeField)', + 'from a_index | stats round(median(counterIntegerField)) + median(counterIntegerField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = median(counterIntegerField)', []); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), median(counterIntegerField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(cartesianShapeField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median(counterIntegerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField)', + 'from a_index | stats median(counterIntegerField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(cartesianShapeField, to_cartesianpoint(stringField))', + 'from a_index | stats var0 = median(counterIntegerField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), median(counterIntegerField) by round(doubleField / 2), ipField', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats avg(doubleField), var0 = median(counterIntegerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats avg(doubleField), median(counterIntegerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField, extraArg)', - ['Error: [st_disjoint] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = median(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); - testErrorsAndWarnings('from a_index | sort st_disjoint(geoPointField, geoPointField)', []); + testErrorsAndWarnings('from a_index | stats var = median(doubleField)', []); + testErrorsAndWarnings('from a_index | stats median(doubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(median(doubleField))', []); + testErrorsAndWarnings('from a_index | stats round(median(doubleField))', []); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var = round(median(doubleField)) + median(doubleField)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats round(median(doubleField)) + median(doubleField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = median(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), median(doubleField)', []); testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = median(doubleField)', [] ); - testErrorsAndWarnings( - 'row var = st_disjoint(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats median(doubleField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats var0 = median(doubleField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), median(doubleField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = median(doubleField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_disjoint(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), median(doubleField) by round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('row var = st_disjoint(true, true)', [ - 'Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]', - 'Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]', - ]); - testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats avg(doubleField), var0 = median(doubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval st_disjoint(booleanField, booleanField)', [ - 'Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]', - 'Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings('from a_index | stats var = median(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats median(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | stats var = round(median(unsignedLongField))', []); + testErrorsAndWarnings('from a_index | stats round(median(unsignedLongField))', []); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats var = round(median(unsignedLongField)) + median(unsignedLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats round(median(unsignedLongField)) + median(unsignedLongField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = median(unsignedLongField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats avg(doubleField), median(unsignedLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats avg(doubleField), var0 = median(unsignedLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats median(unsignedLongField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats var0 = median(unsignedLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats avg(doubleField), median(unsignedLongField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | sort st_disjoint(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median(unsignedLongField) by var1 = round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('from a_index | eval st_disjoint(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval st_disjoint(nullVar, nullVar)', []); - }); - describe('st_intersects', () => { testErrorsAndWarnings( - 'row var = st_intersects(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median(unsignedLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row st_intersects(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('row var = st_intersects(to_geopoint("a"), to_geopoint("a"))', []); - - testErrorsAndWarnings('row var = st_intersects("a", "a")', [ - 'Argument of [st_intersects] must be [cartesian_point], found value ["a"] type [string]', - 'Argument of [st_intersects] must be [cartesian_point], found value ["a"] type [string]', - ]); + testErrorsAndWarnings('from a_index | stats var = median(longField)', []); + testErrorsAndWarnings('from a_index | stats median(longField)', []); + testErrorsAndWarnings('from a_index | stats var = round(median(longField))', []); + testErrorsAndWarnings('from a_index | stats round(median(longField))', []); testErrorsAndWarnings( - 'row var = st_intersects(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats var = round(median(longField)) + median(longField)', [] ); testErrorsAndWarnings( - 'row st_intersects(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats round(median(longField)) + median(longField)', [] ); - + testErrorsAndWarnings('from a_index | stats var0 = median(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), median(longField)', []); testErrorsAndWarnings( - 'row var = st_intersects(to_geopoint("a"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median(longField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | stats median(longField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats var0 = median(longField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row st_intersects(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median(longField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_geoshape("POINT (30 10)"), to_geopoint("a"))', + 'from a_index | stats avg(doubleField), var0 = median(longField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median(longField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row st_intersects(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median(longField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = median(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats median(counterLongField)', []); + testErrorsAndWarnings('from a_index | stats var = round(median(counterLongField))', []); + testErrorsAndWarnings('from a_index | stats round(median(counterLongField))', []); + testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats var = round(median(counterLongField)) + median(counterLongField)', [] ); testErrorsAndWarnings( - 'row st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats round(median(counterLongField)) + median(counterLongField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = median(counterLongField)', []); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianpoint("a"), to_cartesianpoint("a"))', + 'from a_index | stats avg(doubleField), median(counterLongField)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median(counterLongField)', [] ); testErrorsAndWarnings( - 'row st_intersects(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats median(counterLongField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianpoint("a"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats var0 = median(counterLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median(counterLongField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median(counterLongField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("a"))', + 'from a_index | stats avg(doubleField), median(counterLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median(counterLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); + testErrorsAndWarnings('from a_index | stats var = median(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats median(counterDoubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(median(counterDoubleField))', []); + testErrorsAndWarnings('from a_index | stats round(median(counterDoubleField))', []); + testErrorsAndWarnings( - 'row st_intersects(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats var = round(median(counterDoubleField)) + median(counterDoubleField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(geoPointField, geoPointField)', + 'from a_index | stats round(median(counterDoubleField)) + median(counterDoubleField)', [] ); + testErrorsAndWarnings('from a_index | stats var0 = median(counterDoubleField)', []); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoPointField, geoPointField)', + 'from a_index | stats avg(doubleField), median(counterDoubleField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_geopoint(stringField), to_geopoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = median(counterDoubleField)', [] ); - testErrorsAndWarnings('from a_index | eval st_intersects(stringField, stringField)', [ - 'Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]', - 'Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats median(counterDoubleField) by round(doubleField / 2)', + [] + ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoPointField, geoPointField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var0 = median(counterDoubleField) by var1 = round(doubleField / 2)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(geoPointField, geoShapeField)', + 'from a_index | stats avg(doubleField), median(counterDoubleField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoPointField, geoShapeField)', + 'from a_index | stats avg(doubleField), var0 = median(counterDoubleField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_geopoint(stringField), geoShapeField)', + 'from a_index | stats avg(doubleField), median(counterDoubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoPointField, geoShapeField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = median(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); + testErrorsAndWarnings('from a_index | sort median(integerField)', [ + 'SORT does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(integerField)', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(integerField) > 0', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(counterIntegerField)', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(counterIntegerField) > 0', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(doubleField)', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(doubleField) > 0', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(unsignedLongField)', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(unsignedLongField) > 0', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(longField)', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(longField) > 0', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(counterLongField)', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(counterLongField) > 0', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(counterDoubleField)', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | where median(counterDoubleField) > 0', [ + 'WHERE does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(integerField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(integerField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(integerField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(integerField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(counterIntegerField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(counterIntegerField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(counterIntegerField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(counterIntegerField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(doubleField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(doubleField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(doubleField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(doubleField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(unsignedLongField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(unsignedLongField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(unsignedLongField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(unsignedLongField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(longField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(longField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(longField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(longField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(counterLongField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(counterLongField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(counterLongField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(counterLongField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(counterDoubleField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval var = median(counterDoubleField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(counterDoubleField)', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | eval median(counterDoubleField) > 0', [ + 'EVAL does not support function median', + ]); + + testErrorsAndWarnings('from a_index | stats median(null)', []); + testErrorsAndWarnings('row nullVar = null | stats median(nullVar)', []); + }); + + describe('median_absolute_deviation', () => { testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(geoShapeField, geoPointField)', + 'from a_index | stats var = median_absolute_deviation(integerField)', [] ); + testErrorsAndWarnings('from a_index | stats median_absolute_deviation(integerField)', []); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoShapeField, geoPointField)', + 'from a_index | stats var = round(median_absolute_deviation(integerField))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(geoShapeField, to_geopoint(stringField))', + 'from a_index | stats round(median_absolute_deviation(integerField))', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoShapeField, geoPointField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var = round(median_absolute_deviation(integerField)) + median_absolute_deviation(integerField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(geoShapeField, geoShapeField)', + 'from a_index | stats round(median_absolute_deviation(integerField)) + median_absolute_deviation(integerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoShapeField, geoShapeField)', + 'from a_index | stats median_absolute_deviation(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(geoShapeField, geoShapeField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var0 = median_absolute_deviation(doubleField / 2)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), median_absolute_deviation(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_cartesianpoint(stringField), to_cartesianpoint(stringField))', + 'from a_index | stats var0 = median_absolute_deviation(integerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianPointField, cartesianPointField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), median_absolute_deviation(integerField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(cartesianPointField, cartesianShapeField)', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(integerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianPointField, cartesianShapeField)', + 'from a_index | stats median_absolute_deviation(integerField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_cartesianpoint(stringField), cartesianShapeField)', + 'from a_index | stats var0 = median_absolute_deviation(integerField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianPointField, cartesianShapeField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), median_absolute_deviation(integerField) by round(doubleField / 2), ipField', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(cartesianShapeField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(integerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianShapeField, cartesianPointField)', + 'from a_index | stats avg(doubleField), median_absolute_deviation(integerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(cartesianShapeField, to_cartesianpoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(integerField) by var1 = round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianShapeField, cartesianPointField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var = median_absolute_deviation(avg(integerField))', + [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ] ); + testErrorsAndWarnings('from a_index | stats median_absolute_deviation(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); + + testErrorsAndWarnings('from a_index | stats median_absolute_deviation(booleanField)', [ + 'Argument of [median_absolute_deviation] must be [integer], found value [booleanField] type [boolean]', + ]); + + testErrorsAndWarnings('from a_index | stats var = median_absolute_deviation(*)', [ + 'Using wildcards (*) in median_absolute_deviation is not allowed', + ]); + testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats var = median_absolute_deviation(counterIntegerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats median_absolute_deviation(counterIntegerField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField, extraArg)', - ['Error: [st_intersects] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var = round(median_absolute_deviation(counterIntegerField))', + [] ); testErrorsAndWarnings( - 'from a_index | sort st_intersects(geoPointField, geoPointField)', + 'from a_index | stats round(median_absolute_deviation(counterIntegerField))', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var = round(median_absolute_deviation(counterIntegerField)) + median_absolute_deviation(counterIntegerField)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats round(median_absolute_deviation(counterIntegerField)) + median_absolute_deviation(counterIntegerField)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var0 = median_absolute_deviation(counterIntegerField)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterIntegerField)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterIntegerField)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats median_absolute_deviation(counterIntegerField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats var0 = median_absolute_deviation(counterIntegerField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_intersects(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterIntegerField) by round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('row var = st_intersects(true, true)', [ - 'Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]', - 'Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterIntegerField) by var1 = round(doubleField / 2), ipField', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterIntegerField) by round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval st_intersects(booleanField, booleanField)', [ - 'Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]', - 'Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterIntegerField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats var = median_absolute_deviation(doubleField)', [] ); + testErrorsAndWarnings('from a_index | stats median_absolute_deviation(doubleField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats var = round(median_absolute_deviation(doubleField))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats round(median_absolute_deviation(doubleField))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats var = round(median_absolute_deviation(doubleField)) + median_absolute_deviation(doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats round(median_absolute_deviation(doubleField)) + median_absolute_deviation(doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats var0 = median_absolute_deviation(doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | sort st_intersects(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval st_intersects(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval st_intersects(nullVar, nullVar)', []); - }); - describe('st_within', () => { testErrorsAndWarnings( - 'row var = st_within(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats median_absolute_deviation(doubleField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row st_within(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats var0 = median_absolute_deviation(doubleField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('row var = st_within(to_geopoint("a"), to_geopoint("a"))', []); - - testErrorsAndWarnings('row var = st_within("a", "a")', [ - 'Argument of [st_within] must be [cartesian_point], found value ["a"] type [string]', - 'Argument of [st_within] must be [cartesian_point], found value ["a"] type [string]', - ]); - testErrorsAndWarnings( - 'row var = st_within(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(doubleField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row st_within(to_geopoint("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geopoint("a"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(doubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(doubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row st_within(to_geoshape("POINT (30 10)"), to_geopoint("POINT (30 10)"))', + 'from a_index | stats var = median_absolute_deviation(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geoshape("POINT (30 10)"), to_geopoint("a"))', + 'from a_index | stats median_absolute_deviation(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats var = round(median_absolute_deviation(unsignedLongField))', [] ); testErrorsAndWarnings( - 'row st_within(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', + 'from a_index | stats round(median_absolute_deviation(unsignedLongField))', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats var = round(median_absolute_deviation(unsignedLongField)) + median_absolute_deviation(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats round(median_absolute_deviation(unsignedLongField)) + median_absolute_deviation(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianpoint("a"), to_cartesianpoint("a"))', + 'from a_index | stats var0 = median_absolute_deviation(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row st_within(to_cartesianpoint("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(unsignedLongField)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianpoint("a"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats median_absolute_deviation(unsignedLongField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats var0 = median_absolute_deviation(unsignedLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row st_within(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(unsignedLongField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianshape("POINT (30 10)"), to_cartesianpoint("a"))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(unsignedLongField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(unsignedLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row st_within(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(unsignedLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(geoPointField, geoPointField)', + 'from a_index | stats var = median_absolute_deviation(longField)', [] ); - testErrorsAndWarnings('from a_index | eval st_within(geoPointField, geoPointField)', []); + testErrorsAndWarnings('from a_index | stats median_absolute_deviation(longField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_geopoint(stringField), to_geopoint(stringField))', + 'from a_index | stats var = round(median_absolute_deviation(longField))', [] ); - testErrorsAndWarnings('from a_index | eval st_within(stringField, stringField)', [ - 'Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]', - 'Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]', - ]); - testErrorsAndWarnings( - 'from a_index | eval st_within(geoPointField, geoPointField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats round(median_absolute_deviation(longField))', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(geoPointField, geoShapeField)', + 'from a_index | stats var = round(median_absolute_deviation(longField)) + median_absolute_deviation(longField)', [] ); - testErrorsAndWarnings('from a_index | eval st_within(geoPointField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_geopoint(stringField), geoShapeField)', + 'from a_index | stats round(median_absolute_deviation(longField)) + median_absolute_deviation(longField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(geoPointField, geoShapeField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var0 = median_absolute_deviation(longField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(geoShapeField, geoPointField)', + 'from a_index | stats avg(doubleField), median_absolute_deviation(longField)', [] ); - testErrorsAndWarnings('from a_index | eval st_within(geoShapeField, geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = st_within(geoShapeField, to_geopoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(longField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(geoShapeField, geoPointField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats median_absolute_deviation(longField) by round(doubleField / 2)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(geoShapeField, geoShapeField)', + 'from a_index | stats var0 = median_absolute_deviation(longField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | eval st_within(geoShapeField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval st_within(geoShapeField, geoShapeField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), median_absolute_deviation(longField) by round(doubleField / 2), ipField', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(longField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), median_absolute_deviation(longField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_cartesianpoint(stringField), to_cartesianpoint(stringField))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(longField) by var1 = round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianPointField, cartesianPointField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var = median_absolute_deviation(counterLongField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(cartesianPointField, cartesianShapeField)', + 'from a_index | stats median_absolute_deviation(counterLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianPointField, cartesianShapeField)', + 'from a_index | stats var = round(median_absolute_deviation(counterLongField))', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_cartesianpoint(stringField), cartesianShapeField)', + 'from a_index | stats round(median_absolute_deviation(counterLongField))', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianPointField, cartesianShapeField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats var = round(median_absolute_deviation(counterLongField)) + median_absolute_deviation(counterLongField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(cartesianShapeField, cartesianPointField)', + 'from a_index | stats round(median_absolute_deviation(counterLongField)) + median_absolute_deviation(counterLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianShapeField, cartesianPointField)', + 'from a_index | stats var0 = median_absolute_deviation(counterLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(cartesianShapeField, to_cartesianpoint(stringField))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterLongField)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianShapeField, cartesianPointField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterLongField)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats median_absolute_deviation(counterLongField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianShapeField, cartesianShapeField)', + 'from a_index | stats var0 = median_absolute_deviation(counterLongField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval st_within(cartesianShapeField, cartesianShapeField, extraArg)', - ['Error: [st_within] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterLongField) by round(doubleField / 2), ipField', + [] ); - testErrorsAndWarnings('from a_index | sort st_within(geoPointField, geoPointField)', []); - testErrorsAndWarnings( - 'row var = st_within(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterLongField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterLongField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterLongField) by var1 = round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'row var = st_within(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var = median_absolute_deviation(counterDoubleField)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats median_absolute_deviation(counterDoubleField)', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geopoint(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats var = round(median_absolute_deviation(counterDoubleField))', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geoshape(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats round(median_absolute_deviation(counterDoubleField))', [] ); testErrorsAndWarnings( - 'row var = st_within(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats var = round(median_absolute_deviation(counterDoubleField)) + median_absolute_deviation(counterDoubleField)', [] ); - testErrorsAndWarnings('row var = st_within(true, true)', [ - 'Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]', - 'Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]', - ]); - testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats round(median_absolute_deviation(counterDoubleField)) + median_absolute_deviation(counterDoubleField)', [] ); - testErrorsAndWarnings('from a_index | eval st_within(booleanField, booleanField)', [ - 'Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]', - 'Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = median_absolute_deviation(counterDoubleField)', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterDoubleField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterDoubleField)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', + 'from a_index | stats median_absolute_deviation(counterDoubleField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats var0 = median_absolute_deviation(counterDoubleField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterDoubleField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterDoubleField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))', + 'from a_index | stats avg(doubleField), median_absolute_deviation(counterDoubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | sort st_within(cartesianPointField, cartesianPointField)', + 'from a_index | stats avg(doubleField), var0 = median_absolute_deviation(counterDoubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval st_within(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval st_within(nullVar, nullVar)', []); - }); - describe('st_x', () => { - testErrorsAndWarnings('row var = st_x(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row st_x(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = st_x(to_geopoint("a"))', []); + testErrorsAndWarnings('from a_index | sort median_absolute_deviation(integerField)', [ + 'SORT does not support function median_absolute_deviation', + ]); - testErrorsAndWarnings('row var = st_x("a")', [ - 'Argument of [st_x] must be [cartesian_point], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | where median_absolute_deviation(integerField)', [ + 'WHERE does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('row var = st_x(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row st_x(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = st_x(to_cartesianpoint("a"))', []); - testErrorsAndWarnings('from a_index | eval var = st_x(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval st_x(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval var = st_x(to_geopoint(stringField))', []); + testErrorsAndWarnings('from a_index | where median_absolute_deviation(integerField) > 0', [ + 'WHERE does not support function median_absolute_deviation', + ]); + + testErrorsAndWarnings( + 'from a_index | where median_absolute_deviation(counterIntegerField)', + ['WHERE does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings( + 'from a_index | where median_absolute_deviation(counterIntegerField) > 0', + ['WHERE does not support function median_absolute_deviation'] + ); - testErrorsAndWarnings('from a_index | eval st_x(stringField)', [ - 'Argument of [st_x] must be [cartesian_point], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where median_absolute_deviation(doubleField)', [ + 'WHERE does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval st_x(geoPointField, extraArg)', [ - 'Error: [st_x] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | where median_absolute_deviation(doubleField) > 0', [ + 'WHERE does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval var = st_x(*)', [ - 'Using wildcards (*) in st_x is not allowed', + testErrorsAndWarnings('from a_index | where median_absolute_deviation(unsignedLongField)', [ + 'WHERE does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval var = st_x(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval st_x(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval var = st_x(to_cartesianpoint(stringField))', []); + testErrorsAndWarnings( + 'from a_index | where median_absolute_deviation(unsignedLongField) > 0', + ['WHERE does not support function median_absolute_deviation'] + ); - testErrorsAndWarnings('from a_index | eval st_x(cartesianPointField, extraArg)', [ - 'Error: [st_x] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | where median_absolute_deviation(longField)', [ + 'WHERE does not support function median_absolute_deviation', + ]); + + testErrorsAndWarnings('from a_index | where median_absolute_deviation(longField) > 0', [ + 'WHERE does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | sort st_x(geoPointField)', []); + testErrorsAndWarnings('from a_index | where median_absolute_deviation(counterLongField)', [ + 'WHERE does not support function median_absolute_deviation', + ]); testErrorsAndWarnings( - 'row var = st_x(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', - [] + 'from a_index | where median_absolute_deviation(counterLongField) > 0', + ['WHERE does not support function median_absolute_deviation'] ); - testErrorsAndWarnings('row var = st_x(to_geopoint(to_geopoint("POINT (30 10)")))', []); + testErrorsAndWarnings( + 'from a_index | where median_absolute_deviation(counterDoubleField)', + ['WHERE does not support function median_absolute_deviation'] + ); - testErrorsAndWarnings('row var = st_x(true)', [ - 'Argument of [st_x] must be [cartesian_point], found value [true] type [boolean]', + testErrorsAndWarnings( + 'from a_index | where median_absolute_deviation(counterDoubleField) > 0', + ['WHERE does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings('from a_index | eval var = median_absolute_deviation(integerField)', [ + 'EVAL does not support function median_absolute_deviation', ]); testErrorsAndWarnings( - 'from a_index | eval var = st_x(to_cartesianpoint(cartesianPointField))', - [] + 'from a_index | eval var = median_absolute_deviation(integerField) > 0', + ['EVAL does not support function median_absolute_deviation'] ); - testErrorsAndWarnings('from a_index | eval st_x(booleanField)', [ - 'Argument of [st_x] must be [cartesian_point], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(integerField)', [ + 'EVAL does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval var = st_x(to_geopoint(geoPointField))', []); - testErrorsAndWarnings('from a_index | sort st_x(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval st_x(null)', []); - testErrorsAndWarnings('row nullVar = null | eval st_x(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(integerField) > 0', [ + 'EVAL does not support function median_absolute_deviation', + ]); - describe('st_y', () => { - testErrorsAndWarnings('row var = st_y(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row st_y(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = st_y(to_geopoint("a"))', []); + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(counterIntegerField)', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(counterIntegerField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings( + 'from a_index | eval median_absolute_deviation(counterIntegerField)', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings( + 'from a_index | eval median_absolute_deviation(counterIntegerField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); - testErrorsAndWarnings('row var = st_y("a")', [ - 'Argument of [st_y] must be [cartesian_point], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | eval var = median_absolute_deviation(doubleField)', [ + 'EVAL does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('row var = st_y(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row st_y(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = st_y(to_cartesianpoint("a"))', []); - testErrorsAndWarnings('from a_index | eval var = st_y(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval st_y(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval var = st_y(to_geopoint(stringField))', []); + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(doubleField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); - testErrorsAndWarnings('from a_index | eval st_y(stringField)', [ - 'Argument of [st_y] must be [cartesian_point], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(doubleField)', [ + 'EVAL does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval st_y(geoPointField, extraArg)', [ - 'Error: [st_y] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(doubleField) > 0', [ + 'EVAL does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval var = st_y(*)', [ - 'Using wildcards (*) in st_y is not allowed', + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(unsignedLongField)', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(unsignedLongField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(unsignedLongField)', [ + 'EVAL does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval var = st_y(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval st_y(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval var = st_y(to_cartesianpoint(stringField))', []); + testErrorsAndWarnings( + 'from a_index | eval median_absolute_deviation(unsignedLongField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); - testErrorsAndWarnings('from a_index | eval st_y(cartesianPointField, extraArg)', [ - 'Error: [st_y] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval var = median_absolute_deviation(longField)', [ + 'EVAL does not support function median_absolute_deviation', + ]); + + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(longField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(longField)', [ + 'EVAL does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | sort st_y(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(longField) > 0', [ + 'EVAL does not support function median_absolute_deviation', + ]); testErrorsAndWarnings( - 'row var = st_y(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', - [] + 'from a_index | eval var = median_absolute_deviation(counterLongField)', + ['EVAL does not support function median_absolute_deviation'] ); - testErrorsAndWarnings('row var = st_y(to_geopoint(to_geopoint("POINT (30 10)")))', []); + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(counterLongField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); - testErrorsAndWarnings('row var = st_y(true)', [ - 'Argument of [st_y] must be [cartesian_point], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(counterLongField)', [ + 'EVAL does not support function median_absolute_deviation', ]); testErrorsAndWarnings( - 'from a_index | eval var = st_y(to_cartesianpoint(cartesianPointField))', - [] + 'from a_index | eval median_absolute_deviation(counterLongField) > 0', + ['EVAL does not support function median_absolute_deviation'] ); - testErrorsAndWarnings('from a_index | eval st_y(booleanField)', [ - 'Argument of [st_y] must be [cartesian_point], found value [booleanField] type [boolean]', + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(counterDoubleField)', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = median_absolute_deviation(counterDoubleField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings('from a_index | eval median_absolute_deviation(counterDoubleField)', [ + 'EVAL does not support function median_absolute_deviation', ]); - testErrorsAndWarnings('from a_index | eval var = st_y(to_geopoint(geoPointField))', []); - testErrorsAndWarnings('from a_index | sort st_y(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval st_y(null)', []); - testErrorsAndWarnings('row nullVar = null | eval st_y(nullVar)', []); + testErrorsAndWarnings( + 'from a_index | eval median_absolute_deviation(counterDoubleField) > 0', + ['EVAL does not support function median_absolute_deviation'] + ); + + testErrorsAndWarnings('from a_index | stats median_absolute_deviation(null)', []); + testErrorsAndWarnings('row nullVar = null | stats median_absolute_deviation(nullVar)', []); }); + describe('max', () => { + testErrorsAndWarnings('from a_index | stats var = max(doubleField)', []); + testErrorsAndWarnings('from a_index | stats max(doubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(max(doubleField))', []); + testErrorsAndWarnings('from a_index | stats round(max(doubleField))', []); - describe('starts_with', () => { - testErrorsAndWarnings('row var = starts_with("a", "a")', []); - testErrorsAndWarnings('row starts_with("a", "a")', []); - testErrorsAndWarnings('row var = starts_with(to_string("a"), to_string("a"))', []); + testErrorsAndWarnings( + 'from a_index | stats var = round(max(doubleField)) + max(doubleField)', + [] + ); - testErrorsAndWarnings('row var = starts_with(5, 5)', [ - 'Argument of [starts_with] must be [string], found value [5] type [number]', - 'Argument of [starts_with] must be [string], found value [5] type [number]', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(max(doubleField)) + max(doubleField)', + [] + ); + testErrorsAndWarnings('from a_index | stats max(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats var0 = max(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), max(doubleField / 2)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = max(doubleField / 2)', + [] + ); + testErrorsAndWarnings('from a_index | stats var0 = max(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), max(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = max(doubleField)', []); + testErrorsAndWarnings( + 'from a_index | stats max(doubleField) by round(doubleField / 2)', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = starts_with(stringField, stringField)', + 'from a_index | stats var0 = max(doubleField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | eval starts_with(stringField, stringField)', []); testErrorsAndWarnings( - 'from a_index | eval var = starts_with(to_string(stringField), to_string(stringField))', + 'from a_index | stats avg(doubleField), max(doubleField) by round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('from a_index | eval starts_with(numberField, numberField)', [ - 'Argument of [starts_with] must be [string], found value [numberField] type [number]', - 'Argument of [starts_with] must be [string], found value [numberField] type [number]', - ]); - testErrorsAndWarnings( - 'from a_index | eval starts_with(stringField, stringField, extraArg)', - ['Error: [starts_with] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = max(doubleField) by var1 = round(doubleField / 2), ipField', + [] ); - testErrorsAndWarnings('from a_index | sort starts_with(stringField, stringField)', []); - testErrorsAndWarnings('row var = starts_with(to_string(true), to_string(true))', []); - - testErrorsAndWarnings('row var = starts_with(true, true)', [ - 'Argument of [starts_with] must be [string], found value [true] type [boolean]', - 'Argument of [starts_with] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), max(doubleField) by round(doubleField / 2), doubleField / 2', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = starts_with(to_string(booleanField), to_string(booleanField))', + 'from a_index | stats avg(doubleField), var0 = max(doubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | eval starts_with(booleanField, booleanField)', [ - 'Argument of [starts_with] must be [string], found value [booleanField] type [boolean]', - 'Argument of [starts_with] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | stats var = max(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", ]); - testErrorsAndWarnings('from a_index | eval starts_with(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval starts_with(nullVar, nullVar)', []); - }); - describe('substring', () => { - testErrorsAndWarnings('row var = substring("a", 5, 5)', []); - testErrorsAndWarnings('row var = substring("a", 5)', []); - testErrorsAndWarnings('row substring("a", 5, 5)', []); - testErrorsAndWarnings('row substring("a", 5)', []); + testErrorsAndWarnings('from a_index | stats max(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); - testErrorsAndWarnings( - 'row var = substring(to_string("a"), to_integer("a"), to_integer("a"))', - [] - ); + testErrorsAndWarnings('from a_index | stats max(cartesianPointField)', [ + 'Argument of [max] must be [double], found value [cartesianPointField] type [cartesian_point]', + ]); - testErrorsAndWarnings('row var = substring(5, "a", "a")', [ - 'Argument of [substring] must be [string], found value [5] type [number]', - 'Argument of [substring] must be [number], found value ["a"] type [string]', - 'Argument of [substring] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | stats var = max(*)', [ + 'Using wildcards (*) in max is not allowed', ]); + testErrorsAndWarnings('from a_index | stats var = max(longField)', []); + testErrorsAndWarnings('from a_index | stats max(longField)', []); + testErrorsAndWarnings('from a_index | stats var = round(max(longField))', []); + testErrorsAndWarnings('from a_index | stats round(max(longField))', []); testErrorsAndWarnings( - 'from a_index | where length(substring(stringField, numberField, numberField)) > 0', + 'from a_index | stats var = round(max(longField)) + max(longField)', [] ); + testErrorsAndWarnings('from a_index | stats round(max(longField)) + max(longField)', []); + testErrorsAndWarnings('from a_index | stats var0 = max(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), max(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = max(longField)', []); + testErrorsAndWarnings('from a_index | stats max(longField) by round(doubleField / 2)', []); testErrorsAndWarnings( - 'from a_index | where length(substring(numberField, stringField, stringField)) > 0', - [ - 'Argument of [substring] must be [string], found value [numberField] type [number]', - 'Argument of [substring] must be [number], found value [stringField] type [string]', - 'Argument of [substring] must be [number], found value [stringField] type [string]', - ] + 'from a_index | stats var0 = max(longField) by var1 = round(doubleField / 2)', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = substring(stringField, numberField, numberField)', + 'from a_index | stats avg(doubleField), max(longField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval substring(stringField, numberField, numberField)', + 'from a_index | stats avg(doubleField), var0 = max(longField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = substring(to_string(stringField), to_integer(stringField), to_integer(stringField))', + 'from a_index | stats avg(doubleField), max(longField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval substring(numberField, stringField, stringField)', - [ - 'Argument of [substring] must be [string], found value [numberField] type [number]', - 'Argument of [substring] must be [number], found value [stringField] type [string]', - 'Argument of [substring] must be [number], found value [stringField] type [string]', - ] + 'from a_index | stats avg(doubleField), var0 = max(longField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); + testErrorsAndWarnings('from a_index | stats var = max(integerField)', []); + testErrorsAndWarnings('from a_index | stats max(integerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(max(integerField))', []); + testErrorsAndWarnings('from a_index | stats round(max(integerField))', []); + testErrorsAndWarnings( - 'from a_index | eval substring(stringField, numberField, numberField, extraArg)', - ['Error: [substring] function expects no more than 3 arguments, got 4.'] + 'from a_index | stats var = round(max(integerField)) + max(integerField)', + [] ); testErrorsAndWarnings( - 'from a_index | sort substring(stringField, numberField, numberField)', + 'from a_index | stats round(max(integerField)) + max(integerField)', + [] + ); + testErrorsAndWarnings('from a_index | stats var0 = max(integerField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), max(integerField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = max(integerField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | stats max(integerField) by round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | sort substring(stringField, numberField)', []); testErrorsAndWarnings( - 'row var = substring(to_string(true), to_integer(true), to_integer(true))', + 'from a_index | stats var0 = max(integerField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('row var = substring(true, true, true)', [ - 'Argument of [substring] must be [string], found value [true] type [boolean]', - 'Argument of [substring] must be [number], found value [true] type [boolean]', - 'Argument of [substring] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), max(integerField) by round(doubleField / 2), ipField', + [] + ); testErrorsAndWarnings( - 'from a_index | where length(substring(booleanField, booleanField, booleanField)) > 0', - [ - 'Argument of [substring] must be [string], found value [booleanField] type [boolean]', - 'Argument of [substring] must be [number], found value [booleanField] type [boolean]', - 'Argument of [substring] must be [number], found value [booleanField] type [boolean]', - ] + 'from a_index | stats avg(doubleField), var0 = max(integerField) by var1 = round(doubleField / 2), ipField', + [] ); testErrorsAndWarnings( - 'from a_index | eval var = substring(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))', + 'from a_index | stats avg(doubleField), max(integerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval substring(booleanField, booleanField, booleanField)', - [ - 'Argument of [substring] must be [string], found value [booleanField] type [boolean]', - 'Argument of [substring] must be [number], found value [booleanField] type [boolean]', - 'Argument of [substring] must be [number], found value [booleanField] type [boolean]', - ] + 'from a_index | stats avg(doubleField), var0 = max(integerField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); - testErrorsAndWarnings('from a_index | eval substring(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval substring(nullVar, nullVar, nullVar)', []); - }); - describe('tan', () => { - testErrorsAndWarnings('row var = tan(5)', []); - testErrorsAndWarnings('row tan(5)', []); - testErrorsAndWarnings('row var = tan(to_integer("a"))', []); + testErrorsAndWarnings('from a_index | stats var = max(dateField)', []); + testErrorsAndWarnings('from a_index | stats max(dateField)', []); + testErrorsAndWarnings('from a_index | stats var = max(booleanField)', []); + testErrorsAndWarnings('from a_index | stats max(booleanField)', []); + testErrorsAndWarnings('from a_index | stats var = max(ipField)', []); + testErrorsAndWarnings('from a_index | stats max(ipField)', []); - testErrorsAndWarnings('row var = tan("a")', [ - 'Argument of [tan] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | sort max(doubleField)', [ + 'SORT does not support function max', ]); - testErrorsAndWarnings('from a_index | where tan(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where max(doubleField)', [ + 'WHERE does not support function max', + ]); - testErrorsAndWarnings('from a_index | where tan(stringField) > 0', [ - 'Argument of [tan] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where max(doubleField) > 0', [ + 'WHERE does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = tan(numberField)', []); - testErrorsAndWarnings('from a_index | eval tan(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = tan(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | where max(longField)', [ + 'WHERE does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval tan(stringField)', [ - 'Argument of [tan] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where max(longField) > 0', [ + 'WHERE does not support function max', ]); - testErrorsAndWarnings('from a_index | eval tan(numberField, extraArg)', [ - 'Error: [tan] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | where max(integerField)', [ + 'WHERE does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = tan(*)', [ - 'Using wildcards (*) in tan is not allowed', + testErrorsAndWarnings('from a_index | where max(integerField) > 0', [ + 'WHERE does not support function max', ]); - testErrorsAndWarnings('from a_index | sort tan(numberField)', []); - testErrorsAndWarnings('row var = tan(to_integer(true))', []); + testErrorsAndWarnings('from a_index | where max(dateField)', [ + 'WHERE does not support function max', + ]); - testErrorsAndWarnings('row var = tan(true)', [ - 'Argument of [tan] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | where max(dateField) > 0', [ + 'WHERE does not support function max', ]); - testErrorsAndWarnings('from a_index | where tan(booleanField) > 0', [ - 'Argument of [tan] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where max(booleanField)', [ + 'WHERE does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = tan(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | where max(booleanField) > 0', [ + 'WHERE does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval tan(booleanField)', [ - 'Argument of [tan] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where max(ipField)', [ + 'WHERE does not support function max', ]); - testErrorsAndWarnings('from a_index | eval tan(null)', []); - testErrorsAndWarnings('row nullVar = null | eval tan(nullVar)', []); - }); - describe('tanh', () => { - testErrorsAndWarnings('row var = tanh(5)', []); - testErrorsAndWarnings('row tanh(5)', []); - testErrorsAndWarnings('row var = tanh(to_integer("a"))', []); + testErrorsAndWarnings('from a_index | where max(ipField) > 0', [ + 'WHERE does not support function max', + ]); - testErrorsAndWarnings('row var = tanh("a")', [ - 'Argument of [tanh] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | eval var = max(doubleField)', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | where tanh(numberField) > 0', []); + testErrorsAndWarnings('from a_index | eval var = max(doubleField) > 0', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('from a_index | where tanh(stringField) > 0', [ - 'Argument of [tanh] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval max(doubleField)', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = tanh(numberField)', []); - testErrorsAndWarnings('from a_index | eval tanh(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = tanh(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | eval max(doubleField) > 0', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval tanh(stringField)', [ - 'Argument of [tanh] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval var = max(longField)', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | eval tanh(numberField, extraArg)', [ - 'Error: [tanh] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval var = max(longField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = tanh(*)', [ - 'Using wildcards (*) in tanh is not allowed', + testErrorsAndWarnings('from a_index | eval max(longField)', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | sort tanh(numberField)', []); - testErrorsAndWarnings('row var = tanh(to_integer(true))', []); + testErrorsAndWarnings('from a_index | eval max(longField) > 0', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('row var = tanh(true)', [ - 'Argument of [tanh] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = max(integerField)', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | where tanh(booleanField) > 0', [ - 'Argument of [tanh] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = max(integerField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = tanh(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval max(integerField)', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval tanh(booleanField)', [ - 'Argument of [tanh] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval max(integerField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | eval tanh(null)', []); - testErrorsAndWarnings('row nullVar = null | eval tanh(nullVar)', []); - }); - describe('tau', () => { - testErrorsAndWarnings('row var = tau()', []); - testErrorsAndWarnings('row tau()', []); - testErrorsAndWarnings('from a_index | where tau() > 0', []); - testErrorsAndWarnings('from a_index | eval var = tau()', []); - testErrorsAndWarnings('from a_index | eval tau()', []); + testErrorsAndWarnings('from a_index | eval var = max(dateField)', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval tau(extraArg)', [ - 'Error: [tau] function expects exactly 0 arguments, got 1.', + testErrorsAndWarnings('from a_index | eval var = max(dateField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | sort tau()', []); - testErrorsAndWarnings('row nullVar = null | eval tau()', []); - }); + testErrorsAndWarnings('from a_index | eval max(dateField)', [ + 'EVAL does not support function max', + ]); - describe('to_boolean', () => { - testErrorsAndWarnings('row var = to_boolean("a")', []); - testErrorsAndWarnings('row to_boolean("a")', []); - testErrorsAndWarnings('row var = to_bool("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_boolean(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_boolean(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_bool(stringField)', []); + testErrorsAndWarnings('from a_index | eval max(dateField) > 0', [ + 'EVAL does not support function max', + ]); + testErrorsAndWarnings('from a_index | eval var = max(booleanField)', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval var = to_boolean(*)', [ - 'Using wildcards (*) in to_boolean is not allowed', + testErrorsAndWarnings('from a_index | eval var = max(booleanField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | sort to_boolean(stringField)', []); - testErrorsAndWarnings('row var = to_boolean(true)', []); - testErrorsAndWarnings('row to_boolean(true)', []); - testErrorsAndWarnings('row var = to_bool(true)', []); - testErrorsAndWarnings('row var = to_boolean(to_boolean(true))', []); - testErrorsAndWarnings('row var = to_boolean(5)', []); - testErrorsAndWarnings('row to_boolean(5)', []); - testErrorsAndWarnings('row var = to_bool(5)', []); - testErrorsAndWarnings('row var = to_boolean(to_integer(true))', []); - testErrorsAndWarnings('row var = to_boolean(to_string(true))', []); + testErrorsAndWarnings('from a_index | eval max(booleanField)', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('row var = to_boolean(to_cartesianpoint("POINT (30 10)"))', [ - 'Argument of [to_boolean] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval max(booleanField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = to_boolean(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_boolean(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_bool(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_boolean(to_boolean(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = max(ipField)', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval to_boolean(cartesianPointField)', [ - 'Argument of [to_boolean] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval var = max(ipField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | eval var = to_boolean(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_boolean(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_bool(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_boolean(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = to_boolean(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval max(ipField)', [ + 'EVAL does not support function max', + ]); - testErrorsAndWarnings('from a_index | eval to_boolean(booleanField, extraArg)', [ - 'Error: [to_boolean] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval max(ipField) > 0', [ + 'EVAL does not support function max', ]); - testErrorsAndWarnings('from a_index | sort to_boolean(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_boolean(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_boolean(nullVar)', []); + testErrorsAndWarnings('from a_index | stats max(null)', []); + testErrorsAndWarnings('row nullVar = null | stats max(nullVar)', []); + testErrorsAndWarnings('from a_index | stats max("2022")', []); + testErrorsAndWarnings('from a_index | stats max(concat("20", "22"))', [ + 'Argument of [max] must be [double], found value [concat("20","22")] type [keyword]', + ]); }); - describe('to_cartesianpoint', () => { - testErrorsAndWarnings('row var = to_cartesianpoint("a")', []); - testErrorsAndWarnings('row to_cartesianpoint("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_cartesianpoint(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_cartesianpoint(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = to_cartesianpoint(*)', [ - 'Using wildcards (*) in to_cartesianpoint is not allowed', - ]); + describe('min', () => { + testErrorsAndWarnings('from a_index | stats var = min(doubleField)', []); + testErrorsAndWarnings('from a_index | stats min(doubleField)', []); + testErrorsAndWarnings('from a_index | stats var = round(min(doubleField))', []); + testErrorsAndWarnings('from a_index | stats round(min(doubleField))', []); - testErrorsAndWarnings('from a_index | sort to_cartesianpoint(stringField)', []); testErrorsAndWarnings( - 'row var = to_cartesianpoint(to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats var = round(min(doubleField)) + min(doubleField)', [] ); - testErrorsAndWarnings('row to_cartesianpoint(to_cartesianpoint("POINT (30 10)"))', []); testErrorsAndWarnings( - 'row var = to_cartesianpoint(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats round(min(doubleField)) + min(doubleField)', + [] + ); + testErrorsAndWarnings('from a_index | stats min(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats var0 = min(doubleField / 2)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), min(doubleField / 2)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = min(doubleField / 2)', + [] + ); + testErrorsAndWarnings('from a_index | stats var0 = min(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), min(doubleField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = min(doubleField)', []); + testErrorsAndWarnings( + 'from a_index | stats min(doubleField) by round(doubleField / 2)', [] ); - - testErrorsAndWarnings('row var = to_cartesianpoint(to_string(true))', []); - - testErrorsAndWarnings('row var = to_cartesianpoint(true)', [ - 'Argument of [to_cartesianpoint] must be [cartesian_point], found value [true] type [boolean]', - ]); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianpoint(cartesianPointField)', + 'from a_index | stats var0 = min(doubleField) by var1 = round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | eval to_cartesianpoint(cartesianPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))', + 'from a_index | stats avg(doubleField), min(doubleField) by round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('from a_index | eval to_cartesianpoint(booleanField)', [ - 'Argument of [to_cartesianpoint] must be [cartesian_point], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = min(doubleField) by var1 = round(doubleField / 2), ipField', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianpoint(to_string(booleanField))', + 'from a_index | stats avg(doubleField), min(doubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval to_cartesianpoint(cartesianPointField, extraArg)', - ['Error: [to_cartesianpoint] function expects exactly one argument, got 2.'] + 'from a_index | stats avg(doubleField), var0 = min(doubleField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); - testErrorsAndWarnings('from a_index | sort to_cartesianpoint(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval to_cartesianpoint(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_cartesianpoint(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | stats var = min(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); - describe('to_cartesianshape', () => { - testErrorsAndWarnings('row var = to_cartesianshape("a")', []); - testErrorsAndWarnings('row to_cartesianshape("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_cartesianshape(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_cartesianshape(stringField)', []); + testErrorsAndWarnings('from a_index | stats min(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ]); + + testErrorsAndWarnings('from a_index | stats min(cartesianPointField)', [ + 'Argument of [min] must be [double], found value [cartesianPointField] type [cartesian_point]', + ]); - testErrorsAndWarnings('from a_index | eval var = to_cartesianshape(*)', [ - 'Using wildcards (*) in to_cartesianshape is not allowed', + testErrorsAndWarnings('from a_index | stats var = min(*)', [ + 'Using wildcards (*) in min is not allowed', ]); - testErrorsAndWarnings('from a_index | sort to_cartesianshape(stringField)', []); + testErrorsAndWarnings('from a_index | stats var = min(longField)', []); + testErrorsAndWarnings('from a_index | stats min(longField)', []); + testErrorsAndWarnings('from a_index | stats var = round(min(longField))', []); + testErrorsAndWarnings('from a_index | stats round(min(longField))', []); testErrorsAndWarnings( - 'row var = to_cartesianshape(to_cartesianpoint("POINT (30 10)"))', + 'from a_index | stats var = round(min(longField)) + min(longField)', [] ); - testErrorsAndWarnings('row to_cartesianshape(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('from a_index | stats round(min(longField)) + min(longField)', []); + testErrorsAndWarnings('from a_index | stats var0 = min(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), min(longField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), var0 = min(longField)', []); + testErrorsAndWarnings('from a_index | stats min(longField) by round(doubleField / 2)', []); testErrorsAndWarnings( - 'row var = to_cartesianshape(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var0 = min(longField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'row var = to_cartesianshape(to_cartesianshape("POINT (30 10)"))', + 'from a_index | stats avg(doubleField), min(longField) by round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('row to_cartesianshape(to_cartesianshape("POINT (30 10)"))', []); testErrorsAndWarnings( - 'row var = to_cartesianshape(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats avg(doubleField), var0 = min(longField) by var1 = round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('row var = to_cartesianshape(to_string(true))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), min(longField) by round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('row var = to_cartesianshape(true)', [ - 'Argument of [to_cartesianshape] must be [cartesian_point], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = min(longField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); + + testErrorsAndWarnings('from a_index | stats var = min(integerField)', []); + testErrorsAndWarnings('from a_index | stats min(integerField)', []); + testErrorsAndWarnings('from a_index | stats var = round(min(integerField))', []); + testErrorsAndWarnings('from a_index | stats round(min(integerField))', []); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianshape(cartesianPointField)', + 'from a_index | stats var = round(min(integerField)) + min(integerField)', [] ); - testErrorsAndWarnings('from a_index | eval to_cartesianshape(cartesianPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianshape(to_cartesianpoint(cartesianPointField))', + 'from a_index | stats round(min(integerField)) + min(integerField)', + [] + ); + testErrorsAndWarnings('from a_index | stats var0 = min(integerField)', []); + testErrorsAndWarnings('from a_index | stats avg(doubleField), min(integerField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = min(integerField)', + [] + ); + testErrorsAndWarnings( + 'from a_index | stats min(integerField) by round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | eval to_cartesianshape(booleanField)', [ - 'Argument of [to_cartesianshape] must be [cartesian_point], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = min(integerField) by var1 = round(doubleField / 2)', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianshape(cartesianShapeField)', + 'from a_index | stats avg(doubleField), min(integerField) by round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('from a_index | eval to_cartesianshape(cartesianShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianshape(to_cartesianshape(cartesianPointField))', + 'from a_index | stats avg(doubleField), var0 = min(integerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | eval var = to_cartesianshape(to_string(booleanField))', + 'from a_index | stats avg(doubleField), min(integerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | eval to_cartesianshape(cartesianPointField, extraArg)', - ['Error: [to_cartesianshape] function expects exactly one argument, got 2.'] + 'from a_index | stats avg(doubleField), var0 = min(integerField) by var1 = round(doubleField / 2), doubleField / 2', + [] ); - testErrorsAndWarnings('from a_index | sort to_cartesianshape(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval to_cartesianshape(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_cartesianshape(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | stats var = min(dateField)', []); + testErrorsAndWarnings('from a_index | stats min(dateField)', []); + testErrorsAndWarnings('from a_index | stats var = min(booleanField)', []); + testErrorsAndWarnings('from a_index | stats min(booleanField)', []); + testErrorsAndWarnings('from a_index | stats var = min(ipField)', []); + testErrorsAndWarnings('from a_index | stats min(ipField)', []); - describe('to_datetime', () => { - testErrorsAndWarnings('row var = to_datetime("a")', []); - testErrorsAndWarnings('row to_datetime("a")', []); - testErrorsAndWarnings('row var = to_dt("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_datetime(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_datetime(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_dt(stringField)', []); + testErrorsAndWarnings('from a_index | sort min(doubleField)', [ + 'SORT does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval var = to_datetime(*)', [ - 'Using wildcards (*) in to_datetime is not allowed', + testErrorsAndWarnings('from a_index | where min(doubleField)', [ + 'WHERE does not support function min', ]); - testErrorsAndWarnings('from a_index | sort to_datetime(stringField)', []); - testErrorsAndWarnings('row var = to_datetime(now())', []); - testErrorsAndWarnings('row to_datetime(now())', []); - testErrorsAndWarnings('row var = to_dt(now())', []); - testErrorsAndWarnings('row var = to_datetime(to_datetime(now()))', []); - testErrorsAndWarnings('row var = to_datetime(5)', []); - testErrorsAndWarnings('row to_datetime(5)', []); - testErrorsAndWarnings('row var = to_dt(5)', []); - testErrorsAndWarnings('row var = to_datetime(to_integer(true))', []); - testErrorsAndWarnings('row var = to_datetime(to_string(true))', []); + testErrorsAndWarnings('from a_index | where min(doubleField) > 0', [ + 'WHERE does not support function min', + ]); - testErrorsAndWarnings('row var = to_datetime(true)', [ - 'Argument of [to_datetime] must be [date], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | where min(longField)', [ + 'WHERE does not support function min', ]); - testErrorsAndWarnings('from a_index | eval var = to_datetime(dateField)', []); - testErrorsAndWarnings('from a_index | eval to_datetime(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_dt(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_datetime(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | where min(longField) > 0', [ + 'WHERE does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval to_datetime(booleanField)', [ - 'Argument of [to_datetime] must be [date], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where min(integerField)', [ + 'WHERE does not support function min', ]); - testErrorsAndWarnings('from a_index | eval var = to_datetime(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_datetime(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_dt(numberField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = to_datetime(to_integer(booleanField))', - [] - ); - testErrorsAndWarnings('from a_index | eval var = to_datetime(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | where min(integerField) > 0', [ + 'WHERE does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval to_datetime(dateField, extraArg)', [ - 'Error: [to_datetime] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | where min(dateField)', [ + 'WHERE does not support function min', ]); - testErrorsAndWarnings('from a_index | sort to_datetime(dateField)', []); - testErrorsAndWarnings('from a_index | eval to_datetime(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_datetime(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | where min(dateField) > 0', [ + 'WHERE does not support function min', + ]); + testErrorsAndWarnings('from a_index | where min(booleanField)', [ + 'WHERE does not support function min', + ]); - describe('to_degrees', () => { - testErrorsAndWarnings('row var = to_degrees(5)', []); - testErrorsAndWarnings('row to_degrees(5)', []); - testErrorsAndWarnings('row var = to_degrees(to_integer("a"))', []); + testErrorsAndWarnings('from a_index | where min(booleanField) > 0', [ + 'WHERE does not support function min', + ]); - testErrorsAndWarnings('row var = to_degrees("a")', [ - 'Argument of [to_degrees] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | where min(ipField)', [ + 'WHERE does not support function min', ]); - testErrorsAndWarnings('from a_index | where to_degrees(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where min(ipField) > 0', [ + 'WHERE does not support function min', + ]); - testErrorsAndWarnings('from a_index | where to_degrees(stringField) > 0', [ - 'Argument of [to_degrees] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval var = min(doubleField)', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | eval var = to_degrees(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_degrees(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_degrees(to_integer(stringField))', []); + testErrorsAndWarnings('from a_index | eval var = min(doubleField) > 0', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval to_degrees(stringField)', [ - 'Argument of [to_degrees] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval min(doubleField)', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | eval to_degrees(numberField, extraArg)', [ - 'Error: [to_degrees] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval min(doubleField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | eval var = to_degrees(*)', [ - 'Using wildcards (*) in to_degrees is not allowed', + testErrorsAndWarnings('from a_index | eval var = min(longField)', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | sort to_degrees(numberField)', []); - testErrorsAndWarnings('row var = to_degrees(to_integer(true))', []); + testErrorsAndWarnings('from a_index | eval var = min(longField) > 0', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('row var = to_degrees(true)', [ - 'Argument of [to_degrees] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval min(longField)', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | where to_degrees(booleanField) > 0', [ - 'Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval min(longField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | eval var = to_degrees(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = min(integerField)', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval to_degrees(booleanField)', [ - 'Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = min(integerField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | eval to_degrees(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_degrees(nullVar)', []); - }); - describe('to_double', () => { - testErrorsAndWarnings('row var = to_double("a")', []); - testErrorsAndWarnings('row to_double("a")', []); - testErrorsAndWarnings('row var = to_dbl("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_double(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_double(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_dbl(stringField)', []); + testErrorsAndWarnings('from a_index | eval min(integerField)', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval var = to_double(*)', [ - 'Using wildcards (*) in to_double is not allowed', + testErrorsAndWarnings('from a_index | eval min(integerField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | sort to_double(stringField)', []); - testErrorsAndWarnings('row var = to_double(true)', []); - testErrorsAndWarnings('row to_double(true)', []); - testErrorsAndWarnings('row var = to_dbl(true)', []); - testErrorsAndWarnings('row var = to_double(to_boolean(true))', []); - testErrorsAndWarnings('row var = to_double(5)', []); - testErrorsAndWarnings('row to_double(5)', []); - testErrorsAndWarnings('row var = to_dbl(5)', []); - testErrorsAndWarnings('row var = to_double(to_integer(true))', []); - testErrorsAndWarnings('row var = to_double(now())', []); - testErrorsAndWarnings('row to_double(now())', []); - testErrorsAndWarnings('row var = to_dbl(now())', []); - testErrorsAndWarnings('row var = to_double(to_datetime(now()))', []); - testErrorsAndWarnings('row var = to_double(to_string(true))', []); + testErrorsAndWarnings('from a_index | eval var = min(dateField)', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('row var = to_double(to_cartesianpoint("POINT (30 10)"))', [ - 'Argument of [to_double] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval var = min(dateField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | where to_double(booleanField) > 0', []); + testErrorsAndWarnings('from a_index | eval min(dateField)', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('from a_index | where to_double(cartesianPointField) > 0', [ - 'Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval min(dateField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | where to_double(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where to_double(dateField) > 0', []); - testErrorsAndWarnings('from a_index | where to_double(stringField) > 0', []); - testErrorsAndWarnings('from a_index | eval var = to_double(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_double(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_dbl(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_double(to_boolean(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = min(booleanField)', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval to_double(cartesianPointField)', [ - 'Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval var = min(booleanField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | eval var = to_double(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_double(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_dbl(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_double(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = to_double(dateField)', []); - testErrorsAndWarnings('from a_index | eval to_double(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_dbl(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_double(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = to_double(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval min(booleanField)', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval to_double(booleanField, extraArg)', [ - 'Error: [to_double] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval min(booleanField) > 0', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | sort to_double(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_double(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_double(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | eval var = min(ipField)', [ + 'EVAL does not support function min', + ]); - describe('to_geopoint', () => { - testErrorsAndWarnings('row var = to_geopoint("a")', []); - testErrorsAndWarnings('row to_geopoint("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_geopoint(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_geopoint(stringField)', []); + testErrorsAndWarnings('from a_index | eval var = min(ipField) > 0', [ + 'EVAL does not support function min', + ]); - testErrorsAndWarnings('from a_index | eval var = to_geopoint(*)', [ - 'Using wildcards (*) in to_geopoint is not allowed', + testErrorsAndWarnings('from a_index | eval min(ipField)', [ + 'EVAL does not support function min', ]); - testErrorsAndWarnings('from a_index | sort to_geopoint(stringField)', []); - testErrorsAndWarnings('row var = to_geopoint(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row to_geopoint(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('from a_index | eval min(ipField) > 0', [ + 'EVAL does not support function min', + ]); + + testErrorsAndWarnings('from a_index | stats min(null)', []); + testErrorsAndWarnings('row nullVar = null | stats min(nullVar)', []); + testErrorsAndWarnings('from a_index | stats min("2022")', []); + testErrorsAndWarnings('from a_index | stats min(concat("20", "22"))', [ + 'Argument of [min] must be [double], found value [concat("20","22")] type [keyword]', + ]); + }); + + describe('count', () => { + testErrorsAndWarnings('from a_index | stats var = count(textField)', []); + testErrorsAndWarnings('from a_index | stats count(textField)', []); + testErrorsAndWarnings('from a_index | stats var = round(count(textField))', []); + testErrorsAndWarnings('from a_index | stats round(count(textField))', []); + testErrorsAndWarnings( - 'row var = to_geopoint(to_geopoint(to_geopoint("POINT (30 10)")))', + 'from a_index | stats var = round(count(textField)) + count(textField)', [] ); - testErrorsAndWarnings('row var = to_geopoint(to_string(true))', []); - - testErrorsAndWarnings('row var = to_geopoint(true)', [ - 'Argument of [to_geopoint] must be [geo_point], found value [true] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | eval var = to_geopoint(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval to_geopoint(geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_geopoint(to_geopoint(geoPointField))', + 'from a_index | stats round(count(textField)) + count(textField)', [] ); - testErrorsAndWarnings('from a_index | eval to_geopoint(booleanField)', [ - 'Argument of [to_geopoint] must be [geo_point], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | sort count(textField)', [ + 'SORT does not support function count', ]); - testErrorsAndWarnings('from a_index | eval var = to_geopoint(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | where count(textField)', [ + 'WHERE does not support function count', + ]); + + testErrorsAndWarnings('from a_index | where count(textField) > 0', [ + 'WHERE does not support function count', + ]); + + testErrorsAndWarnings('from a_index | eval var = count(textField)', [ + 'EVAL does not support function count', + ]); + + testErrorsAndWarnings('from a_index | eval var = count(textField) > 0', [ + 'EVAL does not support function count', + ]); + + testErrorsAndWarnings('from a_index | eval count(textField)', [ + 'EVAL does not support function count', + ]); - testErrorsAndWarnings('from a_index | eval to_geopoint(geoPointField, extraArg)', [ - 'Error: [to_geopoint] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval count(textField) > 0', [ + 'EVAL does not support function count', ]); - testErrorsAndWarnings('from a_index | sort to_geopoint(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval to_geopoint(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_geopoint(nullVar)', []); + testErrorsAndWarnings('from a_index | stats count(null)', []); + testErrorsAndWarnings('row nullVar = null | stats count(nullVar)', []); }); - describe('to_geoshape', () => { - testErrorsAndWarnings('row var = to_geoshape("a")', []); - testErrorsAndWarnings('row to_geoshape("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_geoshape(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_geoshape(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = to_geoshape(*)', [ - 'Using wildcards (*) in to_geoshape is not allowed', - ]); + describe('count_distinct', () => { + testErrorsAndWarnings( + 'from a_index | stats count_distinct(null, null, null, null, null, null, null, null)', + [] + ); - testErrorsAndWarnings('from a_index | sort to_geoshape(stringField)', []); - testErrorsAndWarnings('row var = to_geoshape(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row to_geoshape(to_geopoint("POINT (30 10)"))', []); testErrorsAndWarnings( - 'row var = to_geoshape(to_geopoint(to_geopoint("POINT (30 10)")))', + 'row nullVar = null | stats count_distinct(nullVar, nullVar, nullVar, nullVar, nullVar, nullVar, nullVar, nullVar)', [] ); - testErrorsAndWarnings('row var = to_geoshape(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row to_geoshape(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings( - 'row var = to_geoshape(to_geoshape(to_geopoint("POINT (30 10)")))', + 'from a_index | stats var = count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', [] ); - testErrorsAndWarnings('row var = to_geoshape(to_string(true))', []); - testErrorsAndWarnings('row var = to_geoshape(true)', [ - 'Argument of [to_geoshape] must be [geo_point], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = to_geoshape(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval to_geoshape(geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_geoshape(to_geopoint(geoPointField))', + 'from a_index | stats var = round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField))', [] ); - testErrorsAndWarnings('from a_index | eval to_geoshape(booleanField)', [ - 'Argument of [to_geoshape] must be [geo_point], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = to_geoshape(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval to_geoshape(geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_geoshape(to_geoshape(geoPointField))', + 'from a_index | stats var = round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)) + count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', [] ); - testErrorsAndWarnings('from a_index | eval var = to_geoshape(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval to_geoshape(geoPointField, extraArg)', [ - 'Error: [to_geoshape] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)) + count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', + [] + ); - testErrorsAndWarnings('from a_index | sort to_geoshape(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval to_geoshape(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_geoshape(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | sort count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', + ['SORT does not support function count_distinct'] + ); - describe('to_integer', () => { - testErrorsAndWarnings('row var = to_integer("a")', []); - testErrorsAndWarnings('row to_integer("a")', []); - testErrorsAndWarnings('row var = to_int("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_integer(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_integer(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_int(stringField)', []); + testErrorsAndWarnings( + 'from a_index | where count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', + ['WHERE does not support function count_distinct'] + ); - testErrorsAndWarnings('from a_index | eval var = to_integer(*)', [ - 'Using wildcards (*) in to_integer is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | where count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField) > 0', + ['WHERE does not support function count_distinct'] + ); - testErrorsAndWarnings('from a_index | sort to_integer(stringField)', []); - testErrorsAndWarnings('row var = to_integer(true)', []); - testErrorsAndWarnings('row to_integer(true)', []); - testErrorsAndWarnings('row var = to_int(true)', []); - testErrorsAndWarnings('row var = to_integer(to_boolean(true))', []); - testErrorsAndWarnings('row var = to_integer(5)', []); - testErrorsAndWarnings('row to_integer(5)', []); - testErrorsAndWarnings('row var = to_int(5)', []); - testErrorsAndWarnings('row var = to_integer(to_integer(true))', []); - testErrorsAndWarnings('row var = to_integer(now())', []); - testErrorsAndWarnings('row to_integer(now())', []); - testErrorsAndWarnings('row var = to_int(now())', []); - testErrorsAndWarnings('row var = to_integer(to_datetime(now()))', []); - testErrorsAndWarnings('row var = to_integer(to_string(true))', []); + testErrorsAndWarnings( + 'from a_index | eval var = count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', + ['EVAL does not support function count_distinct'] + ); - testErrorsAndWarnings('row var = to_integer(to_cartesianpoint("POINT (30 10)"))', [ - 'Argument of [to_integer] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', - ]); + testErrorsAndWarnings( + 'from a_index | eval var = count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField) > 0', + ['EVAL does not support function count_distinct'] + ); - testErrorsAndWarnings('from a_index | where to_integer(booleanField) > 0', []); + testErrorsAndWarnings( + 'from a_index | eval count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField)', + ['EVAL does not support function count_distinct'] + ); - testErrorsAndWarnings('from a_index | where to_integer(cartesianPointField) > 0', [ - 'Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); + testErrorsAndWarnings( + 'from a_index | eval count_distinct(textField, integerField, counterIntegerField, doubleField, unsignedLongField, longField, counterLongField, counterDoubleField) > 0', + ['EVAL does not support function count_distinct'] + ); + }); - testErrorsAndWarnings('from a_index | where to_integer(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where to_integer(dateField) > 0', []); - testErrorsAndWarnings('from a_index | where to_integer(stringField) > 0', []); - testErrorsAndWarnings('from a_index | eval var = to_integer(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_integer(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_int(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_integer(to_boolean(booleanField))', []); + describe('st_centroid_agg', () => { + testErrorsAndWarnings( + 'from a_index | stats var = st_centroid_agg(cartesianPointField)', + [] + ); + testErrorsAndWarnings('from a_index | stats st_centroid_agg(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval to_integer(cartesianPointField)', [ - 'Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings('from a_index | stats var = st_centroid_agg(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", ]); - testErrorsAndWarnings('from a_index | eval var = to_integer(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_integer(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_int(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_integer(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = to_integer(dateField)', []); - testErrorsAndWarnings('from a_index | eval to_integer(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_int(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_integer(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = to_integer(to_string(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval to_integer(booleanField, extraArg)', [ - 'Error: [to_integer] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | stats st_centroid_agg(avg(integerField))', [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", ]); - testErrorsAndWarnings('from a_index | sort to_integer(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_integer(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_integer(nullVar)', []); - }); - - describe('to_ip', () => { - testErrorsAndWarnings('row var = to_ip("a")', []); - testErrorsAndWarnings('row to_ip("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_ip(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_ip(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = to_ip(*)', [ - 'Using wildcards (*) in to_ip is not allowed', + testErrorsAndWarnings('from a_index | stats st_centroid_agg(booleanField)', [ + 'Argument of [st_centroid_agg] must be [cartesian_point], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | sort to_ip(stringField)', []); - testErrorsAndWarnings('row var = to_ip(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row to_ip(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = to_ip(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = to_ip(to_string(true))', []); - - testErrorsAndWarnings('row var = to_ip(true)', [ - 'Argument of [to_ip] must be [ip], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | stats var = st_centroid_agg(*)', [ + 'Using wildcards (*) in st_centroid_agg is not allowed', ]); - testErrorsAndWarnings('from a_index | eval var = to_ip(ipField)', []); - testErrorsAndWarnings('from a_index | eval to_ip(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ip(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | stats var = st_centroid_agg(geoPointField)', []); + testErrorsAndWarnings('from a_index | stats st_centroid_agg(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval to_ip(booleanField)', [ - 'Argument of [to_ip] must be [ip], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | sort st_centroid_agg(cartesianPointField)', [ + 'SORT does not support function st_centroid_agg', ]); - testErrorsAndWarnings('from a_index | eval var = to_ip(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | where st_centroid_agg(cartesianPointField)', [ + 'WHERE does not support function st_centroid_agg', + ]); - testErrorsAndWarnings('from a_index | eval to_ip(ipField, extraArg)', [ - 'Error: [to_ip] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | where st_centroid_agg(cartesianPointField) > 0', [ + 'WHERE does not support function st_centroid_agg', ]); - testErrorsAndWarnings('from a_index | sort to_ip(ipField)', []); - testErrorsAndWarnings('from a_index | eval to_ip(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_ip(nullVar)', []); - }); + testErrorsAndWarnings('from a_index | where st_centroid_agg(geoPointField)', [ + 'WHERE does not support function st_centroid_agg', + ]); - describe('to_long', () => { - testErrorsAndWarnings('row var = to_long("a")', []); - testErrorsAndWarnings('row to_long("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_long(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_long(stringField)', []); + testErrorsAndWarnings('from a_index | where st_centroid_agg(geoPointField) > 0', [ + 'WHERE does not support function st_centroid_agg', + ]); - testErrorsAndWarnings('from a_index | eval var = to_long(*)', [ - 'Using wildcards (*) in to_long is not allowed', + testErrorsAndWarnings('from a_index | eval var = st_centroid_agg(cartesianPointField)', [ + 'EVAL does not support function st_centroid_agg', ]); - testErrorsAndWarnings('from a_index | sort to_long(stringField)', []); - testErrorsAndWarnings('row var = to_long(true)', []); - testErrorsAndWarnings('row to_long(true)', []); - testErrorsAndWarnings('row var = to_long(to_boolean(true))', []); - testErrorsAndWarnings('row var = to_long(5)', []); - testErrorsAndWarnings('row to_long(5)', []); - testErrorsAndWarnings('row var = to_long(to_integer(true))', []); - testErrorsAndWarnings('row var = to_long(now())', []); - testErrorsAndWarnings('row to_long(now())', []); - testErrorsAndWarnings('row var = to_long(to_datetime(now()))', []); - testErrorsAndWarnings('row var = to_long(to_string(true))', []); + testErrorsAndWarnings( + 'from a_index | eval var = st_centroid_agg(cartesianPointField) > 0', + ['EVAL does not support function st_centroid_agg'] + ); - testErrorsAndWarnings('row var = to_long(to_cartesianpoint("POINT (30 10)"))', [ - 'Argument of [to_long] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval st_centroid_agg(cartesianPointField)', [ + 'EVAL does not support function st_centroid_agg', ]); - testErrorsAndWarnings('from a_index | where to_long(booleanField) > 0', []); - - testErrorsAndWarnings('from a_index | where to_long(cartesianPointField) > 0', [ - 'Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval st_centroid_agg(cartesianPointField) > 0', [ + 'EVAL does not support function st_centroid_agg', ]); - testErrorsAndWarnings('from a_index | where to_long(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where to_long(dateField) > 0', []); - testErrorsAndWarnings('from a_index | where to_long(stringField) > 0', []); - testErrorsAndWarnings('from a_index | eval var = to_long(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_long(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_long(to_boolean(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = st_centroid_agg(geoPointField)', [ + 'EVAL does not support function st_centroid_agg', + ]); - testErrorsAndWarnings('from a_index | eval to_long(cartesianPointField)', [ - 'Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', + testErrorsAndWarnings('from a_index | eval var = st_centroid_agg(geoPointField) > 0', [ + 'EVAL does not support function st_centroid_agg', ]); - testErrorsAndWarnings('from a_index | eval var = to_long(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_long(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_long(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = to_long(dateField)', []); - testErrorsAndWarnings('from a_index | eval to_long(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_long(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = to_long(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval st_centroid_agg(geoPointField)', [ + 'EVAL does not support function st_centroid_agg', + ]); - testErrorsAndWarnings('from a_index | eval to_long(booleanField, extraArg)', [ - 'Error: [to_long] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval st_centroid_agg(geoPointField) > 0', [ + 'EVAL does not support function st_centroid_agg', ]); - testErrorsAndWarnings('from a_index | sort to_long(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_long(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_long(nullVar)', []); + testErrorsAndWarnings('from a_index | stats st_centroid_agg(null)', []); + testErrorsAndWarnings('row nullVar = null | stats st_centroid_agg(nullVar)', []); }); - describe('to_lower', () => { - testErrorsAndWarnings('row var = to_lower("a")', []); - testErrorsAndWarnings('row to_lower("a")', []); - testErrorsAndWarnings('row var = to_lower(to_string("a"))', []); + describe('values', () => { + testErrorsAndWarnings('from a_index | stats var = values(textField)', []); + testErrorsAndWarnings('from a_index | stats values(textField)', []); - testErrorsAndWarnings('row var = to_lower(5)', [ - 'Argument of [to_lower] must be [string], found value [5] type [number]', + testErrorsAndWarnings('from a_index | sort values(textField)', [ + 'SORT does not support function values', ]); - testErrorsAndWarnings('from a_index | where length(to_lower(stringField)) > 0', []); - - testErrorsAndWarnings('from a_index | where length(to_lower(numberField)) > 0', [ - 'Argument of [to_lower] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | where values(textField)', [ + 'WHERE does not support function values', ]); - testErrorsAndWarnings('from a_index | eval var = to_lower(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_lower(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_lower(to_string(stringField))', []); - - testErrorsAndWarnings('from a_index | eval to_lower(numberField)', [ - 'Argument of [to_lower] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | where values(textField) > 0', [ + 'WHERE does not support function values', ]); - testErrorsAndWarnings('from a_index | eval to_lower(stringField, extraArg)', [ - 'Error: [to_lower] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval var = values(textField)', [ + 'EVAL does not support function values', ]); - testErrorsAndWarnings('from a_index | eval var = to_lower(*)', [ - 'Using wildcards (*) in to_lower is not allowed', + testErrorsAndWarnings('from a_index | eval var = values(textField) > 0', [ + 'EVAL does not support function values', ]); - testErrorsAndWarnings('from a_index | sort to_lower(stringField)', []); - testErrorsAndWarnings('row var = to_lower(to_string(true))', []); - - testErrorsAndWarnings('row var = to_lower(true)', [ - 'Argument of [to_lower] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval values(textField)', [ + 'EVAL does not support function values', ]); - testErrorsAndWarnings('from a_index | where length(to_lower(booleanField)) > 0', [ - 'Argument of [to_lower] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval values(textField) > 0', [ + 'EVAL does not support function values', ]); - testErrorsAndWarnings('from a_index | eval var = to_lower(to_string(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval to_lower(booleanField)', [ - 'Argument of [to_lower] must be [string], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | eval to_lower(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_lower(nullVar)', []); + testErrorsAndWarnings('from a_index | stats values(null)', []); + testErrorsAndWarnings('row nullVar = null | stats values(nullVar)', []); }); - describe('to_radians', () => { - testErrorsAndWarnings('row var = to_radians(5)', []); - testErrorsAndWarnings('row to_radians(5)', []); - testErrorsAndWarnings('row var = to_radians(to_integer("a"))', []); + describe('top', () => { + testErrorsAndWarnings( + 'from a_index | stats var = top(textField, integerField, textField)', + ['Argument of [=] must be a constant, received [top(textField,integerField,textField)]'] + ); + + testErrorsAndWarnings('from a_index | stats top(textField, integerField, textField)', [ + 'Argument of [top] must be a constant, received [integerField]', + 'Argument of [top] must be a constant, received [textField]', + ]); - testErrorsAndWarnings('row var = to_radians("a")', [ - 'Argument of [to_radians] must be [number], found value ["a"] type [string]', + testErrorsAndWarnings('from a_index | sort top(textField, integerField, textField)', [ + 'SORT does not support function top', + ]); + + testErrorsAndWarnings('from a_index | where top(textField, integerField, textField)', [ + 'WHERE does not support function top', ]); - testErrorsAndWarnings('from a_index | where to_radians(numberField) > 0', []); + testErrorsAndWarnings('from a_index | where top(textField, integerField, textField) > 0', [ + 'WHERE does not support function top', + ]); - testErrorsAndWarnings('from a_index | where to_radians(stringField) > 0', [ - 'Argument of [to_radians] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval var = top(textField, integerField, textField)', [ + 'EVAL does not support function top', ]); - testErrorsAndWarnings('from a_index | eval var = to_radians(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_radians(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_radians(to_integer(stringField))', []); + testErrorsAndWarnings( + 'from a_index | eval var = top(textField, integerField, textField) > 0', + ['EVAL does not support function top'] + ); - testErrorsAndWarnings('from a_index | eval to_radians(stringField)', [ - 'Argument of [to_radians] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval top(textField, integerField, textField)', [ + 'EVAL does not support function top', ]); - testErrorsAndWarnings('from a_index | eval to_radians(numberField, extraArg)', [ - 'Error: [to_radians] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | eval top(textField, integerField, textField) > 0', [ + 'EVAL does not support function top', ]); - testErrorsAndWarnings('from a_index | eval var = to_radians(*)', [ - 'Using wildcards (*) in to_radians is not allowed', + testErrorsAndWarnings('from a_index | stats top(null, null, null)', []); + testErrorsAndWarnings('row nullVar = null | stats top(nullVar, nullVar, nullVar)', [ + 'Argument of [top] must be a constant, received [nullVar]', + 'Argument of [top] must be a constant, received [nullVar]', + ]); + testErrorsAndWarnings('from a_index | stats var = top(textField, integerField, "asc")', [ + 'Argument of [=] must be a constant, received [top(textField,integerField,"asc")]', ]); - testErrorsAndWarnings('from a_index | sort to_radians(numberField)', []); - testErrorsAndWarnings('row var = to_radians(to_integer(true))', []); + testErrorsAndWarnings('from a_index | stats top(textField, integerField, "asc")', [ + 'Argument of [top] must be a constant, received [integerField]', + ]); - testErrorsAndWarnings('row var = to_radians(true)', [ - 'Argument of [to_radians] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | sort top(textField, integerField, "asc")', [ + 'SORT does not support function top', ]); - testErrorsAndWarnings('from a_index | where to_radians(booleanField) > 0', [ - 'Argument of [to_radians] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where top(textField, integerField, "asc")', [ + 'WHERE does not support function top', ]); - testErrorsAndWarnings('from a_index | eval var = to_radians(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | where top(textField, integerField, "asc") > 0', [ + 'WHERE does not support function top', + ]); - testErrorsAndWarnings('from a_index | eval to_radians(booleanField)', [ - 'Argument of [to_radians] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval var = top(textField, integerField, "asc")', [ + 'EVAL does not support function top', ]); - testErrorsAndWarnings('from a_index | eval to_radians(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_radians(nullVar)', []); - }); - describe('to_string', () => { - testErrorsAndWarnings('row var = to_string("a")', []); - testErrorsAndWarnings('row to_string("a")', []); - testErrorsAndWarnings('row var = to_str("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_string(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_string(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(stringField)', []); + testErrorsAndWarnings('from a_index | eval var = top(textField, integerField, "asc") > 0', [ + 'EVAL does not support function top', + ]); - testErrorsAndWarnings('from a_index | eval var = to_string(*)', [ - 'Using wildcards (*) in to_string is not allowed', + testErrorsAndWarnings('from a_index | eval top(textField, integerField, "asc")', [ + 'EVAL does not support function top', ]); - testErrorsAndWarnings('from a_index | sort to_string(stringField)', []); - testErrorsAndWarnings('row var = to_string(true)', []); - testErrorsAndWarnings('row to_string(true)', []); - testErrorsAndWarnings('row var = to_str(true)', []); - testErrorsAndWarnings('row var = to_string(to_boolean(true))', []); - testErrorsAndWarnings('row var = to_string(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row to_string(to_cartesianpoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = to_str(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('from a_index | eval top(textField, integerField, "asc") > 0', [ + 'EVAL does not support function top', + ]); + }); + describe('weighted_avg', () => { testErrorsAndWarnings( - 'row var = to_string(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var = weighted_avg(doubleField, doubleField)', [] ); - - testErrorsAndWarnings('row var = to_string(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row to_string(to_cartesianshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = to_str(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('from a_index | stats weighted_avg(doubleField, doubleField)', []); testErrorsAndWarnings( - 'row var = to_string(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', + 'from a_index | stats var = round(weighted_avg(doubleField, doubleField))', [] ); - testErrorsAndWarnings('row var = to_string(now())', []); - testErrorsAndWarnings('row to_string(now())', []); - testErrorsAndWarnings('row var = to_str(now())', []); - testErrorsAndWarnings('row var = to_string(to_datetime(now()))', []); - testErrorsAndWarnings('row var = to_string(5)', []); - testErrorsAndWarnings('row to_string(5)', []); - testErrorsAndWarnings('row var = to_str(5)', []); - testErrorsAndWarnings('row var = to_string(to_integer(true))', []); - testErrorsAndWarnings('row var = to_string(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row to_string(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = to_str(to_geopoint("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = to_string(to_geopoint(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = to_string(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row to_string(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = to_str(to_geoshape("POINT (30 10)"))', []); - testErrorsAndWarnings('row var = to_string(to_geoshape(to_geopoint("POINT (30 10)")))', []); - testErrorsAndWarnings('row var = to_string(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row to_string(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = to_str(to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row var = to_string(to_ip(to_ip("127.0.0.1")))', []); - testErrorsAndWarnings('row var = to_string(to_string(true))', []); - testErrorsAndWarnings('row var = to_string(to_version("1.0.0"))', []); - testErrorsAndWarnings('row to_string(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = to_str(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = to_string(to_version("a"))', []); - testErrorsAndWarnings('from a_index | where length(to_string(booleanField)) > 0', []); testErrorsAndWarnings( - 'from a_index | where length(to_string(cartesianPointField)) > 0', + 'from a_index | stats round(weighted_avg(doubleField, doubleField))', [] ); + testErrorsAndWarnings( - 'from a_index | where length(to_string(cartesianShapeField)) > 0', + 'from a_index | stats var = round(weighted_avg(doubleField, doubleField)) + weighted_avg(doubleField, doubleField)', [] ); - testErrorsAndWarnings('from a_index | where length(to_string(dateField)) > 0', []); - testErrorsAndWarnings('from a_index | where length(to_string(numberField)) > 0', []); - testErrorsAndWarnings('from a_index | where length(to_string(geoPointField)) > 0', []); - testErrorsAndWarnings('from a_index | where length(to_string(geoShapeField)) > 0', []); - testErrorsAndWarnings('from a_index | where length(to_string(ipField)) > 0', []); - testErrorsAndWarnings('from a_index | where length(to_string(stringField)) > 0', []); - testErrorsAndWarnings('from a_index | where length(to_string(versionField)) > 0', []); - testErrorsAndWarnings('from a_index | eval var = to_string(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_string(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_string(to_boolean(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = to_string(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval to_string(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(cartesianPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_string(to_cartesianpoint(cartesianPointField))', + 'from a_index | stats round(weighted_avg(doubleField, doubleField)) + weighted_avg(doubleField, doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval var = to_string(cartesianShapeField)', []); - testErrorsAndWarnings('from a_index | eval to_string(cartesianShapeField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(cartesianShapeField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = to_string(to_cartesianshape(cartesianPointField))', + 'from a_index | stats weighted_avg(doubleField / 2, doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval var = to_string(dateField)', []); - testErrorsAndWarnings('from a_index | eval to_string(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_string(to_datetime(dateField))', []); - testErrorsAndWarnings('from a_index | eval var = to_string(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_string(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_string(to_integer(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = to_string(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval to_string(geoPointField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_string(to_geopoint(geoPointField))', + 'from a_index | stats var0 = weighted_avg(doubleField / 2, doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval var = to_string(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval to_string(geoShapeField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(geoShapeField)', []); + testErrorsAndWarnings( - 'from a_index | eval var = to_string(to_geoshape(geoPointField))', + 'from a_index | stats avg(doubleField), weighted_avg(doubleField / 2, doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval var = to_string(ipField)', []); - testErrorsAndWarnings('from a_index | eval to_string(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(ipField)', []); - testErrorsAndWarnings('from a_index | eval var = to_string(to_ip(ipField))', []); - testErrorsAndWarnings('from a_index | eval var = to_string(to_string(booleanField))', []); - testErrorsAndWarnings('from a_index | eval var = to_string(versionField)', []); - testErrorsAndWarnings('from a_index | eval to_string(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = to_str(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = to_string(to_version(stringField))', []); - - testErrorsAndWarnings('from a_index | eval to_string(booleanField, extraArg)', [ - 'Error: [to_string] function expects exactly one argument, got 2.', - ]); - - testErrorsAndWarnings('from a_index | sort to_string(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_string(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_string(nullVar)', []); - }); - - describe('to_unsigned_long', () => { - testErrorsAndWarnings('row var = to_unsigned_long("a")', []); - testErrorsAndWarnings('row to_unsigned_long("a")', []); - testErrorsAndWarnings('row var = to_ul("a")', []); - testErrorsAndWarnings('row var = to_ulong("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_unsigned_long(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ul(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ulong(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(*)', [ - 'Using wildcards (*) in to_unsigned_long is not allowed', - ]); - - testErrorsAndWarnings('from a_index | sort to_unsigned_long(stringField)', []); - testErrorsAndWarnings('row var = to_unsigned_long(true)', []); - testErrorsAndWarnings('row to_unsigned_long(true)', []); - testErrorsAndWarnings('row var = to_ul(true)', []); - testErrorsAndWarnings('row var = to_ulong(true)', []); - testErrorsAndWarnings('row var = to_unsigned_long(to_boolean(true))', []); - testErrorsAndWarnings('row var = to_unsigned_long(now())', []); - testErrorsAndWarnings('row to_unsigned_long(now())', []); - testErrorsAndWarnings('row var = to_ul(now())', []); - testErrorsAndWarnings('row var = to_ulong(now())', []); - testErrorsAndWarnings('row var = to_unsigned_long(to_datetime(now()))', []); - testErrorsAndWarnings('row var = to_unsigned_long(5)', []); - testErrorsAndWarnings('row to_unsigned_long(5)', []); - testErrorsAndWarnings('row var = to_ul(5)', []); - testErrorsAndWarnings('row var = to_ulong(5)', []); - testErrorsAndWarnings('row var = to_unsigned_long(to_integer(true))', []); - testErrorsAndWarnings('row var = to_unsigned_long(to_string(true))', []); - - testErrorsAndWarnings('row var = to_unsigned_long(to_cartesianpoint("POINT (30 10)"))', [ - 'Argument of [to_unsigned_long] must be [boolean], found value [to_cartesianpoint("POINT (30 10)")] type [cartesian_point]', - ]); - - testErrorsAndWarnings('from a_index | where to_unsigned_long(booleanField) > 0', []); - - testErrorsAndWarnings('from a_index | where to_unsigned_long(cartesianPointField) > 0', [ - 'Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); - testErrorsAndWarnings('from a_index | where to_unsigned_long(dateField) > 0', []); - testErrorsAndWarnings('from a_index | where to_unsigned_long(numberField) > 0', []); - testErrorsAndWarnings('from a_index | where to_unsigned_long(stringField) > 0', []); - testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_unsigned_long(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ul(booleanField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ulong(booleanField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_unsigned_long(to_boolean(booleanField))', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField / 2, doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval to_unsigned_long(cartesianPointField)', [ - 'Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]', - ]); - - testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(dateField)', []); - testErrorsAndWarnings('from a_index | eval to_unsigned_long(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ul(dateField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ulong(dateField)', []); testErrorsAndWarnings( - 'from a_index | eval var = to_unsigned_long(to_datetime(dateField))', + 'from a_index | stats var0 = weighted_avg(doubleField, doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval var = to_unsigned_long(numberField)', []); - testErrorsAndWarnings('from a_index | eval to_unsigned_long(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ul(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ulong(numberField)', []); + testErrorsAndWarnings( - 'from a_index | eval var = to_unsigned_long(to_integer(booleanField))', + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, doubleField)', [] ); + testErrorsAndWarnings( - 'from a_index | eval var = to_unsigned_long(to_string(booleanField))', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, doubleField)', [] ); - testErrorsAndWarnings('from a_index | eval to_unsigned_long(booleanField, extraArg)', [ - 'Error: [to_unsigned_long] function expects exactly one argument, got 2.', - ]); - - testErrorsAndWarnings('from a_index | sort to_unsigned_long(booleanField)', []); - testErrorsAndWarnings('from a_index | eval to_unsigned_long(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_unsigned_long(nullVar)', []); - }); - - describe('to_upper', () => { - testErrorsAndWarnings('row var = to_upper("a")', []); - testErrorsAndWarnings('row to_upper("a")', []); - testErrorsAndWarnings('row var = to_upper(to_string("a"))', []); - - testErrorsAndWarnings('row var = to_upper(5)', [ - 'Argument of [to_upper] must be [string], found value [5] type [number]', - ]); - - testErrorsAndWarnings('from a_index | where length(to_upper(stringField)) > 0', []); - - testErrorsAndWarnings('from a_index | where length(to_upper(numberField)) > 0', [ - 'Argument of [to_upper] must be [string], found value [numberField] type [number]', - ]); - - testErrorsAndWarnings('from a_index | eval var = to_upper(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_upper(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_upper(to_string(stringField))', []); - - testErrorsAndWarnings('from a_index | eval to_upper(numberField)', [ - 'Argument of [to_upper] must be [string], found value [numberField] type [number]', - ]); - - testErrorsAndWarnings('from a_index | eval to_upper(stringField, extraArg)', [ - 'Error: [to_upper] function expects exactly one argument, got 2.', - ]); - - testErrorsAndWarnings('from a_index | eval var = to_upper(*)', [ - 'Using wildcards (*) in to_upper is not allowed', - ]); - - testErrorsAndWarnings('from a_index | sort to_upper(stringField)', []); - testErrorsAndWarnings('row var = to_upper(to_string(true))', []); - - testErrorsAndWarnings('row var = to_upper(true)', [ - 'Argument of [to_upper] must be [string], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where length(to_upper(booleanField)) > 0', [ - 'Argument of [to_upper] must be [string], found value [booleanField] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | eval var = to_upper(to_string(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval to_upper(booleanField)', [ - 'Argument of [to_upper] must be [string], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | eval to_upper(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_upper(nullVar)', []); - }); - - describe('to_version', () => { - testErrorsAndWarnings('row var = to_version("a")', []); - testErrorsAndWarnings('row to_version("a")', []); - testErrorsAndWarnings('row var = to_ver("a")', []); - testErrorsAndWarnings('from a_index | eval var = to_version(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_version(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ver(stringField)', []); - - testErrorsAndWarnings('from a_index | eval var = to_version(*)', [ - 'Using wildcards (*) in to_version is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(doubleField, doubleField) by round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | sort to_version(stringField)', []); - testErrorsAndWarnings('row var = to_version(to_version("1.0.0"))', []); - testErrorsAndWarnings('row to_version(to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = to_ver(to_version("1.0.0"))', []); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(doubleField, doubleField) by var1 = round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('row var = to_version(true)', [ - 'Argument of [to_version] must be [string], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, doubleField) by round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | eval var = to_version(versionField)', []); - testErrorsAndWarnings('from a_index | eval to_version(versionField)', []); - testErrorsAndWarnings('from a_index | eval var = to_ver(versionField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, doubleField) by var1 = round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | eval to_version(stringField, extraArg)', [ - 'Error: [to_version] function expects exactly one argument, got 2.', - ]); - testErrorsAndWarnings('from a_index | eval to_version(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_version(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, doubleField) by round(doubleField / 2), doubleField / 2', + [] + ); - describe('trim', () => { - testErrorsAndWarnings('row var = trim("a")', []); - testErrorsAndWarnings('row trim("a")', []); - testErrorsAndWarnings('row var = trim(to_string("a"))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, doubleField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('row var = trim(5)', [ - 'Argument of [trim] must be [string], found value [5] type [number]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = weighted_avg(avg(integerField), avg(integerField))', + [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ] + ); - testErrorsAndWarnings('from a_index | where length(trim(stringField)) > 0', []); + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(avg(integerField), avg(integerField))', + [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ] + ); - testErrorsAndWarnings('from a_index | where length(trim(numberField)) > 0', [ - 'Argument of [trim] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | stats weighted_avg(booleanField, booleanField)', [ + 'Argument of [weighted_avg] must be [double], found value [booleanField] type [boolean]', + 'Argument of [weighted_avg] must be [double], found value [booleanField] type [boolean]', ]); - testErrorsAndWarnings('from a_index | eval var = trim(stringField)', []); - testErrorsAndWarnings('from a_index | eval trim(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = trim(to_string(stringField))', []); + testErrorsAndWarnings('from a_index | sort weighted_avg(doubleField, doubleField)', [ + 'SORT does not support function weighted_avg', + ]); - testErrorsAndWarnings('from a_index | eval trim(numberField)', [ - 'Argument of [trim] must be [string], found value [numberField] type [number]', + testErrorsAndWarnings('from a_index | where weighted_avg(doubleField, doubleField)', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval trim(stringField, extraArg)', [ - 'Error: [trim] function expects exactly one argument, got 2.', + testErrorsAndWarnings('from a_index | where weighted_avg(doubleField, doubleField) > 0', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval var = trim(*)', [ - 'Using wildcards (*) in trim is not allowed', + testErrorsAndWarnings('from a_index | eval var = weighted_avg(doubleField, doubleField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | sort trim(stringField)', []); - testErrorsAndWarnings('row var = trim(to_string(true))', []); + testErrorsAndWarnings( + 'from a_index | eval var = weighted_avg(doubleField, doubleField) > 0', + ['EVAL does not support function weighted_avg'] + ); - testErrorsAndWarnings('row var = trim(true)', [ - 'Argument of [trim] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | eval weighted_avg(doubleField, doubleField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | where length(trim(booleanField)) > 0', [ - 'Argument of [trim] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval weighted_avg(doubleField, doubleField) > 0', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval var = trim(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | stats weighted_avg(null, null)', []); + testErrorsAndWarnings('row nullVar = null | stats weighted_avg(nullVar, nullVar)', []); + testErrorsAndWarnings( + 'from a_index | stats var = weighted_avg(doubleField, longField)', + [] + ); + testErrorsAndWarnings('from a_index | stats weighted_avg(doubleField, longField)', []); - testErrorsAndWarnings('from a_index | eval trim(booleanField)', [ - 'Argument of [trim] must be [string], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | eval trim(null)', []); - testErrorsAndWarnings('row nullVar = null | eval trim(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(doubleField, longField))', + [] + ); - describe('avg', () => { - testErrorsAndWarnings('from a_index | stats var = avg(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField)', []); - testErrorsAndWarnings('from a_index | stats var = round(avg(numberField))', []); - testErrorsAndWarnings('from a_index | stats round(avg(numberField))', []); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(doubleField, longField))', + [] + ); testErrorsAndWarnings( - 'from a_index | stats var = round(avg(numberField)) + avg(numberField)', + 'from a_index | stats var = round(weighted_avg(doubleField, longField)) + weighted_avg(doubleField, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats round(avg(numberField)) + avg(numberField)', + 'from a_index | stats round(weighted_avg(doubleField, longField)) + weighted_avg(doubleField, longField)', [] ); - testErrorsAndWarnings('from a_index | stats avg(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats var0 = avg(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), avg(numberField / 2)', []); + + testErrorsAndWarnings('from a_index | stats weighted_avg(doubleField / 2, longField)', []); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = avg(numberField / 2)', + 'from a_index | stats var0 = weighted_avg(doubleField / 2, longField)', [] ); - testErrorsAndWarnings('from a_index | stats var0 = avg(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), avg(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), var0 = avg(numberField)', []); + testErrorsAndWarnings( - 'from a_index | stats avg(numberField) by round(numberField / 2)', + 'from a_index | stats avg(doubleField), weighted_avg(doubleField / 2, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = avg(numberField) by var1 = round(numberField / 2)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField / 2, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), ipField', + 'from a_index | stats var0 = weighted_avg(doubleField, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), ipField', + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), numberField / 2', + 'from a_index | stats weighted_avg(doubleField, longField) by round(doubleField / 2)', [] ); - testErrorsAndWarnings('from a_index | stats var = avg(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(doubleField, longField) by var1 = round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | stats avg(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, longField) by round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | stats avg(stringField)', [ - 'Argument of [avg] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, longField) by var1 = round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | stats var = avg(*)', [ - 'Using wildcards (*) in avg is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, longField) by round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | sort avg(numberField)', [ - 'SORT does not support function avg', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, longField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | where avg(numberField)', [ - 'WHERE does not support function avg', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = weighted_avg(doubleField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | stats weighted_avg(doubleField, integerField)', []); - testErrorsAndWarnings('from a_index | where avg(numberField) > 0', [ - 'WHERE does not support function avg', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(doubleField, integerField))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = avg(numberField)', [ - 'EVAL does not support function avg', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(doubleField, integerField))', + [] + ); - testErrorsAndWarnings('from a_index | eval var = avg(numberField) > 0', [ - 'EVAL does not support function avg', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(doubleField, integerField)) + weighted_avg(doubleField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | eval avg(numberField)', [ - 'EVAL does not support function avg', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(doubleField, integerField)) + weighted_avg(doubleField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | eval avg(numberField) > 0', [ - 'EVAL does not support function avg', - ]); + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(doubleField / 2, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | stats avg(booleanField)', [ - 'Argument of [avg] must be [number], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | stats avg(null)', []); - testErrorsAndWarnings('row nullVar = null | stats avg(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(doubleField / 2, integerField)', + [] + ); - describe('sum', () => { - testErrorsAndWarnings('from a_index | stats var = sum(numberField)', []); - testErrorsAndWarnings('from a_index | stats sum(numberField)', []); - testErrorsAndWarnings('from a_index | stats var = round(sum(numberField))', []); - testErrorsAndWarnings('from a_index | stats round(sum(numberField))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(doubleField / 2, integerField)', + [] + ); testErrorsAndWarnings( - 'from a_index | stats var = round(sum(numberField)) + sum(numberField)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField / 2, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | stats round(sum(numberField)) + sum(numberField)', + 'from a_index | stats var0 = weighted_avg(doubleField, integerField)', [] ); - testErrorsAndWarnings('from a_index | stats sum(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats var0 = sum(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), sum(numberField / 2)', []); + testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = sum(numberField / 2)', + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, integerField)', [] ); - testErrorsAndWarnings('from a_index | stats var0 = sum(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), sum(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), var0 = sum(numberField)', []); + testErrorsAndWarnings( - 'from a_index | stats sum(numberField) by round(numberField / 2)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = sum(numberField) by var1 = round(numberField / 2)', + 'from a_index | stats weighted_avg(doubleField, integerField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), ipField', + 'from a_index | stats var0 = weighted_avg(doubleField, integerField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), ipField', + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, integerField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, integerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), weighted_avg(doubleField, integerField) by round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | stats var = sum(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(doubleField, integerField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | stats sum(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats var = weighted_avg(longField, doubleField)', + [] + ); + testErrorsAndWarnings('from a_index | stats weighted_avg(longField, doubleField)', []); - testErrorsAndWarnings('from a_index | stats sum(stringField)', [ - 'Argument of [sum] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(longField, doubleField))', + [] + ); - testErrorsAndWarnings('from a_index | stats var = sum(*)', [ - 'Using wildcards (*) in sum is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(longField, doubleField))', + [] + ); - testErrorsAndWarnings('from a_index | sort sum(numberField)', [ - 'SORT does not support function sum', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(longField, doubleField)) + weighted_avg(longField, doubleField)', + [] + ); - testErrorsAndWarnings('from a_index | where sum(numberField)', [ - 'WHERE does not support function sum', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(longField, doubleField)) + weighted_avg(longField, doubleField)', + [] + ); - testErrorsAndWarnings('from a_index | where sum(numberField) > 0', [ - 'WHERE does not support function sum', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(longField, doubleField)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = sum(numberField)', [ - 'EVAL does not support function sum', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(longField, doubleField)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = sum(numberField) > 0', [ - 'EVAL does not support function sum', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, doubleField)', + [] + ); - testErrorsAndWarnings('from a_index | eval sum(numberField)', [ - 'EVAL does not support function sum', - ]); + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(longField, doubleField) by round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | eval sum(numberField) > 0', [ - 'EVAL does not support function sum', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(longField, doubleField) by var1 = round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | stats sum(booleanField)', [ - 'Argument of [sum] must be [number], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | stats sum(null)', []); - testErrorsAndWarnings('row nullVar = null | stats sum(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(longField, doubleField) by round(doubleField / 2), ipField', + [] + ); - describe('median', () => { - testErrorsAndWarnings('from a_index | stats var = median(numberField)', []); - testErrorsAndWarnings('from a_index | stats median(numberField)', []); - testErrorsAndWarnings('from a_index | stats var = round(median(numberField))', []); - testErrorsAndWarnings('from a_index | stats round(median(numberField))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, doubleField) by var1 = round(doubleField / 2), ipField', + [] + ); testErrorsAndWarnings( - 'from a_index | stats var = round(median(numberField)) + median(numberField)', + 'from a_index | stats avg(doubleField), weighted_avg(longField, doubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | stats round(median(numberField)) + median(numberField)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, doubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | stats median(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats var0 = median(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), median(numberField / 2)', []); + testErrorsAndWarnings('from a_index | stats var = weighted_avg(longField, longField)', []); + testErrorsAndWarnings('from a_index | stats weighted_avg(longField, longField)', []); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(longField, longField))', + [] + ); + testErrorsAndWarnings('from a_index | stats round(weighted_avg(longField, longField))', []); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median(numberField / 2)', + 'from a_index | stats var = round(weighted_avg(longField, longField)) + weighted_avg(longField, longField)', [] ); - testErrorsAndWarnings('from a_index | stats var0 = median(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), median(numberField)', []); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median(numberField)', + 'from a_index | stats round(weighted_avg(longField, longField)) + weighted_avg(longField, longField)', [] ); + + testErrorsAndWarnings('from a_index | stats var0 = weighted_avg(longField, longField)', []); + testErrorsAndWarnings( - 'from a_index | stats median(numberField) by round(numberField / 2)', + 'from a_index | stats avg(doubleField), weighted_avg(longField, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = median(numberField) by var1 = round(numberField / 2)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, longField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), ipField', + 'from a_index | stats weighted_avg(longField, longField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), ipField', + 'from a_index | stats var0 = weighted_avg(longField, longField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), weighted_avg(longField, longField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, longField) by var1 = round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('from a_index | stats var = median(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(longField, longField) by round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | stats median(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, longField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); - testErrorsAndWarnings('from a_index | stats median(stringField)', [ - 'Argument of [median] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = weighted_avg(longField, integerField)', + [] + ); + testErrorsAndWarnings('from a_index | stats weighted_avg(longField, integerField)', []); - testErrorsAndWarnings('from a_index | stats var = median(*)', [ - 'Using wildcards (*) in median is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(longField, integerField))', + [] + ); - testErrorsAndWarnings('from a_index | sort median(numberField)', [ - 'SORT does not support function median', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(longField, integerField))', + [] + ); - testErrorsAndWarnings('from a_index | where median(numberField)', [ - 'WHERE does not support function median', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(longField, integerField)) + weighted_avg(longField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | where median(numberField) > 0', [ - 'WHERE does not support function median', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(longField, integerField)) + weighted_avg(longField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = median(numberField)', [ - 'EVAL does not support function median', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(longField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = median(numberField) > 0', [ - 'EVAL does not support function median', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(longField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | eval median(numberField)', [ - 'EVAL does not support function median', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, integerField)', + [] + ); - testErrorsAndWarnings('from a_index | eval median(numberField) > 0', [ - 'EVAL does not support function median', - ]); + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(longField, integerField) by round(doubleField / 2)', + [] + ); - testErrorsAndWarnings('from a_index | stats median(booleanField)', [ - 'Argument of [median] must be [number], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | stats median(null)', []); - testErrorsAndWarnings('row nullVar = null | stats median(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(longField, integerField) by var1 = round(doubleField / 2)', + [] + ); + + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(longField, integerField) by round(doubleField / 2), ipField', + [] + ); - describe('median_absolute_deviation', () => { testErrorsAndWarnings( - 'from a_index | stats var = median_absolute_deviation(numberField)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, integerField) by var1 = round(doubleField / 2), ipField', [] ); - testErrorsAndWarnings('from a_index | stats median_absolute_deviation(numberField)', []); testErrorsAndWarnings( - 'from a_index | stats var = round(median_absolute_deviation(numberField))', + 'from a_index | stats avg(doubleField), weighted_avg(longField, integerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | stats round(median_absolute_deviation(numberField))', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(longField, integerField) by var1 = round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | stats var = round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)', + 'from a_index | stats var = weighted_avg(integerField, doubleField)', [] ); + testErrorsAndWarnings('from a_index | stats weighted_avg(integerField, doubleField)', []); testErrorsAndWarnings( - 'from a_index | stats round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)', + 'from a_index | stats var = round(weighted_avg(integerField, doubleField))', [] ); testErrorsAndWarnings( - 'from a_index | stats median_absolute_deviation(numberField / 2)', + 'from a_index | stats round(weighted_avg(integerField, doubleField))', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = median_absolute_deviation(numberField / 2)', + 'from a_index | stats var = round(weighted_avg(integerField, doubleField)) + weighted_avg(integerField, doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), median_absolute_deviation(numberField / 2)', + 'from a_index | stats round(weighted_avg(integerField, doubleField)) + weighted_avg(integerField, doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField / 2)', + 'from a_index | stats var0 = weighted_avg(integerField, doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = median_absolute_deviation(numberField)', + 'from a_index | stats avg(doubleField), weighted_avg(integerField, doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), median_absolute_deviation(numberField)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField)', + 'from a_index | stats weighted_avg(integerField, doubleField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats median_absolute_deviation(numberField) by round(numberField / 2)', + 'from a_index | stats var0 = weighted_avg(integerField, doubleField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2)', + 'from a_index | stats avg(doubleField), weighted_avg(integerField, doubleField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), ipField', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, doubleField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), ipField', + 'from a_index | stats avg(doubleField), weighted_avg(integerField, doubleField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, doubleField) by var1 = round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), numberField / 2', + 'from a_index | stats var = weighted_avg(integerField, longField)', [] ); + testErrorsAndWarnings('from a_index | stats weighted_avg(integerField, longField)', []); testErrorsAndWarnings( - 'from a_index | stats var = median_absolute_deviation(avg(numberField))', - [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ] + 'from a_index | stats var = round(weighted_avg(integerField, longField))', + [] ); - testErrorsAndWarnings('from a_index | stats median_absolute_deviation(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(integerField, longField))', + [] + ); - testErrorsAndWarnings('from a_index | stats median_absolute_deviation(stringField)', [ - 'Argument of [median_absolute_deviation] must be [number], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(integerField, longField)) + weighted_avg(integerField, longField)', + [] + ); - testErrorsAndWarnings('from a_index | stats var = median_absolute_deviation(*)', [ - 'Using wildcards (*) in median_absolute_deviation is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(integerField, longField)) + weighted_avg(integerField, longField)', + [] + ); - testErrorsAndWarnings('from a_index | sort median_absolute_deviation(numberField)', [ - 'SORT does not support function median_absolute_deviation', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = weighted_avg(integerField, longField)', + [] + ); - testErrorsAndWarnings('from a_index | where median_absolute_deviation(numberField)', [ - 'WHERE does not support function median_absolute_deviation', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(integerField, longField)', + [] + ); - testErrorsAndWarnings('from a_index | where median_absolute_deviation(numberField) > 0', [ - 'WHERE does not support function median_absolute_deviation', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, longField)', + [] + ); - testErrorsAndWarnings('from a_index | eval var = median_absolute_deviation(numberField)', [ - 'EVAL does not support function median_absolute_deviation', - ]); + testErrorsAndWarnings( + 'from a_index | stats weighted_avg(integerField, longField) by round(doubleField / 2)', + [] + ); testErrorsAndWarnings( - 'from a_index | eval var = median_absolute_deviation(numberField) > 0', - ['EVAL does not support function median_absolute_deviation'] + 'from a_index | stats var0 = weighted_avg(integerField, longField) by var1 = round(doubleField / 2)', + [] ); - testErrorsAndWarnings('from a_index | eval median_absolute_deviation(numberField)', [ - 'EVAL does not support function median_absolute_deviation', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(integerField, longField) by round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | eval median_absolute_deviation(numberField) > 0', [ - 'EVAL does not support function median_absolute_deviation', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, longField) by var1 = round(doubleField / 2), ipField', + [] + ); - testErrorsAndWarnings('from a_index | stats median_absolute_deviation(booleanField)', [ - 'Argument of [median_absolute_deviation] must be [number], found value [booleanField] type [boolean]', - ]); - testErrorsAndWarnings('from a_index | stats median_absolute_deviation(null)', []); - testErrorsAndWarnings('row nullVar = null | stats median_absolute_deviation(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), weighted_avg(integerField, longField) by round(doubleField / 2), doubleField / 2', + [] + ); - describe('percentile', () => { - testErrorsAndWarnings('from a_index | stats var = percentile(numberField, 5)', []); - testErrorsAndWarnings('from a_index | stats percentile(numberField, 5)', []); - testErrorsAndWarnings('from a_index | stats var = round(percentile(numberField, 5))', []); - testErrorsAndWarnings('from a_index | stats round(percentile(numberField, 5))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, longField) by var1 = round(doubleField / 2), doubleField / 2', + [] + ); testErrorsAndWarnings( - 'from a_index | stats var = round(percentile(numberField, 5)) + percentile(numberField, 5)', + 'from a_index | stats var = weighted_avg(integerField, integerField)', [] ); + testErrorsAndWarnings('from a_index | stats weighted_avg(integerField, integerField)', []); testErrorsAndWarnings( - 'from a_index | stats round(percentile(numberField, 5)) + percentile(numberField, 5)', + 'from a_index | stats var = round(weighted_avg(integerField, integerField))', [] ); - testErrorsAndWarnings('from a_index | stats percentile(numberField, numberField)', [ - 'Argument of [percentile] must be a constant, received [numberField]', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(weighted_avg(integerField, integerField))', + [] + ); - testErrorsAndWarnings('from a_index | stats percentile(numberField / 2, 5)', []); - testErrorsAndWarnings('from a_index | stats var0 = percentile(numberField / 2, 5)', []); + testErrorsAndWarnings( + 'from a_index | stats var = round(weighted_avg(integerField, integerField)) + weighted_avg(integerField, integerField)', + [] + ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), percentile(numberField / 2, 5)', + 'from a_index | stats round(weighted_avg(integerField, integerField)) + weighted_avg(integerField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = percentile(numberField / 2, 5)', + 'from a_index | stats var0 = weighted_avg(integerField, integerField)', [] ); - testErrorsAndWarnings('from a_index | stats var0 = percentile(numberField, 5)', []); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), percentile(numberField, 5)', + 'from a_index | stats avg(doubleField), weighted_avg(integerField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = percentile(numberField, 5)', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, integerField)', [] ); testErrorsAndWarnings( - 'from a_index | stats percentile(numberField, 5) by round(numberField / 2)', + 'from a_index | stats weighted_avg(integerField, integerField) by round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = percentile(numberField, 5) by var1 = round(numberField / 2)', + 'from a_index | stats var0 = weighted_avg(integerField, integerField) by var1 = round(doubleField / 2)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), ipField', + 'from a_index | stats avg(doubleField), weighted_avg(integerField, integerField) by round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), ipField', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, integerField) by var1 = round(doubleField / 2), ipField', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), weighted_avg(integerField, integerField) by round(doubleField / 2), doubleField / 2', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), numberField / 2', + 'from a_index | stats avg(doubleField), var0 = weighted_avg(integerField, integerField) by var1 = round(doubleField / 2), doubleField / 2', [] ); - testErrorsAndWarnings('from a_index | stats var = percentile(avg(numberField), 5)', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + testErrorsAndWarnings('from a_index | where weighted_avg(doubleField, longField)', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats percentile(avg(numberField), 5)', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + testErrorsAndWarnings('from a_index | where weighted_avg(doubleField, longField) > 0', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats percentile(stringField, 5)', [ - 'Argument of [percentile] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | where weighted_avg(doubleField, integerField)', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | sort percentile(numberField, 5)', [ - 'SORT does not support function percentile', + testErrorsAndWarnings('from a_index | where weighted_avg(doubleField, integerField) > 0', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | where percentile(numberField, 5)', [ - 'WHERE does not support function percentile', + testErrorsAndWarnings('from a_index | where weighted_avg(longField, doubleField)', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | where percentile(numberField, 5) > 0', [ - 'WHERE does not support function percentile', + testErrorsAndWarnings('from a_index | where weighted_avg(longField, doubleField) > 0', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval var = percentile(numberField, 5)', [ - 'EVAL does not support function percentile', + testErrorsAndWarnings('from a_index | where weighted_avg(longField, longField)', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval var = percentile(numberField, 5) > 0', [ - 'EVAL does not support function percentile', + testErrorsAndWarnings('from a_index | where weighted_avg(longField, longField) > 0', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval percentile(numberField, 5)', [ - 'EVAL does not support function percentile', + testErrorsAndWarnings('from a_index | where weighted_avg(longField, integerField)', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval percentile(numberField, 5) > 0', [ - 'EVAL does not support function percentile', + testErrorsAndWarnings('from a_index | where weighted_avg(longField, integerField) > 0', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats percentile(booleanField, 5)', [ - 'Argument of [percentile] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | where weighted_avg(integerField, doubleField)', [ + 'WHERE does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats percentile(null, null)', []); - testErrorsAndWarnings('row nullVar = null | stats percentile(nullVar, nullVar)', [ - 'Argument of [percentile] must be a constant, received [nullVar]', + + testErrorsAndWarnings('from a_index | where weighted_avg(integerField, doubleField) > 0', [ + 'WHERE does not support function weighted_avg', ]); - }); - describe('max', () => { - testErrorsAndWarnings('from a_index | stats var = max(numberField)', []); - testErrorsAndWarnings('from a_index | stats max(numberField)', []); - testErrorsAndWarnings('from a_index | stats var = round(max(numberField))', []); - testErrorsAndWarnings('from a_index | stats round(max(numberField))', []); + testErrorsAndWarnings('from a_index | where weighted_avg(integerField, longField)', [ + 'WHERE does not support function weighted_avg', + ]); - testErrorsAndWarnings( - 'from a_index | stats var = round(max(numberField)) + max(numberField)', - [] - ); + testErrorsAndWarnings('from a_index | where weighted_avg(integerField, longField) > 0', [ + 'WHERE does not support function weighted_avg', + ]); - testErrorsAndWarnings( - 'from a_index | stats round(max(numberField)) + max(numberField)', - [] - ); - testErrorsAndWarnings('from a_index | stats max(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats var0 = max(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), max(numberField / 2)', []); - testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = max(numberField / 2)', - [] - ); - testErrorsAndWarnings('from a_index | stats var0 = max(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), max(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), var0 = max(numberField)', []); - testErrorsAndWarnings( - 'from a_index | stats max(numberField) by round(numberField / 2)', - [] - ); + testErrorsAndWarnings('from a_index | where weighted_avg(integerField, integerField)', [ + 'WHERE does not support function weighted_avg', + ]); - testErrorsAndWarnings( - 'from a_index | stats var0 = max(numberField) by var1 = round(numberField / 2)', - [] - ); + testErrorsAndWarnings('from a_index | where weighted_avg(integerField, integerField) > 0', [ + 'WHERE does not support function weighted_avg', + ]); - testErrorsAndWarnings( - 'from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), ipField', - [] - ); + testErrorsAndWarnings('from a_index | eval var = weighted_avg(doubleField, longField)', [ + 'EVAL does not support function weighted_avg', + ]); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), ipField', - [] + 'from a_index | eval var = weighted_avg(doubleField, longField) > 0', + ['EVAL does not support function weighted_avg'] ); + testErrorsAndWarnings('from a_index | eval weighted_avg(doubleField, longField)', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval weighted_avg(doubleField, longField) > 0', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = weighted_avg(doubleField, integerField)', [ + 'EVAL does not support function weighted_avg', + ]); + testErrorsAndWarnings( - 'from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), numberField / 2', - [] + 'from a_index | eval var = weighted_avg(doubleField, integerField) > 0', + ['EVAL does not support function weighted_avg'] ); + testErrorsAndWarnings('from a_index | eval weighted_avg(doubleField, integerField)', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval weighted_avg(doubleField, integerField) > 0', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = weighted_avg(longField, doubleField)', [ + 'EVAL does not support function weighted_avg', + ]); + testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), numberField / 2', - [] + 'from a_index | eval var = weighted_avg(longField, doubleField) > 0', + ['EVAL does not support function weighted_avg'] ); - testErrorsAndWarnings('from a_index | stats var = max(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + testErrorsAndWarnings('from a_index | eval weighted_avg(longField, doubleField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats max(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + testErrorsAndWarnings('from a_index | eval weighted_avg(longField, doubleField) > 0', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats max(stringField)', [ - 'Argument of [max] must be [number], found value [stringField] type [string]', + testErrorsAndWarnings('from a_index | eval var = weighted_avg(longField, longField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats var = max(*)', [ - 'Using wildcards (*) in max is not allowed', + testErrorsAndWarnings('from a_index | eval var = weighted_avg(longField, longField) > 0', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval weighted_avg(longField, longField)', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval weighted_avg(longField, longField) > 0', [ + 'EVAL does not support function weighted_avg', + ]); + + testErrorsAndWarnings('from a_index | eval var = weighted_avg(longField, integerField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | stats var = max(dateField)', []); - testErrorsAndWarnings('from a_index | stats max(dateField)', []); - testErrorsAndWarnings('from a_index | stats var = round(max(dateField))', []); - testErrorsAndWarnings('from a_index | stats round(max(dateField))', []); testErrorsAndWarnings( - 'from a_index | stats var = round(max(dateField)) + max(dateField)', - [] + 'from a_index | eval var = weighted_avg(longField, integerField) > 0', + ['EVAL does not support function weighted_avg'] ); - testErrorsAndWarnings('from a_index | stats round(max(dateField)) + max(dateField)', []); - testErrorsAndWarnings('from a_index | sort max(numberField)', [ - 'SORT does not support function max', + testErrorsAndWarnings('from a_index | eval weighted_avg(longField, integerField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | where max(numberField)', [ - 'WHERE does not support function max', + testErrorsAndWarnings('from a_index | eval weighted_avg(longField, integerField) > 0', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | where max(numberField) > 0', [ - 'WHERE does not support function max', + testErrorsAndWarnings('from a_index | eval var = weighted_avg(integerField, doubleField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | where max(dateField)', [ - 'WHERE does not support function max', + testErrorsAndWarnings( + 'from a_index | eval var = weighted_avg(integerField, doubleField) > 0', + ['EVAL does not support function weighted_avg'] + ); + + testErrorsAndWarnings('from a_index | eval weighted_avg(integerField, doubleField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | where max(dateField) > 0', [ - 'WHERE does not support function max', + testErrorsAndWarnings('from a_index | eval weighted_avg(integerField, doubleField) > 0', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval var = max(numberField)', [ - 'EVAL does not support function max', + testErrorsAndWarnings('from a_index | eval var = weighted_avg(integerField, longField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval var = max(numberField) > 0', [ - 'EVAL does not support function max', + testErrorsAndWarnings( + 'from a_index | eval var = weighted_avg(integerField, longField) > 0', + ['EVAL does not support function weighted_avg'] + ); + + testErrorsAndWarnings('from a_index | eval weighted_avg(integerField, longField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval max(numberField)', [ - 'EVAL does not support function max', + testErrorsAndWarnings('from a_index | eval weighted_avg(integerField, longField) > 0', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval max(numberField) > 0', [ - 'EVAL does not support function max', + testErrorsAndWarnings( + 'from a_index | eval var = weighted_avg(integerField, integerField)', + ['EVAL does not support function weighted_avg'] + ); + + testErrorsAndWarnings( + 'from a_index | eval var = weighted_avg(integerField, integerField) > 0', + ['EVAL does not support function weighted_avg'] + ); + + testErrorsAndWarnings('from a_index | eval weighted_avg(integerField, integerField)', [ + 'EVAL does not support function weighted_avg', ]); - testErrorsAndWarnings('from a_index | eval var = max(dateField)', [ - 'EVAL does not support function max', + testErrorsAndWarnings('from a_index | eval weighted_avg(integerField, integerField) > 0', [ + 'EVAL does not support function weighted_avg', ]); + }); - testErrorsAndWarnings('from a_index | eval var = max(dateField) > 0', [ - 'EVAL does not support function max', + describe('bucket', () => { + testErrorsAndWarnings('from a_index | stats by bucket(dateField, 1 year)', []); + testErrorsAndWarnings('from a_index | stats by bin(dateField, 1 year)', []); + + testErrorsAndWarnings('from a_index | stats by bucket(integerField, integerField)', [ + 'Argument of [bucket] must be a constant, received [integerField]', + ]); + + testErrorsAndWarnings('from a_index | stats by bin(integerField, integerField)', [ + 'Argument of [bin] must be a constant, received [integerField]', ]); - testErrorsAndWarnings('from a_index | eval max(dateField)', [ - 'EVAL does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(dateField, integerField, textField, textField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [textField]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bin(dateField, integerField, textField, textField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [textField]', + 'Argument of [bin] must be a constant, received [textField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bucket(dateField, integerField, dateField, dateField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [dateField]', + 'Argument of [bucket] must be a constant, received [dateField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bin(dateField, integerField, dateField, dateField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [dateField]', + 'Argument of [bin] must be a constant, received [dateField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bucket(dateField, integerField, textField, dateField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [textField]', + 'Argument of [bucket] must be a constant, received [dateField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bin(dateField, integerField, textField, dateField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [textField]', + 'Argument of [bin] must be a constant, received [dateField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bucket(dateField, integerField, dateField, textField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [dateField]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bin(dateField, integerField, dateField, textField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [dateField]', + 'Argument of [bin] must be a constant, received [textField]', + ] + ); + + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, integerField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | eval max(dateField) > 0', [ - 'EVAL does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, integerField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | stats max(booleanField)', []); - testErrorsAndWarnings('from a_index | stats max(null)', []); - testErrorsAndWarnings('row nullVar = null | stats max(nullVar)', []); - testErrorsAndWarnings('from a_index | stats max("2022")', []); - testErrorsAndWarnings('from a_index | stats max(concat("20", "22"))', [ - 'Argument of [max] must be [number], found value [concat("20","22")] type [string]', + testErrorsAndWarnings('from a_index | sort bucket(dateField, 1 year)', [ + 'SORT does not support function bucket', ]); - testErrorsAndWarnings('from a_index | stats max(cartesianPointField)', [ - 'Argument of [max] must be [number], found value [cartesianPointField] type [cartesian_point]', - ]); + testErrorsAndWarnings('from a_index | stats bucket(null, null, null, null)', []); - testErrorsAndWarnings('from a_index | stats var = max(booleanField)', []); + testErrorsAndWarnings( + 'row nullVar = null | stats bucket(nullVar, nullVar, nullVar, nullVar)', + [ + 'Argument of [bucket] must be a constant, received [nullVar]', + 'Argument of [bucket] must be a constant, received [nullVar]', + 'Argument of [bucket] must be a constant, received [nullVar]', + ] + ); - testErrorsAndWarnings('from a_index | where max(booleanField)', [ - 'WHERE does not support function max', + testErrorsAndWarnings('from a_index | stats bucket("2022", 1 year)', []); + testErrorsAndWarnings('from a_index | stats bucket(concat("20", "22"), 1 year)', [ + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', ]); - testErrorsAndWarnings('from a_index | where max(booleanField) > 0', [ - 'WHERE does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats bucket("2022", integerField, textField, textField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [textField]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = max(booleanField)', [ - 'EVAL does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats bucket(concat("20", "22"), integerField, textField, textField)', + [ + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [textField]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = max(booleanField) > 0', [ - 'EVAL does not support function max', + testErrorsAndWarnings('from a_index | stats bucket("2022", integerField, "2022", "2022")', [ + 'Argument of [bucket] must be a constant, received [integerField]', ]); - testErrorsAndWarnings('from a_index | eval max(booleanField)', [ - 'EVAL does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats bucket(concat("20", "22"), integerField, concat("20", "22"), concat("20", "22"))', + [ + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + ] + ); - testErrorsAndWarnings('from a_index | eval max(booleanField) > 0', [ - 'EVAL does not support function max', - ]); - testErrorsAndWarnings('from a_index | stats var = max(ipField)', []); - testErrorsAndWarnings('from a_index | stats max(ipField)', []); + testErrorsAndWarnings( + 'from a_index | stats bucket("2022", integerField, textField, "2022")', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); - testErrorsAndWarnings('from a_index | where max(ipField)', [ - 'WHERE does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats bucket(concat("20", "22"), integerField, textField, concat("20", "22"))', + [ + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [textField]', + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + ] + ); - testErrorsAndWarnings('from a_index | where max(ipField) > 0', [ - 'WHERE does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats bucket("2022", integerField, "2022", textField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = max(ipField)', [ - 'EVAL does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats bucket(concat("20", "22"), integerField, concat("20", "22"), textField)', + [ + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be [date], found value [concat("20","22")] type [keyword]', + 'Argument of [bucket] must be a constant, received [textField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = max(ipField) > 0', [ - 'EVAL does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(dateField, integerField, now(), now())', + ['Argument of [bucket] must be a constant, received [integerField]'] + ); - testErrorsAndWarnings('from a_index | eval max(ipField)', [ - 'EVAL does not support function max', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(dateField, integerField, now(), now())', + ['Argument of [bin] must be a constant, received [integerField]'] + ); - testErrorsAndWarnings('from a_index | eval max(ipField) > 0', [ - 'EVAL does not support function max', + testErrorsAndWarnings('from a_index | stats by bucket(doubleField, doubleField)', [ + 'Argument of [bucket] must be a constant, received [doubleField]', ]); - }); - describe('min', () => { - testErrorsAndWarnings('from a_index | stats var = min(numberField)', []); - testErrorsAndWarnings('from a_index | stats min(numberField)', []); - testErrorsAndWarnings('from a_index | stats var = round(min(numberField))', []); - testErrorsAndWarnings('from a_index | stats round(min(numberField))', []); + testErrorsAndWarnings('from a_index | stats by bin(doubleField, doubleField)', [ + 'Argument of [bin] must be a constant, received [doubleField]', + ]); testErrorsAndWarnings( - 'from a_index | stats var = round(min(numberField)) + min(numberField)', - [] + 'from a_index | stats by bucket(doubleField, integerField, doubleField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats round(min(numberField)) + min(numberField)', - [] + 'from a_index | stats by bin(doubleField, integerField, doubleField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] ); - testErrorsAndWarnings('from a_index | stats min(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats var0 = min(numberField / 2)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), min(numberField / 2)', []); + testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = min(numberField / 2)', - [] + 'from a_index | stats by bucket(doubleField, integerField, doubleField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] ); - testErrorsAndWarnings('from a_index | stats var0 = min(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), min(numberField)', []); - testErrorsAndWarnings('from a_index | stats avg(numberField), var0 = min(numberField)', []); + testErrorsAndWarnings( - 'from a_index | stats min(numberField) by round(numberField / 2)', - [] + 'from a_index | stats by bin(doubleField, integerField, doubleField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats var0 = min(numberField) by var1 = round(numberField / 2)', - [] + 'from a_index | stats by bucket(doubleField, integerField, doubleField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), ipField', - [] + 'from a_index | stats by bin(doubleField, integerField, doubleField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [longField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), ipField', - [] + 'from a_index | stats by bucket(doubleField, integerField, integerField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), numberField / 2', - [] + 'from a_index | stats by bin(doubleField, integerField, integerField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), numberField / 2', - [] + 'from a_index | stats by bucket(doubleField, integerField, integerField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] ); - testErrorsAndWarnings('from a_index | stats var = min(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); - - testErrorsAndWarnings('from a_index | stats min(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); - - testErrorsAndWarnings('from a_index | stats min(stringField)', [ - 'Argument of [min] must be [number], found value [stringField] type [string]', - ]); - - testErrorsAndWarnings('from a_index | stats var = min(*)', [ - 'Using wildcards (*) in min is not allowed', - ]); - - testErrorsAndWarnings('from a_index | stats var = min(dateField)', []); - testErrorsAndWarnings('from a_index | stats min(dateField)', []); - testErrorsAndWarnings('from a_index | stats var = round(min(dateField))', []); - testErrorsAndWarnings('from a_index | stats round(min(dateField))', []); testErrorsAndWarnings( - 'from a_index | stats var = round(min(dateField)) + min(dateField)', - [] + 'from a_index | stats by bin(doubleField, integerField, integerField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] ); - testErrorsAndWarnings('from a_index | stats round(min(dateField)) + min(dateField)', []); - testErrorsAndWarnings('from a_index | sort min(numberField)', [ - 'SORT does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(doubleField, integerField, integerField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(numberField)', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(doubleField, integerField, integerField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(numberField) > 0', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(doubleField, integerField, longField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(dateField)', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(doubleField, integerField, longField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(dateField) > 0', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(doubleField, integerField, longField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = min(numberField)', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(doubleField, integerField, longField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = min(numberField) > 0', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(doubleField, integerField, longField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval min(numberField)', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(doubleField, integerField, longField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval min(numberField) > 0', [ - 'EVAL does not support function min', + testErrorsAndWarnings('from a_index | stats by bucket(integerField, doubleField)', [ + 'Argument of [bucket] must be a constant, received [doubleField]', ]); - testErrorsAndWarnings('from a_index | eval var = min(dateField)', [ - 'EVAL does not support function min', + testErrorsAndWarnings('from a_index | stats by bin(integerField, doubleField)', [ + 'Argument of [bin] must be a constant, received [doubleField]', ]); - testErrorsAndWarnings('from a_index | eval var = min(dateField) > 0', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, doubleField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | eval min(dateField)', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, doubleField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | eval min(dateField) > 0', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, doubleField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | stats min(booleanField)', []); - testErrorsAndWarnings('from a_index | stats min(null)', []); - testErrorsAndWarnings('row nullVar = null | stats min(nullVar)', []); - testErrorsAndWarnings('from a_index | stats min("2022")', []); - testErrorsAndWarnings('from a_index | stats min(concat("20", "22"))', [ - 'Argument of [min] must be [number], found value [concat("20","22")] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, doubleField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | stats min(cartesianPointField)', [ - 'Argument of [min] must be [number], found value [cartesianPointField] type [cartesian_point]', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, doubleField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | stats var = min(booleanField)', []); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, doubleField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(booleanField)', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, integerField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(booleanField) > 0', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, integerField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = min(booleanField)', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, integerField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = min(booleanField) > 0', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, integerField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval min(booleanField)', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, longField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | eval min(booleanField) > 0', [ - 'EVAL does not support function min', - ]); - testErrorsAndWarnings('from a_index | stats var = min(ipField)', []); - testErrorsAndWarnings('from a_index | stats min(ipField)', []); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, longField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(ipField)', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, longField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | where min(ipField) > 0', [ - 'WHERE does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, longField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = min(ipField)', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(integerField, integerField, longField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = min(ipField) > 0', [ - 'EVAL does not support function min', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(integerField, integerField, longField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval min(ipField)', [ - 'EVAL does not support function min', + testErrorsAndWarnings('from a_index | stats by bucket(longField, doubleField)', [ + 'Argument of [bucket] must be a constant, received [doubleField]', ]); - testErrorsAndWarnings('from a_index | eval min(ipField) > 0', [ - 'EVAL does not support function min', + testErrorsAndWarnings('from a_index | stats by bin(longField, doubleField)', [ + 'Argument of [bin] must be a constant, received [doubleField]', ]); - }); - - describe('count', () => { - testErrorsAndWarnings('from a_index | stats var = count(stringField)', []); - testErrorsAndWarnings('from a_index | stats count(stringField)', []); - testErrorsAndWarnings('from a_index | stats var = round(count(stringField))', []); - testErrorsAndWarnings('from a_index | stats round(count(stringField))', []); testErrorsAndWarnings( - 'from a_index | stats var = round(count(stringField)) + count(stringField)', - [] + 'from a_index | stats by bucket(longField, integerField, doubleField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats round(count(stringField)) + count(stringField)', - [] + 'from a_index | stats by bin(longField, integerField, doubleField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] ); - testErrorsAndWarnings('from a_index | sort count(stringField)', [ - 'SORT does not support function count', - ]); - - testErrorsAndWarnings('from a_index | where count(stringField)', [ - 'WHERE does not support function count', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(longField, integerField, doubleField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | where count(stringField) > 0', [ - 'WHERE does not support function count', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(longField, integerField, doubleField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = count(stringField)', [ - 'EVAL does not support function count', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(longField, integerField, doubleField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = count(stringField) > 0', [ - 'EVAL does not support function count', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(longField, integerField, doubleField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + 'Argument of [bin] must be a constant, received [longField]', + ] + ); - testErrorsAndWarnings('from a_index | eval count(stringField)', [ - 'EVAL does not support function count', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(longField, integerField, integerField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | eval count(stringField) > 0', [ - 'EVAL does not support function count', - ]); - testErrorsAndWarnings('from a_index | stats count(null)', []); - testErrorsAndWarnings('row nullVar = null | stats count(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats by bin(longField, integerField, integerField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] + ); - describe('count_distinct', () => { testErrorsAndWarnings( - 'from a_index | stats var = count_distinct(stringField, numberField)', - [] + 'from a_index | stats by bucket(longField, integerField, integerField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] ); - testErrorsAndWarnings('from a_index | stats count_distinct(stringField, numberField)', []); testErrorsAndWarnings( - 'from a_index | stats var = round(count_distinct(stringField, numberField))', - [] + 'from a_index | stats by bin(longField, integerField, integerField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats round(count_distinct(stringField, numberField))', - [] + 'from a_index | stats by bucket(longField, integerField, integerField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats var = round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)', - [] + 'from a_index | stats by bin(longField, integerField, integerField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + ] ); testErrorsAndWarnings( - 'from a_index | stats round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)', - [] + 'from a_index | stats by bucket(longField, integerField, longField, doubleField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [doubleField]', + ] ); - testErrorsAndWarnings('from a_index | sort count_distinct(stringField, numberField)', [ - 'SORT does not support function count_distinct', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(longField, integerField, longField, doubleField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [doubleField]', + ] + ); - testErrorsAndWarnings('from a_index | where count_distinct(stringField, numberField)', [ - 'WHERE does not support function count_distinct', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bucket(longField, integerField, longField, integerField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [integerField]', + ] + ); - testErrorsAndWarnings('from a_index | where count_distinct(stringField, numberField) > 0', [ - 'WHERE does not support function count_distinct', - ]); + testErrorsAndWarnings( + 'from a_index | stats by bin(longField, integerField, longField, integerField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [integerField]', + ] + ); testErrorsAndWarnings( - 'from a_index | eval var = count_distinct(stringField, numberField)', - ['EVAL does not support function count_distinct'] + 'from a_index | stats by bucket(longField, integerField, longField, longField)', + [ + 'Argument of [bucket] must be a constant, received [integerField]', + 'Argument of [bucket] must be a constant, received [longField]', + 'Argument of [bucket] must be a constant, received [longField]', + ] ); testErrorsAndWarnings( - 'from a_index | eval var = count_distinct(stringField, numberField) > 0', - ['EVAL does not support function count_distinct'] + 'from a_index | stats by bin(longField, integerField, longField, longField)', + [ + 'Argument of [bin] must be a constant, received [integerField]', + 'Argument of [bin] must be a constant, received [longField]', + 'Argument of [bin] must be a constant, received [longField]', + ] ); + }); - testErrorsAndWarnings('from a_index | eval count_distinct(stringField, numberField)', [ - 'EVAL does not support function count_distinct', + describe('percentile', () => { + testErrorsAndWarnings('from a_index | stats var = percentile(doubleField, doubleField)', [ + 'Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]', ]); - - testErrorsAndWarnings('from a_index | eval count_distinct(stringField, numberField) > 0', [ - 'EVAL does not support function count_distinct', + testErrorsAndWarnings('from a_index | stats percentile(doubleField, doubleField)', [ + 'Argument of [percentile] must be a constant, received [doubleField]', ]); - testErrorsAndWarnings('from a_index | stats count_distinct(null, null)', []); - testErrorsAndWarnings('row nullVar = null | stats count_distinct(nullVar, nullVar)', []); - }); - describe('st_centroid_agg', () => { testErrorsAndWarnings( - 'from a_index | stats var = st_centroid_agg(cartesianPointField)', - [] + 'from a_index | stats var = round(percentile(doubleField, doubleField))', + [ + 'Argument of [=] must be a constant, received [round(percentile(doubleField,doubleField))]', + ] ); - testErrorsAndWarnings('from a_index | stats st_centroid_agg(cartesianPointField)', []); - testErrorsAndWarnings('from a_index | stats var = st_centroid_agg(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", + testErrorsAndWarnings('from a_index | stats round(percentile(doubleField, doubleField))', [ + 'Argument of [round] must be a constant, received [percentile(doubleField,doubleField)]', ]); - testErrorsAndWarnings('from a_index | stats st_centroid_agg(avg(numberField))', [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(percentile(doubleField, doubleField)) + percentile(doubleField, doubleField)', + [ + 'Argument of [=] must be a constant, received [round(percentile(doubleField,doubleField))+percentile(doubleField,doubleField)]', + ] + ); - testErrorsAndWarnings('from a_index | stats st_centroid_agg(stringField)', [ - 'Argument of [st_centroid_agg] must be [cartesian_point], found value [stringField] type [string]', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(percentile(doubleField, doubleField)) + percentile(doubleField, doubleField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(doubleField,doubleField))]', + 'Argument of [+] must be a constant, received [percentile(doubleField,doubleField)]', + ] + ); - testErrorsAndWarnings('from a_index | stats var = st_centroid_agg(*)', [ - 'Using wildcards (*) in st_centroid_agg is not allowed', + testErrorsAndWarnings('from a_index | stats percentile(doubleField / 2, doubleField)', [ + 'Argument of [percentile] must be a constant, received [doubleField]', ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = percentile(doubleField / 2, doubleField)', + ['Argument of [=] must be a constant, received [percentile(doubleField/2,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | stats var = st_centroid_agg(geoPointField)', []); - testErrorsAndWarnings('from a_index | stats st_centroid_agg(geoPointField)', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField / 2, doubleField)', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - testErrorsAndWarnings('from a_index | sort st_centroid_agg(cartesianPointField)', [ - 'SORT does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField / 2, doubleField)', + ['Argument of [=] must be a constant, received [percentile(doubleField/2,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | where st_centroid_agg(cartesianPointField)', [ - 'WHERE does not support function st_centroid_agg', + testErrorsAndWarnings('from a_index | stats var0 = percentile(doubleField, doubleField)', [ + 'Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]', ]); - testErrorsAndWarnings('from a_index | where st_centroid_agg(cartesianPointField) > 0', [ - 'WHERE does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField, doubleField)', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - testErrorsAndWarnings('from a_index | where st_centroid_agg(geoPointField)', [ - 'WHERE does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, doubleField)', + ['Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | where st_centroid_agg(geoPointField) > 0', [ - 'WHERE does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats percentile(doubleField, doubleField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - testErrorsAndWarnings('from a_index | eval var = st_centroid_agg(cartesianPointField)', [ - 'EVAL does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = percentile(doubleField, doubleField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]'] + ); testErrorsAndWarnings( - 'from a_index | eval var = st_centroid_agg(cartesianPointField) > 0', - ['EVAL does not support function st_centroid_agg'] + 'from a_index | stats avg(doubleField), percentile(doubleField, doubleField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [doubleField]'] ); - testErrorsAndWarnings('from a_index | eval st_centroid_agg(cartesianPointField)', [ - 'EVAL does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, doubleField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | eval st_centroid_agg(cartesianPointField) > 0', [ - 'EVAL does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField, doubleField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - testErrorsAndWarnings('from a_index | eval var = st_centroid_agg(geoPointField)', [ - 'EVAL does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, doubleField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(doubleField,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | eval var = st_centroid_agg(geoPointField) > 0', [ - 'EVAL does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats var = percentile(avg(integerField), avg(integerField))', + [ + 'Argument of [=] must be a constant, received [percentile(avg(integerField),avg(integerField))]', + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ] + ); - testErrorsAndWarnings('from a_index | eval st_centroid_agg(geoPointField)', [ - 'EVAL does not support function st_centroid_agg', - ]); + testErrorsAndWarnings( + 'from a_index | stats percentile(avg(integerField), avg(integerField))', + [ + 'Argument of [percentile] must be a constant, received [avg(integerField)]', + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(integerField)] of type [double]", + ] + ); - testErrorsAndWarnings('from a_index | eval st_centroid_agg(geoPointField) > 0', [ - 'EVAL does not support function st_centroid_agg', + testErrorsAndWarnings('from a_index | stats percentile(booleanField, )', [ + "SyntaxError: no viable alternative at input 'percentile(booleanField, )'", + "SyntaxError: mismatched input ')' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + 'At least one aggregation or grouping expression required in [STATS]', ]); - testErrorsAndWarnings('from a_index | stats st_centroid_agg(booleanField)', [ - 'Argument of [st_centroid_agg] must be [cartesian_point], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | stats var = percentile(doubleField, longField)', [ + 'Argument of [=] must be a constant, received [percentile(doubleField,longField)]', + ]); + testErrorsAndWarnings('from a_index | stats percentile(doubleField, longField)', [ + 'Argument of [percentile] must be a constant, received [longField]', + ]); + testErrorsAndWarnings( + 'from a_index | stats var = round(percentile(doubleField, longField))', + [ + 'Argument of [=] must be a constant, received [round(percentile(doubleField,longField))]', + ] + ); + testErrorsAndWarnings('from a_index | stats round(percentile(doubleField, longField))', [ + 'Argument of [round] must be a constant, received [percentile(doubleField,longField)]', ]); - testErrorsAndWarnings('from a_index | stats st_centroid_agg(null)', []); - testErrorsAndWarnings('row nullVar = null | stats st_centroid_agg(nullVar)', []); - }); - describe('values', () => { - testErrorsAndWarnings('from a_index | stats var = values(stringField)', []); - testErrorsAndWarnings('from a_index | stats values(stringField)', []); + testErrorsAndWarnings( + 'from a_index | stats var = round(percentile(doubleField, longField)) + percentile(doubleField, longField)', + [ + 'Argument of [=] must be a constant, received [round(percentile(doubleField,longField))+percentile(doubleField,longField)]', + ] + ); - testErrorsAndWarnings('from a_index | sort values(stringField)', [ - 'SORT does not support function values', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(percentile(doubleField, longField)) + percentile(doubleField, longField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(doubleField,longField))]', + 'Argument of [+] must be a constant, received [percentile(doubleField,longField)]', + ] + ); - testErrorsAndWarnings('from a_index | where values(stringField)', [ - 'WHERE does not support function values', + testErrorsAndWarnings('from a_index | stats percentile(doubleField / 2, longField)', [ + 'Argument of [percentile] must be a constant, received [longField]', ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = percentile(doubleField / 2, longField)', + ['Argument of [=] must be a constant, received [percentile(doubleField/2,longField)]'] + ); - testErrorsAndWarnings('from a_index | where values(stringField) > 0', [ - 'WHERE does not support function values', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField / 2, longField)', + ['Argument of [percentile] must be a constant, received [longField]'] + ); - testErrorsAndWarnings('from a_index | eval var = values(stringField)', [ - 'EVAL does not support function values', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField / 2, longField)', + ['Argument of [=] must be a constant, received [percentile(doubleField/2,longField)]'] + ); - testErrorsAndWarnings('from a_index | eval var = values(stringField) > 0', [ - 'EVAL does not support function values', + testErrorsAndWarnings('from a_index | stats var0 = percentile(doubleField, longField)', [ + 'Argument of [=] must be a constant, received [percentile(doubleField,longField)]', ]); - testErrorsAndWarnings('from a_index | eval values(stringField)', [ - 'EVAL does not support function values', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField, longField)', + ['Argument of [percentile] must be a constant, received [longField]'] + ); - testErrorsAndWarnings('from a_index | eval values(stringField) > 0', [ - 'EVAL does not support function values', - ]); - testErrorsAndWarnings('from a_index | stats values(null)', []); - testErrorsAndWarnings('row nullVar = null | stats values(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, longField)', + ['Argument of [=] must be a constant, received [percentile(doubleField,longField)]'] + ); - describe('bucket', () => { - testErrorsAndWarnings('from a_index | stats by bucket(dateField, 1 year)', []); - testErrorsAndWarnings('from a_index | stats by bin(dateField, 1 year)', []); - testErrorsAndWarnings('from a_index | stats by bucket(numberField, 5)', []); + testErrorsAndWarnings( + 'from a_index | stats percentile(doubleField, longField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [longField]'] + ); - testErrorsAndWarnings('from a_index | stats by bucket(numberField, numberField)', [ - 'Argument of [bucket] must be a constant, received [numberField]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = percentile(doubleField, longField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(doubleField,longField)]'] + ); - testErrorsAndWarnings('from a_index | stats by bin(numberField, 5)', []); - testErrorsAndWarnings('from a_index | stats by bucket(dateField, 5, "a", "a")', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField, longField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [longField]'] + ); testErrorsAndWarnings( - 'from a_index | stats by bucket(dateField, numberField, stringField, stringField)', - [ - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [stringField]', - 'Argument of [bucket] must be a constant, received [stringField]', - ] + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, longField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(doubleField,longField)]'] ); - testErrorsAndWarnings('from a_index | stats by bin(dateField, 5, "a", "a")', []); - testErrorsAndWarnings('from a_index | stats by bucket(dateField, 5, now(), now())', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField, longField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [longField]'] + ); testErrorsAndWarnings( - 'from a_index | stats by bucket(dateField, numberField, dateField, dateField)', - [ - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [dateField]', - 'Argument of [bucket] must be a constant, received [dateField]', - ] + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, longField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(doubleField,longField)]'] ); - testErrorsAndWarnings('from a_index | stats by bin(dateField, 5, now(), now())', []); - testErrorsAndWarnings('from a_index | stats by bucket(dateField, 5, "a", now())', []); + testErrorsAndWarnings('from a_index | stats var = percentile(doubleField, integerField)', [ + 'Argument of [=] must be a constant, received [percentile(doubleField,integerField)]', + ]); + testErrorsAndWarnings('from a_index | stats percentile(doubleField, integerField)', [ + 'Argument of [percentile] must be a constant, received [integerField]', + ]); testErrorsAndWarnings( - 'from a_index | stats by bucket(dateField, numberField, stringField, dateField)', + 'from a_index | stats var = round(percentile(doubleField, integerField))', [ - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [stringField]', - 'Argument of [bucket] must be a constant, received [dateField]', + 'Argument of [=] must be a constant, received [round(percentile(doubleField,integerField))]', ] ); - testErrorsAndWarnings('from a_index | stats by bin(dateField, 5, "a", now())', []); - testErrorsAndWarnings('from a_index | stats by bucket(dateField, 5, now(), "a")', []); + testErrorsAndWarnings('from a_index | stats round(percentile(doubleField, integerField))', [ + 'Argument of [round] must be a constant, received [percentile(doubleField,integerField)]', + ]); testErrorsAndWarnings( - 'from a_index | stats by bucket(dateField, numberField, dateField, stringField)', + 'from a_index | stats var = round(percentile(doubleField, integerField)) + percentile(doubleField, integerField)', [ - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [dateField]', - 'Argument of [bucket] must be a constant, received [stringField]', + 'Argument of [=] must be a constant, received [round(percentile(doubleField,integerField))+percentile(doubleField,integerField)]', ] ); - testErrorsAndWarnings('from a_index | stats by bin(dateField, 5, now(), "a")', []); - testErrorsAndWarnings('from a_index | stats by bucket(numberField, 5, 5, 5)', []); - testErrorsAndWarnings( - 'from a_index | stats by bucket(numberField, numberField, numberField, numberField)', + 'from a_index | stats round(percentile(doubleField, integerField)) + percentile(doubleField, integerField)', [ - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [numberField]', + 'Argument of [+] must be a constant, received [round(percentile(doubleField,integerField))]', + 'Argument of [+] must be a constant, received [percentile(doubleField,integerField)]', ] ); - testErrorsAndWarnings('from a_index | stats by bin(numberField, 5, 5, 5)', []); - - testErrorsAndWarnings('from a_index | sort bucket(dateField, 1 year)', [ - 'SORT does not support function bucket', + testErrorsAndWarnings('from a_index | stats percentile(doubleField / 2, integerField)', [ + 'Argument of [percentile] must be a constant, received [integerField]', ]); - testErrorsAndWarnings('from a_index | stats bucket(null, null, null, null)', []); testErrorsAndWarnings( - 'row nullVar = null | stats bucket(nullVar, nullVar, nullVar, nullVar)', - [ - 'Argument of [bucket] must be a constant, received [nullVar]', - 'Argument of [bucket] must be a constant, received [nullVar]', - 'Argument of [bucket] must be a constant, received [nullVar]', - ] + 'from a_index | stats var0 = percentile(doubleField / 2, integerField)', + ['Argument of [=] must be a constant, received [percentile(doubleField/2,integerField)]'] ); - testErrorsAndWarnings('from a_index | stats bucket("2022", 1 year)', []); - testErrorsAndWarnings('from a_index | stats bucket(concat("20", "22"), 1 year)', [ - 'Argument of [bucket] must be [date], found value [concat("20","22")] type [string]', - ]); - testErrorsAndWarnings('from a_index | stats by bucket(concat("20", "22"), 1 year)', [ - 'Argument of [bucket] must be [date], found value [concat("20","22")] type [string]', - ]); - testErrorsAndWarnings('from a_index | stats bucket("2022", 5, "a", "a")', []); - testErrorsAndWarnings('from a_index | stats bucket(concat("20", "22"), 5, "a", "a")', [ - 'Argument of [bucket] must be [date], found value [concat("20","22")] type [string]', - ]); - testErrorsAndWarnings('from a_index | stats bucket("2022", 5, "2022", "2022")', []); testErrorsAndWarnings( - 'from a_index | stats bucket(concat("20", "22"), 5, concat("20", "22"), concat("20", "22"))', - ['Argument of [bucket] must be [date], found value [concat("20","22")] type [string]'] + 'from a_index | stats avg(doubleField), percentile(doubleField / 2, integerField)', + ['Argument of [percentile] must be a constant, received [integerField]'] ); - testErrorsAndWarnings('from a_index | stats bucket("2022", 5, "a", "2022")', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField / 2, integerField)', + ['Argument of [=] must be a constant, received [percentile(doubleField/2,integerField)]'] + ); + + testErrorsAndWarnings('from a_index | stats var0 = percentile(doubleField, integerField)', [ + 'Argument of [=] must be a constant, received [percentile(doubleField,integerField)]', + ]); testErrorsAndWarnings( - 'from a_index | stats bucket(concat("20", "22"), 5, "a", concat("20", "22"))', - ['Argument of [bucket] must be [date], found value [concat("20","22")] type [string]'] + 'from a_index | stats avg(doubleField), percentile(doubleField, integerField)', + ['Argument of [percentile] must be a constant, received [integerField]'] ); - testErrorsAndWarnings('from a_index | stats bucket("2022", 5, "2022", "a")', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, integerField)', + ['Argument of [=] must be a constant, received [percentile(doubleField,integerField)]'] + ); testErrorsAndWarnings( - 'from a_index | stats bucket(concat("20", "22"), 5, concat("20", "22"), "a")', - ['Argument of [bucket] must be [date], found value [concat("20","22")] type [string]'] + 'from a_index | stats percentile(doubleField, integerField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [integerField]'] ); - }); - describe('cbrt', () => { - testErrorsAndWarnings('row var = cbrt(5)', []); - testErrorsAndWarnings('row cbrt(5)', []); - testErrorsAndWarnings('row var = cbrt(to_integer(true))', []); + testErrorsAndWarnings( + 'from a_index | stats var0 = percentile(doubleField, integerField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(doubleField,integerField)]'] + ); - testErrorsAndWarnings('row var = cbrt(true)', [ - 'Argument of [cbrt] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField, integerField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [integerField]'] + ); - testErrorsAndWarnings('from a_index | where cbrt(numberField) > 0', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, integerField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(doubleField,integerField)]'] + ); - testErrorsAndWarnings('from a_index | where cbrt(booleanField) > 0', [ - 'Argument of [cbrt] must be [number], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(doubleField, integerField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [integerField]'] + ); - testErrorsAndWarnings('from a_index | eval var = cbrt(numberField)', []); - testErrorsAndWarnings('from a_index | eval cbrt(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = cbrt(to_integer(booleanField))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(doubleField, integerField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(doubleField,integerField)]'] + ); - testErrorsAndWarnings('from a_index | eval cbrt(booleanField)', [ - 'Argument of [cbrt] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | stats var = percentile(longField, doubleField)', [ + 'Argument of [=] must be a constant, received [percentile(longField,doubleField)]', ]); - - testErrorsAndWarnings('from a_index | eval var = cbrt(*)', [ - 'Using wildcards (*) in cbrt is not allowed', + testErrorsAndWarnings('from a_index | stats percentile(longField, doubleField)', [ + 'Argument of [percentile] must be a constant, received [doubleField]', ]); - - testErrorsAndWarnings('from a_index | eval cbrt(numberField, extraArg)', [ - 'Error: [cbrt] function expects exactly one argument, got 2.', + testErrorsAndWarnings( + 'from a_index | stats var = round(percentile(longField, doubleField))', + [ + 'Argument of [=] must be a constant, received [round(percentile(longField,doubleField))]', + ] + ); + testErrorsAndWarnings('from a_index | stats round(percentile(longField, doubleField))', [ + 'Argument of [round] must be a constant, received [percentile(longField,doubleField)]', ]); - testErrorsAndWarnings('from a_index | sort cbrt(numberField)', []); - testErrorsAndWarnings('from a_index | eval cbrt(null)', []); - testErrorsAndWarnings('row nullVar = null | eval cbrt(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats var = round(percentile(longField, doubleField)) + percentile(longField, doubleField)', + [ + 'Argument of [=] must be a constant, received [round(percentile(longField,doubleField))+percentile(longField,doubleField)]', + ] + ); - describe('from_base64', () => { - testErrorsAndWarnings('row var = from_base64("a")', []); - testErrorsAndWarnings('row from_base64("a")', []); - testErrorsAndWarnings('row var = from_base64(to_string(true))', []); + testErrorsAndWarnings( + 'from a_index | stats round(percentile(longField, doubleField)) + percentile(longField, doubleField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(longField,doubleField))]', + 'Argument of [+] must be a constant, received [percentile(longField,doubleField)]', + ] + ); - testErrorsAndWarnings('row var = from_base64(true)', [ - 'Argument of [from_base64] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | stats var0 = percentile(longField, doubleField)', [ + 'Argument of [=] must be a constant, received [percentile(longField,doubleField)]', ]); - testErrorsAndWarnings('from a_index | where length(from_base64(stringField)) > 0', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(longField, doubleField)', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - testErrorsAndWarnings('from a_index | where length(from_base64(booleanField)) > 0', [ - 'Argument of [from_base64] must be [string], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(longField, doubleField)', + ['Argument of [=] must be a constant, received [percentile(longField,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | eval var = from_base64(stringField)', []); - testErrorsAndWarnings('from a_index | eval from_base64(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = from_base64(to_string(booleanField))', []); + testErrorsAndWarnings( + 'from a_index | stats percentile(longField, doubleField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - testErrorsAndWarnings('from a_index | eval from_base64(booleanField)', [ - 'Argument of [from_base64] must be [string], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats var0 = percentile(longField, doubleField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(longField,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | eval var = from_base64(*)', [ - 'Using wildcards (*) in from_base64 is not allowed', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(longField, doubleField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - testErrorsAndWarnings('from a_index | eval from_base64(stringField, extraArg)', [ - 'Error: [from_base64] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(longField, doubleField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(longField,doubleField)]'] + ); - testErrorsAndWarnings('from a_index | sort from_base64(stringField)', []); - testErrorsAndWarnings('from a_index | eval from_base64(null)', []); - testErrorsAndWarnings('row nullVar = null | eval from_base64(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(longField, doubleField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [doubleField]'] + ); - describe('locate', () => { - testErrorsAndWarnings('row var = locate("a", "a")', []); - testErrorsAndWarnings('row locate("a", "a")', []); - testErrorsAndWarnings('row var = locate(to_string(true), to_string(true))', []); - testErrorsAndWarnings('row var = locate("a", "a", 5)', []); - testErrorsAndWarnings('row locate("a", "a", 5)', []); testErrorsAndWarnings( - 'row var = locate(to_string(true), to_string(true), to_integer(true))', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(longField, doubleField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(longField,doubleField)]'] ); - testErrorsAndWarnings('row var = locate(true, true, true)', [ - 'Argument of [locate] must be [string], found value [true] type [boolean]', - 'Argument of [locate] must be [string], found value [true] type [boolean]', - 'Argument of [locate] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | stats var = percentile(longField, longField)', [ + 'Argument of [=] must be a constant, received [percentile(longField,longField)]', ]); - - testErrorsAndWarnings('from a_index | where locate(stringField, stringField) > 0', []); - - testErrorsAndWarnings('from a_index | where locate(booleanField, booleanField) > 0', [ - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | stats percentile(longField, longField)', [ + 'Argument of [percentile] must be a constant, received [longField]', ]); - testErrorsAndWarnings( - 'from a_index | where locate(stringField, stringField, numberField) > 0', - [] + 'from a_index | stats var = round(percentile(longField, longField))', + ['Argument of [=] must be a constant, received [round(percentile(longField,longField))]'] ); + testErrorsAndWarnings('from a_index | stats round(percentile(longField, longField))', [ + 'Argument of [round] must be a constant, received [percentile(longField,longField)]', + ]); testErrorsAndWarnings( - 'from a_index | where locate(booleanField, booleanField, booleanField) > 0', + 'from a_index | stats var = round(percentile(longField, longField)) + percentile(longField, longField)', [ - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', - 'Argument of [locate] must be [number], found value [booleanField] type [boolean]', + 'Argument of [=] must be a constant, received [round(percentile(longField,longField))+percentile(longField,longField)]', ] ); - testErrorsAndWarnings('from a_index | eval var = locate(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval locate(stringField, stringField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = locate(to_string(booleanField), to_string(booleanField))', - [] + 'from a_index | stats round(percentile(longField, longField)) + percentile(longField, longField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(longField,longField))]', + 'Argument of [+] must be a constant, received [percentile(longField,longField)]', + ] ); - testErrorsAndWarnings('from a_index | eval locate(booleanField, booleanField)', [ - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | stats var0 = percentile(longField, longField)', [ + 'Argument of [=] must be a constant, received [percentile(longField,longField)]', ]); testErrorsAndWarnings( - 'from a_index | eval var = locate(stringField, stringField, numberField)', - [] + 'from a_index | stats avg(doubleField), percentile(longField, longField)', + ['Argument of [percentile] must be a constant, received [longField]'] ); testErrorsAndWarnings( - 'from a_index | eval locate(stringField, stringField, numberField)', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(longField, longField)', + ['Argument of [=] must be a constant, received [percentile(longField,longField)]'] ); testErrorsAndWarnings( - 'from a_index | eval var = locate(to_string(booleanField), to_string(booleanField), to_integer(booleanField))', - [] + 'from a_index | stats percentile(longField, longField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [longField]'] ); testErrorsAndWarnings( - 'from a_index | eval locate(booleanField, booleanField, booleanField)', - [ - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', - 'Argument of [locate] must be [string], found value [booleanField] type [boolean]', - 'Argument of [locate] must be [number], found value [booleanField] type [boolean]', - ] + 'from a_index | stats var0 = percentile(longField, longField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(longField,longField)]'] ); testErrorsAndWarnings( - 'from a_index | eval locate(stringField, stringField, numberField, extraArg)', - ['Error: [locate] function expects no more than 3 arguments, got 4.'] + 'from a_index | stats avg(doubleField), percentile(longField, longField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [longField]'] ); - testErrorsAndWarnings('from a_index | sort locate(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval locate(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval locate(nullVar, nullVar, nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(longField, longField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(longField,longField)]'] + ); - describe('to_base64', () => { - testErrorsAndWarnings('row var = to_base64("a")', []); - testErrorsAndWarnings('row to_base64("a")', []); - testErrorsAndWarnings('row var = to_base64(to_string(true))', []); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(longField, longField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [longField]'] + ); - testErrorsAndWarnings('row var = to_base64(true)', [ - 'Argument of [to_base64] must be [string], found value [true] type [boolean]', + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(longField, longField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(longField,longField)]'] + ); + + testErrorsAndWarnings('from a_index | stats var = percentile(longField, integerField)', [ + 'Argument of [=] must be a constant, received [percentile(longField,integerField)]', + ]); + testErrorsAndWarnings('from a_index | stats percentile(longField, integerField)', [ + 'Argument of [percentile] must be a constant, received [integerField]', ]); - testErrorsAndWarnings('from a_index | where length(to_base64(stringField)) > 0', []); + testErrorsAndWarnings( + 'from a_index | stats var = round(percentile(longField, integerField))', + [ + 'Argument of [=] must be a constant, received [round(percentile(longField,integerField))]', + ] + ); - testErrorsAndWarnings('from a_index | where length(to_base64(booleanField)) > 0', [ - 'Argument of [to_base64] must be [string], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | stats round(percentile(longField, integerField))', [ + 'Argument of [round] must be a constant, received [percentile(longField,integerField)]', ]); - testErrorsAndWarnings('from a_index | eval var = to_base64(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_base64(stringField)', []); - testErrorsAndWarnings('from a_index | eval var = to_base64(to_string(booleanField))', []); + testErrorsAndWarnings( + 'from a_index | stats var = round(percentile(longField, integerField)) + percentile(longField, integerField)', + [ + 'Argument of [=] must be a constant, received [round(percentile(longField,integerField))+percentile(longField,integerField)]', + ] + ); - testErrorsAndWarnings('from a_index | eval to_base64(booleanField)', [ - 'Argument of [to_base64] must be [string], found value [booleanField] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats round(percentile(longField, integerField)) + percentile(longField, integerField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(longField,integerField))]', + 'Argument of [+] must be a constant, received [percentile(longField,integerField)]', + ] + ); - testErrorsAndWarnings('from a_index | eval var = to_base64(*)', [ - 'Using wildcards (*) in to_base64 is not allowed', + testErrorsAndWarnings('from a_index | stats var0 = percentile(longField, integerField)', [ + 'Argument of [=] must be a constant, received [percentile(longField,integerField)]', ]); - testErrorsAndWarnings('from a_index | eval to_base64(stringField, extraArg)', [ - 'Error: [to_base64] function expects exactly one argument, got 2.', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(longField, integerField)', + ['Argument of [percentile] must be a constant, received [integerField]'] + ); - testErrorsAndWarnings('from a_index | sort to_base64(stringField)', []); - testErrorsAndWarnings('from a_index | eval to_base64(null)', []); - testErrorsAndWarnings('row nullVar = null | eval to_base64(nullVar)', []); - }); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), var0 = percentile(longField, integerField)', + ['Argument of [=] must be a constant, received [percentile(longField,integerField)]'] + ); - describe('ip_prefix', () => { - testErrorsAndWarnings('row var = ip_prefix(to_ip("127.0.0.1"), 5, 5)', []); - testErrorsAndWarnings('row ip_prefix(to_ip("127.0.0.1"), 5, 5)', []); + testErrorsAndWarnings( + 'from a_index | stats percentile(longField, integerField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [integerField]'] + ); testErrorsAndWarnings( - 'row var = ip_prefix(to_ip(to_ip("127.0.0.1")), to_integer(true), to_integer(true))', - [] + 'from a_index | stats var0 = percentile(longField, integerField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(longField,integerField)]'] ); - testErrorsAndWarnings('row var = ip_prefix(true, true, true)', [ - 'Argument of [ip_prefix] must be [ip], found value [true] type [boolean]', - 'Argument of [ip_prefix] must be [number], found value [true] type [boolean]', - 'Argument of [ip_prefix] must be [number], found value [true] type [boolean]', - ]); + testErrorsAndWarnings( + 'from a_index | stats avg(doubleField), percentile(longField, integerField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [integerField]'] + ); testErrorsAndWarnings( - 'from a_index | eval var = ip_prefix(ipField, numberField, numberField)', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(longField, integerField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(longField,integerField)]'] ); testErrorsAndWarnings( - 'from a_index | eval ip_prefix(ipField, numberField, numberField)', - [] + 'from a_index | stats avg(doubleField), percentile(longField, integerField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [integerField]'] ); testErrorsAndWarnings( - 'from a_index | eval var = ip_prefix(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(longField, integerField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(longField,integerField)]'] ); + testErrorsAndWarnings('from a_index | stats var = percentile(integerField, doubleField)', [ + 'Argument of [=] must be a constant, received [percentile(integerField,doubleField)]', + ]); + testErrorsAndWarnings('from a_index | stats percentile(integerField, doubleField)', [ + 'Argument of [percentile] must be a constant, received [doubleField]', + ]); + testErrorsAndWarnings( - 'from a_index | eval ip_prefix(booleanField, booleanField, booleanField)', + 'from a_index | stats var = round(percentile(integerField, doubleField))', [ - 'Argument of [ip_prefix] must be [ip], found value [booleanField] type [boolean]', - 'Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]', - 'Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]', + 'Argument of [=] must be a constant, received [round(percentile(integerField,doubleField))]', ] ); - testErrorsAndWarnings( - 'from a_index | eval ip_prefix(ipField, numberField, numberField, extraArg)', - ['Error: [ip_prefix] function expects exactly 3 arguments, got 4.'] - ); + testErrorsAndWarnings('from a_index | stats round(percentile(integerField, doubleField))', [ + 'Argument of [round] must be a constant, received [percentile(integerField,doubleField)]', + ]); testErrorsAndWarnings( - 'from a_index | sort ip_prefix(ipField, numberField, numberField)', - [] + 'from a_index | stats var = round(percentile(integerField, doubleField)) + percentile(integerField, doubleField)', + [ + 'Argument of [=] must be a constant, received [round(percentile(integerField,doubleField))+percentile(integerField,doubleField)]', + ] ); - testErrorsAndWarnings('from a_index | eval ip_prefix(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval ip_prefix(nullVar, nullVar, nullVar)', []); - }); - - describe('mv_append', () => { - testErrorsAndWarnings('row var = mv_append(true, true)', []); - testErrorsAndWarnings('row mv_append(true, true)', []); - testErrorsAndWarnings('row var = mv_append(to_boolean(true), to_boolean(true))', []); testErrorsAndWarnings( - 'row var = mv_append(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', - [] + 'from a_index | stats round(percentile(integerField, doubleField)) + percentile(integerField, doubleField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(integerField,doubleField))]', + 'Argument of [+] must be a constant, received [percentile(integerField,doubleField)]', + ] ); + testErrorsAndWarnings('from a_index | stats var0 = percentile(integerField, doubleField)', [ + 'Argument of [=] must be a constant, received [percentile(integerField,doubleField)]', + ]); + testErrorsAndWarnings( - 'row mv_append(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, doubleField)', + ['Argument of [percentile] must be a constant, received [doubleField]'] ); testErrorsAndWarnings( - 'row var = mv_append(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, doubleField)', + ['Argument of [=] must be a constant, received [percentile(integerField,doubleField)]'] ); testErrorsAndWarnings( - 'row var = mv_append(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', - [] + 'from a_index | stats percentile(integerField, doubleField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [doubleField]'] ); testErrorsAndWarnings( - 'row mv_append(to_cartesianshape("POINT (30 10)"), to_cartesianshape("POINT (30 10)"))', - [] + 'from a_index | stats var0 = percentile(integerField, doubleField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(integerField,doubleField)]'] ); testErrorsAndWarnings( - 'row var = mv_append(to_cartesianshape(to_cartesianpoint("POINT (30 10)")), to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, doubleField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [doubleField]'] ); - testErrorsAndWarnings('row var = mv_append(now(), now())', []); - testErrorsAndWarnings('row mv_append(now(), now())', []); - testErrorsAndWarnings('row var = mv_append(to_datetime(now()), to_datetime(now()))', []); - testErrorsAndWarnings('row var = mv_append(5, 5)', []); - testErrorsAndWarnings('row mv_append(5, 5)', []); - testErrorsAndWarnings('row var = mv_append(to_integer(true), to_integer(true))', []); - testErrorsAndWarnings( - 'row var = mv_append(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, doubleField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(integerField,doubleField)]'] ); testErrorsAndWarnings( - 'row mv_append(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, doubleField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [doubleField]'] ); testErrorsAndWarnings( - 'row var = mv_append(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, doubleField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(integerField,doubleField)]'] ); + testErrorsAndWarnings('from a_index | stats var = percentile(integerField, longField)', [ + 'Argument of [=] must be a constant, received [percentile(integerField,longField)]', + ]); + testErrorsAndWarnings('from a_index | stats percentile(integerField, longField)', [ + 'Argument of [percentile] must be a constant, received [longField]', + ]); + testErrorsAndWarnings( - 'row var = mv_append(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', - [] + 'from a_index | stats var = round(percentile(integerField, longField))', + [ + 'Argument of [=] must be a constant, received [round(percentile(integerField,longField))]', + ] ); + testErrorsAndWarnings('from a_index | stats round(percentile(integerField, longField))', [ + 'Argument of [round] must be a constant, received [percentile(integerField,longField)]', + ]); + testErrorsAndWarnings( - 'row mv_append(to_geoshape("POINT (30 10)"), to_geoshape("POINT (30 10)"))', - [] + 'from a_index | stats var = round(percentile(integerField, longField)) + percentile(integerField, longField)', + [ + 'Argument of [=] must be a constant, received [round(percentile(integerField,longField))+percentile(integerField,longField)]', + ] ); testErrorsAndWarnings( - 'row var = mv_append(to_geoshape(to_geopoint("POINT (30 10)")), to_geoshape(to_geopoint("POINT (30 10)")))', - [] + 'from a_index | stats round(percentile(integerField, longField)) + percentile(integerField, longField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(integerField,longField))]', + 'Argument of [+] must be a constant, received [percentile(integerField,longField)]', + ] ); - testErrorsAndWarnings('row var = mv_append(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); - testErrorsAndWarnings('row mv_append(to_ip("127.0.0.1"), to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('from a_index | stats var0 = percentile(integerField, longField)', [ + 'Argument of [=] must be a constant, received [percentile(integerField,longField)]', + ]); testErrorsAndWarnings( - 'row var = mv_append(to_ip(to_ip("127.0.0.1")), to_ip(to_ip("127.0.0.1")))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, longField)', + ['Argument of [percentile] must be a constant, received [longField]'] ); - testErrorsAndWarnings('row var = mv_append("a", "a")', []); - testErrorsAndWarnings('row mv_append("a", "a")', []); - testErrorsAndWarnings('row var = mv_append(to_string(true), to_string(true))', []); - testErrorsAndWarnings('row var = mv_append(to_version("1.0.0"), to_version("1.0.0"))', []); - testErrorsAndWarnings('row mv_append(to_version("1.0.0"), to_version("1.0.0"))', []); - testErrorsAndWarnings('row var = mv_append(to_version("a"), to_version("a"))', []); - testErrorsAndWarnings('from a_index | where mv_append(numberField, numberField) > 0', []); testErrorsAndWarnings( - 'from a_index | where length(mv_append(stringField, stringField)) > 0', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, longField)', + ['Argument of [=] must be a constant, received [percentile(integerField,longField)]'] ); + testErrorsAndWarnings( - 'from a_index | eval var = mv_append(booleanField, booleanField)', - [] + 'from a_index | stats percentile(integerField, longField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [longField]'] ); - testErrorsAndWarnings('from a_index | eval mv_append(booleanField, booleanField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_boolean(booleanField), to_boolean(booleanField))', - [] + 'from a_index | stats var0 = percentile(integerField, longField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(integerField,longField)]'] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(cartesianPointField, cartesianPointField)', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, longField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [longField]'] ); testErrorsAndWarnings( - 'from a_index | eval mv_append(cartesianPointField, cartesianPointField)', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, longField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(integerField,longField)]'] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, longField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [longField]'] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(cartesianShapeField, cartesianShapeField)', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, longField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(integerField,longField)]'] ); + testErrorsAndWarnings('from a_index | stats var = percentile(integerField, integerField)', [ + 'Argument of [=] must be a constant, received [percentile(integerField,integerField)]', + ]); + testErrorsAndWarnings('from a_index | stats percentile(integerField, integerField)', [ + 'Argument of [percentile] must be a constant, received [integerField]', + ]); + testErrorsAndWarnings( - 'from a_index | eval mv_append(cartesianShapeField, cartesianShapeField)', - [] + 'from a_index | stats var = round(percentile(integerField, integerField))', + [ + 'Argument of [=] must be a constant, received [round(percentile(integerField,integerField))]', + ] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))', - [] + 'from a_index | stats round(percentile(integerField, integerField))', + [ + 'Argument of [round] must be a constant, received [percentile(integerField,integerField)]', + ] ); - testErrorsAndWarnings('from a_index | eval var = mv_append(dateField, dateField)', []); - testErrorsAndWarnings('from a_index | eval mv_append(dateField, dateField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_datetime(dateField), to_datetime(dateField))', - [] + 'from a_index | stats var = round(percentile(integerField, integerField)) + percentile(integerField, integerField)', + [ + 'Argument of [=] must be a constant, received [round(percentile(integerField,integerField))+percentile(integerField,integerField)]', + ] ); - testErrorsAndWarnings('from a_index | eval var = mv_append(numberField, numberField)', []); - testErrorsAndWarnings('from a_index | eval mv_append(numberField, numberField)', []); - testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_integer(booleanField), to_integer(booleanField))', - [] + 'from a_index | stats round(percentile(integerField, integerField)) + percentile(integerField, integerField)', + [ + 'Argument of [+] must be a constant, received [round(percentile(integerField,integerField))]', + 'Argument of [+] must be a constant, received [percentile(integerField,integerField)]', + ] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(geoPointField, geoPointField)', - [] + 'from a_index | stats var0 = percentile(integerField, integerField)', + ['Argument of [=] must be a constant, received [percentile(integerField,integerField)]'] ); - testErrorsAndWarnings('from a_index | eval mv_append(geoPointField, geoPointField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, integerField)', + ['Argument of [percentile] must be a constant, received [integerField]'] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(geoShapeField, geoShapeField)', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, integerField)', + ['Argument of [=] must be a constant, received [percentile(integerField,integerField)]'] ); - testErrorsAndWarnings('from a_index | eval mv_append(geoShapeField, geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))', - [] + 'from a_index | stats percentile(integerField, integerField) by round(doubleField / 2)', + ['Argument of [percentile] must be a constant, received [integerField]'] ); - testErrorsAndWarnings('from a_index | eval var = mv_append(ipField, ipField)', []); - testErrorsAndWarnings('from a_index | eval mv_append(ipField, ipField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_ip(ipField), to_ip(ipField))', - [] + 'from a_index | stats var0 = percentile(integerField, integerField) by var1 = round(doubleField / 2)', + ['Argument of [=] must be a constant, received [percentile(integerField,integerField)]'] ); - testErrorsAndWarnings('from a_index | eval var = mv_append(stringField, stringField)', []); - testErrorsAndWarnings('from a_index | eval mv_append(stringField, stringField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_string(booleanField), to_string(booleanField))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, integerField) by round(doubleField / 2), ipField', + ['Argument of [percentile] must be a constant, received [integerField]'] ); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(versionField, versionField)', - [] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, integerField) by var1 = round(doubleField / 2), ipField', + ['Argument of [=] must be a constant, received [percentile(integerField,integerField)]'] ); - testErrorsAndWarnings('from a_index | eval mv_append(versionField, versionField)', []); testErrorsAndWarnings( - 'from a_index | eval var = mv_append(to_version(stringField), to_version(stringField))', - [] + 'from a_index | stats avg(doubleField), percentile(integerField, integerField) by round(doubleField / 2), doubleField / 2', + ['Argument of [percentile] must be a constant, received [integerField]'] ); testErrorsAndWarnings( - 'from a_index | eval mv_append(booleanField, booleanField, extraArg)', - ['Error: [mv_append] function expects exactly 2 arguments, got 3.'] + 'from a_index | stats avg(doubleField), var0 = percentile(integerField, integerField) by var1 = round(doubleField / 2), doubleField / 2', + ['Argument of [=] must be a constant, received [percentile(integerField,integerField)]'] ); - testErrorsAndWarnings('from a_index | sort mv_append(booleanField, booleanField)', []); - testErrorsAndWarnings('from a_index | eval mv_append(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval mv_append(nullVar, nullVar)', []); - }); + testErrorsAndWarnings('from a_index | sort percentile(doubleField, doubleField)', [ + 'SORT does not support function percentile', + ]); - describe('repeat', () => { - testErrorsAndWarnings('row var = repeat("a", 5)', []); - testErrorsAndWarnings('row repeat("a", 5)', []); - testErrorsAndWarnings('row var = repeat(to_string(true), to_integer(true))', []); + testErrorsAndWarnings('from a_index | where percentile(doubleField, doubleField)', [ + 'WHERE does not support function percentile', + ]); - testErrorsAndWarnings('row var = repeat(true, true)', [ - 'Argument of [repeat] must be [string], found value [true] type [boolean]', - 'Argument of [repeat] must be [number], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | where percentile(doubleField, doubleField) > 0', [ + 'WHERE does not support function percentile', ]); - testErrorsAndWarnings( - 'from a_index | where length(repeat(stringField, numberField)) > 0', - [] - ); + testErrorsAndWarnings('from a_index | where percentile(doubleField, longField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(doubleField, longField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(doubleField, integerField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(doubleField, integerField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(longField, doubleField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(longField, doubleField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(longField, longField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(longField, longField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(longField, integerField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(longField, integerField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(integerField, doubleField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(integerField, doubleField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(integerField, longField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(integerField, longField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(integerField, integerField)', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | where percentile(integerField, integerField) > 0', [ + 'WHERE does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | eval var = percentile(doubleField, doubleField)', [ + 'EVAL does not support function percentile', + ]); testErrorsAndWarnings( - 'from a_index | where length(repeat(booleanField, booleanField)) > 0', - [ - 'Argument of [repeat] must be [string], found value [booleanField] type [boolean]', - 'Argument of [repeat] must be [number], found value [booleanField] type [boolean]', - ] + 'from a_index | eval var = percentile(doubleField, doubleField) > 0', + ['EVAL does not support function percentile'] ); - testErrorsAndWarnings('from a_index | eval var = repeat(stringField, numberField)', []); - testErrorsAndWarnings('from a_index | eval repeat(stringField, numberField)', []); + testErrorsAndWarnings('from a_index | eval percentile(doubleField, doubleField)', [ + 'EVAL does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | eval percentile(doubleField, doubleField) > 0', [ + 'EVAL does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | eval var = percentile(doubleField, longField)', [ + 'EVAL does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | eval var = percentile(doubleField, longField) > 0', [ + 'EVAL does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | eval percentile(doubleField, longField)', [ + 'EVAL does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | eval percentile(doubleField, longField) > 0', [ + 'EVAL does not support function percentile', + ]); + + testErrorsAndWarnings('from a_index | eval var = percentile(doubleField, integerField)', [ + 'EVAL does not support function percentile', + ]); testErrorsAndWarnings( - 'from a_index | eval var = repeat(to_string(booleanField), to_integer(booleanField))', - [] + 'from a_index | eval var = percentile(doubleField, integerField) > 0', + ['EVAL does not support function percentile'] ); - testErrorsAndWarnings('from a_index | eval repeat(booleanField, booleanField)', [ - 'Argument of [repeat] must be [string], found value [booleanField] type [boolean]', - 'Argument of [repeat] must be [number], found value [booleanField] type [boolean]', + testErrorsAndWarnings('from a_index | eval percentile(doubleField, integerField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | eval repeat(stringField, numberField, extraArg)', [ - 'Error: [repeat] function expects exactly 2 arguments, got 3.', + testErrorsAndWarnings('from a_index | eval percentile(doubleField, integerField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | sort repeat(stringField, numberField)', []); - testErrorsAndWarnings('from a_index | eval repeat(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval repeat(nullVar, nullVar)', []); - }); - - describe('top', () => { - describe('no errors on correct usage', () => { - testErrorsAndWarnings('from a_index | stats var = top(stringField, 3, "asc")', []); - testErrorsAndWarnings('from a_index | stats top(stringField, 1, "desc")', []); - testErrorsAndWarnings('from a_index | stats var = top(stringField, 5, "asc")', []); - testErrorsAndWarnings('from a_index | stats top(stringField, 5, "asc")', []); - }); + testErrorsAndWarnings('from a_index | eval var = percentile(longField, doubleField)', [ + 'EVAL does not support function percentile', + ]); - describe('errors on invalid argument count', () => { - testErrorsAndWarnings('from a_index | stats var = top(stringField, 3)', [ - 'Error: [top] function expects exactly 3 arguments, got 2.', - ]); - testErrorsAndWarnings('from a_index | stats var = top(stringField)', [ - 'Error: [top] function expects exactly 3 arguments, got 1.', - ]); - }); + testErrorsAndWarnings('from a_index | eval var = percentile(longField, doubleField) > 0', [ + 'EVAL does not support function percentile', + ]); - describe('limit must be a literal', () => { - testErrorsAndWarnings('from a_index | stats var = top(stringField, numberField, "asc")', [ - 'Argument of [=] must be a constant, received [top(stringField,numberField,"asc")]', - ]); - testErrorsAndWarnings( - 'from a_index | stats var = top(stringField, 100 + numberField, "asc")', - [ - 'Argument of [=] must be a constant, received [top(stringField,100+numberField,"asc")]', - ] - ); - }); + testErrorsAndWarnings('from a_index | eval percentile(longField, doubleField)', [ + 'EVAL does not support function percentile', + ]); - describe('order must be "asc" or "desc"', () => { - testErrorsAndWarnings('from a_index | stats var = top(stringField, 1, stringField)', [ - 'Argument of [=] must be a constant, received [top(stringField,1,stringField)]', - ]); - testErrorsAndWarnings( - 'from a_index | stats var = top(stringField, 1, "asdf")', - [], - ['Invalid option ["asdf"] for top. Supported options: ["asc", "desc"].'] - ); - }); + testErrorsAndWarnings('from a_index | eval percentile(longField, doubleField) > 0', [ + 'EVAL does not support function percentile', + ]); - testErrorsAndWarnings('from a_index | sort top(stringField, numberField, "asc")', [ - 'SORT does not support function top', + testErrorsAndWarnings('from a_index | eval var = percentile(longField, longField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | where top(stringField, numberField, "asc")', [ - 'WHERE does not support function top', + testErrorsAndWarnings('from a_index | eval var = percentile(longField, longField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | where top(stringField, numberField, "asc") > 0', [ - 'WHERE does not support function top', + testErrorsAndWarnings('from a_index | eval percentile(longField, longField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | eval var = top(stringField, numberField, "asc")', [ - 'EVAL does not support function top', + testErrorsAndWarnings('from a_index | eval percentile(longField, longField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings( - 'from a_index | eval var = top(stringField, numberField, "asc") > 0', - ['EVAL does not support function top'] - ); + testErrorsAndWarnings('from a_index | eval var = percentile(longField, integerField)', [ + 'EVAL does not support function percentile', + ]); - testErrorsAndWarnings('from a_index | eval top(stringField, numberField, "asc")', [ - 'EVAL does not support function top', + testErrorsAndWarnings('from a_index | eval var = percentile(longField, integerField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | eval top(stringField, numberField, "asc") > 0', [ - 'EVAL does not support function top', + testErrorsAndWarnings('from a_index | eval percentile(longField, integerField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | sort top(stringField, 5, "asc")', [ - 'SORT does not support function top', + testErrorsAndWarnings('from a_index | eval percentile(longField, integerField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | where top(stringField, 5, "asc")', [ - 'WHERE does not support function top', + testErrorsAndWarnings('from a_index | eval var = percentile(integerField, doubleField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | where top(stringField, 5, "asc") > 0', [ - 'WHERE does not support function top', + testErrorsAndWarnings( + 'from a_index | eval var = percentile(integerField, doubleField) > 0', + ['EVAL does not support function percentile'] + ); + + testErrorsAndWarnings('from a_index | eval percentile(integerField, doubleField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | eval var = top(stringField, 5, "asc")', [ - 'EVAL does not support function top', + testErrorsAndWarnings('from a_index | eval percentile(integerField, doubleField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | eval var = top(stringField, 5, "asc") > 0', [ - 'EVAL does not support function top', + testErrorsAndWarnings('from a_index | eval var = percentile(integerField, longField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | eval top(stringField, 5, "asc")', [ - 'EVAL does not support function top', + testErrorsAndWarnings('from a_index | eval var = percentile(integerField, longField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | eval top(stringField, 5, "asc") > 0', [ - 'EVAL does not support function top', + testErrorsAndWarnings('from a_index | eval percentile(integerField, longField)', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | stats var = top(stringField, 5, "asc")', []); - testErrorsAndWarnings('from a_index | stats top(stringField, 5, "asc")', []); - testErrorsAndWarnings('from a_index | stats top(stringField, numberField, "asc")', [ - 'Argument of [top] must be a constant, received [numberField]', + testErrorsAndWarnings('from a_index | eval percentile(integerField, longField) > 0', [ + 'EVAL does not support function percentile', ]); - testErrorsAndWarnings('from a_index | stats top(null, null, null)', []); - testErrorsAndWarnings('row nullVar = null | stats top(nullVar, nullVar, nullVar)', [ - 'Argument of [top] must be a constant, received [nullVar]', - 'Argument of [top] must be a constant, received [nullVar]', + testErrorsAndWarnings('from a_index | eval var = percentile(integerField, integerField)', [ + 'EVAL does not support function percentile', ]); - }); - - describe('st_distance', () => { - testErrorsAndWarnings( - 'row var = st_distance(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', - [] - ); - - testErrorsAndWarnings( - 'row st_distance(to_cartesianpoint("POINT (30 10)"), to_cartesianpoint("POINT (30 10)"))', - [] - ); - - testErrorsAndWarnings( - 'row var = st_distance(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")), to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', - [] - ); testErrorsAndWarnings( - 'row var = st_distance(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', - [] + 'from a_index | eval var = percentile(integerField, integerField) > 0', + ['EVAL does not support function percentile'] ); - testErrorsAndWarnings( - 'row st_distance(to_geopoint("POINT (30 10)"), to_geopoint("POINT (30 10)"))', - [] - ); + testErrorsAndWarnings('from a_index | eval percentile(integerField, integerField)', [ + 'EVAL does not support function percentile', + ]); - testErrorsAndWarnings( - 'row var = st_distance(to_geopoint(to_geopoint("POINT (30 10)")), to_geopoint(to_geopoint("POINT (30 10)")))', - [] - ); + testErrorsAndWarnings('from a_index | eval percentile(integerField, integerField) > 0', [ + 'EVAL does not support function percentile', + ]); - testErrorsAndWarnings('row var = st_distance(true, true)', [ - 'Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]', - 'Argument of [st_distance] must be [cartesian_point], found value [true] type [boolean]', + testErrorsAndWarnings('from a_index | stats percentile(null, null)', []); + testErrorsAndWarnings('row nullVar = null | stats percentile(nullVar, nullVar)', [ + 'Argument of [percentile] must be a constant, received [nullVar]', ]); + }); - testErrorsAndWarnings( - 'from a_index | eval var = st_distance(cartesianPointField, cartesianPointField)', - [] - ); + describe('to_string', () => { + testErrorsAndWarnings('row var = to_string(true)', []); + testErrorsAndWarnings('row to_string(true)', []); + testErrorsAndWarnings('row var = to_str(true)', []); + testErrorsAndWarnings('row var = to_string(to_boolean(true))', []); + testErrorsAndWarnings('row var = to_string(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row to_string(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = to_str(cartesianPointField)', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = to_string(to_cartesianpoint(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = to_string(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row to_string(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_str(to_cartesianshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_string(to_cartesianshape(cartesianPointField))', [ + 'Unknown column [cartesianPointField]', + ]); + testErrorsAndWarnings('row var = to_string(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row to_string(to_datetime("2021-01-01T00:00:00Z"))', []); + testErrorsAndWarnings('row var = to_str(to_datetime("2021-01-01T00:00:00Z"))', []); testErrorsAndWarnings( - 'from a_index | eval st_distance(cartesianPointField, cartesianPointField)', + 'row var = to_string(to_datetime(to_datetime("2021-01-01T00:00:00Z")))', [] ); - testErrorsAndWarnings( - 'from a_index | eval var = st_distance(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))', - [] - ); + testErrorsAndWarnings('row var = to_string(5.5)', []); - testErrorsAndWarnings('from a_index | eval st_distance(booleanField, booleanField)', [ - 'Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]', - 'Argument of [st_distance] must be [cartesian_point], found value [booleanField] type [boolean]', + testErrorsAndWarnings('row to_string(5.5)', []); + testErrorsAndWarnings('row var = to_str(5.5)', []); + testErrorsAndWarnings('row var = to_string(to_double(true))', []); + testErrorsAndWarnings('row var = to_string(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row to_string(geoPointField)', ['Unknown column [geoPointField]']); + testErrorsAndWarnings('row var = to_str(geoPointField)', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = to_string(to_geopoint(geoPointField))', [ + 'Unknown column [geoPointField]', + ]); + testErrorsAndWarnings('row var = to_string(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row to_string(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_str(to_geoshape("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_string(to_geoshape(geoPointField))', [ + 'Unknown column [geoPointField]', ]); + testErrorsAndWarnings('row var = to_string(5)', []); + testErrorsAndWarnings('row to_string(5)', []); + testErrorsAndWarnings('row var = to_str(5)', []); + testErrorsAndWarnings('row var = to_string(to_integer(true))', []); + testErrorsAndWarnings('row var = to_string(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row to_string(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = to_str(to_ip("127.0.0.1"))', []); + testErrorsAndWarnings('row var = to_string(to_ip(to_ip("127.0.0.1")))', []); + testErrorsAndWarnings('row var = to_string("a")', []); + testErrorsAndWarnings('row to_string("a")', []); + testErrorsAndWarnings('row var = to_str("a")', []); + testErrorsAndWarnings('row var = to_string(to_string(true))', []); + testErrorsAndWarnings('row var = to_string(to_version("1.0.0"))', []); + testErrorsAndWarnings('row to_string(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = to_str(to_version("1.0.0"))', []); + testErrorsAndWarnings('row var = to_string(to_version("a"))', []); + testErrorsAndWarnings('from a_index | eval var = to_string(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_string(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(booleanField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(to_boolean(booleanField))', []); - testErrorsAndWarnings( - 'from a_index | eval var = st_distance(geoPointField, geoPointField)', - [] - ); - testErrorsAndWarnings('from a_index | eval st_distance(geoPointField, geoPointField)', []); + testErrorsAndWarnings('from a_index | eval to_string(counterDoubleField)', [ + 'Argument of [to_string] must be [boolean], found value [counterDoubleField] type [counter_double]', + ]); - testErrorsAndWarnings( - 'from a_index | eval var = st_distance(to_geopoint(geoPointField), to_geopoint(geoPointField))', - [] - ); + testErrorsAndWarnings('from a_index | eval var = to_string(*)', [ + 'Using wildcards (*) in to_string is not allowed', + ]); - testErrorsAndWarnings( - 'from a_index | eval st_distance(cartesianPointField, cartesianPointField, extraArg)', - ['Error: [st_distance] function expects exactly 2 arguments, got 3.'] - ); + testErrorsAndWarnings('from a_index | eval var = to_string(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval to_string(cartesianPointField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(cartesianPointField)', []); testErrorsAndWarnings( - 'from a_index | sort st_distance(cartesianPointField, cartesianPointField)', + 'from a_index | eval var = to_string(to_cartesianpoint(cartesianPointField))', [] ); - testErrorsAndWarnings('from a_index | eval st_distance(null, null)', []); - testErrorsAndWarnings('row nullVar = null | eval st_distance(nullVar, nullVar)', []); - }); + testErrorsAndWarnings('from a_index | eval var = to_string(cartesianShapeField)', []); + testErrorsAndWarnings('from a_index | eval to_string(cartesianShapeField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(cartesianShapeField)', []); - describe('weighted_avg', () => { testErrorsAndWarnings( - 'from a_index | stats var = weighted_avg(numberField, numberField)', + 'from a_index | eval var = to_string(to_cartesianshape(cartesianPointField))', [] ); - testErrorsAndWarnings('from a_index | stats weighted_avg(numberField, numberField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(dateField)', []); + testErrorsAndWarnings('from a_index | eval to_string(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(dateField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(to_datetime(dateField))', []); + testErrorsAndWarnings('from a_index | eval var = to_string(doubleField)', []); + testErrorsAndWarnings('from a_index | eval to_string(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(doubleField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(to_double(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_string(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval to_string(geoPointField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(geoPointField)', []); testErrorsAndWarnings( - 'from a_index | stats var = round(weighted_avg(numberField, numberField))', + 'from a_index | eval var = to_string(to_geopoint(geoPointField))', [] ); - + testErrorsAndWarnings('from a_index | eval var = to_string(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval to_string(geoShapeField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(geoShapeField)', []); testErrorsAndWarnings( - 'from a_index | stats round(weighted_avg(numberField, numberField))', + 'from a_index | eval var = to_string(to_geoshape(geoPointField))', [] ); + testErrorsAndWarnings('from a_index | eval var = to_string(integerField)', []); + testErrorsAndWarnings('from a_index | eval to_string(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(integerField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(to_integer(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_string(ipField)', []); + testErrorsAndWarnings('from a_index | eval to_string(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(ipField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(to_ip(ipField))', []); + testErrorsAndWarnings('from a_index | eval var = to_string(keywordField)', []); + testErrorsAndWarnings('from a_index | eval to_string(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(keywordField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(to_string(booleanField))', []); + testErrorsAndWarnings('from a_index | eval var = to_string(longField)', []); + testErrorsAndWarnings('from a_index | eval to_string(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(longField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(textField)', []); + testErrorsAndWarnings('from a_index | eval to_string(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(textField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval to_string(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(unsignedLongField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(versionField)', []); + testErrorsAndWarnings('from a_index | eval to_string(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = to_str(versionField)', []); + testErrorsAndWarnings('from a_index | eval var = to_string(to_version(keywordField))', []); - testErrorsAndWarnings( - 'from a_index | stats var = round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)', - [] - ); + testErrorsAndWarnings('from a_index | eval to_string(booleanField, extraArg)', [ + 'Error: [to_string] function expects exactly one argument, got 2.', + ]); - testErrorsAndWarnings( - 'from a_index | stats round(weighted_avg(numberField, numberField)) + weighted_avg(numberField, numberField)', - [] - ); + testErrorsAndWarnings('from a_index | sort to_string(booleanField)', []); + testErrorsAndWarnings('from a_index | eval to_string(null)', []); + testErrorsAndWarnings('row nullVar = null | eval to_string(nullVar)', []); + testErrorsAndWarnings('from a_index | eval to_string("2022")', []); + testErrorsAndWarnings('from a_index | eval to_string(concat("20", "22"))', []); + testErrorsAndWarnings('row var = to_string(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row to_string(to_cartesianpoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_str(to_cartesianpoint("POINT (30 10)"))', []); testErrorsAndWarnings( - 'from a_index | stats weighted_avg(numberField / 2, numberField)', + 'row var = to_string(to_cartesianpoint(to_cartesianpoint("POINT (30 10)")))', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = weighted_avg(numberField / 2, numberField)', + 'row var = to_string(to_cartesianshape(to_cartesianpoint("POINT (30 10)")))', [] ); - testErrorsAndWarnings( - 'from a_index | stats avg(numberField), weighted_avg(numberField / 2, numberField)', - [] - ); + testErrorsAndWarnings('row var = to_string(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row to_string(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_str(to_geopoint("POINT (30 10)"))', []); + testErrorsAndWarnings('row var = to_string(to_geopoint(to_geopoint("POINT (30 10)")))', []); + testErrorsAndWarnings('row var = to_string(to_geoshape(to_geopoint("POINT (30 10)")))', []); + }); + describe('mv_pseries_weighted_sum', () => { + testErrorsAndWarnings('row var = mv_pseries_weighted_sum(5.5, 5.5)', []); + testErrorsAndWarnings('row mv_pseries_weighted_sum(5.5, 5.5)', []); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField / 2, numberField)', + 'row var = mv_pseries_weighted_sum(to_double(true), to_double(true))', [] ); - testErrorsAndWarnings( - 'from a_index | stats var0 = weighted_avg(numberField, numberField)', - [] - ); + testErrorsAndWarnings('row var = mv_pseries_weighted_sum(true, true)', [ + 'Argument of [mv_pseries_weighted_sum] must be [double], found value [true] type [boolean]', + 'Argument of [mv_pseries_weighted_sum] must be [double], found value [true] type [boolean]', + ]); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), weighted_avg(numberField, numberField)', + 'from a_index | where mv_pseries_weighted_sum(doubleField, doubleField) > 0', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField)', - [] + 'from a_index | where mv_pseries_weighted_sum(booleanField, booleanField) > 0', + [ + 'Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]', + 'Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]', + ] ); testErrorsAndWarnings( - 'from a_index | stats weighted_avg(numberField, numberField) by round(numberField / 2)', + 'from a_index | eval var = mv_pseries_weighted_sum(doubleField, doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | stats var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2)', + 'from a_index | eval mv_pseries_weighted_sum(doubleField, doubleField)', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), ipField', + 'from a_index | eval var = mv_pseries_weighted_sum(to_double(booleanField), to_double(booleanField))', [] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), ipField', - [] + 'from a_index | eval mv_pseries_weighted_sum(booleanField, booleanField)', + [ + 'Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]', + 'Argument of [mv_pseries_weighted_sum] must be [double], found value [booleanField] type [boolean]', + ] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), weighted_avg(numberField, numberField) by round(numberField / 2), numberField / 2', - [] + 'from a_index | eval mv_pseries_weighted_sum(doubleField, doubleField, extraArg)', + ['Error: [mv_pseries_weighted_sum] function expects exactly 2 arguments, got 3.'] ); testErrorsAndWarnings( - 'from a_index | stats avg(numberField), var0 = weighted_avg(numberField, numberField) by var1 = round(numberField / 2), numberField / 2', + 'from a_index | sort mv_pseries_weighted_sum(doubleField, doubleField)', [] ); + testErrorsAndWarnings('from a_index | eval mv_pseries_weighted_sum(null, null)', []); testErrorsAndWarnings( - 'from a_index | stats var = weighted_avg(avg(numberField), avg(numberField))', - [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ] - ); - - testErrorsAndWarnings( - 'from a_index | stats weighted_avg(avg(numberField), avg(numberField))', - [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]", - ] - ); - - testErrorsAndWarnings('from a_index | stats weighted_avg(booleanField, booleanField)', [ - 'Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]', - 'Argument of [weighted_avg] must be [number], found value [booleanField] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | sort weighted_avg(numberField, numberField)', [ - 'SORT does not support function weighted_avg', - ]); - - testErrorsAndWarnings('from a_index | where weighted_avg(numberField, numberField)', [ - 'WHERE does not support function weighted_avg', - ]); - - testErrorsAndWarnings('from a_index | where weighted_avg(numberField, numberField) > 0', [ - 'WHERE does not support function weighted_avg', - ]); - - testErrorsAndWarnings('from a_index | eval var = weighted_avg(numberField, numberField)', [ - 'EVAL does not support function weighted_avg', - ]); - - testErrorsAndWarnings( - 'from a_index | eval var = weighted_avg(numberField, numberField) > 0', - ['EVAL does not support function weighted_avg'] + 'row nullVar = null | eval mv_pseries_weighted_sum(nullVar, nullVar)', + [] ); - - testErrorsAndWarnings('from a_index | eval weighted_avg(numberField, numberField)', [ - 'EVAL does not support function weighted_avg', - ]); - - testErrorsAndWarnings('from a_index | eval weighted_avg(numberField, numberField) > 0', [ - 'EVAL does not support function weighted_avg', - ]); - - testErrorsAndWarnings('from a_index | stats weighted_avg(null, null)', []); - testErrorsAndWarnings('row nullVar = null | stats weighted_avg(nullVar, nullVar)', []); - }); - - describe('exp', () => { - testErrorsAndWarnings('row var = exp(5)', []); - testErrorsAndWarnings('row exp(5)', []); - testErrorsAndWarnings('row var = exp(to_integer(true))', []); - - testErrorsAndWarnings('row var = exp(true)', [ - 'Argument of [exp] must be [number], found value [true] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | where exp(numberField) > 0', []); - - testErrorsAndWarnings('from a_index | where exp(booleanField) > 0', [ - 'Argument of [exp] must be [number], found value [booleanField] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | eval var = exp(numberField)', []); - testErrorsAndWarnings('from a_index | eval exp(numberField)', []); - testErrorsAndWarnings('from a_index | eval var = exp(to_integer(booleanField))', []); - - testErrorsAndWarnings('from a_index | eval exp(booleanField)', [ - 'Argument of [exp] must be [number], found value [booleanField] type [boolean]', - ]); - - testErrorsAndWarnings('from a_index | eval var = exp(*)', [ - 'Using wildcards (*) in exp is not allowed', - ]); - - testErrorsAndWarnings('from a_index | eval exp(numberField, extraArg)', [ - 'Error: [exp] function expects exactly one argument, got 2.', - ]); - - testErrorsAndWarnings('from a_index | sort exp(numberField)', []); - testErrorsAndWarnings('from a_index | eval exp(null)', []); - testErrorsAndWarnings('row nullVar = null | eval exp(nullVar)', []); }); }); }); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts index cea9dccbd8d97..4de1adf4a3153 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts @@ -76,6 +76,7 @@ import { import { collapseWrongArgumentTypeMessages, getMaxMinNumberOfParams } from './helpers'; import { getParamAtPosition } from '../autocomplete/helper'; import { METADATA_FIELDS } from '../shared/constants'; +import { isStringType } from '../shared/esql_types'; function validateFunctionLiteralArg( astFunction: ESQLFunction, @@ -110,7 +111,7 @@ function validateFunctionLiteralArg( messageId: 'wrongArgumentType', values: { name: astFunction.name, - argType: argDef.type, + argType: argDef.type as string, value: typeof actualArg.value === 'number' ? actualArg.value : String(actualArg.value), givenType: actualArg.literalType, }, @@ -138,7 +139,7 @@ function validateFunctionLiteralArg( messageId: 'wrongArgumentType', values: { name: astFunction.name, - argType: argDef.type, + argType: argDef.type as string, value: actualArg.name, givenType: 'duration', }, @@ -168,7 +169,7 @@ function validateInlineCastArg( messageId: 'wrongArgumentType', values: { name: astFunction.name, - argType: parameterDefinition.type, + argType: parameterDefinition.type as string, value: arg.text, givenType: arg.castType, }, @@ -205,7 +206,7 @@ function validateNestedFunctionArg( messages.push( getMessageFromId({ messageId: 'noNestedArgumentSupport', - values: { name: actualArg.text, argType: argFn.signatures[0].returnType }, + values: { name: actualArg.text, argType: argFn.signatures[0].returnType as string }, locations: actualArg.location, }) ); @@ -218,9 +219,9 @@ function validateNestedFunctionArg( messageId: 'wrongArgumentType', values: { name: astFunction.name, - argType: parameterDefinition.type, + argType: parameterDefinition.type as string, value: actualArg.text, - givenType: argFn.signatures[0].returnType, + givenType: argFn.signatures[0].returnType as string, }, locations: actualArg.location, }) @@ -300,7 +301,7 @@ function validateFunctionColumnArg( messageId: 'wrongArgumentType', values: { name: astFunction.name, - argType: parameterDefinition.type, + argType: parameterDefinition.type as string, value: actualArg.name, givenType: columnHit!.type, }, @@ -463,7 +464,7 @@ function validateFunction( allMatchingArgDefinitionsAreConstantOnly || forceConstantOnly, // use the nesting flag for now just for stats and metrics // TODO: revisit this part later on to make it more generic - parentCommand === 'stats' || parentCommand === 'metrics' + ['stats', 'inlinestats', 'metrics'].includes(parentCommand) ? isNested || !isAssignment(astFunction) : false ); @@ -533,14 +534,14 @@ function validateFunction( }); }); - const shouldCollapseMessages = isArrayType(argDef.type) && hasMultipleElements; + const shouldCollapseMessages = isArrayType(argDef.type as string) && hasMultipleElements; failingSignature.push( ...(shouldCollapseMessages ? collapseWrongArgumentTypeMessages( messagesFromAllArgElements, outerArg, astFunction.name, - argDef.type, + argDef.type as string, parentCommand, references ) @@ -879,6 +880,7 @@ function validateColumnForCommand( if (columnParamsWithInnerTypes.length) { const hasSomeWrongInnerTypes = columnParamsWithInnerTypes.every(({ innerType }) => { + if (innerType === 'string' && isStringType(columnRef.type)) return false; return innerType !== 'any' && innerType !== columnRef.type; }); if (hasSomeWrongInnerTypes) { @@ -1008,7 +1010,7 @@ function validateCommand(command: ESQLCommand, references: ReferenceMaps): ESQLM ); } if (isColumnItem(arg)) { - if (command.name === 'stats') { + if (command.name === 'stats' || command.name === 'inlinestats') { messages.push(errors.unknownAggFunction(arg)); } else { messages.push(...validateColumnForCommand(arg, command.name, references)); @@ -1074,15 +1076,17 @@ function validateUnsupportedTypeFields(fields: Map<string, ESQLRealField>) { const messages: ESQLMessage[] = []; for (const field of fields.values()) { if (field.type === 'unsupported') { - messages.push( - getMessageFromId({ - messageId: 'unsupportedFieldType', - values: { - field: field.name, - }, - locations: { min: 1, max: 1 }, - }) - ); + // Removed temporarily to supress all these warnings + // Issue to re-enable in a better way: https://github.com/elastic/kibana/issues/189666 + // messages.push( + // getMessageFromId({ + // messageId: 'unsupportedFieldType', + // values: { + // field: field.name, + // }, + // locations: { min: 1, max: 1 }, + // }) + // ); } } return messages; diff --git a/packages/kbn-ftr-common-functional-services/index.ts b/packages/kbn-ftr-common-functional-services/index.ts index 8bad5e67102fd..4bd3eca34c45c 100644 --- a/packages/kbn-ftr-common-functional-services/index.ts +++ b/packages/kbn-ftr-common-functional-services/index.ts @@ -20,4 +20,9 @@ export type EsArchiver = ProvidedType<typeof EsArchiverProvider>; import { EsProvider } from './services/es'; export type Es = ProvidedType<typeof EsProvider>; +import { SupertestWithoutAuthProvider } from './services/supertest_without_auth'; +export type SupertestWithoutAuthProviderType = ProvidedType<typeof SupertestWithoutAuthProvider>; + +export type { InternalRequestHeader, RoleCredentials } from './services/saml_auth'; + export type { FtrProviderContext } from './services/ftr_provider_context'; diff --git a/packages/kbn-ftr-common-functional-services/services/all.ts b/packages/kbn-ftr-common-functional-services/services/all.ts index 14019caaa582c..49308faeb3dd0 100644 --- a/packages/kbn-ftr-common-functional-services/services/all.ts +++ b/packages/kbn-ftr-common-functional-services/services/all.ts @@ -10,10 +10,14 @@ import { EsArchiverProvider } from './es_archiver'; import { EsProvider } from './es'; import { KibanaServerProvider } from './kibana_server'; import { RetryService } from './retry'; +import { SupertestWithoutAuthProvider } from './supertest_without_auth'; +import { SamlAuthProvider } from './saml_auth'; export const services = { es: EsProvider, kibanaServer: KibanaServerProvider, esArchiver: EsArchiverProvider, retry: RetryService, + supertestWithoutAuth: SupertestWithoutAuthProvider, + samlAuth: SamlAuthProvider, }; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.ts new file mode 100644 index 0000000000000..95983647647f3 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/default_request_headers.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 const COMMON_REQUEST_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + +// possible change in 9.0 to match serverless +const STATEFUL_INTERNAL_REQUEST_HEADERS = { + ...COMMON_REQUEST_HEADERS, +}; + +const SERVERLESS_INTERNAL_REQUEST_HEADERS = { + ...COMMON_REQUEST_HEADERS, + 'x-elastic-internal-origin': 'kibana', +}; + +export type InternalRequestHeader = + | typeof STATEFUL_INTERNAL_REQUEST_HEADERS + | typeof SERVERLESS_INTERNAL_REQUEST_HEADERS; + +export const getServerlessInternalRequestHeaders = (): InternalRequestHeader => { + return SERVERLESS_INTERNAL_REQUEST_HEADERS; +}; + +export const getStatefulInternalRequestHeaders = (): InternalRequestHeader => { + return STATEFUL_INTERNAL_REQUEST_HEADERS; +}; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.ts new file mode 100644 index 0000000000000..3f1ec7af917ff --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/get_auth_provider.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 fs from 'fs'; +import { type Config } from '@kbn/test'; +import { ToolingLog } from '@kbn/tooling-log'; +import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils'; +import { KibanaServer } from '../..'; + +import { ServerlessAuthProvider } from './serverless/auth_provider'; +import { StatefulAuthProvider } from './stateful/auth_provider'; +import { createRole, createRoleMapping } from './stateful/create_role_mapping'; + +const STATEFUL_ADMIN_ROLE_MAPPING_PATH = './stateful/admin_mapping'; + +export interface AuthProvider { + getSupportedRoleDescriptors(): any; + getDefaultRole(): string; + getRolesDefinitionPath(): string; + getCommonRequestHeader(): { [key: string]: string }; + getInternalRequestHeader(): { [key: string]: string }; +} + +export interface AuthProviderProps { + config: Config; + kibanaServer: KibanaServer; + log: ToolingLog; +} + +export const getAuthProvider = async (props: AuthProviderProps) => { + const { config, log, kibanaServer } = props; + const isServerless = !!props.config.get('serverless'); + if (isServerless) { + return new ServerlessAuthProvider(config); + } + + const provider = new StatefulAuthProvider(); + // TODO: Move it to @kbn-es package, so that roles and its mapping are created before FTR services loading starts. + // 'viewer' and 'editor' roles are available by default, but we have to create 'admin' role + const adminRoleMapping = JSON.parse( + fs.readFileSync(require.resolve(STATEFUL_ADMIN_ROLE_MAPPING_PATH), 'utf8') + ); + await createRole({ roleName: 'admin', roleMapping: adminRoleMapping, kibanaServer, log }); + const roles = Object.keys(provider.getSupportedRoleDescriptors()); + // Creating roles mapping for mock-idp + await createRoleMapping({ name: MOCK_IDP_REALM_NAME, roles, config, log }); + return provider; +}; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/index.ts new file mode 100644 index 0000000000000..15735d4bffcbb --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/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 { SamlAuthProvider } from './saml_auth_provider'; +export type { RoleCredentials } from './saml_auth_provider'; +export type { InternalRequestHeader } from './default_request_headers'; diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts new file mode 100644 index 0000000000000..4e79cad656197 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/saml_auth_provider.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SamlSessionManager } from '@kbn/test'; +import expect from '@kbn/expect'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { getAuthProvider } from './get_auth_provider'; +import { InternalRequestHeader } from './default_request_headers'; + +export interface RoleCredentials { + apiKey: { id: string; name: string }; + apiKeyHeader: { Authorization: string }; + cookieHeader: { Cookie: string }; +} + +export async function SamlAuthProvider({ getService }: FtrProviderContext) { + const config = getService('config'); + const log = getService('log'); + const kibanaServer = getService('kibanaServer'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const isCloud = !!process.env.TEST_CLOUD; + + const authRoleProvider = await getAuthProvider({ config, kibanaServer, log }); + const supportedRoleDescriptors = authRoleProvider.getSupportedRoleDescriptors(); + const supportedRoles = Object.keys(supportedRoleDescriptors); + + const customRolesFileName: string | undefined = process.env.ROLES_FILENAME_OVERRIDE; + const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', customRolesFileName ?? 'role_users.json'); + + // Sharing the instance within FTR config run means cookies are persistent for each role between tests. + const sessionManager = new SamlSessionManager({ + hostOptions: { + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: isCloud ? undefined : config.get('servers.kibana.port'), + username: config.get('servers.kibana.username'), + password: config.get('servers.kibana.password'), + }, + log, + isCloud, + supportedRoles: { + roles: supportedRoles, + sourcePath: authRoleProvider.getRolesDefinitionPath(), + }, + cloudUsersFilePath, + }); + + const DEFAULT_ROLE = authRoleProvider.getDefaultRole(); + const COMMON_REQUEST_HEADERS = authRoleProvider.getCommonRequestHeader(); + const INTERNAL_REQUEST_HEADERS = authRoleProvider.getInternalRequestHeader(); + + return { + async getInteractiveUserSessionCookieWithRoleScope(role: string) { + return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role); + }, + async getM2MApiCredentialsWithRoleScope(role: string) { + return sessionManager.getApiCredentialsForRole(role); + }, + async getEmail(role: string) { + return sessionManager.getEmail(role); + }, + + async getUserData(role: string) { + return sessionManager.getUserData(role); + }, + async createM2mApiKeyWithDefaultRoleScope() { + log.debug(`Creating api key for default role: [${this.DEFAULT_ROLE}]`); + return this.createM2mApiKeyWithRoleScope(this.DEFAULT_ROLE); + }, + async createM2mApiKeyWithRoleScope(role: string): Promise<RoleCredentials> { + // Get admin credentials in order to create the API key + const adminCookieHeader = await this.getM2MApiCredentialsWithRoleScope('admin'); + + // Get the role descrtiptor for the role + let roleDescriptors = {}; + if (role !== 'admin') { + const roleDescriptor = supportedRoleDescriptors[role]; + if (!roleDescriptor) { + throw new Error(`Cannot create API key for non-existent role "${role}"`); + } + log.debug( + `Creating api key for ${role} role with the following privileges ${JSON.stringify( + roleDescriptor + )}` + ); + roleDescriptors = { + [role]: roleDescriptor, + }; + } + + const { body, status } = await supertestWithoutAuth + .post('/internal/security/api_key') + .set(INTERNAL_REQUEST_HEADERS) + .set(adminCookieHeader) + .send({ + name: 'myTestApiKey', + metadata: {}, + role_descriptors: roleDescriptors, + }); + expect(status).to.be(200); + + const apiKey = body; + const apiKeyHeader = { Authorization: 'ApiKey ' + apiKey.encoded }; + + log.debug(`Created api key for role: [${role}]`); + return { apiKey, apiKeyHeader, cookieHeader: adminCookieHeader }; + }, + async invalidateM2mApiKeyWithRoleScope(roleCredentials: RoleCredentials) { + const requestBody = { + apiKeys: [ + { + id: roleCredentials.apiKey.id, + name: roleCredentials.apiKey.name, + }, + ], + isAdmin: true, + }; + + const { status } = await supertestWithoutAuth + .post('/internal/security/api_key/invalidate') + .set(INTERNAL_REQUEST_HEADERS) + .set(roleCredentials.cookieHeader) + .send(requestBody); + + expect(status).to.be(200); + }, + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + }, + + getInternalRequestHeader(): InternalRequestHeader { + return INTERNAL_REQUEST_HEADERS; + }, + DEFAULT_ROLE, + }; +} diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.ts new file mode 100644 index 0000000000000..eecbe3a5862f2 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/serverless/auth_provider.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 { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; +import { type Config } from '@kbn/test'; +import { isServerlessProjectType, readRolesDescriptorsFromResource } from '@kbn/es/src/utils'; +import { resolve } from 'path'; +import { Role } from '@kbn/test/src/auth/types'; +import { + getServerlessInternalRequestHeaders, + COMMON_REQUEST_HEADERS, +} from '../default_request_headers'; +import { AuthProvider } from '../get_auth_provider'; + +const projectDefaultRoles = new Map<string, Role>([ + ['es', 'developer'], + ['security', 'editor'], + ['oblt', 'editor'], +]); + +const getDefaultServerlessRole = (projectType: string) => { + if (projectDefaultRoles.has(projectType)) { + return projectDefaultRoles.get(projectType)!; + } else { + throw new Error(`Default role is not defined for ${projectType} project`); + } +}; + +export class ServerlessAuthProvider implements AuthProvider { + private readonly projectType: string; + private readonly rolesDefinitionPath: string; + + constructor(config: Config) { + const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[]; + this.projectType = kbnServerArgs.reduce((acc, arg) => { + const match = arg.match(/--serverless[=\s](\w+)/); + return acc + (match ? match[1] : ''); + }, '') as ServerlessProjectType; + + if (!isServerlessProjectType(this.projectType)) { + throw new Error(`Unsupported serverless projectType: ${this.projectType}`); + } + + this.rolesDefinitionPath = resolve(SERVERLESS_ROLES_ROOT_PATH, this.projectType, 'roles.yml'); + } + + getSupportedRoleDescriptors(): any { + return readRolesDescriptorsFromResource(this.rolesDefinitionPath); + } + getDefaultRole(): string { + return getDefaultServerlessRole(this.projectType); + } + getRolesDefinitionPath(): string { + return this.rolesDefinitionPath; + } + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + } + getInternalRequestHeader() { + return getServerlessInternalRequestHeaders(); + } +} diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json new file mode 100644 index 0000000000000..f8544f16bd239 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/admin_mapping.json @@ -0,0 +1,46 @@ +{ + "kibana":[ + { + "base":[ + "all" + ], + "feature":{ + + }, + "spaces":[ + "*" + ] + } + ], + "elasticsearch":{ + "cluster":[ + "all" + ], + "indices":[ + { + "names":[ + "*" + ], + "privileges":[ + "all" + ], + "allow_restricted_indices":false + }, + { + "names":[ + "*" + ], + "privileges":[ + "monitor", + "read", + "read_cross_cluster", + "view_index_metadata" + ], + "allow_restricted_indices":true + } + ], + "run_as":[ + + ] + } +} \ No newline at end of file diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.ts new file mode 100644 index 0000000000000..cf27fd9d5d506 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/auth_provider.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 { readRolesDescriptorsFromResource, STATEFUL_ROLES_ROOT_PATH } from '@kbn/es'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; +import { AuthProvider } from '../get_auth_provider'; +import { + getStatefulInternalRequestHeaders, + COMMON_REQUEST_HEADERS, +} from '../default_request_headers'; + +export class StatefulAuthProvider implements AuthProvider { + private readonly rolesDefinitionPath = resolve(REPO_ROOT, STATEFUL_ROLES_ROOT_PATH, 'roles.yml'); + getSupportedRoleDescriptors(): any { + return readRolesDescriptorsFromResource(this.rolesDefinitionPath); + } + getDefaultRole() { + return 'editor'; + } + getRolesDefinitionPath() { + return this.rolesDefinitionPath; + } + + getCommonRequestHeader() { + return COMMON_REQUEST_HEADERS; + } + + getInternalRequestHeader() { + return getStatefulInternalRequestHeaders(); + } +} diff --git a/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.ts new file mode 100644 index 0000000000000..1e9b897362dc6 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/saml_auth/stateful/create_role_mapping.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 { Config, createEsClientForFtrConfig } from '@kbn/test'; +import { ToolingLog } from '@kbn/tooling-log'; +import { KibanaServer } from '../../..'; + +export interface CreateRoleProps { + roleName: string; + roleMapping: string[]; + kibanaServer: KibanaServer; + log: ToolingLog; +} + +export interface CreateRoleMappingProps { + name: string; + roles: string[]; + config: Config; + log: ToolingLog; +} + +export async function createRole(props: CreateRoleProps) { + const { roleName, roleMapping, kibanaServer, log } = props; + log.debug(`Adding a role: ${roleName}`); + const { status, statusText } = await kibanaServer.request({ + path: `/api/security/role/${roleName}`, + method: 'PUT', + body: roleMapping, + retries: 0, + }); + if (status !== 204) { + throw new Error(`Expected status code of 204, received ${status} ${statusText}`); + } +} + +export async function createRoleMapping(props: CreateRoleMappingProps) { + const { name, roles, config, log } = props; + log.debug(`Creating a role mapping: {realm.name: ${name}, roles: ${roles}}`); + const esClient = createEsClientForFtrConfig(config); + await esClient.security.putRoleMapping({ + name, + roles, + enabled: true, + // @ts-ignore + rules: { field: { 'realm.name': name } }, + }); +} diff --git a/packages/kbn-ftr-common-functional-services/services/supertest_without_auth.ts b/packages/kbn-ftr-common-functional-services/services/supertest_without_auth.ts new file mode 100644 index 0000000000000..6340c06d62535 --- /dev/null +++ b/packages/kbn-ftr-common-functional-services/services/supertest_without_auth.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 { format as formatUrl } from 'url'; +import supertest from 'supertest'; +import { FtrProviderContext } from './ftr_provider_context'; + +/** + * Returns supertest.SuperTest<supertest.Test> instance that will not persist cookie between API requests. + * If you need to pass certificate, do the following: + * await supertestWithoutAuth + * .get('/abc') + * .ca(CA_CERT) + */ +export function SupertestWithoutAuthProvider({ getService }: FtrProviderContext) { + const config = getService('config'); + const kbnUrl = formatUrl({ + ...config.get('servers.kibana'), + auth: false, + }); + + return supertest(kbnUrl); +} diff --git a/packages/kbn-ftr-common-functional-services/tsconfig.json b/packages/kbn-ftr-common-functional-services/tsconfig.json index 3641c807e4d6d..ff10ec6c9d5fb 100644 --- a/packages/kbn-ftr-common-functional-services/tsconfig.json +++ b/packages/kbn-ftr-common-functional-services/tsconfig.json @@ -14,7 +14,11 @@ "@kbn/core-saved-objects-server", "@kbn/tooling-log", "@kbn/es-archiver", - "@kbn/test" + "@kbn/test", + "@kbn/expect", + "@kbn/repo-info", + "@kbn/es", + "@kbn/mock-idp-utils" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts b/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts index dc51bbbb126d6..d664333129fb1 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts +++ b/packages/kbn-ftr-common-functional-ui-services/services/remote/webdriver.ts @@ -118,7 +118,9 @@ function initChromiumOptions(browserType: Browsers, acceptInsecureCerts: boolean // Bypass the media stream infobar by selecting the default device for media streams (e.g. WebRTC). Works with --use-fake-device-for-media-stream. 'use-fake-ui-for-media-stream', // Do not show "Choose your search engine" dialog (> Chrome v127) - 'disable-search-engine-choice-screen' + 'disable-search-engine-choice-screen', + // Disable component updater used for Chrome Certificate Verifier + 'disable-component-update' ); if (process.platform === 'linux') { diff --git a/packages/kbn-language-documentation-popover/README.md b/packages/kbn-language-documentation-popover/README.md index 3d5723b526c0d..8e3e3d14230a9 100644 --- a/packages/kbn-language-documentation-popover/README.md +++ b/packages/kbn-language-documentation-popover/README.md @@ -7,7 +7,7 @@ It can be used in every application that would like to add an in-app documentati - A details page ``` -<LanguageDocumentationPopover language={language} sections={documentationSections} /> +<LanguageDocumentationPopover language={language} sections={documentationSections} onHelpMenuVisibilityChange={onHelpMenuVisibilityChange} isHelpMenuOpen={isHelpMenuOpen} /> ``` The properties are typed as: diff --git a/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx b/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx index dc0270b3fc85e..947a8048c3097 100644 --- a/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx +++ b/packages/kbn-language-documentation-popover/src/__stories__/language_documentation_popover.stories.tsx @@ -66,5 +66,7 @@ storiesOf('Language documentation popover', module).add('default', () => ( language="Test" sections={sections} buttonProps={{ color: 'text' }} + isHelpMenuOpen={true} + onHelpMenuVisibilityChange={() => {}} /> )); diff --git a/packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx b/packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx index a0ab5b9e5d661..b66a521b475a4 100644 --- a/packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx +++ b/packages/kbn-language-documentation-popover/src/components/documentation_popover.tsx @@ -5,7 +5,7 @@ * 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 React, { useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiPopover, @@ -21,6 +21,8 @@ import { interface DocumentationPopoverProps { language: string; + isHelpMenuOpen: boolean; + onHelpMenuVisibilityChange: (status: boolean) => void; sections?: LanguageDocumentationSections; buttonProps?: Omit<EuiButtonIconProps, 'iconType'>; searchInDescription?: boolean; @@ -33,24 +35,28 @@ function DocumentationPopover({ buttonProps, searchInDescription, linkToDocumentation, + isHelpMenuOpen, + onHelpMenuVisibilityChange, }: DocumentationPopoverProps) { - const [isHelpOpen, setIsHelpOpen] = useState<boolean>(false); - const toggleDocumentationPopover = useCallback(() => { - setIsHelpOpen(!isHelpOpen); - }, [isHelpOpen]); + onHelpMenuVisibilityChange?.(!isHelpMenuOpen); + }, [isHelpMenuOpen, onHelpMenuVisibilityChange]); + + useEffect(() => { + onHelpMenuVisibilityChange(isHelpMenuOpen ?? false); + }, [isHelpMenuOpen, onHelpMenuVisibilityChange]); return ( <EuiOutsideClickDetector onOutsideClick={() => { - setIsHelpOpen(false); + onHelpMenuVisibilityChange?.(false); }} > <EuiPopover panelClassName="documentation__docs--overlay" panelPaddingSize="none" - isOpen={isHelpOpen} - closePopover={() => setIsHelpOpen(false)} + isOpen={isHelpMenuOpen} + closePopover={() => onHelpMenuVisibilityChange(false)} button={ <EuiToolTip position="top" @@ -62,7 +68,7 @@ function DocumentationPopover({ })} > <EuiButtonIcon - iconType="iInCircle" + iconType="documentation" onClick={toggleDocumentationPopover} {...buttonProps} /> diff --git a/packages/kbn-management/cards_navigation/src/consts.tsx b/packages/kbn-management/cards_navigation/src/consts.tsx index ba0d370c9577f..0215c66d1b09d 100644 --- a/packages/kbn-management/cards_navigation/src/consts.tsx +++ b/packages/kbn-management/cards_navigation/src/consts.tsx @@ -97,6 +97,13 @@ export const appDefinitions: Record<AppId, AppDefinition> = { icon: 'wrench', }, + [AppIds.SPACES]: { + category: appCategories.CONTENT, + description: i18n.translate('management.landing.withCardNavigation.spacesDescription', { + defaultMessage: 'Organize your saved objects into meaningful categories.', + }), + icon: 'spaces', + }, [AppIds.SAVED_OBJECTS]: { category: appCategories.CONTENT, description: i18n.translate('management.landing.withCardNavigation.objectsDescription', { diff --git a/packages/kbn-management/cards_navigation/src/types.ts b/packages/kbn-management/cards_navigation/src/types.ts index 438275b246529..24e4e73624e73 100644 --- a/packages/kbn-management/cards_navigation/src/types.ts +++ b/packages/kbn-management/cards_navigation/src/types.ts @@ -29,6 +29,7 @@ export enum AppIds { ROLES = 'roles', API_KEYS = 'api_keys', DATA_QUALITY = 'data_quality', + SPACES = 'spaces', } // Create new type that is a union of all the appId values diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts index b914451abdb55..2d09f5a2b2ad7 100644 --- a/packages/kbn-management/settings/setting_ids/index.ts +++ b/packages/kbn-management/settings/setting_ids/index.ts @@ -143,6 +143,14 @@ export const OBSERVABILITY_LOGS_EXPLORER_ALLOWED_DATA_VIEWS_ID = 'observability:logsExplorer:allowedDataViews'; export const OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE = 'observability:entityCentricExperience'; export const OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID = 'observability:logSources'; +export const OBSERVABILITY_AI_ASSISTANT_LOGS_INDEX_PATTERN_ID = + 'observability:aiAssistantLogsIndexPattern'; +export const OBSERVABILITY_AI_ASSISTANT_RESPONSE_LANGUAGE = + 'observability:aiAssistantResponseLanguage'; +export const OBSERVABILITY_AI_ASSISTANT_SIMULATED_FUNCTION_CALLING = + 'observability:aiAssistantSimulatedFunctionCalling'; +export const OBSERVABILITY_AI_ASSISTANT_SEARCH_CONNECTOR_INDEX_PATTERN = + 'observability:aiAssistantSearchConnectorIndexPattern'; // Reporting settings export const XPACK_REPORTING_CUSTOM_PDF_LOGO_ID = 'xpackReporting:customPdfLogo'; diff --git a/packages/kbn-monaco/src/console/lexer_rules/console_output.ts b/packages/kbn-monaco/src/console/lexer_rules/console_output.ts index 8209820930910..697a594d0cd19 100644 --- a/packages/kbn-monaco/src/console/lexer_rules/console_output.ts +++ b/packages/kbn-monaco/src/console/lexer_rules/console_output.ts @@ -35,8 +35,10 @@ export const consoleOutputLexerRules: monaco.languages.IMonarchLanguage = { matchTokensWithEOL('status.success', /\b2\d{2}(?: \w+)*$/, 'root'), // Redirection messages (status codes 300 – 399) matchTokensWithEOL('status.redirect', /\b3\d{2}(?: \w+)*$/, 'root'), - // Client and server error responses (status codes 400 – 599) - matchTokensWithEOL('status.error', /\b[4-5]\d{2}(?: \w+)*$/, 'root'), + // Client error responses (status codes 400 – 499) + matchTokensWithEOL('status.warning', /\b4\d{2}(?: \w+)*$/, 'root'), + // Server error responses (status codes 500 – 599) + matchTokensWithEOL('status.error', /\b5\d{2}(?: \w+)*$/, 'root'), ], }, }; diff --git a/packages/kbn-monaco/src/console/lexer_rules/nested_sql.ts b/packages/kbn-monaco/src/console/lexer_rules/nested_sql.ts new file mode 100644 index 0000000000000..f6f79b0cba7fc --- /dev/null +++ b/packages/kbn-monaco/src/console/lexer_rules/nested_sql.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 { lexerRules as sqlLexerRules, keywords, builtinFunctions } from '../../sql/lexer_rules'; +/* + * This rule is used inside json root to start a sql highlighting sequence + */ +export const buildSqlStartRule = (sqlRoot: string = 'sql_root') => { + return [ + /("query")(\s*?)(:)(\s*?)(""")/, + [ + 'variable', + 'whitespace', + 'punctuation.colon', + 'whitespace', + { + token: 'punctuation', + next: `@${sqlRoot}`, + }, + ], + ]; +}; + +/* + * This function creates a group of rules needed for sql highlighting in console. + * It reuses the lexer ruls from the "sql" language, but since not all rules are referenced in the root + * tokenizer and to avoid conflicts with existing console rules, only selected rules are used. + */ +export const buildSqlRules = (sqlRoot: string = 'sql_root') => { + const { root, comment, numbers } = sqlLexerRules.tokenizer; + return { + [sqlRoot]: [ + // the rule to end sql highlighting and get back to the previous tokenizer state + [ + /"""/, + { + token: 'punctuation', + next: '@pop', + }, + ], + ...root, + ...numbers, + ], + comment, + }; +}; + +/* + * These language attributes need to be added to the console language definition for sql tokenizer + * to work correctly. + */ +export const sqlLanguageAttributes = { keywords, builtinFunctions }; diff --git a/packages/kbn-monaco/src/console/lexer_rules/shared.ts b/packages/kbn-monaco/src/console/lexer_rules/shared.ts index c46ef69af1002..9a877d8d131b1 100644 --- a/packages/kbn-monaco/src/console/lexer_rules/shared.ts +++ b/packages/kbn-monaco/src/console/lexer_rules/shared.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { buildSqlRules, buildSqlStartRule, sqlLanguageAttributes } from './nested_sql'; import { monaco } from '../../..'; import { globals } from '../../common/lexer_rules'; import { buildXjsonRules } from '../../xjson/lexer_rules/xjson'; @@ -87,21 +88,26 @@ export const matchTokensWithEOL = ( }; }; -export const xjsonRules = { ...buildXjsonRules('json_root') }; -// @ts-expect-error include comments into json -xjsonRules.json_root = [{ include: '@comments' }, ...xjsonRules.json_root]; +const xjsonRules = { ...buildXjsonRules('json_root') }; + xjsonRules.json_root = [ + // @ts-expect-error include comments into json + { include: '@comments' }, // @ts-expect-error include variables into json matchToken('variable.template', /("\${\w+}")/), + // @ts-expect-error include a rule to start sql highlighting + buildSqlStartRule(), ...xjsonRules.json_root, ]; +const sqlRules = buildSqlRules(); /* Lexer rules that are shared between the Console editor and the Console output panel. */ export const consoleSharedLexerRules: monaco.languages.IMonarchLanguage = { ...(globals as any), defaultToken: 'invalid', + ...sqlLanguageAttributes, tokenizer: { root: [ // warning comment @@ -127,5 +133,7 @@ export const consoleSharedLexerRules: monaco.languages.IMonarchLanguage = { ], // include json rules ...xjsonRules, + // include sql rules + ...sqlRules, }, }; diff --git a/packages/kbn-monaco/src/console/theme.ts b/packages/kbn-monaco/src/console/theme.ts index 3a90771354a53..4c9d4c2e03885 100644 --- a/packages/kbn-monaco/src/console/theme.ts +++ b/packages/kbn-monaco/src/console/theme.ts @@ -39,23 +39,23 @@ export const buildConsoleTheme = (): monaco.editor.IStandaloneThemeData => { ), ...buildRuleGroup( ['status.info'], - makeHighContrastColor(euiThemeVars.euiColorWarningText)(background), - true + makeHighContrastColor(euiThemeVars.euiTextColor)(background) ), ...buildRuleGroup( ['status.success'], - makeHighContrastColor(euiThemeVars.euiColorSuccessText)(background), - true + makeHighContrastColor(euiThemeVars.euiTextColor)(euiThemeVars.euiColorSuccess) ), ...buildRuleGroup( ['status.redirect'], - makeHighContrastColor(euiThemeVars.euiColorWarningText)(background), - true + makeHighContrastColor(euiThemeVars.euiTextColor)(background) + ), + ...buildRuleGroup( + ['status.warning'], + makeHighContrastColor(euiThemeVars.euiTextColor)(euiThemeVars.euiColorWarning) ), ...buildRuleGroup( ['status.error'], - makeHighContrastColor(euiThemeVars.euiColorDangerText)(background), - true + makeHighContrastColor('#FFFFFF')(euiThemeVars.euiColorDanger) ), ...buildRuleGroup(['method'], makeHighContrastColor(methodTextColor)(background)), ...buildRuleGroup(['url'], makeHighContrastColor(urlTextColor)(background)), diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts new file mode 100644 index 0000000000000..90abbb14c7913 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ESQLErrorListener, getLexer as _getLexer } from '@kbn/esql-ast'; +import { ESQL_TOKEN_POSTFIX } from './constants'; +import { buildESQlTheme } from './esql_theme'; +import { CharStreams } from 'antlr4'; + +describe('ESQL Theme', () => { + it('should not have multiple rules for a single token', () => { + const theme = buildESQlTheme(); + + const seen = new Set<string>(); + const duplicates: string[] = []; + for (const rule of theme.rules) { + if (seen.has(rule.token)) { + duplicates.push(rule.token); + } + seen.add(rule.token); + } + + expect(duplicates).toEqual([]); + }); + + const getLexer = () => { + const errorListener = new ESQLErrorListener(); + const inputStream = CharStreams.fromString('FROM foo'); + return _getLexer(inputStream, errorListener); + }; + + const lexer = getLexer(); + const lexicalNames = lexer.symbolicNames + .filter((name) => typeof name === 'string') + .map((name) => name!.toLowerCase()); + + it('every rule should apply to a valid lexical name', () => { + const theme = buildESQlTheme(); + + // These names aren't from the lexer... they are added on our side + // see packages/kbn-monaco/src/esql/lib/esql_token_helpers.ts + const syntheticNames = ['functions', 'nulls_order', 'timespan_literal']; + + for (const rule of theme.rules) { + expect([...lexicalNames, ...syntheticNames]).toContain( + rule.token.replace(ESQL_TOKEN_POSTFIX, '').toLowerCase() + ); + } + }); + + it('every valid lexical name should have a corresponding rule', () => { + const theme = buildESQlTheme(); + const tokenIDs = theme.rules.map((rule) => rule.token.replace(ESQL_TOKEN_POSTFIX, '')); + + const validExceptions = [ + 'unquoted_source', + 'false', // @TODO consider if this should get styling + 'true', // @TODO consider if this should get styling + 'info', // @TODO consider if this should get styling + 'colon', // @TODO consider if this should get styling + + 'nulls', // nulls is a part of nulls_order so it doesn't need its own rule + 'first', // first is a part of nulls_order so it doesn't need its own rule + 'last', // last is a part of nulls_order so it doesn't need its own rule + + 'id_pattern', // "KEEP <id_pattern>, <id_pattern>"... no styling needed + 'enrich_policy_name', // "ENRICH <enrich_policy_name>" + 'expr_ws', // whitespace, so no reason to style it + 'unknown_cmd', // unknown command, so no reason to style it + + // Lexer-mode-specific stuff + 'explain_line_comment', + 'explain_multiline_comment', + 'explain_ws', + 'project_line_comment', + 'project_multiline_comment', + 'project_ws', + 'rename_line_comment', + 'rename_multiline_comment', + 'rename_ws', + 'from_line_comment', + 'from_multiline_comment', + 'from_ws', + 'enrich_line_comment', + 'enrich_multiline_comment', + 'enrich_ws', + 'mvexpand_line_comment', + 'mvexpand_multiline_comment', + 'mvexpand_ws', + 'enrich_field_line_comment', + 'enrich_field_multiline_comment', + 'enrich_field_ws', + 'lookup_line_comment', + 'lookup_multiline_comment', + 'lookup_ws', + 'lookup_field_line_comment', + 'lookup_field_multiline_comment', + 'lookup_field_ws', + 'show_line_comment', + 'show_multiline_comment', + 'show_ws', + 'meta_line_comment', + 'meta_multiline_comment', + 'meta_ws', + 'setting', + 'setting_line_comment', + 'settting_multiline_comment', + 'setting_ws', + 'metrics_line_comment', + 'metrics_multiline_comment', + 'metrics_ws', + 'closing_metrics_line_comment', + 'closing_metrics_multiline_comment', + 'closing_metrics_ws', + ]; + + // First, check that every valid exception is actually valid + for (const name of validExceptions) { + expect(lexicalNames).toContain(name); + } + + const namesToCheck = lexicalNames.filter((name) => !validExceptions.includes(name)); + + // Now, check that every lexical name has a corresponding rule + for (const name of namesToCheck) { + expect(tokenIDs).toContain(name); + } + }); +}); diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.ts index 1e2028d672e1d..89e61ca0d7b62 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_theme.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.ts @@ -21,27 +21,15 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ ...buildRuleGroup( [ 'explain', - 'row', - 'limit', 'ws', 'assign', 'comma', 'dot', - 'first', - 'last', 'opening_bracket', 'closing_bracket', 'quoted_identifier', - 'src_ws', 'unquoted_identifier', 'pipe', - 'not', - 'percent', - 'integer_literal', - 'decimal_literal', - 'src_unquoted_identifier', - 'src_quoted_identifier', - 'string', ], euiThemeVars.euiTextColor ), @@ -57,12 +45,14 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ ...buildRuleGroup( [ 'metrics', + 'meta', 'metadata', + 'match', 'mv_expand', 'stats', + 'inlinestats', 'dissect', 'grok', - 'project', 'keep', 'rename', 'drop', @@ -76,8 +66,8 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'rlike', 'in', 'as', - 'expr_ws', 'limit', + 'lookup', 'null', 'enrich', 'on', @@ -112,26 +102,27 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'asterisk', // '*' 'slash', // '/' 'percent', // '%' + 'cast_op', // '::' ], euiThemeVars.euiColorPrimaryText ), // comments ...buildRuleGroup( - [ - 'line_comment', - 'multiline_comment', - 'expr_line_comment', - 'expr_multiline_comment', - 'src_line_comment', - 'src_multiline_comment', - ], + ['line_comment', 'multiline_comment', 'expr_line_comment', 'expr_multiline_comment'], euiThemeVars.euiColorDisabledText ), // values ...buildRuleGroup( - ['quoted_string', 'integer_literal', 'decimal_literal', 'named_or_positional_param'], + [ + 'quoted_string', + 'integer_literal', + 'decimal_literal', + 'named_or_positional_param', + 'param', + 'timespan_literal', + ], euiThemeVars.euiColorSuccessText ), ], diff --git a/packages/kbn-monaco/src/esql/lib/esql_token_helpers.ts b/packages/kbn-monaco/src/esql/lib/esql_token_helpers.ts index a43360f48e9c9..e672805767535 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_token_helpers.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_token_helpers.ts @@ -8,6 +8,7 @@ import { monaco } from '../../monaco_imports'; import { ESQL_TOKEN_POSTFIX } from './constants'; +import { ESQLToken } from './esql_token'; function nonNullable<T>(value: T | undefined): value is T { return value != null; @@ -33,17 +34,46 @@ export function addFunctionTokens(tokens: monaco.languages.IToken[]): monaco.lan return [...tokens]; } -export function addNullsOrder(tokens: monaco.languages.IToken[]): void { - const nullsIndex = tokens.findIndex((token) => token.scopes === 'nulls' + ESQL_TOKEN_POSTFIX); - if ( - // did we find a "nulls"? - nullsIndex > -1 && - // is the next non-whitespace token an order? - ['first' + ESQL_TOKEN_POSTFIX, 'last' + ESQL_TOKEN_POSTFIX].includes( - tokens[nullsIndex + 2]?.scopes - ) - ) { - tokens[nullsIndex].scopes = 'nulls_order' + ESQL_TOKEN_POSTFIX; - tokens.splice(nullsIndex + 1, 2); +const mergeRules = [ + [['nulls', 'expr_ws', 'first'], 'nulls_order'], + [['nulls', 'expr_ws', 'last'], 'nulls_order'], + [['integer', 'unquoted_identifier'], 'timespan_literal'], + [['integer_literal', 'expr_ws', 'unquoted_identifier'], 'timespan_literal'], +] as const; + +export function mergeTokens(tokens: ESQLToken[]): monaco.languages.IToken[] { + for (const [scopes, newScope] of mergeRules) { + let foundAnyMatches = false; + do { + foundAnyMatches = false; + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].scopes === scopes[0] + ESQL_TOKEN_POSTFIX) { + // first matched so look ahead if there's room + if (i + scopes.length > tokens.length) { + continue; + } + + let match = true; + for (let j = 1; j < scopes.length; j++) { + if (tokens[i + j].scopes !== scopes[j] + ESQL_TOKEN_POSTFIX) { + match = false; + break; + } + } + + if (match) { + foundAnyMatches = true; + const mergedToken = new ESQLToken( + newScope, + tokens[i].startIndex, + tokens[i + scopes.length - 1].stopIndex + ); + tokens.splice(i, scopes.length, mergedToken); + } + } + } + } while (foundAnyMatches); } + + return tokens; } diff --git a/packages/kbn-monaco/src/esql/lib/esql_tokens_provider.test.ts b/packages/kbn-monaco/src/esql/lib/esql_tokens_provider.test.ts new file mode 100644 index 0000000000000..ba0d690aafeee --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/esql_tokens_provider.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ESQLState } from './esql_state'; +import { ESQLToken } from './esql_token'; +import { ESQLTokensProvider } from './esql_tokens_provider'; + +describe('ES|QL Tokens Provider', () => { + it('should tokenize a line', () => { + const line = 'SELECT * FROM my_index'; + const prevState = new ESQLState(); + const provider = new ESQLTokensProvider(); + const { tokens } = provider.tokenize(line, prevState); + expect(tokens.map((t) => t.scopes)).toEqual([ + 'unknown_cmd.esql', + 'expr_ws.esql', + 'asterisk.esql', + 'expr_ws.esql', + 'unquoted_identifier.esql', + 'expr_ws.esql', + 'unquoted_identifier.esql', + ]); + }); + + it('should properly tokenize functions', () => { + const line = 'FROM my_index | EVAL date_diff("day", NOW()) | STATS abs(field1), avg(field1)'; + const provider = new ESQLTokensProvider(); + const { tokens } = provider.tokenize(line, new ESQLState()); + const functionTokens = tokens.filter((t) => t.scopes === 'functions.esql'); + expect(functionTokens).toHaveLength(4); + }); + + it('should properly tokenize SORT... NULLS clauses', () => { + const line = 'SELECT * FROM my_index | SORT BY field1 ASC NULLS FIRST, field2 DESC NULLS LAST'; + const provider = new ESQLTokensProvider(); + const { tokens } = provider.tokenize(line, new ESQLState()); + // Make sure the tokens got merged properly + const nullsOrderTokens = tokens.filter((t) => t.scopes === 'nulls_order.esql'); + expect(nullsOrderTokens).toHaveLength(2); + expect(nullsOrderTokens).toEqual<ESQLToken[]>([ + { + scopes: 'nulls_order.esql', + startIndex: 44, + stopIndex: 54, + }, + { + scopes: 'nulls_order.esql', + startIndex: 69, + stopIndex: 78, + }, + ]); + // Ensure that the NULLS FIRST and NULLS LAST tokens are not present + expect(tokens.map((t) => t.scopes)).not.toContain('nulls.esql'); + expect(tokens.map((t) => t.scopes)).not.toContain('first.esql'); + expect(tokens.map((t) => t.scopes)).not.toContain('last.esql'); + }); + + it('should properly tokenize timespan literals', () => { + const line = 'SELECT * FROM my_index | WHERE date_field > 1 day AND other_field < 2 hours'; + const provider = new ESQLTokensProvider(); + const { tokens } = provider.tokenize(line, new ESQLState()); + const timespanTokens = tokens.filter((t) => t.scopes === 'timespan_literal.esql'); + expect(timespanTokens).toHaveLength(2); + }); +}); diff --git a/packages/kbn-monaco/src/esql/lib/esql_tokens_provider.ts b/packages/kbn-monaco/src/esql/lib/esql_tokens_provider.ts index d5cbdf4349b4c..ae4877f410eb1 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_tokens_provider.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_tokens_provider.ts @@ -15,7 +15,7 @@ import { ESQLLineTokens } from './esql_line_tokens'; import { ESQLState } from './esql_state'; import { ESQL_TOKEN_POSTFIX } from './constants'; -import { addFunctionTokens, addNullsOrder } from './esql_token_helpers'; +import { addFunctionTokens, mergeTokens } from './esql_token_helpers'; const EOF = -1; @@ -37,7 +37,7 @@ export class ESQLTokensProvider implements monaco.languages.TokensProvider { const lexer = getLexer(inputStream, errorListener); let done = false; - const myTokens: monaco.languages.IToken[] = []; + const myTokens: ESQLToken[] = []; do { let token: Token | null; @@ -78,7 +78,7 @@ export class ESQLTokensProvider implements monaco.languages.TokensProvider { // the previous custom Kibana grammar baked functions directly as tokens, so highlight was easier // The ES grammar doesn't have the token concept of "function" const tokensWithFunctions = addFunctionTokens(myTokens); - addNullsOrder(tokensWithFunctions); + mergeTokens(tokensWithFunctions); return new ESQLLineTokens(tokensWithFunctions, prevState.getLineNumber() + 1); } diff --git a/packages/kbn-monaco/src/esql/lib/hover/hover.test.ts b/packages/kbn-monaco/src/esql/lib/hover/hover.test.ts index 141653c2b2cf6..d49fb150d7c12 100644 --- a/packages/kbn-monaco/src/esql/lib/hover/hover.test.ts +++ b/packages/kbn-monaco/src/esql/lib/hover/hover.test.ts @@ -11,17 +11,21 @@ import { getHoverItem } from './hover'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { ENRICH_MODES, + ESQLRealField, getFunctionDefinition, getFunctionSignatures, } from '@kbn/esql-validation-autocomplete'; +import { FieldType } from '@kbn/esql-validation-autocomplete/src/definitions/types'; -const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [ - ...['string', 'number', 'date', 'boolean', 'ip'].map((type) => ({ +const types: FieldType[] = ['keyword', 'double', 'date', 'boolean', 'ip']; + +const fields: Array<ESQLRealField & { suggestedAs?: string }> = [ + ...types.map((type) => ({ name: `${type}Field`, type, })), - { name: 'any#Char$Field', type: 'number', suggestedAs: '`any#Char$Field`' }, - { name: 'kubernetes.something.something', type: 'number' }, + { name: 'any#Char$Field', type: 'double', suggestedAs: '`any#Char$Field`' }, + { name: 'kubernetes.something.something', type: 'double' }, ]; const indexes = ( @@ -56,7 +60,7 @@ const policies = [ ]; function createCustomCallbackMocks( - customFields: Array<{ name: string; type: string }> | undefined, + customFields: ESQLRealField[] | undefined, customSources: Array<{ name: string; hidden: boolean }> | undefined, customPolicies: | Array<{ diff --git a/packages/kbn-monaco/src/monaco_imports.ts b/packages/kbn-monaco/src/monaco_imports.ts index 41109a35fa160..f7f81b03f371b 100644 --- a/packages/kbn-monaco/src/monaco_imports.ts +++ b/packages/kbn-monaco/src/monaco_imports.ts @@ -31,6 +31,7 @@ import 'monaco-editor/esm/vs/editor/contrib/codeAction/browser/codeActionMenu.js import 'monaco-editor/esm/vs/editor/contrib/codeAction/browser/codeActionModel.js'; import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController'; // Needed for Search bar functionality +import 'monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js'; // Needed for inspect tokens functionality import 'monaco-editor/esm/vs/language/json/monaco.contribution.js'; import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js'; // Needed for basic javascript support diff --git a/packages/kbn-monaco/src/sql/lexer_rules/index.ts b/packages/kbn-monaco/src/sql/lexer_rules/index.ts index 5b32abfde89a5..f8d6fdc8a1ef5 100644 --- a/packages/kbn-monaco/src/sql/lexer_rules/index.ts +++ b/packages/kbn-monaco/src/sql/lexer_rules/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { lexerRules } from './sql'; +export { lexerRules, keywords, builtinFunctions } from './sql'; diff --git a/packages/kbn-monaco/src/sql/lexer_rules/sql.ts b/packages/kbn-monaco/src/sql/lexer_rules/sql.ts index 827f3cba68851..5248e34a3f4ac 100644 --- a/packages/kbn-monaco/src/sql/lexer_rules/sql.ts +++ b/packages/kbn-monaco/src/sql/lexer_rules/sql.ts @@ -13,7 +13,7 @@ const brackets = [ { open: '(', close: ')', token: 'delimiter.parenthesis' }, ]; -const keywords = [ +export const keywords = [ 'describe', 'between', 'in', @@ -47,7 +47,7 @@ const keywords = [ 'distinct', 'is', ]; -const builtinFunctions = [ +export const builtinFunctions = [ 'avg', 'count', 'first', @@ -212,7 +212,7 @@ export const lexerRules = { { cases: { '@keywords': 'keyword', - '@builtinFunctions': 'identifier', + '@builtinFunctions': 'keyword', '@default': 'identifier', }, }, diff --git a/packages/kbn-openapi-bundler/src/bundler/merge_documents/create_blank_oas_document.ts b/packages/kbn-openapi-bundler/src/bundler/merge_documents/create_blank_oas_document.ts index 925471719b345..363888566aa31 100644 --- a/packages/kbn-openapi-bundler/src/bundler/merge_documents/create_blank_oas_document.ts +++ b/packages/kbn-openapi-bundler/src/bundler/merge_documents/create_blank_oas_document.ts @@ -10,12 +10,32 @@ import { OpenAPIV3 } from 'openapi-types'; export function createBlankOpenApiDocument( oasVersion: string, - info: OpenAPIV3.InfoObject + overrides?: Partial<OpenAPIV3.Document> ): OpenAPIV3.Document { return { openapi: oasVersion, - info, - servers: [ + info: overrides?.info ?? { + title: 'Merged OpenAPI specs', + version: 'not specified', + }, + paths: overrides?.paths ?? {}, + components: { + schemas: overrides?.components?.schemas, + responses: overrides?.components?.responses, + parameters: overrides?.components?.parameters, + examples: overrides?.components?.examples, + requestBodies: overrides?.components?.requestBodies, + headers: overrides?.components?.headers, + securitySchemes: overrides?.components?.securitySchemes ?? { + BasicAuth: { + type: 'http', + scheme: 'basic', + }, + }, + links: overrides?.components?.links, + callbacks: overrides?.components?.callbacks, + }, + servers: overrides?.servers ?? [ { url: 'http://{kibana_host}:{port}', variables: { @@ -28,19 +48,12 @@ export function createBlankOpenApiDocument( }, }, ], - security: [ + security: overrides?.security ?? [ { BasicAuth: [], }, ], - paths: {}, - components: { - securitySchemes: { - BasicAuth: { - type: 'http', - scheme: 'basic', - }, - }, - }, + tags: overrides?.tags, + externalDocs: overrides?.externalDocs, }; } diff --git a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_documents.ts b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_documents.ts index 24e507bd0e283..0976f43da7cfb 100644 --- a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_documents.ts +++ b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_documents.ts @@ -17,8 +17,9 @@ import { mergeTags } from './merge_tags'; import { getOasVersion } from '../../utils/get_oas_version'; import { getOasDocumentVersion } from '../../utils/get_oas_document_version'; import { enrichWithVersionMimeParam } from './enrich_with_version_mime_param'; +import { MergeOptions } from './merge_options'; -export interface MergeDocumentsOptions { +interface MergeDocumentsOptions extends MergeOptions { splitDocumentsByVersion: boolean; } @@ -52,11 +53,25 @@ export async function mergeDocuments( ...documentsGroup, ]; - mergedDocument.servers = mergeServers(documentsToMerge); - mergedDocument.paths = mergePaths(documentsToMerge); - mergedDocument.components = mergeSharedComponents(documentsToMerge); - mergedDocument.security = mergeSecurityRequirements(documentsToMerge); - mergedDocument.tags = mergeTags(documentsToMerge); + mergedDocument.paths = mergePaths(documentsToMerge, options); + mergedDocument.components = { + ...mergedDocument.components, + ...mergeSharedComponents(documentsToMerge, options), + }; + + if (!options.skipServers) { + mergedDocument.servers = mergeServers(documentsToMerge); + } + + if (!options.skipSecurity) { + mergedDocument.security = mergeSecurityRequirements(documentsToMerge); + } + + const mergedTags = [...(options.addTags ?? []), ...(mergeTags(documentsToMerge) ?? [])]; + + if (mergedTags.length) { + mergedDocument.tags = mergedTags; + } mergedByVersion.set(mergedDocument.info.version, mergedDocument); } diff --git a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_operations.ts b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_operations.ts index c7a4ae4edbd7f..e48f7e8a958fb 100644 --- a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_operations.ts +++ b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_operations.ts @@ -11,10 +11,12 @@ import deepEqual from 'fast-deep-equal'; import { OpenAPIV3 } from 'openapi-types'; import { KNOWN_HTTP_METHODS } from './http_methods'; import { isRefNode } from '../process_document'; +import { MergeOptions } from './merge_options'; export function mergeOperations( sourcePathItem: OpenAPIV3.PathItemObject, - mergedPathItem: OpenAPIV3.PathItemObject + mergedPathItem: OpenAPIV3.PathItemObject, + options: MergeOptions ) { for (const httpMethod of KNOWN_HTTP_METHODS) { const sourceOperation = sourcePathItem[httpMethod]; @@ -24,12 +26,25 @@ export function mergeOperations( continue; } - if (!mergedOperation || deepEqual(sourceOperation, mergedOperation)) { - mergedPathItem[httpMethod] = sourceOperation; + // Adding tags before merging helps to reuse already existing functionality + // without changes. It imitates a case when such tags already existed in source operations. + const extendedTags = [ + ...(options.addTags?.map((t) => t.name) ?? []), + ...(sourceOperation.tags ?? []), + ]; + const normalizedSourceOperation = { + ...sourceOperation, + ...(options.skipServers ? { servers: undefined } : { servers: sourceOperation.servers }), + ...(options.skipSecurity ? { security: undefined } : { security: sourceOperation.security }), + ...(extendedTags.length > 0 ? { tags: extendedTags } : {}), + }; + + if (!mergedOperation || deepEqual(normalizedSourceOperation, mergedOperation)) { + mergedPathItem[httpMethod] = normalizedSourceOperation; continue; } - mergeOperation(sourceOperation, mergedOperation); + mergeOperation(normalizedSourceOperation, mergedOperation); } } diff --git a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_options.ts b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_options.ts new file mode 100644 index 0000000000000..837ef9db8114e --- /dev/null +++ b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { OpenAPIV3 } from 'openapi-types'; + +export interface MergeOptions { + skipServers: boolean; + skipSecurity: boolean; + addTags?: OpenAPIV3.TagObject[]; +} diff --git a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_paths.ts b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_paths.ts index d1a775e07b278..938da557aa07b 100644 --- a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_paths.ts +++ b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_paths.ts @@ -12,8 +12,12 @@ import { ResolvedDocument } from '../ref_resolver/resolved_document'; import { isRefNode } from '../process_document'; import { mergeOperations } from './merge_operations'; import { mergeArrays } from './merge_arrays'; +import { MergeOptions } from './merge_options'; -export function mergePaths(resolvedDocuments: ResolvedDocument[]): OpenAPIV3.PathsObject { +export function mergePaths( + resolvedDocuments: ResolvedDocument[], + options: MergeOptions +): OpenAPIV3.PathsObject { const mergedPaths: Record<string, OpenAPIV3.PathItemObject> = {}; for (const { absolutePath, document } of resolvedDocuments) { @@ -60,7 +64,7 @@ export function mergePaths(resolvedDocuments: ResolvedDocument[]): OpenAPIV3.Pat } try { - mergeOperations(sourcePathItem, mergedPathItem); + mergeOperations(sourcePathItem, mergedPathItem, options); } catch (e) { throw new Error( `❌ Unable to merge ${chalk.bold(absolutePath)} due to an error in ${chalk.bold( @@ -69,7 +73,9 @@ export function mergePaths(resolvedDocuments: ResolvedDocument[]): OpenAPIV3.Pat ); } - mergePathItemServers(sourcePathItem, mergedPathItem); + if (!options.skipServers) { + mergePathItemServers(sourcePathItem, mergedPathItem); + } try { mergeParameters(sourcePathItem, mergedPathItem); diff --git a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_shared_components.ts b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_shared_components.ts index 2efddc09e1de0..10b9300f69935 100644 --- a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_shared_components.ts +++ b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_shared_components.ts @@ -12,6 +12,7 @@ import { OpenAPIV3 } from 'openapi-types'; import { ResolvedDocument } from '../ref_resolver/resolved_document'; import { extractObjectByJsonPointer } from '../../utils/extract_by_json_pointer'; import { logger } from '../../logger'; +import { MergeOptions } from './merge_options'; const MERGEABLE_COMPONENT_TYPES = [ 'schemas', @@ -26,11 +27,16 @@ const MERGEABLE_COMPONENT_TYPES = [ ] as const; export function mergeSharedComponents( - bundledDocuments: ResolvedDocument[] + bundledDocuments: ResolvedDocument[], + options: MergeOptions ): OpenAPIV3.ComponentsObject { const mergedComponents: Record<string, unknown> = {}; for (const componentsType of MERGEABLE_COMPONENT_TYPES) { + if (options.skipSecurity && componentsType === 'securitySchemes') { + continue; + } + const mergedTypedComponents = mergeObjects(bundledDocuments, `/components/${componentsType}`); if (Object.keys(mergedTypedComponents).length === 0) { diff --git a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_tags.ts b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_tags.ts index e1b8411538deb..3eeb555359df3 100644 --- a/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_tags.ts +++ b/packages/kbn-openapi-bundler/src/bundler/merge_documents/merge_tags.ts @@ -19,5 +19,13 @@ export function mergeTags( const merged = mergeArrays(tagsArrayOfArrays); - return merged.length > 0 ? merged : undefined; + if (merged.length === 0) { + return; + } + + // To streamline API endpoints categorization it's expected that + // tags are sorted alphabetically by name + merged.sort((a, b) => a.name.localeCompare(b.name)); + + return merged; } diff --git a/packages/kbn-openapi-bundler/src/openapi_bundler.ts b/packages/kbn-openapi-bundler/src/openapi_bundler.ts index 90382150400dd..7d424af177dd7 100644 --- a/packages/kbn-openapi-bundler/src/openapi_bundler.ts +++ b/packages/kbn-openapi-bundler/src/openapi_bundler.ts @@ -7,8 +7,6 @@ */ import chalk from 'chalk'; -import { isUndefined, omitBy } from 'lodash'; -import { OpenAPIV3 } from 'openapi-types'; import { basename, dirname } from 'path'; import { bundleDocument, SkipException } from './bundler/bundle_document'; import { mergeDocuments } from './bundler/merge_documents'; @@ -19,6 +17,8 @@ import { writeDocuments } from './utils/write_documents'; import { ResolvedDocument } from './bundler/ref_resolver/resolved_document'; import { resolveGlobs } from './utils/resolve_globs'; import { DEFAULT_BUNDLING_PROCESSORS, withIncludeLabelsProcessor } from './bundler/processor_sets'; +import { PrototypeDocument } from './prototype_document'; +import { validatePrototypeDocument } from './validate_prototype_document'; export interface BundlerConfig { sourceGlob: string; @@ -27,15 +27,26 @@ export interface BundlerConfig { } interface BundleOptions { + /** + * OpenAPI document itself or path to the document + */ + prototypeDocument?: PrototypeDocument | string; + /** + * When `includeLabels` are specified the produced bundle will contain only + * operations objects with matching labels + */ includeLabels?: string[]; - specInfo?: Omit<Partial<OpenAPIV3.InfoObject>, 'version'>; } export const bundle = async ({ sourceGlob, - outputFilePath = 'bundled-{version}.schema.yaml', + outputFilePath = 'bundled_{version}.schema.yaml', options, }: BundlerConfig) => { + const prototypeDocument = options?.prototypeDocument + ? await validatePrototypeDocument(options?.prototypeDocument) + : undefined; + logger.debug(chalk.bold(`Bundling API route schemas`)); logger.debug(`👀 Searching for source files in ${chalk.underline(sourceGlob)}`); @@ -56,22 +67,22 @@ export const bundle = async ({ logger.success(`Processed ${bundledDocuments.length} schemas`); - const blankOasFactory = (oasVersion: string, apiVersion: string) => + const blankOasDocumentFactory = (oasVersion: string, apiVersion: string) => createBlankOpenApiDocument(oasVersion, { - version: apiVersion, - title: options?.specInfo?.title ?? 'Bundled OpenAPI specs', - ...omitBy( - { - description: options?.specInfo?.description, - termsOfService: options?.specInfo?.termsOfService, - contact: options?.specInfo?.contact, - license: options?.specInfo?.license, - }, - isUndefined - ), + info: prototypeDocument?.info + ? { ...DEFAULT_INFO, ...prototypeDocument.info, version: apiVersion } + : { ...DEFAULT_INFO, version: apiVersion }, + servers: prototypeDocument?.servers, + security: prototypeDocument?.security, + components: { + securitySchemes: prototypeDocument?.components?.securitySchemes, + }, }); - const resultDocumentsMap = await mergeDocuments(bundledDocuments, blankOasFactory, { + const resultDocumentsMap = await mergeDocuments(bundledDocuments, blankOasDocumentFactory, { splitDocumentsByVersion: true, + skipServers: Boolean(prototypeDocument?.servers), + skipSecurity: Boolean(prototypeDocument?.security), + addTags: prototypeDocument?.tags, }); await writeDocuments(resultDocumentsMap, outputFilePath); @@ -130,3 +141,7 @@ function filterOutSkippedDocuments( return processedDocuments; } + +const DEFAULT_INFO = { + title: 'Bundled OpenAPI specs', +} as const; diff --git a/packages/kbn-openapi-bundler/src/openapi_merger.ts b/packages/kbn-openapi-bundler/src/openapi_merger.ts index a7ac3c3492dfe..3fb04de279d1d 100644 --- a/packages/kbn-openapi-bundler/src/openapi_merger.ts +++ b/packages/kbn-openapi-bundler/src/openapi_merger.ts @@ -7,7 +7,7 @@ */ import chalk from 'chalk'; -import { OpenAPIV3 } from 'openapi-types'; + import { mergeDocuments } from './bundler/merge_documents'; import { logger } from './logger'; import { createBlankOpenApiDocument } from './bundler/merge_documents/create_blank_oas_document'; @@ -16,13 +16,20 @@ import { writeDocuments } from './utils/write_documents'; import { resolveGlobs } from './utils/resolve_globs'; import { bundleDocument } from './bundler/bundle_document'; import { withNamespaceComponentsProcessor } from './bundler/processor_sets'; +import { PrototypeDocument } from './prototype_document'; +import { validatePrototypeDocument } from './validate_prototype_document'; export interface MergerConfig { sourceGlobs: string[]; outputFilePath: string; - options?: { - mergedSpecInfo?: Partial<OpenAPIV3.InfoObject>; - }; + options?: MergerOptions; +} + +interface MergerOptions { + /** + * OpenAPI document itself or path to the document + */ + prototypeDocument?: PrototypeDocument | string; } export const merge = async ({ @@ -34,6 +41,10 @@ export const merge = async ({ throw new Error('As minimum one source glob is expected'); } + const prototypeDocument = options?.prototypeDocument + ? await validatePrototypeDocument(options?.prototypeDocument) + : undefined; + logger.info(chalk.bold(`Merging OpenAPI specs`)); logger.info( `👀 Searching for source files in ${sourceGlobs @@ -52,13 +63,19 @@ export const merge = async ({ const blankOasDocumentFactory = (oasVersion: string) => createBlankOpenApiDocument(oasVersion, { - title: 'Merged OpenAPI specs', - version: 'not specified', - ...(options?.mergedSpecInfo ?? {}), + info: prototypeDocument?.info ? { ...DEFAULT_INFO, ...prototypeDocument.info } : DEFAULT_INFO, + servers: prototypeDocument?.servers, + security: prototypeDocument?.security, + components: { + securitySchemes: prototypeDocument?.components?.securitySchemes, + }, }); const resultDocumentsMap = await mergeDocuments(bundledDocuments, blankOasDocumentFactory, { splitDocumentsByVersion: false, + skipServers: Boolean(prototypeDocument?.servers), + skipSecurity: Boolean(prototypeDocument?.security), + addTags: prototypeDocument?.tags, }); // Only one document is expected when `splitDocumentsByVersion` is set to `false` const mergedDocument = Array.from(resultDocumentsMap.values())[0]; @@ -80,3 +97,8 @@ async function bundleDocuments(schemaFilePaths: string[]): Promise<ResolvedDocum ) ); } + +const DEFAULT_INFO = { + title: 'Merged OpenAPI specs', + version: 'not specified', +} as const; diff --git a/packages/kbn-openapi-bundler/src/prototype_document.ts b/packages/kbn-openapi-bundler/src/prototype_document.ts new file mode 100644 index 0000000000000..924ca223cb0b4 --- /dev/null +++ b/packages/kbn-openapi-bundler/src/prototype_document.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 { OpenAPIV3 } from 'openapi-types'; + +/** + * `PrototypeDocument` is used as a prototype for the result file. + * Only specified properties are used. All the other properties will be ignored. + */ +export interface PrototypeDocument { + /** + * Defines OpenAPI Info Object to be used in the result document. + * `bundle()` utility doesn't use `info.version`. + */ + info?: Partial<OpenAPIV3.InfoObject>; + /** + * Defines `servers` to be used in the result document. When `servers` + * are set existing source documents `servers` aren't included into + * the result document. + */ + servers?: OpenAPIV3.ServerObject[]; + /** + * Defines security requirements to be used in the result document. It must + * be used together with `components.securitySchemes` When `security` + * is set existing source documents `security` isn't included into + * the result document. + */ + security?: OpenAPIV3.SecurityRequirementObject[]; + components?: { + /** + * Defines security schemes for security requirements. + */ + securitySchemes: Record<string, OpenAPIV3.SecuritySchemeObject>; + }; + /** + * Defines tags to be added to the result document. Tags are added to + * root level tags and prepended to operation object tags. + */ + tags?: OpenAPIV3.TagObject[]; +} diff --git a/packages/kbn-openapi-bundler/src/utils/read_document.ts b/packages/kbn-openapi-bundler/src/utils/read_document.ts index 019f5103cf621..49476c134e91f 100644 --- a/packages/kbn-openapi-bundler/src/utils/read_document.ts +++ b/packages/kbn-openapi-bundler/src/utils/read_document.ts @@ -7,37 +7,48 @@ */ import fs from 'fs/promises'; -import { load } from 'js-yaml'; import { basename, extname } from 'path'; +import { load } from 'js-yaml'; import chalk from 'chalk'; import { logger } from '../logger'; +import { isPlainObjectType } from './is_plain_object_type'; export async function readDocument(documentPath: string): Promise<Record<string, unknown>> { - const extension = extname(documentPath); - logger.debug(`Reading ${chalk.bold(basename(documentPath))}`); + const maybeDocument = await readFile(documentPath); + + if (!isPlainObjectType(maybeDocument)) { + throw new Error(`File at ${chalk.bold(documentPath)} is not valid OpenAPI document`); + } + + return maybeDocument; +} + +async function readFile(filePath: string): Promise<unknown> { + const extension = extname(filePath); + switch (extension) { case '.yaml': case '.yml': - return await readYamlDocument(documentPath); + return await readYamlFile(filePath); case '.json': - return await readJsonDocument(documentPath); + return await readJsonFile(filePath); default: throw new Error(`${extension} files are not supported`); } } -async function readYamlDocument(filePath: string): Promise<Record<string, unknown>> { +async function readYamlFile(filePath: string): Promise<Record<string, unknown>> { // Typing load's result to Record<string, unknown> is optimistic as we can't be sure // there is object inside a yaml file. We don't have this validation layer so far // but using JSON Schemas here should mitigate this problem. return load(await fs.readFile(filePath, { encoding: 'utf8' })); } -export async function readJsonDocument(filePath: string): Promise<Record<string, unknown>> { +async function readJsonFile(filePath: string): Promise<Record<string, unknown>> { // Typing load's result to Record<string, unknown> is optimistic as we can't be sure // there is object inside a yaml file. We don't have this validation layer so far // but using JSON Schemas here should mitigate this problem. diff --git a/packages/kbn-openapi-bundler/src/validate_prototype_document.ts b/packages/kbn-openapi-bundler/src/validate_prototype_document.ts new file mode 100644 index 0000000000000..7e87bd565c515 --- /dev/null +++ b/packages/kbn-openapi-bundler/src/validate_prototype_document.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 chalk from 'chalk'; +import { PrototypeDocument } from './prototype_document'; +import { readDocument } from './utils/read_document'; + +/** + * Validates that passed `prototypeDocument` fulfills the requirements. + * + * In particular security requirements must be specified via `security` and + * `components.securitySchemes` properties. + * + */ +export async function validatePrototypeDocument( + prototypeDocumentOrString: PrototypeDocument | string +): Promise<PrototypeDocument> { + const prototypeDocument: PrototypeDocument | undefined = + typeof prototypeDocumentOrString === 'string' + ? await readDocument(prototypeDocumentOrString) + : prototypeDocumentOrString; + + if (prototypeDocument.servers && !Array.isArray(prototypeDocument.servers)) { + throw new Error(`Prototype document's ${chalk.bold('servers')} must be an array`); + } + + if (prototypeDocument.servers && prototypeDocument.servers.length === 0) { + throw new Error( + `Prototype document's ${chalk.bold('servers')} should have as minimum one entry` + ); + } + + if (prototypeDocument.security && !Array.isArray(prototypeDocument.security)) { + throw new Error(`Prototype document's ${chalk.bold('security')} must be an array`); + } + + if (prototypeDocument.security && prototypeDocument.security.length === 0) { + throw new Error( + `Prototype document's ${chalk.bold('security')} should have as minimum one entry` + ); + } + + if (prototypeDocument.tags && !Array.isArray(prototypeDocument.tags)) { + throw new Error(`Prototype document's ${chalk.bold('tags')} must be an array`); + } + + if (prototypeDocument.tags && prototypeDocument.tags.length === 0) { + throw new Error(`Prototype document's ${chalk.bold('tags')} should have as minimum one entry`); + } + + if (prototypeDocument.security && !prototypeDocument.components?.securitySchemes) { + throw new Error( + `Prototype document must contain ${chalk.bold( + 'components.securitySchemes' + )} when security requirements are specified` + ); + } + + if (prototypeDocument.components?.securitySchemes && !prototypeDocument.security) { + throw new Error( + `Prototype document must have ${chalk.bold('security')} defined ${chalk.bold( + 'components.securitySchemes' + )} are specified` + ); + } + + return prototypeDocument; +} diff --git a/packages/kbn-openapi-bundler/tests/bundler/result_overrides/add_tags.test.ts b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/add_tags.test.ts new file mode 100644 index 0000000000000..405f429f30ce5 --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/add_tags.test.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 { createOASDocument } from '../../create_oas_document'; +import { bundleSpecs } from '../bundle_specs'; + +describe('OpenAPI Bundler - assign a tag', () => { + it('adds tags when nothing is set', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + tags: [ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + ], + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.tags).toEqual(['Some Tag', 'Another Tag']); + expect(bundledSpec.paths['/api/another_api']?.get?.tags).toEqual(['Some Tag', 'Another Tag']); + expect(bundledSpec.tags).toEqual([ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + ]); + }); + + it('adds tags to existing tags', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + tags: ['Local tag'], + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + tags: ['Global tag'], + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + tags: [{ name: 'Global tag', description: 'Global tag description' }], + }); + + const [bundledSpec] = Object.values( + await bundleSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + tags: [ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + ], + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.tags).toEqual([ + 'Some Tag', + 'Another Tag', + 'Local tag', + ]); + expect(bundledSpec.paths['/api/another_api']?.get?.tags).toEqual([ + 'Some Tag', + 'Another Tag', + 'Global tag', + ]); + expect(bundledSpec.tags).toEqual([ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + { name: 'Global tag', description: 'Global tag description' }, + ]); + }); +}); diff --git a/packages/kbn-openapi-bundler/tests/bundler/result_overrides/security.test.ts b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/security.test.ts new file mode 100644 index 0000000000000..12f3f14ac45c2 --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/security.test.ts @@ -0,0 +1,403 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import chalk from 'chalk'; +import { OpenAPIV3 } from 'openapi-types'; +import { createOASDocument } from '../../create_oas_document'; +import { bundleSpecs } from '../bundle_specs'; + +describe('OpenAPI Bundler - with security requirements overrides', () => { + describe('enabled', () => { + it('throws an error when security requirements are specified without components security schemes', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: {}, + }, + }, + }, + }); + + await expect( + bundleSpecs( + { + 1: spec1, + }, + { + prototypeDocument: { + security: [{ ShouldBeUsedSecurityRequirement: [] }], + }, + } + ) + ).rejects.toThrowError( + `Prototype document must contain ${chalk.bold( + 'components.securitySchemes' + )} when security requirements are specified` + ); + }); + + it('throws an error when components security schemes are specified without security requirements', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: {}, + }, + }, + }, + }); + + await expect( + bundleSpecs( + { + 1: spec1, + }, + { + prototypeDocument: { + components: { + securitySchemes: { + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }, + } + ) + ).rejects.toThrowError( + `Prototype document must have ${chalk.bold('security')} defined ${chalk.bold( + 'components.securitySchemes' + )} are specified` + ); + }); + + it('overrides root level `security`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: [{ SomeSecurityRequirement: [] }], + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }], + }); + + const [bundledSpec] = Object.values( + await bundleSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + security: [{ ShouldBeUsedSecurityRequirement: [] }], + components: { + securitySchemes: { + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }, + } + ) + ); + + expect(bundledSpec.security).toEqual([{ ShouldBeUsedSecurityRequirement: [] }]); + expect(bundledSpec.components?.securitySchemes).toEqual({ + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }); + }); + + it('drops operation level security requirements', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: [{ SomeSecurityRequirement: [] }], + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }], + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + security: [{ ShouldBeUsedSecurityRequirement: [] }], + components: { + securitySchemes: { + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.security).toBeUndefined(); + expect(bundledSpec.paths['/api/another_api']?.get?.security).toBeUndefined(); + }); + }); + + describe('disabled', () => { + it('bundles root level security requirements', async () => { + const spec1Security = [{ SomeSecurityRequirement: [] }]; + const spec1SecuritySchemes = { + SomeSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + } as const; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: spec1Security, + components: { + securitySchemes: spec1SecuritySchemes, + }, + }); + const spec2Security: OpenAPIV3.SecurityRequirementObject[] = [ + { AnotherSecurityRequirement: [] }, + { AdditionalSecurityRequirement: [] }, + ]; + const spec2SecuritySchemes = { + AnotherSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + AdditionalSecurityRequirement: { + type: 'apiKey', + name: 'apiKey', + in: 'header', + }, + } as const; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: spec2Security, + components: { + securitySchemes: spec2SecuritySchemes, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.security).toEqual( + expect.arrayContaining([...spec1Security, ...spec2Security]) + ); + expect(bundledSpec.components?.securitySchemes).toMatchObject({ + ...spec1SecuritySchemes, + ...spec2SecuritySchemes, + }); + }); + + it('bundles operation level security requirements', async () => { + const spec1Security = [{ SomeSecurityRequirement: [] }]; + const spec1SecuritySchemes = { + SomeSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + } as const; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: spec1Security, + }, + }, + }, + components: { + securitySchemes: spec1SecuritySchemes, + }, + }); + const spec2Security: OpenAPIV3.SecurityRequirementObject[] = [ + { AnotherSecurityRequirement: [] }, + { AdditionalSecurityRequirement: [] }, + ]; + const spec2SecuritySchemes = { + AnotherSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + AdditionalSecurityRequirement: { + type: 'apiKey', + name: 'apiKey', + in: 'header', + }, + } as const; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: spec2Security, + }, + }, + }, + components: { + securitySchemes: spec2SecuritySchemes, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.security).toEqual(spec1Security); + expect(bundledSpec.paths['/api/another_api']?.get?.security).toEqual(spec2Security); + expect(bundledSpec.components?.securitySchemes).toMatchObject({ + ...spec1SecuritySchemes, + ...spec2SecuritySchemes, + }); + }); + }); +}); diff --git a/packages/kbn-openapi-bundler/tests/bundler/result_overrides/servers.test.ts b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/servers.test.ts new file mode 100644 index 0000000000000..da22e2dcfc74c --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/servers.test.ts @@ -0,0 +1,409 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createOASDocument } from '../../create_oas_document'; +import { bundleSpecs } from '../bundle_specs'; + +describe('OpenAPI Bundler - with `servers` overrides', () => { + describe('enabled', () => { + it('overrides root level `servers`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: [{ url: 'https://some-url' }], + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ], + }); + + const [bundledSpec] = Object.values( + await bundleSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + servers: [ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ], + }, + } + ) + ); + + expect(bundledSpec.servers).toEqual([ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ]); + }); + + it('drops path level `servers`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: [{ url: 'https://some-url' }], + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ], + }, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + servers: [ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ], + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.servers).toBeUndefined(); + expect(bundledSpec.paths['/api/another_api']?.servers).toBeUndefined(); + }); + + it('drops operation level `servers`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: [{ url: 'https://some-url' }], + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ], + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + servers: [ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ], + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.servers).toBeUndefined(); + expect(bundledSpec.paths['/api/another_api']?.get?.servers).toBeUndefined(); + }); + }); + + describe('disabled', () => { + it('bundles root level `servers`', async () => { + const spec1Servers = [{ url: 'https://some-url' }]; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: spec1Servers, + }); + const spec2Servers = [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ]; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: spec2Servers, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + const DEFAULT_ENTRY = { + url: 'http://{kibana_host}:{port}', + variables: { + kibana_host: { + default: 'localhost', + }, + port: { + default: '5601', + }, + }, + }; + + expect(bundledSpec.servers).toEqual([DEFAULT_ENTRY, ...spec1Servers, ...spec2Servers]); + }); + + it('bundles path level `servers`', async () => { + const spec1Servers = [{ url: 'https://some-url' }]; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: spec1Servers, + }, + }, + }); + const spec2Servers = [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ]; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: spec2Servers, + }, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.paths['/api/some_api']?.servers).toEqual(spec1Servers); + expect(bundledSpec.paths['/api/another_api']?.servers).toEqual(spec2Servers); + }); + + it('bundles operation level `servers`', async () => { + const spec1Servers = [{ url: 'https://some-url' }]; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: spec1Servers, + }, + }, + }, + }); + const spec2Servers = [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ]; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: spec2Servers, + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await bundleSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.servers).toEqual(spec1Servers); + expect(bundledSpec.paths['/api/another_api']?.get?.servers).toEqual(spec2Servers); + }); + }); +}); diff --git a/packages/kbn-openapi-bundler/tests/bundler/result_overrides/sort_tags.test.ts b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/sort_tags.test.ts new file mode 100644 index 0000000000000..0e4a8e77e576b --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/bundler/result_overrides/sort_tags.test.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 { bundleSpecs } from '../bundle_specs'; +import { createOASDocument } from '../../create_oas_document'; + +describe('OpenAPI Bundler - sort tags', () => { + it('sorts tags in the result bundle', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: {}, + }, + }, + }, + tags: [ + { name: 'Some tag name', description: 'Some description' }, + { name: '1 tag', description: 'Some description' }, + ], + }); + const spec2 = createOASDocument({ + paths: { + '/api/some_api': { + post: { + responses: {}, + }, + }, + }, + tags: [{ name: 'Another tag name', description: 'Another description' }], + }); + const spec3 = createOASDocument({ + paths: { + '/api/some_api': { + put: { + responses: {}, + }, + }, + }, + tags: [{ name: 'Spec3 tag name', description: 'Spec3 tag description' }], + }); + + const [bundledSpec] = Object.values( + await bundleSpecs({ + 1: spec1, + 2: spec2, + 3: spec3, + }) + ); + + expect(bundledSpec.tags).toEqual([ + { name: '1 tag', description: 'Some description' }, + { name: 'Another tag name', description: 'Another description' }, + { name: 'Some tag name', description: 'Some description' }, + { name: 'Spec3 tag name', description: 'Spec3 tag description' }, + ]); + }); +}); diff --git a/packages/kbn-openapi-bundler/tests/create_oas_document.ts b/packages/kbn-openapi-bundler/tests/create_oas_document.ts index a16cef4119f03..2ce877ebe3524 100644 --- a/packages/kbn-openapi-bundler/tests/create_oas_document.ts +++ b/packages/kbn-openapi-bundler/tests/create_oas_document.ts @@ -15,6 +15,7 @@ export function createOASDocument(overrides: { components?: OpenAPIV3.ComponentsObject; servers?: OpenAPIV3.ServerObject[]; security?: OpenAPIV3.SecurityRequirementObject[]; + tags?: OpenAPIV3.TagObject[]; }): OpenAPIV3.Document { const document: OpenAPIV3.Document = { openapi: overrides.openapi ?? '3.0.3', @@ -39,5 +40,9 @@ export function createOASDocument(overrides: { document.security = overrides.security; } + if (overrides.tags) { + document.tags = overrides.tags; + } + return document; } diff --git a/packages/kbn-openapi-bundler/tests/merger/result_overrides/add_tags.test.ts b/packages/kbn-openapi-bundler/tests/merger/result_overrides/add_tags.test.ts new file mode 100644 index 0000000000000..ad665f16228b4 --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/merger/result_overrides/add_tags.test.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 { createOASDocument } from '../../create_oas_document'; +import { mergeSpecs } from '../merge_specs'; + +describe('OpenAPI Bundler - assign a tag', () => { + it('adds tags when nothing is set', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }); + + const [mergedSpec] = Object.values( + await mergeSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + tags: [ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + ], + }, + } + ) + ); + + expect(mergedSpec.paths['/api/some_api']?.get?.tags).toEqual(['Some Tag', 'Another Tag']); + expect(mergedSpec.paths['/api/another_api']?.get?.tags).toEqual(['Some Tag', 'Another Tag']); + expect(mergedSpec.tags).toEqual([ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + ]); + }); + + it('adds tags to existing tags', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + tags: ['Local tag'], + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + tags: ['Global tag'], + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + tags: [{ name: 'Global tag', description: 'Global tag description' }], + }); + + const [mergedSpec] = Object.values( + await mergeSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + tags: [ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + ], + }, + } + ) + ); + + expect(mergedSpec.paths['/api/some_api']?.get?.tags).toEqual([ + 'Some Tag', + 'Another Tag', + 'Local tag', + ]); + expect(mergedSpec.paths['/api/another_api']?.get?.tags).toEqual([ + 'Some Tag', + 'Another Tag', + 'Global tag', + ]); + expect(mergedSpec.tags).toEqual([ + { + name: 'Some Tag', + description: 'Some tag description', + }, + { + name: 'Another Tag', + description: 'Another tag description', + }, + { name: 'Global tag', description: 'Global tag description' }, + ]); + }); +}); diff --git a/packages/kbn-openapi-bundler/tests/merger/result_overrides/security.test.ts b/packages/kbn-openapi-bundler/tests/merger/result_overrides/security.test.ts new file mode 100644 index 0000000000000..f570416d48d75 --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/merger/result_overrides/security.test.ts @@ -0,0 +1,415 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import chalk from 'chalk'; +import { createOASDocument } from '../../create_oas_document'; +import { mergeSpecs } from '../merge_specs'; + +// Disable naming convention check due to tests on spec title prefixes +// like Spec1_Something which violates that rule +/* eslint-disable @typescript-eslint/naming-convention */ + +describe('OpenAPI Merger - with security requirements overrides', () => { + describe('enabled', () => { + it('throws an error when security requirements are specified without components security schemes', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: {}, + }, + }, + }, + }); + + await expect( + mergeSpecs( + { + 1: spec1, + }, + { + prototypeDocument: { + security: [{ ShouldBeUsedSecurityRequirement: [] }], + }, + } + ) + ).rejects.toThrowError( + `Prototype document must contain ${chalk.bold( + 'components.securitySchemes' + )} when security requirements are specified` + ); + }); + + it('throws an error when components security schemes are specified without security requirements', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: {}, + }, + }, + }, + }); + + await expect( + mergeSpecs( + { + 1: spec1, + }, + { + prototypeDocument: { + components: { + securitySchemes: { + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }, + } + ) + ).rejects.toThrowError( + `Prototype document must have ${chalk.bold('security')} defined ${chalk.bold( + 'components.securitySchemes' + )} are specified` + ); + }); + + it('overrides root level `security`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: [{ SomeSecurityRequirement: [] }], + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }], + }); + + const [bundledSpec] = Object.values( + await mergeSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + security: [{ ShouldBeUsedSecurityRequirement: [] }], + components: { + securitySchemes: { + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }, + } + ) + ); + + expect(bundledSpec.security).toEqual([{ ShouldBeUsedSecurityRequirement: [] }]); + expect(bundledSpec.components?.securitySchemes).toEqual({ + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }); + }); + + it('drops operation level security requirements', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: [{ SomeSecurityRequirement: [] }], + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }], + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + security: [{ ShouldBeUsedSecurityRequirement: [] }], + components: { + securitySchemes: { + ShouldBeUsedSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.security).toBeUndefined(); + expect(bundledSpec.paths['/api/another_api']?.get?.security).toBeUndefined(); + }); + }); + + describe('disabled', () => { + it('bundles root level security requirements', async () => { + const spec1 = createOASDocument({ + info: { + title: 'Spec1', + }, + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: [{ SomeSecurityRequirement: [] }], + components: { + securitySchemes: { + SomeSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }); + const spec2 = createOASDocument({ + info: { + title: 'Spec2', + }, + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }], + components: { + securitySchemes: { + AnotherSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + AdditionalSecurityRequirement: { + type: 'apiKey', + name: 'apiKey', + in: 'header', + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.security).toEqual( + expect.arrayContaining([ + { Spec1_SomeSecurityRequirement: [] }, + { Spec2_AnotherSecurityRequirement: [] }, + { Spec2_AdditionalSecurityRequirement: [] }, + ]) + ); + expect(bundledSpec.components?.securitySchemes).toMatchObject({ + Spec1_SomeSecurityRequirement: expect.anything(), + Spec2_AnotherSecurityRequirement: expect.anything(), + Spec2_AdditionalSecurityRequirement: expect.anything(), + }); + }); + + it('bundles operation level security requirements', async () => { + const spec1 = createOASDocument({ + info: { + title: 'Spec1', + }, + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: [{ SomeSecurityRequirement: [] }], + }, + }, + }, + components: { + securitySchemes: { + SomeSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + }, + }, + }); + const spec2 = createOASDocument({ + info: { + title: 'Spec2', + }, + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }], + }, + }, + }, + components: { + securitySchemes: { + AnotherSecurityRequirement: { + type: 'http', + scheme: 'basic', + }, + AdditionalSecurityRequirement: { + type: 'apiKey', + name: 'apiKey', + in: 'header', + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.security).toEqual([ + { Spec1_SomeSecurityRequirement: [] }, + ]); + expect(bundledSpec.paths['/api/another_api']?.get?.security).toEqual([ + { Spec2_AnotherSecurityRequirement: [] }, + { Spec2_AdditionalSecurityRequirement: [] }, + ]); + expect(bundledSpec.components?.securitySchemes).toMatchObject({ + Spec1_SomeSecurityRequirement: expect.anything(), + Spec2_AnotherSecurityRequirement: expect.anything(), + Spec2_AdditionalSecurityRequirement: expect.anything(), + }); + }); + }); +}); diff --git a/packages/kbn-openapi-bundler/tests/merger/result_overrides/servers.test.ts b/packages/kbn-openapi-bundler/tests/merger/result_overrides/servers.test.ts new file mode 100644 index 0000000000000..0b2fe5a5ceb6c --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/merger/result_overrides/servers.test.ts @@ -0,0 +1,409 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createOASDocument } from '../../create_oas_document'; +import { mergeSpecs } from '../merge_specs'; + +describe('OpenAPI Merger - with `servers` overrides', () => { + describe('enabled', () => { + it('overrides root level `servers`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: [{ url: 'https://some-url' }], + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ], + }); + + const [bundledSpec] = Object.values( + await mergeSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + servers: [ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ], + }, + } + ) + ); + + expect(bundledSpec.servers).toEqual([ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ]); + }); + + it('drops path level `servers`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: [{ url: 'https://some-url' }], + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ], + }, + }, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + servers: [ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ], + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.servers).toBeUndefined(); + expect(bundledSpec.paths['/api/another_api']?.servers).toBeUndefined(); + }); + + it('drops operation level `servers`', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: [{ url: 'https://some-url' }], + }, + }, + }, + }); + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ], + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs( + { + 1: spec1, + 2: spec2, + }, + { + prototypeDocument: { + servers: [ + { url: 'https://should-be-used-url', description: 'Should be used description' }, + ], + }, + } + ) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.servers).toBeUndefined(); + expect(bundledSpec.paths['/api/another_api']?.get?.servers).toBeUndefined(); + }); + }); + + describe('disabled', () => { + it('bundles root level `servers`', async () => { + const spec1Servers = [{ url: 'https://some-url' }]; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: spec1Servers, + }); + const spec2Servers = [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ]; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + servers: spec2Servers, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + const DEFAULT_ENTRY = { + url: 'http://{kibana_host}:{port}', + variables: { + kibana_host: { + default: 'localhost', + }, + port: { + default: '5601', + }, + }, + }; + + expect(bundledSpec.servers).toEqual([DEFAULT_ENTRY, ...spec1Servers, ...spec2Servers]); + }); + + it('bundles path level `servers`', async () => { + const spec1Servers = [{ url: 'https://some-url' }]; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: spec1Servers, + }, + }, + }); + const spec2Servers = [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ]; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + }, + servers: spec2Servers, + }, + }, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.paths['/api/some_api']?.servers).toEqual(spec1Servers); + expect(bundledSpec.paths['/api/another_api']?.servers).toEqual(spec2Servers); + }); + + it('bundles operation level `servers`', async () => { + const spec1Servers = [{ url: 'https://some-url' }]; + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: spec1Servers, + }, + }, + }, + }); + const spec2Servers = [ + { url: 'https://another-url', description: 'some description' }, + { url: 'https://something-else-url', description: 'some description' }, + ]; + const spec2 = createOASDocument({ + paths: { + '/api/another_api': { + get: { + responses: { + '200': { + description: 'Successful response', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + }, + servers: spec2Servers, + }, + }, + }, + }); + + const [bundledSpec] = Object.values( + await mergeSpecs({ + 1: spec1, + 2: spec2, + }) + ); + + expect(bundledSpec.paths['/api/some_api']?.get?.servers).toEqual(spec1Servers); + expect(bundledSpec.paths['/api/another_api']?.get?.servers).toEqual(spec2Servers); + }); + }); +}); diff --git a/packages/kbn-openapi-bundler/tests/merger/result_overrides/sort_tags.test.ts b/packages/kbn-openapi-bundler/tests/merger/result_overrides/sort_tags.test.ts new file mode 100644 index 0000000000000..1fa25fe59ecf1 --- /dev/null +++ b/packages/kbn-openapi-bundler/tests/merger/result_overrides/sort_tags.test.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 { mergeSpecs } from '../merge_specs'; +import { createOASDocument } from '../../create_oas_document'; + +describe('OpenAPI Merger - sort tags', () => { + it('sorts tags in the result bundle', async () => { + const spec1 = createOASDocument({ + paths: { + '/api/some_api': { + get: { + responses: {}, + }, + }, + }, + tags: [ + { name: 'Some tag name', description: 'Some description' }, + { name: '1 tag', description: 'Some description' }, + ], + }); + const spec2 = createOASDocument({ + paths: { + '/api/some_api': { + post: { + responses: {}, + }, + }, + }, + tags: [{ name: 'Another tag name', description: 'Another description' }], + }); + const spec3 = createOASDocument({ + paths: { + '/api/some_api': { + put: { + responses: {}, + }, + }, + }, + tags: [{ name: 'Spec3 tag name', description: 'Spec3 tag description' }], + }); + + const [mergedSpec] = Object.values( + await mergeSpecs({ + 1: spec1, + 2: spec2, + 3: spec3, + }) + ); + + expect(mergedSpec.tags).toEqual([ + { name: '1 tag', description: 'Some description' }, + { name: 'Another tag name', description: 'Another description' }, + { name: 'Some tag name', description: 'Some description' }, + { name: 'Spec3 tag name', description: 'Spec3 tag description' }, + ]); + }); +}); diff --git a/packages/kbn-optimizer/kibana.jsonc b/packages/kbn-optimizer/kibana.jsonc index 1e912e055844e..0e8097384c533 100644 --- a/packages/kbn-optimizer/kibana.jsonc +++ b/packages/kbn-optimizer/kibana.jsonc @@ -1,5 +1,5 @@ { - "type": "shared-common", + "type": "shared-server", "id": "@kbn/optimizer", "devOnly": true, "owner": "@elastic/kibana-operations" diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index b044ba8e093f4..84055d662a65b 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -20,7 +20,7 @@ pageLoadAssetSize: cloudSecurityPosture: 19109 console: 46091 contentManagement: 16254 - controls: 55082 + controls: 60000 core: 435325 crossClusterReplication: 65408 customIntegrations: 22034 @@ -79,6 +79,7 @@ pageLoadAssetSize: imageEmbeddable: 12500 indexLifecycleManagement: 107090 indexManagement: 140608 + inference: 20403 infra: 184320 ingestPipelines: 58003 inputControlVis: 172675 diff --git a/packages/kbn-plugin-helpers/kibana.jsonc b/packages/kbn-plugin-helpers/kibana.jsonc index bee9b9486a644..bf41df21ee751 100644 --- a/packages/kbn-plugin-helpers/kibana.jsonc +++ b/packages/kbn-plugin-helpers/kibana.jsonc @@ -1,5 +1,5 @@ { - "type": "shared-common", + "type": "shared-server", "id": "@kbn/plugin-helpers", "devOnly": true, "owner": "@elastic/kibana-operations" diff --git a/packages/kbn-search-api-panels/components/select_client.tsx b/packages/kbn-search-api-panels/components/select_client.tsx index e8a596db21d31..548e9d14b58e8 100644 --- a/packages/kbn-search-api-panels/components/select_client.tsx +++ b/packages/kbn-search-api-panels/components/select_client.tsx @@ -97,26 +97,23 @@ export const SelectClientPanel: FC<PropsWithChildren<SelectClientPanelProps>> = return ( <OverviewPanel description={ - <FormattedMessage - id="searchApiPanels.welcomeBanner.selectClient.description" - defaultMessage="Elastic builds and maintains clients in several popular languages and our community has contributed many more. Select your favorite language client or dive into the {console} to get started." - values={{ - console: ( - <TryInConsoleButton - application={application} - consolePlugin={consolePlugin} - sharePlugin={sharePlugin} - content={i18n.translate( - 'searchApiPanels.welcomeBanner.selectClient.description.console.link', - { - defaultMessage: 'Console', - } - )} - link - /> - ), - }} - /> + <EuiFlexGroup direction="column" alignItems="flexStart" justifyContent="flexStart"> + <EuiFlexItem> + <FormattedMessage + id="searchApiPanels.welcomeBanner.selectClient.description" + defaultMessage="Elastic builds and maintains clients in several popular languages and our community has contributed many more. Select your favorite language client or dive into the console to get started." + /> + </EuiFlexItem> + <EuiFlexItem> + <TryInConsoleButton + application={application} + consolePlugin={consolePlugin} + sharePlugin={sharePlugin} + type="button" + showIcon={false} + /> + </EuiFlexItem> + </EuiFlexGroup> } leftPanelContent={isPanelLeft ? panelContent : undefined} rightPanelContent={!isPanelLeft ? panelContent : undefined} diff --git a/packages/kbn-search-connectors/types/native_connectors.ts b/packages/kbn-search-connectors/types/native_connectors.ts index 6459bbdf20ff9..4f95f0c9003fc 100644 --- a/packages/kbn-search-connectors/types/native_connectors.ts +++ b/packages/kbn-search-connectors/types/native_connectors.ts @@ -2017,12 +2017,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde }, use_document_level_security: { default_value: null, - depends_on: [ - { - field: 'data_source', - value: 'jira_cloud', - }, - ], + depends_on: [], display: DisplayType.TOGGLE, label: ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL, options: [], @@ -4662,6 +4657,45 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde }, sharepoint_server: { configuration: { + authentication: { + default_value: null, + depends_on: [], + display: DisplayType.DROPDOWN, + label: i18n.translate( + 'searchConnectors.nativeConnectors.sharepoint_server.configuration.authentication', + { + defaultMessage: 'Authentication', + } + ), + options: [ + { + label: i18n.translate( + 'searchConnectors.nativeConnectors.sharepoint_server.options.basicLabel', + { + defaultMessage: 'Basic', + } + ), + value: 'basic_auth', + }, + { + label: i18n.translate( + 'searchConnectors.nativeConnectors.sharepoint_server.options.ntlmLabel', + { + defaultMessage: 'NTLM', + } + ), + value: 'ntlm_auth', + }, + ], + order: 1, + required: true, + sensitive: false, + tooltip: null, + type: FieldType.STRING, + ui_restrictions: [], + validations: [], + value: 'basic_auth', + }, username: { default_value: null, depends_on: [], @@ -4673,7 +4707,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde } ), options: [], - order: 1, + order: 2, required: true, sensitive: false, tooltip: '', @@ -4693,7 +4727,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde } ), options: [], - order: 2, + order: 3, required: true, sensitive: true, tooltip: '', @@ -4713,7 +4747,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde } ), options: [], - order: 3, + order: 4, required: true, sensitive: false, tooltip: '', @@ -4733,7 +4767,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde } ), options: [], - order: 4, + order: 5, required: true, sensitive: false, tooltip: '', @@ -4748,7 +4782,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde display: DisplayType.TOGGLE, label: ENABLE_SSL_LABEL, options: [], - order: 5, + order: 6, required: true, sensitive: false, tooltip: null, @@ -4768,7 +4802,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde display: DisplayType.TEXTBOX, label: SSL_CERTIFICATE_LABEL, options: [], - order: 6, + order: 7, required: true, sensitive: false, tooltip: null, @@ -4783,7 +4817,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde display: DisplayType.NUMERIC, label: RETRIES_PER_REQUEST_LABEL, options: [], - order: 7, + order: 8, required: false, sensitive: false, tooltip: null, @@ -4798,7 +4832,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde display: DisplayType.TOGGLE, label: USE_TEXT_EXTRACTION_SERVICE_LABEL, options: [], - order: 8, + order: 9, required: true, sensitive: false, tooltip: USE_TEXT_EXTRACTION_SERVICE_TOOLTIP, @@ -4813,7 +4847,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde display: DisplayType.TOGGLE, label: ENABLE_DOCUMENT_LEVEL_SECURITY_LABEL, options: [], - order: 9, + order: 10, required: true, sensitive: false, tooltip: getEnableDocumentLevelSecurityTooltip( @@ -4842,7 +4876,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde } ), options: [], - order: 10, + order: 11, required: true, sensitive: false, tooltip: i18n.translate( @@ -4873,7 +4907,7 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record<string, NativeConnector | unde } ), options: [], - order: 11, + order: 12, required: true, sensitive: false, tooltip: i18n.translate( diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list/create_endpoint_list.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list/create_endpoint_list.schema.yaml index 12132ce5562b3..9703e31287ae6 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list/create_endpoint_list.schema.yaml +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list/create_endpoint_list.schema.yaml @@ -10,8 +10,6 @@ paths: operationId: CreateEndpointList summary: Creates an endpoint list description: Creates an endpoint list or does nothing if the list already exists - tags: - - Endpoint exceptions API responses: 200: description: Successful response diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list_item/create_endpoint_list_item.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list_item/create_endpoint_list_item.schema.yaml index 575e77ef84910..8807f6cf76f7c 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list_item/create_endpoint_list_item.schema.yaml +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/api/create_endpoint_list_item/create_endpoint_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: x-codegen-enabled: true operationId: CreateEndpointListItem summary: Creates an endpoint list item - tags: - - Endpoint exceptions API requestBody: description: Exception list item's properties required: true diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/api/delete_endpoint_list_item/delete_endpoint_list_item.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/api/delete_endpoint_list_item/delete_endpoint_list_item.schema.yaml index e60fe54abb261..cd0ddd9dd69cb 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/api/delete_endpoint_list_item/delete_endpoint_list_item.schema.yaml +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/api/delete_endpoint_list_item/delete_endpoint_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: x-codegen-enabled: true operationId: DeleteEndpointListItem summary: Deletes an endpoint list item - tags: - - Endpoint exceptions API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/api/find_endpoint_list_item/find_endpoint_list_item.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/api/find_endpoint_list_item/find_endpoint_list_item.schema.yaml index ca5d656c57c83..e727367ff9c7d 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/api/find_endpoint_list_item/find_endpoint_list_item.schema.yaml +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/api/find_endpoint_list_item/find_endpoint_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: x-codegen-enabled: true operationId: FindEndpointListItems summary: Finds endpoint list items - tags: - - Endpoint exceptions API parameters: - name: filter in: query diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/api/read_endpoint_list_item/read_endpoint_list_item.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/api/read_endpoint_list_item/read_endpoint_list_item.schema.yaml index c4025370763f4..81be7c67a00b3 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/api/read_endpoint_list_item/read_endpoint_list_item.schema.yaml +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/api/read_endpoint_list_item/read_endpoint_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: x-codegen-enabled: true operationId: ReadEndpointListItem summary: Reads an endpoint list item - tags: - - Endpoint exceptions API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/api/update_endpoint_list_item/update_endpoint_list_item.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/api/update_endpoint_list_item/update_endpoint_list_item.schema.yaml index 740ebb0107eb0..dcbf24be28763 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/api/update_endpoint_list_item/update_endpoint_list_item.schema.yaml +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/api/update_endpoint_list_item/update_endpoint_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: x-codegen-enabled: true operationId: UpdateEndpointListItem summary: Updates an endpoint list item - tags: - - Endpoint exceptions API requestBody: description: Exception list item's properties required: true diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/docs/openapi/ess/security_solution_endpoint_exceptions_api_2023_10_31.bundled.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/docs/openapi/ess/security_solution_endpoint_exceptions_api_2023_10_31.bundled.schema.yaml new file mode 100644 index 0000000000000..cbd98091ca378 --- /dev/null +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/docs/openapi/ess/security_solution_endpoint_exceptions_api_2023_10_31.bundled.schema.yaml @@ -0,0 +1,853 @@ +openapi: 3.0.3 +info: + description: Endpoint Exceptions API allow you to manage Endpoint lists. + title: Security Solution Endpoint Exceptions API (Elastic Cloud and self-hosted) + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/endpoint_list: + post: + description: Creates an endpoint list or does nothing if the list already exists + operationId: CreateEndpointList + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointList' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Creates an endpoint list + /api/endpoint_list/items: + delete: + operationId: DeleteEndpointListItem + parameters: + - description: Either `id` or `item_id` must be specified + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemId' + - description: Either `id` or `item_id` must be specified + in: query + name: item_id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemHumanId' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointListItem' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Deletes an endpoint list item + get: + operationId: ReadEndpointListItem + parameters: + - description: Either `id` or `item_id` must be specified + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemId' + - description: Either `id` or `item_id` must be specified + in: query + name: item_id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemHumanId' + responses: + '200': + content: + application/json: + schema: + items: + $ref: '#/components/schemas/EndpointListItem' + type: array + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Reads an endpoint list item + post: + operationId: CreateEndpointListItem + requestBody: + content: + application/json: + schema: + type: object + properties: + comments: + $ref: '#/components/schemas/ExceptionListItemCommentArray' + default: [] + description: + $ref: '#/components/schemas/ExceptionListItemDescription' + entries: + $ref: '#/components/schemas/ExceptionListItemEntryArray' + item_id: + $ref: '#/components/schemas/ExceptionListItemHumanId' + meta: + $ref: '#/components/schemas/ExceptionListItemMeta' + name: + $ref: '#/components/schemas/ExceptionListItemName' + os_types: + $ref: '#/components/schemas/ExceptionListItemOsTypeArray' + default: [] + tags: + $ref: '#/components/schemas/ExceptionListItemTags' + default: [] + type: + $ref: '#/components/schemas/ExceptionListItemType' + required: + - type + - name + - description + - entries + description: Exception list item's properties + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointListItem' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item already exists + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Creates an endpoint list item + put: + operationId: UpdateEndpointListItem + requestBody: + content: + application/json: + schema: + type: object + properties: + _version: + type: string + comments: + $ref: '#/components/schemas/ExceptionListItemCommentArray' + default: [] + description: + $ref: '#/components/schemas/ExceptionListItemDescription' + entries: + $ref: '#/components/schemas/ExceptionListItemEntryArray' + id: + $ref: '#/components/schemas/ExceptionListItemId' + description: Either `id` or `item_id` must be specified + item_id: + $ref: '#/components/schemas/ExceptionListItemHumanId' + description: Either `id` or `item_id` must be specified + meta: + $ref: '#/components/schemas/ExceptionListItemMeta' + name: + $ref: '#/components/schemas/ExceptionListItemName' + os_types: + $ref: '#/components/schemas/ExceptionListItemOsTypeArray' + default: [] + tags: + $ref: '#/components/schemas/ExceptionListItemTags' + type: + $ref: '#/components/schemas/ExceptionListItemType' + required: + - type + - name + - description + - entries + description: Exception list item's properties + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointListItem' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Updates an endpoint list item + /api/endpoint_list/items/_find: + get: + operationId: FindEndpointListItems + parameters: + - description: > + Filters the returned results according to the value of the specified + field, + + using the `<field name>:<field value>` syntax. + in: query + name: filter + required: false + schema: + $ref: '#/components/schemas/FindEndpointListItemsFilter' + - description: The page number to return + in: query + name: page + required: false + schema: + minimum: 0 + type: integer + - description: The number of exception list items to return per page + in: query + name: per_page + required: false + schema: + minimum: 0 + type: integer + - description: Determines which field is used to sort the results + in: query + name: sort_field + required: false + schema: + $ref: '#/components/schemas/NonEmptyString' + - description: 'Determines the sort order, which can be `desc` or `asc`' + in: query + name: sort_order + required: false + schema: + enum: + - desc + - asc + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + items: + $ref: '#/components/schemas/EndpointListItem' + type: array + page: + minimum: 0 + type: integer + per_page: + minimum: 0 + type: integer + pit: + type: string + total: + minimum: 0 + type: integer + required: + - data + - page + - per_page + - total + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Finds endpoint list items +components: + schemas: + EndpointList: + oneOf: + - $ref: '#/components/schemas/ExceptionList' + - additionalProperties: false + type: object + EndpointListItem: + $ref: '#/components/schemas/ExceptionListItem' + ExceptionList: + type: object + properties: + _version: + type: string + created_at: + format: date-time + type: string + created_by: + type: string + description: + $ref: '#/components/schemas/ExceptionListDescription' + id: + $ref: '#/components/schemas/ExceptionListId' + immutable: + type: boolean + list_id: + $ref: '#/components/schemas/ExceptionListHumanId' + meta: + $ref: '#/components/schemas/ExceptionListMeta' + name: + $ref: '#/components/schemas/ExceptionListName' + namespace_type: + $ref: '#/components/schemas/ExceptionNamespaceType' + os_types: + $ref: '#/components/schemas/ExceptionListOsTypeArray' + tags: + $ref: '#/components/schemas/ExceptionListTags' + tie_breaker_id: + type: string + type: + $ref: '#/components/schemas/ExceptionListType' + updated_at: + format: date-time + type: string + updated_by: + type: string + version: + $ref: '#/components/schemas/ExceptionListVersion' + required: + - id + - list_id + - type + - name + - description + - immutable + - namespace_type + - version + - tie_breaker_id + - created_at + - created_by + - updated_at + - updated_by + ExceptionListDescription: + type: string + ExceptionListHumanId: + $ref: '#/components/schemas/NonEmptyString' + description: 'Human readable string identifier, e.g. `trusted-linux-processes`' + ExceptionListId: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItem: + type: object + properties: + _version: + type: string + comments: + $ref: '#/components/schemas/ExceptionListItemCommentArray' + created_at: + format: date-time + type: string + created_by: + type: string + description: + $ref: '#/components/schemas/ExceptionListItemDescription' + entries: + $ref: '#/components/schemas/ExceptionListItemEntryArray' + expire_time: + format: date-time + type: string + id: + $ref: '#/components/schemas/ExceptionListItemId' + item_id: + $ref: '#/components/schemas/ExceptionListItemHumanId' + list_id: + $ref: '#/components/schemas/ExceptionListHumanId' + meta: + $ref: '#/components/schemas/ExceptionListItemMeta' + name: + $ref: '#/components/schemas/ExceptionListItemName' + namespace_type: + $ref: '#/components/schemas/ExceptionNamespaceType' + os_types: + $ref: '#/components/schemas/ExceptionListItemOsTypeArray' + tags: + $ref: '#/components/schemas/ExceptionListItemTags' + tie_breaker_id: + type: string + type: + $ref: '#/components/schemas/ExceptionListItemType' + updated_at: + format: date-time + type: string + updated_by: + type: string + required: + - id + - item_id + - list_id + - type + - name + - description + - entries + - namespace_type + - comments + - tie_breaker_id + - created_at + - created_by + - updated_at + - updated_by + ExceptionListItemComment: + type: object + properties: + comment: + $ref: '#/components/schemas/NonEmptyString' + created_at: + format: date-time + type: string + created_by: + $ref: '#/components/schemas/NonEmptyString' + id: + $ref: '#/components/schemas/NonEmptyString' + updated_at: + format: date-time + type: string + updated_by: + $ref: '#/components/schemas/NonEmptyString' + required: + - id + - comment + - created_at + - created_by + ExceptionListItemCommentArray: + items: + $ref: '#/components/schemas/ExceptionListItemComment' + type: array + ExceptionListItemDescription: + type: string + ExceptionListItemEntry: + anyOf: + - $ref: '#/components/schemas/ExceptionListItemEntryMatch' + - $ref: '#/components/schemas/ExceptionListItemEntryMatchAny' + - $ref: '#/components/schemas/ExceptionListItemEntryList' + - $ref: '#/components/schemas/ExceptionListItemEntryExists' + - $ref: '#/components/schemas/ExceptionListItemEntryNested' + - $ref: '#/components/schemas/ExceptionListItemEntryMatchWildcard' + discriminator: + propertyName: type + ExceptionListItemEntryArray: + items: + $ref: '#/components/schemas/ExceptionListItemEntry' + type: array + ExceptionListItemEntryExists: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - exists + type: string + required: + - type + - field + - operator + ExceptionListItemEntryList: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + list: + type: object + properties: + id: + $ref: '#/components/schemas/ListId' + type: + $ref: '#/components/schemas/ListType' + required: + - id + - type + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - list + type: string + required: + - type + - field + - list + - operator + ExceptionListItemEntryMatch: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - match + type: string + value: + $ref: '#/components/schemas/NonEmptyString' + required: + - type + - field + - value + - operator + ExceptionListItemEntryMatchAny: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - match_any + type: string + value: + items: + $ref: '#/components/schemas/NonEmptyString' + minItems: 1 + type: array + required: + - type + - field + - value + - operator + ExceptionListItemEntryMatchWildcard: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - wildcard + type: string + value: + $ref: '#/components/schemas/NonEmptyString' + required: + - type + - field + - value + - operator + ExceptionListItemEntryNested: + type: object + properties: + entries: + items: + $ref: '#/components/schemas/ExceptionListItemEntryNestedEntryItem' + minItems: 1 + type: array + field: + $ref: '#/components/schemas/NonEmptyString' + type: + enum: + - nested + type: string + required: + - type + - field + - entries + ExceptionListItemEntryNestedEntryItem: + oneOf: + - $ref: '#/components/schemas/ExceptionListItemEntryMatch' + - $ref: '#/components/schemas/ExceptionListItemEntryMatchAny' + - $ref: '#/components/schemas/ExceptionListItemEntryExists' + ExceptionListItemEntryOperator: + enum: + - excluded + - included + type: string + ExceptionListItemHumanId: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItemId: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItemMeta: + additionalProperties: true + type: object + ExceptionListItemName: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItemOsTypeArray: + items: + $ref: '#/components/schemas/ExceptionListOsType' + type: array + ExceptionListItemTags: + items: + $ref: '#/components/schemas/NonEmptyString' + type: array + ExceptionListItemType: + enum: + - simple + type: string + ExceptionListMeta: + additionalProperties: true + type: object + ExceptionListName: + type: string + ExceptionListOsType: + enum: + - linux + - macos + - windows + type: string + ExceptionListOsTypeArray: + items: + $ref: '#/components/schemas/ExceptionListOsType' + type: array + ExceptionListTags: + items: + type: string + type: array + ExceptionListType: + enum: + - detection + - rule_default + - endpoint + - endpoint_trusted_apps + - endpoint_events + - endpoint_host_isolation_exceptions + - endpoint_blocklists + type: string + ExceptionListVersion: + minimum: 1 + type: integer + ExceptionNamespaceType: + description: > + Determines whether the exception container is available in all Kibana + spaces or just the space + + in which it is created, where: + + + - `single`: Only available in the Kibana space in which it is created. + + - `agnostic`: Available in all Kibana spaces. + enum: + - agnostic + - single + type: string + FindEndpointListItemsFilter: + $ref: '#/components/schemas/NonEmptyString' + ListId: + $ref: '#/components/schemas/NonEmptyString' + ListType: + enum: + - binary + - boolean + - byte + - date + - date_nanos + - date_range + - double + - double_range + - float + - float_range + - geo_point + - geo_shape + - half_float + - integer + - integer_range + - ip + - ip_range + - keyword + - long + - long_range + - shape + - short + - text + type: string + NonEmptyString: + description: A string that is not empty and does not contain only whitespace + minLength: 1 + pattern: ^(?! *$).+$ + type: string + PlatformErrorResponse: + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: integer + required: + - statusCode + - error + - message + SiemErrorResponse: + type: object + properties: + message: + type: string + status_code: + type: integer + required: + - status_code + - message + securitySchemes: + BasicAuth: + scheme: basic + type: http +security: + - BasicAuth: [] diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/docs/openapi/serverless/security_solution_endpoint_exceptions_api_2023_10_31.bundled.schema.yaml b/packages/kbn-securitysolution-endpoint-exceptions-common/docs/openapi/serverless/security_solution_endpoint_exceptions_api_2023_10_31.bundled.schema.yaml new file mode 100644 index 0000000000000..e8a8966c18586 --- /dev/null +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/docs/openapi/serverless/security_solution_endpoint_exceptions_api_2023_10_31.bundled.schema.yaml @@ -0,0 +1,853 @@ +openapi: 3.0.3 +info: + description: Endpoint Exceptions API allow you to manage Endpoint lists. + title: Security Solution Endpoint Exceptions API (Elastic Cloud Serverless) + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/endpoint_list: + post: + description: Creates an endpoint list or does nothing if the list already exists + operationId: CreateEndpointList + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointList' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Creates an endpoint list + /api/endpoint_list/items: + delete: + operationId: DeleteEndpointListItem + parameters: + - description: Either `id` or `item_id` must be specified + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemId' + - description: Either `id` or `item_id` must be specified + in: query + name: item_id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemHumanId' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointListItem' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Deletes an endpoint list item + get: + operationId: ReadEndpointListItem + parameters: + - description: Either `id` or `item_id` must be specified + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemId' + - description: Either `id` or `item_id` must be specified + in: query + name: item_id + required: false + schema: + $ref: '#/components/schemas/ExceptionListItemHumanId' + responses: + '200': + content: + application/json: + schema: + items: + $ref: '#/components/schemas/EndpointListItem' + type: array + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Reads an endpoint list item + post: + operationId: CreateEndpointListItem + requestBody: + content: + application/json: + schema: + type: object + properties: + comments: + $ref: '#/components/schemas/ExceptionListItemCommentArray' + default: [] + description: + $ref: '#/components/schemas/ExceptionListItemDescription' + entries: + $ref: '#/components/schemas/ExceptionListItemEntryArray' + item_id: + $ref: '#/components/schemas/ExceptionListItemHumanId' + meta: + $ref: '#/components/schemas/ExceptionListItemMeta' + name: + $ref: '#/components/schemas/ExceptionListItemName' + os_types: + $ref: '#/components/schemas/ExceptionListItemOsTypeArray' + default: [] + tags: + $ref: '#/components/schemas/ExceptionListItemTags' + default: [] + type: + $ref: '#/components/schemas/ExceptionListItemType' + required: + - type + - name + - description + - entries + description: Exception list item's properties + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointListItem' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item already exists + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Creates an endpoint list item + put: + operationId: UpdateEndpointListItem + requestBody: + content: + application/json: + schema: + type: object + properties: + _version: + type: string + comments: + $ref: '#/components/schemas/ExceptionListItemCommentArray' + default: [] + description: + $ref: '#/components/schemas/ExceptionListItemDescription' + entries: + $ref: '#/components/schemas/ExceptionListItemEntryArray' + id: + $ref: '#/components/schemas/ExceptionListItemId' + description: Either `id` or `item_id` must be specified + item_id: + $ref: '#/components/schemas/ExceptionListItemHumanId' + description: Either `id` or `item_id` must be specified + meta: + $ref: '#/components/schemas/ExceptionListItemMeta' + name: + $ref: '#/components/schemas/ExceptionListItemName' + os_types: + $ref: '#/components/schemas/ExceptionListItemOsTypeArray' + default: [] + tags: + $ref: '#/components/schemas/ExceptionListItemTags' + type: + $ref: '#/components/schemas/ExceptionListItemType' + required: + - type + - name + - description + - entries + description: Exception list item's properties + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/EndpointListItem' + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list item not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Updates an endpoint list item + /api/endpoint_list/items/_find: + get: + operationId: FindEndpointListItems + parameters: + - description: > + Filters the returned results according to the value of the specified + field, + + using the `<field name>:<field value>` syntax. + in: query + name: filter + required: false + schema: + $ref: '#/components/schemas/FindEndpointListItemsFilter' + - description: The page number to return + in: query + name: page + required: false + schema: + minimum: 0 + type: integer + - description: The number of exception list items to return per page + in: query + name: per_page + required: false + schema: + minimum: 0 + type: integer + - description: Determines which field is used to sort the results + in: query + name: sort_field + required: false + schema: + $ref: '#/components/schemas/NonEmptyString' + - description: 'Determines the sort order, which can be `desc` or `asc`' + in: query + name: sort_order + required: false + schema: + enum: + - desc + - asc + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + items: + $ref: '#/components/schemas/EndpointListItem' + type: array + page: + minimum: 0 + type: integer + per_page: + minimum: 0 + type: integer + pit: + type: string + total: + minimum: 0 + type: integer + required: + - data + - page + - per_page + - total + description: Successful response + '400': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/PlatformErrorResponse' + - $ref: '#/components/schemas/SiemErrorResponse' + description: Invalid input data + '401': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Unsuccessful authentication + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/PlatformErrorResponse' + description: Insufficient privileges + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Endpoint list not found + '500': + content: + application/json: + schema: + $ref: '#/components/schemas/SiemErrorResponse' + description: Internal server error + summary: Finds endpoint list items +components: + schemas: + EndpointList: + oneOf: + - $ref: '#/components/schemas/ExceptionList' + - additionalProperties: false + type: object + EndpointListItem: + $ref: '#/components/schemas/ExceptionListItem' + ExceptionList: + type: object + properties: + _version: + type: string + created_at: + format: date-time + type: string + created_by: + type: string + description: + $ref: '#/components/schemas/ExceptionListDescription' + id: + $ref: '#/components/schemas/ExceptionListId' + immutable: + type: boolean + list_id: + $ref: '#/components/schemas/ExceptionListHumanId' + meta: + $ref: '#/components/schemas/ExceptionListMeta' + name: + $ref: '#/components/schemas/ExceptionListName' + namespace_type: + $ref: '#/components/schemas/ExceptionNamespaceType' + os_types: + $ref: '#/components/schemas/ExceptionListOsTypeArray' + tags: + $ref: '#/components/schemas/ExceptionListTags' + tie_breaker_id: + type: string + type: + $ref: '#/components/schemas/ExceptionListType' + updated_at: + format: date-time + type: string + updated_by: + type: string + version: + $ref: '#/components/schemas/ExceptionListVersion' + required: + - id + - list_id + - type + - name + - description + - immutable + - namespace_type + - version + - tie_breaker_id + - created_at + - created_by + - updated_at + - updated_by + ExceptionListDescription: + type: string + ExceptionListHumanId: + $ref: '#/components/schemas/NonEmptyString' + description: 'Human readable string identifier, e.g. `trusted-linux-processes`' + ExceptionListId: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItem: + type: object + properties: + _version: + type: string + comments: + $ref: '#/components/schemas/ExceptionListItemCommentArray' + created_at: + format: date-time + type: string + created_by: + type: string + description: + $ref: '#/components/schemas/ExceptionListItemDescription' + entries: + $ref: '#/components/schemas/ExceptionListItemEntryArray' + expire_time: + format: date-time + type: string + id: + $ref: '#/components/schemas/ExceptionListItemId' + item_id: + $ref: '#/components/schemas/ExceptionListItemHumanId' + list_id: + $ref: '#/components/schemas/ExceptionListHumanId' + meta: + $ref: '#/components/schemas/ExceptionListItemMeta' + name: + $ref: '#/components/schemas/ExceptionListItemName' + namespace_type: + $ref: '#/components/schemas/ExceptionNamespaceType' + os_types: + $ref: '#/components/schemas/ExceptionListItemOsTypeArray' + tags: + $ref: '#/components/schemas/ExceptionListItemTags' + tie_breaker_id: + type: string + type: + $ref: '#/components/schemas/ExceptionListItemType' + updated_at: + format: date-time + type: string + updated_by: + type: string + required: + - id + - item_id + - list_id + - type + - name + - description + - entries + - namespace_type + - comments + - tie_breaker_id + - created_at + - created_by + - updated_at + - updated_by + ExceptionListItemComment: + type: object + properties: + comment: + $ref: '#/components/schemas/NonEmptyString' + created_at: + format: date-time + type: string + created_by: + $ref: '#/components/schemas/NonEmptyString' + id: + $ref: '#/components/schemas/NonEmptyString' + updated_at: + format: date-time + type: string + updated_by: + $ref: '#/components/schemas/NonEmptyString' + required: + - id + - comment + - created_at + - created_by + ExceptionListItemCommentArray: + items: + $ref: '#/components/schemas/ExceptionListItemComment' + type: array + ExceptionListItemDescription: + type: string + ExceptionListItemEntry: + anyOf: + - $ref: '#/components/schemas/ExceptionListItemEntryMatch' + - $ref: '#/components/schemas/ExceptionListItemEntryMatchAny' + - $ref: '#/components/schemas/ExceptionListItemEntryList' + - $ref: '#/components/schemas/ExceptionListItemEntryExists' + - $ref: '#/components/schemas/ExceptionListItemEntryNested' + - $ref: '#/components/schemas/ExceptionListItemEntryMatchWildcard' + discriminator: + propertyName: type + ExceptionListItemEntryArray: + items: + $ref: '#/components/schemas/ExceptionListItemEntry' + type: array + ExceptionListItemEntryExists: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - exists + type: string + required: + - type + - field + - operator + ExceptionListItemEntryList: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + list: + type: object + properties: + id: + $ref: '#/components/schemas/ListId' + type: + $ref: '#/components/schemas/ListType' + required: + - id + - type + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - list + type: string + required: + - type + - field + - list + - operator + ExceptionListItemEntryMatch: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - match + type: string + value: + $ref: '#/components/schemas/NonEmptyString' + required: + - type + - field + - value + - operator + ExceptionListItemEntryMatchAny: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - match_any + type: string + value: + items: + $ref: '#/components/schemas/NonEmptyString' + minItems: 1 + type: array + required: + - type + - field + - value + - operator + ExceptionListItemEntryMatchWildcard: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + operator: + $ref: '#/components/schemas/ExceptionListItemEntryOperator' + type: + enum: + - wildcard + type: string + value: + $ref: '#/components/schemas/NonEmptyString' + required: + - type + - field + - value + - operator + ExceptionListItemEntryNested: + type: object + properties: + entries: + items: + $ref: '#/components/schemas/ExceptionListItemEntryNestedEntryItem' + minItems: 1 + type: array + field: + $ref: '#/components/schemas/NonEmptyString' + type: + enum: + - nested + type: string + required: + - type + - field + - entries + ExceptionListItemEntryNestedEntryItem: + oneOf: + - $ref: '#/components/schemas/ExceptionListItemEntryMatch' + - $ref: '#/components/schemas/ExceptionListItemEntryMatchAny' + - $ref: '#/components/schemas/ExceptionListItemEntryExists' + ExceptionListItemEntryOperator: + enum: + - excluded + - included + type: string + ExceptionListItemHumanId: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItemId: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItemMeta: + additionalProperties: true + type: object + ExceptionListItemName: + $ref: '#/components/schemas/NonEmptyString' + ExceptionListItemOsTypeArray: + items: + $ref: '#/components/schemas/ExceptionListOsType' + type: array + ExceptionListItemTags: + items: + $ref: '#/components/schemas/NonEmptyString' + type: array + ExceptionListItemType: + enum: + - simple + type: string + ExceptionListMeta: + additionalProperties: true + type: object + ExceptionListName: + type: string + ExceptionListOsType: + enum: + - linux + - macos + - windows + type: string + ExceptionListOsTypeArray: + items: + $ref: '#/components/schemas/ExceptionListOsType' + type: array + ExceptionListTags: + items: + type: string + type: array + ExceptionListType: + enum: + - detection + - rule_default + - endpoint + - endpoint_trusted_apps + - endpoint_events + - endpoint_host_isolation_exceptions + - endpoint_blocklists + type: string + ExceptionListVersion: + minimum: 1 + type: integer + ExceptionNamespaceType: + description: > + Determines whether the exception container is available in all Kibana + spaces or just the space + + in which it is created, where: + + + - `single`: Only available in the Kibana space in which it is created. + + - `agnostic`: Available in all Kibana spaces. + enum: + - agnostic + - single + type: string + FindEndpointListItemsFilter: + $ref: '#/components/schemas/NonEmptyString' + ListId: + $ref: '#/components/schemas/NonEmptyString' + ListType: + enum: + - binary + - boolean + - byte + - date + - date_nanos + - date_range + - double + - double_range + - float + - float_range + - geo_point + - geo_shape + - half_float + - integer + - integer_range + - ip + - ip_range + - keyword + - long + - long_range + - shape + - short + - text + type: string + NonEmptyString: + description: A string that is not empty and does not contain only whitespace + minLength: 1 + pattern: ^(?! *$).+$ + type: string + PlatformErrorResponse: + type: object + properties: + error: + type: string + message: + type: string + statusCode: + type: integer + required: + - statusCode + - error + - message + SiemErrorResponse: + type: object + properties: + message: + type: string + status_code: + type: integer + required: + - status_code + - message + securitySchemes: + BasicAuth: + scheme: basic + type: http +security: + - BasicAuth: [] diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/package.json b/packages/kbn-securitysolution-endpoint-exceptions-common/package.json index e07ac8c130c36..e3c9567435b0f 100644 --- a/packages/kbn-securitysolution-endpoint-exceptions-common/package.json +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/package.json @@ -5,6 +5,7 @@ "private": true, "version": "1.0.0", "scripts": { - "openapi:generate": "node scripts/openapi_generate" + "openapi:generate": "node scripts/openapi_generate", + "openapi:bundle": "node scripts/openapi_bundle" } } diff --git a/packages/kbn-securitysolution-endpoint-exceptions-common/scripts/openapi_bundle.js b/packages/kbn-securitysolution-endpoint-exceptions-common/scripts/openapi_bundle.js new file mode 100644 index 0000000000000..2e35c5fc9cb90 --- /dev/null +++ b/packages/kbn-securitysolution-endpoint-exceptions-common/scripts/openapi_bundle.js @@ -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. + */ + +require('../../../src/setup_node_env'); +const { join, resolve } = require('path'); +const { bundle } = require('@kbn/openapi-bundler'); + +const ROOT = resolve(__dirname, '..'); + +(async () => { + await bundle({ + sourceGlob: join(ROOT, 'api/**/*.schema.yaml'), + outputFilePath: join( + ROOT, + 'docs/openapi/serverless/security_solution_endpoint_exceptions_api_{version}.bundled.schema.yaml' + ), + options: { + includeLabels: ['serverless'], + prototypeDocument: { + info: { + title: 'Security Solution Endpoint Exceptions API (Elastic Cloud Serverless)', + description: 'Endpoint Exceptions API allow you to manage Endpoint lists.', + }, + }, + }, + }); + + await bundle({ + sourceGlob: join(ROOT, 'api/**/*.schema.yaml'), + outputFilePath: join( + ROOT, + 'docs/openapi/ess/security_solution_endpoint_exceptions_api_{version}.bundled.schema.yaml' + ), + options: { + includeLabels: ['ess'], + prototypeDocument: { + info: { + title: 'Security Solution Endpoint Exceptions API (Elastic Cloud and self-hosted)', + description: 'Endpoint Exceptions API allow you to manage Endpoint lists.', + }, + }, + }, + }); +})(); diff --git a/packages/kbn-securitysolution-exceptions-common/api/create_exception_list/create_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/create_exception_list/create_exception_list.schema.yaml index 823ead2991330..463009b233af1 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/create_exception_list/create_exception_list.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/create_exception_list/create_exception_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: CreateExceptionList x-codegen-enabled: true summary: Creates an exception list - tags: - - Exceptions API requestBody: description: Exception list's properties required: true diff --git a/packages/kbn-securitysolution-exceptions-common/api/create_exception_list_item/create_exception_list_item.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/create_exception_list_item/create_exception_list_item.schema.yaml index 6ee00569336e0..f7eb416f953a6 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/create_exception_list_item/create_exception_list_item.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/create_exception_list_item/create_exception_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: CreateExceptionListItem x-codegen-enabled: true summary: Creates an exception list item - tags: - - Exceptions API requestBody: description: Exception list item's properties required: true diff --git a/packages/kbn-securitysolution-exceptions-common/api/create_rule_exceptions/create_rule_exceptions.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/create_rule_exceptions/create_rule_exceptions.schema.yaml index b7cad73c9bb22..0928d9e7f4e21 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/create_rule_exceptions/create_rule_exceptions.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/create_rule_exceptions/create_rule_exceptions.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: CreateRuleExceptionListItems x-codegen-enabled: true summary: Creates rule exception list items - tags: - - Exceptions API parameters: - name: id in: path diff --git a/packages/kbn-securitysolution-exceptions-common/api/create_shared_exceptions_list/create_shared_exceptions_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/create_shared_exceptions_list/create_shared_exceptions_list.schema.yaml index 0a072e855f0d2..e76ec4c50c5c8 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/create_shared_exceptions_list/create_shared_exceptions_list.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/create_shared_exceptions_list/create_shared_exceptions_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: CreateSharedExceptionList x-codegen-enabled: true summary: Creates a shared exception list - tags: - - Exceptions API requestBody: required: true content: diff --git a/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list/delete_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list/delete_exception_list.schema.yaml index f8522ddf2de68..ccc0749dd206e 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list/delete_exception_list.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list/delete_exception_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: DeleteExceptionList x-codegen-enabled: true summary: Deletes an exception list - tags: - - Exceptions API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list_item/delete_exception_list_item.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list_item/delete_exception_list_item.schema.yaml index a34339e4f4c31..e9d7fa0687044 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list_item/delete_exception_list_item.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/delete_exception_list_item/delete_exception_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: DeleteExceptionListItem x-codegen-enabled: true summary: Deletes an exception list item - tags: - - Exceptions API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-exceptions-common/api/duplicate_exception_list/duplicate_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/duplicate_exception_list/duplicate_exception_list.schema.yaml index 50d5c5e4d5c2d..f041fcb1d1062 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/duplicate_exception_list/duplicate_exception_list.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/duplicate_exception_list/duplicate_exception_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: DuplicateExceptionList x-codegen-enabled: true summary: Duplicates an exception list - tags: - - Exceptions API parameters: - name: list_id in: query diff --git a/packages/kbn-securitysolution-exceptions-common/api/export_exception_list/export_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/export_exception_list/export_exception_list.schema.yaml index 85482d6339f70..41637963d9923 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/export_exception_list/export_exception_list.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/export_exception_list/export_exception_list.schema.yaml @@ -10,8 +10,6 @@ paths: x-codegen-enabled: true summary: Exports an exception list description: Exports an exception list and its associated items to an .ndjson file - tags: - - Exceptions API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-exceptions-common/api/find_exception_list/find_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/find_exception_list/find_exception_list.schema.yaml deleted file mode 100644 index dae00e044102f..0000000000000 --- a/packages/kbn-securitysolution-exceptions-common/api/find_exception_list/find_exception_list.schema.yaml +++ /dev/null @@ -1,121 +0,0 @@ -openapi: 3.0.0 -info: - title: Find exception lists API endpoint - version: '2023-10-31' -paths: - /api/exception_lists/_find: - get: - x-labels: [serverless, ess] - operationId: FindExceptionLists - x-codegen-enabled: true - summary: Finds exception lists - tags: - - Exceptions API - parameters: - - name: filter - in: query - required: false - description: | - Filters the returned results according to the value of the specified field. - - Uses the `so type.field name:field` value syntax, where `so type` can be: - - - `exception-list`: Specify a space-aware exception list. - - `exception-list-agnostic`: Specify an exception list that is shared across spaces. - schema: - $ref: '#/components/schemas/FindExceptionListsFilter' - - name: namespace_type - in: query - required: false - description: | - Determines whether the returned containers are Kibana associated with a Kibana space - or available in all spaces (`agnostic` or `single`) - schema: - type: array - items: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionNamespaceType' - default: [single] - - name: page - in: query - required: false - description: The page number to return - schema: - type: integer - minimum: 1 - - name: per_page - in: query - required: false - description: The number of exception lists to return per page - schema: - type: integer - minimum: 1 - - name: sort_field - in: query - required: false - description: Determines which field is used to sort the results - schema: - type: string - - name: sort_order - in: query - required: false - description: Determines the sort order, which can be `desc` or `asc` - schema: - type: string - enum: [desc, asc] - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionList' - page: - type: integer - minimum: 1 - per_page: - type: integer - minimum: 1 - total: - type: integer - minimum: 0 - required: - - data - - page - - per_page - - total - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - -components: - schemas: - FindExceptionListsFilter: - type: string diff --git a/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_item/find_exception_list_item.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_item/find_exception_list_item.schema.yaml deleted file mode 100644 index 2fae12499d513..0000000000000 --- a/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_item/find_exception_list_item.schema.yaml +++ /dev/null @@ -1,141 +0,0 @@ -openapi: 3.0.0 -info: - title: Find exception list items API endpoint - version: '2023-10-31' -paths: - /api/exception_lists/items/_find: - get: - x-labels: [serverless, ess] - operationId: FindExceptionListItems - x-codegen-enabled: true - summary: Finds exception list items - tags: - - Exceptions API - parameters: - - name: list_id - in: query - required: true - description: List's id - schema: - type: array - items: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListHumanId' - - name: filter - in: query - required: false - description: | - Filters the returned results according to the value of the specified field, - using the `<field name>:<field value>` syntax. - schema: - type: array - items: - $ref: '#/components/schemas/FindExceptionListItemsFilter' - default: [] - - name: namespace_type - in: query - required: false - description: | - Determines whether the returned containers are Kibana associated with a Kibana space - or available in all spaces (`agnostic` or `single`) - schema: - type: array - items: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionNamespaceType' - default: [single] - - name: search - in: query - required: false - schema: - type: string - - name: page - in: query - required: false - description: The page number to return - schema: - type: integer - minimum: 0 - - name: per_page - in: query - required: false - description: The number of exception list items to return per page - schema: - type: integer - minimum: 0 - - name: sort_field - in: query - required: false - description: Determines which field is used to sort the results - schema: - $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' - - name: sort_order - in: query - required: false - description: Determines the sort order, which can be `desc` or `asc` - schema: - type: string - enum: [desc, asc] - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListItem' - page: - type: integer - minimum: 1 - per_page: - type: integer - minimum: 1 - total: - type: integer - minimum: 0 - pit: - type: string - required: - - data - - page - - per_page - - total - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 404: - description: Exception list not found response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - -components: - schemas: - FindExceptionListItemsFilter: - $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' diff --git a/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_item/find_exception_list_item.gen.ts b/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_items/find_exception_list_items.gen.ts similarity index 100% rename from packages/kbn-securitysolution-exceptions-common/api/find_exception_list_item/find_exception_list_item.gen.ts rename to packages/kbn-securitysolution-exceptions-common/api/find_exception_list_items/find_exception_list_items.gen.ts diff --git a/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_items/find_exception_list_items.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_items/find_exception_list_items.schema.yaml new file mode 100644 index 0000000000000..f2b3aafdee107 --- /dev/null +++ b/packages/kbn-securitysolution-exceptions-common/api/find_exception_list_items/find_exception_list_items.schema.yaml @@ -0,0 +1,139 @@ +openapi: 3.0.0 +info: + title: Find exception list items API endpoint + version: '2023-10-31' +paths: + /api/exception_lists/items/_find: + get: + x-labels: [serverless, ess] + operationId: FindExceptionListItems + x-codegen-enabled: true + summary: Finds exception list items + parameters: + - name: list_id + in: query + required: true + description: List's id + schema: + type: array + items: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListHumanId' + - name: filter + in: query + required: false + description: | + Filters the returned results according to the value of the specified field, + using the `<field name>:<field value>` syntax. + schema: + type: array + items: + $ref: '#/components/schemas/FindExceptionListItemsFilter' + default: [] + - name: namespace_type + in: query + required: false + description: | + Determines whether the returned containers are Kibana associated with a Kibana space + or available in all spaces (`agnostic` or `single`) + schema: + type: array + items: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionNamespaceType' + default: [single] + - name: search + in: query + required: false + schema: + type: string + - name: page + in: query + required: false + description: The page number to return + schema: + type: integer + minimum: 0 + - name: per_page + in: query + required: false + description: The number of exception list items to return per page + schema: + type: integer + minimum: 0 + - name: sort_field + in: query + required: false + description: Determines which field is used to sort the results + schema: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + - name: sort_order + in: query + required: false + description: Determines the sort order, which can be `desc` or `asc` + schema: + type: string + enum: [desc, asc] + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListItem' + page: + type: integer + minimum: 1 + per_page: + type: integer + minimum: 1 + total: + type: integer + minimum: 0 + pit: + type: string + required: + - data + - page + - per_page + - total + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: Exception list not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + FindExceptionListItemsFilter: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' diff --git a/packages/kbn-securitysolution-exceptions-common/api/find_exception_list/find_exception_list.gen.ts b/packages/kbn-securitysolution-exceptions-common/api/find_exception_lists/find_exception_lists.gen.ts similarity index 100% rename from packages/kbn-securitysolution-exceptions-common/api/find_exception_list/find_exception_list.gen.ts rename to packages/kbn-securitysolution-exceptions-common/api/find_exception_lists/find_exception_lists.gen.ts diff --git a/packages/kbn-securitysolution-exceptions-common/api/find_exception_lists/find_exception_lists.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/find_exception_lists/find_exception_lists.schema.yaml new file mode 100644 index 0000000000000..a1df1d12a27ea --- /dev/null +++ b/packages/kbn-securitysolution-exceptions-common/api/find_exception_lists/find_exception_lists.schema.yaml @@ -0,0 +1,119 @@ +openapi: 3.0.0 +info: + title: Find exception lists API endpoint + version: '2023-10-31' +paths: + /api/exception_lists/_find: + get: + x-labels: [serverless, ess] + operationId: FindExceptionLists + x-codegen-enabled: true + summary: Finds exception lists + parameters: + - name: filter + in: query + required: false + description: | + Filters the returned results according to the value of the specified field. + + Uses the `so type.field name:field` value syntax, where `so type` can be: + + - `exception-list`: Specify a space-aware exception list. + - `exception-list-agnostic`: Specify an exception list that is shared across spaces. + schema: + $ref: '#/components/schemas/FindExceptionListsFilter' + - name: namespace_type + in: query + required: false + description: | + Determines whether the returned containers are Kibana associated with a Kibana space + or available in all spaces (`agnostic` or `single`) + schema: + type: array + items: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionNamespaceType' + default: [single] + - name: page + in: query + required: false + description: The page number to return + schema: + type: integer + minimum: 1 + - name: per_page + in: query + required: false + description: The number of exception lists to return per page + schema: + type: integer + minimum: 1 + - name: sort_field + in: query + required: false + description: Determines which field is used to sort the results + schema: + type: string + - name: sort_order + in: query + required: false + description: Determines the sort order, which can be `desc` or `asc` + schema: + type: string + enum: [desc, asc] + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionList' + page: + type: integer + minimum: 1 + per_page: + type: integer + minimum: 1 + total: + type: integer + minimum: 0 + required: + - data + - page + - per_page + - total + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + FindExceptionListsFilter: + type: string diff --git a/packages/kbn-securitysolution-exceptions-common/api/import_exceptions/import_exceptions.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/import_exceptions/import_exceptions.schema.yaml index baa4a3e6b3a66..dc67d1386475f 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/import_exceptions/import_exceptions.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/import_exceptions/import_exceptions.schema.yaml @@ -10,8 +10,6 @@ paths: x-codegen-enabled: true summary: Imports an exception list description: Imports an exception list and associated items - tags: - - Exceptions API requestBody: required: true content: diff --git a/packages/kbn-securitysolution-exceptions-common/api/index.ts b/packages/kbn-securitysolution-exceptions-common/api/index.ts index b9e866aeb6a12..bd6f5eea48928 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/index.ts +++ b/packages/kbn-securitysolution-exceptions-common/api/index.ts @@ -16,11 +16,11 @@ export * from './delete_exception_list_item/delete_exception_list_item.gen'; export * from './delete_exception_list/delete_exception_list.gen'; export * from './duplicate_exception_list/duplicate_exception_list.gen'; export * from './export_exception_list/export_exception_list.gen'; -export * from './find_exception_list_item/find_exception_list_item.gen'; -export * from './find_exception_list/find_exception_list.gen'; +export * from './find_exception_list_items/find_exception_list_items.gen'; +export * from './find_exception_lists/find_exception_lists.gen'; export * from './import_exceptions/import_exceptions.gen'; export * from './read_exception_list_item/read_exception_list_item.gen'; export * from './read_exception_list/read_exception_list.gen'; -export * from './summary_exception_list/summary_exception_list.gen'; +export * from './read_exception_list_summary/read_exception_list_summary.gen'; export * from './update_exception_list_item/update_exception_list_item.gen'; export * from './update_exception_list/update_exception_list.gen'; diff --git a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.gen.ts b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.gen.ts index 3fdb7c7473f08..1cd161cbcbc20 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.gen.ts +++ b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.gen.ts @@ -11,7 +11,7 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Get exception list API endpoint + * title: Read exception list API endpoint * version: 2023-10-31 */ @@ -24,8 +24,8 @@ import { ExceptionList, } from '../model/exception_list_common.gen'; -export type GetExceptionListRequestQuery = z.infer<typeof GetExceptionListRequestQuery>; -export const GetExceptionListRequestQuery = z.object({ +export type ReadExceptionListRequestQuery = z.infer<typeof ReadExceptionListRequestQuery>; +export const ReadExceptionListRequestQuery = z.object({ /** * Either `id` or `list_id` must be specified */ @@ -36,7 +36,7 @@ export const GetExceptionListRequestQuery = z.object({ list_id: ExceptionListHumanId.optional(), namespace_type: ExceptionNamespaceType.optional().default('single'), }); -export type GetExceptionListRequestQueryInput = z.input<typeof GetExceptionListRequestQuery>; +export type ReadExceptionListRequestQueryInput = z.input<typeof ReadExceptionListRequestQuery>; -export type GetExceptionListResponse = z.infer<typeof GetExceptionListResponse>; -export const GetExceptionListResponse = ExceptionList; +export type ReadExceptionListResponse = z.infer<typeof ReadExceptionListResponse>; +export const ReadExceptionListResponse = ExceptionList; diff --git a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.schema.yaml index f9dcf6ea51177..69f5b4a9a8aa2 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list/read_exception_list.schema.yaml @@ -1,16 +1,14 @@ openapi: 3.0.0 info: - title: Get exception list API endpoint + title: Read exception list API endpoint version: '2023-10-31' paths: /api/exception_lists: get: x-labels: [serverless, ess] - operationId: GetExceptionList + operationId: ReadExceptionList x-codegen-enabled: true summary: Retrieves an exception list using its `id` or `list_id` field - tags: - - Exceptions API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.gen.ts b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.gen.ts index 5acf491245ce2..5c8d5a27c38d8 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.gen.ts +++ b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.gen.ts @@ -11,7 +11,7 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Get exception list item API endpoint + * title: Read exception list item API endpoint * version: 2023-10-31 */ @@ -24,8 +24,8 @@ import { ExceptionListItem, } from '../model/exception_list_common.gen'; -export type GetExceptionListItemRequestQuery = z.infer<typeof GetExceptionListItemRequestQuery>; -export const GetExceptionListItemRequestQuery = z.object({ +export type ReadExceptionListItemRequestQuery = z.infer<typeof ReadExceptionListItemRequestQuery>; +export const ReadExceptionListItemRequestQuery = z.object({ /** * Either `id` or `item_id` must be specified */ @@ -36,9 +36,9 @@ export const GetExceptionListItemRequestQuery = z.object({ item_id: ExceptionListItemHumanId.optional(), namespace_type: ExceptionNamespaceType.optional().default('single'), }); -export type GetExceptionListItemRequestQueryInput = z.input< - typeof GetExceptionListItemRequestQuery +export type ReadExceptionListItemRequestQueryInput = z.input< + typeof ReadExceptionListItemRequestQuery >; -export type GetExceptionListItemResponse = z.infer<typeof GetExceptionListItemResponse>; -export const GetExceptionListItemResponse = ExceptionListItem; +export type ReadExceptionListItemResponse = z.infer<typeof ReadExceptionListItemResponse>; +export const ReadExceptionListItemResponse = ExceptionListItem; diff --git a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.schema.yaml index 1ebad10125160..9cc6b79b91e63 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.schema.yaml @@ -1,16 +1,14 @@ openapi: 3.0.0 info: - title: Get exception list item API endpoint + title: Read exception list item API endpoint version: '2023-10-31' paths: /api/exception_lists/items: get: x-labels: [serverless, ess] - operationId: GetExceptionListItem + operationId: ReadExceptionListItem x-codegen-enabled: true summary: Gets an exception list item - tags: - - Exceptions API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_summary/read_exception_list_summary.gen.ts b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_summary/read_exception_list_summary.gen.ts new file mode 100644 index 0000000000000..d740f5843b5ce --- /dev/null +++ b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_summary/read_exception_list_summary.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Read exception list summary API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { + ExceptionListId, + ExceptionListHumanId, + ExceptionNamespaceType, +} from '../model/exception_list_common.gen'; + +export type ReadExceptionListSummaryRequestQuery = z.infer< + typeof ReadExceptionListSummaryRequestQuery +>; +export const ReadExceptionListSummaryRequestQuery = z.object({ + /** + * Exception list's identifier generated upon creation + */ + id: ExceptionListId.optional(), + /** + * Exception list's human readable identifier + */ + list_id: ExceptionListHumanId.optional(), + namespace_type: ExceptionNamespaceType.optional().default('single'), + /** + * Search filter clause + */ + filter: z.string().optional(), +}); +export type ReadExceptionListSummaryRequestQueryInput = z.input< + typeof ReadExceptionListSummaryRequestQuery +>; + +export type ReadExceptionListSummaryResponse = z.infer<typeof ReadExceptionListSummaryResponse>; +export const ReadExceptionListSummaryResponse = z.object({ + windows: z.number().int().min(0).optional(), + linux: z.number().int().min(0).optional(), + macos: z.number().int().min(0).optional(), + total: z.number().int().min(0).optional(), +}); diff --git a/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_summary/read_exception_list_summary.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_summary/read_exception_list_summary.schema.yaml new file mode 100644 index 0000000000000..bae534bf3260b --- /dev/null +++ b/packages/kbn-securitysolution-exceptions-common/api/read_exception_list_summary/read_exception_list_summary.schema.yaml @@ -0,0 +1,88 @@ +openapi: 3.0.0 +info: + title: Read exception list summary API endpoint + version: '2023-10-31' +paths: + /api/exception_lists/summary: + get: + x-labels: [serverless, ess] + operationId: ReadExceptionListSummary + x-codegen-enabled: true + summary: Retrieves an exception list summary + parameters: + - name: id + in: query + required: false + description: Exception list's identifier generated upon creation + schema: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListId' + - name: list_id + in: query + required: false + description: Exception list's human readable identifier + schema: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListHumanId' + - name: namespace_type + in: query + required: false + schema: + $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionNamespaceType' + default: single + - name: filter + in: query + required: false + description: Search filter clause + schema: + type: string + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + windows: + type: integer + minimum: 0 + linux: + type: integer + minimum: 0 + macos: + type: integer + minimum: 0 + total: + type: integer + minimum: 0 + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: Exception list not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-exceptions-common/api/summary_exception_list/summary_exception_list.gen.ts b/packages/kbn-securitysolution-exceptions-common/api/summary_exception_list/summary_exception_list.gen.ts deleted file mode 100644 index 166093ccbeb30..0000000000000 --- a/packages/kbn-securitysolution-exceptions-common/api/summary_exception_list/summary_exception_list.gen.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Get exception list summary API endpoint - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { - ExceptionListId, - ExceptionListHumanId, - ExceptionNamespaceType, -} from '../model/exception_list_common.gen'; - -export type GetExceptionListSummaryRequestQuery = z.infer< - typeof GetExceptionListSummaryRequestQuery ->; -export const GetExceptionListSummaryRequestQuery = z.object({ - /** - * Exception list's identifier generated upon creation - */ - id: ExceptionListId.optional(), - /** - * Exception list's human readable identifier - */ - list_id: ExceptionListHumanId.optional(), - namespace_type: ExceptionNamespaceType.optional().default('single'), - /** - * Search filter clause - */ - filter: z.string().optional(), -}); -export type GetExceptionListSummaryRequestQueryInput = z.input< - typeof GetExceptionListSummaryRequestQuery ->; - -export type GetExceptionListSummaryResponse = z.infer<typeof GetExceptionListSummaryResponse>; -export const GetExceptionListSummaryResponse = z.object({ - windows: z.number().int().min(0).optional(), - linux: z.number().int().min(0).optional(), - macos: z.number().int().min(0).optional(), - total: z.number().int().min(0).optional(), -}); diff --git a/packages/kbn-securitysolution-exceptions-common/api/summary_exception_list/summary_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/summary_exception_list/summary_exception_list.schema.yaml deleted file mode 100644 index cc288ee524ff2..0000000000000 --- a/packages/kbn-securitysolution-exceptions-common/api/summary_exception_list/summary_exception_list.schema.yaml +++ /dev/null @@ -1,90 +0,0 @@ -openapi: 3.0.0 -info: - title: Get exception list summary API endpoint - version: '2023-10-31' -paths: - /api/exception_lists/summary: - get: - x-labels: [serverless, ess] - operationId: GetExceptionListSummary - x-codegen-enabled: true - summary: Retrieves an exception list summary - tags: - - Exceptions API - parameters: - - name: id - in: query - required: false - description: Exception list's identifier generated upon creation - schema: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListId' - - name: list_id - in: query - required: false - description: Exception list's human readable identifier - schema: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionListHumanId' - - name: namespace_type - in: query - required: false - schema: - $ref: '../model/exception_list_common.schema.yaml#/components/schemas/ExceptionNamespaceType' - default: single - - name: filter - in: query - required: false - description: Search filter clause - schema: - type: string - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - windows: - type: integer - minimum: 0 - linux: - type: integer - minimum: 0 - macos: - type: integer - minimum: 0 - total: - type: integer - minimum: 0 - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 404: - description: Exception list not found response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-exceptions-common/api/update_exception_list/update_exception_list.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/update_exception_list/update_exception_list.schema.yaml index af31f2af4273d..a58caeb465428 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/update_exception_list/update_exception_list.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/update_exception_list/update_exception_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: UpdateExceptionList x-codegen-enabled: true summary: Updates an exception list - tags: - - Exceptions API requestBody: description: Exception list's properties required: true diff --git a/packages/kbn-securitysolution-exceptions-common/api/update_exception_list_item/update_exception_list_item.schema.yaml b/packages/kbn-securitysolution-exceptions-common/api/update_exception_list_item/update_exception_list_item.schema.yaml index 02ff5e2d01711..180d4865f887b 100644 --- a/packages/kbn-securitysolution-exceptions-common/api/update_exception_list_item/update_exception_list_item.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/api/update_exception_list_item/update_exception_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: UpdateExceptionListItem x-codegen-enabled: true summary: Updates an exception list item - tags: - - Exceptions API requestBody: description: Exception list item's properties required: true diff --git a/packages/kbn-securitysolution-exceptions-common/docs/openapi/ess/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml b/packages/kbn-securitysolution-exceptions-common/docs/openapi/ess/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml index 1c12ee8057a3b..8acc028520f11 100644 --- a/packages/kbn-securitysolution-exceptions-common/docs/openapi/ess/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/docs/openapi/ess/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml @@ -75,7 +75,7 @@ paths: description: Internal server error response summary: Creates rule exception list items tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists: delete: operationId: DeleteExceptionList @@ -139,9 +139,9 @@ paths: description: Internal server error response summary: Deletes an exception list tags: - - Exceptions API + - Security Solution Exceptions API get: - operationId: GetExceptionList + operationId: ReadExceptionList parameters: - description: Either `id` or `list_id` must be specified in: query @@ -202,7 +202,7 @@ paths: description: Internal server error response summary: Retrieves an exception list using its `id` or `list_id` field tags: - - Exceptions API + - Security Solution Exceptions API post: operationId: CreateExceptionList requestBody: @@ -279,7 +279,7 @@ paths: description: Internal server error response summary: Creates an exception list tags: - - Exceptions API + - Security Solution Exceptions API put: operationId: UpdateExceptionList requestBody: @@ -359,7 +359,7 @@ paths: description: Internal server error response summary: Updates an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_duplicate: post: operationId: DuplicateExceptionList @@ -428,7 +428,7 @@ paths: description: Internal server error response summary: Duplicates an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_export: post: description: Exports an exception list and its associated items to an .ndjson file @@ -508,7 +508,7 @@ paths: description: Internal server error response summary: Exports an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_find: get: operationId: FindExceptionLists @@ -628,7 +628,7 @@ paths: description: Internal server error response summary: Finds exception lists tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_import: post: description: Imports an exception list and associated items @@ -744,7 +744,7 @@ paths: description: Internal server error response summary: Imports an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/items: delete: operationId: DeleteExceptionListItem @@ -808,9 +808,9 @@ paths: description: Internal server error response summary: Deletes an exception list item tags: - - Exceptions API + - Security Solution Exceptions API get: - operationId: GetExceptionListItem + operationId: ReadExceptionListItem parameters: - description: Either `id` or `item_id` must be specified in: query @@ -871,7 +871,7 @@ paths: description: Internal server error response summary: Gets an exception list item tags: - - Exceptions API + - Security Solution Exceptions API post: operationId: CreateExceptionListItem requestBody: @@ -958,7 +958,7 @@ paths: description: Internal server error response summary: Creates an exception list item tags: - - Exceptions API + - Security Solution Exceptions API put: operationId: UpdateExceptionListItem requestBody: @@ -1049,7 +1049,7 @@ paths: description: Internal server error response summary: Updates an exception list item tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/items/_find: get: operationId: FindExceptionListItems @@ -1185,10 +1185,10 @@ paths: description: Internal server error response summary: Finds exception list items tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/summary: get: - operationId: GetExceptionListSummary + operationId: ReadExceptionListSummary parameters: - description: Exception list's identifier generated upon creation in: query @@ -1268,7 +1268,7 @@ paths: description: Internal server error response summary: Retrieves an exception list summary tags: - - Exceptions API + - Security Solution Exceptions API /api/exceptions/shared: post: operationId: CreateSharedExceptionList @@ -1327,7 +1327,7 @@ paths: description: Internal server error response summary: Creates a shared exception list tags: - - Exceptions API + - Security Solution Exceptions API components: schemas: CreateExceptionListItemComment: @@ -1852,3 +1852,9 @@ components: type: http security: - BasicAuth: [] +tags: + - description: >- + Exceptions API allows you to manage detection rule exceptions to prevent a + rule from generating an alert from incoming events even when the rule's + other criteria are met. + name: Security Solution Exceptions API diff --git a/packages/kbn-securitysolution-exceptions-common/docs/openapi/serverless/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml b/packages/kbn-securitysolution-exceptions-common/docs/openapi/serverless/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml index ebf3c74f39e2e..cd104baacda20 100644 --- a/packages/kbn-securitysolution-exceptions-common/docs/openapi/serverless/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml +++ b/packages/kbn-securitysolution-exceptions-common/docs/openapi/serverless/security_solution_exceptions_api_2023_10_31.bundled.schema.yaml @@ -75,7 +75,7 @@ paths: description: Internal server error response summary: Creates rule exception list items tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists: delete: operationId: DeleteExceptionList @@ -139,9 +139,9 @@ paths: description: Internal server error response summary: Deletes an exception list tags: - - Exceptions API + - Security Solution Exceptions API get: - operationId: GetExceptionList + operationId: ReadExceptionList parameters: - description: Either `id` or `list_id` must be specified in: query @@ -202,7 +202,7 @@ paths: description: Internal server error response summary: Retrieves an exception list using its `id` or `list_id` field tags: - - Exceptions API + - Security Solution Exceptions API post: operationId: CreateExceptionList requestBody: @@ -279,7 +279,7 @@ paths: description: Internal server error response summary: Creates an exception list tags: - - Exceptions API + - Security Solution Exceptions API put: operationId: UpdateExceptionList requestBody: @@ -359,7 +359,7 @@ paths: description: Internal server error response summary: Updates an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_duplicate: post: operationId: DuplicateExceptionList @@ -428,7 +428,7 @@ paths: description: Internal server error response summary: Duplicates an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_export: post: description: Exports an exception list and its associated items to an .ndjson file @@ -508,7 +508,7 @@ paths: description: Internal server error response summary: Exports an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_find: get: operationId: FindExceptionLists @@ -628,7 +628,7 @@ paths: description: Internal server error response summary: Finds exception lists tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/_import: post: description: Imports an exception list and associated items @@ -744,7 +744,7 @@ paths: description: Internal server error response summary: Imports an exception list tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/items: delete: operationId: DeleteExceptionListItem @@ -808,9 +808,9 @@ paths: description: Internal server error response summary: Deletes an exception list item tags: - - Exceptions API + - Security Solution Exceptions API get: - operationId: GetExceptionListItem + operationId: ReadExceptionListItem parameters: - description: Either `id` or `item_id` must be specified in: query @@ -871,7 +871,7 @@ paths: description: Internal server error response summary: Gets an exception list item tags: - - Exceptions API + - Security Solution Exceptions API post: operationId: CreateExceptionListItem requestBody: @@ -958,7 +958,7 @@ paths: description: Internal server error response summary: Creates an exception list item tags: - - Exceptions API + - Security Solution Exceptions API put: operationId: UpdateExceptionListItem requestBody: @@ -1049,7 +1049,7 @@ paths: description: Internal server error response summary: Updates an exception list item tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/items/_find: get: operationId: FindExceptionListItems @@ -1185,10 +1185,10 @@ paths: description: Internal server error response summary: Finds exception list items tags: - - Exceptions API + - Security Solution Exceptions API /api/exception_lists/summary: get: - operationId: GetExceptionListSummary + operationId: ReadExceptionListSummary parameters: - description: Exception list's identifier generated upon creation in: query @@ -1268,7 +1268,7 @@ paths: description: Internal server error response summary: Retrieves an exception list summary tags: - - Exceptions API + - Security Solution Exceptions API /api/exceptions/shared: post: operationId: CreateSharedExceptionList @@ -1327,7 +1327,7 @@ paths: description: Internal server error response summary: Creates a shared exception list tags: - - Exceptions API + - Security Solution Exceptions API components: schemas: CreateExceptionListItemComment: @@ -1852,3 +1852,9 @@ components: type: http security: - BasicAuth: [] +tags: + - description: >- + Exceptions API allows you to manage detection rule exceptions to prevent a + rule from generating an alert from incoming events even when the rule's + other criteria are met. + name: Security Solution Exceptions API diff --git a/packages/kbn-securitysolution-exceptions-common/scripts/openapi_bundle.js b/packages/kbn-securitysolution-exceptions-common/scripts/openapi_bundle.js index a3e82f172b05e..eed4b9c8a8ac8 100644 --- a/packages/kbn-securitysolution-exceptions-common/scripts/openapi_bundle.js +++ b/packages/kbn-securitysolution-exceptions-common/scripts/openapi_bundle.js @@ -21,10 +21,19 @@ const ROOT = resolve(__dirname, '..'); ), options: { includeLabels: ['serverless'], - specInfo: { - title: 'Security Solution Exceptions API (Elastic Cloud Serverless)', - description: - "Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.", + prototypeDocument: { + info: { + title: 'Security Solution Exceptions API (Elastic Cloud Serverless)', + description: + "Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.", + }, + tags: [ + { + name: 'Security Solution Exceptions API', + description: + "Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.", + }, + ], }, }, }); @@ -37,10 +46,19 @@ const ROOT = resolve(__dirname, '..'); ), options: { includeLabels: ['ess'], - specInfo: { - title: 'Security Solution Exceptions API (Elastic Cloud and self-hosted)', - description: - "Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.", + prototypeDocument: { + info: { + title: 'Security Solution Exceptions API (Elastic Cloud and self-hosted)', + description: + "Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.", + }, + tags: [ + { + name: 'Security Solution Exceptions API', + description: + "Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.", + }, + ], }, }, }); diff --git a/packages/kbn-securitysolution-lists-common/api/create_list/create_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/create_list/create_list.schema.yaml index 0cd635c46ff7f..bae6565b0f1e0 100644 --- a/packages/kbn-securitysolution-lists-common/api/create_list/create_list.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/create_list/create_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: CreateList x-codegen-enabled: true summary: Creates a list - tags: - - List API requestBody: description: List's properties required: true diff --git a/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.schema.yaml b/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.schema.yaml index f42937a8885d2..dcb1baa3ee3d8 100644 --- a/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: CreateListIndex x-codegen-enabled: true summary: Creates necessary list data streams - tags: - - List API responses: 200: description: Successful response diff --git a/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.schema.yaml index ca75b8555b9c4..10fef88e7a042 100644 --- a/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: CreateListItem x-codegen-enabled: true summary: Creates a list item - tags: - - List item API requestBody: description: List item's properties required: true diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.schema.yaml index c116d43edd93a..4a5974003918e 100644 --- a/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: DeleteList x-codegen-enabled: true summary: Deletes a list - tags: - - List API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.schema.yaml b/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.schema.yaml index c3b4969aa3283..34dcf91f548d0 100644 --- a/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: DeleteListIndex x-codegen-enabled: true summary: Deletes list data streams - tags: - - List API responses: 200: description: Successful response diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.schema.yaml index 63938d313aea2..413c85b55dd3b 100644 --- a/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: DeleteListItem x-codegen-enabled: true summary: Deletes a list item - tags: - - List item API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.schema.yaml deleted file mode 100644 index 69dd492e86638..0000000000000 --- a/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.schema.yaml +++ /dev/null @@ -1,62 +0,0 @@ -openapi: 3.0.0 -info: - title: Export list items API endpoint - version: '2023-10-31' -paths: - /api/lists/items/_export: - post: - x-labels: [serverless, ess] - operationId: ExportListItems - x-codegen-enabled: true - summary: Exports list items - description: Exports list item values from the specified list - tags: - - List items Import/Export API - parameters: - - name: list_id - in: query - required: true - description: List's id to export - schema: - $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' - responses: - 200: - description: Successful response - content: - application/ndjson: - schema: - type: string - format: binary - description: A `.txt` file containing list items from the specified list - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 404: - description: List not found response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/export_list_items/export_list_items.gen.ts similarity index 100% rename from packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.gen.ts rename to packages/kbn-securitysolution-lists-common/api/export_list_items/export_list_items.gen.ts diff --git a/packages/kbn-securitysolution-lists-common/api/export_list_items/export_list_items.schema.yaml b/packages/kbn-securitysolution-lists-common/api/export_list_items/export_list_items.schema.yaml new file mode 100644 index 0000000000000..06eda41d042fb --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/export_list_items/export_list_items.schema.yaml @@ -0,0 +1,60 @@ +openapi: 3.0.0 +info: + title: Export list items API endpoint + version: '2023-10-31' +paths: + /api/lists/items/_export: + post: + x-labels: [serverless, ess] + operationId: ExportListItems + x-codegen-enabled: true + summary: Exports list items + description: Exports list item values from the specified list + parameters: + - name: list_id + in: query + required: true + description: List's id to export + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + responses: + 200: + description: Successful response + content: + application/ndjson: + schema: + type: string + format: binary + description: A `.txt` file containing list items from the specified list + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/find_list/find_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/find_list/find_list.schema.yaml deleted file mode 100644 index 236fa747599ac..0000000000000 --- a/packages/kbn-securitysolution-lists-common/api/find_list/find_list.schema.yaml +++ /dev/null @@ -1,119 +0,0 @@ -openapi: 3.0.0 -info: - title: Find lists API endpoint - version: '2023-10-31' -paths: - /api/lists/_find: - get: - x-labels: [serverless, ess] - operationId: FindLists - x-codegen-enabled: true - summary: Finds lists - tags: - - List API - parameters: - - name: page - in: query - required: false - description: The page number to return - schema: - type: integer - - name: per_page - in: query - required: false - description: The number of lists to return per page - schema: - type: integer - - name: sort_field - in: query - required: false - description: Determines which field is used to sort the results - schema: - $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' - - name: sort_order - in: query - required: false - description: Determines the sort order, which can be `desc` or `asc` - schema: - type: string - enum: [desc, asc] - - name: cursor - in: query - required: false - description: | - Returns the list that come after the last list returned in the previous call - (use the cursor value returned in the previous call). This parameter uses - the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. - schema: - $ref: '#/components/schemas/FindListsCursor' - - name: filter - in: query - required: false - description: | - Filters the returned results according to the value of the specified field, - using the <field name>:<field value> syntax. - schema: - $ref: '#/components/schemas/FindListsFilter' - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' - page: - type: integer - minimum: 0 - per_page: - type: integer - minimum: 0 - total: - type: integer - minimum: 0 - cursor: - $ref: '#/components/schemas/FindListsCursor' - required: - - data - - page - - per_page - - total - - cursor - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - -components: - schemas: - FindListsCursor: - $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' - - FindListsFilter: - type: string diff --git a/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.schema.yaml deleted file mode 100644 index 1e822d442a4e2..0000000000000 --- a/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.schema.yaml +++ /dev/null @@ -1,125 +0,0 @@ -openapi: 3.0.0 -info: - title: Find list items API endpoint - version: '2023-10-31' -paths: - /api/lists/items/_find: - get: - x-labels: [serverless, ess] - operationId: FindListItems - x-codegen-enabled: true - summary: Finds list items - tags: - - List API - parameters: - - name: list_id - in: query - required: true - description: List's id - schema: - $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' - - name: page - in: query - required: false - description: The page number to return - schema: - type: integer - - name: per_page - in: query - required: false - description: The number of list items to return per page - schema: - type: integer - - name: sort_field - in: query - required: false - description: Determines which field is used to sort the results - schema: - $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' - - name: sort_order - in: query - required: false - description: Determines the sort order, which can be `desc` or `asc` - schema: - type: string - enum: [desc, asc] - - name: cursor - in: query - required: false - description: | - Returns the list that come after the last list returned in the previous call - (use the cursor value returned in the previous call). This parameter uses - the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. - schema: - $ref: '#/components/schemas/FindListItemsCursor' - - name: filter - in: query - required: false - description: | - Filters the returned results according to the value of the specified field, - using the <field name>:<field value> syntax. - schema: - $ref: '#/components/schemas/FindListItemsFilter' - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' - page: - type: integer - minimum: 0 - per_page: - type: integer - minimum: 0 - total: - type: integer - minimum: 0 - cursor: - $ref: '#/components/schemas/FindListItemsCursor' - required: - - data - - page - - per_page - - total - - cursor - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - -components: - schemas: - FindListItemsCursor: - $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' - - FindListItemsFilter: - type: string diff --git a/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/find_list_items/find_list_items.gen.ts similarity index 100% rename from packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.gen.ts rename to packages/kbn-securitysolution-lists-common/api/find_list_items/find_list_items.gen.ts diff --git a/packages/kbn-securitysolution-lists-common/api/find_list_items/find_list_items.schema.yaml b/packages/kbn-securitysolution-lists-common/api/find_list_items/find_list_items.schema.yaml new file mode 100644 index 0000000000000..b08e7b4719374 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/find_list_items/find_list_items.schema.yaml @@ -0,0 +1,123 @@ +openapi: 3.0.0 +info: + title: Find list items API endpoint + version: '2023-10-31' +paths: + /api/lists/items/_find: + get: + x-labels: [serverless, ess] + operationId: FindListItems + x-codegen-enabled: true + summary: Finds list items + parameters: + - name: list_id + in: query + required: true + description: List's id + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: page + in: query + required: false + description: The page number to return + schema: + type: integer + - name: per_page + in: query + required: false + description: The number of list items to return per page + schema: + type: integer + - name: sort_field + in: query + required: false + description: Determines which field is used to sort the results + schema: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + - name: sort_order + in: query + required: false + description: Determines the sort order, which can be `desc` or `asc` + schema: + type: string + enum: [desc, asc] + - name: cursor + in: query + required: false + description: | + Returns the list that come after the last list returned in the previous call + (use the cursor value returned in the previous call). This parameter uses + the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. + schema: + $ref: '#/components/schemas/FindListItemsCursor' + - name: filter + in: query + required: false + description: | + Filters the returned results according to the value of the specified field, + using the <field name>:<field value> syntax. + schema: + $ref: '#/components/schemas/FindListItemsFilter' + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + page: + type: integer + minimum: 0 + per_page: + type: integer + minimum: 0 + total: + type: integer + minimum: 0 + cursor: + $ref: '#/components/schemas/FindListItemsCursor' + required: + - data + - page + - per_page + - total + - cursor + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + FindListItemsCursor: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + FindListItemsFilter: + type: string diff --git a/packages/kbn-securitysolution-lists-common/api/find_list/find_list.gen.ts b/packages/kbn-securitysolution-lists-common/api/find_lists/find_lists.gen.ts similarity index 100% rename from packages/kbn-securitysolution-lists-common/api/find_list/find_list.gen.ts rename to packages/kbn-securitysolution-lists-common/api/find_lists/find_lists.gen.ts diff --git a/packages/kbn-securitysolution-lists-common/api/find_lists/find_lists.schema.yaml b/packages/kbn-securitysolution-lists-common/api/find_lists/find_lists.schema.yaml new file mode 100644 index 0000000000000..071dba08254ec --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/find_lists/find_lists.schema.yaml @@ -0,0 +1,117 @@ +openapi: 3.0.0 +info: + title: Find lists API endpoint + version: '2023-10-31' +paths: + /api/lists/_find: + get: + x-labels: [serverless, ess] + operationId: FindLists + x-codegen-enabled: true + summary: Finds lists + parameters: + - name: page + in: query + required: false + description: The page number to return + schema: + type: integer + - name: per_page + in: query + required: false + description: The number of lists to return per page + schema: + type: integer + - name: sort_field + in: query + required: false + description: Determines which field is used to sort the results + schema: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + - name: sort_order + in: query + required: false + description: Determines the sort order, which can be `desc` or `asc` + schema: + type: string + enum: [desc, asc] + - name: cursor + in: query + required: false + description: | + Returns the list that come after the last list returned in the previous call + (use the cursor value returned in the previous call). This parameter uses + the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. + schema: + $ref: '#/components/schemas/FindListsCursor' + - name: filter + in: query + required: false + description: | + Filters the returned results according to the value of the specified field, + using the <field name>:<field value> syntax. + schema: + $ref: '#/components/schemas/FindListsFilter' + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + page: + type: integer + minimum: 0 + per_page: + type: integer + minimum: 0 + total: + type: integer + minimum: 0 + cursor: + $ref: '#/components/schemas/FindListsCursor' + required: + - data + - page + - per_page + - total + - cursor + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + FindListsCursor: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + FindListsFilter: + type: string diff --git a/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.gen.ts b/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.gen.ts deleted file mode 100644 index 3fe6348242822..0000000000000 --- a/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Get list privileges API endpoint - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -export type ListPrivileges = z.infer<typeof ListPrivileges>; -export const ListPrivileges = z.object({ - username: z.string(), - has_all_requested: z.boolean(), - cluster: z.object({}).catchall(z.boolean()), - index: z.object({}).catchall(z.object({}).catchall(z.boolean())), - application: z.object({}).catchall(z.boolean()), -}); - -export type ListItemPrivileges = z.infer<typeof ListItemPrivileges>; -export const ListItemPrivileges = z.object({ - username: z.string(), - has_all_requested: z.boolean(), - cluster: z.object({}).catchall(z.boolean()), - index: z.object({}).catchall(z.object({}).catchall(z.boolean())), - application: z.object({}).catchall(z.boolean()), -}); - -export type GetListPrivilegesResponse = z.infer<typeof GetListPrivilegesResponse>; -export const GetListPrivilegesResponse = z.object({ - lists: ListPrivileges, - listItems: ListItemPrivileges, - is_authenticated: z.boolean(), -}); diff --git a/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.schema.yaml b/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.schema.yaml deleted file mode 100644 index 729da9b8f62a8..0000000000000 --- a/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.schema.yaml +++ /dev/null @@ -1,115 +0,0 @@ -openapi: 3.0.0 -info: - title: Get list privileges API endpoint - version: '2023-10-31' -paths: - /api/lists/privileges: - get: - x-labels: [serverless, ess] - operationId: GetListPrivileges - x-codegen-enabled: true - summary: Gets list privileges - tags: - - List API - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - lists: - $ref: '#/components/schemas/ListPrivileges' - listItems: - $ref: '#/components/schemas/ListItemPrivileges' - is_authenticated: - type: boolean - required: - - lists - - listItems - - is_authenticated - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - -components: - schemas: - ListPrivileges: - type: object - properties: - username: - type: string - has_all_requested: - type: boolean - cluster: - type: object - additionalProperties: - type: boolean - index: - type: object - additionalProperties: - type: object - additionalProperties: - type: boolean - application: - type: object - additionalProperties: - type: boolean - required: - - username - - has_all_requested - - cluster - - index - - application - - ListItemPrivileges: - type: object - properties: - username: - type: string - has_all_requested: - type: boolean - cluster: - type: object - additionalProperties: - type: boolean - index: - type: object - additionalProperties: - type: object - additionalProperties: - type: boolean - application: - type: object - additionalProperties: - type: boolean - required: - - username - - has_all_requested - - cluster - - index - - application diff --git a/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.schema.yaml deleted file mode 100644 index 04a0e18d76782..0000000000000 --- a/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.schema.yaml +++ /dev/null @@ -1,103 +0,0 @@ -openapi: 3.0.0 -info: - title: Import list items API endpoint - version: '2023-10-31' -paths: - /api/lists/items/_import: - post: - x-labels: [serverless, ess] - operationId: ImportListItems - x-codegen-enabled: true - summary: Imports list items - description: | - Imports a list of items from a `.txt` or `.csv` file. The maximum file size is 9 million bytes. - - You can import items to a new or existing list. - tags: - - List items Import/Export API - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - properties: - file: - type: string - format: binary - description: A `.txt` or `.csv` file containing newline separated list items - parameters: - - name: list_id - in: query - required: false - description: | - List's id. - - Required when importing to an existing list. - schema: - $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' - - name: type - in: query - required: false - description: | - Type of the importing list. - - Required when importing a new list that is `list_id` is not specified. - schema: - $ref: '../model/list_common.schema.yaml#/components/schemas/ListType' - - name: serializer - in: query - required: false - schema: - type: string - - name: deserializer - in: query - required: false - schema: - type: string - - name: refresh - in: query - required: false - description: Determines when changes made by the request are made visible to search - schema: - type: string - enum: ['true', 'false', 'wait_for'] - responses: - 200: - description: Successful response - content: - application/json: - schema: - $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 403: - description: Not enough privileges response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 409: - description: List with specified list_id does not exist response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/import_list_items/import_list_items.gen.ts similarity index 100% rename from packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.gen.ts rename to packages/kbn-securitysolution-lists-common/api/import_list_items/import_list_items.gen.ts diff --git a/packages/kbn-securitysolution-lists-common/api/import_list_items/import_list_items.schema.yaml b/packages/kbn-securitysolution-lists-common/api/import_list_items/import_list_items.schema.yaml new file mode 100644 index 0000000000000..895e222c05207 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/import_list_items/import_list_items.schema.yaml @@ -0,0 +1,101 @@ +openapi: 3.0.0 +info: + title: Import list items API endpoint + version: '2023-10-31' +paths: + /api/lists/items/_import: + post: + x-labels: [serverless, ess] + operationId: ImportListItems + x-codegen-enabled: true + summary: Imports list items + description: | + Imports a list of items from a `.txt` or `.csv` file. The maximum file size is 9 million bytes. + + You can import items to a new or existing list. + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + description: A `.txt` or `.csv` file containing newline separated list items + parameters: + - name: list_id + in: query + required: false + description: | + List's id. + + Required when importing to an existing list. + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: type + in: query + required: false + description: | + Type of the importing list. + + Required when importing a new list that is `list_id` is not specified. + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListType' + - name: serializer + in: query + required: false + schema: + type: string + - name: deserializer + in: query + required: false + schema: + type: string + - name: refresh + in: query + required: false + description: Determines when changes made by the request are made visible to search + schema: + type: string + enum: ['true', 'false', 'wait_for'] + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 409: + description: List with specified list_id does not exist response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/index.ts b/packages/kbn-securitysolution-lists-common/api/index.ts index 7b1fb1508653d..1daadd3284bff 100644 --- a/packages/kbn-securitysolution-lists-common/api/index.ts +++ b/packages/kbn-securitysolution-lists-common/api/index.ts @@ -13,10 +13,10 @@ export * from './create_list/create_list.gen'; export * from './delete_list_index/delete_list_index.gen'; export * from './delete_list_item/delete_list_item.gen'; export * from './delete_list/delete_list.gen'; -export * from './find_list_item/find_list_item.gen'; -export * from './find_list/find_list.gen'; -export * from './export_list_item/export_list_item.gen'; -export * from './import_list_item/import_list_item.gen'; +export * from './find_list_items/find_list_items.gen'; +export * from './find_lists/find_lists.gen'; +export * from './export_list_items/export_list_items.gen'; +export * from './import_list_items/import_list_items.gen'; export * from './patch_list_item/patch_list_item.gen'; export * from './patch_list/patch_list.gen'; export * from './read_list_index/read_list_index.gen'; diff --git a/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.schema.yaml index cae09887db312..1ca568acb2bbc 100644 --- a/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: PatchList x-codegen-enabled: true summary: Patches a list - tags: - - List API requestBody: description: List's properties required: true diff --git a/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.schema.yaml index 36ca55c7ae065..a17982db14452 100644 --- a/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: PatchListItem x-codegen-enabled: true summary: Patches a list item - tags: - - List item API requestBody: description: List item's properties required: true diff --git a/packages/kbn-securitysolution-lists-common/api/read_list/read_list.gen.ts b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.gen.ts index 2ae0eb6f6c91b..c0f00a8718de1 100644 --- a/packages/kbn-securitysolution-lists-common/api/read_list/read_list.gen.ts +++ b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.gen.ts @@ -11,7 +11,7 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Get list API endpoint + * title: Read list API endpoint * version: 2023-10-31 */ @@ -20,14 +20,14 @@ import { z } from 'zod'; import { ListId } from '../model/list_common.gen'; import { List } from '../model/list_schemas.gen'; -export type GetListRequestQuery = z.infer<typeof GetListRequestQuery>; -export const GetListRequestQuery = z.object({ +export type ReadListRequestQuery = z.infer<typeof ReadListRequestQuery>; +export const ReadListRequestQuery = z.object({ /** * List's `id` value */ id: ListId, }); -export type GetListRequestQueryInput = z.input<typeof GetListRequestQuery>; +export type ReadListRequestQueryInput = z.input<typeof ReadListRequestQuery>; -export type GetListResponse = z.infer<typeof GetListResponse>; -export const GetListResponse = List; +export type ReadListResponse = z.infer<typeof ReadListResponse>; +export const ReadListResponse = List; diff --git a/packages/kbn-securitysolution-lists-common/api/read_list/read_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.schema.yaml index 4a2ae5d2cd42c..770c6fa8a9e7d 100644 --- a/packages/kbn-securitysolution-lists-common/api/read_list/read_list.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.schema.yaml @@ -1,16 +1,14 @@ openapi: 3.0.0 info: - title: Get list API endpoint + title: Read list API endpoint version: '2023-10-31' paths: /api/lists: get: x-labels: [serverless, ess] - operationId: GetList + operationId: ReadList x-codegen-enabled: true summary: Retrieves a list using its id field - tags: - - List API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.gen.ts b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.gen.ts index c25105f038636..3658cb1001bed 100644 --- a/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.gen.ts +++ b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.gen.ts @@ -11,14 +11,14 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Get list DS existence status API endpoint + * title: Read list DS existence status API endpoint * version: 2023-10-31 */ import { z } from 'zod'; -export type GetListIndexResponse = z.infer<typeof GetListIndexResponse>; -export const GetListIndexResponse = z.object({ +export type ReadListIndexResponse = z.infer<typeof ReadListIndexResponse>; +export const ReadListIndexResponse = z.object({ list_index: z.boolean(), list_item_index: z.boolean(), }); diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.schema.yaml b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.schema.yaml index accef8b58411e..3706563c008ce 100644 --- a/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.schema.yaml @@ -1,16 +1,14 @@ openapi: 3.0.0 info: - title: Get list DS existence status API endpoint + title: Read list DS existence status API endpoint version: '2023-10-31' paths: /api/lists/index: get: x-labels: [serverless, ess] - operationId: GetListIndex + operationId: ReadListIndex x-codegen-enabled: true summary: Get list data stream existence status - tags: - - List API responses: 200: description: Successful response diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.gen.ts index e9f5b9eef9cf8..f981835219c80 100644 --- a/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.gen.ts +++ b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.gen.ts @@ -11,7 +11,7 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Get list item API endpoint + * title: Read list item API endpoint * version: 2023-10-31 */ @@ -20,8 +20,8 @@ import { z } from 'zod'; import { ListId } from '../model/list_common.gen'; import { ListItem } from '../model/list_schemas.gen'; -export type GetListItemRequestQuery = z.infer<typeof GetListItemRequestQuery>; -export const GetListItemRequestQuery = z.object({ +export type ReadListItemRequestQuery = z.infer<typeof ReadListItemRequestQuery>; +export const ReadListItemRequestQuery = z.object({ /** * Required if `list_id` and `value` are not specified */ @@ -35,7 +35,7 @@ export const GetListItemRequestQuery = z.object({ */ value: z.string().optional(), }); -export type GetListItemRequestQueryInput = z.input<typeof GetListItemRequestQuery>; +export type ReadListItemRequestQueryInput = z.input<typeof ReadListItemRequestQuery>; -export type GetListItemResponse = z.infer<typeof GetListItemResponse>; -export const GetListItemResponse = z.union([ListItem, z.array(ListItem)]); +export type ReadListItemResponse = z.infer<typeof ReadListItemResponse>; +export const ReadListItemResponse = z.union([ListItem, z.array(ListItem)]); diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.schema.yaml index 3a651617163aa..fe4a6046f012c 100644 --- a/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.schema.yaml @@ -1,16 +1,14 @@ openapi: 3.0.0 info: - title: Get list item API endpoint + title: Read list item API endpoint version: '2023-10-31' paths: /api/lists/items: get: x-labels: [serverless, ess] - operationId: GetListItem + operationId: ReadListItem x-codegen-enabled: true summary: Gets a list item - tags: - - List item API parameters: - name: id in: query diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_privileges/read_list_privileges.gen.ts b/packages/kbn-securitysolution-lists-common/api/read_list_privileges/read_list_privileges.gen.ts new file mode 100644 index 0000000000000..26dd21fed8652 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list_privileges/read_list_privileges.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Read list privileges API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type ListPrivileges = z.infer<typeof ListPrivileges>; +export const ListPrivileges = z.object({ + username: z.string(), + has_all_requested: z.boolean(), + cluster: z.object({}).catchall(z.boolean()), + index: z.object({}).catchall(z.object({}).catchall(z.boolean())), + application: z.object({}).catchall(z.boolean()), +}); + +export type ListItemPrivileges = z.infer<typeof ListItemPrivileges>; +export const ListItemPrivileges = z.object({ + username: z.string(), + has_all_requested: z.boolean(), + cluster: z.object({}).catchall(z.boolean()), + index: z.object({}).catchall(z.object({}).catchall(z.boolean())), + application: z.object({}).catchall(z.boolean()), +}); + +export type ReadListPrivilegesResponse = z.infer<typeof ReadListPrivilegesResponse>; +export const ReadListPrivilegesResponse = z.object({ + lists: ListPrivileges, + listItems: ListItemPrivileges, + is_authenticated: z.boolean(), +}); diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_privileges/read_list_privileges.schema.yaml b/packages/kbn-securitysolution-lists-common/api/read_list_privileges/read_list_privileges.schema.yaml new file mode 100644 index 0000000000000..fd22321c9ed29 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list_privileges/read_list_privileges.schema.yaml @@ -0,0 +1,113 @@ +openapi: 3.0.0 +info: + title: Read list privileges API endpoint + version: '2023-10-31' +paths: + /api/lists/privileges: + get: + x-labels: [serverless, ess] + operationId: ReadListPrivileges + x-codegen-enabled: true + summary: Gets list privileges + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + lists: + $ref: '#/components/schemas/ListPrivileges' + listItems: + $ref: '#/components/schemas/ListItemPrivileges' + is_authenticated: + type: boolean + required: + - lists + - listItems + - is_authenticated + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + ListPrivileges: + type: object + properties: + username: + type: string + has_all_requested: + type: boolean + cluster: + type: object + additionalProperties: + type: boolean + index: + type: object + additionalProperties: + type: object + additionalProperties: + type: boolean + application: + type: object + additionalProperties: + type: boolean + required: + - username + - has_all_requested + - cluster + - index + - application + + ListItemPrivileges: + type: object + properties: + username: + type: string + has_all_requested: + type: boolean + cluster: + type: object + additionalProperties: + type: boolean + index: + type: object + additionalProperties: + type: object + additionalProperties: + type: boolean + application: + type: object + additionalProperties: + type: boolean + required: + - username + - has_all_requested + - cluster + - index + - application diff --git a/packages/kbn-securitysolution-lists-common/api/update_list/update_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/update_list/update_list.schema.yaml index d25b21157c725..b31bea393c91b 100644 --- a/packages/kbn-securitysolution-lists-common/api/update_list/update_list.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/update_list/update_list.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: UpdateList x-codegen-enabled: true summary: Updates a list - tags: - - List API requestBody: description: List's properties required: true diff --git a/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.schema.yaml index 21df82f4ba40a..95a4df349ff93 100644 --- a/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.schema.yaml @@ -9,8 +9,6 @@ paths: operationId: UpdateListItem x-codegen-enabled: true summary: Updates a list item - tags: - - List item API requestBody: description: List item's properties required: true diff --git a/packages/kbn-securitysolution-lists-common/docs/openapi/ess/security_solution_lists_api_2023_10_31.bundled.schema.yaml b/packages/kbn-securitysolution-lists-common/docs/openapi/ess/security_solution_lists_api_2023_10_31.bundled.schema.yaml index 07d6236b5c519..7fdb215489101 100644 --- a/packages/kbn-securitysolution-lists-common/docs/openapi/ess/security_solution_lists_api_2023_10_31.bundled.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/docs/openapi/ess/security_solution_lists_api_2023_10_31.bundled.schema.yaml @@ -74,9 +74,9 @@ paths: description: Internal server error response summary: Deletes a list tags: - - List API + - Security Solution Lists API get: - operationId: GetList + operationId: ReadList parameters: - description: List's `id` value in: query @@ -125,7 +125,7 @@ paths: description: Internal server error response summary: Retrieves a list using its id field tags: - - List API + - Security Solution Lists API patch: operationId: PatchList requestBody: @@ -192,7 +192,7 @@ paths: description: Internal server error response summary: Patches a list tags: - - List API + - Security Solution Lists API post: operationId: CreateList requestBody: @@ -266,7 +266,7 @@ paths: description: Internal server error response summary: Creates a list tags: - - List API + - Security Solution Lists API put: operationId: UpdateList requestBody: @@ -335,7 +335,7 @@ paths: description: Internal server error response summary: Updates a list tags: - - List API + - Security Solution Lists API /api/lists/_find: get: operationId: FindLists @@ -448,7 +448,7 @@ paths: description: Internal server error response summary: Finds lists tags: - - List API + - Security Solution Lists API /api/lists/index: delete: operationId: DeleteListIndex @@ -498,9 +498,9 @@ paths: description: Internal server error response summary: Deletes list data streams tags: - - List API + - Security Solution Lists API get: - operationId: GetListIndex + operationId: ReadListIndex responses: '200': content: @@ -550,7 +550,7 @@ paths: description: Internal server error response summary: Get list data stream existence status tags: - - List API + - Security Solution Lists API post: operationId: CreateListIndex responses: @@ -599,7 +599,7 @@ paths: description: Internal server error response summary: Creates necessary list data streams tags: - - List API + - Security Solution Lists API /api/lists/items: delete: operationId: DeleteListItem @@ -680,9 +680,9 @@ paths: description: Internal server error response summary: Deletes a list item tags: - - List item API + - Security Solution Lists API get: - operationId: GetListItem + operationId: ReadListItem parameters: - description: Required if `list_id` and `value` are not specified in: query @@ -747,7 +747,7 @@ paths: description: Internal server error response summary: Gets a list item tags: - - List item API + - Security Solution Lists API patch: operationId: PatchListItem requestBody: @@ -818,7 +818,7 @@ paths: description: Internal server error response summary: Patches a list item tags: - - List item API + - Security Solution Lists API post: operationId: CreateListItem requestBody: @@ -890,7 +890,7 @@ paths: description: Internal server error response summary: Creates a list item tags: - - List item API + - Security Solution Lists API put: operationId: UpdateListItem requestBody: @@ -953,7 +953,7 @@ paths: description: Internal server error response summary: Updates a list item tags: - - List item API + - Security Solution Lists API /api/lists/items/_export: post: description: Exports list item values from the specified list @@ -1008,7 +1008,7 @@ paths: description: Internal server error response summary: Exports list items tags: - - List items Import/Export API + - Security Solution Lists API /api/lists/items/_find: get: operationId: FindListItems @@ -1127,7 +1127,7 @@ paths: description: Internal server error response summary: Finds list items tags: - - List API + - Security Solution Lists API /api/lists/items/_import: post: description: > @@ -1234,10 +1234,10 @@ paths: description: Internal server error response summary: Imports list items tags: - - List items Import/Export API + - Security Solution Lists API /api/lists/privileges: get: - operationId: GetListPrivileges + operationId: ReadListPrivileges responses: '200': content: @@ -1284,7 +1284,7 @@ paths: description: Internal server error response summary: Gets list privileges tags: - - List API + - Security Solution Lists API components: schemas: FindListItemsCursor: @@ -1520,3 +1520,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.' + name: Security Solution Lists API diff --git a/packages/kbn-securitysolution-lists-common/docs/openapi/serverless/security_solution_lists_api_2023_10_31.bundled.schema.yaml b/packages/kbn-securitysolution-lists-common/docs/openapi/serverless/security_solution_lists_api_2023_10_31.bundled.schema.yaml index 8c5bcdc93edba..c55ffe963a607 100644 --- a/packages/kbn-securitysolution-lists-common/docs/openapi/serverless/security_solution_lists_api_2023_10_31.bundled.schema.yaml +++ b/packages/kbn-securitysolution-lists-common/docs/openapi/serverless/security_solution_lists_api_2023_10_31.bundled.schema.yaml @@ -74,9 +74,9 @@ paths: description: Internal server error response summary: Deletes a list tags: - - List API + - Security Solution Lists API get: - operationId: GetList + operationId: ReadList parameters: - description: List's `id` value in: query @@ -125,7 +125,7 @@ paths: description: Internal server error response summary: Retrieves a list using its id field tags: - - List API + - Security Solution Lists API patch: operationId: PatchList requestBody: @@ -192,7 +192,7 @@ paths: description: Internal server error response summary: Patches a list tags: - - List API + - Security Solution Lists API post: operationId: CreateList requestBody: @@ -266,7 +266,7 @@ paths: description: Internal server error response summary: Creates a list tags: - - List API + - Security Solution Lists API put: operationId: UpdateList requestBody: @@ -335,7 +335,7 @@ paths: description: Internal server error response summary: Updates a list tags: - - List API + - Security Solution Lists API /api/lists/_find: get: operationId: FindLists @@ -448,7 +448,7 @@ paths: description: Internal server error response summary: Finds lists tags: - - List API + - Security Solution Lists API /api/lists/index: delete: operationId: DeleteListIndex @@ -498,9 +498,9 @@ paths: description: Internal server error response summary: Deletes list data streams tags: - - List API + - Security Solution Lists API get: - operationId: GetListIndex + operationId: ReadListIndex responses: '200': content: @@ -550,7 +550,7 @@ paths: description: Internal server error response summary: Get list data stream existence status tags: - - List API + - Security Solution Lists API post: operationId: CreateListIndex responses: @@ -599,7 +599,7 @@ paths: description: Internal server error response summary: Creates necessary list data streams tags: - - List API + - Security Solution Lists API /api/lists/items: delete: operationId: DeleteListItem @@ -680,9 +680,9 @@ paths: description: Internal server error response summary: Deletes a list item tags: - - List item API + - Security Solution Lists API get: - operationId: GetListItem + operationId: ReadListItem parameters: - description: Required if `list_id` and `value` are not specified in: query @@ -747,7 +747,7 @@ paths: description: Internal server error response summary: Gets a list item tags: - - List item API + - Security Solution Lists API patch: operationId: PatchListItem requestBody: @@ -818,7 +818,7 @@ paths: description: Internal server error response summary: Patches a list item tags: - - List item API + - Security Solution Lists API post: operationId: CreateListItem requestBody: @@ -890,7 +890,7 @@ paths: description: Internal server error response summary: Creates a list item tags: - - List item API + - Security Solution Lists API put: operationId: UpdateListItem requestBody: @@ -953,7 +953,7 @@ paths: description: Internal server error response summary: Updates a list item tags: - - List item API + - Security Solution Lists API /api/lists/items/_export: post: description: Exports list item values from the specified list @@ -1008,7 +1008,7 @@ paths: description: Internal server error response summary: Exports list items tags: - - List items Import/Export API + - Security Solution Lists API /api/lists/items/_find: get: operationId: FindListItems @@ -1127,7 +1127,7 @@ paths: description: Internal server error response summary: Finds list items tags: - - List API + - Security Solution Lists API /api/lists/items/_import: post: description: > @@ -1234,10 +1234,10 @@ paths: description: Internal server error response summary: Imports list items tags: - - List items Import/Export API + - Security Solution Lists API /api/lists/privileges: get: - operationId: GetListPrivileges + operationId: ReadListPrivileges responses: '200': content: @@ -1284,7 +1284,7 @@ paths: description: Internal server error response summary: Gets list privileges tags: - - List API + - Security Solution Lists API components: schemas: FindListItemsCursor: @@ -1520,3 +1520,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.' + name: Security Solution Lists API diff --git a/packages/kbn-securitysolution-lists-common/scripts/openapi_bundle.js b/packages/kbn-securitysolution-lists-common/scripts/openapi_bundle.js index 9bed9a313882f..7034c662aa4c2 100644 --- a/packages/kbn-securitysolution-lists-common/scripts/openapi_bundle.js +++ b/packages/kbn-securitysolution-lists-common/scripts/openapi_bundle.js @@ -21,9 +21,18 @@ const ROOT = resolve(__dirname, '..'); ), options: { includeLabels: ['serverless'], - specInfo: { - title: 'Security Solution Lists API (Elastic Cloud Serverless)', - description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.', + prototypeDocument: { + info: { + title: 'Security Solution Lists API (Elastic Cloud Serverless)', + description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.', + }, + tags: [ + { + name: 'Security Solution Lists API', + description: + 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.', + }, + ], }, }, }); @@ -36,9 +45,18 @@ const ROOT = resolve(__dirname, '..'); ), options: { includeLabels: ['ess'], - specInfo: { - title: 'Security Solution Lists API (Elastic Cloud and self-hosted)', - description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.', + prototypeDocument: { + info: { + title: 'Security Solution Lists API (Elastic Cloud and self-hosted)', + description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.', + }, + tags: [ + { + name: 'Security Solution Lists API', + description: + 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.', + }, + ], }, }, }); diff --git a/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts b/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts index 44deada7cd155..251c00d4a75b4 100644 --- a/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts +++ b/packages/kbn-securitysolution-utils/src/esql/compute_if_esql_query_aggregating.ts @@ -6,11 +6,18 @@ * Side Public License, v 1. */ +import { ESQLAst, getAstAndSyntaxErrors } from '@kbn/esql-ast'; + +export const isAggregatingQuery = (ast: ESQLAst): boolean => { + return ast.some((astItem) => astItem.type === 'command' && astItem.name === 'stats'); +}; + /** * compute if esqlQuery is aggregating/grouping, i.e. using STATS...BY command * @param esqlQuery * @returns boolean */ export const computeIsESQLQueryAggregating = (esqlQuery: string): boolean => { - return /\|\s+stats\s/i.test(esqlQuery); + const { ast } = getAstAndSyntaxErrors(esqlQuery); + return isAggregatingQuery(ast); }; diff --git a/packages/kbn-securitysolution-utils/tsconfig.json b/packages/kbn-securitysolution-utils/tsconfig.json index c734b5c153fb0..5b9520c487e31 100644 --- a/packages/kbn-securitysolution-utils/tsconfig.json +++ b/packages/kbn-securitysolution-utils/tsconfig.json @@ -12,7 +12,8 @@ ], "kbn_references": [ "@kbn/i18n", - "@kbn/esql-utils" + "@kbn/esql-utils", + "@kbn/esql-ast" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-server-route-repository-client/README.md b/packages/kbn-server-route-repository-client/README.md new file mode 100644 index 0000000000000..59257f7c9a652 --- /dev/null +++ b/packages/kbn-server-route-repository-client/README.md @@ -0,0 +1,3 @@ +# @kbn/server-route-repository-client + +Extension of `@kbn/server-route-repository` with the browser side parts of the `@kbn/server-route-repository` package. diff --git a/packages/kbn-server-route-repository-client/index.ts b/packages/kbn-server-route-repository-client/index.ts new file mode 100644 index 0000000000000..d2572e009bd66 --- /dev/null +++ b/packages/kbn-server-route-repository-client/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 { createRepositoryClient } from './src/create_repository_client'; +export type { DefaultClientOptions } from '@kbn/server-route-repository-utils'; diff --git a/packages/kbn-server-route-repository-client/jest.config.js b/packages/kbn-server-route-repository-client/jest.config.js new file mode 100644 index 0000000000000..b913a23f9441a --- /dev/null +++ b/packages/kbn-server-route-repository-client/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: ['<rootDir>/packages/kbn-server-route-repository-client'], +}; diff --git a/packages/kbn-server-route-repository-client/kibana.jsonc b/packages/kbn-server-route-repository-client/kibana.jsonc new file mode 100644 index 0000000000000..5a3974d7ab3ca --- /dev/null +++ b/packages/kbn-server-route-repository-client/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-browser", + "id": "@kbn/server-route-repository-client", + "owner": "@elastic/obs-knowledge-team" +} diff --git a/packages/kbn-server-route-repository-client/package.json b/packages/kbn-server-route-repository-client/package.json new file mode 100644 index 0000000000000..0f4289c3c86fd --- /dev/null +++ b/packages/kbn-server-route-repository-client/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/server-route-repository-client", + "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-server-route-repository-client/src/create_repository_client.test.ts b/packages/kbn-server-route-repository-client/src/create_repository_client.test.ts new file mode 100644 index 0000000000000..c7e12e2440fed --- /dev/null +++ b/packages/kbn-server-route-repository-client/src/create_repository_client.test.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 * as t from 'io-ts'; +import { CoreSetup } from '@kbn/core-lifecycle-browser'; +import { createRepositoryClient } from './create_repository_client'; + +describe('createRepositoryClient', () => { + const getMock = jest.fn(); + const coreSetupMock = { + http: { + get: getMock, + }, + } as unknown as CoreSetup; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('provides a default value for options when they are not required', () => { + const repository = { + 'GET /internal/handler': { + endpoint: 'GET /internal/handler', + handler: jest.fn().mockResolvedValue('OK'), + }, + }; + const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock); + + fetch('GET /internal/handler'); + + expect(getMock).toHaveBeenCalledTimes(1); + expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', { + body: undefined, + query: undefined, + version: undefined, + }); + }); + + it('extract the version from the endpoint', () => { + const repository = { + 'GET /api/handler 2024-08-05': { + endpoint: 'GET /api/handler 2024-08-05', + handler: jest.fn().mockResolvedValue('OK'), + }, + }; + const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock); + + fetch('GET /api/handler 2024-08-05'); + + expect(getMock).toHaveBeenCalledTimes(1); + expect(getMock).toHaveBeenNthCalledWith(1, '/api/handler', { + body: undefined, + query: undefined, + version: '2024-08-05', + }); + }); + + it('passes on the provided client parameters', () => { + const repository = { + 'GET /internal/handler': { + endpoint: 'GET /internal/handler', + handler: jest.fn().mockResolvedValue('OK'), + }, + }; + const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock); + + fetch('GET /internal/handler', { + headers: { + some_header: 'header_value', + }, + }); + + expect(getMock).toHaveBeenCalledTimes(1); + expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', { + headers: { + some_header: 'header_value', + }, + body: undefined, + query: undefined, + version: undefined, + }); + }); + + it('replaces path params before making the call', () => { + const repository = { + 'GET /internal/handler/{param}': { + endpoint: 'GET /internal/handler/{param}', + params: t.type({ + path: t.type({ + param: t.string, + }), + }), + handler: jest.fn().mockResolvedValue('OK'), + }, + }; + const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock); + + fetch('GET /internal/handler/{param}', { + params: { + path: { + param: 'param_value', + }, + }, + }); + + expect(getMock).toHaveBeenCalledTimes(1); + expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler/param_value', { + body: undefined, + query: undefined, + version: undefined, + }); + }); + + it('passes on the stringified body content when provided', () => { + const repository = { + 'GET /internal/handler': { + endpoint: 'GET /internal/handler', + params: t.type({ + body: t.type({ + payload: t.string, + }), + }), + handler: jest.fn().mockResolvedValue('OK'), + }, + }; + const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock); + + fetch('GET /internal/handler', { + params: { + body: { + payload: 'body_value', + }, + }, + }); + + expect(getMock).toHaveBeenCalledTimes(1); + expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', { + body: JSON.stringify({ + payload: 'body_value', + }), + query: undefined, + version: undefined, + }); + }); + + it('passes on the query parameters when provided', () => { + const repository = { + 'GET /internal/handler': { + endpoint: 'GET /internal/handler', + params: t.type({ + query: t.type({ + parameter: t.string, + }), + }), + handler: jest.fn().mockResolvedValue('OK'), + }, + }; + const { fetch } = createRepositoryClient<typeof repository>(coreSetupMock); + + fetch('GET /internal/handler', { + params: { + query: { + parameter: 'query_value', + }, + }, + }); + + expect(getMock).toHaveBeenCalledTimes(1); + expect(getMock).toHaveBeenNthCalledWith(1, '/internal/handler', { + body: undefined, + query: { + parameter: 'query_value', + }, + version: undefined, + }); + }); +}); diff --git a/packages/kbn-server-route-repository-client/src/create_repository_client.ts b/packages/kbn-server-route-repository-client/src/create_repository_client.ts new file mode 100644 index 0000000000000..ff7bc13234dba --- /dev/null +++ b/packages/kbn-server-route-repository-client/src/create_repository_client.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreSetup, CoreStart } from '@kbn/core-lifecycle-browser'; +import { + RouteRepositoryClient, + ServerRouteRepository, + DefaultClientOptions, + formatRequest, +} from '@kbn/server-route-repository-utils'; + +export function createRepositoryClient< + TRepository extends ServerRouteRepository, + TClientOptions extends Record<string, any> = DefaultClientOptions +>(core: CoreStart | CoreSetup) { + return { + fetch: (endpoint, optionsWithParams) => { + const { params, ...options } = (optionsWithParams ?? { params: {} }) as unknown as { + params?: Partial<Record<string, any>>; + }; + + const { method, pathname, version } = formatRequest(endpoint, params?.path); + + return core.http[method](pathname, { + ...options, + body: params && params.body ? JSON.stringify(params.body) : undefined, + query: params?.query, + version, + }); + }, + } as { fetch: RouteRepositoryClient<TRepository, TClientOptions> }; +} diff --git a/packages/kbn-server-route-repository-client/tsconfig.json b/packages/kbn-server-route-repository-client/tsconfig.json new file mode 100644 index 0000000000000..8ef10ede60a1a --- /dev/null +++ b/packages/kbn-server-route-repository-client/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/server-route-repository-utils", + "@kbn/core-lifecycle-browser", + ] +} diff --git a/packages/kbn-server-route-repository-utils/index.ts b/packages/kbn-server-route-repository-utils/index.ts index 02ff5f70211fa..bfcc03c0e64c5 100644 --- a/packages/kbn-server-route-repository-utils/index.ts +++ b/packages/kbn-server-route-repository-utils/index.ts @@ -8,3 +8,20 @@ export { formatRequest } from './src/format_request'; export { parseEndpoint } from './src/parse_endpoint'; + +export type { + ServerRouteCreateOptions, + ServerRouteHandlerResources, + RouteParamsRT, + ServerRoute, + EndpointOf, + ReturnOf, + RouteRepositoryClient, + RouteState, + ClientRequestParamsOf, + DecodedRequestParamsOf, + ServerRouteRepository, + DefaultClientOptions, + DefaultRouteCreateOptions, + DefaultRouteHandlerResources, +} from './src/typings'; diff --git a/packages/kbn-server-route-repository/src/typings.ts b/packages/kbn-server-route-repository-utils/src/typings.ts similarity index 85% rename from packages/kbn-server-route-repository/src/typings.ts rename to packages/kbn-server-route-repository-utils/src/typings.ts index 28212f80ca5da..0539d9ea1d38e 100644 --- a/packages/kbn-server-route-repository/src/typings.ts +++ b/packages/kbn-server-route-repository-utils/src/typings.ts @@ -6,7 +6,16 @@ * Side Public License, v 1. */ -import { IKibanaResponse } from '@kbn/core-http-server'; +import type { HttpFetchOptions } from '@kbn/core-http-browser'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import type { + RequestHandlerContext, + Logger, + RouteConfigOptions, + RouteMethod, + KibanaRequest, + KibanaResponseFactory, +} from '@kbn/core/server'; import * as t from 'io-ts'; import { RequiredKeys } from 'utility-types'; @@ -137,9 +146,25 @@ type MaybeOptionalArgs<T extends Record<string, any>> = RequiredKeys<T> extends export type RouteRepositoryClient< TServerRouteRepository extends ServerRouteRepository, TAdditionalClientOptions extends Record<string, any> -> = <TEndpoint extends keyof TServerRouteRepository>( +> = <TEndpoint extends Extract<keyof TServerRouteRepository, string>>( endpoint: TEndpoint, ...args: MaybeOptionalArgs< ClientRequestParamsOf<TServerRouteRepository, TEndpoint> & TAdditionalClientOptions > ) => Promise<ReturnOf<TServerRouteRepository, TEndpoint>>; + +export type DefaultClientOptions = HttpFetchOptions; + +interface CoreRouteHandlerResources { + request: KibanaRequest; + response: KibanaResponseFactory; + context: RequestHandlerContext; +} + +export interface DefaultRouteHandlerResources extends CoreRouteHandlerResources { + logger: Logger; +} + +export interface DefaultRouteCreateOptions { + options?: RouteConfigOptions<RouteMethod>; +} diff --git a/packages/kbn-server-route-repository-utils/tsconfig.json b/packages/kbn-server-route-repository-utils/tsconfig.json index 87f865132f4b4..0f3dd221ec6b7 100644 --- a/packages/kbn-server-route-repository-utils/tsconfig.json +++ b/packages/kbn-server-route-repository-utils/tsconfig.json @@ -15,5 +15,9 @@ "exclude": [ "target/**/*" ], - "kbn_references": [] + "kbn_references": [ + "@kbn/core-http-browser", + "@kbn/core-http-server", + "@kbn/core", + ] } diff --git a/packages/kbn-server-route-repository/README.md b/packages/kbn-server-route-repository/README.md index 6810b092403bc..f46a8f3ee3677 100644 --- a/packages/kbn-server-route-repository/README.md +++ b/packages/kbn-server-route-repository/README.md @@ -2,12 +2,346 @@ Utility functions for creating a typed server route repository, and a typed client, generating runtime validation and type validation from the same route definition. -## Usage +## Overview -TBD +There are three main functions that make up this package: +1. `createServerRouteFactory` +2. `registerRoutes` +3. `createRepositoryClient` -## Server vs. Browser entry points +`createServerRouteFactory` and `registerRoutes` are used in the server and `createRepositoryClient` in the browser (thus it is imported from `@kbn/server-route-repository-client`). -This package can only be used on the server. The browser utilities can be found in `@kbn/server-route-repository-utils`. +`createServerRouteFactory` returns a function that can be used to create routes for the repository, when calling it you can specify the resources that will be available to your route handler as well as which other options should be specified on your routes. -When adding utilities to this package, please make sure to update the entry points accordingly and the [BUILD.bazel](./BUILD.bazel)'s `target_web` target build to include all the necessary files. +Once the routes have been created and put into a plain object (the "repository"), this repository can then be passed to `registerRoutes` which also accepts the dependencies to be injected into each route handler. `registerRoutes` handles the creation of the Core HTTP router, as well as the final registration of the routes with versioning and request validation. + +By exporting the type of the repository from the server to the browser (make sure you use a `type` import), we can pass that as a generic argument to `createRepositoryClient` and get back a thin but strongly typed wrapper around the Core HTTP service, with auto completion for the available routes, type checking for the request parameters required by each specific route and response type inference. You can also add a generic type for which additional options the client should pass with each request. + +## Basic example + +In the server side, we'll start by creating the route factory, to make things easier it is recommended to keep this in its own file and export it. + +> server/create_my_plugin_server_route.ts +```javascript +import { createServerRouteFactory } from '@kbn/server-route-repository'; +import { + DefaultRouteHandlerResources, + DefaultRouteCreateOptions, +} from '@kbn/server-route-repository-utils'; + +export const createMyPluginServerRoute = createServerRouteFactory< + DefaultRouteHandlerResources, + DefaultRouteCreateOptions +>(); +``` + +The two generic arguments are optional, this example shows a "default" setup which exposes what Core HTTP would normally provide (`request`, `context`, `response`) plus a logger. + +Next, let's create a minimal route. + +> server/my_route.ts +```javascript +import { createMyPluginServerRoute } from './create_my_plugin_server_route'; + +export const myRoute = createMyPluginServerRoute({ + endpoint: 'GET /internal/my_plugin/route', + handler: async (resources) => { + const { request, context, response, logger } = resources; + return response.ok({ + body: 'Hello, my route!', + }); + }, +}); +``` + +After this we can add the route to a "repository", which is just a plain object, and call `registerRoutes`. + +> server/plugin.ts + +```javascript +import { registerRoutes } from '@kbn/server-route-repository'; + +import { myRoute } from './my_route'; + +const repository = { + ...myRoute, +}; + +export type MyPluginRouteRepository = typeof repository; + +class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + registerRoutes({ + core, + logger, + repository, + dependencies: {}, + }); + } +} +``` + +Since this example doesn't use any dependencies, the generic argument for `registerRoutes` is optional and we pass an empty object. +We also export the type of the repository, we'll need this for the client which is next! + +The client can be created either in `setup` or `start`. + +> browser/plugin.ts +```javascript +import { isHttpFetchError } from '@kbn/core-http-browser'; +import { DefaultClientOptions } from '@kbn/server-route-repository-utils'; +import { createRepositoryClient } from '@kbn/server-route-repository-client'; +import type { MyPluginRouteRepository } from '../server/plugin'; + +export type MyPluginRepositoryClient = + ReturnType<typeof createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions>>; + +class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + const myPluginRepositoryClient = + createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions>(core); + + myPluginRepositoryClient + .fetch('GET /internal/my_plugin/route') + .then((response) => console.log(response)) + .catch((error) => { + if (isHttpFetchError(error)) { + console.log(error.message); + } + + throw error; + }); + } +} +``` + +This example prints 'Hello, my route!' and the type of the response is **inferred** to this. + +We pass in the type of the repository that we (_type_) imported from the server. The second generic parameter for `createRepositoryClient` is optional. +We also export the type of the client itself so we can use it to type the client as we pass it around. + +When using the client's `fetch` function, the first argument is the route to call and this is auto completed to only the available routes. +The second argument is optional in this case but allows you to send in any extra options. + +The client translates the endpoint and the options (including request parameters) to the right Core HTTP request. + +## Request parameter validation + +When creating your routes, you can also provide an `io-ts` codec to be used when validating incoming requests. + +```javascript +import * as t from 'io-ts'; + +const myRoute = createMyPluginServerRoute({ + endpoint: 'GET /internal/my_plugin/route/{my_path_param}', + params: t.type({ + path: t.type({ + my_path_param: t.string, + }), + query: t.type({ + my_query_param: t.string, + }), + body: t.type({ + my_body_param: t.string, + }), + }), + handler: async (resources) => { + const { request, context, response, logger, params } = resources; + + const { path, query, body } = params; + + return response.ok({ + body: 'Hello, my route!', + }); + }, +}); +``` + +The `params` object is added to the route resources. +`path`, `query` and `body` are validated before your handler is called and the types are **inferred** inside of the handler. + +When calling this endpoint, it will look like this: +```javascript +client('GET /internal/my_plugin/route/{my_path_param}', { + params: { + path: { + my_path_param: 'some_path_value', + }, + query: { + my_query_param: 'some_query_value', + }, + body: { + my_body_param: 'some_body_value', + }, + }, +}).then(console.log); +``` + +Where the shape of `params` is typed to match the expected shape, meaning you don't need to manually use the codec when calling the route. + +## Public routes + +To define a public route, you need to change the endpoint path and add a version. + +```javascript +const myRoute = createMyPluginServerRoute({ + endpoint: 'GET /api/my_plugin/route 2024-08-02', + handler: async (resources) => { + const { request, context, response, logger } = resources; + return response.ok({ + body: 'Hello, my route!', + }); + }, +}); +``` + +`registerRoutes` takes care of setting the `access` option correctly for you and using the right versioned router. + +## Convenient return and throw + +`registerRoutes` translate any returned or thrown non-Kibana response into a Kibana response (including `Boom`). +It also handles common concerns like abort signals. + +```javascript +import { teapot } from '@hapi/boom'; + +const myRoute = createMyPluginServerRoute({ + endpoint: 'GET /internal/my_plugin/route', + handler: async (resources) => { + const { request, context, response, logger } = resources; + + const result = coinFlip(); + if (result === 'heads') { + throw teapot(); + } else { + return 'Hello, my route!'; + } + }, +}); +``` + +Both the teapot error and the plain string will be translated into a Kibana response. + +## Route dependencies + +If you want to provide additional dependencies to your route, you need to change the generic argument to `createServerRouteFactory` and `registerRoutes`. + +```javascript +import { createServerRouteFactory } from '@kbn/server-route-repository'; +import { DefaultRouteHandlerResources } from '@kbn/server-route-repository-utils'; + +export interface MyPluginRouteDependencies { + myDependency: MyDependency; +} + +export const createMyPluginServerRoute = + createServerRouteFactory<DefaultRouteHandlerResources & MyPluginRouteDependencies>(); +``` + +If you don't want your route to have access to the default resources, you could pass in only `MyPluginRouteDependencies`. + +Then we use the same type when calling `registerRoutes` + +```javascript +registerRoutes<MyPluginRouteDependencies>({ + core, + logger, + repository, + dependencies: { + myDependency: new MyDependency(), + }, +}); +``` + +This way, when creating a route, you will have `myDependency` available in the route resources. + +```javascript +import { createMyPluginServerRoute } from './create_my_plugin_server_route'; + +export const myRoute = createMyPluginServerRoute({ + endpoint: 'GET /internal/my_plugin/route', + handler: async (resources) => { + const { request, context, response, logger, myDependency } = resources; + return response.ok({ + body: myDependency.sayHello(), + }); + }, +}); +``` + +## Route creation options + +Core HTTP allows certain options to be passed to the route when it's being created, and you may want to include your own options as well. +To do this, override the second generic argument when calling `createServerRouteFactory`. + +```javascript +import { createServerRouteFactory } from '@kbn/server-route-repository'; +import { + DefaultRouteHandlerResources, + DefaultRouteCreateOptions, +} from '@kbn/server-route-repository-utils'; + +interface MyPluginRouteCreateOptions { + isDangerous: boolean; +} + +export const createMyPluginServerRoute = createServerRouteFactory< + DefaultRouteHandlerResources, + DefaultRouteCreateOptions & MyPluginRouteCreateOptions +>(); +``` + +If you don't want your route to have access to the options provided by Core HTTP, you could pass in only `MyPluginRouteCreateOptions`. + +You can then specify this option when creating the route. +```javascript +import { createMyPluginServerRoute } from './create_my_plugin_server_route'; + +export const myRoute = createMyPluginServerRoute({ + options: { + access: 'internal', + }, + isDangerous: true, + endpoint: 'GET /internal/my_plugin/route', + handler: async (resources) => { + const { request, context, response, logger } = resources; + return response.ok({ + body: 'Hello, my route!', + }); + }, +}); +``` + +## Client calling options + +Core HTTP allows certain options to be passed with the request, and you may want to include your own options as well. +To do this, override the second generic argument when calling `createRepositoryClient`. + +```javascript +import { DefaultClientOptions } from '@kbn/server-route-repository-utils'; +import { createRepositoryClient } from '@kbn/server-route-repository-client'; +import type { MyPluginRouteRepository } from '../server/plugin'; + +interface MyPluginClientOptions { + makeSafe: boolean; +} + +export type MyPluginRepositoryClient = + ReturnType<typeof createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions & MyPluginClientOptions>>; + +class MyPlugin implements Plugin { + public setup(core: CoreSetup) { + const myPluginRepositoryClient = + createRepositoryClient<MyPluginRouteRepository, DefaultClientOptions & MyPluginClientOptions>(core); + + myPluginRepositoryClient.fetch('GET /internal/my_plugin/route', { + makeSafe: true, + headers: { + my_plugin_header: 'I am a header', + }, + }).then(console.log); + } +} +``` + +If you don't want your route to have access to the options provided by Core HTTP, you could pass in only `MyPluginClientOptions`. diff --git a/packages/kbn-server-route-repository/index.ts b/packages/kbn-server-route-repository/index.ts index ef942919f1480..e4e43523d25c3 100644 --- a/packages/kbn-server-route-repository/index.ts +++ b/packages/kbn-server-route-repository/index.ts @@ -22,4 +22,6 @@ export type { ServerRoute, RouteParamsRT, RouteState, -} from './src/typings'; + DefaultRouteCreateOptions, + DefaultRouteHandlerResources, +} from '@kbn/server-route-repository-utils'; diff --git a/packages/kbn-server-route-repository/src/create_server_route_factory.ts b/packages/kbn-server-route-repository/src/create_server_route_factory.ts index b506b5263e857..9361e1c12dbd0 100644 --- a/packages/kbn-server-route-repository/src/create_server_route_factory.ts +++ b/packages/kbn-server-route-repository/src/create_server_route_factory.ts @@ -5,16 +5,19 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + import { - ServerRouteCreateOptions, - ServerRouteHandlerResources, RouteParamsRT, ServerRoute, -} from './typings'; + ServerRouteCreateOptions, + ServerRouteHandlerResources, + DefaultRouteHandlerResources, + DefaultRouteCreateOptions, +} from '@kbn/server-route-repository-utils'; export function createServerRouteFactory< - TRouteHandlerResources extends ServerRouteHandlerResources, - TRouteCreateOptions extends ServerRouteCreateOptions + TRouteHandlerResources extends ServerRouteHandlerResources = DefaultRouteHandlerResources, + TRouteCreateOptions extends ServerRouteCreateOptions = DefaultRouteCreateOptions >(): < TEndpoint extends string, TReturnType, diff --git a/packages/kbn-server-route-repository/src/decode_request_params.ts b/packages/kbn-server-route-repository/src/decode_request_params.ts index 0893524a3f9e9..bae6e4d4a0e12 100644 --- a/packages/kbn-server-route-repository/src/decode_request_params.ts +++ b/packages/kbn-server-route-repository/src/decode_request_params.ts @@ -7,10 +7,10 @@ */ import Boom from '@hapi/boom'; import { formatErrors, strictKeysRt } from '@kbn/io-ts-utils'; +import { RouteParamsRT } from '@kbn/server-route-repository-utils'; import { isLeft } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; import { isEmpty, isPlainObject, omitBy } from 'lodash'; -import { RouteParamsRT } from './typings'; interface KibanaRequestParams { body: unknown; diff --git a/packages/kbn-server-route-repository/src/register_routes.test.ts b/packages/kbn-server-route-repository/src/register_routes.test.ts index d6599241f5f72..39180a093835a 100644 --- a/packages/kbn-server-route-repository/src/register_routes.test.ts +++ b/packages/kbn-server-route-repository/src/register_routes.test.ts @@ -88,19 +88,19 @@ describe('registerRoutes', () => { registerRoutes({ core: coreSetup, repository: { - internal: { + 'GET /internal/app/feature': { endpoint: 'GET /internal/app/feature', handler: internalHandler, params: paramsRt, options: internalOptions, }, - public: { + 'GET /api/app/feature version': { endpoint: 'GET /api/app/feature version', handler: publicHandler, params: paramsRt, options: publicOptions, }, - error: { + 'GET /internal/app/feature/error': { endpoint: 'GET /internal/app/feature/error', handler: errorHandler, params: paramsRt, @@ -124,11 +124,6 @@ describe('registerRoutes', () => { expect(internalRoute.options).toEqual(internalOptions); expect(internalRoute.validate).toEqual(routeValidationObject); - const [errorRoute] = get.mock.calls[1]; - expect(errorRoute.path).toEqual('/internal/app/feature/error'); - expect(errorRoute.options).toEqual(internalOptions); - expect(errorRoute.validate).toEqual(routeValidationObject); - expect(getWithVersion).toHaveBeenCalledTimes(1); const [publicRoute] = getWithVersion.mock.calls[0]; expect(publicRoute.path).toEqual('/api/app/feature'); diff --git a/packages/kbn-server-route-repository/src/register_routes.ts b/packages/kbn-server-route-repository/src/register_routes.ts index 4f409adda9577..fcf3c5c3281ee 100644 --- a/packages/kbn-server-route-repository/src/register_routes.ts +++ b/packages/kbn-server-route-repository/src/register_routes.ts @@ -14,10 +14,13 @@ import type { CoreSetup } from '@kbn/core-lifecycle-server'; import type { Logger } from '@kbn/logging'; import * as t from 'io-ts'; import { merge, pick } from 'lodash'; -import { parseEndpoint } from '@kbn/server-route-repository-utils'; +import { + ServerRoute, + ServerRouteCreateOptions, + parseEndpoint, +} from '@kbn/server-route-repository-utils'; import { decodeRequestParams } from './decode_request_params'; import { routeValidationObject } from './route_validation_object'; -import type { ServerRoute, ServerRouteCreateOptions } from './typings'; const CLIENT_CLOSED_REQUEST = { statusCode: 499, @@ -26,7 +29,7 @@ const CLIENT_CLOSED_REQUEST = { }, }; -export function registerRoutes({ +export function registerRoutes<TDependencies extends Record<string, any>>({ core, repository, logger, @@ -35,7 +38,7 @@ export function registerRoutes({ core: CoreSetup; repository: Record<string, ServerRoute<string, any, any, any, ServerRouteCreateOptions>>; logger: Logger; - dependencies: Record<string, any>; + dependencies: TDependencies; }) { const routes = Object.values(repository); diff --git a/packages/kbn-server-route-repository/src/test_types.ts b/packages/kbn-server-route-repository/src/test_types.ts index a55c1014317ac..16447a6ef000f 100644 --- a/packages/kbn-server-route-repository/src/test_types.ts +++ b/packages/kbn-server-route-repository/src/test_types.ts @@ -7,9 +7,9 @@ */ import * as t from 'io-ts'; import { kibanaResponseFactory } from '@kbn/core/server'; +import { EndpointOf, ReturnOf, RouteRepositoryClient } from '@kbn/server-route-repository-utils'; import { createServerRouteFactory } from './create_server_route_factory'; import { decodeRequestParams } from './decode_request_params'; -import { EndpointOf, ReturnOf, RouteRepositoryClient } from './typings'; function assertType<TShape = never>(value: TShape) { return value; diff --git a/packages/kbn-test/src/auth/helper.ts b/packages/kbn-test/src/auth/helper.ts index fc32eab773c8e..3c8374911af8c 100644 --- a/packages/kbn-test/src/auth/helper.ts +++ b/packages/kbn-test/src/auth/helper.ts @@ -10,12 +10,13 @@ import * as fs from 'fs'; import { Role, User } from './types'; export const readCloudUsersFromFile = (filePath: string): Array<[Role, User]> => { + const defaultMessage = `Cannot read roles and email/password from ${filePath}`; if (!fs.existsSync(filePath)) { - throw new Error(`Please define user roles with email/password in ${filePath}`); + throw new Error(`${defaultMessage}: file does not exist`); } const data = fs.readFileSync(filePath, 'utf8'); if (data.length === 0) { - throw new Error(`'${filePath}' is empty: no roles are defined`); + throw new Error(`${defaultMessage}: file is empty`); } return Object.entries(JSON.parse(data)) as Array<[Role, User]>; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index 40beccdfc3c6c..fb12e181e8a1e 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; -import { REPO_ROOT } from '@kbn/repo-info'; import { ToolingLog } from '@kbn/tooling-log'; -import { resolve } from 'path'; import Url from 'url'; import { KbnClient } from '../kbn_client'; import { readCloudUsersFromFile } from './helper'; @@ -32,31 +29,32 @@ export interface HostOptions { export interface SamlSessionManagerOptions { hostOptions: HostOptions; isCloud: boolean; - supportedRoles?: string[]; + supportedRoles?: SupportedRoles; + cloudUsersFilePath: string; log: ToolingLog; } +export interface SupportedRoles { + sourcePath: string; + roles: string[]; +} + /** * Manages cookies associated with user roles */ export class SamlSessionManager { - private readonly DEFAULT_ROLES_FILE_NAME: string = 'role_users.json'; private readonly isCloud: boolean; private readonly kbnHost: string; private readonly kbnClient: KbnClient; private readonly log: ToolingLog; private readonly roleToUserMap: Map<Role, User>; private readonly sessionCache: Map<Role, Session>; - private readonly supportedRoles: string[]; - private readonly userRoleFilePath: string; + private readonly supportedRoles?: SupportedRoles; + private readonly cloudUsersFilePath: string; - constructor(options: SamlSessionManagerOptions, rolesFilename?: string) { + constructor(options: SamlSessionManagerOptions) { this.isCloud = options.isCloud; this.log = options.log; - // if the rolesFilename is provided, respect it. Otherwise use DEFAULT_ROLES_FILE_NAME. - const rolesFile = rolesFilename ? rolesFilename : this.DEFAULT_ROLES_FILE_NAME; - this.log.info(`Using the file ${rolesFile} for the role users`); - this.userRoleFilePath = resolve(REPO_ROOT, '.ftr', rolesFile); const hostOptionsWithoutAuth = { protocol: options.hostOptions.protocol, hostname: options.hostOptions.hostname, @@ -70,9 +68,10 @@ export class SamlSessionManager { auth: `${options.hostOptions.username}:${options.hostOptions.password}`, }), }); + this.cloudUsersFilePath = options.cloudUsersFilePath; this.sessionCache = new Map<Role, Session>(); this.roleToUserMap = new Map<Role, User>(); - this.supportedRoles = options.supportedRoles ?? []; + this.supportedRoles = options.supportedRoles; } /** @@ -81,7 +80,8 @@ export class SamlSessionManager { */ private getCloudUsers = () => { if (this.roleToUserMap.size === 0) { - const data = readCloudUsersFromFile(this.userRoleFilePath); + this.log.info(`Reading cloud user credentials from ${this.cloudUsersFilePath}`); + const data = readCloudUsersFromFile(this.cloudUsersFilePath); for (const [roleName, user] of data) { this.roleToUserMap.set(roleName, user); } @@ -104,11 +104,11 @@ export class SamlSessionManager { } // Validate role before creating SAML session - if (this.supportedRoles.length && !this.supportedRoles.includes(role)) { + if (this.supportedRoles && !this.supportedRoles.roles.includes(role)) { throw new Error( - `Role '${role}' is not defined in the supported list: ${this.supportedRoles.join( + `Role '${role}' is not in the supported list: ${this.supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing` + )}. Add role descriptor in ${this.supportedRoles.sourcePath} to enable it for testing` ); } diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/sesson_manager.test.ts index 1f04620584507..929517e7c5a10 100644 --- a/packages/kbn-test/src/auth/sesson_manager.test.ts +++ b/packages/kbn-test/src/auth/sesson_manager.test.ts @@ -9,17 +9,23 @@ import { ToolingLog } from '@kbn/tooling-log'; import { Cookie } from 'tough-cookie'; import { Session } from './saml_auth'; -import { SamlSessionManager } from './session_manager'; +import { SamlSessionManager, SupportedRoles } from './session_manager'; import * as samlAuth from './saml_auth'; import * as helper from './helper'; import { Role, User, UserProfile } from './types'; import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; +import { resolve } from 'path'; +import { REPO_ROOT } from '@kbn/repo-info'; const log = new ToolingLog(); -const supportedRoles = ['admin', 'editor', 'viewer']; +const supportedRoles: SupportedRoles = { + roles: ['admin', 'editor', 'viewer'], + sourcePath: 'test/roles.yml', +}; const roleViewer = 'viewer'; const roleEditor = 'editor'; +const cloudUsersFilePath = resolve(REPO_ROOT, SERVERLESS_ROLES_ROOT_PATH, 'role_users.json'); const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession'); const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); @@ -58,7 +64,7 @@ describe('SamlSessionManager', () => { hostOptions, isCloud, log, - supportedRoles, + cloudUsersFilePath, }; const testEmail = 'testuser@elastic.com'; const testFullname = 'Test User'; @@ -67,7 +73,7 @@ describe('SamlSessionManager', () => { )!; test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({ hostOptions, log, isCloud }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); @@ -118,10 +124,13 @@ describe('SamlSessionManager', () => { test(`throws error when role is not in 'supportedRoles'`, async () => { const nonExistingRole = 'tester'; - const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join( + const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`; - const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + )}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`; + const samlSessionManager = new SamlSessionManager({ + ...samlSessionManagerOptions, + supportedRoles, + }); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(expectedErrorMessage); @@ -145,11 +154,7 @@ describe('SamlSessionManager', () => { elastic_cloud_user: false, }; getSecurityProfileMock.mockResolvedValueOnce(testData); - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole); await samlSessionManager.getApiCredentialsForRole(nonExistingRole); await samlSessionManager.getUserData(nonExistingRole); @@ -171,7 +176,7 @@ describe('SamlSessionManager', () => { hostOptions, isCloud, log, - supportedRoles, + cloudUsersFilePath, }; const cloudCookieInstance = Cookie.parse( 'sid=cloud_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT' @@ -195,11 +200,7 @@ describe('SamlSessionManager', () => { test('should throw error if TEST_CLOUD_HOST_NAME is not set', async () => { isValidHostnameMock.mockReturnValueOnce(false); - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(roleViewer) ).rejects.toThrow( @@ -220,11 +221,7 @@ describe('SamlSessionManager', () => { }); test('should create an instance of SamlSessionManager', () => { - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); expect(samlSessionManager).toBeInstanceOf(SamlSessionManager); }); @@ -276,10 +273,13 @@ describe('SamlSessionManager', () => { test(`throws error for non-existing role when 'supportedRoles' is defined`, async () => { const nonExistingRole = 'tester'; - const expectedErrorMessage = `Role '${nonExistingRole}' is not defined in the supported list: ${supportedRoles.join( + const expectedErrorMessage = `Role '${nonExistingRole}' is not in the supported list: ${supportedRoles.roles.join( ', ' - )}. Update roles resource file in ${SERVERLESS_ROLES_ROOT_PATH} to enable it for testing`; - const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + )}. Add role descriptor in ${supportedRoles.sourcePath} to enable it for testing`; + const samlSessionManager = new SamlSessionManager({ + ...samlSessionManagerOptions, + supportedRoles, + }); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(expectedErrorMessage); @@ -294,11 +294,7 @@ describe('SamlSessionManager', () => { test(`throws error for non-existing role when 'supportedRoles' is not defined`, async () => { const nonExistingRole = 'tester'; - const samlSessionManager = new SamlSessionManager({ - hostOptions, - log, - isCloud, - }); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); await expect( samlSessionManager.getInteractiveUserSessionCookieWithRoleScope(nonExistingRole) ).rejects.toThrow(`User with '${nonExistingRole}' role is not defined`); diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts index 7176d21b8f0b3..309cac56ca30c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/run_check_ftr_configs_cli.ts @@ -125,7 +125,8 @@ export async function runCheckFtrConfigsCli() { const invalid = possibleConfigs.filter((path) => !allFtrConfigs.includes(path)); if (invalid.length) { - const invalidList = invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); + const invalidList = + ' - ' + invalid.map((path) => Path.relative(REPO_ROOT, path)).join('\n - '); log.error( `The following files look like FTR configs which are not listed in one of manifest files:\n${invalidList}\n Make sure to add your new FTR config to the correct manifest file.\n diff --git a/packages/kbn-text-based-editor/index.ts b/packages/kbn-text-based-editor/index.ts index 01f106af6639f..1933edfb919d9 100644 --- a/packages/kbn-text-based-editor/index.ts +++ b/packages/kbn-text-based-editor/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export type { TextBasedLanguagesEditorProps } from './src/text_based_languages_editor'; +export type { TextBasedLanguagesEditorProps } from './src/types'; export { fetchFieldsFromESQL } from './src/fetch_fields_from_esql'; import { TextBasedLanguagesEditor } from './src/text_based_languages_editor'; 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 f6e0a751e7755..004cc3729dfa6 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 @@ -28,16 +28,15 @@ The TextBasedLanguagesEditor component is a reusable component and can be used t <Canvas> <Story - name='compact mode' + name='expanded mode' args={ { query: { esql: 'from dataview | keep field1, field2' }, - isCodeEditorExpanded:false, 'data-test-subj':'test-id' } } argTypes={ - { onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }} + { onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }} } > {Template.bind({})} @@ -52,7 +51,6 @@ When there are errors to the query the UI displays the errors to the editor: args={ { query: { esql: 'from dataview | keep field1, field2' }, - isCodeEditorExpanded:false, 'data-test-subj':'test-id', errors: [ new Error( @@ -62,69 +60,7 @@ When there are errors to the query the UI displays the errors to the editor: } } argTypes={ - { onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }} - } - > - {Template.bind({})} - </Story> -</Canvas> - -When there the query is long and the editor is on the compact view: - -<Canvas> - <Story - name='with long query' - args={ - { - 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', - } - } - argTypes={ - { onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }} - } - > - {Template.bind({})} - </Story> -</Canvas> - - -The editor also works on the expanded mode: - -<Canvas> - <Story - name='on expanded mode' - args={ - { - query: { esql: 'from dataview | keep field1, field2' }, - isCodeEditorExpanded:true, - 'data-test-subj':'test-id', - } - } - argTypes={ - { onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }} - } - > - {Template.bind({})} - </Story> -</Canvas> - -The editor also works on the expanded mode with the minimize button hidden: - -<Canvas> - <Story - name='on expanded mode with hidden the minimize button' - args={ - { - query: { esql: 'from dataview | keep field1, field2' }, - isCodeEditorExpanded:true, - hideMinimizeButton: true, - 'data-test-subj':'test-id', - } - } - argTypes={ - { onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }, expandCodeEditor: { action: 'expanded' }} + { onTextLangQueryChange: { action: 'changed' }, onTextLangQuerySubmit: { action: 'submitted' }} } > {Template.bind({})} @@ -135,4 +71,4 @@ The editor also works on the expanded mode with the minimize button hidden: The component exposes the following properties: -<ArgsTable story="compact mode"/> \ No newline at end of file +<ArgsTable story="expanded mode"/> \ No newline at end of file diff --git a/packages/kbn-text-based-editor/src/ecs_metadata_helper.test.ts b/packages/kbn-text-based-editor/src/ecs_metadata_helper.test.ts index aa60c10a4a4a6..511d14d2da3f0 100644 --- a/packages/kbn-text-based-editor/src/ecs_metadata_helper.test.ts +++ b/packages/kbn-text-based-editor/src/ecs_metadata_helper.test.ts @@ -7,15 +7,15 @@ */ import { getColumnsWithMetadata } from './ecs_metadata_helper'; -import type { DatatableColumnType } from '@kbn/expressions-plugin/common'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; +import { ESQLRealField } from '@kbn/esql-validation-autocomplete'; describe('getColumnsWithMetadata', () => { it('should return original columns if fieldsMetadata is not provided', async () => { - const columns = [ - { name: 'ecs.version', type: 'string' as DatatableColumnType }, - { name: 'field1', type: 'string' as DatatableColumnType }, - { name: 'field2', type: 'number' as DatatableColumnType }, + const columns: ESQLRealField[] = [ + { name: 'ecs.version', type: 'keyword' }, + { name: 'field1', type: 'text' }, + { name: 'field2', type: 'double' }, ]; const result = await getColumnsWithMetadata(columns); @@ -23,17 +23,17 @@ describe('getColumnsWithMetadata', () => { }); it('should return columns with metadata if both name and type match with ECS fields', async () => { - const columns = [ - { name: 'ecs.field', type: 'string' as DatatableColumnType }, - { name: 'ecs.fakeBooleanField', type: 'boolean' as DatatableColumnType }, - { name: 'field2', type: 'number' as DatatableColumnType }, + const columns: ESQLRealField[] = [ + { name: 'ecs.field', type: 'text' }, + { name: 'ecs.fakeBooleanField', type: 'boolean' }, + { name: 'field2', type: 'double' }, ]; const fieldsMetadata = { getClient: jest.fn().mockResolvedValue({ find: jest.fn().mockResolvedValue({ fields: { 'ecs.version': { description: 'ECS version field', type: 'keyword' }, - 'ecs.field': { description: 'ECS field description', type: 'keyword' }, + 'ecs.field': { description: 'ECS field description', type: 'text' }, 'ecs.fakeBooleanField': { description: 'ECS fake boolean field description', type: 'keyword', @@ -48,19 +48,19 @@ describe('getColumnsWithMetadata', () => { expect(result).toEqual([ { name: 'ecs.field', - type: 'string', + type: 'text', metadata: { description: 'ECS field description' }, }, { name: 'ecs.fakeBooleanField', type: 'boolean' }, - { name: 'field2', type: 'number' }, + { name: 'field2', type: 'double' }, ]); }); it('should handle keyword suffix correctly', async () => { - const columns = [ - { name: 'ecs.version', type: 'string' as DatatableColumnType }, - { name: 'ecs.version.keyword', type: 'string' as DatatableColumnType }, - { name: 'field2', type: 'number' as DatatableColumnType }, + const columns: ESQLRealField[] = [ + { name: 'ecs.version', type: 'keyword' }, + { name: 'ecs.version.keyword', type: 'keyword' }, + { name: 'field2', type: 'double' }, ]; const fieldsMetadata = { getClient: jest.fn().mockResolvedValue({ @@ -75,13 +75,13 @@ describe('getColumnsWithMetadata', () => { const result = await getColumnsWithMetadata(columns, fieldsMetadata); expect(result).toEqual([ - { name: 'ecs.version', type: 'string', metadata: { description: 'ECS version field' } }, + { name: 'ecs.version', type: 'keyword', metadata: { description: 'ECS version field' } }, { name: 'ecs.version.keyword', - type: 'string', + type: 'keyword', metadata: { description: 'ECS version field' }, }, - { name: 'field2', type: 'number' }, + { name: 'field2', type: 'double' }, ]); }); }); diff --git a/packages/kbn-text-based-editor/src/ecs_metadata_helper.ts b/packages/kbn-text-based-editor/src/ecs_metadata_helper.ts index 687999350be0e..14198ab9d911a 100644 --- a/packages/kbn-text-based-editor/src/ecs_metadata_helper.ts +++ b/packages/kbn-text-based-editor/src/ecs_metadata_helper.ts @@ -7,7 +7,6 @@ */ import type { ESQLRealField } from '@kbn/esql-validation-autocomplete'; -import { esFieldTypeToKibanaFieldType } from '@kbn/field-types'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import { chunk } from 'lodash'; @@ -45,11 +44,7 @@ export async function getColumnsWithMetadata( const metadata = fields.fields[removeKeywordSuffix(c.name)]; // Need to convert metadata's type (e.g. keyword) to ES|QL type (e.g. string) to check if they are the same - if ( - !metadata || - (metadata?.type && esFieldTypeToKibanaFieldType(metadata.type) !== c.type) - ) - return c; + if (!metadata || (metadata?.type && metadata.type !== c.type)) return c; return { ...c, metadata: { description: metadata.description }, diff --git a/packages/kbn-text-based-editor/src/editor_footer.tsx b/packages/kbn-text-based-editor/src/editor_footer.tsx deleted file mode 100644 index ca4d30c9c5071..0000000000000 --- a/packages/kbn-text-based-editor/src/editor_footer.tsx +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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, { memo, useState, useCallback } from 'react'; - -import { i18n } from '@kbn/i18n'; -import { - EuiText, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - useEuiTheme, - EuiLink, - EuiCode, - EuiButtonIcon, - EuiToolTip, -} from '@elastic/eui'; -import { Interpolation, Theme, css } from '@emotion/react'; -import type { MonacoMessage } from './helpers'; -import { ErrorsWarningsFooterPopover } from './errors_warnings_popover'; -import { QueryHistoryAction, QueryHistory } from './query_history'; - -const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; -const COMMAND_KEY = isMac ? '⌘' : '^'; -const FEEDBACK_LINK = 'https://ela.st/esql-feedback'; - -export function SubmitFeedbackComponent({ isSpaceReduced }: { isSpaceReduced?: boolean }) { - const { euiTheme } = useEuiTheme(); - return ( - <> - {isSpaceReduced && ( - <EuiFlexItem grow={false}> - <EuiLink - href={FEEDBACK_LINK} - external={false} - target="_blank" - data-test-subj="TextBasedLangEditor-feedback-link" - > - <EuiToolTip - position="top" - content={i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.submitFeedback', - { - defaultMessage: 'Submit feedback', - } - )} - > - <EuiIcon - type="editorComment" - color="primary" - size="m" - css={css` - margin-right: ${euiTheme.size.s}; - `} - /> - </EuiToolTip> - </EuiLink> - </EuiFlexItem> - )} - {!isSpaceReduced && ( - <> - <EuiFlexItem grow={false}> - <EuiIcon type="editorComment" color="primary" size="s" /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiLink - href={FEEDBACK_LINK} - external={false} - target="_blank" - css={css` - font-size: 12px; - margin-right: ${euiTheme.size.m}; - `} - data-test-subj="TextBasedLangEditor-feedback-link" - > - {isSpaceReduced - ? i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.feedback', { - defaultMessage: 'Feedback', - }) - : i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.submitFeedback', { - defaultMessage: 'Submit feedback', - })} - </EuiLink> - </EuiFlexItem> - </> - )} - </> - ); -} - -interface EditorFooterProps { - lines: number; - styles: { - bottomContainer: Interpolation<Theme>; - historyContainer: Interpolation<Theme>; - }; - errors?: MonacoMessage[]; - warnings?: MonacoMessage[]; - detectedTimestamp?: string; - onErrorClick: (error: MonacoMessage) => void; - runQuery: () => void; - updateQuery: (qs: string) => void; - isHistoryOpen: boolean; - setIsHistoryOpen: (status: boolean) => void; - measuredContainerWidth: number; - hideRunQueryText?: boolean; - disableSubmitAction?: boolean; - editorIsInline?: boolean; - isSpaceReduced?: boolean; - isLoading?: boolean; - allowQueryCancellation?: boolean; - hideTimeFilterInfo?: boolean; - hideQueryHistory?: boolean; - refetchHistoryItems?: boolean; - isInCompactMode?: boolean; - queryHasChanged?: boolean; -} - -export const EditorFooter = memo(function EditorFooter({ - lines, - styles, - errors, - warnings, - detectedTimestamp, - onErrorClick, - runQuery, - updateQuery, - hideRunQueryText, - disableSubmitAction, - editorIsInline, - isSpaceReduced, - isLoading, - allowQueryCancellation, - hideTimeFilterInfo, - isHistoryOpen, - setIsHistoryOpen, - hideQueryHistory, - refetchHistoryItems, - isInCompactMode, - queryHasChanged, - measuredContainerWidth, -}: EditorFooterProps) { - const [isErrorPopoverOpen, setIsErrorPopoverOpen] = useState(false); - const [isWarningPopoverOpen, setIsWarningPopoverOpen] = useState(false); - const onUpdateAndSubmit = useCallback( - (qs: string) => { - // update the query first - updateQuery(qs); - // submit the query with some latency - // if I do it immediately there is some race condition until - // the state is updated and it won't be sumbitted correctly - setTimeout(() => { - runQuery(); - }, 300); - }, - [runQuery, updateQuery] - ); - - return ( - <EuiFlexGroup - gutterSize="none" - responsive={false} - direction="column" - css={css` - width: 100%; - `} - > - <EuiFlexItem grow={false}> - <EuiFlexGroup - gutterSize="s" - justifyContent="spaceBetween" - data-test-subj="TextBasedLangEditor-footer" - css={styles.bottomContainer} - responsive={false} - > - <EuiFlexItem grow={false}> - <EuiFlexGroup gutterSize="s" responsive={false} alignItems="center"> - <EuiFlexItem grow={false} style={{ marginRight: '8px' }}> - <EuiText - size="xs" - color="subdued" - data-test-subj="TextBasedLangEditor-footer-lines" - > - <p> - {i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.lineCount', { - defaultMessage: '{count} {count, plural, one {line} other {lines}}', - values: { count: lines }, - })} - </p> - </EuiText> - </EuiFlexItem> - {/* If there is no space and no @timestamp detected hide the information */} - {(detectedTimestamp || !isSpaceReduced) && !hideTimeFilterInfo && ( - <EuiFlexItem grow={false} style={{ marginRight: '16px' }}> - <EuiFlexGroup gutterSize="xs" responsive={false} alignItems="center"> - <EuiFlexItem grow={false}> - <EuiText - size="xs" - color="subdued" - data-test-subj="TextBasedLangEditor-date-info" - > - <p> - {isSpaceReduced - ? '@timestamp' - : detectedTimestamp - ? i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.timestampDetected', - { - defaultMessage: '{detectedTimestamp} found', - values: { detectedTimestamp }, - } - ) - : i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.timestampNotDetected', - { - defaultMessage: '@timestamp not found', - } - )} - </p> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - )} - {errors && errors.length > 0 && ( - <ErrorsWarningsFooterPopover - isPopoverOpen={isErrorPopoverOpen} - items={errors} - type="error" - setIsPopoverOpen={(isOpen) => { - if (isOpen) { - setIsWarningPopoverOpen(false); - } - setIsErrorPopoverOpen(isOpen); - }} - onErrorClick={onErrorClick} - /> - )} - {warnings && warnings.length > 0 && ( - <ErrorsWarningsFooterPopover - isPopoverOpen={isWarningPopoverOpen} - items={warnings} - type="warning" - setIsPopoverOpen={(isOpen) => { - if (isOpen) { - setIsErrorPopoverOpen(false); - } - setIsWarningPopoverOpen(isOpen); - }} - onErrorClick={onErrorClick} - /> - )} - </EuiFlexGroup> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiFlexGroup gutterSize="xs" responsive={false} alignItems="center"> - {!Boolean(editorIsInline) && ( - <> - <SubmitFeedbackComponent /> - {!hideQueryHistory && ( - <QueryHistoryAction - toggleHistory={() => setIsHistoryOpen(!isHistoryOpen)} - isHistoryOpen={isHistoryOpen} - /> - )} - </> - )} - {!hideRunQueryText && ( - <EuiFlexItem grow={false}> - <EuiFlexGroup gutterSize="xs" responsive={false} alignItems="center"> - <EuiFlexItem grow={false}> - <EuiText - size="xs" - color="subdued" - data-test-subj="TextBasedLangEditor-run-query" - > - <p> - {i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.runQuery', - { - defaultMessage: 'Run query', - } - )} - </p> - </EuiText> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiCode - transparentBackground - css={css` - font-size: 12px; - `} - >{`${COMMAND_KEY} + Enter`}</EuiCode> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - )} - </EuiFlexGroup> - </EuiFlexItem> - {Boolean(editorIsInline) && ( - <> - <EuiFlexItem grow={false}> - <EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center"> - <SubmitFeedbackComponent isSpaceReduced={true} /> - {!hideQueryHistory && ( - <QueryHistoryAction - toggleHistory={() => setIsHistoryOpen(!isHistoryOpen)} - isHistoryOpen={isHistoryOpen} - isSpaceReduced={true} - /> - )} - <EuiFlexItem grow={false}> - <EuiToolTip - position="top" - content={i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.runQuery', - { - defaultMessage: 'Run query', - } - )} - > - <EuiButtonIcon - display="base" - color={queryHasChanged ? 'success' : 'primary'} - onClick={runQuery} - iconType={ - allowQueryCancellation && isLoading - ? 'cross' - : queryHasChanged - ? 'play' - : 'refresh' - } - size="s" - isLoading={isLoading && !allowQueryCancellation} - isDisabled={Boolean(disableSubmitAction && !allowQueryCancellation)} - data-test-subj="TextBasedLangEditor-run-query-button" - aria-label={ - allowQueryCancellation && isLoading - ? i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.cancel', - { - defaultMessage: 'Cancel', - } - ) - : i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.runQuery', - { - defaultMessage: 'Run query', - } - ) - } - /> - </EuiToolTip> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </> - )} - </EuiFlexGroup> - </EuiFlexItem> - {isHistoryOpen && ( - <EuiFlexItem grow={false}> - <QueryHistory - containerCSS={styles.historyContainer} - onUpdateAndSubmit={onUpdateAndSubmit} - containerWidth={measuredContainerWidth} - refetchHistoryItems={refetchHistoryItems} - isInCompactMode={isInCompactMode} - /> - </EuiFlexItem> - )} - </EuiFlexGroup> - ); -}); diff --git a/packages/kbn-text-based-editor/src/errors_warnings_popover.tsx b/packages/kbn-text-based-editor/src/editor_footer/errors_warnings_popover.tsx similarity index 77% rename from packages/kbn-text-based-editor/src/errors_warnings_popover.tsx rename to packages/kbn-text-based-editor/src/editor_footer/errors_warnings_popover.tsx index 99a44efbe6571..f2d21d6b2a53d 100644 --- a/packages/kbn-text-based-editor/src/errors_warnings_popover.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer/errors_warnings_popover.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiText, @@ -17,13 +17,12 @@ import { EuiPopoverTitle, EuiDescriptionList, EuiDescriptionListDescription, - EuiBadge, } from '@elastic/eui'; -import { css, Interpolation, Theme } from '@emotion/react'; +import { css } from '@emotion/react'; import { css as classNameCss } from '@emotion/css'; -import type { MonacoMessage } from './helpers'; +import type { MonacoMessage } from '../helpers'; -export const getConstsByType = (type: 'error' | 'warning', count: number) => { +const getConstsByType = (type: 'error' | 'warning', count: number) => { if (type === 'error') { return { color: 'danger', @@ -49,7 +48,7 @@ export const getConstsByType = (type: 'error' | 'warning', count: number) => { } }; -export function ErrorsWarningsContent({ +function ErrorsWarningsContent({ items, type, onErrorClick, @@ -100,48 +99,6 @@ export function ErrorsWarningsContent({ ); } -export function ErrorsWarningsCompactViewPopover({ - items, - type, - onErrorClick, - popoverCSS, -}: { - items: MonacoMessage[]; - type: 'error' | 'warning'; - onErrorClick: (error: MonacoMessage) => void; - popoverCSS: Interpolation<Theme>; -}) { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const { color, message } = getConstsByType(type, items.length); - return ( - <EuiPopover - button={ - <EuiBadge - color={color} - onClick={() => setIsPopoverOpen(true)} - onClickAriaLabel={message} - iconType={type} - iconSide="left" - data-test-subj={`TextBasedLangEditor-inline-${type}-badge`} - title={message} - css={css` - cursor: pointer; - `} - > - {items.length} - </EuiBadge> - } - css={popoverCSS} - ownFocus={false} - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} - data-test-subj={`TextBasedLangEditor-inline-${type}-popover`} - > - <ErrorsWarningsContent items={items} type={type} onErrorClick={onErrorClick} /> - </EuiPopover> - ); -} - export function ErrorsWarningsFooterPopover({ isPopoverOpen, items, diff --git a/packages/kbn-text-based-editor/src/editor_footer/feedback_component.tsx b/packages/kbn-text-based-editor/src/editor_footer/feedback_component.tsx new file mode 100644 index 0000000000000..b67dbfc05fc16 --- /dev/null +++ b/packages/kbn-text-based-editor/src/editor_footer/feedback_component.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 { EuiFlexItem, EuiIcon, useEuiTheme, EuiLink, EuiToolTip } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FEEDBACK_LINK } from '@kbn/esql-utils'; + +export function SubmitFeedbackComponent({ isSpaceReduced }: { isSpaceReduced?: boolean }) { + const { euiTheme } = useEuiTheme(); + return ( + <> + {isSpaceReduced && ( + <EuiFlexItem grow={false}> + <EuiLink + href={FEEDBACK_LINK} + external={false} + target="_blank" + data-test-subj="TextBasedLangEditor-feedback-link" + > + <EuiToolTip + position="top" + content={i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.feedback', { + defaultMessage: 'Feedback', + })} + > + <EuiIcon + type="editorComment" + color="primary" + size="m" + css={css` + margin-right: ${euiTheme.size.s}; + `} + /> + </EuiToolTip> + </EuiLink> + </EuiFlexItem> + )} + {!isSpaceReduced && ( + <> + <EuiFlexItem grow={false}> + <EuiIcon type="editorComment" color="primary" size="s" /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiLink + href={FEEDBACK_LINK} + external={false} + target="_blank" + css={css` + font-size: 12px; + margin-right: ${euiTheme.size.m}; + `} + data-test-subj="TextBasedLangEditor-feedback-link" + > + {i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.submitFeedback', { + defaultMessage: 'Submit feedback', + })} + </EuiLink> + </EuiFlexItem> + </> + )} + </> + ); +} diff --git a/packages/kbn-text-based-editor/src/editor_footer/index.tsx b/packages/kbn-text-based-editor/src/editor_footer/index.tsx new file mode 100644 index 0000000000000..dcab34c0845a3 --- /dev/null +++ b/packages/kbn-text-based-editor/src/editor_footer/index.tsx @@ -0,0 +1,330 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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, { memo, useState, useCallback, useEffect } from 'react'; + +import { i18n } from '@kbn/i18n'; +import { EuiText, EuiFlexGroup, EuiFlexItem, EuiCode } from '@elastic/eui'; +import { Interpolation, Theme, css } from '@emotion/react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { + LanguageDocumentationPopover, + type LanguageDocumentationSections, +} from '@kbn/language-documentation-popover'; +import { type MonacoMessage, getDocumentationSections } from '../helpers'; +import { ErrorsWarningsFooterPopover } from './errors_warnings_popover'; +import { QueryHistoryAction, QueryHistory } from './query_history'; +import { SubmitFeedbackComponent } from './feedback_component'; +import { QueryWrapComponent } from './query_wrap_component'; +import type { TextBasedEditorDeps } from '../types'; + +const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; +const COMMAND_KEY = isMac ? '⌘' : '^'; + +interface EditorFooterProps { + lines: number; + styles: { + bottomContainer: Interpolation<Theme>; + historyContainer: Interpolation<Theme>; + }; + code: string; + errors?: MonacoMessage[]; + warnings?: MonacoMessage[]; + detectedTimestamp?: string; + onErrorClick: (error: MonacoMessage) => void; + runQuery: () => void; + updateQuery: (qs: string) => void; + isHistoryOpen: boolean; + setIsHistoryOpen: (status: boolean) => void; + isHelpMenuOpen: boolean; + setIsHelpMenuOpen: (status: boolean) => void; + measuredContainerWidth: number; + hideRunQueryText?: boolean; + editorIsInline?: boolean; + isSpaceReduced?: boolean; + hideTimeFilterInfo?: boolean; + hideQueryHistory?: boolean; + refetchHistoryItems?: boolean; + isInCompactMode?: boolean; +} + +export const EditorFooter = memo(function EditorFooter({ + lines, + styles, + errors, + warnings, + detectedTimestamp, + onErrorClick, + runQuery, + updateQuery, + hideRunQueryText, + editorIsInline, + isSpaceReduced, + hideTimeFilterInfo, + isHistoryOpen, + setIsHistoryOpen, + hideQueryHistory, + refetchHistoryItems, + isInCompactMode, + measuredContainerWidth, + code, + isHelpMenuOpen, + setIsHelpMenuOpen, +}: EditorFooterProps) { + const kibana = useKibana<TextBasedEditorDeps>(); + const { docLinks } = kibana.services; + + const [isErrorPopoverOpen, setIsErrorPopoverOpen] = useState(false); + const [isWarningPopoverOpen, setIsWarningPopoverOpen] = useState(false); + const [documentationSections, setDocumentationSections] = + useState<LanguageDocumentationSections>(); + + const onUpdateAndSubmit = useCallback( + (qs: string) => { + // update the query first + updateQuery(qs); + // submit the query with some latency + // if I do it immediately there is some race condition until + // the state is updated and it won't be sumbitted correctly + setTimeout(() => { + runQuery(); + }, 300); + }, + [runQuery, updateQuery] + ); + + useEffect(() => { + async function getDocumentation() { + const sections = await getDocumentationSections('esql'); + setDocumentationSections(sections); + } + if (!documentationSections) { + getDocumentation(); + } + }, [documentationSections]); + + return ( + <EuiFlexGroup + gutterSize="none" + responsive={false} + direction="column" + css={css` + width: 100%; + `} + > + <EuiFlexItem grow={false}> + <EuiFlexGroup + gutterSize="s" + justifyContent="spaceBetween" + data-test-subj="TextBasedLangEditor-footer" + css={styles.bottomContainer} + responsive={false} + > + <EuiFlexItem grow={false}> + <EuiFlexGroup gutterSize="s" responsive={false} alignItems="center"> + <QueryWrapComponent code={code} updateQuery={updateQuery} /> + <EuiFlexItem grow={false} style={{ marginRight: '8px' }}> + <EuiText + size="xs" + color="subdued" + data-test-subj="TextBasedLangEditor-footer-lines" + > + <p> + {i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.lineCount', { + defaultMessage: '{count} {count, plural, one {line} other {lines}}', + values: { count: lines }, + })} + </p> + </EuiText> + </EuiFlexItem> + {/* If there is no space and no @timestamp detected hide the information */} + {(detectedTimestamp || !isSpaceReduced) && !hideTimeFilterInfo && ( + <EuiFlexItem grow={false} style={{ marginRight: '16px' }}> + <EuiFlexGroup gutterSize="xs" responsive={false} alignItems="center"> + <EuiFlexItem grow={false}> + <EuiText + size="xs" + color="subdued" + data-test-subj="TextBasedLangEditor-date-info" + > + <p> + {isSpaceReduced + ? '@timestamp' + : detectedTimestamp + ? i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.timestampDetected', + { + defaultMessage: '{detectedTimestamp} found', + values: { detectedTimestamp }, + } + ) + : i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.timestampNotDetected', + { + defaultMessage: '@timestamp not found', + } + )} + </p> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + )} + {errors && errors.length > 0 && ( + <ErrorsWarningsFooterPopover + isPopoverOpen={isErrorPopoverOpen} + items={errors} + type="error" + setIsPopoverOpen={(isOpen) => { + if (isOpen) { + setIsWarningPopoverOpen(false); + } + setIsErrorPopoverOpen(isOpen); + }} + onErrorClick={onErrorClick} + /> + )} + {warnings && warnings.length > 0 && ( + <ErrorsWarningsFooterPopover + isPopoverOpen={isWarningPopoverOpen} + items={warnings} + type="warning" + setIsPopoverOpen={(isOpen) => { + if (isOpen) { + setIsErrorPopoverOpen(false); + } + setIsWarningPopoverOpen(isOpen); + }} + onErrorClick={onErrorClick} + /> + )} + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiFlexGroup gutterSize="xs" responsive={false} alignItems="center"> + {!Boolean(editorIsInline) && ( + <> + <SubmitFeedbackComponent /> + {!hideQueryHistory && ( + <QueryHistoryAction + toggleHistory={() => setIsHistoryOpen(!isHistoryOpen)} + isHistoryOpen={isHistoryOpen} + /> + )} + </> + )} + {!hideRunQueryText && ( + <EuiFlexItem grow={false}> + <EuiFlexGroup gutterSize="xs" responsive={false} alignItems="center"> + <EuiFlexItem grow={false}> + <EuiText + size="xs" + color="subdued" + data-test-subj="TextBasedLangEditor-run-query" + > + <p> + {i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.runQuery', + { + defaultMessage: 'Run query', + } + )} + </p> + </EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiCode + transparentBackground + css={css` + font-size: 12px; + `} + >{`${COMMAND_KEY} + Enter`}</EuiCode> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + )} + {documentationSections && !editorIsInline && ( + <EuiFlexItem grow={false}> + <LanguageDocumentationPopover + language="ES|QL" + sections={documentationSections} + searchInDescription + linkToDocumentation={docLinks?.links?.query?.queryESQL ?? ''} + buttonProps={{ + color: 'text', + size: 'xs', + 'data-test-subj': 'TextBasedLangEditor-documentation', + 'aria-label': i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel', + { + defaultMessage: 'Documentation', + } + ), + }} + isHelpMenuOpen={isHelpMenuOpen} + onHelpMenuVisibilityChange={setIsHelpMenuOpen} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiFlexItem> + {Boolean(editorIsInline) && ( + <> + <EuiFlexItem grow={false}> + <EuiFlexGroup responsive={false} gutterSize="xs" alignItems="center"> + <SubmitFeedbackComponent isSpaceReduced={true} /> + {!hideQueryHistory && ( + <QueryHistoryAction + toggleHistory={() => setIsHistoryOpen(!isHistoryOpen)} + isHistoryOpen={isHistoryOpen} + isSpaceReduced={true} + /> + )} + {documentationSections && ( + <EuiFlexItem grow={false}> + <LanguageDocumentationPopover + language="ES|QL" + sections={documentationSections} + searchInDescription + linkToDocumentation={docLinks?.links?.query?.queryESQL ?? ''} + buttonProps={{ + color: 'text', + size: 'xs', + 'data-test-subj': 'TextBasedLangEditor-documentation', + 'aria-label': i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel', + { + defaultMessage: 'Documentation', + } + ), + }} + isHelpMenuOpen={isHelpMenuOpen} + onHelpMenuVisibilityChange={setIsHelpMenuOpen} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiFlexItem> + </> + )} + </EuiFlexGroup> + </EuiFlexItem> + {isHistoryOpen && ( + <EuiFlexItem grow={false}> + <QueryHistory + containerCSS={styles.historyContainer} + onUpdateAndSubmit={onUpdateAndSubmit} + containerWidth={measuredContainerWidth} + refetchHistoryItems={refetchHistoryItems} + isInCompactMode={isInCompactMode} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + ); +}); diff --git a/packages/kbn-text-based-editor/src/query_history.test.tsx b/packages/kbn-text-based-editor/src/editor_footer/query_history.test.tsx similarity index 98% rename from packages/kbn-text-based-editor/src/query_history.test.tsx rename to packages/kbn-text-based-editor/src/editor_footer/query_history.test.tsx index d1b356e31eaa1..86b3bb44fe292 100644 --- a/packages/kbn-text-based-editor/src/query_history.test.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer/query_history.test.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { QueryHistoryAction, getTableColumns, QueryHistory, QueryColumn } from './query_history'; import { render, screen } from '@testing-library/react'; -jest.mock('./history_local_storage', () => { - const module = jest.requireActual('./history_local_storage'); +jest.mock('../history_local_storage', () => { + const module = jest.requireActual('../history_local_storage'); return { ...module, getHistoryItems: () => [ diff --git a/packages/kbn-text-based-editor/src/query_history.tsx b/packages/kbn-text-based-editor/src/editor_footer/query_history.tsx similarity index 99% rename from packages/kbn-text-based-editor/src/query_history.tsx rename to packages/kbn-text-based-editor/src/editor_footer/query_history.tsx index 9f0f82deb3db7..8706ea233e462 100644 --- a/packages/kbn-text-based-editor/src/query_history.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer/query_history.tsx @@ -24,7 +24,7 @@ import { euiScrollBarStyles, } from '@elastic/eui'; import { css, Interpolation, Theme } from '@emotion/react'; -import { type QueryHistoryItem, getHistoryItems } from './history_local_storage'; +import { type QueryHistoryItem, getHistoryItems } from '../history_local_storage'; import { getReducedSpaceStyling, swapArrayElements } from './query_history_helpers'; const CONTAINER_MAX_HEIGHT_EXPANDED = 190; diff --git a/packages/kbn-text-based-editor/src/query_history_helpers.test.ts b/packages/kbn-text-based-editor/src/editor_footer/query_history_helpers.test.ts similarity index 100% rename from packages/kbn-text-based-editor/src/query_history_helpers.test.ts rename to packages/kbn-text-based-editor/src/editor_footer/query_history_helpers.test.ts diff --git a/packages/kbn-text-based-editor/src/query_history_helpers.ts b/packages/kbn-text-based-editor/src/editor_footer/query_history_helpers.ts similarity index 96% rename from packages/kbn-text-based-editor/src/query_history_helpers.ts rename to packages/kbn-text-based-editor/src/editor_footer/query_history_helpers.ts index 97c9ceac454c6..4c868be34149f 100644 --- a/packages/kbn-text-based-editor/src/query_history_helpers.ts +++ b/packages/kbn-text-based-editor/src/editor_footer/query_history_helpers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import type { EuiBasicTableColumn } from '@elastic/eui'; -import type { QueryHistoryItem } from './history_local_storage'; +import type { QueryHistoryItem } from '../history_local_storage'; export const getReducedSpaceStyling = () => { return ` diff --git a/packages/kbn-text-based-editor/src/editor_footer/query_wrap_component.tsx b/packages/kbn-text-based-editor/src/editor_footer/query_wrap_component.tsx new file mode 100644 index 0000000000000..fec79559a38ae --- /dev/null +++ b/packages/kbn-text-based-editor/src/editor_footer/query_wrap_component.tsx @@ -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 and the Server Side Public License, v 1; you may not 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 { i18n } from '@kbn/i18n'; +import { EuiFlexItem, EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { getWrappedInPipesCode } from '../helpers'; + +export function QueryWrapComponent({ + code, + updateQuery, +}: { + code: string; + updateQuery: (qs: string) => void; +}) { + const isWrappedInPipes = useMemo(() => { + const pipes = code.split('|'); + const pipesWithNewLine = code?.split('\n|'); + return pipes?.length === pipesWithNewLine?.length; + }, [code]); + + return ( + <EuiFlexItem grow={false}> + <EuiToolTip + position="top" + content={ + isWrappedInPipes + ? i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel', + { + defaultMessage: 'Remove line breaks on pipes', + } + ) + : i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel', { + defaultMessage: 'Add line breaks on pipes', + }) + } + > + <EuiButtonIcon + iconType={isWrappedInPipes ? 'pipeNoBreaks' : 'pipeBreaks'} + color="text" + size="xs" + data-test-subj="TextBasedLangEditor-toggleWordWrap" + aria-label={ + isWrappedInPipes + ? i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel', + { + defaultMessage: 'Remove line breaks on pipes', + } + ) + : i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel', + { + defaultMessage: 'Add line breaks on pipes', + } + ) + } + onClick={() => { + const updatedCode = getWrappedInPipesCode(code, isWrappedInPipes); + if (code !== updatedCode) { + updateQuery(updatedCode); + } + }} + /> + </EuiToolTip> + </EuiFlexItem> + ); +} diff --git a/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx index 6dbcbad824ddd..05cb7b452c18b 100644 --- a/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx +++ b/packages/kbn-text-based-editor/src/esql_documentation_sections.tsx @@ -2176,6 +2176,40 @@ export const functions = { ROW a=[2, 1] | EVAL min_a = MV_MIN(a) \`\`\` + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + ignoreTag: true, + } + )} + /> + ), + }, + // Do not edit manually... automatically generated by scripts/generate_esql_docs.ts + { + label: i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mv_pseries_weighted_sum', + { + defaultMessage: 'MV_PSERIES_WEIGHTED_SUM', + } + ), + description: ( + <Markdown + markdownContent={i18n.translate( + 'textBasedEditor.query.textBasedLanguagesEditor.documentationESQL.mv_pseries_weighted_sum.markdown', + { + defaultMessage: `<!-- + This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it. + --> + + ### MV_PSERIES_WEIGHTED_SUM + Converts a multivalued expression into a single-valued column by multiplying every element on the input list by its corresponding term in P-Series and computing the sum. + + \`\`\` + ROW a = [70.0, 45.0, 21.0, 21.0, 21.0] + | EVAL sum = MV_PSERIES_WEIGHTED_SUM(a, 1.5) + | KEEP sum + \`\`\` `, description: 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', diff --git a/packages/kbn-text-based-editor/src/helpers.test.ts b/packages/kbn-text-based-editor/src/helpers.test.ts index 839ae424a11d9..797e68ce99056 100644 --- a/packages/kbn-text-based-editor/src/helpers.test.ts +++ b/packages/kbn-text-based-editor/src/helpers.test.ts @@ -9,7 +9,6 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { parseErrors, parseWarning, - getInlineEditorText, getWrappedInPipesCode, getIndicesList, getRemoteIndicesList, @@ -209,33 +208,6 @@ describe('helpers', function () { }); }); - describe('getInlineEditorText', function () { - it('should return the entire query if it is one liner', function () { - 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( - 'FROM index1 | keep field1, field2\n| keep field1, field2 | order field1', - true - ); - 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( - '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); diff --git a/packages/kbn-text-based-editor/src/helpers.ts b/packages/kbn-text-based-editor/src/helpers.ts index 59dffbd29e07f..95652a942d7dd 100644 --- a/packages/kbn-text-based-editor/src/helpers.ts +++ b/packages/kbn-text-based-editor/src/helpers.ts @@ -195,10 +195,6 @@ export const getDocumentationSections = async (language: string) => { } }; -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) => { diff --git a/packages/kbn-text-based-editor/src/overwrite.scss b/packages/kbn-text-based-editor/src/overwrite.scss index 692ed30886938..5f96bb4a580f2 100644 --- a/packages/kbn-text-based-editor/src/overwrite.scss +++ b/packages/kbn-text-based-editor/src/overwrite.scss @@ -1,14 +1,8 @@ /* Editor styles for any layout mode */ /* NOTE: Much of this is overriding Monaco styles so the specificity is intentional */ -// Radius for both the main container and the margin (container for line numbers) -.TextBasedLangEditor .monaco-editor, .TextBasedLangEditor .monaco-editor .margin, .TextBasedLangEditor .monaco-editor .overflow-guard { - border-top-left-radius: $euiBorderRadius; - border-bottom-left-radius: $euiBorderRadius; -} - .TextBasedLangEditor .monaco-editor .monaco-hover { - display: none !important; + display: block !important; } .TextBasedLangEditor .monaco-editor .margin-view-overlays .line-numbers { @@ -35,29 +29,15 @@ @include euiTextBreakWord; } -/* For compact mode */ - // All scrollable containers (e.g. main container and suggest menu) -.TextBasedLangEditor--compact .monaco-editor .monaco-scrollable-element { +.TextBasedLangEditor .monaco-editor .monaco-scrollable-element { margin-left: $euiSizeS; } -// Suggest menu in compact mode -.TextBasedLangEditor--compact .monaco-editor .monaco-list .monaco-scrollable-element { +.TextBasedLangEditor .monaco-editor .monaco-list .monaco-scrollable-element { margin-left: 0; .monaco-list-row.focused { border-radius: $euiBorderRadius; } -} - -/* For expanded mode */ - -.TextBasedLangEditor--expanded .monaco-editor .monaco-hover { - display: block !important; -} - -.TextBasedLangEditor--expanded .monaco-editor, .TextBasedLangEditor--expanded .monaco-editor .margin, .TextBasedLangEditor--expanded .monaco-editor .overflow-guard { - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} +} \ No newline at end of file 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 130d0ca69fc4a..4b83ed3f67341 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 @@ -7,61 +7,37 @@ */ import type { EuiThemeComputed } from '@elastic/eui'; -export const EDITOR_INITIAL_HEIGHT = 38; -export const EDITOR_INITIAL_HEIGHT_EXPANDED = 140; +export const EDITOR_INITIAL_HEIGHT = 80; +export const EDITOR_INITIAL_HEIGHT_INLINE_EDITING = 140; export const EDITOR_MIN_HEIGHT = 40; export const EDITOR_MAX_HEIGHT = 400; export const textBasedLanguageEditorStyles = ( euiTheme: EuiThemeComputed, - isCompactFocused: boolean, editorHeight: number, - isCodeEditorExpanded: boolean, hasErrors: boolean, hasWarning: boolean, isCodeEditorExpandedFocused: boolean, - hasReference: boolean, editorIsInline: boolean, - historyIsOpen: boolean, - hideHeaderWhenExpanded: boolean + hasOutline: boolean ) => { const bottomContainerBorderColor = hasErrors ? euiTheme.colors.danger : euiTheme.colors.primary; - const showHeader = hideHeaderWhenExpanded === true && isCodeEditorExpanded; - - let position = isCompactFocused ? ('absolute' as const) : ('relative' as const); - if (isCodeEditorExpanded) { - position = 'relative'; - } - return { editorContainer: { - position, + position: 'relative' as const, left: 0, right: 0, - zIndex: isCompactFocused ? 4 : 0, + zIndex: 4, height: `${editorHeight}px`, - border: isCompactFocused ? euiTheme.border.thin : 'none', - borderLeft: editorIsInline || !isCompactFocused ? 'none' : euiTheme.border.thin, - borderRight: editorIsInline || !isCompactFocused ? 'none' : euiTheme.border.thin, - borderTopLeftRadius: isCodeEditorExpanded ? 0 : euiTheme.border.radius.medium, - borderBottom: isCodeEditorExpanded - ? 'none' - : isCompactFocused - ? euiTheme.border.thin - : 'none', }, resizableContainer: { display: 'flex', - width: isCodeEditorExpanded ? '100%' : `calc(100% - ${hasReference ? 80 : 40}px)`, - alignItems: isCompactFocused ? 'flex-start' : 'center', - border: !isCompactFocused ? euiTheme.border.thin : 'none', - borderTopLeftRadius: isCodeEditorExpanded ? 0 : euiTheme.border.radius.medium, - borderBottomLeftRadius: isCodeEditorExpanded ? 0 : euiTheme.border.radius.medium, - borderBottomWidth: hasErrors ? '2px' : '1px', - borderBottomColor: hasErrors ? euiTheme.colors.danger : euiTheme.colors.lightShade, - borderRight: isCodeEditorExpanded ? euiTheme.border.thin : 'none', - ...(isCodeEditorExpanded && { overflow: 'hidden' }), + width: '100%', + alignItems: 'flex-start', + border: hasOutline ? euiTheme.border.thin : 'none', + borderBottom: 'none', + overflow: 'hidden', }, linesBadge: { position: 'absolute' as const, @@ -78,51 +54,44 @@ export const textBasedLanguageEditorStyles = ( transform: 'translate(0, -50%)', }, bottomContainer: { - borderLeft: editorIsInline ? 'none' : euiTheme.border.thin, - borderRight: editorIsInline ? 'none' : euiTheme.border.thin, - borderTop: - isCodeEditorExpanded && !isCodeEditorExpandedFocused - ? hasErrors - ? `2px solid ${euiTheme.colors.danger}` - : euiTheme.border.thin - : `2px solid ${bottomContainerBorderColor}`, - borderBottom: editorIsInline ? 'none' : euiTheme.border.thin, + borderTop: !isCodeEditorExpandedFocused + ? hasErrors + ? `2px solid ${euiTheme.colors.danger}` + : `2px solid ${euiTheme.colors.lightestShade}` + : `2px solid ${bottomContainerBorderColor}`, backgroundColor: euiTheme.colors.lightestShade, - paddingLeft: euiTheme.size.base, - paddingRight: euiTheme.size.base, + paddingLeft: euiTheme.size.xs, + paddingRight: euiTheme.size.xs, paddingTop: editorIsInline ? euiTheme.size.s : euiTheme.size.xs, paddingBottom: editorIsInline ? euiTheme.size.s : euiTheme.size.xs, - width: isCodeEditorExpanded ? '100%' : 'calc(100% + 2px)', + width: '100%', position: 'relative' as const, marginTop: 0, - marginLeft: isCodeEditorExpanded ? 0 : -1, + marginLeft: 0, marginBottom: 0, - borderBottomLeftRadius: editorIsInline || historyIsOpen ? 0 : euiTheme.border.radius.medium, - borderBottomRightRadius: editorIsInline || historyIsOpen ? 0 : euiTheme.border.radius.medium, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, }, historyContainer: { - border: euiTheme.border.thin, - borderTop: 'none', - borderLeft: editorIsInline ? 'none' : euiTheme.border.thin, - borderRight: editorIsInline ? 'none' : euiTheme.border.thin, + border: 'none', backgroundColor: euiTheme.colors.lightestShade, width: '100%', position: 'relative' as const, marginTop: 0, marginLeft: 0, marginBottom: 0, - borderBottomLeftRadius: editorIsInline ? 0 : euiTheme.border.radius.medium, - borderBottomRightRadius: editorIsInline ? 0 : euiTheme.border.radius.medium, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, }, topContainer: { - border: editorIsInline ? 'none' : euiTheme.border.thin, - borderTopLeftRadius: editorIsInline ? 0 : euiTheme.border.radius.medium, - borderTopRightRadius: editorIsInline ? 0 : euiTheme.border.radius.medium, + border: 'none', + borderTopLeftRadius: 0, + borderTopRightRadius: 0, backgroundColor: euiTheme.colors.lightestShade, paddingLeft: euiTheme.size.s, paddingRight: euiTheme.size.s, - paddingTop: showHeader ? euiTheme.size.s : euiTheme.size.xs, - paddingBottom: showHeader ? euiTheme.size.s : euiTheme.size.xs, + paddingTop: euiTheme.size.s, + paddingBottom: euiTheme.size.s, width: '100%', position: 'relative' as const, marginLeft: 0, 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 1c6ed13acfa00..1337d204759b1 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 @@ -12,10 +12,8 @@ import { IUiSettingsClient } from '@kbn/core/public'; import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { - TextBasedLanguagesEditor, - TextBasedLanguagesEditorProps, -} from './text_based_languages_editor'; +import { TextBasedLanguagesEditor } from './text_based_languages_editor'; +import type { TextBasedLanguagesEditorProps } from './types'; import { ReactWrapper } from 'enzyme'; jest.mock('./helpers', () => { @@ -63,10 +61,8 @@ describe('TextBasedLanguagesEditor', () => { beforeEach(() => { props = { query: { esql: 'from test' }, - isCodeEditorExpanded: false, onTextLangQueryChange: jest.fn(), onTextLangQuerySubmit: jest.fn(), - expandCodeEditor: jest.fn(), }; }); it('should render the editor component', async () => { @@ -74,13 +70,6 @@ describe('TextBasedLanguagesEditor', () => { expect(component.find('[data-test-subj="TextBasedLangEditor"]').length).not.toBe(0); }); - it('should render the lines badge for the inline mode by default', async () => { - const component = mount(renderTextBasedLanguagesEditorComponent({ ...props })); - expect( - component.find('[data-test-subj="TextBasedLangEditor-inline-lines-badge"]').length - ).not.toBe(0); - }); - it('should render the date info with no @timestamp found', async () => { const newProps = { ...props, @@ -163,60 +152,6 @@ describe('TextBasedLanguagesEditor', () => { ).toBe(0); }); - it('should render the errors badge for the inline mode by default if errors are provided', async () => { - const newProps = { - ...props, - errors: [new Error('error1')], - }; - const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); - const errorBadge = component.find('[data-test-subj="TextBasedLangEditor-inline-error-badge"]'); - expect(errorBadge.length).not.toBe(0); - errorBadge.at(0).simulate('click'); - expect( - component.find('[data-test-subj="TextBasedLangEditor-inline-error-popover"]').length - ).not.toBe(0); - }); - - it('should render the warnings badge for the inline mode by default if warning are provided', async () => { - const newProps = { - ...props, - warning: 'Line 1: 20: Warning', - }; - const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); - const warningBadge = component.find( - '[data-test-subj="TextBasedLangEditor-inline-warning-badge"]' - ); - expect(warningBadge.length).not.toBe(0); - warningBadge.at(0).simulate('click'); - expect( - component.find('[data-test-subj="TextBasedLangEditor-inline-warning-popover"]').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 = { - ...props, - expandCodeEditor: expandCodeEditorSpy, - }; - const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); - findTestSubject(component, 'TextBasedLangEditor-expand').simulate('click'); - expect(expandCodeEditorSpy).toHaveBeenCalled(); - }); - it('should render the correct buttons for the expanded code editor mode', async () => { const newProps = { ...props, @@ -230,46 +165,11 @@ describe('TextBasedLanguagesEditor', () => { 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 () => { - expect( - component.find('[data-test-subj="TextBasedLangEditor-toggleWordWrap"]').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); - }); - }); - - it('should call the expand editor function when minimize button is clicked', async () => { - const expandCodeEditorSpy = jest.fn(); - const newProps = { - ...props, - isCodeEditorExpanded: true, - expandCodeEditor: expandCodeEditorSpy, - }; - const component = mount(renderTextBasedLanguagesEditorComponent({ ...newProps })); - findTestSubject(component, 'TextBasedLangEditor-minimize').simulate('click'); - expect(expandCodeEditorSpy).toHaveBeenCalled(); - }); - it('should render the resize for the expanded code editor mode', async () => { const newProps = { ...props, 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 e2d3fd630999d..326dc471a86c1 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 @@ -7,45 +7,35 @@ */ import { - EuiBadge, - EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector, - EuiToolTip, useEuiTheme, EuiDatePicker, + EuiToolTip, + EuiButton, + type EuiButtonColor, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { CodeEditor, CodeEditorProps } from '@kbn/code-editor'; import type { CoreStart } from '@kbn/core/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { AggregateQuery } from '@kbn/es-query'; -import { getAggregateQueryMode, getLanguageDisplayName } from '@kbn/es-query'; import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; -import { i18n } from '@kbn/i18n'; -import type { IndexManagementPluginSetup } from '@kbn/index-management'; -import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { - LanguageDocumentationPopover, - type LanguageDocumentationSections, -} from '@kbn/language-documentation-popover'; import { ESQLLang, ESQL_LANG_ID, ESQL_THEME_ID, monaco, type ESQLCallbacks } from '@kbn/monaco'; -import classNames from 'classnames'; import memoize from 'lodash/memoize'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { css } from '@emotion/react'; +import { ESQLRealField } from '@kbn/esql-validation-autocomplete'; +import { FieldType } from '@kbn/esql-validation-autocomplete/src/definitions/types'; import { EditorFooter } from './editor_footer'; -import { ErrorsWarningsCompactViewPopover } from './errors_warnings_popover'; import { fetchFieldsFromESQL } from './fetch_fields_from_esql'; import { clearCacheWhenOld, - getDocumentationSections, getESQLSources, - getInlineEditorText, - getWrappedInPipesCode, parseErrors, parseWarning, useDebounceWithOptions, @@ -55,112 +45,31 @@ import { addQueriesToCache, updateCachedQueries } from './history_local_storage' import { ResizableButton } from './resizable_button'; import { EDITOR_INITIAL_HEIGHT, - EDITOR_INITIAL_HEIGHT_EXPANDED, + EDITOR_INITIAL_HEIGHT_INLINE_EDITING, EDITOR_MAX_HEIGHT, EDITOR_MIN_HEIGHT, textBasedLanguageEditorStyles, } from './text_based_languages_editor.styles'; import { getRateLimitedColumnsWithMetadata } from './ecs_metadata_helper'; +import type { TextBasedLanguagesEditorProps, TextBasedEditorDeps } from './types'; import './overwrite.scss'; -export interface TextBasedLanguagesEditorProps { - /** The aggregate type query */ - query: AggregateQuery; - /** Callback running everytime the query changes */ - onTextLangQueryChange: (query: AggregateQuery) => void; - /** Callback running when the user submits the query */ - onTextLangQuerySubmit: ( - query?: AggregateQuery, - abortController?: AbortController - ) => Promise<void>; - /** Can be used to expand/minimize the editor */ - expandCodeEditor: (status: boolean) => void; - /** If it is true, the editor initializes with height EDITOR_INITIAL_HEIGHT_EXPANDED */ - isCodeEditorExpanded: boolean; - /** If it is true, the editor displays the message @timestamp found - * The text based queries are relying on adhoc dataviews which - * can have an @timestamp timefield or nothing - */ - detectedTimestamp?: string; - /** Array of errors */ - errors?: Error[]; - /** Warning string as it comes from ES */ - warning?: string; - /** Disables the editor and displays loading icon in run button - * It is also used for hiding the history component if it is not defined - */ - isLoading?: boolean; - /** Disables the editor */ - isDisabled?: boolean; - /** Indicator if the editor is on dark mode */ - isDarkMode?: boolean; - dataTestSubj?: string; - /** If true it hides the minimize button and the user can't return to the minimized version - * Useful when the application doesn't want to give this capability - */ - hideMinimizeButton?: boolean; - /** Hide the Run query information which appears on the footer*/ - hideRunQueryText?: boolean; - /** This is used for applications (such as the inline editing flyout in dashboards) - * which want to add the editor without being part of the Unified search component - * It renders a submit query button inside the editor - */ - editorIsInline?: boolean; - /** Disables the submit query action*/ - disableSubmitAction?: boolean; - - /** when set to true enables query cancellation **/ - allowQueryCancellation?: boolean; - - /** hide @timestamp info **/ - hideTimeFilterInfo?: boolean; - - /** hide query history **/ - hideQueryHistory?: boolean; - - /** hide header buttons when editor is expanded */ - hideHeaderWhenExpanded?: boolean; -} - -interface TextBasedEditorDeps { - core: CoreStart; - dataViews: DataViewsPublicPluginStart; - expressions: ExpressionsStart; - indexManagementApiService?: IndexManagementPluginSetup['apiService']; - fieldsMetadata?: FieldsMetadataPublicStart; -} - -const MAX_COMPACT_VIEW_LENGTH = 250; -const FONT_WIDTH = 8; -const EDITOR_ONE_LINER_UNUSED_SPACE = 180; -const EDITOR_ONE_LINER_UNUSED_SPACE_WITH_ERRORS = 220; - const KEYCODE_ARROW_UP = 38; const KEYCODE_ARROW_DOWN = 40; // for editor width smaller than this value we want to start hiding some text const BREAKPOINT_WIDTH = 540; -let clickedOutside = false; -let initialRender = true; -let updateLinesFromModel = false; -let lines = 1; -let isDatePickerOpen = false; - export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ query, onTextLangQueryChange, onTextLangQuerySubmit, - expandCodeEditor, - isCodeEditorExpanded, detectedTimestamp, errors: serverErrors, warning: serverWarning, isLoading, isDisabled, - isDarkMode, - hideMinimizeButton, hideRunQueryText, editorIsInline, disableSubmitAction, @@ -168,41 +77,30 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ allowQueryCancellation, hideTimeFilterInfo, hideQueryHistory, - hideHeaderWhenExpanded, + hasOutline, }: TextBasedLanguagesEditorProps) { const popoverRef = useRef<HTMLDivElement>(null); + const datePickerOpenStatusRef = useRef<boolean>(false); const { euiTheme } = useEuiTheme(); - const language = getAggregateQueryMode(query); - const queryString: string = query[language] ?? ''; const kibana = useKibana<TextBasedEditorDeps>(); - const { - dataViews, - expressions, - indexManagementApiService, - application, - docLinks, - core, - fieldsMetadata, - } = kibana.services; + const { dataViews, expressions, indexManagementApiService, application, core, fieldsMetadata } = + kibana.services; const timeZone = core?.uiSettings?.get('dateFormat:tz'); - const [code, setCode] = useState<string>(queryString ?? ''); - const [codeOneLiner, setCodeOneLiner] = useState<string | null>(null); + const [code, setCode] = useState<string>(query.esql ?? ''); // To make server side errors less "sticky", register the state of the code when submitting const [codeWhenSubmitted, setCodeStateOnSubmission] = useState(code); const [editorHeight, setEditorHeight] = useState( - isCodeEditorExpanded ? EDITOR_INITIAL_HEIGHT_EXPANDED : EDITOR_INITIAL_HEIGHT + editorIsInline ? EDITOR_INITIAL_HEIGHT_INLINE_EDITING : EDITOR_INITIAL_HEIGHT ); const [popoverPosition, setPopoverPosition] = useState<{ top?: number; left?: number }>({}); const [timePickerDate, setTimePickerDate] = useState(moment()); const [measuredEditorWidth, setMeasuredEditorWidth] = useState(0); - const [measuredContentWidth, setMeasuredContentWidth] = useState(0); const isSpaceReduced = Boolean(editorIsInline) && measuredEditorWidth < BREAKPOINT_WIDTH; const [isHistoryOpen, setIsHistoryOpen] = useState(false); - const [showLineNumbers, setShowLineNumbers] = useState(isCodeEditorExpanded); - const [isCompactFocused, setIsCompactFocused] = useState(isCodeEditorExpanded); const [isCodeEditorExpandedFocused, setIsCodeEditorExpandedFocused] = useState(false); + const [isLanguagePopoverOpen, setIsLanguagePopoverOpen] = useState(false); const [isQueryLoading, setIsQueryLoading] = useState(true); const [abortController, setAbortController] = useState(new AbortController()); // contains both client side validation and server messages @@ -230,10 +128,9 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const onQueryUpdate = useCallback( (value: string) => { - setCode(value); - onTextLangQueryChange({ [language]: value } as AggregateQuery); + onTextLangQueryChange({ esql: value } as AggregateQuery); }, - [language, onTextLangQueryChange] + [onTextLangQueryChange] ); const onQuerySubmit = useCallback(() => { @@ -249,16 +146,9 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ if (currentValue != null) { setCodeStateOnSubmission(currentValue); } - onTextLangQuerySubmit({ [language]: currentValue } as AggregateQuery, abc); + onTextLangQuerySubmit({ esql: currentValue } as AggregateQuery, abc); } - }, [ - isQueryLoading, - isLoading, - allowQueryCancellation, - abortController, - onTextLangQuerySubmit, - language, - ]); + }, [isQueryLoading, isLoading, allowQueryCancellation, abortController, onTextLangQuerySubmit]); const onCommentLine = useCallback(() => { const currentSelection = editor1?.current?.getSelection(); @@ -290,8 +180,13 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ if (!isLoading) setIsQueryLoading(false); }, [isLoading]); - const [documentationSections, setDocumentationSections] = - useState<LanguageDocumentationSections>(); + useEffect(() => { + if (editor1.current) { + if (code !== query.esql) { + setCode(query.esql); + } + } + }, [code, query.esql]); const toggleHistory = useCallback((status: boolean) => { setIsHistoryOpen(status); @@ -310,7 +205,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const absoluteLeft = editorLeft + (editorPosition?.left ?? 0); setPopoverPosition({ top: absoluteTop, left: absoluteLeft }); - isDatePickerOpen = true; + datePickerOpenStatusRef.current = true; popoverRef.current?.focus(); } }, []); @@ -330,28 +225,17 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const styles = textBasedLanguageEditorStyles( euiTheme, - isCompactFocused, editorHeight, - isCodeEditorExpanded, Boolean(editorMessages.errors.length), Boolean(editorMessages.warnings.length), isCodeEditorExpandedFocused, - Boolean(documentationSections), Boolean(editorIsInline), - isHistoryOpen, - !!hideHeaderWhenExpanded + Boolean(hasOutline) ); - const isDark = isDarkMode; const editorModel = useRef<monaco.editor.ITextModel>(); const editor1 = useRef<monaco.editor.IStandaloneCodeEditor>(); const containerRef = useRef<HTMLElement>(null); - const editorClassName = classNames('TextBasedLangEditor', { - 'TextBasedLangEditor--expanded': isCodeEditorExpanded, - 'TextBasedLangEditor--compact': isCompactFocused, - 'TextBasedLangEditor--initial': !isCompactFocused, - }); - // When the editor is on full size mode, the user can resize the height of the editor. const onMouseDownResizeHandler = useCallback( (mouseDownEvent) => { @@ -389,36 +273,8 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ [editorHeight] ); - const restoreInitialMode = () => { - setIsCodeEditorExpandedFocused(false); - if (isCodeEditorExpanded) return; - setEditorHeight(EDITOR_INITIAL_HEIGHT); - setIsCompactFocused(false); - setShowLineNumbers(false); - updateLinesFromModel = false; - clickedOutside = true; - if (editor1.current) { - const contentWidth = editor1.current.getLayoutInfo().width; - calculateVisibleCode(contentWidth, true); - editor1.current.layout({ width: contentWidth, height: EDITOR_INITIAL_HEIGHT }); - } - }; - - const updateHeight = useCallback((editor: monaco.editor.IStandaloneCodeEditor) => { - if (clickedOutside || initialRender) return; - const contentHeight = Math.min(MAX_COMPACT_VIEW_LENGTH, editor.getContentHeight()); - setEditorHeight(contentHeight); - editor.layout({ width: editor.getLayoutInfo().width, height: contentHeight }); - }, []); - const onEditorFocus = useCallback(() => { - setIsCompactFocused(true); setIsCodeEditorExpandedFocused(true); - setShowLineNumbers(true); - setCodeOneLiner(null); - clickedOutside = false; - initialRender = false; - updateLinesFromModel = true; }, []); const { cache: esqlFieldsCache, memoizedFieldsFromESQL } = useMemo(() => { @@ -449,7 +305,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const esqlCallbacks: ESQLCallbacks = useMemo(() => { const callbacks: ESQLCallbacks = { getSources: async () => { - clearCacheWhenOld(dataSourcesCache, queryString); + clearCacheWhenOld(dataSourcesCache, query.esql); const sources = await memoizedSources(dataViews, core).result; return sources; }, @@ -468,7 +324,13 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ undefined, abortController ).result; - const columns = table?.columns.map((c) => ({ name: c.name, type: c.meta.type })) || []; + const columns: ESQLRealField[] = + table?.columns.map((c) => { + return { + name: c.name, + type: c.meta.esType as FieldType, + }; + }) || []; return await getRateLimitedColumnsWithMetadata(columns, fieldsMetadata); } catch (e) { // no action yet @@ -487,7 +349,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ }; return callbacks; }, [ - queryString, + query.esql, memoizedSources, dataSourcesCache, dataViews, @@ -500,15 +362,43 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ fieldsMetadata, ]); + const queryRunButtonProperties = useMemo(() => { + if (allowQueryCancellation && isLoading) { + return { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.cancel', { + defaultMessage: 'Cancel', + }), + iconType: 'cross', + color: 'text', + }; + } + if (code !== codeWhenSubmitted) { + return { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.runQuery', { + defaultMessage: 'Run query', + }), + iconType: 'play', + color: 'success', + }; + } + return { + label: i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.refreshLabel', { + defaultMessage: 'Refresh', + }), + iconType: 'refresh', + color: 'primary', + }; + }, [allowQueryCancellation, code, codeWhenSubmitted, isLoading]); + const parseMessages = useCallback(async () => { if (editorModel.current) { - return await ESQLLang.validate(editorModel.current, queryString, esqlCallbacks); + return await ESQLLang.validate(editorModel.current, code, esqlCallbacks); } return { errors: [], warnings: [], }; - }, [esqlCallbacks, queryString]); + }, [esqlCallbacks, code]); const clientParserStatus = clientParserMessages.errors?.length ? 'error' @@ -528,24 +418,24 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ }; if (isQueryLoading || isLoading) { addQueriesToCache({ - queryString, + queryString: code, timeZone, }); validateQuery(); setRefetchHistoryItems(false); } else { updateCachedQueries({ - queryString, + queryString: code, status: clientParserStatus, }); setRefetchHistoryItems(true); } - }, [clientParserStatus, isLoading, isQueryLoading, parseMessages, queryString, timeZone]); + }, [clientParserStatus, isLoading, isQueryLoading, parseMessages, code, timeZone]); const queryValidation = useCallback( async ({ active }: { active: boolean }) => { - if (!editorModel.current || language !== 'esql' || editorModel.current.isDisposed()) return; + if (!editorModel.current || editorModel.current.isDisposed()) return; monaco.editor.setModelMarkers(editorModel.current, 'Unified search', []); const { warnings: parserWarnings, errors: parserErrors } = await parseMessages(); const markers = []; @@ -559,7 +449,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ return; } }, - [language, parseMessages] + [parseMessages] ); useDebounceWithOptions( @@ -595,18 +485,15 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ ); const suggestionProvider = useMemo( - () => (language === 'esql' ? ESQLLang.getSuggestionProvider?.(esqlCallbacks) : undefined), - [language, esqlCallbacks] + () => ESQLLang.getSuggestionProvider?.(esqlCallbacks), + [esqlCallbacks] ); - const hoverProvider = useMemo( - () => (language === 'esql' ? ESQLLang.getHoverProvider?.(esqlCallbacks) : undefined), - [language, esqlCallbacks] - ); + const hoverProvider = useMemo(() => ESQLLang.getHoverProvider?.(esqlCallbacks), [esqlCallbacks]); const codeActionProvider = useMemo( - () => (language === 'esql' ? ESQLLang.getCodeActionProvider?.(esqlCallbacks) : undefined), - [language, esqlCallbacks] + () => ESQLLang.getCodeActionProvider?.(esqlCallbacks), + [esqlCallbacks] ); const onErrorClick = useCallback(({ startLineNumber, startColumn }: MonacoMessage) => { @@ -632,46 +519,11 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ }; }, []); - const calculateVisibleCode = useCallback( - (width: number, force?: boolean) => { - const containerWidth = containerRef.current?.offsetWidth; - if (containerWidth && (!isCompactFocused || force)) { - const hasLines = /\r|\n/.exec(queryString); - if (hasLines && !updateLinesFromModel) { - lines = queryString.split(/\r|\n/).length; - } - const text = getInlineEditorText(queryString, Boolean(hasLines)); - const queryLength = text.length; - const unusedSpace = - editorMessages.errors.length || editorMessages.warnings.length - ? EDITOR_ONE_LINER_UNUSED_SPACE_WITH_ERRORS - : EDITOR_ONE_LINER_UNUSED_SPACE; - const charactersAlowed = Math.floor((width - unusedSpace) / FONT_WIDTH); - if (queryLength > charactersAlowed) { - const shortedCode = text.substring(0, charactersAlowed) + '...'; - setCodeOneLiner(shortedCode); - } else { - const shortedCode = text; - setCodeOneLiner(shortedCode); - } - } - }, - [isCompactFocused, queryString, editorMessages] - ); - // When the layout changes, and the editor is not focused, we want to // recalculate the visible code so it fills up the available space. We // use a ref because editorDidMount is only called once, and the reference // to the state becomes stale after re-renders. const onLayoutChange = (layoutInfoEvent: monaco.editor.EditorLayoutInfo) => { - if (layoutInfoEvent.contentWidth !== measuredContentWidth) { - const nextMeasuredWidth = layoutInfoEvent.contentWidth; - setMeasuredContentWidth(nextMeasuredWidth); - if (!isCodeEditorExpandedFocused && !isCompactFocused) { - calculateVisibleCode(nextMeasuredWidth, true); - } - } - if (layoutInfoEvent.width !== measuredEditorWidth) { setMeasuredEditorWidth(layoutInfoEvent.width); } @@ -681,38 +533,6 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ onLayoutChangeRef.current = onLayoutChange; - useEffect(() => { - if (editor1.current && !isCompactFocused) { - if (code !== queryString) { - setCode(queryString); - calculateVisibleCode(editor1.current.getLayoutInfo().width); - } - } - }, [calculateVisibleCode, code, isCompactFocused, queryString]); - - useEffect(() => { - // make sure to always update the code in expanded editor when query prop changes - if (isCodeEditorExpanded && editor1.current?.getValue() !== queryString) { - setCode(queryString); - } - }, [isCodeEditorExpanded, queryString]); - - const isWrappedInPipes = useMemo(() => { - const pipes = code?.split('|'); - const pipesWithNewLine = code?.split('\n|'); - return pipes?.length === pipesWithNewLine?.length; - }, [code]); - - useEffect(() => { - async function getDocumentation() { - const sections = await getDocumentationSections(language); - setDocumentationSections(sections); - } - if (!documentationSections) { - getDocumentation(); - } - }, [language, documentationSections]); - const codeEditorOptions: CodeEditorProps['options'] = { accessibilitySupport: 'off', autoIndent: 'none', @@ -727,7 +547,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ enabled: false, }, lineDecorationsWidth: 12, - lineNumbers: showLineNumbers ? 'on' : 'off', + lineNumbers: 'on', lineNumbersMinChars: 3, minimap: { enabled: false }, overviewRulerLanes: 0, @@ -737,204 +557,78 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ bottom: 8, }, quickSuggestions: true, - readOnly: - isDisabled || Boolean(!isCompactFocused && codeOneLiner && codeOneLiner.includes('...')), - renderLineHighlight: !isCodeEditorExpanded ? 'none' : 'line', + readOnly: isDisabled, + renderLineHighlight: 'line', renderLineHighlightOnlyWhenFocus: true, scrollbar: { horizontal: 'hidden', vertical: 'auto', }, scrollBeyondLastLine: false, - theme: language === 'esql' ? ESQL_THEME_ID : isDark ? 'vs-dark' : 'vs', + theme: ESQL_THEME_ID, wordWrap: 'on', wrappingIndent: 'none', }; - if (isCompactFocused) { - codeEditorOptions.overviewRulerLanes = 4; - codeEditorOptions.hideCursorInOverviewRuler = false; - codeEditorOptions.overviewRulerBorder = true; - } - const editorPanel = ( <> - {isCodeEditorExpanded && !hideHeaderWhenExpanded && ( + {Boolean(editorIsInline) && ( <EuiFlexGroup - gutterSize="s" - justifyContent="spaceBetween" - css={styles.topContainer} + gutterSize="none" responsive={false} + justifyContent="flexEnd" + css={css` + padding: ${euiTheme.size.s}; + `} > <EuiFlexItem grow={false}> - <EuiFlexGroup responsive={false} gutterSize="none" alignItems="center"> - <EuiFlexItem grow={false}> - <EuiToolTip - position="top" - content={ - isWrappedInPipes - ? i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel', - { - defaultMessage: 'Remove line breaks on pipes', - } - ) - : i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel', - { - defaultMessage: 'Add line breaks on pipes', - } - ) - } - > - <EuiButtonIcon - iconType={isWrappedInPipes ? 'pipeNoBreaks' : 'pipeBreaks'} - color="text" - size="xs" - data-test-subj="TextBasedLangEditor-toggleWordWrap" - aria-label={ - isWrappedInPipes - ? i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.disableWordWrapLabel', - { - defaultMessage: 'Remove line breaks on pipes', - } - ) - : i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.EnableWordWrapLabel', - { - defaultMessage: 'Add line breaks on pipes', - } - ) - } - onClick={() => { - const updatedCode = getWrappedInPipesCode(code, isWrappedInPipes); - if (code !== updatedCode) { - setCode(updatedCode); - onTextLangQueryChange({ [language]: updatedCode } as AggregateQuery); - } - }} - /> - </EuiToolTip> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiFlexGroup responsive={false} gutterSize="none" alignItems="center"> - <EuiFlexItem grow={false}> - {documentationSections && ( - <EuiFlexItem grow={false}> - <LanguageDocumentationPopover - language={getLanguageDisplayName(String(language))} - sections={documentationSections} - searchInDescription - linkToDocumentation={ - language === 'esql' ? docLinks?.links?.query?.queryESQL : '' - } - buttonProps={{ - color: 'text', - size: 'xs', - 'data-test-subj': 'TextBasedLangEditor-documentation', - 'aria-label': i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel', - { - defaultMessage: 'Documentation', - } - ), - }} - /> - </EuiFlexItem> - )} - </EuiFlexItem> - {!Boolean(hideMinimizeButton) && ( - <EuiFlexItem grow={false}> - <EuiToolTip - position="top" - content={i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip', - { - defaultMessage: 'Compact query editor', - } - )} - > - <EuiButtonIcon - iconType="minimize" - color="text" - aria-label={i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor', - { - defaultMessage: 'Minimize editor', - } - )} - data-test-subj="TextBasedLangEditor-minimize" - size="xs" - onClick={() => { - expandCodeEditor(false); - updateLinesFromModel = false; - }} - /> - </EuiToolTip> - </EuiFlexItem> - )} - </EuiFlexGroup> + <EuiToolTip + position="top" + content={i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.runQuery', { + defaultMessage: 'Run query', + })} + > + <EuiButton + color={queryRunButtonProperties.color as EuiButtonColor} + onClick={onQuerySubmit} + iconType={queryRunButtonProperties.iconType} + size="s" + isLoading={isLoading && !allowQueryCancellation} + isDisabled={Boolean(disableSubmitAction && !allowQueryCancellation)} + data-test-subj="TextBasedLangEditor-run-query-button" + aria-label={queryRunButtonProperties.label} + > + {queryRunButtonProperties.label} + </EuiButton> + </EuiToolTip> </EuiFlexItem> </EuiFlexGroup> )} <EuiFlexGroup gutterSize="none" responsive={false} ref={containerRef}> <EuiOutsideClickDetector onOutsideClick={() => { - restoreInitialMode(); + setIsCodeEditorExpandedFocused(false); }} > <div css={styles.resizableContainer}> <EuiFlexItem data-test-subj={dataTestSubj ?? 'TextBasedLangEditor'} - className={editorClassName} + className="TextBasedLangEditor" css={css` max-width: 100%; position: relative; `} > <div css={styles.editorContainer}> - {!isCompactFocused && ( - <EuiBadge - color={euiTheme.colors.lightShade} - css={styles.linesBadge} - data-test-subj="TextBasedLangEditor-inline-lines-badge" - > - {i18n.translate('textBasedEditor.query.textBasedLanguagesEditor.lineCount', { - defaultMessage: '{count} {count, plural, one {line} other {lines}}', - values: { count: lines }, - })} - </EuiBadge> - )} - {!isCompactFocused && editorMessages.errors.length > 0 && ( - <ErrorsWarningsCompactViewPopover - items={editorMessages.errors} - type="error" - onErrorClick={onErrorClick} - popoverCSS={styles.errorsBadge} - /> - )} - {!isCompactFocused && - editorMessages.warnings.length > 0 && - editorMessages.errors.length === 0 && ( - <ErrorsWarningsCompactViewPopover - items={editorMessages.warnings} - type="warning" - onErrorClick={onErrorClick} - popoverCSS={styles.errorsBadge} - /> - )} <CodeEditor languageId={ESQL_LANG_ID} - value={codeOneLiner || code} + value={code} options={codeEditorOptions} width="100%" suggestionProvider={suggestionProvider} hoverProvider={{ provideHover: (model, position, token) => { - if (isCompactFocused || !hoverProvider?.provideHover) { + if (!hoverProvider?.provideHover) { return { contents: [] }; } return hoverProvider?.provideHover(model, position, token); @@ -948,10 +642,6 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ if (model) { editorModel.current = model; } - if (isCodeEditorExpanded) { - lines = model?.getLineCount() || 1; - } - // this is fixing a bug between the EUIPopover and the monaco editor // when the user clicks the editor, we force it to focus and the onDidFocusEditorText // to fire, the timeout is needed because otherwise it refocuses on the popover icon @@ -961,7 +651,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ setTimeout(() => { editor.focus(); }, 100); - if (isDatePickerOpen) { + if (datePickerOpenStatusRef.current) { setPopoverPosition({}); } }); @@ -989,156 +679,45 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ ); setMeasuredEditorWidth(editor.getLayoutInfo().width); - setMeasuredContentWidth(editor.getContentWidth()); - editor.onDidLayoutChange((layoutInfoEvent) => { onLayoutChangeRef.current(layoutInfoEvent); }); - - if (!isCodeEditorExpanded) { - editor.onDidContentSizeChange((e) => { - if (e.contentHeightChanged) { - updateHeight(editor); - } - }); - } }} /> - {isCompactFocused && !isCodeEditorExpanded && ( - <EditorFooter - lines={lines} - styles={{ - bottomContainer: styles.bottomContainer, - historyContainer: styles.historyContainer, - }} - {...editorMessages} - onErrorClick={onErrorClick} - runQuery={onQuerySubmit} - updateQuery={onQueryUpdate} - detectedTimestamp={detectedTimestamp} - editorIsInline={editorIsInline} - disableSubmitAction={disableSubmitAction} - hideRunQueryText={hideRunQueryText} - isSpaceReduced={isSpaceReduced} - isLoading={isQueryLoading} - allowQueryCancellation={allowQueryCancellation} - hideTimeFilterInfo={hideTimeFilterInfo} - isHistoryOpen={isHistoryOpen} - setIsHistoryOpen={toggleHistory} - measuredContainerWidth={measuredEditorWidth} - hideQueryHistory={hideHistoryComponent} - refetchHistoryItems={refetchHistoryItems} - isInCompactMode={true} - queryHasChanged={code !== codeWhenSubmitted} - /> - )} </div> </EuiFlexItem> </div> </EuiOutsideClickDetector> - {!isCodeEditorExpanded && ( - <EuiFlexItem grow={false}> - <EuiFlexGroup responsive={false} gutterSize="none" alignItems="center"> - <EuiFlexItem grow={false}> - {documentationSections && ( - <EuiFlexItem grow={false}> - <LanguageDocumentationPopover - language={ - String(language) === 'esql' ? 'ES|QL' : String(language).toUpperCase() - } - linkToDocumentation={ - language === 'esql' ? docLinks?.links?.query?.queryESQL : '' - } - searchInDescription - sections={documentationSections} - buttonProps={{ - display: 'empty', - 'data-test-subj': 'TextBasedLangEditor-inline-documentation', - 'aria-label': i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.documentationLabel', - { - defaultMessage: 'Documentation', - } - ), - size: 'm', - css: { - borderRadius: 0, - backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', - border: '1px solid rgb(17 43 134 / 10%) !important', - transform: 'none !important', - }, - }} - /> - </EuiFlexItem> - )} - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiToolTip - position="top" - content={i18n.translate( - 'textBasedEditor.query.textBasedLanguagesEditor.expandTooltip', - { - defaultMessage: 'Expand query editor', - } - )} - > - <EuiButtonIcon - display="empty" - iconType="expand" - size="m" - aria-label="Expand" - onClick={() => expandCodeEditor(true)} - data-test-subj="TextBasedLangEditor-expand" - css={{ - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', - border: '1px solid rgb(17 43 134 / 10%) !important', - borderLeft: 'transparent !important', - transform: 'none !important', - }} - /> - </EuiToolTip> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - )} </EuiFlexGroup> - {isCodeEditorExpanded && ( - <EditorFooter - lines={lines} - styles={{ - bottomContainer: styles.bottomContainer, - historyContainer: styles.historyContainer, - }} - onErrorClick={onErrorClick} - runQuery={onQuerySubmit} - updateQuery={onQueryUpdate} - detectedTimestamp={detectedTimestamp} - hideRunQueryText={hideRunQueryText} - editorIsInline={editorIsInline} - disableSubmitAction={disableSubmitAction} - isSpaceReduced={isSpaceReduced} - isLoading={isQueryLoading} - allowQueryCancellation={allowQueryCancellation} - hideTimeFilterInfo={hideTimeFilterInfo} - {...editorMessages} - isHistoryOpen={isHistoryOpen} - setIsHistoryOpen={toggleHistory} - measuredContainerWidth={measuredEditorWidth} - hideQueryHistory={hideHistoryComponent} - refetchHistoryItems={refetchHistoryItems} - queryHasChanged={code !== codeWhenSubmitted} - /> - )} - {isCodeEditorExpanded && ( - <ResizableButton - onMouseDownResizeHandler={onMouseDownResizeHandler} - onKeyDownResizeHandler={onKeyDownResizeHandler} - editorIsInline={editorIsInline} - /> - )} - + <EditorFooter + lines={editorModel.current?.getLineCount() || 1} + styles={{ + bottomContainer: styles.bottomContainer, + historyContainer: styles.historyContainer, + }} + code={code} + onErrorClick={onErrorClick} + runQuery={onQuerySubmit} + updateQuery={onQueryUpdate} + detectedTimestamp={detectedTimestamp} + hideRunQueryText={hideRunQueryText} + editorIsInline={editorIsInline} + isSpaceReduced={isSpaceReduced} + hideTimeFilterInfo={hideTimeFilterInfo} + {...editorMessages} + isHistoryOpen={isHistoryOpen} + setIsHistoryOpen={toggleHistory} + measuredContainerWidth={measuredEditorWidth} + hideQueryHistory={hideHistoryComponent} + refetchHistoryItems={refetchHistoryItems} + isHelpMenuOpen={isLanguagePopoverOpen} + setIsHelpMenuOpen={setIsLanguagePopoverOpen} + /> + <ResizableButton + onMouseDownResizeHandler={onMouseDownResizeHandler} + onKeyDownResizeHandler={onKeyDownResizeHandler} + editorIsInline={editorIsInline} + /> {createPortal( Object.keys(popoverPosition).length !== 0 && popoverPosition.constructor === Object && ( <div @@ -1185,8 +764,18 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ forceMoveMarkers: true, }, ]); + setPopoverPosition({}); - isDatePickerOpen = false; + + datePickerOpenStatusRef.current = false; + + // move the cursor past the date we just inserted + editor1.current?.setPosition({ + lineNumber: currentCursorPosition?.lineNumber ?? 0, + column: (currentCursorPosition?.column ?? 0) + addition.length - 1, + }); + // restore focus to the editor + editor1.current?.focus(); } }} inline diff --git a/packages/kbn-text-based-editor/src/types.ts b/packages/kbn-text-based-editor/src/types.ts new file mode 100644 index 0000000000000..56bdafa7f2a95 --- /dev/null +++ b/packages/kbn-text-based-editor/src/types.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { CoreStart } from '@kbn/core/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { AggregateQuery } from '@kbn/es-query'; +import type { ExpressionsStart } from '@kbn/expressions-plugin/public'; +import type { IndexManagementPluginSetup } from '@kbn/index-management'; +import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; + +export interface TextBasedLanguagesEditorProps { + /** The aggregate type query */ + query: AggregateQuery; + /** Callback running everytime the query changes */ + onTextLangQueryChange: (query: AggregateQuery) => void; + /** Callback running when the user submits the query */ + onTextLangQuerySubmit: ( + query?: AggregateQuery, + abortController?: AbortController + ) => Promise<void>; + /** If it is true, the editor displays the message @timestamp found + * The text based queries are relying on adhoc dataviews which + * can have an @timestamp timefield or nothing + */ + detectedTimestamp?: string; + /** Array of errors */ + errors?: Error[]; + /** Warning string as it comes from ES */ + warning?: string; + /** Disables the editor and displays loading icon in run button + * It is also used for hiding the history component if it is not defined + */ + isLoading?: boolean; + /** Disables the editor */ + isDisabled?: boolean; + dataTestSubj?: string; + /** Hide the Run query information which appears on the footer*/ + hideRunQueryText?: boolean; + /** This is used for applications (such as the inline editing flyout in dashboards) + * which want to add the editor without being part of the Unified search component + * It renders a submit query button inside the editor + */ + editorIsInline?: boolean; + /** Disables the submit query action*/ + disableSubmitAction?: boolean; + + /** when set to true enables query cancellation **/ + allowQueryCancellation?: boolean; + + /** hide @timestamp info **/ + hideTimeFilterInfo?: boolean; + + /** hide query history **/ + hideQueryHistory?: boolean; + + /** adds border in the editor **/ + hasOutline?: boolean; +} + +export interface TextBasedEditorDeps { + core: CoreStart; + dataViews: DataViewsPublicPluginStart; + expressions: ExpressionsStart; + indexManagementApiService?: IndexManagementPluginSetup['apiService']; + fieldsMetadata?: FieldsMetadataPublicStart; +} diff --git a/packages/kbn-text-based-editor/tsconfig.json b/packages/kbn-text-based-editor/tsconfig.json index 632dd05753459..b5ebfb8526df0 100644 --- a/packages/kbn-text-based-editor/tsconfig.json +++ b/packages/kbn-text-based-editor/tsconfig.json @@ -28,7 +28,7 @@ "@kbn/shared-ux-markdown", "@kbn/fields-metadata-plugin", "@kbn/esql-validation-autocomplete", - "@kbn/field-types" + "@kbn/esql-utils", ], "exclude": [ "target/**/*", diff --git a/packages/kbn-try-in-console/components/try_in_console_button.test.tsx b/packages/kbn-try-in-console/components/try_in_console_button.test.tsx index 8f7ed793fc7a4..fb4cce16d6134 100644 --- a/packages/kbn-try-in-console/components/try_in_console_button.test.tsx +++ b/packages/kbn-try-in-console/components/try_in_console_button.test.tsx @@ -45,7 +45,7 @@ describe('TryInConsoleButton', () => { sharePlugin, content, showIcon, - link, + type, }: Partial<TryInConsoleButtonProps>) => ({ application: (application ?? mockApplication) as ApplicationStart, sharePlugin: (sharePlugin ?? mockShare) as SharePluginStart | undefined, @@ -53,7 +53,7 @@ describe('TryInConsoleButton', () => { consolePlugin, content, showIcon, - link, + type, }); beforeEach(() => { jest.resetAllMocks(); @@ -83,7 +83,7 @@ describe('TryInConsoleButton', () => { ); }); it('can render as a link', async () => { - const props: Partial<TryInConsoleButtonProps> = { request: 'GET /_stats', link: true }; + const props: Partial<TryInConsoleButtonProps> = { request: 'GET /_stats', type: 'link' }; const wrapper = render(<TryInConsoleButton {...defaultProps(props)} />); expect(wrapper.getByTestId('tryInConsoleLink')).toBeTruthy(); @@ -142,7 +142,7 @@ describe('TryInConsoleButton', () => { const props: Partial<TryInConsoleButtonProps> = { request: 'GET /_stats', content: 'Try my console!!', - link: true, + type: 'link', }; const wrapper = render(<TryInConsoleButton {...defaultProps(props)} />); diff --git a/packages/kbn-try-in-console/components/try_in_console_button.tsx b/packages/kbn-try-in-console/components/try_in_console_button.tsx index e54b139fe6734..077c87ca66ed0 100644 --- a/packages/kbn-try-in-console/components/try_in_console_button.tsx +++ b/packages/kbn-try-in-console/components/try_in_console_button.tsx @@ -8,7 +8,7 @@ import React from 'react'; -import { EuiButtonEmpty, EuiLink } from '@elastic/eui'; +import { EuiLink, EuiButton, EuiButtonEmpty } from '@elastic/eui'; import type { ApplicationStart } from '@kbn/core-application-browser'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { ConsolePluginStart } from '@kbn/console-plugin/public'; @@ -25,16 +25,16 @@ export interface TryInConsoleButtonProps { sharePlugin?: SharePluginStart; content?: string | React.ReactElement; showIcon?: boolean; - link?: boolean; + type?: 'link' | 'button' | 'emptyButton'; } export const TryInConsoleButton = ({ request, application, consolePlugin, sharePlugin, - content, + content = TRY_IN_CONSOLE, showIcon = true, - link = false, + type = 'emptyButton', }: TryInConsoleButtonProps) => { const url = sharePlugin?.url; const canShowDevtools = !!application?.capabilities?.dev_tools?.show; @@ -64,37 +64,42 @@ export const TryInConsoleButton = ({ } }; - if (link) { - return ( - <EuiLink data-test-subj="tryInConsoleLink" onClick={onClick}> - {content ?? TRY_IN_CONSOLE} - </EuiLink> - ); - } - const getAriaLabel = () => { if ( consolePlugin?.openEmbeddedConsole !== undefined && consolePlugin?.isEmbeddedConsoleAvailable?.() ) { - return i18n.translate('tryInConsole.embeddedConsoleButton', { - defaultMessage: 'Try the snipped in the Console - opens in embedded console', + return i18n.translate('tryInConsole.embeddedConsoleButton.ariaLabel', { + defaultMessage: 'Try in Console - opens in embedded console', }); } - return i18n.translate('tryInConsole.inNewTab.button', { - defaultMessage: 'Try the below snippet in Console - opens in a new tab', + return i18n.translate('tryInConsole.inNewTab.button.ariaLabel', { + defaultMessage: 'Try in Console - opens in a new tab', }); }; - return ( - <EuiButtonEmpty - data-test-subj="tryInConsoleButton" - onClick={onClick} - iconType={showIcon ? 'popout' : undefined} - size="s" - aria-label={getAriaLabel()} - > - {content ?? TRY_IN_CONSOLE} - </EuiButtonEmpty> - ); + const commonProps = { + 'data-test-subj': type === 'link' ? 'tryInConsoleLink' : 'tryInConsoleButton', + 'aria-label': getAriaLabel(), + onClick, + }; + const iconType = showIcon ? 'popout' : undefined; + + switch (type) { + case 'link': + return <EuiLink {...commonProps}>{content}</EuiLink>; + case 'button': + return ( + <EuiButton color="primary" iconType={iconType} size="s" {...commonProps}> + {content} + </EuiButton> + ); + case 'emptyButton': + default: + return ( + <EuiButtonEmpty iconType={iconType} size="s" {...commonProps}> + {content} + </EuiButtonEmpty> + ); + } }; diff --git a/packages/kbn-unified-data-table/README.md b/packages/kbn-unified-data-table/README.md index 576a676289d7a..7a2db17781ad1 100644 --- a/packages/kbn-unified-data-table/README.md +++ b/packages/kbn-unified-data-table/README.md @@ -41,13 +41,12 @@ Props description: | **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. | +| **rowAdditionalLeadingControls** | (optional)RowControlColumn[] | Optional value for providing an 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<string,(props: EuiDataGridCellValueElementProps) => 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. | @@ -141,9 +140,7 @@ Usage example: [browserFields, handleOnPanelClosed, runtimeMappings, timelineId] ); } - externalControlColumns={leadingControlColumns} externalAdditionalControls={additionalControls} - trailingControlColumns={trailingControlColumns} renderCustomGridBody={renderCustomGridBody} rowsPerPageOptions={[10, 30, 40, 100]} showFullScreenButton={false} diff --git a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx index d67afccc01559..dce5e13a3c89d 100644 --- a/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx +++ b/packages/kbn-unified-data-table/__mocks__/external_control_columns.tsx @@ -17,6 +17,7 @@ import { EuiSpacer, EuiDataGridControlColumn, } from '@elastic/eui'; +import type { RowControlColumn } from '../src/types'; const SelectionHeaderCell = () => { return ( @@ -116,3 +117,22 @@ export const testLeadingControlColumn: EuiDataGridControlColumn = { rowCellRender: SelectionRowCell, width: 100, }; + +export const mockRowAdditionalLeadingControls = ['visBarVerticalStacked', 'heart', 'inspect'].map( + (iconType, index): RowControlColumn => ({ + id: `exampleControl_${iconType}`, + headerAriaLabel: `Example Row Control ${iconType}`, + renderControl: (Control, rowProps) => { + return ( + <Control + data-test-subj={`exampleRowControl-${iconType}`} + label={`Example ${iconType}`} + iconType={iconType} + onClick={() => { + alert(`Example "${iconType}" control clicked. Row index: ${rowProps.rowIndex}`); + }} + /> + ); + }, + }) +); diff --git a/packages/kbn-unified-data-table/__mocks__/table_context.ts b/packages/kbn-unified-data-table/__mocks__/table_context.ts index 4a4a75b0fa9e5..9cf28f89eb810 100644 --- a/packages/kbn-unified-data-table/__mocks__/table_context.ts +++ b/packages/kbn-unified-data-table/__mocks__/table_context.ts @@ -15,6 +15,7 @@ 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'; +import type { UseSelectedDocsState } from '../src/hooks/use_selected_docs'; const buildTableContext = (dataView: DataView, rows: EsHitRecord[]): DataTableContext => { const usedRows = rows.map((row) => { @@ -28,8 +29,9 @@ const buildTableContext = (dataView: DataView, rows: EsHitRecord[]): DataTableCo onFilter: jest.fn(), dataView, isDarkMode: false, - selectedDocs: [], - setSelectedDocs: jest.fn(), + selectedDocsState: buildSelectedDocsState([]), + pageIndex: 0, + pageSize: 10, valueToStringConverter: (rowIndex, columnId, options) => convertValueToString({ rowIndex, @@ -45,3 +47,21 @@ const buildTableContext = (dataView: DataView, rows: EsHitRecord[]): DataTableCo export const dataTableContextMock = buildTableContext(dataViewMock, esHitsMock); export const dataTableContextComplexMock = buildTableContext(dataViewComplexMock, esHitsComplex); + +export function buildSelectedDocsState(selectedDocIds: string[]): UseSelectedDocsState { + const selectedDocsSet = new Set(selectedDocIds); + + return { + isDocSelected: (docId: string) => selectedDocsSet.has(docId), + getCountOfSelectedDocs: (docIds: string[]) => + docIds.reduce((acc, docId) => (selectedDocsSet.has(docId) ? acc + 1 : acc), 0), + hasSelectedDocs: selectedDocsSet.size > 0, + selectedDocIds, + toggleDocSelection: jest.fn(), + selectAllDocs: jest.fn(), + selectMoreDocs: jest.fn(), + deselectSomeDocs: jest.fn(), + replaceSelectedDocs: jest.fn(), + clearAllSelectedDocs: jest.fn(), + }; +} diff --git a/packages/kbn-unified-data-table/index.ts b/packages/kbn-unified-data-table/index.ts index 0929c33208fa0..7dace83c3774e 100644 --- a/packages/kbn-unified-data-table/index.ts +++ b/packages/kbn-unified-data-table/index.ts @@ -25,7 +25,7 @@ export { getRowsPerPageOptions } from './src/utils/rows_per_page'; export { popularizeField } from './src/utils/popularize_field'; export { useColumns } from './src/hooks/use_data_grid_columns'; -export { OPEN_DETAILS, SELECT_ROW } from './src/components/data_table_columns'; +export { OPEN_DETAILS, SELECT_ROW } from './src/components/data_table_columns'; // TODO: deprecate? export { DataTableRowControl } from './src/components/data_table_row_control'; export type { diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.test.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.test.tsx index 5bf4c631cf5cf..63856a2c82ac2 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.test.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.test.tsx @@ -52,7 +52,7 @@ const getDocById = (id: string) => docs.find((doc) => doc.raw._id === id); const renderCompareDocuments = ({ forceShowAllFields = false, }: { forceShowAllFields?: boolean } = {}) => { - const setSelectedDocs = jest.fn(); + const replaceSelectedDocs = jest.fn(); const getCompareDocuments = (props?: Partial<CompareDocumentsProps>) => ( <CompareDocuments id="test" @@ -63,20 +63,20 @@ const renderCompareDocuments = ({ dataView={dataViewWithTimefieldMock} isPlainRecord={false} selectedFieldNames={['message', 'extension', 'bytes']} - selectedDocs={['0', '1', '2']} + selectedDocIds={['0', '1', '2']} schemaDetectors={[]} forceShowAllFields={forceShowAllFields} showFullScreenButton={true} fieldFormats={{} as any} getDocById={getDocById} - setSelectedDocs={setSelectedDocs} + replaceSelectedDocs={replaceSelectedDocs} setIsCompareActive={jest.fn()} {...props} /> ); const { rerender } = render(getCompareDocuments()); return { - setSelectedDocs, + replaceSelectedDocs, rerender: (props?: Partial<CompareDocumentsProps>) => rerender(getCompareDocuments(props)), }; }; @@ -146,10 +146,10 @@ describe('CompareDocuments', () => { }); it('should set selected docs when columns change', () => { - const { setSelectedDocs } = renderCompareDocuments(); + const { replaceSelectedDocs } = renderCompareDocuments(); const visibleColumns = ['fields_generated-id', '0', '1', '2']; mockDataGridProps?.columnVisibility.setVisibleColumns(visibleColumns); - expect(setSelectedDocs).toHaveBeenCalledWith(visibleColumns.slice(1)); + expect(replaceSelectedDocs).toHaveBeenCalledWith(visibleColumns.slice(1)); }); it('should force show all fields when prop is true', () => { diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.tsx index f04e8888f37a4..5679ac0f236b9 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/compare_documents.tsx @@ -42,13 +42,13 @@ export interface CompareDocumentsProps { dataView: DataView; isPlainRecord: boolean; selectedFieldNames: string[]; - selectedDocs: string[]; + selectedDocIds: string[]; schemaDetectors: EuiDataGridSchemaDetector[]; forceShowAllFields: boolean; showFullScreenButton?: boolean; fieldFormats: FieldFormatsStart; getDocById: (id: string) => DataTableRecord | undefined; - setSelectedDocs: (selectedDocs: string[]) => void; + replaceSelectedDocs: (docIds: string[]) => void; setIsCompareActive: (isCompareActive: boolean) => void; additionalFieldGroups?: AdditionalFieldGroups; } @@ -69,13 +69,13 @@ const CompareDocuments = ({ isPlainRecord, selectedFieldNames, additionalFieldGroups, - selectedDocs, + selectedDocIds, schemaDetectors, forceShowAllFields, showFullScreenButton, fieldFormats, getDocById, - setSelectedDocs, + replaceSelectedDocs, setIsCompareActive, }: CompareDocumentsProps) => { // Memoize getDocById to ensure we don't lose access to the comparison docs if, for example, @@ -104,7 +104,7 @@ const CompareDocuments = ({ dataView, selectedFieldNames, additionalFieldGroups, - selectedDocs, + selectedDocIds, showAllFields: Boolean(forceShowAllFields || showAllFields), showMatchingValues: Boolean(showMatchingValues), getDocById: memoizedGetDocById, @@ -113,25 +113,25 @@ const CompareDocuments = ({ wrapper, isPlainRecord, fieldColumnId, - selectedDocs, + selectedDocIds, getDocById: memoizedGetDocById, - setSelectedDocs, + replaceSelectedDocs, }); const comparisonColumnVisibility = useMemo<EuiDataGridColumnVisibility>( () => ({ visibleColumns: comparisonColumns.map(({ id: columnId }) => columnId), setVisibleColumns: (visibleColumns) => { const [_fieldColumnId, ...newSelectedDocs] = visibleColumns; - setSelectedDocs(newSelectedDocs); + replaceSelectedDocs(newSelectedDocs); }, }), - [comparisonColumns, setSelectedDocs] + [comparisonColumns, replaceSelectedDocs] ); const additionalControls = useMemo( () => ( <ComparisonControls isPlainRecord={isPlainRecord} - selectedDocs={selectedDocs} + selectedDocIds={selectedDocIds} showDiff={showDiff} diffMode={diffMode} showDiffDecorations={showDiffDecorations} @@ -150,7 +150,7 @@ const CompareDocuments = ({ diffMode, forceShowAllFields, isPlainRecord, - selectedDocs, + selectedDocIds, setDiffMode, setIsCompareActive, setShowAllFields, @@ -184,7 +184,7 @@ const CompareDocuments = ({ dataView, comparisonFields, fieldColumnId, - selectedDocs, + selectedDocIds, diffMode: showDiff ? diffMode : undefined, fieldFormats, getDocById: memoizedGetDocById, diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.test.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.test.tsx index f95c547fac0ac..545413df32941 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.test.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.test.tsx @@ -20,7 +20,7 @@ const renderComparisonControls = ({ isPlainRecord?: ComparisonControlsProps['isPlainRecord']; forceShowAllFields?: ComparisonControlsProps['forceShowAllFields']; } = {}) => { - const selectedDocs = ['0', '1', '2']; + const selectedDocIds = ['0', '1', '2']; const Wrapper = () => { const [showDiff, setShowDiff] = useState(true); const [diffMode, setDiffMode] = useState<DocumentDiffMode>('basic'); @@ -34,7 +34,7 @@ const renderComparisonControls = ({ <IntlProvider locale="en"> <ComparisonControls isPlainRecord={isPlainRecord} - selectedDocs={selectedDocs} + selectedDocIds={selectedDocIds} showDiff={showDiff} diffMode={diffMode} showDiffDecorations={showDiffDecorations} @@ -67,7 +67,7 @@ const renderComparisonControls = ({ return { getComparisonCountDisplay: () => screen.getByText( - `Comparing ${selectedDocs.length} ${isPlainRecord ? 'results' : 'documents'}` + `Comparing ${selectedDocIds.length} ${isPlainRecord ? 'results' : 'documents'}` ), getComparisonSettingsButton, clickComparisonSettingsButton: () => userEvent.click(getComparisonSettingsButton()), diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.tsx index 48c4f11960e57..61f8009bca584 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/comparison_controls.tsx @@ -30,7 +30,7 @@ import type { DocumentDiffMode } from './types'; export interface ComparisonControlsProps { isPlainRecord?: boolean; - selectedDocs: string[]; + selectedDocIds: string[]; showDiff: boolean | undefined; diffMode: DocumentDiffMode | undefined; showDiffDecorations: boolean | undefined; @@ -47,7 +47,7 @@ export interface ComparisonControlsProps { export const ComparisonControls = ({ isPlainRecord, - selectedDocs, + selectedDocIds, showDiff, diffMode, showDiffDecorations, @@ -72,13 +72,13 @@ export const ComparisonControls = ({ <FormattedMessage id="unifiedDataTable.comparingResults" defaultMessage="Comparing {documentCount} results" - values={{ documentCount: selectedDocs.length }} + values={{ documentCount: selectedDocIds.length }} /> ) : ( <FormattedMessage id="unifiedDataTable.comparingDocuments" defaultMessage="Comparing {documentCount} documents" - values={{ documentCount: selectedDocs.length }} + values={{ documentCount: selectedDocIds.length }} /> )} </strong> diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx index 636d5a1269bf7..71604adf1f004 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.test.tsx @@ -55,7 +55,7 @@ const renderComparisonCellValue = (props: Partial<UseComparisonCellValueProps> = dataView: dataViewWithTimefieldMock, comparisonFields: ['message', 'extension', 'bytes'], fieldColumnId, - selectedDocs: ['0', '1', '2'], + selectedDocIds: ['0', '1', '2'], diffMode: undefined, fieldFormats: fieldFormatsMock, getDocById, @@ -408,7 +408,7 @@ describe('useComparisonCellValue', () => { expect(calculateDiff).toHaveBeenCalledTimes(2); renderComparisonCell(cellProps2); expect(calculateDiff).toHaveBeenCalledTimes(2); - rerender({ diffMode: 'words', selectedDocs: ['1', '2', '0'] }); + rerender({ diffMode: 'words', selectedDocIds: ['1', '2', '0'] }); const cellProps3 = { ...cellProps1, columnId: '2', @@ -425,7 +425,7 @@ describe('useComparisonCellValue', () => { expect(calculateDiff).toHaveBeenCalledTimes(4); renderComparisonCell(cellProps4); expect(calculateDiff).toHaveBeenCalledTimes(4); - rerender({ diffMode: 'lines', selectedDocs: ['2', '0', '1'] }); + rerender({ diffMode: 'lines', selectedDocIds: ['2', '0', '1'] }); const cellProps5 = { ...cellProps1, columnId: '0', diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.tsx index 7b7e378e8dbe0..f5eddf807a89e 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_cell_value.tsx @@ -39,7 +39,7 @@ export interface UseComparisonCellValueProps { dataView: DataView; comparisonFields: string[]; fieldColumnId: string; - selectedDocs: string[]; + selectedDocIds: string[]; diffMode: DocumentDiffMode | undefined; fieldFormats: FieldFormatsStart; getDocById: (id: string) => DataTableRecord | undefined; @@ -50,13 +50,13 @@ export const useComparisonCellValue = ({ dataView, comparisonFields, fieldColumnId, - selectedDocs, + selectedDocIds, diffMode, fieldFormats, getDocById, additionalFieldGroups, }: UseComparisonCellValueProps) => { - const baseDocId = selectedDocs[0]; + const baseDocId = selectedDocIds[0]; const baseDoc = useMemo(() => getDocById(baseDocId)?.flattened, [baseDocId, getDocById]); const [calculateDiffMemoized] = useState(() => createCalculateDiffMemoized()); @@ -92,7 +92,7 @@ export const useComparisonCellValue = ({ ); }; -type CellValueProps = Omit<UseComparisonCellValueProps, 'selectedDocs'> & +type CellValueProps = Omit<UseComparisonCellValueProps, 'selectedDocIds'> & EuiDataGridCellValueElementProps & { baseDocId: string; baseDoc: DataTableRecord['flattened'] | undefined; diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx index c6df0f7fabcb6..212d4a04498a9 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.test.tsx @@ -78,7 +78,7 @@ const docs = generateEsHits(dataViewWithTimefieldMock, 4).map((hit) => const defaultGetDocById = (id: string) => docs.find((doc) => doc.raw._id === id); const fieldColumnId = 'fieldColumnId'; -const selectedDocs = ['0', '1', '2', '3']; +const selectedDocIds = ['0', '1', '2', '3']; const renderColumns = ({ wrapperWidth, @@ -93,7 +93,7 @@ const renderColumns = ({ if (wrapperWidth) { Object.defineProperty(wrapper, 'offsetWidth', { value: wrapperWidth }); } - const setSelectedDocs = jest.fn(); + const replaceSelectedDocs = jest.fn(); const { result: { current: columns }, } = renderHook(() => @@ -101,17 +101,17 @@ const renderColumns = ({ wrapper, isPlainRecord, fieldColumnId, - selectedDocs, + selectedDocIds, getDocById, - setSelectedDocs, + replaceSelectedDocs, }) ); - return { columns, setSelectedDocs }; + return { columns, replaceSelectedDocs }; }; describe('useComparisonColumns', () => { it('should return comparison columns', () => { - const { columns, setSelectedDocs } = renderColumns(); + const { columns, replaceSelectedDocs } = renderColumns(); expect(columns).toEqual([ { id: fieldColumnId, @@ -123,17 +123,17 @@ describe('useComparisonColumns', () => { }, getComparisonColumn({ column: { - id: selectedDocs[0], + id: selectedDocIds[0], display: expect.anything(), - displayAsText: `Pinned document: ${selectedDocs[0]}`, + displayAsText: `Pinned document: ${selectedDocIds[0]}`, }, includeRemoveAction: true, }), getComparisonColumn({ column: { - id: selectedDocs[1], - display: selectedDocs[1], - displayAsText: `Comparison document: ${selectedDocs[1]}`, + id: selectedDocIds[1], + display: selectedDocIds[1], + displayAsText: `Comparison document: ${selectedDocIds[1]}`, actions: { showMoveRight: true, }, @@ -143,9 +143,9 @@ describe('useComparisonColumns', () => { }), getComparisonColumn({ column: { - id: selectedDocs[2], - display: selectedDocs[2], - displayAsText: `Comparison document: ${selectedDocs[2]}`, + id: selectedDocIds[2], + display: selectedDocIds[2], + displayAsText: `Comparison document: ${selectedDocIds[2]}`, actions: { showMoveLeft: true, showMoveRight: true, @@ -156,9 +156,9 @@ describe('useComparisonColumns', () => { }), getComparisonColumn({ column: { - id: selectedDocs[3], - display: selectedDocs[3], - displayAsText: `Comparison document: ${selectedDocs[3]}`, + id: selectedDocIds[3], + display: selectedDocIds[3], + displayAsText: `Comparison document: ${selectedDocIds[3]}`, actions: { showMoveLeft: true, }, @@ -192,17 +192,17 @@ describe('useComparisonColumns', () => { const removeAction = actions.additional?.[1].onClick; render(<button onClick={pinAction} data-test-subj="pin" />); userEvent.click(screen.getByTestId('pin')); - expect(setSelectedDocs).toHaveBeenCalledTimes(1); - expect(setSelectedDocs).toHaveBeenLastCalledWith(['1', '0', '2', '3']); + expect(replaceSelectedDocs).toHaveBeenCalledTimes(1); + expect(replaceSelectedDocs).toHaveBeenLastCalledWith(['1', '0', '2', '3']); render(<button onClick={removeAction} data-test-subj="remove" />); userEvent.click(screen.getByTestId('remove')); - expect(setSelectedDocs).toHaveBeenCalledTimes(2); - expect(setSelectedDocs).toHaveBeenLastCalledWith(['0', '2', '3']); + expect(replaceSelectedDocs).toHaveBeenCalledTimes(2); + expect(replaceSelectedDocs).toHaveBeenLastCalledWith(['0', '2', '3']); }); it('should not set column widths if there is sufficient space', () => { const { columns } = renderColumns({ - wrapperWidth: FIELD_COLUMN_WIDTH + selectedDocs.length * DEFAULT_COLUMN_WIDTH, + wrapperWidth: FIELD_COLUMN_WIDTH + selectedDocIds.length * DEFAULT_COLUMN_WIDTH, }); expect(columns[0].initialWidth).toBe(FIELD_COLUMN_WIDTH); expect(columns[1].initialWidth).toBe(undefined); @@ -220,11 +220,12 @@ describe('useComparisonColumns', () => { }); it('should skip columns for missing docs', () => { - const getDocById = (id: string) => (id === selectedDocs[1] ? undefined : defaultGetDocById(id)); + const getDocById = (id: string) => + id === selectedDocIds[1] ? undefined : defaultGetDocById(id); const { columns } = renderColumns({ getDocById }); expect(columns).toHaveLength(4); - expect(columns[1].displayAsText).toBe(`Pinned document: ${selectedDocs[0]}`); - expect(columns[2].displayAsText).toBe(`Comparison document: ${selectedDocs[2]}`); - expect(columns[3].displayAsText).toBe(`Comparison document: ${selectedDocs[3]}`); + expect(columns[1].displayAsText).toBe(`Pinned document: ${selectedDocIds[0]}`); + expect(columns[2].displayAsText).toBe(`Comparison document: ${selectedDocIds[2]}`); + expect(columns[3].displayAsText).toBe(`Comparison document: ${selectedDocIds[3]}`); }); }); diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.tsx b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.tsx index 810dd8ba54081..64303164af3cc 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_columns.tsx @@ -22,9 +22,9 @@ export interface UseComparisonColumnsProps { wrapper: HTMLElement | null; isPlainRecord: boolean; fieldColumnId: string; - selectedDocs: string[]; + selectedDocIds: string[]; getDocById: (docId: string) => DataTableRecord | undefined; - setSelectedDocs: (selectedDocs: string[]) => void; + replaceSelectedDocs: (docIds: string[]) => void; } export const DEFAULT_COLUMN_WIDTH = 300; @@ -37,9 +37,9 @@ export const useComparisonColumns = ({ wrapper, isPlainRecord, fieldColumnId, - selectedDocs, + selectedDocIds, getDocById, - setSelectedDocs, + replaceSelectedDocs, }: UseComparisonColumnsProps) => { const comparisonColumns = useMemo<EuiDataGridColumn[]>(() => { const fieldsColumn: EuiDataGridColumn = { @@ -54,11 +54,11 @@ export const useComparisonColumns = ({ const currentColumns = [fieldsColumn]; const wrapperWidth = wrapper?.offsetWidth ?? 0; const columnWidth = - DEFAULT_COLUMN_WIDTH * selectedDocs.length + FIELD_COLUMN_WIDTH > wrapperWidth + DEFAULT_COLUMN_WIDTH * selectedDocIds.length + FIELD_COLUMN_WIDTH > wrapperWidth ? DEFAULT_COLUMN_WIDTH : undefined; - selectedDocs.forEach((docId, docIndex) => { + selectedDocIds.forEach((docId, docIndex) => { const doc = getDocById(docId); if (!doc) { @@ -75,19 +75,19 @@ export const useComparisonColumns = ({ }), size: 'xs', onClick: () => { - const newSelectedDocs = [...selectedDocs]; + const newSelectedDocs = [...selectedDocIds]; const index = newSelectedDocs.indexOf(docId); const [baseDocId] = newSelectedDocs; newSelectedDocs[0] = docId; newSelectedDocs[index] = baseDocId; - setSelectedDocs(newSelectedDocs); + replaceSelectedDocs(newSelectedDocs); }, }); } - if (selectedDocs.length > 2) { + if (selectedDocIds.length > 2) { additional.push({ iconType: 'cross', label: i18n.translate('unifiedDataTable.removeFromComparison', { @@ -95,7 +95,7 @@ export const useComparisonColumns = ({ }), size: 'xs', onClick: () => { - setSelectedDocs(selectedDocs.filter((id) => id !== docId)); + replaceSelectedDocs(selectedDocIds.filter((id) => id !== docId)); }, }); } @@ -151,7 +151,7 @@ export const useComparisonColumns = ({ actions: { showHide: false, showMoveLeft: docIndex > 1, - showMoveRight: docIndex > 0 && docIndex < selectedDocs.length - 1, + showMoveRight: docIndex > 0 && docIndex < selectedDocIds.length - 1, showSortAsc: false, showSortDesc: false, additional, @@ -164,8 +164,8 @@ export const useComparisonColumns = ({ fieldColumnId, getDocById, isPlainRecord, - selectedDocs, - setSelectedDocs, + selectedDocIds, + replaceSelectedDocs, wrapper?.offsetWidth, ]); diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts index deb3f66a5f55d..7ff5cbce9d40f 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.test.ts @@ -50,7 +50,7 @@ const renderFields = ({ useComparisonFields({ dataView, selectedFieldNames: ['message', 'extension', 'bytes'], - selectedDocs: ['0', '1', '2'], + selectedDocIds: ['0', '1', '2'], showAllFields: true, showMatchingValues: true, getDocById, diff --git a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.ts b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.ts index b1653c8e26336..d7bf3d758e1e4 100644 --- a/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.ts +++ b/packages/kbn-unified-data-table/src/components/compare_documents/hooks/use_comparison_fields.ts @@ -17,7 +17,7 @@ export const MAX_COMPARISON_FIELDS = 250; export interface UseComparisonFieldsProps { dataView: DataView; selectedFieldNames: string[]; - selectedDocs: string[]; + selectedDocIds: string[]; showAllFields: boolean; showMatchingValues: boolean; getDocById: (id: string) => DataTableRecord | undefined; @@ -27,14 +27,14 @@ export interface UseComparisonFieldsProps { export const useComparisonFields = ({ dataView, selectedFieldNames, - selectedDocs, + selectedDocIds, showAllFields, showMatchingValues, getDocById, additionalFieldGroups, }: UseComparisonFieldsProps) => { const { baseDoc, comparisonDocs } = useMemo(() => { - const [baseDocId, ...comparisonDocIds] = selectedDocs; + const [baseDocId, ...comparisonDocIds] = selectedDocIds; return { baseDoc: getDocById(baseDocId), @@ -42,7 +42,7 @@ export const useComparisonFields = ({ .map((docId) => getDocById(docId)) .filter((doc): doc is DataTableRecord => Boolean(doc)), }; - }, [getDocById, selectedDocs]); + }, [getDocById, selectedDocIds]); return useMemo(() => { let comparisonFields = convertFieldsToFallbackFields({ diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.test.tsx new file mode 100644 index 0000000000000..044b864213d82 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAdditionalRowControlColumns } from './get_additional_row_control_columns'; +import { mockRowAdditionalLeadingControls } from '../../../../__mocks__/external_control_columns'; + +describe('getAdditionalRowControlColumns', () => { + it('should work correctly for 0 controls', () => { + const columns = getAdditionalRowControlColumns([]); + + expect(columns).toHaveLength(0); + }); + + it('should work correctly for 1 control', () => { + const columns = getAdditionalRowControlColumns([mockRowAdditionalLeadingControls[0]]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + ]); + }); + + it('should work correctly for 2 controls', () => { + const columns = getAdditionalRowControlColumns([ + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + ]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + `additionalRowControl_${mockRowAdditionalLeadingControls[1].id}`, + ]); + }); + + it('should work correctly for 3 and more controls', () => { + const columns = getAdditionalRowControlColumns([ + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + mockRowAdditionalLeadingControls[2], + ]); + + expect(columns.map((column) => column.id)).toEqual([ + `additionalRowControl_${mockRowAdditionalLeadingControls[0].id}`, + `additionalRowControl_menuControl`, + ]); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.ts new file mode 100644 index 0000000000000..bd297b37bb5df --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/get_additional_row_control_columns.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 type { EuiDataGridControlColumn } from '@elastic/eui'; +import type { RowControlColumn } from '../../../types'; +import { getRowControlColumn } from './row_control_column'; +import { getRowMenuControlColumn } from './row_menu_control_column'; + +export const getAdditionalRowControlColumns = ( + rowControlColumns: RowControlColumn[] +): EuiDataGridControlColumn[] => { + if (rowControlColumns.length <= 2) { + return rowControlColumns.map(getRowControlColumn); + } + + return [ + getRowControlColumn(rowControlColumns[0]), + getRowMenuControlColumn(rowControlColumns.slice(1)), + ]; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/index.ts new file mode 100644 index 0000000000000..d3a79fc3a0d50 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/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 { getAdditionalRowControlColumns } from './get_additional_row_control_columns'; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx new file mode 100644 index 0000000000000..360fa7bc235c4 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.test.tsx @@ -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 React from 'react'; +import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { render, screen } from '@testing-library/react'; +import { getRowControlColumn } from './row_control_column'; +import { dataTableContextMock } from '../../../../__mocks__/table_context'; +import { UnifiedDataTableContext } from '../../../table_context'; + +describe('getRowControlColumn', () => { + const contextMock = { + ...dataTableContextMock, + }; + + it('should render the component', () => { + const mockClick = jest.fn(); + const props = { + id: 'test_row_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + <Control label={`test-${rowProps.rowIndex}`} iconType="heart" onClick={mockClick} /> + )), + }; + const rowControlColumn = getRowControlColumn(props); + const RowControlColumn = + rowControlColumn.rowCellRender as React.FC<EuiDataGridCellValueElementProps>; + render( + <UnifiedDataTableContext.Provider value={contextMock}> + <RowControlColumn + rowIndex={1} + setCellProps={jest.fn()} + columnId={props.id} + colIndex={0} + isDetails={false} + isExpandable={false} + isExpanded={false} + /> + </UnifiedDataTableContext.Provider> + ); + const button = screen.getByTestId('unifiedDataTable_rowControl_test_row_control'); + expect(button).toBeInTheDocument(); + + button.click(); + + expect(mockClick).toHaveBeenCalledWith({ record: contextMock.rows[1], rowIndex: 1 }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx new file mode 100644 index 0000000000000..f8d3ad063fb2d --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_control_column.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { + EuiButtonIcon, + EuiDataGridCellValueElementProps, + EuiDataGridControlColumn, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; +import { DataTableRowControl, Size } from '../../data_table_row_control'; +import type { RowControlColumn, RowControlProps } from '../../../types'; +import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; +import { useControlColumn } from '../../../hooks/use_control_column'; + +export const RowControlCell = ({ + renderControl, + ...props +}: EuiDataGridCellValueElementProps & { + renderControl: RowControlColumn['renderControl']; +}) => { + const rowProps = useControlColumn(props); + + const Control: React.FC<RowControlProps> = useMemo( + () => + ({ 'data-test-subj': dataTestSubj, color, disabled, label, iconType, onClick }) => { + return ( + <DataTableRowControl size={Size.normal}> + <EuiToolTip content={label} delay="long"> + <EuiButtonIcon + data-test-subj={dataTestSubj ?? `unifiedDataTable_rowControl_${props.columnId}`} + disabled={disabled} + iconSize="s" + iconType={iconType} + color={color ?? 'text'} + aria-label={label} + onClick={() => { + onClick?.(rowProps); + }} + /> + </EuiToolTip> + </DataTableRowControl> + ); + }, + [props.columnId, rowProps] + ); + + return renderControl(Control, rowProps); +}; + +export const getRowControlColumn = ( + rowControlColumn: RowControlColumn +): EuiDataGridControlColumn => { + const { id, headerAriaLabel, headerCellRender, renderControl } = rowControlColumn; + + return { + id: `additionalRowControl_${id}`, + width: DEFAULT_CONTROL_COLUMN_WIDTH, + headerCellRender: + headerCellRender ?? + (() => ( + <EuiScreenReaderOnly> + <span>{headerAriaLabel}</span> + </EuiScreenReaderOnly> + )), + rowCellRender: (props) => { + return <RowControlCell {...props} renderControl={renderControl} />; + }, + }; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.test.tsx new file mode 100644 index 0000000000000..8e26e3f01d062 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.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 type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import { render, screen } from '@testing-library/react'; +import { getRowMenuControlColumn } from './row_menu_control_column'; +import { dataTableContextMock } from '../../../../__mocks__/table_context'; +import { mockRowAdditionalLeadingControls } from '../../../../__mocks__/external_control_columns'; +import { UnifiedDataTableContext } from '../../../table_context'; + +describe('getRowMenuControlColumn', () => { + const contextMock = { + ...dataTableContextMock, + }; + + it('should render the component', () => { + const mockClick = jest.fn(); + const props = { + id: 'test_row_menu_control', + headerAriaLabel: 'row control', + renderControl: jest.fn((Control, rowProps) => ( + <Control label={`test-${rowProps.rowIndex}`} iconType="heart" onClick={mockClick} /> + )), + }; + const rowMenuControlColumn = getRowMenuControlColumn([ + props, + mockRowAdditionalLeadingControls[0], + mockRowAdditionalLeadingControls[1], + ]); + const RowMenuControlColumn = + rowMenuControlColumn.rowCellRender as React.FC<EuiDataGridCellValueElementProps>; + render( + <UnifiedDataTableContext.Provider value={contextMock}> + <RowMenuControlColumn + rowIndex={1} + setCellProps={jest.fn()} + columnId={props.id} + colIndex={0} + isDetails={false} + isExpandable={false} + isExpanded={false} + /> + </UnifiedDataTableContext.Provider> + ); + const menuButton = screen.getByTestId('unifiedDataTable_test_row_menu_control'); + expect(menuButton).toBeInTheDocument(); + + menuButton.click(); + + expect(screen.getByTestId('exampleRowControl-visBarVerticalStacked')).toBeInTheDocument(); + expect(screen.getByTestId('exampleRowControl-heart')).toBeInTheDocument(); + + const button = screen.getByTestId('unifiedDataTable_rowMenu_test_row_menu_control'); + expect(button).toBeInTheDocument(); + + button.click(); + expect(mockClick).toHaveBeenCalledWith({ record: contextMock.rows[1], rowIndex: 1 }); + }); +}); diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx new file mode 100644 index 0000000000000..917174618fa37 --- /dev/null +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/additional_row_control/row_menu_control_column.tsx @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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, { Fragment, useCallback, useMemo, useState } from 'react'; +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiDataGridCellValueElementProps, + EuiDataGridControlColumn, + EuiPopover, + EuiScreenReaderOnly, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import { DataTableRowControl, Size } from '../../data_table_row_control'; +import type { RowControlColumn, RowControlProps } from '../../../types'; +import { DEFAULT_CONTROL_COLUMN_WIDTH } from '../../../constants'; +import { useControlColumn } from '../../../hooks/use_control_column'; + +/** + * Menu button under which all other additional row controls would be placed + */ +export const RowMenuControlCell = ({ + rowControlColumns, + ...props +}: EuiDataGridCellValueElementProps & { + rowControlColumns: RowControlColumn[]; +}) => { + const rowProps = useControlColumn(props); + const [isMoreActionsPopoverOpen, setIsMoreActionsPopoverOpen] = useState<boolean>(false); + + const buttonLabel = i18n.translate('unifiedDataTable.grid.additionalRowActions', { + defaultMessage: 'Additional actions', + }); + + const getControlComponent: (id: string) => React.FC<RowControlProps> = useCallback( + (id) => + ({ 'data-test-subj': dataTestSubj, color, disabled, label, iconType, onClick }) => { + return ( + <EuiContextMenuItem + data-test-subj={dataTestSubj ?? `unifiedDataTable_rowMenu_${id}`} + disabled={disabled} + icon={iconType} + color={color} + onClick={() => { + onClick?.(rowProps); + setIsMoreActionsPopoverOpen(false); + }} + > + {label} + </EuiContextMenuItem> + ); + }, + [rowProps, setIsMoreActionsPopoverOpen] + ); + + const popoverMenuItems = useMemo( + () => + rowControlColumns.map((rowControlColumn) => { + const Control = getControlComponent(rowControlColumn.id); + return ( + <Fragment key={rowControlColumn.id}> + {rowControlColumn.renderControl(Control, rowProps)} + </Fragment> + ); + }), + [rowControlColumns, rowProps, getControlComponent] + ); + + return ( + <EuiPopover + id={`rowMenuActionsPopover_${props.rowIndex}`} + button={ + <DataTableRowControl size={Size.normal}> + <EuiToolTip content={buttonLabel} delay="long"> + <EuiButtonIcon + data-test-subj={`unifiedDataTable_${props.columnId}`} + iconSize="s" + iconType="boxesVertical" + color="text" + aria-label={buttonLabel} + css={css` + .euiDataGridRowCell__content--defaultHeight & { + margin-top: 2px; // to align with other controls + } + `} + onClick={() => { + setIsMoreActionsPopoverOpen(!isMoreActionsPopoverOpen); + }} + /> + </EuiToolTip> + </DataTableRowControl> + } + isOpen={isMoreActionsPopoverOpen} + closePopover={() => setIsMoreActionsPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + <EuiContextMenuPanel size="s" items={popoverMenuItems} /> + </EuiPopover> + ); +}; + +export const getRowMenuControlColumn = ( + rowControlColumns: RowControlColumn[] +): EuiDataGridControlColumn => { + return { + id: 'additionalRowControl_menuControl', + width: DEFAULT_CONTROL_COLUMN_WIDTH, + headerCellRender: () => ( + <EuiScreenReaderOnly> + <span> + {i18n.translate('unifiedDataTable.additionalActionsColumnHeader', { + defaultMessage: 'Additional actions column', + })} + </span> + </EuiScreenReaderOnly> + ), + rowCellRender: (props) => { + return <RowMenuControlCell {...props} rowControlColumns={rowControlColumns} />; + }, + }; +}; diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx b/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx index dd9be4ab90d13..902667810e613 100644 --- a/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useContext, useEffect } from 'react'; +import React from 'react'; import { css } from '@emotion/react'; import { EuiDataGridControlColumn, @@ -15,7 +15,7 @@ import { EuiDataGridCellValueElementProps, } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils'; -import { UnifiedDataTableContext } from '../../../table_context'; +import { useControlColumn } from '../../../hooks/use_control_column'; const COLOR_INDICATOR_WIDTH = 4; @@ -28,32 +28,14 @@ interface ColorIndicatorCellParams { ) => { color: string; label: string } | undefined; } -const ColorIndicatorCell: React.FC<ColorIndicatorCellParams> = ({ - rowIndex, - setCellProps, - getRowIndicator, -}) => { +const ColorIndicatorCell: React.FC<ColorIndicatorCellParams> = ({ getRowIndicator, ...props }) => { + const { record } = useControlColumn(props); const { euiTheme } = useEuiTheme(); - const { rows, expanded } = useContext(UnifiedDataTableContext); - const row = rows[rowIndex]; - const configuration = row ? getRowIndicator(row, euiTheme) : undefined; + + const configuration = record ? getRowIndicator(record, euiTheme) : undefined; const color = configuration?.color || 'transparent'; const label = configuration?.label; - useEffect(() => { - if (row.isAnchor) { - setCellProps({ - className: 'unifiedDataTable__cell--highlight', - }); - } else if (expanded && row && expanded.id === row.id) { - setCellProps({ - className: 'unifiedDataTable__cell--expanded', - }); - } else { - setCellProps({ className: '' }); - } - }, [expanded, row, setCellProps]); - return ( <div data-test-subj="unifiedDataTableRowColorIndicatorCell" diff --git a/packages/kbn-unified-data-table/src/components/custom_control_columns/index.ts b/packages/kbn-unified-data-table/src/components/custom_control_columns/index.ts index 8ff889b66d9e5..7e79f6e995f4f 100644 --- a/packages/kbn-unified-data-table/src/components/custom_control_columns/index.ts +++ b/packages/kbn-unified-data-table/src/components/custom_control_columns/index.ts @@ -10,3 +10,5 @@ export { getColorIndicatorControlColumn, type ColorIndicatorControlColumnParams, } from './color_indicator'; + +export { getAdditionalRowControlColumns } from './additional_row_control'; diff --git a/packages/kbn-unified-data-table/src/components/custom_toolbar/__snapshots__/render_custom_toolbar.test.tsx.snap b/packages/kbn-unified-data-table/src/components/custom_toolbar/__snapshots__/render_custom_toolbar.test.tsx.snap index 431851e200ed8..d830b9b197f98 100644 --- a/packages/kbn-unified-data-table/src/components/custom_toolbar/__snapshots__/render_custom_toolbar.test.tsx.snap +++ b/packages/kbn-unified-data-table/src/components/custom_toolbar/__snapshots__/render_custom_toolbar.test.tsx.snap @@ -87,9 +87,7 @@ exports[`renderCustomToolbar should render correctly with an element 1`] = ` <EuiFlexItem grow={false} > - <div - className="unifiedDataTableToolbarControlButton" - > + <div> additional </div> </EuiFlexItem> @@ -190,9 +188,7 @@ exports[`renderCustomToolbar should render successfully 1`] = ` <EuiFlexItem grow={false} > - <div - className="unifiedDataTableToolbarControlButton" - > + <div> additional </div> </EuiFlexItem> diff --git a/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.scss b/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.scss index 054627d9340a6..fc6424e06ab62 100644 --- a/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.scss +++ b/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.scss @@ -1,58 +1,68 @@ .unifiedDataTableToolbar { padding: $euiSizeS $euiSizeS $euiSizeXS; -} -.unifiedDataTableToolbarControlButton .euiDataGridToolbarControl { - block-size: $euiSizeXL; - border: $euiBorderThin; - border-radius: $euiBorderRadiusSmall; + .unifiedDataTableToolbarControlButton .euiDataGridToolbarControl { + block-size: $euiSizeXL; + border: $euiBorderThin; + border-radius: $euiBorderRadiusSmall; - // making the icons larger than the default size - & svg { - inline-size: $euiSize; - block-size: $euiSize; - } + // making the icons larger than the default size + & svg { + inline-size: $euiSize; + block-size: $euiSize; + } - // cancel default background changes - &:active, &:focus { - background: transparent; - } + // cancel default background changes + &:active, &:focus { + background: transparent; + } - // add toolbar control animation - transition: transform $euiAnimSpeedNormal ease-in-out; - &:hover { - transform: translateY(-1px); + // add toolbar control animation + transition: transform $euiAnimSpeedNormal ease-in-out; + &:hover { + transform: translateY(-1px); + } + &:active { + transform: translateY(0); + } } - &:active { - transform: translateY(0); - } -} -.unifiedDataTableToolbarControlGroup { - box-shadow: inset 0 0 0 $euiBorderWidthThin $euiBorderColor; - border-radius: $euiBorderRadiusSmall; - display: inline-flex; - align-items: stretch; - flex-direction: row; -} + .unifiedDataTableToolbarControlGroup { + box-shadow: inset 0 0 0 $euiBorderWidthThin $euiBorderColor; + border-radius: $euiBorderRadiusSmall; + display: inline-flex; + align-items: stretch; + flex-direction: row; -.unifiedDataTableToolbarControlIconButton .euiButtonIcon { - inline-size: $euiSizeXL; - block-size: $euiSizeXL; + & .unifiedDataTableToolbarControlButton .euiDataGridToolbarControl { + border-radius: 0; + border: none; + } - // cancel default behaviour - &:hover, &:active, &:focus { - background: transparent; - animation: none !important; - transform: none !important; + & .unifiedDataTableToolbarControlButton + .unifiedDataTableToolbarControlButton { + border-inline-start: $euiBorderThin; + border-radius: 0; + } } - .unifiedDataTableToolbarControlIconButton + & { + .unifiedDataTableToolbarControlIconButton .euiButtonIcon { + inline-size: $euiSizeXL; + block-size: $euiSizeXL; + + // cancel default behaviour + &:hover, &:active, &:focus { + background: transparent; + animation: none !important; + transform: none !important; + } + } + + .unifiedDataTableToolbarControlIconButton + .unifiedDataTableToolbarControlIconButton { border-inline-start: $euiBorderThin; border-radius: 0; } -} -.unifiedDataTableToolbarBottom { - position: relative; // for placing a loading indicator correctly + .unifiedDataTableToolbarBottom { + position: relative; // for placing a loading indicator correctly + } } diff --git a/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.tsx b/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.tsx index cda9960876457..6d52830197b76 100644 --- a/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.tsx +++ b/packages/kbn-unified-data-table/src/components/custom_toolbar/render_custom_toolbar.tsx @@ -52,7 +52,7 @@ export const internalRenderCustomToolbar = ( <> {leftSide && additionalControls && ( <EuiFlexItem grow={false}> - <div className="unifiedDataTableToolbarControlButton">{additionalControls}</div> + <div>{additionalControls}</div> </EuiFlexItem> )} {columnControl && ( @@ -67,7 +67,7 @@ export const internalRenderCustomToolbar = ( )} {!leftSide && additionalControls && ( <EuiFlexItem grow={false}> - <div className="unifiedDataTableToolbarControlButton">{additionalControls}</div> + <div>{additionalControls}</div> </EuiFlexItem> )} </> diff --git a/packages/kbn-unified-data-table/src/components/data_table.scss b/packages/kbn-unified-data-table/src/components/data_table.scss index cb00516ee63e6..f80b6cdab904c 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.scss +++ b/packages/kbn-unified-data-table/src/components/data_table.scss @@ -55,9 +55,18 @@ .euiDataGridRowCell--controlColumn .euiDataGridRowCell__content, .euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='openDetails'], - .euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='select'] { + .euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id='select'], + .euiDataGridRowCell.euiDataGridRowCell--controlColumn[data-gridcell-column-id^='additionalRowControl_'], + .euiDataGridHeaderCell.euiDataGridHeaderCell--controlColumn[data-gridcell-column-id^='additionalRowControl_'] { padding-left: 0; padding-right: 0; + border-left: 0; + border-right: 0; + } + + .euiDataGridHeaderCell.euiDataGridHeaderCell--controlColumn[data-gridcell-column-id='select'] { + padding-left: $euiSizeXS; + padding-right: 0; } .euiDataGridHeaderCell.euiDataGridHeaderCell--controlColumn[data-gridcell-column-id='colorIndicator'], @@ -132,6 +141,13 @@ .euiDataGridRowCell__content--defaultHeight & { // "Single line" row height setting margin-top: 0; } + + &--size-normal { + display: inline-block; + width: $euiSizeL; + height: $euiSizeL; + overflow: hidden; + } } .unifiedDataTable__descriptionList { 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 index e1bd33f711949..6366aa4445630 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.test.tsx @@ -26,6 +26,7 @@ import { buildDataTableRecord, getDocId } from '@kbn/discover-utils'; import type { DataTableRecord, EsHitRecord } from '@kbn/discover-utils/types'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { + mockRowAdditionalLeadingControls, testLeadingControlColumn, testTrailingControlColumns, } from '../../__mocks__/external_control_columns'; @@ -451,9 +452,8 @@ describe('UnifiedDataTable', () => { }); }); - describe('customControlColumnsConfiguration', () => { - const customControlColumnsConfiguration = jest.fn(); - it('should be able to customise the leading control column', async () => { + describe('custom control columns', () => { + it('should be able to customise the leading controls', async () => { const component = await getComponent({ ...getProps(), expandedDoc: { @@ -467,23 +467,19 @@ describe('UnifiedDataTable', () => { setExpandedDoc: jest.fn(), renderDocumentView: jest.fn(), externalControlColumns: [testLeadingControlColumn], - customControlColumnsConfiguration: customControlColumnsConfiguration.mockImplementation( - () => { - return { - leadingControlColumns: [testLeadingControlColumn, testTrailingControlColumns[0]], - trailingControlColumns: [], - }; - } - ), + rowAdditionalLeadingControls: mockRowAdditionalLeadingControls, }); expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy(); expect( - findTestSubject(component, 'test-trailing-column-popover-button').exists() + findTestSubject(component, 'exampleRowControl-visBarVerticalStacked').exists() + ).toBeTruthy(); + expect( + findTestSubject(component, 'unifiedDataTable_additionalRowControl_menuControl').exists() ).toBeTruthy(); }); - it('should be able to customise the trailing control column', async () => { + it('should be able to customise the trailing controls', async () => { const component = await getComponent({ ...getProps(), expandedDoc: { @@ -497,14 +493,7 @@ describe('UnifiedDataTable', () => { setExpandedDoc: jest.fn(), renderDocumentView: jest.fn(), externalControlColumns: [testLeadingControlColumn], - customControlColumnsConfiguration: customControlColumnsConfiguration.mockImplementation( - () => { - return { - leadingControlColumns: [], - trailingControlColumns: [testLeadingControlColumn, testTrailingControlColumns[0]], - }; - } - ), + trailingControlColumns: testTrailingControlColumns, }); expect(findTestSubject(component, 'test-body-control-column-cell').exists()).toBeTruthy(); @@ -691,7 +680,7 @@ describe('UnifiedDataTable', () => { // additional controls become available after selecting a document act(() => { component - .find('[data-gridcell-column-id="select"] .euiCheckbox__input') + .find('.euiDataGridRowCell[data-gridcell-column-id="select"] .euiCheckbox__input') .first() .simulate('change'); }); @@ -767,17 +756,28 @@ describe('UnifiedDataTable', () => { ); }; - const getSelectedDocumentsButton = () => screen.queryByRole('button', { name: /Selected/ }); + const getSelectedDocumentsButton = () => screen.queryByTestId('unifiedDataTableSelectionBtn'); const selectDocument = (document: EsHitRecord) => userEvent.click(screen.getByTestId(`dscGridSelectDoc-${getDocId(document)}`)); - const getCompareDocumentsButton = () => screen.queryByRole('button', { name: /Compare/ }); + const openSelectedRowsMenu = async () => { + userEvent.click(await screen.findByTestId('unifiedDataTableSelectionBtn')); + await screen.findAllByText('Clear selection'); + }; + + const closeSelectedRowsMenu = async () => { + userEvent.click(await screen.findByTestId('unifiedDataTableSelectionBtn')); + }; + + const getCompareDocumentsButton = () => + screen.queryByTestId('unifiedDataTableCompareSelectedDocuments'); const goToComparisonMode = async () => { selectDocument(esHitsMock[0]); selectDocument(esHitsMock[1]); - userEvent.click(getCompareDocumentsButton()!); + await openSelectedRowsMenu(); + userEvent.click(await screen.findByTestId('unifiedDataTableCompareSelectedDocuments')); await screen.findByText('Comparing 2 documents'); }; @@ -796,22 +796,28 @@ describe('UnifiedDataTable', () => { const getCellValues = () => Array.from(document.querySelectorAll(`.${CELL_CLASS}`)).map(({ textContent }) => textContent); - it('should not allow comparison if less than 2 documents are selected', () => { + it('should not allow comparison if less than 2 documents are selected', async () => { renderDataTable({ enableComparisonMode: true }); expect(getSelectedDocumentsButton()).not.toBeInTheDocument(); selectDocument(esHitsMock[0]); expect(getSelectedDocumentsButton()).toBeInTheDocument(); + await openSelectedRowsMenu(); expect(getCompareDocumentsButton()).not.toBeInTheDocument(); + await closeSelectedRowsMenu(); selectDocument(esHitsMock[1]); expect(getSelectedDocumentsButton()).toBeInTheDocument(); + await openSelectedRowsMenu(); expect(getCompareDocumentsButton()).toBeInTheDocument(); + await closeSelectedRowsMenu(); }); - it('should not allow comparison if comparison mode is disabled', () => { + it('should not allow comparison if comparison mode is disabled', async () => { renderDataTable({ enableComparisonMode: false }); selectDocument(esHitsMock[0]); selectDocument(esHitsMock[1]); + await openSelectedRowsMenu(); expect(getCompareDocumentsButton()).not.toBeInTheDocument(); + await closeSelectedRowsMenu(); }); it('should allow comparison if 2 or more documents are selected and comparison mode is enabled', async () => { diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx index a47f7e389b743..243b86b540865 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useCallback, useMemo, useState, useRef, useEffect } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classnames from 'classnames'; import { FormattedMessage } from '@kbn/i18n-react'; import { of } from 'rxjs'; @@ -29,8 +29,6 @@ import { EuiDataGridToolBarVisibilityDisplaySelectorOptions, EuiDataGridStyle, EuiDataGridProps, - EuiFlexGroup, - EuiFlexItem, } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; import { @@ -53,25 +51,23 @@ import { DataTableColumnsMeta, CustomCellRenderer, CustomGridColumnsConfiguration, - CustomControlColumnConfiguration, + RowControlColumn, } 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 { - getAllControlColumns, getEuiGridColumns, getLeadControlColumns, getVisibleColumns, canPrependTimeFieldColumn, + SELECT_ROW, + OPEN_DETAILS, } from './data_table_columns'; import { UnifiedDataTableContext } from '../table_context'; import { getSchemaDetectors } from './data_table_schema'; -import { - DataTableCompareToolbarBtn, - DataTableDocumentToolbarBtn, -} from './data_table_document_selection'; +import { DataTableDocumentToolbarBtn } from './data_table_document_selection'; import { useRowHeightsOptions } from '../hooks/use_row_heights_options'; import { DEFAULT_ROWS_PER_PAGE, @@ -86,11 +82,15 @@ import { CompareDocuments } from './compare_documents'; import { useFullScreenWatcher } from '../hooks/use_full_screen_watcher'; import { UnifiedDataTableRenderCustomToolbar } from './custom_toolbar/render_custom_toolbar'; import { getCustomCellPopoverRenderer } from '../utils/get_render_cell_popover'; +import { useSelectedDocs } from '../hooks/use_selected_docs'; import { getColorIndicatorControlColumn, type ColorIndicatorControlColumnParams, + getAdditionalRowControlColumns, } from './custom_control_columns'; +const CONTROL_COLUMN_IDS_DEFAULT = [SELECT_ROW, OPEN_DETAILS]; + export type SortOrder = [string, string]; export enum DataLoadingState { @@ -294,9 +294,20 @@ export interface UnifiedDataTableProps { */ maxDocFieldsDisplayed?: number; /** + * @deprecated Use only `rowAdditionalLeadingControls` instead * Optional value for providing EuiDataGridControlColumn list of the additional leading control columns. UnifiedDataTable includes two control columns: Open Details and Select. */ externalControlColumns?: EuiDataGridControlColumn[]; + /** + * An optional list of the EuiDataGridControlColumn type for setting trailing control columns standard for EuiDataGrid. + * We recommend to rather position all controls in the beginning of rows and use `rowAdditionalLeadingControls` for that + * as number of columns can be dynamically changed and we don't want the controls to become hidden due to horizontal scroll. + */ + trailingControlColumns?: EuiDataGridControlColumn[]; + /** + * Optional value to extend the list of default row actions + */ + rowAdditionalLeadingControls?: RowControlColumn[]; /** * Number total hits from ES */ @@ -331,10 +342,6 @@ export interface UnifiedDataTableProps { * @param gridProps */ renderCustomToolbar?: UnifiedDataTableRenderCustomToolbar; - /** - * 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. **/ @@ -351,10 +358,6 @@ export interface UnifiedDataTableProps { * An optional settings for customising the column */ customGridColumnsConfiguration?: CustomGridColumnsConfiguration; - /** - * An optional settings to control which columns to render as trailing and leading control columns - */ - customControlColumnsConfiguration?: CustomControlColumnConfiguration; /** * Name of the UnifiedDataTable consumer component or application */ @@ -400,8 +403,6 @@ export interface UnifiedDataTableProps { export const EuiDataGridMemoized = React.memo(EuiDataGrid); -const CONTROL_COLUMN_IDS_DEFAULT = ['openDetails', 'select']; - export const UnifiedDataTable = ({ ariaLabelledBy, columns, @@ -411,6 +412,7 @@ export const UnifiedDataTable = ({ headerRowHeightState, onUpdateHeaderRowHeight, controlColumnIds = CONTROL_COLUMN_IDS_DEFAULT, + rowAdditionalLeadingControls, dataView, loadingState, onFilter, @@ -441,7 +443,8 @@ export const UnifiedDataTable = ({ services, renderCustomGridBody, renderCustomToolbar, - trailingControlColumns, + externalControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls + trailingControlColumns, // TODO: deprecate in favor of rowAdditionalLeadingControls totalHits, onFetchMoreRecords, renderDocumentView, @@ -450,7 +453,6 @@ export const UnifiedDataTable = ({ configRowHeight, showMultiFields = true, maxDocFieldsDisplayed = 50, - externalControlColumns, externalAdditionalControls, rowsPerPageOptions, visibleCellActions, @@ -462,7 +464,6 @@ export const UnifiedDataTable = ({ rowLineHeightOverride, cellActionsMetadata, customGridColumnsConfiguration, - customControlColumnsConfiguration, enableComparisonMode, cellContext, renderCellPopover, @@ -472,39 +473,35 @@ export const UnifiedDataTable = ({ services; const { darkMode } = useObservable(services.theme?.theme$ ?? of(themeDefault), themeDefault); const dataGridRef = useRef<EuiDataGridRefProps>(null); - const [selectedDocs, setSelectedDocs] = useState<string[]>([]); const [isFilterActive, setIsFilterActive] = useState(false); const [isCompareActive, setIsCompareActive] = useState(false); const displayedColumns = getDisplayedColumns(columns, dataView); const defaultColumns = displayedColumns.includes('_source'); const docMap = useMemo(() => new Map(rows?.map((row) => [row.id, row]) ?? []), [rows]); const getDocById = useCallback((id: string) => docMap.get(id), [docMap]); - const usedSelectedDocs = useMemo(() => { - if (!selectedDocs.length || !rows?.length) { - return []; - } - // filter out selected docs that are no longer part of the current data - const result = selectedDocs.filter((docId) => !!getDocById(docId)); - if (result.length === 0 && isFilterActive) { + const selectedDocsState = useSelectedDocs(docMap); + const { isDocSelected, hasSelectedDocs, selectedDocIds, replaceSelectedDocs } = selectedDocsState; + + useEffect(() => { + if (!hasSelectedDocs && isFilterActive) { setIsFilterActive(false); } - return result; - }, [selectedDocs, rows?.length, isFilterActive, getDocById]); + }, [isFilterActive, hasSelectedDocs, setIsFilterActive]); const displayedRows = useMemo(() => { if (!rows) { return []; } - if (!isFilterActive || usedSelectedDocs.length === 0) { + if (!isFilterActive || !hasSelectedDocs) { return rows; } - const rowsFiltered = rows.filter((row) => usedSelectedDocs.includes(row.id)); + const rowsFiltered = rows.filter((row) => isDocSelected(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]); + }, [rows, isFilterActive, hasSelectedDocs, isDocSelected]); const valueToStringConverter: ValueToStringConverter = useCallback( (rowIndex, columnId, options) => { @@ -520,40 +517,6 @@ export const UnifiedDataTable = ({ [displayedRows, dataView, fieldFormats] ); - const unifiedDataTableContextValue = useMemo( - () => ({ - expanded: expandedDoc, - setExpanded: setExpandedDoc, - rows: displayedRows, - onFilter, - dataView, - isDarkMode: darkMode, - selectedDocs: usedSelectedDocs, - setSelectedDocs: (newSelectedDocs: React.SetStateAction<string[]>) => { - setSelectedDocs(newSelectedDocs); - if (isFilterActive && newSelectedDocs.length === 0) { - setIsFilterActive(false); - } - }, - valueToStringConverter, - componentsTourSteps, - isPlainRecord, - }), - [ - componentsTourSteps, - darkMode, - dataView, - isPlainRecord, - displayedRows, - expandedDoc, - isFilterActive, - onFilter, - setExpandedDoc, - usedSelectedDocs, - valueToStringConverter, - ] - ); - /** * Pagination */ @@ -605,6 +568,37 @@ export const UnifiedDataTable = ({ ); }, [currentPageSize, setPagination]); + const unifiedDataTableContextValue = useMemo( + () => ({ + expanded: expandedDoc, + setExpanded: setExpandedDoc, + rows: displayedRows, + onFilter, + dataView, + isDarkMode: darkMode, + selectedDocsState, + valueToStringConverter, + componentsTourSteps, + isPlainRecord, + pageIndex: isPaginationEnabled ? paginationObj?.pageIndex : 0, + pageSize: isPaginationEnabled ? paginationObj?.pageSize : displayedRows.length, + }), + [ + componentsTourSteps, + darkMode, + dataView, + isPlainRecord, + isPaginationEnabled, + displayedRows, + expandedDoc, + onFilter, + setExpandedDoc, + selectedDocsState, + paginationObj, + valueToStringConverter, + ] + ); + const shouldShowFieldHandler = useMemo(() => { const dataViewFields = dataView.fields.getAll().map((fld) => fld.name); return getShouldShowFieldHandler(dataViewFields, dataView, showMultiFields); @@ -858,10 +852,19 @@ export const UnifiedDataTable = ({ const canSetExpandedDoc = Boolean(setExpandedDoc && !!renderDocumentView); const leadingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { - const internalControlColumns = getLeadControlColumns(canSetExpandedDoc).filter(({ id }) => - controlColumnIds.includes(id) - ); - const leadingColumns = externalControlColumns + const defaultControlColumns = getLeadControlColumns(canSetExpandedDoc); + const internalControlColumns = controlColumnIds + ? // reorder the default controls as per controlColumnIds + controlColumnIds.reduce((acc, id) => { + const controlColumn = defaultControlColumns.find((col) => col.id === id); + if (controlColumn) { + acc.push(controlColumn); + } + return acc; + }, [] as EuiDataGridControlColumn[]) + : defaultControlColumns; + + const leadingColumns: EuiDataGridControlColumn[] = externalControlColumns ? [...internalControlColumns, ...externalControlColumns] : internalControlColumns; @@ -872,57 +875,55 @@ export const UnifiedDataTable = ({ leadingColumns.unshift(colorIndicatorControlColumn); } - return leadingColumns; - }, [canSetExpandedDoc, controlColumnIds, externalControlColumns, getRowIndicator]); - - const controlColumnsConfig = customControlColumnsConfiguration?.({ - controlColumns: getAllControlColumns(), - }); + if (rowAdditionalLeadingControls?.length) { + leadingColumns.push(...getAdditionalRowControlColumns(rowAdditionalLeadingControls)); + } - const customLeadingControlColumn = - controlColumnsConfig?.leadingControlColumns ?? leadingControlColumns; - const customTrailingControlColumn = - controlColumnsConfig?.trailingControlColumns ?? trailingControlColumns; + return leadingColumns; + }, [ + canSetExpandedDoc, + controlColumnIds, + externalControlColumns, + getRowIndicator, + rowAdditionalLeadingControls, + ]); const additionalControls = useMemo(() => { - if (!externalAdditionalControls && !usedSelectedDocs.length) { + if (!externalAdditionalControls && !selectedDocIds.length) { return null; } return ( <> - {Boolean(usedSelectedDocs.length) && ( - <EuiFlexGroup gutterSize="s" responsive={false}> - {enableComparisonMode && usedSelectedDocs.length > 1 && ( - <EuiFlexItem grow={false}> - <DataTableCompareToolbarBtn - selectedDocs={usedSelectedDocs} - setIsCompareActive={setIsCompareActive} - /> - </EuiFlexItem> - )} - <EuiFlexItem grow={false}> - <DataTableDocumentToolbarBtn - isPlainRecord={isPlainRecord} - isFilterActive={isFilterActive} - rows={rows!} - selectedDocs={usedSelectedDocs} - setSelectedDocs={setSelectedDocs} - setIsFilterActive={setIsFilterActive} - /> - </EuiFlexItem> - </EuiFlexGroup> + {Boolean(selectedDocIds.length) && ( + <DataTableDocumentToolbarBtn + isPlainRecord={isPlainRecord} + isFilterActive={isFilterActive} + rows={rows!} + setIsFilterActive={setIsFilterActive} + selectedDocsState={selectedDocsState} + enableComparisonMode={enableComparisonMode} + setIsCompareActive={setIsCompareActive} + fieldFormats={fieldFormats} + pageIndex={unifiedDataTableContextValue.pageIndex} + pageSize={unifiedDataTableContextValue.pageSize} + /> )} {externalAdditionalControls} </> ); }, [ + selectedDocIds, + selectedDocsState, externalAdditionalControls, - usedSelectedDocs, isPlainRecord, isFilterActive, + setIsFilterActive, enableComparisonMode, rows, + fieldFormats, + unifiedDataTableContextValue.pageIndex, + unifiedDataTableContextValue.pageSize, ]); const renderCustomToolbarFn: EuiDataGridProps['renderCustomToolbar'] | undefined = useMemo( @@ -1079,13 +1080,13 @@ export const UnifiedDataTable = ({ isPlainRecord={isPlainRecord} selectedFieldNames={visibleColumns} additionalFieldGroups={additionalFieldGroups} - selectedDocs={selectedDocs} + selectedDocIds={selectedDocIds} schemaDetectors={schemaDetectors} forceShowAllFields={defaultColumns} showFullScreenButton={showFullScreenButton} fieldFormats={fieldFormats} getDocById={getDocById} - setSelectedDocs={setSelectedDocs} + replaceSelectedDocs={replaceSelectedDocs} setIsCompareActive={setIsCompareActive} /> ) : ( @@ -1096,7 +1097,7 @@ export const UnifiedDataTable = ({ columns={euiGridColumns} columnVisibility={columnsVisibility} data-test-subj="docTable" - leadingControlColumns={customLeadingControlColumn} + leadingControlColumns={leadingControlColumns} onColumnResize={onResize} pagination={paginationObj} renderCellValue={renderCellValue} @@ -1110,16 +1111,16 @@ export const UnifiedDataTable = ({ gridStyle={gridStyleOverride ?? GRID_STYLE} renderCustomGridBody={renderCustomGridBody} renderCustomToolbar={renderCustomToolbarFn} - trailingControlColumns={customTrailingControlColumn} + trailingControlColumns={trailingControlColumns} cellContext={cellContext} renderCellPopover={renderCustomPopover} /> )} </div> {loadingState !== DataLoadingState.loading && - !usedSelectedDocs.length && // hide footer when showing selected documents - isPaginationEnabled && - !isCompareActive && ( // we hide the footer for Surrounding Documents page + isPaginationEnabled && // we hide the footer for Surrounding Documents page + !isFilterActive && // hide footer when showing selected documents + !isCompareActive && ( <UnifiedDataTableFooter isLoadingMore={loadingState === DataLoadingState.loadingMore} rowCount={rowCount} 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 index 638a81020e33b..4528e323c2047 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -17,12 +17,16 @@ import { type DataView, DataViewField } 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 { ControlColumns, CustomGridColumnsConfiguration, UnifiedDataTableSettings } from '../types'; +import { CustomGridColumnsConfiguration, UnifiedDataTableSettings } from '../types'; import type { ValueToStringConverter, DataTableColumnsMeta } from '../types'; import { buildCellActions } from './default_cell_actions'; import { getSchemaByKbnType } from './data_table_schema'; -import { SelectButton } from './data_table_document_selection'; -import { defaultTimeColumnWidth, ROWS_HEIGHT_OPTIONS } from '../constants'; +import { SelectButton, SelectAllButton } from './data_table_document_selection'; +import { + defaultTimeColumnWidth, + ROWS_HEIGHT_OPTIONS, + DEFAULT_CONTROL_COLUMN_WIDTH, +} from '../constants'; import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; import { buildEditFieldButton } from './build_edit_field_button'; import { DataTableColumnHeader, DataTableTimeColumnHeader } from './data_table_column_header'; @@ -53,7 +57,7 @@ export const SELECT_ROW = 'select'; const openDetails = { id: OPEN_DETAILS, - width: 26, + width: DEFAULT_CONTROL_COLUMN_WIDTH, headerCellRender: () => ( <EuiScreenReaderOnly> <span> @@ -68,26 +72,11 @@ const openDetails = { const select = { id: SELECT_ROW, - width: 24, + width: DEFAULT_CONTROL_COLUMN_WIDTH, rowCellRender: SelectButton, - headerCellRender: () => ( - <EuiScreenReaderOnly> - <span> - {i18n.translate('unifiedDataTable.selectColumnHeader', { - defaultMessage: 'Select column', - })} - </span> - </EuiScreenReaderOnly> - ), + headerCellRender: SelectAllButton, }; -export function getAllControlColumns(): ControlColumns { - return { - [SELECT_ROW]: select, - [OPEN_DETAILS]: openDetails, - }; -} - export function getLeadControlColumns(canSetExpandedDoc: boolean) { if (!canSetExpandedDoc) { return [select]; 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 index 9835f6f0798f9..a1c39158f2b8d 100644 --- 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 @@ -6,18 +6,21 @@ * Side Public License, v 1. */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DataTableCompareToolbarBtn, DataTableDocumentToolbarBtn, SelectButton, + SelectAllButton, } from './data_table_document_selection'; -import { dataTableContextMock } from '../../__mocks__/table_context'; +import { buildSelectedDocsState, dataTableContextMock } from '../../__mocks__/table_context'; import { UnifiedDataTableContext } from '../table_context'; import { getDocId } from '@kbn/discover-utils'; import { render, screen } from '@testing-library/react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { servicesMock } from '../../__mocks__/services'; describe('document selection', () => { describe('getDocId', () => { @@ -39,6 +42,39 @@ describe('document selection', () => { }); }); + describe('SelectAllButton', () => { + test('is not checked', () => { + const contextMock = { + ...dataTableContextMock, + }; + + const component = mountWithIntl( + <UnifiedDataTableContext.Provider value={contextMock}> + <SelectAllButton /> + </UnifiedDataTableContext.Provider> + ); + + const checkBox = findTestSubject(component, 'selectAllDocsOnPageToggle'); + expect(checkBox.props().checked).toBeFalsy(); + }); + + test('is checked correctly', () => { + const contextMock = { + ...dataTableContextMock, + selectedDocsState: buildSelectedDocsState(['i::1::']), + }; + + const component = mountWithIntl( + <UnifiedDataTableContext.Provider value={contextMock}> + <SelectAllButton /> + </UnifiedDataTableContext.Provider> + ); + + const checkBox = findTestSubject(component, 'selectAllDocsOnPageToggle'); + expect(checkBox.props().checked).toBeTruthy(); + }); + }); + describe('SelectButton', () => { test('is not checked', () => { const contextMock = { @@ -63,13 +99,13 @@ describe('document selection', () => { expect(checkBox.props().checked).toBeFalsy(); }); - test('is checked', () => { + test('is checked correctly', () => { const contextMock = { ...dataTableContextMock, - selectedDocs: ['i::1::'], + selectedDocsState: buildSelectedDocsState(['i::1::']), }; - const component = mountWithIntl( + const component1 = mountWithIntl( <UnifiedDataTableContext.Provider value={contextMock}> <SelectButton rowIndex={0} @@ -83,8 +119,25 @@ describe('document selection', () => { </UnifiedDataTableContext.Provider> ); - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - expect(checkBox.props().checked).toBeTruthy(); + const checkBox1 = findTestSubject(component1, 'dscGridSelectDoc-i::1::'); + expect(checkBox1.props().checked).toBeTruthy(); + + const component2 = mountWithIntl( + <UnifiedDataTableContext.Provider value={contextMock}> + <SelectButton + rowIndex={1} + colIndex={0} + setCellProps={jest.fn()} + columnId="test" + isExpanded={false} + isDetails={false} + isExpandable={false} + /> + </UnifiedDataTableContext.Provider> + ); + + const checkBox2 = findTestSubject(component2, 'dscGridSelectDoc-i::2::'); + expect(checkBox2.props().checked).toBeFalsy(); }); test('adding a selection', () => { @@ -108,13 +161,13 @@ describe('document selection', () => { const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); checkBox.simulate('change'); - expect(contextMock.setSelectedDocs).toHaveBeenCalledWith(['i::1::']); + expect(contextMock.selectedDocsState.toggleDocSelection).toHaveBeenCalledWith('i::1::'); }); test('removing a selection', () => { const contextMock = { ...dataTableContextMock, - selectedDocs: ['i::1::'], + selectedDocsState: buildSelectedDocsState(['i::1::']), }; const component = mountWithIntl( @@ -133,55 +186,192 @@ describe('document selection', () => { const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); checkBox.simulate('change'); - expect(contextMock.setSelectedDocs).toHaveBeenCalledWith([]); + expect(contextMock.selectedDocsState.toggleDocSelection).toHaveBeenCalledWith('i::1::'); }); }); describe('DataTableDocumentToolbarBtn', () => { - test('it renders a button clickable button', () => { + test('it renders the button and its menu correctly', () => { const props = { isPlainRecord: false, isFilterActive: false, rows: dataTableContextMock.rows, - selectedDocs: ['i::1::'], + selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), setIsFilterActive: jest.fn(), - setSelectedDocs: jest.fn(), + enableComparisonMode: true, setIsCompareActive: jest.fn(), + fieldFormats: servicesMock.fieldFormats, + pageIndex: 0, + pageSize: 2, }; const component = mountWithIntl(<DataTableDocumentToolbarBtn {...props} />); const button = findTestSubject(component, 'unifiedDataTableSelectionBtn'); expect(button.length).toBe(1); + expect(button.text()).toBe('Selected2'); + + act(() => { + button.simulate('click'); + }); + + component.update(); + + expect(findTestSubject(component, 'dscGridShowSelectedDocuments').length).toBe(1); + expect(findTestSubject(component, 'unifiedDataTableCompareSelectedDocuments').length).toBe(1); + expect(findTestSubject(component, 'dscGridSelectAllDocs').text()).toBe('Select all 5'); + + act(() => { + findTestSubject(component, 'dscGridClearSelectedDocuments').simulate('click'); + }); + + expect(props.selectedDocsState.clearAllSelectedDocs).toHaveBeenCalled(); + }); + + test('it should not render "Select all X" button if less than pageSize is selected', () => { + const props = { + isPlainRecord: false, + isFilterActive: false, + rows: dataTableContextMock.rows, + selectedDocsState: buildSelectedDocsState(['i::1::']), + setIsFilterActive: jest.fn(), + enableComparisonMode: true, + setIsCompareActive: jest.fn(), + fieldFormats: servicesMock.fieldFormats, + pageIndex: 0, + pageSize: 2, + }; + const component = mountWithIntl(<DataTableDocumentToolbarBtn {...props} />); + expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe('Selected1'); + + expect(findTestSubject(component, 'dscGridSelectAllDocs').exists()).toBe(false); + }); + + test('it should render "Select all X" button if all rows on the page are selected', () => { + const props = { + isPlainRecord: false, + isFilterActive: false, + rows: dataTableContextMock.rows, + selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), + setIsFilterActive: jest.fn(), + enableComparisonMode: true, + setIsCompareActive: jest.fn(), + fieldFormats: servicesMock.fieldFormats, + pageIndex: 0, + pageSize: 2, + }; + const component = mountWithIntl(<DataTableDocumentToolbarBtn {...props} />); + expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe('Selected2'); + + const button = findTestSubject(component, 'dscGridSelectAllDocs'); + expect(button.exists()).toBe(true); + + act(() => { + button.simulate('click'); + }); + + expect(props.selectedDocsState.selectAllDocs).toHaveBeenCalled(); + }); + + test('it should render "Select all X" button even if on another page', () => { + const props = { + isPlainRecord: false, + isFilterActive: false, + rows: dataTableContextMock.rows, + selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), + setIsFilterActive: jest.fn(), + enableComparisonMode: true, + setIsCompareActive: jest.fn(), + fieldFormats: servicesMock.fieldFormats, + pageIndex: 1, + pageSize: 2, + }; + const component = mountWithIntl(<DataTableDocumentToolbarBtn {...props} />); + expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe('Selected2'); + + expect(findTestSubject(component, 'dscGridSelectAllDocs').exists()).toBe(true); + }); + + test('it should not render "Select all X" button if all rows are selected', () => { + const props = { + isPlainRecord: false, + isFilterActive: false, + rows: dataTableContextMock.rows, + selectedDocsState: buildSelectedDocsState(dataTableContextMock.rows.map((row) => row.id)), + setIsFilterActive: jest.fn(), + enableComparisonMode: true, + setIsCompareActive: jest.fn(), + fieldFormats: servicesMock.fieldFormats, + pageIndex: 1, + pageSize: 2, + }; + const component = mountWithIntl(<DataTableDocumentToolbarBtn {...props} />); + expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe( + `Selected${dataTableContextMock.rows.length}` + ); + + expect(findTestSubject(component, 'dscGridSelectAllDocs').exists()).toBe(false); }); }); describe('DataTableCompareToolbarBtn', () => { + const props = { + isPlainRecord: false, + isFilterActive: false, + rows: dataTableContextMock.rows, + selectedDocsState: buildSelectedDocsState([]), + setIsFilterActive: jest.fn(), + enableComparisonMode: true, + setIsCompareActive: jest.fn(), + fieldFormats: servicesMock.fieldFormats, + pageIndex: 0, + pageSize: 2, + }; + const renderCompareBtn = ({ - selectedDocs = ['1', '2'], + selectedDocIds = ['1', '2'], setIsCompareActive = jest.fn(), }: Partial<Parameters<typeof DataTableCompareToolbarBtn>[0]> = {}) => { render( <IntlProvider locale="en"> - <DataTableCompareToolbarBtn - selectedDocs={selectedDocs} + <DataTableDocumentToolbarBtn + {...props} + selectedDocsState={buildSelectedDocsState(selectedDocIds)} setIsCompareActive={setIsCompareActive} /> </IntlProvider> ); return { - getButton: () => screen.queryByRole('button', { name: /Compare/ }), + getButton: async () => { + const menuButton = await screen.findByTestId('unifiedDataTableSelectionBtn'); + menuButton.click(); + return screen.queryByRole('button', { name: /Compare/ }); + }, }; }; - it('should render the compare button', () => { + it('should render the compare button', async () => { const { getButton } = renderCompareBtn(); - expect(getButton()).toBeInTheDocument(); + expect(await getButton()).toBeInTheDocument(); }); - it('should call setIsCompareActive when the button is clicked', () => { + it('should call setIsCompareActive when the button is clicked', async () => { const setIsCompareActive = jest.fn(); const { getButton } = renderCompareBtn({ setIsCompareActive }); - getButton()?.click(); + const button = await getButton(); + expect(button).toBeInTheDocument(); + expect(button?.getAttribute('disabled')).toBeNull(); + button?.click(); expect(setIsCompareActive).toHaveBeenCalledWith(true); }); + + it('should disable the button if limit is reached', async () => { + const selectedDocIds = Array.from({ length: 500 }, (_, i) => i.toString()); + const setIsCompareActive = jest.fn(); + const { getButton } = renderCompareBtn({ selectedDocIds, setIsCompareActive }); + const button = await getButton(); + expect(button).toBeInTheDocument(); + expect(button?.getAttribute('disabled')).toBe(''); + button?.click(); + expect(setIsCompareActive).not.toHaveBeenCalled(); + }); }); }); 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 index 492d739456b49..2a34c4866cb86 100644 --- 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 @@ -5,7 +5,9 @@ * 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 React, { useCallback, useContext, useMemo, useState } from 'react'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { EuiCheckbox, EuiContextMenuItem, @@ -16,36 +18,29 @@ import { EuiPopover, EuiFlexGroup, EuiFlexItem, + EuiToolTip, useEuiTheme, + EuiScreenReaderOnly, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; +import type { UseSelectedDocsState } from '../hooks/use_selected_docs'; import { UnifiedDataTableContext } from '../table_context'; +import { useControlColumn } from '../hooks/use_control_column'; -export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { +export const SelectButton = (props: EuiDataGridCellValueElementProps) => { + const { record, rowIndex } = useControlColumn(props); 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 { selectedDocsState } = useContext(UnifiedDataTableContext); + const { isDocSelected, toggleDocSelection } = selectedDocsState; const toggleDocumentSelectionLabel = i18n.translate('unifiedDataTable.grid.selectDoc', { defaultMessage: `Select document ''{rowNumber}''`, values: { rowNumber: rowIndex + 1 }, }); - useEffect(() => { - if (expanded && doc && expanded.id === doc.id) { - setCellProps({ - className: 'unifiedDataTable__cell--selected', - }); - } else { - setCellProps({ style: undefined }); - } - }, [expanded, doc, setCellProps, isDarkMode]); - return ( <EuiFlexGroup responsive={false} @@ -59,17 +54,12 @@ export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle > <EuiFlexItem grow={false}> <EuiCheckbox - id={doc.id} + id={record.id} aria-label={toggleDocumentSelectionLabel} - checked={checked} - data-test-subj={`dscGridSelectDoc-${doc.id}`} + checked={isDocSelected(record.id)} + data-test-subj={`dscGridSelectDoc-${record.id}`} onChange={() => { - if (checked) { - const newSelection = selectedDocs.filter((docId) => docId !== doc.id); - setSelectedDocs(newSelection); - } else { - setSelectedDocs([...selectedDocs, doc.id]); - } + toggleDocSelection(record.id); }} /> </EuiFlexItem> @@ -77,26 +67,149 @@ export const SelectButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle ); }; +export const SelectAllButton = () => { + const { selectedDocsState, pageIndex, pageSize, rows } = useContext(UnifiedDataTableContext); + const { getCountOfSelectedDocs, deselectSomeDocs, selectMoreDocs } = selectedDocsState; + + const docIdsFromCurrentPage = useMemo(() => { + return getDocIdsForCurrentPage(rows, pageIndex, pageSize); + }, [rows, pageIndex, pageSize]); + + const countOfSelectedDocs = useMemo(() => { + return docIdsFromCurrentPage?.length ? getCountOfSelectedDocs(docIdsFromCurrentPage) : 0; + }, [docIdsFromCurrentPage, getCountOfSelectedDocs]); + + const isIndeterminateForCurrentPage = useMemo(() => { + if (docIdsFromCurrentPage?.length) { + return countOfSelectedDocs > 0 && countOfSelectedDocs < docIdsFromCurrentPage.length; + } + return false; + }, [docIdsFromCurrentPage, countOfSelectedDocs]); + + const areDocsSelectedForCurrentPage = useMemo(() => { + if (docIdsFromCurrentPage?.length) { + return countOfSelectedDocs > 0; + } + return false; + }, [docIdsFromCurrentPage, countOfSelectedDocs]); + + if (!docIdsFromCurrentPage) { + return null; + } + + const title = + isIndeterminateForCurrentPage || areDocsSelectedForCurrentPage + ? i18n.translate('unifiedDataTable.deselectAllRowsOnPageColumnHeader', { + defaultMessage: 'Deselect all visible rows', + }) + : i18n.translate('unifiedDataTable.selectAllRowsOnPageColumnHeader', { + defaultMessage: 'Select all visible rows', + }); + + return ( + <> + <EuiScreenReaderOnly> + <span> + {i18n.translate('unifiedDataTable.selectColumnHeader', { + defaultMessage: 'Select column', + })} + </span> + </EuiScreenReaderOnly> + <EuiCheckbox + data-test-subj="selectAllDocsOnPageToggle" + id="select-all-docs-on-page-toggle" + aria-label={title} + title={title} + indeterminate={isIndeterminateForCurrentPage} + checked={areDocsSelectedForCurrentPage} + onChange={(e) => { + const shouldClearSelection = isIndeterminateForCurrentPage || !e.target.checked; + + if (shouldClearSelection) { + deselectSomeDocs(docIdsFromCurrentPage); + } else { + selectMoreDocs(docIdsFromCurrentPage); + } + }} + /> + </> + ); +}; + export function DataTableDocumentToolbarBtn({ isPlainRecord, isFilterActive, rows, - selectedDocs, setIsFilterActive, - setSelectedDocs, + selectedDocsState, + enableComparisonMode, + setIsCompareActive, + fieldFormats, + pageIndex, + pageSize, }: { isPlainRecord: boolean; isFilterActive: boolean; rows: DataTableRecord[]; - selectedDocs: string[]; setIsFilterActive: (value: boolean) => void; - setSelectedDocs: (value: string[]) => void; + selectedDocsState: UseSelectedDocsState; + enableComparisonMode: boolean | undefined; + setIsCompareActive: (value: boolean) => void; + fieldFormats: FieldFormatsStart; + pageIndex: number | undefined; + pageSize: number | undefined; }) { const [isSelectionPopoverOpen, setIsSelectionPopoverOpen] = useState(false); + const { selectAllDocs, clearAllSelectedDocs, isDocSelected, selectedDocIds } = selectedDocsState; + + const shouldSuggestToSelectAll = useMemo(() => { + const canSelectMore = selectedDocIds.length < rows.length && rows.length > 1; + if (typeof pageSize !== 'number' || isFilterActive || !canSelectMore) { + return false; + } + return selectedDocIds.length >= pageSize; + }, [rows, pageSize, selectedDocIds.length, isFilterActive]); const getMenuItems = useCallback(() => { return [ + // Compare selected documents + ...(enableComparisonMode && selectedDocIds.length > 1 + ? [ + <DataTableCompareToolbarBtn + key="compareSelected" + selectedDocIds={selectedDocIds} + setIsCompareActive={setIsCompareActive} + />, + ] + : []), + // Copy results to clipboard (JSON) + <EuiCopy + key="copyJsonWrapper" + data-test-subj="dscGridCopySelectedDocumentsJSON" + textToCopy={ + rows + ? JSON.stringify(rows.filter((row) => isDocSelected(row.id)).map((row) => row.raw)) + : '' + } + > + {(copy) => ( + <EuiContextMenuItem key="copyJSON" icon="copyClipboard" onClick={copy}> + {isPlainRecord ? ( + <FormattedMessage + id="unifiedDataTable.copyResultsToClipboardJSON" + defaultMessage="Copy results to clipboard (JSON)" + /> + ) : ( + <FormattedMessage + id="unifiedDataTable.copyToClipboardJSON" + defaultMessage="Copy documents to clipboard (JSON)" + /> + )} + </EuiContextMenuItem> + )} + </EuiCopy>, isFilterActive ? ( + // Show all documents <EuiContextMenuItem data-test-subj="dscGridShowAllDocuments" key="showAllDocuments" @@ -119,6 +232,7 @@ export function DataTableDocumentToolbarBtn({ )} </EuiContextMenuItem> ) : ( + // Show selected documents only <EuiContextMenuItem data-test-subj="dscGridShowSelectedDocuments" key="showSelectedDocuments" @@ -141,66 +255,58 @@ export function DataTableDocumentToolbarBtn({ )} </EuiContextMenuItem> ), - <EuiCopy - key="copyJsonWrapper" - data-test-subj="dscGridCopySelectedDocumentsJSON" - textToCopy={ - rows - ? JSON.stringify( - rows.filter((row) => selectedDocs.includes(row.id)).map((row) => row.raw) - ) - : '' - } - > - {(copy) => ( - <EuiContextMenuItem key="copyJSON" icon="copyClipboard" onClick={copy}> - {isPlainRecord ? ( - <FormattedMessage - id="unifiedDataTable.copyResultsToClipboardJSON" - defaultMessage="Copy results to clipboard (JSON)" - /> - ) : ( - <FormattedMessage - id="unifiedDataTable.copyToClipboardJSON" - defaultMessage="Copy documents to clipboard (JSON)" - /> - )} - </EuiContextMenuItem> - )} - </EuiCopy>, + // Clear selection <EuiContextMenuItem data-test-subj="dscGridClearSelectedDocuments" key="clearSelection" icon="cross" onClick={() => { setIsSelectionPopoverOpen(false); - setSelectedDocs([]); + clearAllSelectedDocs(); setIsFilterActive(false); }} > <FormattedMessage id="unifiedDataTable.clearSelection" defaultMessage="Clear selection" /> </EuiContextMenuItem>, ]; - }, [isFilterActive, isPlainRecord, rows, selectedDocs, setIsFilterActive, setSelectedDocs]); + }, [ + isFilterActive, + isPlainRecord, + rows, + setIsFilterActive, + isDocSelected, + clearAllSelectedDocs, + selectedDocIds, + enableComparisonMode, + setIsCompareActive, + ]); const toggleSelectionToolbar = useCallback( () => setIsSelectionPopoverOpen((prevIsOpen) => !prevIsOpen), [] ); - return ( + const selectedRowsMenuButton = ( <EuiPopover closePopover={() => setIsSelectionPopoverOpen(false)} isOpen={isSelectionPopoverOpen} panelPaddingSize="none" button={ <EuiDataGridToolbarControl - iconType="documents" + iconSide="left" + iconType="arrowDown" onClick={toggleSelectionToolbar} - data-selected-documents={selectedDocs.length} + data-selected-documents={selectedDocIds.length} data-test-subj="unifiedDataTableSelectionBtn" isSelected={isFilterActive} - badgeContent={selectedDocs.length} + badgeContent={fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(selectedDocIds.length)} + css={css` + .euiButtonEmpty__content { + flex-direction: row-reverse; + } + `} > {isPlainRecord ? ( <FormattedMessage @@ -226,28 +332,92 @@ export function DataTableDocumentToolbarBtn({ )} </EuiPopover> ); + + return ( + <EuiFlexGroup + responsive={false} + gutterSize="none" + wrap={false} + className="unifiedDataTableToolbarControlGroup" + > + <EuiFlexItem className="unifiedDataTableToolbarControlButton" grow={false}> + {selectedRowsMenuButton} + </EuiFlexItem> + {shouldSuggestToSelectAll ? ( + <EuiFlexItem className="unifiedDataTableToolbarControlButton" grow={false}> + <EuiDataGridToolbarControl + data-test-subj="dscGridSelectAllDocs" + onClick={() => { + setIsSelectionPopoverOpen(false); + selectAllDocs(); + }} + > + <FormattedMessage + id="unifiedDataTable.selectAllDocs" + defaultMessage="Select all {rowsCount}" + values={{ + rowsCount: fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.NUMBER, [ES_FIELD_TYPES.INTEGER]) + .convert(rows.length), + }} + /> + </EuiDataGridToolbarControl> + </EuiFlexItem> + ) : null} + </EuiFlexGroup> + ); } +const MAX_SELECTED_DOCS_FOR_COMPARE = 100; + export const DataTableCompareToolbarBtn = ({ - selectedDocs, + selectedDocIds, setIsCompareActive, }: { - selectedDocs: string[]; + selectedDocIds: string[]; setIsCompareActive: (value: boolean) => void; }) => { + const isDisabled = selectedDocIds.length > MAX_SELECTED_DOCS_FOR_COMPARE; + const label = ( + <FormattedMessage + id="unifiedDataTable.compareSelectedRowsButtonLabel" + defaultMessage="Compare selected" + /> + ); return ( - <EuiDataGridToolbarControl - iconType="diff" - badgeContent={selectedDocs.length} + <EuiContextMenuItem data-test-subj="unifiedDataTableCompareSelectedDocuments" + disabled={isDisabled} + icon="diff" onClick={() => { setIsCompareActive(true); }} > - <FormattedMessage - id="unifiedDataTable.compareSelectedRowsButtonLabel" - defaultMessage="Compare" - /> - </EuiDataGridToolbarControl> + {isDisabled ? ( + <EuiToolTip + content={i18n.translate('unifiedDataTable.compareSelectedRowsButtonDisabledTooltip', { + defaultMessage: 'Comparison is limited to {limit} rows', + values: { limit: MAX_SELECTED_DOCS_FOR_COMPARE }, + })} + > + {label} + </EuiToolTip> + ) : ( + label + )} + </EuiContextMenuItem> ); }; + +function getDocIdsForCurrentPage( + rows: DataTableRecord[], + pageIndex: number | undefined, + pageSize: number | undefined +): string[] | undefined { + if (typeof pageIndex === 'number' && typeof pageSize === 'number') { + const start = pageIndex * pageSize; + const end = start + pageSize; + return rows.slice(start, end).map((row) => row.id); + } + return undefined; // pagination is disabled +} 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 index 76210b6aeee23..04ae49abec141 100644 --- 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 @@ -10,39 +10,27 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { UnifiedDataTableContext } from '../table_context'; -import { DataTableRowControl } from './data_table_row_control'; +import { DataTableRowControl, Size } from './data_table_row_control'; +import { useControlColumn } from '../hooks/use_control_column'; /** * Button to expand a given row */ -export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueElementProps) => { +export const ExpandButton = (props: EuiDataGridCellValueElementProps) => { + const { record, rowIndex } = useControlColumn(props); + const toolTipRef = useRef<EuiToolTip>(null); const [pressed, setPressed] = useState<boolean>(false); - const { expanded, setExpanded, rows, isDarkMode, componentsTourSteps } = - useContext(UnifiedDataTableContext); - const current = rows[rowIndex]; + const { expanded, setExpanded, componentsTourSteps } = useContext(UnifiedDataTableContext); const tourStep = componentsTourSteps ? componentsTourSteps.expandButton : undefined; - useEffect(() => { - if (current.isAnchor) { - setCellProps({ - className: 'unifiedDataTable__cell--highlight', - }); - } else if (expanded && current && expanded.id === current.id) { - setCellProps({ - className: 'unifiedDataTable__cell--expanded', - }); - } else { - setCellProps({ style: undefined }); - } - }, [expanded, current, setCellProps, isDarkMode]); - const isCurrentRowExpanded = current === expanded; + const isCurrentRowExpanded = record === expanded; const buttonLabel = i18n.translate('unifiedDataTable.grid.viewDoc', { defaultMessage: 'Toggle dialog with details', }); - const testSubj = current.isAnchor + const testSubj = record.isAnchor ? 'docTableExpandToggleColumnAnchor' : 'docTableExpandToggleColumn'; @@ -60,7 +48,7 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle } return ( - <DataTableRowControl> + <DataTableRowControl size={Size.normal}> <EuiToolTip content={buttonLabel} delay="long" ref={toolTipRef}> <EuiButtonIcon id={rowIndex === 0 ? tourStep : undefined} @@ -69,7 +57,7 @@ export const ExpandButton = ({ rowIndex, setCellProps }: EuiDataGridCellValueEle aria-label={buttonLabel} data-test-subj={testSubj} onClick={() => { - const nextHit = isCurrentRowExpanded ? undefined : current; + const nextHit = isCurrentRowExpanded ? undefined : record; toolTipRef.current?.hideToolTip(); setPressed(Boolean(nextHit)); setExpanded?.(nextHit); diff --git a/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx b/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx index 4ceadea549dce..0ac0bbd4cb2f6 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_row_control.tsx @@ -7,7 +7,16 @@ */ import React from 'react'; +import classnames from 'classnames'; -export const DataTableRowControl = ({ children }: { children: React.ReactNode }) => { - return <span className="unifiedDataTable__rowControl">{children}</span>; +export enum Size { + normal = 'normal', +} + +export const DataTableRowControl: React.FC<{ size?: Size }> = ({ size, children }) => { + const classes = classnames('unifiedDataTable__rowControl', { + // normalize the size of the control + [`unifiedDataTable__rowControl--size-${size}`]: size, + }); + return <span className={classes}>{children}</span>; }; diff --git a/packages/kbn-unified-data-table/src/constants.ts b/packages/kbn-unified-data-table/src/constants.ts index c2d5654c602c2..c7cf1793039a5 100644 --- a/packages/kbn-unified-data-table/src/constants.ts +++ b/packages/kbn-unified-data-table/src/constants.ts @@ -7,6 +7,8 @@ */ import { EuiDataGridStyle } from '@elastic/eui'; +export const DEFAULT_CONTROL_COLUMN_WIDTH = 24; + export const DEFAULT_ROWS_PER_PAGE = 100; export const MAX_LOADED_GRID_ROWS = 10000; diff --git a/packages/kbn-unified-data-table/src/hooks/use_control_column.ts b/packages/kbn-unified-data-table/src/hooks/use_control_column.ts new file mode 100644 index 0000000000000..e2bc05f668508 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_control_column.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 { useContext, useEffect, useMemo } from 'react'; +import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; +import type { DataTableRecord } from '@kbn/discover-utils'; +import { UnifiedDataTableContext } from '../table_context'; + +export const useControlColumn = ({ + rowIndex, + setCellProps, +}: Pick<EuiDataGridCellValueElementProps, 'rowIndex' | 'setCellProps'>): { + record: DataTableRecord; + rowIndex: number; +} => { + const { expanded, rows } = useContext(UnifiedDataTableContext); + const record = useMemo(() => rows[rowIndex], [rows, rowIndex]); + + useEffect(() => { + if (record.isAnchor) { + setCellProps({ + className: 'unifiedDataTable__cell--highlight', + }); + } else if (expanded && record && expanded.id === record.id) { + setCellProps({ + className: 'unifiedDataTable__cell--expanded', + }); + } else { + setCellProps({ + className: '', + }); + } + }, [expanded, record, setCellProps]); + + return useMemo(() => ({ record, rowIndex }), [record, rowIndex]); +}; diff --git a/packages/kbn-unified-data-table/src/hooks/use_selected_docs.test.ts b/packages/kbn-unified-data-table/src/hooks/use_selected_docs.test.ts new file mode 100644 index 0000000000000..9dac8b38f42a7 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_selected_docs.test.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 { act, renderHook } from '@testing-library/react-hooks'; +import { buildDataTableRecord } from '@kbn/discover-utils'; +import { useSelectedDocs } from './use_selected_docs'; +import { generateEsHits } from '@kbn/discover-utils/src/__mocks__'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; + +describe('useSelectedDocs', () => { + const docs = generateEsHits(dataViewWithTimefieldMock, 5).map((hit) => + buildDataTableRecord(hit, dataViewWithTimefieldMock) + ); + const docsMap = new Map(docs.map((doc) => [doc.id, doc])); + + test('should have a correct default state', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [], + hasSelectedDocs: false, + }) + ); + }); + + test('should toggleDocSelection correctly', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[0].id], + hasSelectedDocs: true, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(true); + expect(result.current.isDocSelected(docs[1].id)).toBe(false); + + act(() => { + result.current.toggleDocSelection(docs[1].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[0].id, docs[1].id], + hasSelectedDocs: true, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(true); + expect(result.current.isDocSelected(docs[1].id)).toBe(true); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[1].id], + hasSelectedDocs: true, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(false); + expect(result.current.isDocSelected(docs[1].id)).toBe(true); + + act(() => { + result.current.toggleDocSelection(docs[1].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [], + hasSelectedDocs: false, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(false); + expect(result.current.isDocSelected(docs[1].id)).toBe(false); + }); + + test('should replaceSelectedDocs correctly', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + result.current.toggleDocSelection(docs[1].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[0].id, docs[1].id], + hasSelectedDocs: true, + }) + ); + + act(() => { + result.current.replaceSelectedDocs([docs[1].id, docs[2].id]); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[1].id, docs[2].id], + hasSelectedDocs: true, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(false); + expect(result.current.isDocSelected(docs[1].id)).toBe(true); + expect(result.current.isDocSelected(docs[2].id)).toBe(true); + }); + + test('should selectAllDocs correctly', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + + act(() => { + result.current.selectAllDocs(); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: docs.map((doc) => doc.id), + hasSelectedDocs: true, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(true); + expect(result.current.isDocSelected(docs[docs.length - 1].id)).toBe(true); + }); + + test('should selectMoreDocs correctly', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + result.current.toggleDocSelection(docs[1].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[0].id, docs[1].id], + hasSelectedDocs: true, + }) + ); + + act(() => { + result.current.selectMoreDocs([docs[1].id, docs[2].id]); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[0].id, docs[1].id, docs[2].id], + hasSelectedDocs: true, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(true); + expect(result.current.isDocSelected(docs[1].id)).toBe(true); + expect(result.current.isDocSelected(docs[2].id)).toBe(true); + }); + + test('should deselectSomeDocs correctly', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + result.current.toggleDocSelection(docs[1].id); + result.current.toggleDocSelection(docs[2].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[0].id, docs[1].id, docs[2].id], + hasSelectedDocs: true, + }) + ); + + act(() => { + result.current.deselectSomeDocs([docs[0].id, docs[2].id]); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[1].id], + hasSelectedDocs: true, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(false); + expect(result.current.isDocSelected(docs[1].id)).toBe(true); + expect(result.current.isDocSelected(docs[2].id)).toBe(false); + }); + + test('should clearAllSelectedDocs correctly', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + result.current.toggleDocSelection(docs[1].id); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [docs[0].id, docs[1].id], + hasSelectedDocs: true, + }) + ); + + act(() => { + result.current.clearAllSelectedDocs(); + }); + + expect(result.current).toEqual( + expect.objectContaining({ + selectedDocIds: [], + hasSelectedDocs: false, + }) + ); + + expect(result.current.isDocSelected(docs[0].id)).toBe(false); + expect(result.current.isDocSelected(docs[1].id)).toBe(false); + }); + + test('should getCountOfSelectedDocs correctly', () => { + const { result } = renderHook(() => useSelectedDocs(docsMap)); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + result.current.toggleDocSelection(docs[1].id); + }); + + expect(result.current.getCountOfSelectedDocs([docs[0].id, docs[1].id])).toBe(2); + expect(result.current.getCountOfSelectedDocs([docs[2].id, docs[3].id])).toBe(0); + + act(() => { + result.current.toggleDocSelection(docs[0].id); + }); + + expect(result.current.getCountOfSelectedDocs([docs[0].id, docs[1].id])).toBe(1); + expect(result.current.getCountOfSelectedDocs([docs[1].id])).toBe(1); + expect(result.current.getCountOfSelectedDocs([docs[0].id])).toBe(0); + expect(result.current.getCountOfSelectedDocs([docs[2].id, docs[3].id])).toBe(0); + }); +}); diff --git a/packages/kbn-unified-data-table/src/hooks/use_selected_docs.ts b/packages/kbn-unified-data-table/src/hooks/use_selected_docs.ts new file mode 100644 index 0000000000000..c61238bd3f778 --- /dev/null +++ b/packages/kbn-unified-data-table/src/hooks/use_selected_docs.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 and the Server Side Public License, v 1; you may not 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, useMemo, useState } from 'react'; +import type { DataTableRecord } from '@kbn/discover-utils'; + +export interface UseSelectedDocsState { + isDocSelected: (docId: string) => boolean; + getCountOfSelectedDocs: (docIds: string[]) => number; + hasSelectedDocs: boolean; + selectedDocIds: string[]; + toggleDocSelection: (docId: string) => void; + selectAllDocs: () => void; + selectMoreDocs: (docIds: string[]) => void; + deselectSomeDocs: (docIds: string[]) => void; + replaceSelectedDocs: (docIds: string[]) => void; + clearAllSelectedDocs: () => void; +} + +export const useSelectedDocs = (docMap: Map<string, DataTableRecord>): UseSelectedDocsState => { + const [selectedDocsSet, setSelectedDocsSet] = useState<Set<string>>(new Set()); + + const toggleDocSelection = useCallback((docId: string) => { + setSelectedDocsSet((prevSelectedRowsSet) => { + const newSelectedRowsSet = new Set(prevSelectedRowsSet); + if (newSelectedRowsSet.has(docId)) { + newSelectedRowsSet.delete(docId); + } else { + newSelectedRowsSet.add(docId); + } + return newSelectedRowsSet; + }); + }, []); + + const replaceSelectedDocs = useCallback((docIds: string[]) => { + setSelectedDocsSet(new Set(docIds)); + }, []); + + const selectAllDocs = useCallback(() => { + setSelectedDocsSet(new Set(docMap.keys())); + }, [docMap]); + + const selectMoreDocs = useCallback((docIds: string[]) => { + setSelectedDocsSet((prevSelectedRowsSet) => new Set([...prevSelectedRowsSet, ...docIds])); + }, []); + + const deselectSomeDocs = useCallback((docIds: string[]) => { + setSelectedDocsSet( + (prevSelectedRowsSet) => + new Set([...prevSelectedRowsSet].filter((docId) => !docIds.includes(docId))) + ); + }, []); + + const clearAllSelectedDocs = useCallback(() => { + setSelectedDocsSet(new Set()); + }, []); + + const selectedDocIds = useMemo( + () => Array.from(selectedDocsSet).filter((docId) => docMap.has(docId)), + [selectedDocsSet, docMap] + ); + + const isDocSelected = useCallback( + (docId: string) => selectedDocsSet.has(docId) && docMap.has(docId), + [selectedDocsSet, docMap] + ); + + const usedSelectedDocsCount = selectedDocIds.length; + + const getCountOfSelectedDocs = useCallback( + (docIds) => { + if (!usedSelectedDocsCount) { + return 0; + } + + return docIds.filter(isDocSelected).length; + }, + [usedSelectedDocsCount, isDocSelected] + ); + + return useMemo( + () => ({ + isDocSelected, + hasSelectedDocs: usedSelectedDocsCount > 0, + getCountOfSelectedDocs, + selectedDocIds, + toggleDocSelection, + selectAllDocs, + selectMoreDocs, + deselectSomeDocs, + replaceSelectedDocs, + clearAllSelectedDocs, + }), + [ + isDocSelected, + getCountOfSelectedDocs, + toggleDocSelection, + selectAllDocs, + selectMoreDocs, + deselectSomeDocs, + replaceSelectedDocs, + clearAllSelectedDocs, + usedSelectedDocsCount, + selectedDocIds, + ] + ); +}; diff --git a/packages/kbn-unified-data-table/src/table_context.tsx b/packages/kbn-unified-data-table/src/table_context.tsx index cc6c63ab87682..667826a01c06d 100644 --- a/packages/kbn-unified-data-table/src/table_context.tsx +++ b/packages/kbn-unified-data-table/src/table_context.tsx @@ -11,6 +11,7 @@ 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'; +import type { UseSelectedDocsState } from './hooks/use_selected_docs'; export interface DataTableContext { expanded?: DataTableRecord | undefined; @@ -19,11 +20,12 @@ export interface DataTableContext { onFilter?: DocViewFilterFn; dataView: DataView; isDarkMode: boolean; - selectedDocs: string[]; - setSelectedDocs: (selected: string[]) => void; + selectedDocsState: UseSelectedDocsState; valueToStringConverter: ValueToStringConverter; componentsTourSteps?: Record<string, string>; isPlainRecord?: boolean; + pageIndex: number | undefined; // undefined when the pagination is disabled + pageSize: number | undefined; } const defaultContext = {} as unknown as DataTableContext; diff --git a/packages/kbn-unified-data-table/src/types.ts b/packages/kbn-unified-data-table/src/types.ts index 5914fa03f8827..b08818e1a861a 100644 --- a/packages/kbn-unified-data-table/src/types.ts +++ b/packages/kbn-unified-data-table/src/types.ts @@ -6,8 +6,13 @@ * Side Public License, v 1. */ -import type { ReactElement } from 'react'; -import type { EuiDataGridCellValueElementProps, EuiDataGridColumn } from '@elastic/eui'; +import type { ReactElement, FC } from 'react'; +import type { + EuiDataGridCellValueElementProps, + EuiDataGridColumn, + IconType, + EuiButtonIconProps, +} from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils/src/types'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -70,16 +75,25 @@ export type CustomGridColumnsConfiguration = Record< (props: CustomGridColumnProps) => EuiDataGridColumn >; -export interface ControlColumns { - select: EuiDataGridControlColumn; - openDetails: EuiDataGridControlColumn; +export interface RowControlRowProps { + rowIndex: number; + record: DataTableRecord; } -export interface ControlColumnsProps { - controlColumns: ControlColumns; +export interface RowControlProps { + 'data-test-subj'?: string; + color?: EuiButtonIconProps['color']; + disabled?: boolean; + label: string; + iconType: IconType; + onClick: ((props: RowControlRowProps) => void) | undefined; } -export type CustomControlColumnConfiguration = (props: ControlColumnsProps) => { - leadingControlColumns: EuiDataGridControlColumn[]; - trailingControlColumns?: EuiDataGridControlColumn[]; -}; +export type RowControlComponent = FC<RowControlProps>; + +export interface RowControlColumn { + id: string; + headerAriaLabel: string; + headerCellRender?: EuiDataGridControlColumn['headerCellRender']; + renderControl: (Control: RowControlComponent, props: RowControlRowProps) => ReactElement; +} diff --git a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap index 3bbf6a09a5019..582f07fbeb950 100644 --- a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap +++ b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap @@ -7,6 +7,7 @@ exports[`<DocViewer /> Render <DocViewer/> with 3 different tabs 1`] = ` > <EuiTabbedContent autoFocus="initial" + onTabClick={[Function]} size="s" tabs={ Array [ diff --git a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.test.tsx b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.test.tsx index f40b15b90571b..6b34ed1ef9cbb 100644 --- a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.test.tsx +++ b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.test.tsx @@ -8,11 +8,28 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { DocViewer } from './doc_viewer'; +import { render, screen } from '@testing-library/react'; import { findTestSubject } from '@elastic/eui/lib/test'; -import type { DocViewRenderProps } from '../../types'; import { buildDataTableRecord } from '@kbn/discover-utils'; +import { DocViewer, INITIAL_TAB } from './doc_viewer'; +import type { DocViewRenderProps } from '../../types'; import { DocViewsRegistry } from '../..'; +import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__'; + +const records = esHitsMockWithSort.map((hit) => buildDataTableRecord(hit, dataViewMock)); + +const mockSetLocalStorage = jest.fn(); +const mockLocalStorageKey = INITIAL_TAB; +let mockTestInitialLocalStorageValue: string | undefined; + +jest.mock('react-use/lib/useLocalStorage', () => { + return jest.fn((key: string, initialValue: number) => { + if (key !== mockLocalStorageKey) { + throw new Error(`Unexpected key: ${key}`); + } + return [mockTestInitialLocalStorageValue ?? initialValue, mockSetLocalStorage]; + }); +}); describe('<DocViewer />', () => { test('Render <DocViewer/> with 3 different tabs', () => { @@ -59,4 +76,57 @@ describe('<DocViewer />', () => { const errorMsgComponent = findTestSubject(wrapper, 'docViewerError'); expect(errorMsgComponent.text()).toMatch(new RegExp(`${errorMsg}`)); }); + + test('should save active tab to local storage', () => { + const registry = new DocViewsRegistry(); + registry.add({ id: 'test1', order: 10, title: 'Render function', render: jest.fn() }); + registry.add({ id: 'test2', order: 20, title: 'Render function', render: jest.fn() }); + + render(<DocViewer docViews={registry.getAll()} hit={records[0]} dataView={dataViewMock} />); + + expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('true'); + expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('false'); + + screen.getByTestId('docViewerTab-test2').click(); + + expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('false'); + expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('true'); + expect(mockSetLocalStorage).toHaveBeenCalledWith('kbn_doc_viewer_tab_test2'); + + screen.getByTestId('docViewerTab-test1').click(); + + expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('true'); + expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('false'); + expect(mockSetLocalStorage).toHaveBeenCalledWith('kbn_doc_viewer_tab_test1'); + }); + + test('should restore active tab from local storage', () => { + const registry = new DocViewsRegistry(); + registry.add({ id: 'test1', order: 10, title: 'Render function', render: jest.fn() }); + registry.add({ id: 'test2', order: 20, title: 'Render function', render: jest.fn() }); + + mockTestInitialLocalStorageValue = 'kbn_doc_viewer_tab_test2'; + + render(<DocViewer docViews={registry.getAll()} hit={records[0]} dataView={dataViewMock} />); + + expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('false'); + expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('true'); + + mockTestInitialLocalStorageValue = undefined; + }); + + test('should not restore a tab from local storage if unavailable', () => { + const registry = new DocViewsRegistry(); + registry.add({ id: 'test1', order: 10, title: 'Render function', render: jest.fn() }); + registry.add({ id: 'test2', order: 20, title: 'Render function', render: jest.fn() }); + + mockTestInitialLocalStorageValue = 'kbn_doc_viewer_tab_test3'; + + render(<DocViewer docViews={registry.getAll()} hit={records[0]} dataView={dataViewMock} />); + + expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('true'); + expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('false'); + + mockTestInitialLocalStorageValue = undefined; + }); }); diff --git a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.tsx b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.tsx index 74fbfcf0a6133..b139fae0b3f18 100644 --- a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.tsx +++ b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer.tsx @@ -6,11 +6,14 @@ * Side Public License, v 1. */ -import React from 'react'; -import { EuiTabbedContent } from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { DocViewerTab } from './doc_viewer_tab'; import type { DocView, DocViewRenderProps } from '../../types'; +export const INITIAL_TAB = 'unifiedDocViewer:initialTab'; + export interface DocViewerProps extends DocViewRenderProps { docViews: DocView[]; } @@ -26,7 +29,7 @@ export function DocViewer({ docViews, ...renderProps }: DocViewerProps) { .filter(({ enabled }) => enabled) // Filter out disabled doc views .map(({ id, title, render, component }: DocView) => { return { - id: `kbn_doc_viewer_tab_${id}`, + id: `kbn_doc_viewer_tab_${id}`, // `id` value is used to persist the selected tab in localStorage name: title, content: ( <DocViewerTab @@ -41,6 +44,16 @@ export function DocViewer({ docViews, ...renderProps }: DocViewerProps) { }; }); + const [initialTabId, setInitialTabId] = useLocalStorage<string>(INITIAL_TAB); + const initialSelectedTab = initialTabId ? tabs.find(({ id }) => id === initialTabId) : undefined; + + const onTabClick = useCallback( + (tab: EuiTabbedContentTab) => { + setInitialTabId(tab.id); + }, + [setInitialTabId] + ); + if (!tabs.length) { // There's a minimum of 2 tabs active in Discover. // This condition takes care of unit tests with 0 tabs. @@ -49,7 +62,12 @@ export function DocViewer({ docViews, ...renderProps }: DocViewerProps) { return ( <div className="kbnDocViewer" data-test-subj="kbnDocViewer"> - <EuiTabbedContent size="s" tabs={tabs} /> + <EuiTabbedContent + size="s" + tabs={tabs} + initialSelectedTab={initialSelectedTab} + onTabClick={onTabClick} + /> </div> ); } 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 68b3380b712ad..2b2aa90427a8d 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 @@ -193,8 +193,8 @@ const UnifiedFieldListSidebarContainer = memo( const deleteField = useMemo( () => dataView && dataViewFieldEditor && editField - ? (fieldName: string) => { - const ref = dataViewFieldEditor.openDeleteModal({ + ? async (fieldName: string) => { + const ref = await dataViewFieldEditor.openDeleteModal({ ctx: { dataView, }, diff --git a/packages/presentation/presentation_containers/index.ts b/packages/presentation/presentation_containers/index.ts index 06c1d7c04ee9c..224cfbb876214 100644 --- a/packages/presentation/presentation_containers/index.ts +++ b/packages/presentation/presentation_containers/index.ts @@ -13,6 +13,8 @@ export { type HasRuntimeChildState, type HasSerializedChildState, } from './interfaces/child_state'; +export { childrenUnsavedChanges$ } from './interfaces/unsaved_changes/children_unsaved_changes'; +export { initializeUnsavedChanges } from './interfaces/unsaved_changes/initialize_unsaved_changes'; export { apiHasSaveNotification, type HasSaveNotification, diff --git a/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.test.ts b/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.test.ts new file mode 100644 index 0000000000000..d816ce835fa51 --- /dev/null +++ b/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.test.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 { BehaviorSubject } from 'rxjs'; +import { childrenUnsavedChanges$, DEBOUNCE_TIME } from './children_unsaved_changes'; +import { waitFor } from '@testing-library/react'; + +describe('childrenUnsavedChanges$', () => { + const child1Api = { + unsavedChanges: new BehaviorSubject<object | undefined>(undefined), + resetUnsavedChanges: () => undefined, + }; + const child2Api = { + unsavedChanges: new BehaviorSubject<object | undefined>(undefined), + resetUnsavedChanges: () => undefined, + }; + const children$ = new BehaviorSubject<{ [key: string]: unknown }>({}); + const onFireMock = jest.fn(); + + beforeEach(() => { + onFireMock.mockReset(); + child1Api.unsavedChanges.next(undefined); + child2Api.unsavedChanges.next(undefined); + children$.next({ + child1: child1Api, + child2: child2Api, + }); + }); + + test('should emit on subscribe', async () => { + const subscription = childrenUnsavedChanges$(children$).subscribe(onFireMock); + + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(1); + const childUnsavedChanges = onFireMock.mock.calls[0][0]; + expect(childUnsavedChanges).toBeUndefined(); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); + + subscription.unsubscribe(); + }); + + test('should emit when child has new unsaved changes', async () => { + const subscription = childrenUnsavedChanges$(children$).subscribe(onFireMock); + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(1); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); + + child1Api.unsavedChanges.next({ + key1: 'modified value', + }); + + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(2); + const childUnsavedChanges = onFireMock.mock.calls[1][0]; + expect(childUnsavedChanges).toEqual({ + child1: { + key1: 'modified value', + }, + }); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); + + subscription.unsubscribe(); + }); + + test('should emit when children changes', async () => { + const subscription = childrenUnsavedChanges$(children$).subscribe(onFireMock); + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(1); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); + + // add child + children$.next({ + ...children$.value, + child3: { + unsavedChanges: new BehaviorSubject<object | undefined>({ key1: 'modified value' }), + resetUnsavedChanges: () => undefined, + }, + }); + + await waitFor( + () => { + expect(onFireMock).toHaveBeenCalledTimes(2); + const childUnsavedChanges = onFireMock.mock.calls[1][0]; + expect(childUnsavedChanges).toEqual({ + child3: { + key1: 'modified value', + }, + }); + }, + { + interval: DEBOUNCE_TIME + 1, + } + ); + + subscription.unsubscribe(); + }); +}); diff --git a/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.ts b/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.ts new file mode 100644 index 0000000000000..fa504f8eec4a3 --- /dev/null +++ b/packages/presentation/presentation_containers/interfaces/unsaved_changes/children_unsaved_changes.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. + */ + +import { combineLatest, debounceTime, distinctUntilChanged, map, of, switchMap } from 'rxjs'; +import deepEqual from 'fast-deep-equal'; +import { apiPublishesUnsavedChanges, PublishesUnsavedChanges } from '@kbn/presentation-publishing'; +import { PresentationContainer } from '../presentation_container'; + +export const DEBOUNCE_TIME = 100; + +/** + * Create an observable stream of unsaved changes from all react embeddable children + */ +export function childrenUnsavedChanges$(children$: PresentationContainer['children$']) { + return children$.pipe( + map((children) => Object.keys(children)), + distinctUntilChanged(deepEqual), + + // children may change, so make sure we subscribe/unsubscribe with switchMap + switchMap((newChildIds: string[]) => { + if (newChildIds.length === 0) return of([]); + const childrenThatPublishUnsavedChanges = Object.entries(children$.value).filter( + ([childId, child]) => apiPublishesUnsavedChanges(child) + ) as Array<[string, PublishesUnsavedChanges]>; + + return childrenThatPublishUnsavedChanges.length === 0 + ? of([]) + : combineLatest( + childrenThatPublishUnsavedChanges.map(([childId, child]) => + child.unsavedChanges.pipe(map((unsavedChanges) => ({ childId, unsavedChanges }))) + ) + ); + }), + debounceTime(DEBOUNCE_TIME), + map((unsavedChildStates) => { + const unsavedChildrenState: { [key: string]: object } = {}; + unsavedChildStates.forEach(({ childId, unsavedChanges }) => { + if (unsavedChanges) { + unsavedChildrenState[childId] = unsavedChanges; + } + }); + return Object.keys(unsavedChildrenState).length ? unsavedChildrenState : undefined; + }) + ); +} diff --git a/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.test.ts b/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.test.ts new file mode 100644 index 0000000000000..2a8f5a625d42a --- /dev/null +++ b/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.test.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 { BehaviorSubject, Subject } from 'rxjs'; +import { + COMPARATOR_SUBJECTS_DEBOUNCE, + initializeUnsavedChanges, +} from './initialize_unsaved_changes'; +import { PublishesUnsavedChanges, StateComparators } from '@kbn/presentation-publishing'; +import { waitFor } from '@testing-library/react'; + +interface TestState { + key1: string; + key2: string; +} + +describe('unsavedChanges api', () => { + const lastSavedState = { + key1: 'original key1 value', + key2: 'original key2 value', + } as TestState; + const key1$ = new BehaviorSubject(lastSavedState.key1); + const key2$ = new BehaviorSubject(lastSavedState.key2); + const comparators = { + key1: [key1$, (next: string) => key1$.next(next)], + key2: [key2$, (next: string) => key2$.next(next)], + } as StateComparators<TestState>; + const parentApi = { + saveNotification$: new Subject<void>(), + }; + + let api: undefined | PublishesUnsavedChanges; + beforeEach(() => { + key1$.next(lastSavedState.key1); + key2$.next(lastSavedState.key2); + ({ api } = initializeUnsavedChanges<TestState>(lastSavedState, parentApi, comparators)); + }); + + test('should have no unsaved changes after initialization', () => { + expect(api?.unsavedChanges.value).toBeUndefined(); + }); + + test('should have unsaved changes when state changes', async () => { + key1$.next('modified key1 value'); + await waitFor( + () => + expect(api?.unsavedChanges.value).toEqual({ + key1: 'modified key1 value', + }), + { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + } + ); + }); + + test('should have no unsaved changes after save', async () => { + key1$.next('modified key1 value'); + await waitFor(() => expect(api?.unsavedChanges.value).not.toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); + + // trigger save + parentApi.saveNotification$.next(); + + await waitFor(() => expect(api?.unsavedChanges.value).toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); + }); + + test('should have no unsaved changes after reset', async () => { + key1$.next('modified key1 value'); + await waitFor(() => expect(api?.unsavedChanges.value).not.toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); + + // trigger reset + api?.resetUnsavedChanges(); + + await waitFor(() => expect(api?.unsavedChanges.value).toBeUndefined(), { + interval: COMPARATOR_SUBJECTS_DEBOUNCE + 1, + }); + }); +}); diff --git a/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.ts b/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.ts new file mode 100644 index 0000000000000..7f4770d39cd2d --- /dev/null +++ b/packages/presentation/presentation_containers/interfaces/unsaved_changes/initialize_unsaved_changes.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + BehaviorSubject, + combineLatest, + combineLatestWith, + debounceTime, + map, + skip, + Subscription, +} from 'rxjs'; +import { + getInitialValuesFromComparators, + PublishesUnsavedChanges, + PublishingSubject, + runComparators, + StateComparators, +} from '@kbn/presentation-publishing'; +import { HasSnapshottableState } from '../serialized_state'; +import { apiHasSaveNotification } from '../has_save_notification'; + +export const COMPARATOR_SUBJECTS_DEBOUNCE = 100; + +export const initializeUnsavedChanges = <RuntimeState extends {} = {}>( + initialLastSavedState: RuntimeState, + parentApi: unknown, + comparators: StateComparators<RuntimeState> +) => { + const subscriptions: Subscription[] = []; + const lastSavedState$ = new BehaviorSubject<RuntimeState | undefined>(initialLastSavedState); + + const snapshotRuntimeState = () => { + const comparatorKeys = Object.keys(comparators) as Array<keyof RuntimeState>; + const snapshot = {} as RuntimeState; + comparatorKeys.forEach((key) => { + const comparatorSubject = comparators[key][0]; // 0th element of tuple is the subject + snapshot[key] = comparatorSubject.value as RuntimeState[typeof key]; + }); + return snapshot; + }; + + if (apiHasSaveNotification(parentApi)) { + subscriptions.push( + // any time the parent saves, the current state becomes the last saved state... + parentApi.saveNotification$.subscribe(() => { + lastSavedState$.next(snapshotRuntimeState()); + }) + ); + } + + const comparatorSubjects: Array<PublishingSubject<unknown>> = []; + const comparatorKeys: Array<keyof RuntimeState> = []; // index maps comparator subject to comparator key + for (const key of Object.keys(comparators) as Array<keyof RuntimeState>) { + const comparatorSubject = comparators[key][0]; // 0th element of tuple is the subject + comparatorSubjects.push(comparatorSubject as PublishingSubject<unknown>); + comparatorKeys.push(key); + } + + const unsavedChanges = new BehaviorSubject<Partial<RuntimeState> | undefined>( + runComparators( + comparators, + comparatorKeys, + lastSavedState$.getValue() as RuntimeState, + getInitialValuesFromComparators(comparators, comparatorKeys) + ) + ); + + subscriptions.push( + combineLatest(comparatorSubjects) + .pipe( + skip(1), + debounceTime(COMPARATOR_SUBJECTS_DEBOUNCE), + map((latestStates) => + comparatorKeys.reduce((acc, key, index) => { + acc[key] = latestStates[index] as RuntimeState[typeof key]; + return acc; + }, {} as Partial<RuntimeState>) + ), + combineLatestWith(lastSavedState$) + ) + .subscribe(([latestState, lastSavedState]) => { + unsavedChanges.next( + runComparators(comparators, comparatorKeys, lastSavedState, latestState) + ); + }) + ); + + return { + api: { + unsavedChanges, + resetUnsavedChanges: () => { + const lastSaved = lastSavedState$.getValue(); + for (const key of comparatorKeys) { + const setter = comparators[key][1]; // setter function is the 1st element of the tuple + setter(lastSaved?.[key] as RuntimeState[typeof key]); + } + }, + snapshotRuntimeState, + } as PublishesUnsavedChanges<RuntimeState> & HasSnapshottableState<RuntimeState>, + cleanup: () => { + subscriptions.forEach((subscription) => subscription.unsubscribe()); + }, + }; +}; diff --git a/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts b/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts index 4ac551620c376..626959f41a941 100644 --- a/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts +++ b/packages/presentation/presentation_publishing/interfaces/publishes_unsaved_changes.ts @@ -8,8 +8,8 @@ import { PublishingSubject } from '../publishing_subject'; -export interface PublishesUnsavedChanges { - unsavedChanges: PublishingSubject<object | undefined>; +export interface PublishesUnsavedChanges<Runtime extends object = object> { + unsavedChanges: PublishingSubject<Partial<Runtime> | undefined>; resetUnsavedChanges: () => void; } diff --git a/packages/serverless/settings/observability_project/index.ts b/packages/serverless/settings/observability_project/index.ts index 00d9d7b6d544f..07a9c84e210ff 100644 --- a/packages/serverless/settings/observability_project/index.ts +++ b/packages/serverless/settings/observability_project/index.ts @@ -33,4 +33,8 @@ export const OBSERVABILITY_PROJECT_SETTINGS = [ settings.OBSERVABILITY_APM_ENABLE_TABLE_SEARCH_BAR, settings.OBSERVABILITY_APM_ENABLE_SERVICE_INVENTORY_TABLE_SEARCH_BAR, settings.OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE, + settings.OBSERVABILITY_AI_ASSISTANT_LOGS_INDEX_PATTERN_ID, + settings.OBSERVABILITY_AI_ASSISTANT_RESPONSE_LANGUAGE, + settings.OBSERVABILITY_AI_ASSISTANT_SIMULATED_FUNCTION_CALLING, + settings.OBSERVABILITY_AI_ASSISTANT_SEARCH_CONNECTOR_INDEX_PATTERN, ]; diff --git a/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx b/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx index 36e275ced131b..d4e983f86e665 100644 --- a/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx +++ b/packages/shared-ux/chrome/navigation/__jest__/build_nav_tree.test.tsx @@ -12,6 +12,7 @@ import type { ChromeProjectNavigationNode, } from '@kbn/core-chrome-browser'; +import { EventTracker } from '../src/analytics'; import { renderNavigation } from './utils'; describe('builds navigation tree', () => { @@ -135,6 +136,43 @@ describe('builds navigation tree', () => { } }); + test('should track click event', async () => { + const navigateToUrl = jest.fn(); + const reportEvent = jest.fn(); + + const node: ChromeProjectNavigationNode = { + id: 'group1', + title: 'Group 1', + path: 'group1', + defaultIsCollapsed: false, + children: [ + { + id: 'item1', + title: 'Item 1', + href: 'https://foo', + path: 'group1.item1', + }, + ], + }; + + const { findByTestId } = renderNavigation({ + navTreeDef: of({ + body: [node], + }), + services: { navigateToUrl, eventTracker: new EventTracker({ reportEvent }) }, + }); + + const navItem = await findByTestId(/nav-item-group1.item1\s/); + navItem.click(); + + expect(navigateToUrl).toHaveBeenCalled(); + expect(reportEvent).toHaveBeenCalledWith('solutionNav_click_navlink', { + href: undefined, + href_prev: undefined, + id: 'item1', + }); + }); + test('should allow custom onClick handler for links', async () => { const navigateToUrl = jest.fn(); const onClick = jest.fn(); diff --git a/packages/shared-ux/chrome/navigation/__jest__/utils.tsx b/packages/shared-ux/chrome/navigation/__jest__/utils.tsx index 04d67c914ad42..41e5409613aac 100644 --- a/packages/shared-ux/chrome/navigation/__jest__/utils.tsx +++ b/packages/shared-ux/chrome/navigation/__jest__/utils.tsx @@ -19,13 +19,15 @@ import { NavigationProvider } from '../src/services'; import { Navigation } from '../src/ui/navigation'; import type { PanelContentProvider } from '../src/ui'; import { NavigationServices } from '../src/types'; +import { EventTracker } from '../src/analytics'; const activeNodes: ChromeProjectNavigationNode[][] = []; export const getServicesMock = (): NavigationServices => { const navigateToUrl = jest.fn().mockResolvedValue(undefined); - const basePath = { prepend: jest.fn((path: string) => `/base${path}`) }; + const basePath = { prepend: jest.fn((path: string) => `/base${path}`), remove: jest.fn() }; const recentlyAccessed$ = new BehaviorSubject([]); + const eventTracker = new EventTracker({ reportEvent: jest.fn() }); return { basePath, @@ -34,6 +36,7 @@ export const getServicesMock = (): NavigationServices => { navigateToUrl, activeNodes$: of(activeNodes), isSideNavCollapsed: false, + eventTracker, }; }; diff --git a/packages/shared-ux/chrome/navigation/index.ts b/packages/shared-ux/chrome/navigation/index.ts index aadb517ae31bc..77dceca515c47 100644 --- a/packages/shared-ux/chrome/navigation/index.ts +++ b/packages/shared-ux/chrome/navigation/index.ts @@ -9,6 +9,8 @@ export { NavigationKibanaProvider, NavigationProvider } from './src/services'; export { Navigation } from './src/ui'; +export { EventType, FieldType } from './src/analytics'; + export type { NavigationProps } from './src/ui'; export type { PanelComponentProps, PanelContent, PanelContentProvider } from './src/ui'; diff --git a/packages/shared-ux/chrome/navigation/mocks/storybook.ts b/packages/shared-ux/chrome/navigation/mocks/storybook.ts index c34819090c5f4..4a68717a9a5d9 100644 --- a/packages/shared-ux/chrome/navigation/mocks/storybook.ts +++ b/packages/shared-ux/chrome/navigation/mocks/storybook.ts @@ -9,6 +9,7 @@ import { AbstractStorybookMock } from '@kbn/shared-ux-storybook-mock'; import { action } from '@storybook/addon-actions'; import { BehaviorSubject } from 'rxjs'; +import { EventTracker } from '../src/analytics'; import { NavigationServices } from '../src/types'; type Arguments = NavigationServices; @@ -35,11 +36,12 @@ export class StorybookMock extends AbstractStorybookMock<{}, NavigationServices> return { ...params, - basePath: { prepend: (suffix: string) => `/basepath${suffix}` }, + basePath: { prepend: (suffix: string) => `/basepath${suffix}`, remove: () => '' }, navigateToUrl, recentlyAccessed$: params.recentlyAccessed$ ?? new BehaviorSubject([]), activeNodes$: params.activeNodes$ ?? new BehaviorSubject([]), isSideNavCollapsed: true, + eventTracker: new EventTracker({ reportEvent: action('Report event') }), }; } diff --git a/packages/shared-ux/chrome/navigation/src/analytics/event_tracker.ts b/packages/shared-ux/chrome/navigation/src/analytics/event_tracker.ts new file mode 100644 index 0000000000000..4d7a6258986be --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/analytics/event_tracker.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 { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; + +export enum EventType { + CLICK_NAVLINK = 'solutionNav_click_navlink', +} + +export enum FieldType { + ID = 'id', + HREF = 'href', + HREF_PREV = 'href_prev', +} + +export class EventTracker { + constructor(private analytics: Pick<AnalyticsServiceStart, 'reportEvent'>) {} + + private track(eventType: string, eventFields: object) { + try { + this.analytics.reportEvent(eventType, eventFields); + } catch (err) { + // eslint-disable-next-line no-console + console.error(`Navigation EventTracker error: ${err.toString()}`); + } + } + + /* + * Track whenever a user clicks on a navigation link in the side nav + */ + public clickNavLink({ id, href, hrefPrev }: { id: string; href?: string; hrefPrev?: string }) { + this.track(EventType.CLICK_NAVLINK, { + [FieldType.ID]: id, + [FieldType.HREF]: href, + [FieldType.HREF_PREV]: hrefPrev, + }); + } +} diff --git a/packages/shared-ux/chrome/navigation/src/analytics/index.ts b/packages/shared-ux/chrome/navigation/src/analytics/index.ts new file mode 100644 index 0000000000000..585ac82ca2835 --- /dev/null +++ b/packages/shared-ux/chrome/navigation/src/analytics/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 { EventTracker, EventType, FieldType } from './event_tracker'; diff --git a/packages/shared-ux/chrome/navigation/src/services.tsx b/packages/shared-ux/chrome/navigation/src/services.tsx index 617e5695579f3..4a82aa31dc966 100644 --- a/packages/shared-ux/chrome/navigation/src/services.tsx +++ b/packages/shared-ux/chrome/navigation/src/services.tsx @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import React, { FC, PropsWithChildren, useContext } from 'react'; +import React, { FC, PropsWithChildren, useContext, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; +import { EventTracker } from './analytics'; import { NavigationKibanaDependencies, NavigationServices } from './types'; @@ -31,19 +32,30 @@ export const NavigationKibanaProvider: FC<PropsWithChildren<NavigationKibanaDepe ...dependencies }) => { const { core, activeNodes$ } = dependencies; - const { chrome, http } = core; + const { chrome, http, analytics } = core; const { basePath } = http; const { navigateToUrl } = core.application; const isSideNavCollapsed = useObservable(chrome.getIsSideNavCollapsed$(), true); - const value: NavigationServices = { - basePath, - recentlyAccessed$: chrome.recentlyAccessed.get$(), - navigateToUrl, - navIsOpen: true, - activeNodes$, - isSideNavCollapsed, - }; + const value: NavigationServices = useMemo( + () => ({ + basePath, + recentlyAccessed$: chrome.recentlyAccessed.get$(), + navigateToUrl, + navIsOpen: true, + activeNodes$, + isSideNavCollapsed, + eventTracker: new EventTracker({ reportEvent: analytics.reportEvent }), + }), + [ + activeNodes$, + analytics.reportEvent, + basePath, + chrome.recentlyAccessed, + isSideNavCollapsed, + navigateToUrl, + ] + ); return <Context.Provider value={value}>{children}</Context.Provider>; }; diff --git a/packages/shared-ux/chrome/navigation/src/types.ts b/packages/shared-ux/chrome/navigation/src/types.ts index f15bb1a51e117..82d987250a981 100644 --- a/packages/shared-ux/chrome/navigation/src/types.ts +++ b/packages/shared-ux/chrome/navigation/src/types.ts @@ -15,8 +15,9 @@ import type { ChromeProjectNavigationNode, ChromeRecentlyAccessedHistoryItem, } from '@kbn/core-chrome-browser'; +import { EventTracker } from './analytics'; -type BasePathService = Pick<IBasePath, 'prepend'>; +export type BasePathService = Pick<IBasePath, 'prepend' | 'remove'>; /** * @internal @@ -35,6 +36,7 @@ export interface NavigationServices { navigateToUrl: NavigateToUrlFn; activeNodes$: Observable<ChromeProjectNavigationNode[][]>; isSideNavCollapsed: boolean; + eventTracker: EventTracker; } /** @@ -56,6 +58,9 @@ export interface NavigationKibanaDependencies { basePath: BasePathService; getLoadingCount$(): Observable<number>; }; + analytics: { + reportEvent: (eventType: string, eventData: object) => void; + }; }; activeNodes$: Observable<ChromeProjectNavigationNode[][]>; } diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx index 3d549cf663703..6a53239590d92 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx @@ -21,8 +21,9 @@ import type { EuiThemeSize, RenderAs } from '@kbn/core-chrome-browser/src/projec import { useNavigation as useServices } from '../../services'; import { isAbsoluteLink, isActiveFromUrl, isAccordionNode } from '../../utils'; -import type { NavigateToUrlFn } from '../../types'; +import type { BasePathService, NavigateToUrlFn } from '../../types'; import { useNavigation } from '../navigation'; +import { EventTracker } from '../../analytics'; import { useAccordionState } from '../hooks'; import { DEFAULT_IS_COLLAPSIBLE, @@ -183,6 +184,8 @@ const getEuiProps = ( treeDepth: number; getIsCollapsed: (path: string) => boolean; activeNodes: ChromeProjectNavigationNode[][]; + eventTracker: EventTracker; + basePath: BasePathService; } ): { navNode: ChromeProjectNavigationNode; @@ -192,7 +195,15 @@ const getEuiProps = ( dataTestSubj: string; spaceBefore?: EuiThemeSize | null; } & Pick<EuiCollapsibleNavItemProps, 'linkProps' | 'onClick'> => { - const { navigateToUrl, closePanel, treeDepth, getIsCollapsed, activeNodes } = deps; + const { + navigateToUrl, + closePanel, + treeDepth, + getIsCollapsed, + activeNodes, + eventTracker, + basePath, + } = deps; const { navNode, isItem, hasChildren, hasLink } = serializeNavNode(_navNode); const { path, href, onClick: customOnClick, isCollapsible = DEFAULT_IS_COLLAPSIBLE } = navNode; @@ -239,6 +250,14 @@ const getEuiProps = ( href, external: isExternal, onClick: (e) => { + if (href) { + eventTracker.clickNavLink({ + href: basePath.remove(href), + id: navNode.id, + hrefPrev: basePath.remove(window.location.pathname), + }); + } + if (customOnClick) { customOnClick(e); return; @@ -253,6 +272,14 @@ const getEuiProps = ( : undefined; const onClick = (e: React.MouseEvent<HTMLElement | HTMLButtonElement>) => { + if (href) { + eventTracker.clickNavLink({ + href: basePath.remove(href), + id: navNode.id, + hrefPrev: basePath.remove(window.location.pathname), + }); + } + if (customOnClick) { customOnClick(e); return; @@ -293,6 +320,8 @@ function nodeToEuiCollapsibleNavProps( treeDepth: number; getIsCollapsed: (path: string) => boolean; activeNodes: ChromeProjectNavigationNode[][]; + eventTracker: EventTracker; + basePath: BasePathService; } ): { items: Array<EuiCollapsibleNavItemProps | EuiCollapsibleNavSubItemPropsEnhanced>; @@ -369,7 +398,7 @@ interface Props { export const NavigationSectionUI: FC<Props> = React.memo(({ navNode: _navNode }) => { const { activeNodes } = useNavigation(); - const { navigateToUrl } = useServices(); + const { navigateToUrl, eventTracker, basePath } = useServices(); const [items, setItems] = useState<EuiCollapsibleNavSubItemProps[] | undefined>(); const { navNode } = useMemo( @@ -394,8 +423,10 @@ export const NavigationSectionUI: FC<Props> = React.memo(({ navNode: _navNode }) treeDepth: 0, getIsCollapsed, activeNodes, + eventTracker, + basePath, }); - }, [navNode, navigateToUrl, closePanel, getIsCollapsed, activeNodes]); + }, [navNode, navigateToUrl, closePanel, getIsCollapsed, activeNodes, eventTracker, basePath]); const { items: topLevelItems } = props; diff --git a/packages/shared-ux/chrome/navigation/tsconfig.json b/packages/shared-ux/chrome/navigation/tsconfig.json index d123d451597e6..9936cc9f1b892 100644 --- a/packages/shared-ux/chrome/navigation/tsconfig.json +++ b/packages/shared-ux/chrome/navigation/tsconfig.json @@ -22,6 +22,7 @@ "@kbn/i18n", "@kbn/shared-ux-storybook-mock", "@kbn/core-http-browser", + "@kbn/core-analytics-browser", ], "exclude": [ "target/**/*" diff --git a/packages/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx b/packages/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx index 8e8838e710e1f..c1842aff3baef 100644 --- a/packages/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx +++ b/packages/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx @@ -17,6 +17,7 @@ import { BadComponent } from '../../mocks'; describe('<KibanaErrorBoundaryProvider>', () => { let analytics: KibanaErrorBoundaryProviderDeps['analytics']; beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); analytics = analyticsServiceMock.createAnalyticsServiceStart(); }); diff --git a/packages/shared-ux/error_boundary/src/services/error_service.test.ts b/packages/shared-ux/error_boundary/src/services/error_service.test.ts index e89ff74af220f..4f72e997a41e4 100644 --- a/packages/shared-ux/error_boundary/src/services/error_service.test.ts +++ b/packages/shared-ux/error_boundary/src/services/error_service.test.ts @@ -9,6 +9,10 @@ import { KibanaErrorService } from './error_service'; describe('KibanaErrorBoundary Error Service', () => { + beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + const mockDeps = { analytics: { reportEvent: jest.fn() }, }; diff --git a/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx b/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx index 472aeba92ad5a..cc95b2978aeca 100644 --- a/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx +++ b/packages/shared-ux/error_boundary/src/ui/error_boundary.test.tsx @@ -19,6 +19,7 @@ import { errorMessageStrings as strings } from './message_strings'; describe('<KibanaErrorBoundary>', () => { let services: KibanaErrorBoundaryServices; beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}); services = getServicesMock(); }); diff --git a/packages/shared-ux/error_boundary/src/ui/error_boundary.tsx b/packages/shared-ux/error_boundary/src/ui/error_boundary.tsx index 8e38a9e172f13..7fd601f65cfc1 100644 --- a/packages/shared-ux/error_boundary/src/ui/error_boundary.tsx +++ b/packages/shared-ux/error_boundary/src/ui/error_boundary.tsx @@ -42,6 +42,9 @@ class ErrorBoundaryInternal extends React.Component< } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('Error caught by Kibana React Error Boundary'); // eslint-disable-line no-console + console.error(error); // eslint-disable-line no-console + const { name, isFatal } = this.props.services.errorService.registerError(error, errorInfo); this.setState(() => { return { error, errorInfo, componentName: name, isFatal }; diff --git a/renovate.json b/renovate.json index bc94410bdea46..6bdfe3bb5aec5 100644 --- a/renovate.json +++ b/renovate.json @@ -1,8 +1,8 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended"], + "extends": ["config:recommended", "helpers:pinGitHubActionDigests"], "ignorePaths": ["**/__fixtures__/**", "**/fixtures/**"], - "enabledManagers": ["npm"], + "enabledManagers": ["npm", "github-actions"], "baseBranches": ["main", "7.17"], "prConcurrentLimit": 0, "prHourlyLimit": 0, @@ -20,6 +20,20 @@ "matchDepPatterns": [".*"], "enabled": false }, + { + "matchPackageNames": [ + "docker.elastic.co/wolfi/chainguard-base" + ], + "enabled": true + }, + { + "groupName": "GitHub actions", + "matchManagers": ["github-actions"], + "reviewers": ["team:kibana-operations"], + "matchBaseBranches": ["main"], + "labels": ["Team:Operations", "backport:all-open", "release_note:skip"], + "enabled": true + }, { "groupName": "@elastic/charts", "matchDepNames": ["@elastic/charts"], @@ -117,6 +131,15 @@ "minimumReleaseAge": "7 days", "enabled": true }, + { + "groupName": "eslint-plugin-depend", + "matchDepPatterns": ["eslint-plugin-depend"], + "reviewers": ["team:kibana-operations"], + "matchBaseBranches": ["main"], + "labels": ["Team:Operations", "release_note:skip"], + "minimumReleaseAge": "7 days", + "enabled": true + }, { "groupName": "polyfills", "matchDepNames": ["core-js"], @@ -426,5 +449,18 @@ "labels": ["release_note:skip", "backport:all-open", "Team:Visualizations"], "enabled": true } + ], + "customManagers": [ + { + "description": "Update Wolfi base image", + "customType": "regex", + "fileMatch": [ + "^src/dev/build/tasks/os_packages/docker_generator/run\\.ts$" + ], + "matchStrings": [ + "docker\\.elastic\\.co/wolfi/chainguard-base:(?<currentValue>[-a-zA-Z0-9.]+)?(?:@(?<currentDigest>sha256:[a-fA-F0-9]+))?" + ], + "datasourceTemplate": "docker" + } ] } diff --git a/sonar-project.properties b/sonar-project.properties index 3cac455d961f4..ed771429e6f76 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,7 +4,6 @@ sonar.host.url=https://sonar.elastic.dev sonar.sources=\ packages, \ - plugins, \ src, \ x-pack/packages, \ x-pack/plugins @@ -13,6 +12,8 @@ sonar.exclusions=\ **/*.mocks.*, \ **/*.spec.*, \ **/*.stories.js, \ + **/*.md, \ + **/*.mdx, \ **/*.stories.ts, \ **/*.story.js, \ **/*.story.ts, \ @@ -37,7 +38,6 @@ sonar.exclusions=\ **/mock_responses/**/*, \ **/mocks/**/*, \ **/node_modules/**/*, \ - **/packages/**/*, \ **/public/**/*, \ **/scripts/**/*, \ **/storybook/**/*, \ diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 02ad163c43931..90f020a42e278 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -86,6 +86,7 @@ describe('checking migration metadata changes on all registered SO types', () => "core-usage-stats": "b3c04da317c957741ebcdedfea4524049fdc79ff", "csp-rule-template": "c151324d5f85178169395eecb12bac6b96064654", "dashboard": "211e9ca30f5a95d5f3c27b1bf2b58e6cfa0c9ae9", + "dynamic-config-overrides": "eb3ec7d96a42991068eda5421eecba9349c82d2b", "endpoint:unified-user-artifact-manifest": "71c7fcb52c658b21ea2800a6b6a76972ae1c776e", "endpoint:user-artifact-manifest": "1c3533161811a58772e30cdc77bac4631da3ef2b", "enterprise_search_telemetry": "9ac912e1417fc8681e0cd383775382117c9e3d3d", diff --git a/src/core/server/integration_tests/config/check_dynamic_config.test.ts b/src/core/server/integration_tests/config/check_dynamic_config.test.ts index 7239f051f41e7..335b688d5b2f4 100644 --- a/src/core/server/integration_tests/config/check_dynamic_config.test.ts +++ b/src/core/server/integration_tests/config/check_dynamic_config.test.ts @@ -7,11 +7,73 @@ */ import { set } from '@kbn/safer-lodash-set'; -import { Root } from '@kbn/core-root-server-internal'; -import { createRootWithCorePlugins } from '@kbn/core-test-helpers-kbn-server'; +import type { Root } from '@kbn/core-root-server-internal'; +import { + createTestServers, + createRootWithCorePlugins, + type TestElasticsearchUtils, + request, +} from '@kbn/core-test-helpers-kbn-server'; import { PLUGIN_SYSTEM_ENABLE_ALL_PLUGINS_CONFIG_PATH } from '@kbn/core-plugins-server-internal/src/constants'; -describe('checking migration metadata changes on all registered SO types', () => { +describe('PUT /internal/core/_settings', () => { + let esServer: TestElasticsearchUtils; + let root: Root; + + const loggerName = 'my-test-logger'; + + beforeAll(async () => { + const settings = { + coreApp: { allowDynamicConfigOverrides: true }, + logging: { + loggers: [{ name: loggerName, level: 'error', appenders: ['console'] }], + }, + }; + const { startES, startKibana } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + settings: { + kbn: settings, + }, + }); + + esServer = await startES(); + + const kbnUtils = await startKibana(); + root = kbnUtils.root; + + // eslint-disable-next-line dot-notation + root['server'].configService.addDynamicConfigPaths('logging', ['loggers']); // just for the sake of being able to change something easy to test + }); + + afterAll(async () => { + await root?.shutdown(); + await esServer?.stop(); + }); + + test('should update the log level', async () => { + const logger = root.logger.get(loggerName); + expect(logger.isLevelEnabled('info')).toBe(false); + await request + .put(root, '/internal/core/_settings') + .set('Elastic-Api-Version', '1') + .send({ 'logging.loggers': [{ name: loggerName, level: 'debug', appenders: ['console'] }] }) + .expect(200); + expect(logger.isLevelEnabled('info')).toBe(true); + }); + + test('should remove the setting', async () => { + const logger = root.logger.get(loggerName); + expect(logger.isLevelEnabled('info')).toBe(true); // still true from the previous test + await request + .put(root, '/internal/core/_settings') + .set('Elastic-Api-Version', '1') + .send({ 'logging.loggers': null }) + .expect(200); + expect(logger.isLevelEnabled('info')).toBe(false); + }); +}); + +describe('checking all opted-in dynamic config settings', () => { let root: Root; beforeAll(async () => { diff --git a/src/core/server/integration_tests/elasticsearch/version_compatibility.test.ts b/src/core/server/integration_tests/elasticsearch/version_compatibility.test.ts index bfd7575d58283..eb6cc767d5817 100644 --- a/src/core/server/integration_tests/elasticsearch/version_compatibility.test.ts +++ b/src/core/server/integration_tests/elasticsearch/version_compatibility.test.ts @@ -91,8 +91,7 @@ describe('Version Compatibility', () => { await expect(startServers({ customKibanaVersion: previousMinor() })).resolves.toBeUndefined(); }); - // FLAKY: https://github.com/elastic/kibana/issues/171289 - it.skip('should flag the incompatibility on version mismatch (ES is previous minor)', async () => { + it('should flag the incompatibility on version mismatch (ES is previous minor)', async () => { const found$ = new Subject<void>(); consoleSpy.mockImplementation((str) => { if (str.includes('is incompatible')) { diff --git a/src/core/server/integration_tests/metrics/elu_load.test.ts b/src/core/server/integration_tests/metrics/elu_load.test.ts index a36017c9dcda2..8c4299fd5fa5d 100644 --- a/src/core/server/integration_tests/metrics/elu_load.test.ts +++ b/src/core/server/integration_tests/metrics/elu_load.test.ts @@ -52,7 +52,10 @@ describe('GET /api/_elu_load', () => { }); it('gets ELU load average', async () => { - const { body } = await supertest(listener).get('/api/_elu_history').expect(200); + const { body } = await supertest(listener) + .get('/api/_elu_history') + .query({ apiVersion: '1', elasticInternalOrigin: 'true' }) + .expect(200); expect(body).toEqual({ history: { short: expect.any(Number), diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts index d82863f01928c..c02f9bc47d36e 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/multiple_kibana_nodes.test.ts @@ -106,8 +106,7 @@ async function createRoot({ logFileName }: CreateRootConfig) { // suite is very long, the 10mins default can cause timeouts jest.setTimeout(15 * 60 * 1000); -// FLAKY: https://github.com/elastic/kibana/issues/156117 -describe.skip('migration v2', () => { +describe('migration v2', () => { let esServer: TestElasticsearchUtils; let rootA: Root; let rootB: Root; 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 eebeaf9673973..6235f95147639 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 @@ -47,6 +47,7 @@ const previouslyRegisteredTypes = [ 'csp-rule-template', 'csp_rule', 'dashboard', + 'dynamic-config-overrides', // Added in 8.16 to persist the dynamic config overrides and share it with other nodes 'event-annotation-group', 'endpoint:user-artifact', 'endpoint:user-artifact-manifest', diff --git a/src/dev/build/lib/config.test.ts b/src/dev/build/lib/config.test.ts index 76a45ddb2e182..e19c2cb58a7c1 100644 --- a/src/dev/build/lib/config.test.ts +++ b/src/dev/build/lib/config.test.ts @@ -127,6 +127,7 @@ describe('#getTargetPlatforms()', () => { "linux-arm64", "linux-x64", "linux-x64", + "win32-arm64", "win32-x64", ] `); @@ -156,6 +157,7 @@ describe('#getNodePlatforms()', () => { 'linux-arm64', 'linux-x64', 'linux-x64', + 'win32-arm64', 'win32-x64', ]); }); diff --git a/src/dev/build/lib/platform.ts b/src/dev/build/lib/platform.ts index dc70fb47dcefc..a17228a48e79b 100644 --- a/src/dev/build/lib/platform.ts +++ b/src/dev/build/lib/platform.ts @@ -61,6 +61,7 @@ export const DOWNLOAD_PLATFORMS = [ new Platform('darwin', 'x64', 'darwin-x86_64', null), new Platform('darwin', 'arm64', 'darwin-aarch64', null), new Platform('win32', 'x64', 'windows-x86_64', null), + new Platform('win32', 'arm64', 'windows-arm64', null), ]; export const SERVERLESS_PLATFORMS = [ diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts index e52e500df42b9..531fe1b5ce174 100644 --- a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts @@ -92,8 +92,15 @@ it('runs expected fs operations', async () => { Object { "copy": Array [ Array [ - <absolute path>/.node_binaries/<node version>/default/node.exe, - <absolute path>/.node_binaries/<node version>/default/win32-x64/node.exe, + <absolute path>/.node_binaries/<node version>/default/win32-x64/download/node.exe, + <absolute path>/.node_binaries/<node version>/default/win32-x64/extract/node.exe, + Object { + "clone": true, + }, + ], + Array [ + <absolute path>/.node_binaries/<node version>/default/win32-arm64/download/node.exe, + <absolute path>/.node_binaries/<node version>/default/win32-arm64/extract/node.exe, Object { "clone": true, }, @@ -101,57 +108,57 @@ it('runs expected fs operations', async () => { ], "untar": Array [ Array [ - <absolute path>/.node_binaries/<node version>/<node variant>/node-v<node version>-linux-x64.tar.gz, - <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64/download/node-v<node version>-linux-x64.tar.gz, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64/extract, Object { "strip": 1, }, ], Array [ - <absolute path>/.node_binaries/<node version>/<node variant>/node-v<node version>-linux-arm64.tar.gz, - <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64/download/node-v<node version>-linux-arm64.tar.gz, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64/extract, Object { "strip": 1, }, ], Array [ - <absolute path>/.node_binaries/<node version>/default/node-v<node version>-darwin-x64.tar.gz, - <absolute path>/.node_binaries/<node version>/default/darwin-x64, + <absolute path>/.node_binaries/<node version>/default/darwin-x64/download/node-v<node version>-darwin-x64.tar.gz, + <absolute path>/.node_binaries/<node version>/default/darwin-x64/extract, Object { "strip": 1, }, ], Array [ - <absolute path>/.node_binaries/<node version>/default/node-v<node version>-darwin-arm64.tar.gz, - <absolute path>/.node_binaries/<node version>/default/darwin-arm64, + <absolute path>/.node_binaries/<node version>/default/darwin-arm64/download/node-v<node version>-darwin-arm64.tar.gz, + <absolute path>/.node_binaries/<node version>/default/darwin-arm64/extract, Object { "strip": 1, }, ], Array [ - <absolute path>/.node_binaries/<node version>/<node variant>/node-v<node version>-linux-x64.tar.gz, - <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64/download/node-v<node version>-linux-x64.tar.gz, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64/extract, Object { "strip": 1, }, ], Array [ - <absolute path>/.node_binaries/<node version>/<node variant>/node-v<node version>-linux-x64.tar.gz, - <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64/download/node-v<node version>-linux-x64.tar.gz, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-x64/extract, Object { "strip": 1, }, ], Array [ - <absolute path>/.node_binaries/<node version>/<node variant>/node-v<node version>-linux-arm64.tar.gz, - <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64/download/node-v<node version>-linux-arm64.tar.gz, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64/extract, Object { "strip": 1, }, ], Array [ - <absolute path>/.node_binaries/<node version>/<node variant>/node-v<node version>-linux-arm64.tar.gz, - <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64/download/node-v<node version>-linux-arm64.tar.gz, + <absolute path>/.node_binaries/<node version>/<node variant>/linux-arm64/extract, Object { "strip": 1, }, diff --git a/src/dev/build/tasks/nodejs/node_download_info.ts b/src/dev/build/tasks/nodejs/node_download_info.ts index 263704c41546d..9e5237a08e183 100644 --- a/src/dev/build/tasks/nodejs/node_download_info.ts +++ b/src/dev/build/tasks/nodejs/node_download_info.ts @@ -26,7 +26,7 @@ export function getNodeDownloadInfo(config: Config, platform: Platform) { return variants.map((variant) => { const downloadName = platform.isWindows() - ? 'win-x64/node.exe' + ? `win-${platform.getArchitecture()}/node.exe` : `node-v${version}-${arch}.tar.gz`; let variantPath = ''; @@ -36,9 +36,17 @@ export function getNodeDownloadInfo(config: Config, platform: Platform) { '.node_binaries', version, variant, + platform.getNodeArch(), + 'download', basename(downloadName) ); - const extractDir = config.resolveFromRepo('.node_binaries', version, variant, arch); + const extractDir = config.resolveFromRepo( + '.node_binaries', + version, + variant, + platform.getNodeArch(), + 'extract' + ); return { url, diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts index 995244e9d2b84..799ed86671730 100644 --- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts @@ -141,6 +141,11 @@ it('checks shasums for each downloaded node build', async () => { "<node version>", undefined, ], + Array [ + <ToolingLog>, + "<node version>", + undefined, + ], ], "results": Array [ Object { @@ -152,6 +157,20 @@ it('checks shasums for each downloaded node build', async () => { "linux:default:linux-x64:downloadName": "valid shasum", "linux:serverless:linux-arm64:downloadName": "valid shasum", "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", + }, + }, + Object { + "type": "return", + "value": Object { + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", "win32:default:win32-x64:downloadName": "valid shasum", }, }, @@ -164,6 +183,7 @@ it('checks shasums for each downloaded node build', async () => { "linux:default:linux-x64:downloadName": "valid shasum", "linux:serverless:linux-arm64:downloadName": "valid shasum", "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", "win32:default:win32-x64:downloadName": "valid shasum", }, }, @@ -176,6 +196,7 @@ it('checks shasums for each downloaded node build', async () => { "linux:default:linux-x64:downloadName": "valid shasum", "linux:serverless:linux-arm64:downloadName": "valid shasum", "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", "win32:default:win32-x64:downloadName": "valid shasum", }, }, @@ -188,6 +209,7 @@ it('checks shasums for each downloaded node build', async () => { "linux:default:linux-x64:downloadName": "valid shasum", "linux:serverless:linux-arm64:downloadName": "valid shasum", "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", "win32:default:win32-x64:downloadName": "valid shasum", }, }, @@ -200,6 +222,7 @@ it('checks shasums for each downloaded node build', async () => { "linux:default:linux-x64:downloadName": "valid shasum", "linux:serverless:linux-arm64:downloadName": "valid shasum", "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", "win32:default:win32-x64:downloadName": "valid shasum", }, }, @@ -212,6 +235,7 @@ it('checks shasums for each downloaded node build', async () => { "linux:default:linux-x64:downloadName": "valid shasum", "linux:serverless:linux-arm64:downloadName": "valid shasum", "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", "win32:default:win32-x64:downloadName": "valid shasum", }, }, @@ -224,6 +248,7 @@ it('checks shasums for each downloaded node build', async () => { "linux:default:linux-x64:downloadName": "valid shasum", "linux:serverless:linux-arm64:downloadName": "valid shasum", "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-arm64:downloadName": "valid shasum", "win32:default:win32-x64:downloadName": "valid shasum", }, }, @@ -278,6 +303,15 @@ it('checks shasums for each downloaded node build', async () => { "variant": null, }, ], + Array [ + <Config>, + Platform { + "architecture": "arm64", + "buildName": "windows-arm64", + "name": "win32", + "variant": null, + }, + ], Array [ <Config>, Platform { @@ -343,6 +377,15 @@ it('checks shasums for each downloaded node build', async () => { }, ], }, + Object { + "type": "return", + "value": Array [ + Object { + "downloadName": "win32:default:win32-arm64:downloadName", + "downloadPath": "win32:default:win32-arm64:downloadPath", + }, + ], + }, Object { "type": "return", "value": Array [ @@ -387,6 +430,10 @@ it('checks shasums for each downloaded node build', async () => { "win32:default:win32-x64:downloadPath", "sha256", ], + Array [ + "win32:default:win32-arm64:downloadPath", + "sha256", + ], Array [ "linux:serverless:linux-x64:downloadPath", "sha256", @@ -425,6 +472,10 @@ it('checks shasums for each downloaded node build', async () => { "type": "return", "value": "valid shasum", }, + Object { + "type": "return", + "value": "valid shasum", + }, ], } `); diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index 052d7592024d7..1a3708a3d5bcd 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -111,7 +111,7 @@ export const CreateDockerServerless: Task = { async run(config, log, build) { await runDockerGenerator(config, log, build, { architecture: 'x64', - baseImage: 'ubuntu', + baseImage: 'wolfi', context: false, serverless: true, image: true, @@ -119,7 +119,7 @@ export const CreateDockerServerless: Task = { }); await runDockerGenerator(config, log, build, { architecture: 'aarch64', - baseImage: 'ubuntu', + baseImage: 'wolfi', context: false, serverless: true, image: true, @@ -210,7 +210,7 @@ export const CreateDockerContexts: Task = { image: false, }); await runDockerGenerator(config, log, build, { - baseImage: 'ubuntu', + baseImage: 'wolfi', serverless: true, context: true, image: false, diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index 25cb0d8d0d2d1..24b5a8c950b61 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -42,12 +42,19 @@ export async function runDockerGenerator( let baseImageName = ''; if (flags.baseImage === 'ubuntu') baseImageName = 'ubuntu:20.04'; if (flags.baseImage === 'ubi') baseImageName = 'docker.elastic.co/ubi9/ubi-minimal:latest'; + /** + * Renovate config contains a regex manager to automatically updates this Chainguard reference + * + * If this logic moves to another file or under another name, then the Renovate regex manager + * for automatic Chainguard updates will break. + */ if (flags.baseImage === 'wolfi') - baseImageName = 'docker.elastic.co/wolfi/chainguard-base:20230214'; + baseImageName = + 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:082266206be6e559baea1c8b2eeb4fb86ea1318a0cf99cbf0e612dd2c611e80b'; let imageFlavor = ''; if (flags.baseImage === 'ubi') imageFlavor += `-ubi`; - if (flags.baseImage === 'wolfi') imageFlavor += `-wolfi`; + if (flags.baseImage === 'wolfi' && !flags.serverless) imageFlavor += `-wolfi`; if (flags.ironbank) imageFlavor += '-ironbank'; if (flags.cloud) imageFlavor += '-cloud'; if (flags.serverless) imageFlavor += '-serverless'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index 0195825313b57..acd5b54a74f1b 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -134,7 +134,7 @@ RUN for iter in {1..10}; do \ (exit $exit_code) {{/ubuntu}} {{#wolfi}} -RUN apk --no-cache add bash curl fontconfig libstdc++ freetype nss findutils shadow +RUN apk --no-cache add bash curl fontconfig libstdc++ nss findutils shadow {{/wolfi}} # Bring in Kibana from the initial stage. diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index b241cc96b8a83..b859c92a36f36 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -86,7 +86,7 @@ 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.5.3': ['Elastic License 2.0'], - '@elastic/eui@95.4.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@95.6.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 '@bufbuild/protobuf@1.2.1': ['Apache-2.0'], // license (Apache-2.0 AND BSD-3-Clause) diff --git a/src/plugins/ai_assistant_management/selection/public/app_context.tsx b/src/plugins/ai_assistant_management/selection/public/app_context.tsx index 9f7998b36800d..b8e27c9bc34bf 100644 --- a/src/plugins/ai_assistant_management/selection/public/app_context.tsx +++ b/src/plugins/ai_assistant_management/selection/public/app_context.tsx @@ -9,6 +9,7 @@ import React, { createContext, useContext } from 'react'; import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser'; import type { CoreStart } from '@kbn/core/public'; +import type { BuildFlavor } from '@kbn/config'; import type { StartDependencies } from './plugin'; interface ContextValue extends StartDependencies { @@ -16,6 +17,8 @@ interface ContextValue extends StartDependencies { capabilities: CoreStart['application']['capabilities']; navigateToApp: CoreStart['application']['navigateToApp']; + kibanaBranch: string; + buildFlavor: BuildFlavor; } const AppContext = createContext<ContextValue>(null as any); diff --git a/src/plugins/ai_assistant_management/selection/public/index.ts b/src/plugins/ai_assistant_management/selection/public/index.ts index 46a20ceb7ffc8..a68138b5eb139 100644 --- a/src/plugins/ai_assistant_management/selection/public/index.ts +++ b/src/plugins/ai_assistant_management/selection/public/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { PluginInitializer } from '@kbn/core/public'; +import type { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; import { AIAssistantManagementPlugin } from './plugin'; import type { @@ -24,4 +24,5 @@ export type { export const plugin: PluginInitializer< AIAssistantManagementSelectionPluginPublicSetup, AIAssistantManagementSelectionPluginPublicStart -> = () => new AIAssistantManagementPlugin(); +> = (initializerContext: PluginInitializerContext) => + new AIAssistantManagementPlugin(initializerContext); diff --git a/src/plugins/ai_assistant_management/selection/public/management_section/mount_section.tsx b/src/plugins/ai_assistant_management/selection/public/management_section/mount_section.tsx index a1bb4e19f2265..5dc59b979cca0 100644 --- a/src/plugins/ai_assistant_management/selection/public/management_section/mount_section.tsx +++ b/src/plugins/ai_assistant_management/selection/public/management_section/mount_section.tsx @@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n'; import type { CoreSetup } from '@kbn/core/public'; import { wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import type { ManagementAppMountParams } from '@kbn/management-plugin/public'; +import type { BuildFlavor } from '@kbn/config'; import type { StartDependencies, AIAssistantManagementSelectionPluginPublicStart } from '../plugin'; import { aIAssistantManagementSelectionRouter } from '../routes/config'; import { RedirectToHomeIfUnauthorized } from '../routes/components/redirect_to_home_if_unauthorized'; @@ -22,9 +23,16 @@ import { AppContextProvider } from '../app_context'; interface MountParams { core: CoreSetup<StartDependencies, AIAssistantManagementSelectionPluginPublicStart>; mountParams: ManagementAppMountParams; + kibanaBranch: string; + buildFlavor: BuildFlavor; } -export const mountManagementSection = async ({ core, mountParams }: MountParams) => { +export const mountManagementSection = async ({ + core, + mountParams, + kibanaBranch, + buildFlavor, +}: MountParams) => { const [coreStart, startDeps] = await core.getStartServices(); const { element, history, setBreadcrumbs } = mountParams; const { theme$ } = core.theme; @@ -45,6 +53,8 @@ export const mountManagementSection = async ({ core, mountParams }: MountParams) capabilities: coreStart.application.capabilities, navigateToApp: coreStart.application.navigateToApp, setBreadcrumbs, + kibanaBranch, + buildFlavor, }} > <RouterProvider history={history} router={aIAssistantManagementSelectionRouter as any}> diff --git a/src/plugins/ai_assistant_management/selection/public/plugin.ts b/src/plugins/ai_assistant_management/selection/public/plugin.ts index e24a82dbddf20..99b96b90a5b53 100644 --- a/src/plugins/ai_assistant_management/selection/public/plugin.ts +++ b/src/plugins/ai_assistant_management/selection/public/plugin.ts @@ -7,11 +7,12 @@ */ import { i18n } from '@kbn/i18n'; -import { type CoreSetup, Plugin, type CoreStart } from '@kbn/core/public'; +import { type CoreSetup, Plugin, type CoreStart, PluginInitializerContext } from '@kbn/core/public'; import type { ManagementSetup } from '@kbn/management-plugin/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import type { ServerlessPluginSetup } from '@kbn/serverless/public'; import { BehaviorSubject, Observable } from 'rxjs'; +import type { BuildFlavor } from '@kbn/config'; import { AIAssistantType } from '../common/ai_assistant_type'; import { PREFERRED_AI_ASSISTANT_TYPE_SETTING_KEY } from '../common/ui_setting_keys'; @@ -40,7 +41,13 @@ export class AIAssistantManagementPlugin StartDependencies > { - constructor() {} + private readonly kibanaBranch: string; + private readonly buildFlavor: BuildFlavor; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.kibanaBranch = this.initializerContext.env.packageInfo.branch; + this.buildFlavor = this.initializerContext.env.packageInfo.buildFlavor; + } public setup( core: CoreSetup<StartDependencies, AIAssistantManagementSelectionPluginPublicStart>, @@ -78,6 +85,8 @@ export class AIAssistantManagementPlugin return mountManagementSection({ core, mountParams, + kibanaBranch: this.kibanaBranch, + buildFlavor: this.buildFlavor, }); }, }); diff --git a/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.test.tsx b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.test.tsx new file mode 100644 index 0000000000000..c926b83716302 --- /dev/null +++ b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.test.tsx @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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, fireEvent } from '@testing-library/react'; +import type { CoreStart } from '@kbn/core/public'; +import { AiAssistantSelectionPage } from './ai_assistant_selection_page'; +import { useAppContext } from '../../app_context'; +import { I18nProvider } from '@kbn/i18n-react'; + +jest.mock('../../app_context'); + +describe('AiAssistantSelectionPage', () => { + const setBreadcrumbs = jest.fn(); + const navigateToApp = jest.fn(); + + const generateMockCapabilities = (hasPermission: boolean) => + ({ + observabilityAIAssistant: { show: hasPermission }, + securitySolutionAssistant: { 'ai-assistant': hasPermission }, + } as unknown as CoreStart['application']['capabilities']); + + const testCapabilities = generateMockCapabilities(true); + + const renderComponent = (capabilities: CoreStart['application']['capabilities']) => { + (useAppContext as jest.Mock).mockReturnValue({ + capabilities, + setBreadcrumbs, + navigateToApp, + kibanaBranch: 'main', + buildFlavor: 'ess', + }); + render(<AiAssistantSelectionPage />, { + wrapper: I18nProvider, + }); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('sets the breadcrumbs on mount', () => { + renderComponent(testCapabilities); + expect(setBreadcrumbs).toHaveBeenCalledWith([ + { + text: 'AI Assistant', + }, + ]); + }); + + it('renders the title and description', () => { + renderComponent(generateMockCapabilities(true)); + expect(screen.getByTestId('pluginsAiAssistantSelectionPageTitle')).toBeInTheDocument(); + expect(screen.getByTestId('pluginsAiAssistantSelectionPageDescription')).toBeInTheDocument(); + }); + + describe('Observability AI Assistant Card', () => { + describe('when the feature is disabled', () => { + it('displays the disabled callout', () => { + renderComponent(generateMockCapabilities(false)); + expect( + screen.getByTestId('pluginsAiAssistantSelectionPageObservabilityDocumentationCallout') + ).toBeInTheDocument(); + }); + }); + + describe('when the feature is enabled', () => { + it('does not display the disabled callout', () => { + renderComponent(testCapabilities); + expect( + screen.queryByTestId('pluginsAiAssistantSelectionPageObservabilityDocumentationCallout') + ).not.toBeInTheDocument(); + }); + + it('renders the manage settings button', () => { + renderComponent(testCapabilities); + expect(screen.getByTestId('pluginsAiAssistantSelectionPageButton')).toBeInTheDocument(); + }); + + it('navigates to the observability AI Assistant settings on button click', () => { + renderComponent(testCapabilities); + fireEvent.click(screen.getByTestId('pluginsAiAssistantSelectionPageButton')); + expect(navigateToApp).toHaveBeenCalledWith('management', { + path: 'kibana/observabilityAiAssistantManagement', + }); + }); + + it('renders the documentation links correctly', () => { + renderComponent(testCapabilities); + + expect( + screen.getByTestId('pluginsAiAssistantSelectionPageDocumentationLink') + ).toHaveAttribute( + 'href', + 'https://www.elastic.co/guide/en/observability/master/obs-ai-assistant.html' + ); + }); + }); + }); + + describe('Security AI Assistant Card', () => { + describe('when the feature is disabled', () => { + it('displays the disabled callout', () => { + renderComponent(generateMockCapabilities(false)); + expect( + screen.getByTestId('pluginsAiAssistantSelectionPageSecurityDocumentationCallout') + ).toBeInTheDocument(); + }); + }); + + describe('when the feature is enabled', () => { + it('does not display the disabled callout', () => { + renderComponent(testCapabilities); + expect( + screen.queryByTestId('pluginsAiAssistantSelectionPageSecurityDocumentationCallout') + ).not.toBeInTheDocument(); + }); + + it('renders the manage settings button', () => { + renderComponent(testCapabilities); + expect( + screen.getByTestId('pluginsAiAssistantSelectionSecurityPageButton') + ).toBeInTheDocument(); + }); + + it('navigates to the security AI Assistant settings on button click', () => { + renderComponent(testCapabilities); + fireEvent.click(screen.getByTestId('pluginsAiAssistantSelectionSecurityPageButton')); + expect(navigateToApp).toHaveBeenCalledWith('management', { + path: 'kibana/securityAiAssistantManagement', + }); + }); + + it('renders the documentation links correctly', () => { + renderComponent(testCapabilities); + + expect( + screen.getByTestId('securityAiAssistantSelectionPageDocumentationLink') + ).toHaveAttribute( + 'href', + 'https://www.elastic.co/guide/en/security/master/security-assistant.html' + ); + }); + }); + }); +}); diff --git a/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx index 3b50e26d4de08..ad99c58af1f83 100644 --- a/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx +++ b/src/plugins/ai_assistant_management/selection/public/routes/components/ai_assistant_selection_page.tsx @@ -21,12 +21,16 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { getDocLinks } from '@kbn/doc-links'; import { useAppContext } from '../../app_context'; export function AiAssistantSelectionPage() { - const { capabilities, setBreadcrumbs, navigateToApp } = useAppContext(); + const { capabilities, setBreadcrumbs, navigateToApp, buildFlavor, kibanaBranch } = + useAppContext(); const observabilityAIAssistantEnabled = capabilities.observabilityAIAssistant?.show; const securityAIAssistantEnabled = capabilities.securitySolutionAssistant?.['ai-assistant']; + const observabilityDoc = getDocLinks({ buildFlavor, kibanaBranch }).observability.aiAssistant; + const securityDoc = getDocLinks({ buildFlavor, kibanaBranch }).securitySolution.aiAssistant; useEffect(() => { setBreadcrumbs([ @@ -40,7 +44,7 @@ export function AiAssistantSelectionPage() { return ( <> - <EuiTitle size="l"> + <EuiTitle size="l" data-test-subj="pluginsAiAssistantSelectionPageTitle"> <h2> {i18n.translate( 'aiAssistantManagementSelection.aiAssistantSettingsPage.h2.aIAssistantLabel', @@ -53,7 +57,7 @@ export function AiAssistantSelectionPage() { <EuiSpacer size="m" /> - <EuiText> + <EuiText data-test-subj="pluginsAiAssistantSelectionPageDescription"> {i18n.translate( 'aiAssistantManagementSelection.aiAssistantSettingsPage.descriptionTextLabel', { @@ -68,6 +72,7 @@ export function AiAssistantSelectionPage() { <EuiFlexGrid columns={2}> <EuiFlexItem grow> <EuiCard + data-test-subj="aiAssistantSelectionPageObservabilityCard" description={ <div> {!observabilityAIAssistantEnabled ? ( @@ -75,6 +80,7 @@ export function AiAssistantSelectionPage() { <EuiSpacer size="s" /> <EuiCallOut iconType="warning" + data-test-subj="pluginsAiAssistantSelectionPageObservabilityDocumentationCallout" title={i18n.translate( 'aiAssistantManagementSelection.aiAssistantSelectionPage.observabilityAi.thisFeatureIsDisabledCallOutLabel', { @@ -82,6 +88,7 @@ export function AiAssistantSelectionPage() { } )} size="s" + className="eui-displayInlineBlock" /> <EuiSpacer size="s" /> </> @@ -89,14 +96,14 @@ export function AiAssistantSelectionPage() { <p> <FormattedMessage id="aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkDescription" - defaultMessage="For more info, see our {documentation}." + defaultMessage="For more info, refer to our {documentation}." values={{ documentation: ( <EuiLink data-test-subj="pluginsAiAssistantSelectionPageDocumentationLink" external target="_blank" - href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html" + href={observabilityDoc} > {i18n.translate( 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.documentationLinkLabel', @@ -107,20 +114,22 @@ export function AiAssistantSelectionPage() { }} /> </p> - <EuiButton - iconType="gear" - data-test-subj="pluginsAiAssistantSelectionPageButton" - onClick={() => - navigateToApp('management', { - path: 'kibana/observabilityAiAssistantManagement', - }) - } - > - {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.manageSettingsButtonLabel', - { defaultMessage: 'Manage Settings' } - )} - </EuiButton> + {observabilityAIAssistantEnabled && ( + <EuiButton + iconType="gear" + data-test-subj="pluginsAiAssistantSelectionPageButton" + onClick={() => + navigateToApp('management', { + path: 'kibana/observabilityAiAssistantManagement', + }) + } + > + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSelectionPage.obsAssistant.manageSettingsButtonLabel', + { defaultMessage: 'Manage Settings' } + )} + </EuiButton> + )} </div> } display="plain" @@ -143,14 +152,16 @@ export function AiAssistantSelectionPage() { <EuiSpacer size="s" /> <EuiCallOut iconType="warning" + data-test-subj="pluginsAiAssistantSelectionPageSecurityDocumentationCallout" title={i18n.translate( 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityAi.thisFeatureIsDisabledCallOutLabel', { defaultMessage: - 'This feature is disabled. It can be enabled from Spaces > Features.', + 'This feature is disabled. You can enable it from from Spaces > Features.', } )} size="s" + className="eui-displayInlineBlock" /> <EuiSpacer size="s" /> </> @@ -158,14 +169,14 @@ export function AiAssistantSelectionPage() { <p> <FormattedMessage id="aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.documentationLinkDescription" - defaultMessage="For more info, see our {documentation}." + defaultMessage="For more info, refer to our {documentation}." values={{ documentation: ( <EuiLink data-test-subj="securityAiAssistantSelectionPageDocumentationLink" external target="_blank" - href="https://www.elastic.co/guide/en/security/current/security-assistant.html" + href={securityDoc} > {i18n.translate( 'aiAssistantManagementSelection.aiAssistantSettingsPage.securityAssistant.documentationLinkLabel', @@ -176,18 +187,20 @@ export function AiAssistantSelectionPage() { }} /> </p> - <EuiButton - data-test-subj="pluginsAiAssistantSelectionPageButton" - iconType="gear" - onClick={() => - navigateToApp('management', { path: 'kibana/securityAiAssistantManagement' }) - } - > - {i18n.translate( - 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.manageSettingsButtonLabel', - { defaultMessage: 'Manage Settings' } - )} - </EuiButton> + {securityAIAssistantEnabled && ( + <EuiButton + data-test-subj="pluginsAiAssistantSelectionSecurityPageButton" + iconType="gear" + onClick={() => + navigateToApp('management', { path: 'kibana/securityAiAssistantManagement' }) + } + > + {i18n.translate( + 'aiAssistantManagementSelection.aiAssistantSelectionPage.securityAssistant.manageSettingsButtonLabel', + { defaultMessage: 'Manage Settings' } + )} + </EuiButton> + )} </div> } display="plain" diff --git a/src/plugins/ai_assistant_management/selection/tsconfig.json b/src/plugins/ai_assistant_management/selection/tsconfig.json index a57f7dbc6c12d..6bd8efe0e80b8 100644 --- a/src/plugins/ai_assistant_management/selection/tsconfig.json +++ b/src/plugins/ai_assistant_management/selection/tsconfig.json @@ -16,7 +16,9 @@ "@kbn/serverless", "@kbn/config-schema", "@kbn/core-plugins-server", - "@kbn/features-plugin" + "@kbn/features-plugin", + "@kbn/config", + "@kbn/doc-links" ], "exclude": ["target/**/*"] } diff --git a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx index 30697210a902a..d8fe60dc8c5e9 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx +++ b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx @@ -38,6 +38,7 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => { const { services: { notifications, esHostService, settings: settingsService, autocompleteInfo }, docLinkVersion, + config: { isDevMode }, } = context; const { toasts } = notifications; const { settings } = useEditorReadContext(); @@ -73,13 +74,13 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => { const editorDidMountCallback = useCallback( (editor: monaco.editor.IStandaloneCodeEditor) => { - const provider = new MonacoEditorActionsProvider(editor, setEditorActionsCss); + const provider = new MonacoEditorActionsProvider(editor, setEditorActionsCss, isDevMode); setInputEditor(provider); actionsProvider.current = provider; setupResizeChecker(divRef.current!, editor); setEditorInstace(editor); }, - [setupResizeChecker, setInputEditor, setEditorInstace] + [setupResizeChecker, setInputEditor, setEditorInstace, isDevMode] ); useEffect(() => { @@ -176,6 +177,9 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => { fontSize: settings.fontSize, wordWrap: settings.wrapMode === true ? 'on' : 'off', theme: CONSOLE_THEME_ID, + // Make the quick-fix window be fixed to the window rather than clipped by + // the parent content set with overflow: hidden/auto + fixedOverflowWidgets: true, }} suggestionProvider={suggestionProvider} enableFindAction={true} diff --git a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.test.ts b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.test.ts index b2bcbb70bcfab..cf7bd2090c5bc 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.test.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.test.ts @@ -94,7 +94,7 @@ describe('Editor actions provider', () => { const setEditorActionsCssMock = jest.fn(); - editorActionsProvider = new MonacoEditorActionsProvider(editor, setEditorActionsCssMock); + editorActionsProvider = new MonacoEditorActionsProvider(editor, setEditorActionsCssMock, true); }); describe('getCurl', () => { diff --git a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts index 6ed00d04837b8..2a89147b0de29 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_actions_provider.ts @@ -46,13 +46,16 @@ const TRIGGER_SUGGESTIONS_ACTION_LABEL = 'Trigger suggestions'; const TRIGGER_SUGGESTIONS_HANDLER_ID = 'editor.action.triggerSuggest'; const DEBOUNCE_HIGHLIGHT_WAIT_MS = 200; const DEBOUNCE_AUTOCOMPLETE_WAIT_MS = 500; +const INSPECT_TOKENS_LABEL = 'Inspect tokens'; +const INSPECT_TOKENS_HANDLER_ID = 'editor.action.inspectTokens'; export class MonacoEditorActionsProvider { private parsedRequestsProvider: ConsoleParsedRequestsProvider; private highlightedLines: monaco.editor.IEditorDecorationsCollection; constructor( private editor: monaco.editor.IStandaloneCodeEditor, - private setEditorActionsCss: (css: CSSProperties) => void + private setEditorActionsCss: (css: CSSProperties) => void, + private isDevMode: boolean ) { this.parsedRequestsProvider = getParsedRequestsProvider(this.editor.getModel()); this.highlightedLines = this.editor.createDecorationsCollection(); @@ -97,6 +100,9 @@ export class MonacoEditorActionsProvider { if (event.keyCode === monaco.KeyCode.Backspace) { debouncedTriggerSuggestions(); } + if (this.isDevMode && event.keyCode === monaco.KeyCode.F1) { + this.editor.trigger(INSPECT_TOKENS_LABEL, INSPECT_TOKENS_HANDLER_ID, {}); + } }); } @@ -215,8 +221,20 @@ export class MonacoEditorActionsProvider { } = context; const { toasts } = notifications; try { - const requests = await this.getRequests(); - if (!requests.length) { + const allRequests = await this.getRequests(); + // if any request doesnt have a method then we gonna treat it as a non-valid + // request + const requests = allRequests.filter((request) => request.method); + + // If we do have requests but none have methods we are not sending the request + if (allRequests.length > 0 && !requests.length) { + toasts.addWarning( + i18n.translate('console.notification.monaco.error.nonSupportedRequest', { + defaultMessage: 'The selected request is not valid.', + }) + ); + return; + } else if (!requests.length) { toasts.add( i18n.translate('console.notification.monaco.error.noRequestSelectedTitle', { defaultMessage: diff --git a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_output.tsx b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_output.tsx index dfbcfd7e4aa75..e1a8a692f40db 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_output.tsx +++ b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor_output.tsx @@ -14,6 +14,7 @@ import Protobuf from 'pbf'; import { i18n } from '@kbn/i18n'; import { EuiScreenReaderOnly } from '@elastic/eui'; import { CONSOLE_THEME_ID, CONSOLE_OUTPUT_LANG_ID, monaco } from '@kbn/monaco'; +import { getStatusCodeDecorations } from './utils'; import { useEditorReadContext, useRequestReadContext } from '../../../contexts'; import { convertMapboxVectorTileToJson } from '../legacy/console_editor/mapbox_vector_tile'; import { @@ -33,10 +34,12 @@ export const MonacoEditorOutput: FunctionComponent = () => { const [mode, setMode] = useState('text'); const divRef = useRef<HTMLDivElement | null>(null); const { setupResizeChecker, destroyResizeChecker } = useResizeCheckerUtils(); + const lineDecorations = useRef<monaco.editor.IEditorDecorationsCollection | null>(null); const editorDidMountCallback = useCallback( (editor: monaco.editor.IStandaloneCodeEditor) => { setupResizeChecker(divRef.current!, editor); + lineDecorations.current = editor.createDecorationsCollection(); }, [setupResizeChecker] ); @@ -46,6 +49,8 @@ export const MonacoEditorOutput: FunctionComponent = () => { }, [destroyResizeChecker]); useEffect(() => { + // Clean up any existing line decorations + lineDecorations.current?.clear(); if (data) { const isMultipleRequest = data.length > 1; setMode( @@ -73,6 +78,11 @@ export const MonacoEditorOutput: FunctionComponent = () => { }) .join('\n') ); + if (isMultipleRequest) { + // If there are multiple responses, add decorations for their status codes + const decorations = getStatusCodeDecorations(data); + lineDecorations.current?.set(decorations); + } } else { setValue(''); } diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.test.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.test.ts index c5a423e38902d..8822902627944 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.test.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.test.ts @@ -8,6 +8,8 @@ /* * Mock the function "populateContext" that accesses the autocomplete definitions */ +import { monaco } from '@kbn/monaco'; + const mockPopulateContext = jest.fn(); jest.mock('../../../../../lib/autocomplete/engine', () => { @@ -20,6 +22,7 @@ jest.mock('../../../../../lib/autocomplete/engine', () => { import { AutoCompleteContext } from '../../../../../lib/autocomplete/types'; import { getDocumentationLinkFromAutocomplete, + getUrlPathCompletionItems, shouldTriggerSuggestions, } from './autocomplete_utils'; @@ -136,4 +139,62 @@ describe('autocomplete_utils', () => { expect(actual).toBe(false); }); }); + + describe('getUrlPathCompletionItems', () => { + beforeEach(() => { + // mock autocomplete set with endpoints and index names + const mockAutocompleteSet = [ + { + name: '_cat', + }, + { + name: '_search', + }, + { + name: 'index1', + meta: 'index', + }, + { + name: 'index2', + meta: 'index', + }, + ] as AutoCompleteContext['autoCompleteSet']; + // mock the populateContext function that finds the correct autocomplete endpoint object and puts it into the context object + mockPopulateContext.mockImplementation((...args) => { + const context = args[0][1]; + context.autoCompleteSet = mockAutocompleteSet; + }); + }); + it('only suggests index items if there is a comma at the end of the line', () => { + const mockModel = { + getValueInRange: () => 'GET .kibana,', + getWordUntilPosition: () => ({ startColumn: 13 }), + } as unknown as monaco.editor.ITextModel; + const mockPosition = { lineNumber: 1, column: 13 } as unknown as monaco.Position; + const items = getUrlPathCompletionItems(mockModel, mockPosition); + expect(items.length).toBe(2); + expect(items.every((item) => item.detail === 'index')).toBe(true); + }); + + it('only suggests index items if there is a comma in the last url path token', () => { + const mockModel = { + getValueInRange: () => 'GET .kibana,index', + getWordUntilPosition: () => ({ startColumn: 13 }), + } as unknown as monaco.editor.ITextModel; + const mockPosition = { lineNumber: 1, column: 18 } as unknown as monaco.Position; + const items = getUrlPathCompletionItems(mockModel, mockPosition); + expect(items.length).toBe(2); + expect(items.every((item) => item.detail === 'index')).toBe(true); + }); + + it('suggest endpoints and index names if no comma', () => { + const mockModel = { + getValueInRange: () => 'GET _search', + getWordUntilPosition: () => ({ startColumn: 12 }), + } as unknown as monaco.editor.ITextModel; + const mockPosition = { lineNumber: 1, column: 12 } as unknown as monaco.Position; + const items = getUrlPathCompletionItems(mockModel, mockPosition); + expect(items.length).toBe(4); + }); + }); }); diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts index 3f23c4cce2418..bbfa33d84e70e 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/autocomplete_utils.ts @@ -121,43 +121,50 @@ export const getUrlPathCompletionItems = ( endColumn: column, }); + // flag to only suggest index names + let onlyIndexNames = false; // get the method and previous url parts for context const { method, urlPathTokens } = parseLine(lineContent); // if the line ends with /, then we use all url path tokens for autocomplete suggestions - // otherwise, we want to ignore the last token + // otherwise, we don't use the last token for populating the autocomplete context if (!lineContent.trim().endsWith('/')) { - urlPathTokens.pop(); + const lastToken = urlPathTokens.pop(); + // if the last token contains a comma, only suggest index names + if (lastToken?.includes(',')) { + onlyIndexNames = true; + } + } + let { autoCompleteSet } = populateContextForMethodAndUrl(method, urlPathTokens); + autoCompleteSet = autoCompleteSet ?? []; + // filter out non index names items if needed + if (onlyIndexNames) { + autoCompleteSet = autoCompleteSet.filter((term) => term.meta === 'index'); } - const { autoCompleteSet } = populateContextForMethodAndUrl(method, urlPathTokens); - const wordUntilPosition = model.getWordUntilPosition(position); const range = { - startLineNumber: position.lineNumber, + startLineNumber: lineNumber, // replace the whole word with the suggestion startColumn: lineContent.endsWith('.') ? // if there is a dot at the end of the content, it's ignored in the wordUntilPosition wordUntilPosition.startColumn - 1 : wordUntilPosition.startColumn, - endLineNumber: position.lineNumber, - endColumn: position.column, + endLineNumber: lineNumber, + endColumn: column, }; - if (autoCompleteSet && autoCompleteSet.length > 0) { - return ( - filterTermsWithoutName(autoCompleteSet) - // map autocomplete items to completion items - .map((item) => { - return { - label: item.name + '', - insertText: item.name + '', - detail: item.meta ?? i18nTexts.endpoint, - // the kind is only used to configure the icon - kind: monaco.languages.CompletionItemKind.Constant, - range, - }; - }) - ); - } - return []; + return ( + filterTermsWithoutName(autoCompleteSet) + // map autocomplete items to completion items + .map((item) => { + return { + label: item.name + '', + insertText: item.name + '', + detail: item.meta ?? i18nTexts.endpoint, + // the kind is only used to configure the icon + kind: monaco.languages.CompletionItemKind.Constant, + range, + }; + }) + ); }; /* @@ -336,7 +343,16 @@ const getInsertText = ( } let insertText = ''; if (typeof name === 'string') { - insertText = bodyContent.endsWith('"') ? '' : '"'; + const bodyContentLines = bodyContent.split('\n'); + const currentContentLine = bodyContentLines[bodyContentLines.length - 1]; + const incompleteFieldRegex = /.*"[^"]*$/; + if (incompleteFieldRegex.test(currentContentLine)) { + // The cursor is after an unmatched quote (e.g. '..."abc', '..."') + insertText = ''; + } else { + // The cursor is at the beginning of a field so the insert text should start with a quote + insertText = '"'; + } if (insertValue && insertValue !== '{' && insertValue !== '[') { insertText += `${insertValue}"`; } else { diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts index 5d6088653f243..0f4664be48994 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/constants.ts @@ -13,6 +13,15 @@ import { i18n } from '@kbn/i18n'; */ export const SELECTED_REQUESTS_CLASSNAME = 'console__monaco_editor__selectedRequests'; +/* + * CSS class names used for the styling of multiple-response status codes + */ +export const PRIMARY_STATUS_BADGE_CLASSNAME = 'monaco__status_badge--primary'; +export const SUCCESS_STATUS_BADGE_CLASSNAME = 'monaco__status_badge--success'; +export const DEFAULT_STATUS_BADGE_CLASSNAME = 'monaco__status_badge--default'; +export const WARNING_STATUS_BADGE_CLASSNAME = 'monaco__status_badge--warning'; +export const DANGER_STATUS_BADGE_CLASSNAME = 'monaco__status_badge--danger'; + export const whitespacesRegex = /\s+/; export const newLineRegex = /\n/; export const slashesRegex = /\/+/; diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts index 069f99552222a..0997aa682b630 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/index.ts @@ -6,7 +6,15 @@ * Side Public License, v 1. */ -export { AutocompleteType, SELECTED_REQUESTS_CLASSNAME } from './constants'; +export { + AutocompleteType, + SELECTED_REQUESTS_CLASSNAME, + SUCCESS_STATUS_BADGE_CLASSNAME, + WARNING_STATUS_BADGE_CLASSNAME, + PRIMARY_STATUS_BADGE_CLASSNAME, + DEFAULT_STATUS_BADGE_CLASSNAME, + DANGER_STATUS_BADGE_CLASSNAME, +} from './constants'; export { getRequestStartLineNumber, getRequestEndLineNumber, @@ -25,3 +33,4 @@ export { shouldTriggerSuggestions, } from './autocomplete_utils'; export { getLineTokens, containsUrlParams } from './tokens_utils'; +export { getStatusCodeDecorations } from './status_code_decoration_utils'; diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/status_code_decoration_utils.test.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/status_code_decoration_utils.test.ts new file mode 100644 index 0000000000000..c65f55c2fed35 --- /dev/null +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/status_code_decoration_utils.test.ts @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getStatusCodeDecorations } from './status_code_decoration_utils'; +import { + SUCCESS_STATUS_BADGE_CLASSNAME, + WARNING_STATUS_BADGE_CLASSNAME, + DANGER_STATUS_BADGE_CLASSNAME, +} from './constants'; +import { RequestResult } from '../../../../hooks/use_send_current_request/send_request'; + +describe('getStatusCodeDecorations', () => { + it('correctly returns all decorations on full data', () => { + // Sample multiple-response data returned from ES: + // 1 # GET _search 200 OK + // 2 { + // 3 "took": 1, + // 4 "timed_out": false, + // 5 "hits": { + // 6 "total": { + // 7 "value": 0, + // 8 "relation": "eq" + // 9 } + // 10 } + // 11 } + // 12 # GET _test 400 Bad Request + // 13 { + // 14 "error": { + // 15 "root_cause": [], + // 16 "status": 400 + // 17 } + // 18 # PUT /library/_bulk 500 Internal Server Error + // 19 { + // 20 "error": { + // 21 "root_cause": [], + // 22 "status": 500 + // 23 } + const SAMPLE_COMPLETE_DATA: RequestResult[] = [ + { + response: { + timeMs: 50, + statusCode: 200, + statusText: 'OK', + contentType: 'application/json', + value: + '# GET _search 200 OK\n{\n"took": 1,\n"timed_out": false,\n"hits": {\n"total": {\n"value": 0,\n"relation": "eq"\n}\n}\n}', + }, + request: { + data: '', + method: 'GET', + path: '_search', + }, + }, + { + response: { + timeMs: 22, + statusCode: 400, + statusText: 'Bad Request', + contentType: 'application/json', + value: '# GET _test 400 Bad Request\n{\n"error": {\n"root_cause": [],\n"status": 400\n}', + }, + request: { + data: '', + method: 'GET', + path: '_test', + }, + }, + { + response: { + timeMs: 23, + statusCode: 500, + statusText: 'Internal Server Error', + contentType: 'application/json', + value: + '# PUT /library/_bulk 500 Internal Server Error\n{\n"error": {\n"root_cause": [],\n"status": 500\n}', + }, + request: { + data: '', + method: 'PUT', + path: '/library/_bulk?refresh', + }, + }, + ]; + + const EXPECTED_DECORATIONS = [ + { + range: { + endColumn: 21, + endLineNumber: 1, + startColumn: 15, + startLineNumber: 1, + }, + options: { + inlineClassName: SUCCESS_STATUS_BADGE_CLASSNAME, + }, + }, + { + range: { + endColumn: 28, + endLineNumber: 12, + startColumn: 13, + startLineNumber: 12, + }, + options: { + inlineClassName: WARNING_STATUS_BADGE_CLASSNAME, + }, + }, + { + range: { + endColumn: 47, + endLineNumber: 18, + startColumn: 22, + startLineNumber: 18, + }, + options: { + inlineClassName: DANGER_STATUS_BADGE_CLASSNAME, + }, + }, + ]; + + expect(getStatusCodeDecorations(SAMPLE_COMPLETE_DATA)).toEqual(EXPECTED_DECORATIONS); + }); + + it('only returns decorations for data with complete status code and text', () => { + // This sample data is same as in previous test but some of it has incomplete status code or status text + const SAMPLE_INCOMPLETE_DATA: RequestResult[] = [ + { + response: { + timeMs: 50, + // @ts-ignore + statusCode: undefined, + statusText: 'OK', + contentType: 'application/json', + value: + '# GET _search OK\n{\n"took": 1,\n"timed_out": false,\n"hits": {\n"total": {\n"value": 0,\n"relation": "eq"\n}\n}\n}', + }, + request: { + data: '', + method: 'GET', + path: '_search', + }, + }, + { + response: { + timeMs: 22, + statusCode: 400, + statusText: 'Bad Request', + contentType: 'application/json', + value: '# GET _test 400 Bad Request\n{\n"error": {\n"root_cause": [],\n"status": 400\n}', + }, + request: { + data: '', + method: 'GET', + path: '_test', + }, + }, + { + response: { + timeMs: 23, + // @ts-ignore + statusCode: undefined, + // @ts-ignore + statusText: undefined, + contentType: 'application/json', + value: '# PUT /library/_bulk\n{\n"error": {\n"root_cause": [],\n"status": 500\n}', + }, + request: { + data: '', + method: 'PUT', + path: '/library/_bulk?refresh', + }, + }, + ]; + + // Only the second response has complete status code and text + const EXPECTED_DECORATIONS = [ + { + range: { + endColumn: 28, + endLineNumber: 12, + startColumn: 13, + startLineNumber: 12, + }, + options: { + inlineClassName: WARNING_STATUS_BADGE_CLASSNAME, + }, + }, + ]; + + expect(getStatusCodeDecorations(SAMPLE_INCOMPLETE_DATA)).toEqual(EXPECTED_DECORATIONS); + }); +}); diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/status_code_decoration_utils.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/status_code_decoration_utils.ts new file mode 100644 index 0000000000000..3a0724c874ca5 --- /dev/null +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/status_code_decoration_utils.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 { monaco } from '@kbn/monaco'; +import { RequestResult } from '../../../../hooks/use_send_current_request/send_request'; +import { + DEFAULT_STATUS_BADGE_CLASSNAME, + SUCCESS_STATUS_BADGE_CLASSNAME, + PRIMARY_STATUS_BADGE_CLASSNAME, + WARNING_STATUS_BADGE_CLASSNAME, + DANGER_STATUS_BADGE_CLASSNAME, +} from './constants'; + +const getStatusCodeClassName = (statusCode: number) => { + if (statusCode <= 199) { + return DEFAULT_STATUS_BADGE_CLASSNAME; + } + if (statusCode <= 299) { + return SUCCESS_STATUS_BADGE_CLASSNAME; + } + if (statusCode <= 399) { + return PRIMARY_STATUS_BADGE_CLASSNAME; + } + if (statusCode <= 499) { + return WARNING_STATUS_BADGE_CLASSNAME; + } + return DANGER_STATUS_BADGE_CLASSNAME; +}; + +export const getStatusCodeDecorations = (data: RequestResult[]) => { + const decorations: monaco.editor.IModelDeltaDecoration[] = []; + let lastResponseEndLine = 0; + + data.forEach(({ response }) => { + if (response?.value) { + const totalStatus = + response.statusCode && response.statusText + ? response.statusCode + ' ' + response.statusText + : ''; + const startColumn = (response.value as string).indexOf(totalStatus) + 1; + if (totalStatus && startColumn !== 0) { + const range = { + startLineNumber: lastResponseEndLine + 1, + startColumn, + endLineNumber: lastResponseEndLine + 1, + endColumn: startColumn + totalStatus.length, + }; + decorations.push({ + range, + options: { + inlineClassName: getStatusCodeClassName(response.statusCode), + }, + }); + } + lastResponseEndLine += (response.value as string).split(/\\n|\n/).length; + } + }); + + return decorations; +}; diff --git a/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx b/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx index 3a60a3706fa5b..6ba264db4166e 100644 --- a/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx +++ b/src/plugins/console/public/application/containers/embeddable/console_wrapper.tsx @@ -117,7 +117,7 @@ interface ConsoleWrapperProps export const ConsoleWrapper = (props: ConsoleWrapperProps) => { const [dependencies, setDependencies] = useState<ConsoleDependencies | null>(null); - const { core, usageCollection, onKeyDown, isMonacoEnabled, isOpen } = props; + const { core, usageCollection, onKeyDown, isMonacoEnabled, isDevMode, isOpen } = props; useEffect(() => { if (dependencies === null && isOpen) { @@ -169,6 +169,7 @@ export const ConsoleWrapper = (props: ConsoleWrapperProps) => { }, config: { isMonacoEnabled, + isDevMode, }, }} > diff --git a/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx b/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx index c1ec9514d2bdc..098bb76646d95 100644 --- a/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx +++ b/src/plugins/console/public/application/containers/embeddable/embeddable_console.tsx @@ -70,6 +70,7 @@ export const EmbeddableConsole = ({ setDispatch, alternateView, isMonacoEnabled, + isDevMode, getConsoleHeight, setConsoleHeight, }: EmbeddableConsoleDependencies) => { @@ -198,6 +199,7 @@ export const EmbeddableConsole = ({ usageCollection={usageCollection} onKeyDown={onKeyDown} isMonacoEnabled={isMonacoEnabled} + isDevMode={isDevMode} /> ) : null} {showAlternateView ? ( diff --git a/src/plugins/console/public/application/contexts/services_context.mock.ts b/src/plugins/console/public/application/contexts/services_context.mock.ts index e17b4e529a198..174b408c2bf4e 100644 --- a/src/plugins/console/public/application/contexts/services_context.mock.ts +++ b/src/plugins/console/public/application/contexts/services_context.mock.ts @@ -48,6 +48,7 @@ export const serviceContextMock = { docLinks: docLinksServiceMock.createStartContract().links, config: { isMonacoEnabled: false, + isDevMode: false, }, }; }, diff --git a/src/plugins/console/public/application/contexts/services_context.tsx b/src/plugins/console/public/application/contexts/services_context.tsx index d8885b8ce0788..a0818d148b79d 100644 --- a/src/plugins/console/public/application/contexts/services_context.tsx +++ b/src/plugins/console/public/application/contexts/services_context.tsx @@ -32,6 +32,7 @@ export interface ContextValue extends ConsoleStartServices { docLinks: DocLinksStart['links']; config: { isMonacoEnabled: boolean; + isDevMode: boolean; }; } diff --git a/src/plugins/console/public/application/index.tsx b/src/plugins/console/public/application/index.tsx index 0b140d105aa53..16469f5bf4bbc 100644 --- a/src/plugins/console/public/application/index.tsx +++ b/src/plugins/console/public/application/index.tsx @@ -36,6 +36,7 @@ export interface BootDependencies extends ConsoleStartServices { docLinks: DocLinksStart['links']; autocompleteInfo: AutocompleteInfo; isMonacoEnabled: boolean; + isDevMode: boolean; } export async function renderApp({ @@ -47,6 +48,7 @@ export async function renderApp({ docLinks, autocompleteInfo, isMonacoEnabled, + isDevMode, ...startServices }: BootDependencies) { const trackUiMetric = createUsageTracker(usageCollection); @@ -86,6 +88,7 @@ export async function renderApp({ }, config: { isMonacoEnabled, + isDevMode, }, }} > diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts index 05b26da2c110a..2728629d288b8 100644 --- a/src/plugins/console/public/plugin.ts +++ b/src/plugins/console/public/plugin.ts @@ -99,6 +99,7 @@ export class ConsoleUIPlugin element, autocompleteInfo: this.autocompleteInfo, isMonacoEnabled, + isDevMode: this.ctx.env.mode.dev, }); }, }); @@ -125,6 +126,7 @@ export class ConsoleUIPlugin ui: { enabled: isConsoleUiEnabled, embeddedEnabled: isEmbeddedConsoleEnabled }, dev: { enableMonaco: isMonacoEnabled }, } = this.ctx.config.get<ClientConfigType>(); + const isDevMode = this.ctx.env.mode.dev; const consoleStart: ConsolePluginStart = {}; const embeddedConsoleUiSetting = core.uiSettings.get<boolean>( @@ -146,6 +148,7 @@ export class ConsoleUIPlugin }, alternateView: this._embeddableConsole.alternateView, isMonacoEnabled, + isDevMode, getConsoleHeight: this._embeddableConsole.getConsoleHeight.bind(this._embeddableConsole), setConsoleHeight: this._embeddableConsole.setConsoleHeight.bind(this._embeddableConsole), }); @@ -154,6 +157,8 @@ export class ConsoleUIPlugin this._embeddableConsole.isEmbeddedConsoleAvailable(); consoleStart.openEmbeddedConsole = (content?: string) => this._embeddableConsole.openEmbeddedConsole(content); + consoleStart.openEmbeddedConsoleAlternateView = () => + this._embeddableConsole.openEmbeddedConsoleAlternateView(); consoleStart.registerEmbeddedConsoleAlternateView = (view: EmbeddedConsoleView | null) => { this._embeddableConsole.registerAlternateView(view); }; diff --git a/src/plugins/console/public/services/embeddable_console.test.ts b/src/plugins/console/public/services/embeddable_console.test.ts index 92cc4d8450906..f35ed8a6fc0bd 100644 --- a/src/plugins/console/public/services/embeddable_console.test.ts +++ b/src/plugins/console/public/services/embeddable_console.test.ts @@ -54,6 +54,49 @@ describe('EmbeddableConsoleInfo', () => { payload: { content: 'GET /_cat/_indices' }, }); }); + it('does nothing if dispatch not set', () => { + eConsole.setDispatch(null); + + eConsole.openEmbeddedConsole(); + + expect(mockDispatch).toHaveBeenCalledTimes(0); + }); + }); + describe('openEmbeddedConsoleAlternateView', () => { + const mockDispatch = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + + eConsole.setDispatch(mockDispatch); + }); + it('dispatches open when alt view does not exist', () => { + eConsole.openEmbeddedConsoleAlternateView(); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'open', + payload: { alternateView: false }, + }); + }); + it('dispatches open alt view when alt view exists', () => { + eConsole.registerAlternateView({ + ActivationButton: jest.fn(), + ViewContent: jest.fn(), + }); + + eConsole.openEmbeddedConsoleAlternateView(); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'open', + payload: { alternateView: true }, + }); + }); + it('does nothing if dispatch not set', () => { + eConsole.setDispatch(null); + + eConsole.openEmbeddedConsoleAlternateView(); + + expect(mockDispatch).toHaveBeenCalledTimes(0); + }); }); describe('getConsoleHeight', () => { it('returns value in storage when found', () => { diff --git a/src/plugins/console/public/services/embeddable_console.ts b/src/plugins/console/public/services/embeddable_console.ts index f5e0197ad833b..2daadfaec5eb4 100644 --- a/src/plugins/console/public/services/embeddable_console.ts +++ b/src/plugins/console/public/services/embeddable_console.ts @@ -47,6 +47,16 @@ export class EmbeddableConsoleInfo { this._dispatch({ type: 'open', payload: content ? { content } : undefined }); } + public openEmbeddedConsoleAlternateView() { + // Embedded Console is not rendered on the page, nothing to do + if (!this._dispatch) return; + + this._dispatch({ + type: 'open', + payload: { alternateView: this._alternateView !== undefined }, + }); + } + public registerAlternateView(view: EmbeddedConsoleView | null) { this._alternateView = view ?? undefined; } diff --git a/src/plugins/console/public/styles/_app.scss b/src/plugins/console/public/styles/_app.scss index 161fd913c32ae..fdeaf67963fa1 100644 --- a/src/plugins/console/public/styles/_app.scss +++ b/src/plugins/console/public/styles/_app.scss @@ -133,10 +133,52 @@ .console__monaco_editor__selectedRequests { background: transparentize($euiColorPrimary, .9); } + /* - * The z-index for the autocomplete suggestions popup + * The styling for the multiple-response status code decorations */ +%monaco__status_badge { + font-family: $euiFontFamily; + font-size: $euiFontSizeS; + font-weight: $euiFontWeightMedium; + line-height: $euiLineHeight; + padding: calc($euiSizeXS / 2) $euiSizeXS; + display: inline-block; + border-radius: calc($euiBorderRadius / 2); + white-space: nowrap; + vertical-align: top; + cursor: default; + max-width: 100%; +} + +.monaco__status_badge--primary { + @extend %monaco__status_badge; + background-color: $euiColorVis1; +} + +.monaco__status_badge--success { + @extend %monaco__status_badge; + background-color: $euiColorSuccess; +} + +.monaco__status_badge--default { + @extend %monaco__status_badge; + background-color: $euiColorLightShade; +} + +.monaco__status_badge--warning { + @extend %monaco__status_badge; + background-color: $euiColorWarning; +} + +.monaco__status_badge--danger { + @extend %monaco__status_badge; + background-color: $euiColorDanger; +} +/* + * The z-index for the autocomplete suggestions popup + */ .kibanaCodeEditor .monaco-editor .suggest-widget { // the value needs to be above the z-index of the resizer bar z-index: $euiZLevel1 + 2; diff --git a/src/plugins/console/public/types/embeddable_console.ts b/src/plugins/console/public/types/embeddable_console.ts index 0c4f6844edb6e..5a3021b9a8be8 100644 --- a/src/plugins/console/public/types/embeddable_console.ts +++ b/src/plugins/console/public/types/embeddable_console.ts @@ -16,6 +16,7 @@ export interface EmbeddableConsoleDependencies { setDispatch: (dispatch: Dispatch<EmbeddedConsoleAction> | null) => void; alternateView?: EmbeddedConsoleView; isMonacoEnabled: boolean; + isDevMode: boolean; getConsoleHeight: () => string | undefined; setConsoleHeight: (value: string) => void; } diff --git a/src/plugins/console/public/types/plugin_dependencies.ts b/src/plugins/console/public/types/plugin_dependencies.ts index 39b82939619ec..b6c91de724caf 100644 --- a/src/plugins/console/public/types/plugin_dependencies.ts +++ b/src/plugins/console/public/types/plugin_dependencies.ts @@ -59,6 +59,12 @@ export interface ConsolePluginStart { * this function will open the embedded console on the page if it is currently rendered. */ openEmbeddedConsole?: (content?: string) => void; + /** + * openEmbeddedConsoleAlternateView is available if the embedded console can be rendered. + * Calling this function will open the embedded console to the alternative view. If there is no alternative view registered + * this will open the embedded console. + */ + openEmbeddedConsoleAlternateView?: () => void; /** * EmbeddableConsole is a functional component used to render a portable version of the dev tools console on any page in Kibana */ diff --git a/src/plugins/controls/common/options_list/types.ts b/src/plugins/controls/common/options_list/types.ts index 1242e866ca089..4192cc6aac26d 100644 --- a/src/plugins/controls/common/options_list/types.ts +++ b/src/plugins/controls/common/options_list/types.ts @@ -7,7 +7,7 @@ */ import { DataView, FieldSpec, RuntimeFieldSpec } from '@kbn/data-views-plugin/common'; -import type { BoolQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import type { AggregateQuery, BoolQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import type { DataControlInput } from '../types'; import { OptionsListSelection } from './options_list_selections'; @@ -68,7 +68,7 @@ export type OptionsListRequest = Omit< dataView: DataView; filters?: Filter[]; field: FieldSpec; - query?: Query; + query?: Query | AggregateQuery; }; /** diff --git a/src/plugins/controls/public/control_group/actions/edit_control_action.test.tsx b/src/plugins/controls/public/control_group/actions/edit_control_action.test.tsx index a496e8671f6d6..22313f8600b0a 100644 --- a/src/plugins/controls/public/control_group/actions/edit_control_action.test.tsx +++ b/src/plugins/controls/public/control_group/actions/edit_control_action.test.tsx @@ -12,7 +12,7 @@ import { OPTIONS_LIST_CONTROL } from '../../../common'; import { ControlOutput } from '../../types'; import { ControlGroupInput } from '../types'; import { pluginServices } from '../../services'; -import { EditControlAction } from './edit_control_action'; +import { EditLegacyEmbeddableControlAction } from './edit_control_action'; import { DeleteControlAction } from './delete_control_action'; import { TimeSliderEmbeddableFactory } from '../../time_slider'; import { OptionsListEmbeddableFactory, OptionsListEmbeddableInput } from '../../options_list'; @@ -24,7 +24,7 @@ const controlGroupInput = { chainingSystem: 'NONE', panels: {} } as ControlGroup const deleteControlAction = new DeleteControlAction(); test('Action is incompatible with Error Embeddables', async () => { - const editControlAction = new EditControlAction(deleteControlAction); + const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction); const errorEmbeddable = new ErrorEmbeddable('Wow what an awful error', { id: ' 404' }); expect(await editControlAction.isCompatible({ embeddable: errorEmbeddable as any })).toBe(false); }); @@ -35,7 +35,7 @@ test('Action is incompatible with embeddables that are not editable', async () = pluginServices.getServices().controls.getControlFactory = mockGetFactory; pluginServices.getServices().embeddable.getEmbeddableFactory = mockGetFactory; - const editControlAction = new EditControlAction(deleteControlAction); + const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction); const emptyContainer = new ControlGroupContainer(mockedReduxEmbeddablePackage, controlGroupInput); await emptyContainer.untilInitialized(); await emptyContainer.addTimeSliderControl(); @@ -53,7 +53,7 @@ test('Action is compatible with embeddables that are editable', async () => { pluginServices.getServices().controls.getControlFactory = mockGetFactory; pluginServices.getServices().embeddable.getEmbeddableFactory = mockGetFactory; - const editControlAction = new EditControlAction(deleteControlAction); + const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction); const emptyContainer = new ControlGroupContainer(mockedReduxEmbeddablePackage, controlGroupInput); await emptyContainer.untilInitialized(); const control = await emptyContainer.addOptionsListControl({ @@ -73,7 +73,7 @@ test('Action is compatible with embeddables that are editable', async () => { }); test('Execute throws an error when called with an embeddable not in a parent', async () => { - const editControlAction = new EditControlAction(deleteControlAction); + const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction); const optionsListEmbeddable = new OptionsListEmbeddable( mockedReduxEmbeddablePackage, {} as OptionsListEmbeddableInput, @@ -99,7 +99,7 @@ test('Execute should open a flyout', async () => { })) as OptionsListEmbeddable; expect(emptyContainer.getInput().panels[control.getInput().id].type).toBe(OPTIONS_LIST_CONTROL); - const editControlAction = new EditControlAction(deleteControlAction); + const editControlAction = new EditLegacyEmbeddableControlAction(deleteControlAction); await editControlAction.execute({ embeddable: control }); expect(spyOn).toHaveBeenCalled(); }); diff --git a/src/plugins/controls/public/control_group/actions/edit_control_action.tsx b/src/plugins/controls/public/control_group/actions/edit_control_action.tsx index 8f457925eb99c..0227f53a7ec97 100644 --- a/src/plugins/controls/public/control_group/actions/edit_control_action.tsx +++ b/src/plugins/controls/public/control_group/actions/edit_control_action.tsx @@ -26,7 +26,7 @@ export interface EditControlActionContext { embeddable: ControlEmbeddable<DataControlInput>; } -export class EditControlAction implements Action<EditControlActionContext> { +export class EditLegacyEmbeddableControlAction implements Action<EditControlActionContext> { public readonly type = ACTION_EDIT_CONTROL; public readonly id = ACTION_EDIT_CONTROL; public order = 2; diff --git a/src/plugins/controls/public/control_group/actions/index.ts b/src/plugins/controls/public/control_group/actions/index.ts index 91a3f2a431572..046ddbd2597c1 100644 --- a/src/plugins/controls/public/control_group/actions/index.ts +++ b/src/plugins/controls/public/control_group/actions/index.ts @@ -6,6 +6,6 @@ * Side Public License, v 1. */ -export const ACTION_EDIT_CONTROL = 'editControl'; +export const ACTION_EDIT_CONTROL = 'editLegacyEmbeddableControl'; export const ACTION_CLEAR_CONTROL = 'clearControl'; export const ACTION_DELETE_CONTROL = 'deleteControl'; diff --git a/src/plugins/controls/public/index.ts b/src/plugins/controls/public/index.ts index a9d7e950ee05b..310ded6083cfe 100644 --- a/src/plugins/controls/public/index.ts +++ b/src/plugins/controls/public/index.ts @@ -8,6 +8,22 @@ import { ControlsPlugin } from './plugin'; +export type { + ControlGroupApi, + ControlGroupRuntimeState, + ControlGroupSerializedState, +} from './react_controls/control_group/types'; +export type { + DataControlApi, + DefaultDataControlState, + DataControlFactory, + DataControlServices, +} from './react_controls/controls/data_controls/types'; + +/** + * TODO: remove all exports below this when control group embeddable is removed + */ + export type { ControlOutput, ControlFactory, diff --git a/src/plugins/controls/public/plugin.ts b/src/plugins/controls/public/plugin.ts index 6874926d125c2..6d2690c70935f 100644 --- a/src/plugins/controls/public/plugin.ts +++ b/src/plugins/controls/public/plugin.ts @@ -28,6 +28,11 @@ import { IEditableControlFactory, ControlInput, } from './types'; +import { registerControlGroupEmbeddable } from './react_controls/control_group/register_control_group_embeddable'; +import { registerOptionsListControl } from './react_controls/controls/data_controls/options_list_control/register_options_list_control'; +import { registerRangeSliderControl } from './react_controls/controls/data_controls/range_slider/register_range_slider_control'; +import { registerTimeSliderControl } from './react_controls/controls/timeslider_control/register_timeslider_control'; +import { EditControlAction } from './react_controls/actions/edit_control_action/edit_control_action'; export class ControlsPlugin implements Plugin< @@ -63,6 +68,11 @@ export class ControlsPlugin const { registerControlType } = controlsService; const { embeddable } = _setupPlugins; + registerControlGroupEmbeddable(_coreSetup, embeddable); + registerOptionsListControl(_coreSetup); + registerRangeSliderControl(_coreSetup); + registerTimeSliderControl(_coreSetup); + // register control group embeddable factory _coreSetup.getStartServices().then(([, deps]) => { embeddable.registerEmbeddableFactory( @@ -120,11 +130,22 @@ export class ControlsPlugin uiActions.registerAction(deleteControlAction); uiActions.attachAction(PANEL_HOVER_TRIGGER, deleteControlAction.id); - const { EditControlAction } = await import('./control_group/actions/edit_control_action'); - const editControlAction = new EditControlAction(deleteControlAction); + const editControlAction = new EditControlAction(); uiActions.registerAction(editControlAction); uiActions.attachAction(PANEL_HOVER_TRIGGER, editControlAction.id); + /** + * TODO: Remove edit legacy control embeddable action when embeddable controls are removed + */ + const { EditLegacyEmbeddableControlAction } = await import( + './control_group/actions/edit_control_action' + ); + const editLegacyEmbeddableControlAction = new EditLegacyEmbeddableControlAction( + deleteControlAction + ); + uiActions.registerAction(editLegacyEmbeddableControlAction); + uiActions.attachAction(PANEL_HOVER_TRIGGER, editLegacyEmbeddableControlAction.id); + const { ClearControlAction } = await import('./control_group/actions/clear_control_action'); const clearControlAction = new ClearControlAction(); uiActions.registerAction(clearControlAction); diff --git a/src/plugins/controls/public/react_controls/actions/edit_control_action/compatibility_check.ts b/src/plugins/controls/public/react_controls/actions/edit_control_action/compatibility_check.ts new file mode 100644 index 0000000000000..b67228209044b --- /dev/null +++ b/src/plugins/controls/public/react_controls/actions/edit_control_action/compatibility_check.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 { apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { + apiCanAccessViewMode, + apiHasParentApi, + apiHasType, + apiHasUniqueId, + apiIsOfType, + getInheritedViewMode, + hasEditCapabilities, +} from '@kbn/presentation-publishing'; +import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { CONTROL_GROUP_TYPE } from '../../../../common'; +import { DataControlApi } from '../../controls/data_controls/types'; + +export const compatibilityCheck = (api: unknown): api is DataControlApi => { + return Boolean( + apiHasType(api) && + apiHasUniqueId(api) && + hasEditCapabilities(api) && + apiHasParentApi(api) && + apiCanAccessViewMode(api.parentApi) && + apiIsOfType(api.parentApi, CONTROL_GROUP_TYPE) && + apiIsPresentationContainer(api.parentApi) + ); +}; + +export function isCompatible(api: unknown) { + return ( + compatibilityCheck(api) && + getInheritedViewMode(api.parentApi) === ViewMode.EDIT && + api.isEditingEnabled() + ); +} diff --git a/src/plugins/controls/public/react_controls/actions/edit_control_action/edit_control_action.tsx b/src/plugins/controls/public/react_controls/actions/edit_control_action/edit_control_action.tsx new file mode 100644 index 0000000000000..4ba4ce2b16cc8 --- /dev/null +++ b/src/plugins/controls/public/react_controls/actions/edit_control_action/edit_control_action.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 { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { EmbeddableApiContext, HasUniqueId } from '@kbn/presentation-publishing'; +import { type Action, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; + +const ACTION_EDIT_CONTROL = 'editDataControl'; + +export class EditControlAction implements Action<EmbeddableApiContext> { + public readonly type = ACTION_EDIT_CONTROL; + public readonly id = ACTION_EDIT_CONTROL; + public order = 2; + + constructor() {} + + public readonly MenuItem = ({ context }: { context: EmbeddableApiContext }) => { + return ( + <EuiToolTip content={this.getDisplayName(context)}> + <EuiButtonIcon + data-test-subj={`control-action-${(context.embeddable as HasUniqueId).uuid}-edit`} + aria-label={this.getDisplayName(context)} + iconType={this.getIconType(context)} + onClick={() => this.execute(context)} + color="text" + /> + </EuiToolTip> + ); + }; + + public getDisplayName({ embeddable }: EmbeddableApiContext) { + return i18n.translate('controls.controlGroup.floatingActions.editTitle', { + defaultMessage: 'Edit', + }); + } + + public getIconType({ embeddable }: EmbeddableApiContext) { + return 'pencil'; + } + + public async isCompatible({ embeddable }: EmbeddableApiContext) { + const { isCompatible } = await import('./compatibility_check'); + return isCompatible(embeddable); + } + + public async execute({ embeddable }: EmbeddableApiContext) { + const { compatibilityCheck } = await import('./compatibility_check'); + if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError(); + await embeddable.onEdit(); + } +} diff --git a/examples/controls_example/public/react_controls/control_factory_registry.ts b/src/plugins/controls/public/react_controls/control_factory_registry.ts similarity index 85% rename from examples/controls_example/public/react_controls/control_factory_registry.ts rename to src/plugins/controls/public/react_controls/control_factory_registry.ts index b4dcde2d2253d..594dad179176c 100644 --- a/examples/controls_example/public/react_controls/control_factory_registry.ts +++ b/src/plugins/controls/public/react_controls/control_factory_registry.ts @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ControlFactory, DefaultControlApi } from './types'; +import { ControlFactory, DefaultControlApi } from './controls/types'; const registry: { [key: string]: ControlFactory<any, any> } = {}; @@ -20,7 +20,7 @@ export const registerControlFactory = async < ) => { if (registry[type] !== undefined) throw new Error( - i18n.translate('controlFactoryRegistry.factoryAlreadyExistsError', { + i18n.translate('controls.controlFactoryRegistry.factoryAlreadyExistsError', { defaultMessage: 'A control factory for type: {key} is already registered.', values: { key: type }, }) @@ -36,7 +36,7 @@ export const getControlFactory = < ): ControlFactory<State, ApiType> => { if (registry[key] === undefined) throw new Error( - i18n.translate('controlFactoryRegistry.factoryNotFoundError', { + i18n.translate('controls.controlFactoryRegistry.factoryNotFoundError', { defaultMessage: 'No control factory found for type: {key}', values: { key }, }) diff --git a/examples/controls_example/public/react_controls/components/control_clone.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_clone.tsx similarity index 95% rename from examples/controls_example/public/react_controls/components/control_clone.tsx rename to src/plugins/controls/public/react_controls/control_group/components/control_clone.tsx index b44b5a33310a9..cc4ed2ea97687 100644 --- a/examples/controls_example/public/react_controls/components/control_clone.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_clone.tsx @@ -11,10 +11,10 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiFormLabel, EuiIcon } from '@elastic/eui'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; -import { DEFAULT_CONTROL_GROW } from '@kbn/controls-plugin/common'; - import { BehaviorSubject } from 'rxjs'; -import { DefaultControlApi } from '../types'; +import { DEFAULT_CONTROL_GROW } from '../../../../common'; + +import { DefaultControlApi } from '../../controls/types'; /** * A simplified clone version of the control which is dragged. This version only shows diff --git a/examples/controls_example/public/react_controls/components/control_error.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_error.tsx similarity index 100% rename from examples/controls_example/public/react_controls/components/control_error.tsx rename to src/plugins/controls/public/react_controls/control_group/components/control_error.tsx diff --git a/examples/controls_example/public/react_controls/control_group/components/control_group.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_group.tsx similarity index 96% rename from examples/controls_example/public/react_controls/control_group/components/control_group.tsx rename to src/plugins/controls/public/react_controls/control_group/components/control_group.tsx index 9608d8b082f40..72c891444081a 100644 --- a/examples/controls_example/public/react_controls/control_group/components/control_group.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_group.tsx @@ -32,13 +32,13 @@ import { EuiPanel, EuiToolTip, } from '@elastic/eui'; -import { ControlStyle } from '@kbn/controls-plugin/public'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import { ControlStyle } from '../../..'; import { ControlsInOrder } from '../init_controls_manager'; import { ControlGroupApi } from '../types'; -import { ControlRenderer } from '../../control_renderer'; -import { ControlClone } from '../../components/control_clone'; -import { DefaultControlApi } from '../../types'; +import { ControlRenderer } from './control_renderer'; +import { ControlClone } from './control_clone'; +import { DefaultControlApi } from '../../controls/types'; import { ControlGroupStrings } from '../control_group_strings'; interface Props { diff --git a/examples/controls_example/public/react_controls/components/control_panel.scss b/src/plugins/controls/public/react_controls/control_group/components/control_panel.scss similarity index 100% rename from examples/controls_example/public/react_controls/components/control_panel.scss rename to src/plugins/controls/public/react_controls/control_group/components/control_panel.scss diff --git a/examples/controls_example/public/react_controls/components/control_panel.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_panel.tsx similarity index 94% rename from examples/controls_example/public/react_controls/components/control_panel.tsx rename to src/plugins/controls/public/react_controls/control_group/components/control_panel.tsx index 7127f158511be..b83b0b464b5c5 100644 --- a/examples/controls_example/public/react_controls/components/control_panel.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_panel.tsx @@ -27,8 +27,9 @@ import { useBatchedOptionalPublishingSubjects, } from '@kbn/presentation-publishing'; import { FloatingActions } from '@kbn/presentation-util-plugin/public'; +import { DEFAULT_CONTROL_WIDTH } from '../../../../common'; -import { ControlPanelProps, DefaultControlApi } from '../types'; +import { ControlPanelProps, DefaultControlApi } from '../../controls/types'; import { ControlError } from './control_error'; import './control_panel.scss'; @@ -119,6 +120,7 @@ export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlA const viewMode = (rawViewMode ?? ViewMode.VIEW) as ViewMode; const isEditable = viewMode === ViewMode.EDIT; + const controlWidth = width ?? DEFAULT_CONTROL_WIDTH; return ( <EuiFlexItem @@ -130,9 +132,9 @@ export const ControlPanel = <ApiType extends DefaultControlApi = DefaultControlA data-render-complete="true" className={classNames('controlFrameWrapper', { 'controlFrameWrapper--grow': grow, - 'controlFrameWrapper--small': width === 'small', - 'controlFrameWrapper--medium': width === 'medium', - 'controlFrameWrapper--large': width === 'large', + 'controlFrameWrapper--small': controlWidth === 'small', + 'controlFrameWrapper--medium': controlWidth === 'medium', + 'controlFrameWrapper--large': controlWidth === 'large', 'controlFrameWrapper-isDragging': isDragging, 'controlFrameWrapper--insertBefore': isOver && (index ?? -1) < (activeIndex ?? -1), 'controlFrameWrapper--insertAfter': isOver && (index ?? -1) > (activeIndex ?? -1), diff --git a/examples/controls_example/public/react_controls/control_renderer.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_renderer.tsx similarity index 75% rename from examples/controls_example/public/react_controls/control_renderer.tsx rename to src/plugins/controls/public/react_controls/control_group/components/control_renderer.tsx index cdaf53276760b..a10c274c4091f 100644 --- a/examples/controls_example/public/react_controls/control_renderer.tsx +++ b/src/plugins/controls/public/react_controls/control_group/components/control_renderer.tsx @@ -6,15 +6,20 @@ * Side Public License, v 1. */ -import React, { useEffect, useImperativeHandle, useState } from 'react'; +import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'; import { BehaviorSubject } from 'rxjs'; +import { initializeUnsavedChanges } from '@kbn/presentation-containers'; import { StateComparators } from '@kbn/presentation-publishing'; -import { getControlFactory } from './control_factory_registry'; -import { ControlGroupApi } from './control_group/types'; -import { ControlPanel } from './components/control_panel'; -import { ControlApiRegistration, DefaultControlApi, DefaultControlState } from './types'; +import { getControlFactory } from '../../control_factory_registry'; +import { ControlGroupApi } from '../types'; +import { ControlPanel } from './control_panel'; +import { + ControlApiRegistration, + DefaultControlApi, + DefaultControlState, +} from '../../controls/types'; /** * Renders a component from the control registry into a Control Panel @@ -35,6 +40,8 @@ export const ControlRenderer = < onApiAvailable?: (api: ApiType) => void; isControlGroupInitialized: boolean; }) => { + const cleanupFunction = useRef<(() => void) | null>(null); + const [component, setComponent] = useState<undefined | React.FC<{ className: string }>>( undefined ); @@ -48,25 +55,29 @@ export const ControlRenderer = < const factory = getControlFactory<StateType, ApiType>(type); const buildApi = ( apiRegistration: ControlApiRegistration<ApiType>, - comparators: StateComparators<StateType> // TODO: Use these to calculate unsaved changes + comparators: StateComparators<StateType> ): ApiType => { + const unsavedChanges = initializeUnsavedChanges<StateType>( + parentApi.getLastSavedControlState(uuid) as StateType, + parentApi, + comparators + ); + + cleanupFunction.current = () => unsavedChanges.cleanup(); + return { ...apiRegistration, + ...unsavedChanges.api, uuid, parentApi, - unsavedChanges: new BehaviorSubject<Partial<StateType> | undefined>(undefined), - resetUnsavedChanges: () => {}, type: factory.type, } as unknown as ApiType; }; - const { rawState: initialState } = parentApi.getSerializedStateForChild(uuid) ?? {}; - - return await factory.buildControl( - initialState as unknown as StateType, - buildApi, - uuid, - parentApi - ); + + const { rawState: initialState } = parentApi.getSerializedStateForChild(uuid) ?? { + rawState: {}, + }; + return await factory.buildControl(initialState as StateType, buildApi, uuid, parentApi); } buildControl() @@ -118,6 +129,12 @@ export const ControlRenderer = < [type] ); + useEffect(() => { + return () => { + cleanupFunction.current?.(); + }; + }, []); + return component && isControlGroupInitialized ? ( // @ts-expect-error <ControlPanel<ApiType> Component={component} uuid={uuid} /> diff --git a/src/plugins/controls/public/react_controls/control_group/components/control_setting_tooltip_label.tsx b/src/plugins/controls/public/react_controls/control_group/components/control_setting_tooltip_label.tsx new file mode 100644 index 0000000000000..91b40e6a95e67 --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/components/control_setting_tooltip_label.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; +import { css } from '@emotion/react'; + +export const ControlSettingTooltipLabel = ({ + label, + tooltip, +}: { + label: string; + tooltip: string; +}) => ( + <EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}> + <EuiFlexItem grow={false}>{label}</EuiFlexItem> + <EuiFlexItem + grow={false} + css={css` + margin-top: 0px !important; + `} + > + <EuiIconTip content={tooltip} position="right" /> + </EuiFlexItem> + </EuiFlexGroup> +); diff --git a/examples/controls_example/public/react_controls/control_group/control_fetch/chaining.test.ts b/src/plugins/controls/public/react_controls/control_group/control_fetch/chaining.test.ts similarity index 99% rename from examples/controls_example/public/react_controls/control_group/control_fetch/chaining.test.ts rename to src/plugins/controls/public/react_controls/control_group/control_fetch/chaining.test.ts index 981cff523b406..d3e369d7c2b64 100644 --- a/examples/controls_example/public/react_controls/control_group/control_fetch/chaining.test.ts +++ b/src/plugins/controls/public/react_controls/control_group/control_fetch/chaining.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ControlGroupChainingSystem } from '@kbn/controls-plugin/common'; +import { ControlGroupChainingSystem } from '../../../../common'; import { Filter } from '@kbn/es-query'; import { BehaviorSubject, skip } from 'rxjs'; import { chaining$ } from './chaining'; diff --git a/examples/controls_example/public/react_controls/control_group/control_fetch/chaining.ts b/src/plugins/controls/public/react_controls/control_group/control_fetch/chaining.ts similarity index 97% rename from examples/controls_example/public/react_controls/control_group/control_fetch/chaining.ts rename to src/plugins/controls/public/react_controls/control_group/control_fetch/chaining.ts index 21ce2f0d55148..7cbe188ad5be2 100644 --- a/examples/controls_example/public/react_controls/control_group/control_fetch/chaining.ts +++ b/src/plugins/controls/public/react_controls/control_group/control_fetch/chaining.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { ControlGroupChainingSystem } from '@kbn/controls-plugin/common'; import { Filter, TimeRange } from '@kbn/es-query'; import { apiPublishesFilters, @@ -22,6 +21,7 @@ import { skipWhile, switchMap, } from 'rxjs'; +import { ControlGroupChainingSystem } from '../../../../common'; export interface ChainingContext { chainingFilters?: Filter[] | undefined; diff --git a/examples/controls_example/public/react_controls/control_group/control_fetch/control_fetch.ts b/src/plugins/controls/public/react_controls/control_group/control_fetch/control_fetch.ts similarity index 100% rename from examples/controls_example/public/react_controls/control_group/control_fetch/control_fetch.ts rename to src/plugins/controls/public/react_controls/control_group/control_fetch/control_fetch.ts diff --git a/examples/controls_example/public/react_controls/control_group/control_fetch/control_group_fetch.ts b/src/plugins/controls/public/react_controls/control_group/control_fetch/control_group_fetch.ts similarity index 97% rename from examples/controls_example/public/react_controls/control_group/control_fetch/control_group_fetch.ts rename to src/plugins/controls/public/react_controls/control_group/control_fetch/control_group_fetch.ts index fdca7576ae8a1..599f4610d1b33 100644 --- a/examples/controls_example/public/react_controls/control_group/control_fetch/control_group_fetch.ts +++ b/src/plugins/controls/public/react_controls/control_group/control_fetch/control_group_fetch.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { ParentIgnoreSettings } from '@kbn/controls-plugin/public'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { PublishesUnifiedSearch, PublishingSubject } from '@kbn/presentation-publishing'; import { apiPublishesReload } from '@kbn/presentation-publishing/interfaces/fetch/publishes_reload'; import { BehaviorSubject, debounceTime, map, merge, Observable, switchMap } from 'rxjs'; +import { ParentIgnoreSettings } from '../../..'; export interface ControlGroupFetchContext { unifiedSearchFilters?: Filter[] | undefined; diff --git a/examples/controls_example/public/react_controls/control_group/control_fetch/index.ts b/src/plugins/controls/public/react_controls/control_group/control_fetch/index.ts similarity index 100% rename from examples/controls_example/public/react_controls/control_group/control_fetch/index.ts rename to src/plugins/controls/public/react_controls/control_group/control_fetch/index.ts diff --git a/examples/controls_example/public/react_controls/control_group/control_group_editor.tsx b/src/plugins/controls/public/react_controls/control_group/control_group_editor.tsx similarity index 98% rename from examples/controls_example/public/react_controls/control_group/control_group_editor.tsx rename to src/plugins/controls/public/react_controls/control_group/control_group_editor.tsx index cb08aad929efd..9c8ca5b52b0ae 100644 --- a/examples/controls_example/public/react_controls/control_group/control_group_editor.tsx +++ b/src/plugins/controls/public/react_controls/control_group/control_group_editor.tsx @@ -26,10 +26,10 @@ import { EuiTitle, } from '@elastic/eui'; import { css } from '@emotion/react'; -import { ControlStyle, ParentIgnoreSettings } from '@kbn/controls-plugin/public'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import { ControlStyle, ParentIgnoreSettings } from '../..'; -import { ControlStateManager } from '../types'; +import { ControlStateManager } from '../controls/types'; import { ControlGroupStrings } from './control_group_strings'; import { ControlGroupApi, ControlGroupEditorState } from './types'; diff --git a/examples/controls_example/public/react_controls/control_group/control_group_strings.tsx b/src/plugins/controls/public/react_controls/control_group/control_group_strings.tsx similarity index 100% rename from examples/controls_example/public/react_controls/control_group/control_group_strings.tsx rename to src/plugins/controls/public/react_controls/control_group/control_group_strings.tsx diff --git a/src/plugins/controls/public/react_controls/control_group/control_group_unsaved_changes_api.ts b/src/plugins/controls/public/react_controls/control_group/control_group_unsaved_changes_api.ts new file mode 100644 index 0000000000000..ecdc0826115a7 --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/control_group_unsaved_changes_api.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { omit } from 'lodash'; +import { + childrenUnsavedChanges$, + initializeUnsavedChanges, + PresentationContainer, +} from '@kbn/presentation-containers'; +import { + apiPublishesUnsavedChanges, + PublishesUnsavedChanges, + StateComparators, +} from '@kbn/presentation-publishing'; +import { combineLatest, map } from 'rxjs'; +import { ControlsInOrder, getControlsInOrder } from './init_controls_manager'; +import { ControlGroupRuntimeState, ControlPanelsState } from './types'; +import { apiPublishesAsyncFilters } from '../controls/data_controls/publishes_async_filters'; + +export type ControlGroupComparatorState = Pick< + ControlGroupRuntimeState, + | 'autoApplySelections' + | 'chainingSystem' + | 'ignoreParentSettings' + | 'initialChildControlState' + | 'labelPosition' +> & { + controlsInOrder: ControlsInOrder; +}; + +export function initializeControlGroupUnsavedChanges( + applySelections: () => void, + children$: PresentationContainer['children$'], + comparators: StateComparators<ControlGroupComparatorState>, + snapshotControlsRuntimeState: () => ControlPanelsState, + parentApi: unknown, + lastSavedRuntimeState: ControlGroupRuntimeState +) { + const controlGroupUnsavedChanges = initializeUnsavedChanges<ControlGroupComparatorState>( + { + autoApplySelections: lastSavedRuntimeState.autoApplySelections, + chainingSystem: lastSavedRuntimeState.chainingSystem, + controlsInOrder: getControlsInOrder(lastSavedRuntimeState.initialChildControlState), + ignoreParentSettings: lastSavedRuntimeState.ignoreParentSettings, + initialChildControlState: lastSavedRuntimeState.initialChildControlState, + labelPosition: lastSavedRuntimeState.labelPosition, + }, + parentApi, + comparators + ); + + return { + api: { + unsavedChanges: combineLatest([ + controlGroupUnsavedChanges.api.unsavedChanges, + childrenUnsavedChanges$(children$), + ]).pipe( + map(([unsavedControlGroupState, unsavedControlsState]) => { + const unsavedChanges: Partial<ControlGroupRuntimeState> = unsavedControlGroupState + ? omit(unsavedControlGroupState, 'controlsInOrder') + : {}; + if (unsavedControlsState || unsavedControlGroupState?.controlsInOrder) { + unsavedChanges.initialChildControlState = snapshotControlsRuntimeState(); + } + return Object.keys(unsavedChanges).length ? unsavedChanges : undefined; + }) + ), + asyncResetUnsavedChanges: async () => { + controlGroupUnsavedChanges.api.resetUnsavedChanges(); + + const filtersReadyPromises: Array<Promise<void>> = []; + Object.values(children$.value).forEach((controlApi) => { + if (apiPublishesUnsavedChanges(controlApi)) controlApi.resetUnsavedChanges(); + if (apiPublishesAsyncFilters(controlApi)) { + filtersReadyPromises.push(controlApi.untilFiltersReady()); + } + }); + + await Promise.all(filtersReadyPromises); + + if (!comparators.autoApplySelections[0].value) { + applySelections(); + } + }, + } as Pick<PublishesUnsavedChanges, 'unsavedChanges'> & { + asyncResetUnsavedChanges: () => Promise<void>; + }, + }; +} diff --git a/src/plugins/controls/public/react_controls/control_group/get_control_group_factory.tsx b/src/plugins/controls/public/react_controls/control_group/get_control_group_factory.tsx new file mode 100644 index 0000000000000..06676a6c9d4be --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/get_control_group_factory.tsx @@ -0,0 +1,264 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 } from 'react'; +import { BehaviorSubject } from 'rxjs'; +import fastIsEqual from 'fast-deep-equal'; +import { CoreStart } from '@kbn/core/public'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { + apiHasSaveNotification, + combineCompatibleChildrenApis, +} from '@kbn/presentation-containers'; +import { + apiPublishesDataViews, + PublishesDataViews, + useBatchedPublishingSubjects, +} from '@kbn/presentation-publishing'; +import { ControlStyle, ParentIgnoreSettings } from '../..'; +import { + ControlGroupChainingSystem, + ControlWidth, + CONTROL_GROUP_TYPE, + DEFAULT_CONTROL_GROW, + DEFAULT_CONTROL_STYLE, + DEFAULT_CONTROL_WIDTH, +} from '../../../common'; +import { chaining$, controlFetch$, controlGroupFetch$ } from './control_fetch'; +import { initControlsManager } from './init_controls_manager'; +import { openEditControlGroupFlyout } from './open_edit_control_group_flyout'; +import { deserializeControlGroup } from './serialization_utils'; +import { ControlGroupApi, ControlGroupRuntimeState, ControlGroupSerializedState } from './types'; +import { ControlGroup } from './components/control_group'; +import { initSelectionsManager } from './selections_manager'; +import { initializeControlGroupUnsavedChanges } from './control_group_unsaved_changes_api'; +import { openDataControlEditor } from '../controls/data_controls/open_data_control_editor'; + +export const getControlGroupEmbeddableFactory = (services: { + core: CoreStart; + dataViews: DataViewsPublicPluginStart; +}) => { + const controlGroupEmbeddableFactory: ReactEmbeddableFactory< + ControlGroupSerializedState, + ControlGroupRuntimeState, + ControlGroupApi + > = { + type: CONTROL_GROUP_TYPE, + deserializeState: (state) => deserializeControlGroup(state), + buildEmbeddable: async ( + initialRuntimeState, + buildApi, + uuid, + parentApi, + setApi, + lastSavedRuntimeState + ) => { + const { + initialChildControlState, + defaultControlGrow, + defaultControlWidth, + labelPosition: initialLabelPosition, + chainingSystem, + autoApplySelections, + ignoreParentSettings, + } = initialRuntimeState; + + const autoApplySelections$ = new BehaviorSubject<boolean>(autoApplySelections); + const controlsManager = initControlsManager(initialChildControlState); + const selectionsManager = initSelectionsManager({ + ...controlsManager.api, + autoApplySelections$, + }); + const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined); + const chainingSystem$ = new BehaviorSubject<ControlGroupChainingSystem>(chainingSystem); + const ignoreParentSettings$ = new BehaviorSubject<ParentIgnoreSettings | undefined>( + ignoreParentSettings + ); + const grow = new BehaviorSubject<boolean | undefined>( + defaultControlGrow === undefined ? DEFAULT_CONTROL_GROW : defaultControlGrow + ); + const width = new BehaviorSubject<ControlWidth | undefined>( + defaultControlWidth ?? DEFAULT_CONTROL_WIDTH + ); + const labelPosition$ = new BehaviorSubject<ControlStyle>( // TODO: Rename `ControlStyle` + initialLabelPosition ?? DEFAULT_CONTROL_STYLE // TODO: Rename `DEFAULT_CONTROL_STYLE` + ); + const allowExpensiveQueries$ = new BehaviorSubject<boolean>(true); + + /** TODO: Handle loading; loading should be true if any child is loading */ + const dataLoading$ = new BehaviorSubject<boolean | undefined>(false); + + const unsavedChanges = initializeControlGroupUnsavedChanges( + selectionsManager.applySelections, + controlsManager.api.children$, + { + ...controlsManager.comparators, + autoApplySelections: [ + autoApplySelections$, + (next: boolean) => autoApplySelections$.next(next), + ], + chainingSystem: [ + chainingSystem$, + (next: ControlGroupChainingSystem) => chainingSystem$.next(next), + ], + ignoreParentSettings: [ + ignoreParentSettings$, + (next: ParentIgnoreSettings | undefined) => ignoreParentSettings$.next(next), + fastIsEqual, + ], + labelPosition: [labelPosition$, (next: ControlStyle) => labelPosition$.next(next)], + }, + controlsManager.snapshotControlsRuntimeState, + parentApi, + lastSavedRuntimeState + ); + + const api = setApi({ + ...controlsManager.api, + getLastSavedControlState: (controlUuid: string) => { + return lastSavedRuntimeState.initialChildControlState[controlUuid] ?? {}; + }, + ...unsavedChanges.api, + ...selectionsManager.api, + controlFetch$: (controlUuid: string) => + controlFetch$( + chaining$( + controlUuid, + chainingSystem$, + controlsManager.controlsInOrder$, + controlsManager.api.children$ + ), + controlGroupFetch$(ignoreParentSettings$, parentApi ? parentApi : {}) + ), + ignoreParentSettings$, + autoApplySelections$, + allowExpensiveQueries$, + snapshotRuntimeState: () => { + // TODO: Remove this if it ends up being unnecessary + return {} as unknown as ControlGroupRuntimeState; + }, + dataLoading: dataLoading$, + onEdit: async () => { + openEditControlGroupFlyout( + api, + { + chainingSystem: chainingSystem$, + labelPosition: labelPosition$, + autoApplySelections: autoApplySelections$, + ignoreParentSettings: ignoreParentSettings$, + }, + { core: services.core } + ); + }, + isEditingEnabled: () => true, + getTypeDisplayName: () => + i18n.translate('controls.controlGroup.displayName', { + defaultMessage: 'Controls', + }), + openAddDataControlFlyout: (settings) => { + const { controlInputTransform } = settings ?? { + controlInputTransform: (state) => state, + }; + openDataControlEditor({ + initialState: { + grow: api.grow.getValue(), + width: api.width.getValue(), + }, + onSave: ({ type: controlType, state: initialState }) => { + api.addNewPanel({ + panelType: controlType, + initialState: controlInputTransform!( + initialState as Partial<ControlGroupSerializedState>, + controlType + ), + }); + }, + controlGroupApi: api, + services, + }); + }, + serializeState: () => { + const { panelsJSON, references } = controlsManager.serializeControls(); + return { + rawState: { + chainingSystem: chainingSystem$.getValue(), + controlStyle: labelPosition$.getValue(), // Rename "labelPosition" to "controlStyle" + showApplySelections: !autoApplySelections$.getValue(), + ignoreParentSettingsJSON: JSON.stringify(ignoreParentSettings$.getValue()), + panelsJSON, + }, + references, + }; + }, + grow, + width, + dataViews, + labelPosition: labelPosition$, + saveNotification$: apiHasSaveNotification(parentApi) + ? parentApi.saveNotification$ + : undefined, + }); + + /** Subscribe to all children's output data views, combine them, and output them */ + const childrenDataViewsSubscription = combineCompatibleChildrenApis< + PublishesDataViews, + DataView[] + >(api, 'dataViews', apiPublishesDataViews, []).subscribe((newDataViews) => + dataViews.next(newDataViews) + ); + + /** Fetch the allowExpensiveQuries setting for the children to use if necessary */ + try { + const { allowExpensiveQueries } = await services.core.http.get<{ + allowExpensiveQueries: boolean; + // TODO: Rename this route as part of https://github.com/elastic/kibana/issues/174961 + }>('/internal/controls/optionsList/getExpensiveQueriesSetting', { + version: '1', + }); + if (!allowExpensiveQueries) { + // only set if this returns false, since it defaults to true + allowExpensiveQueries$.next(allowExpensiveQueries); + } + } catch { + // do nothing - default to true on error (which it was initialized to) + } + + return { + api, + Component: () => { + const [hasUnappliedSelections, labelPosition] = useBatchedPublishingSubjects( + selectionsManager.hasUnappliedSelections$, + labelPosition$ + ); + + useEffect(() => { + return () => { + selectionsManager.cleanup(); + childrenDataViewsSubscription.unsubscribe(); + }; + }, []); + + return ( + <ControlGroup + applySelections={selectionsManager.applySelections} + controlGroupApi={api} + controlsManager={controlsManager} + hasUnappliedSelections={hasUnappliedSelections} + labelPosition={labelPosition} + /> + ); + }, + }; + }, + }; + + return controlGroupEmbeddableFactory; +}; diff --git a/src/plugins/controls/public/react_controls/control_group/init_controls_manager.test.ts b/src/plugins/controls/public/react_controls/control_group/init_controls_manager.test.ts new file mode 100644 index 0000000000000..451681fe5b3fd --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/init_controls_manager.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DefaultControlApi } from '../controls/types'; +import { initControlsManager } from './init_controls_manager'; + +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('delta'), +})); + +describe('PresentationContainer api', () => { + test('addNewPanel should add control at end of controls', async () => { + const controlsManager = initControlsManager({ + alpha: { type: 'testControl', order: 0 }, + bravo: { type: 'testControl', order: 1 }, + charlie: { type: 'testControl', order: 2 }, + }); + const addNewPanelPromise = controlsManager.api.addNewPanel({ + panelType: 'testControl', + initialState: {}, + }); + controlsManager.setControlApi('delta', {} as unknown as DefaultControlApi); + await addNewPanelPromise; + expect(controlsManager.controlsInOrder$.value.map((element) => element.id)).toEqual([ + 'alpha', + 'bravo', + 'charlie', + 'delta', + ]); + }); + + test('removePanel should remove control', () => { + const controlsManager = initControlsManager({ + alpha: { type: 'testControl', order: 0 }, + bravo: { type: 'testControl', order: 1 }, + charlie: { type: 'testControl', order: 2 }, + }); + controlsManager.api.removePanel('bravo'); + expect(controlsManager.controlsInOrder$.value.map((element) => element.id)).toEqual([ + 'alpha', + 'charlie', + ]); + }); + + test('replacePanel should replace control', async () => { + const controlsManager = initControlsManager({ + alpha: { type: 'testControl', order: 0 }, + bravo: { type: 'testControl', order: 1 }, + charlie: { type: 'testControl', order: 2 }, + }); + const replacePanelPromise = controlsManager.api.replacePanel('bravo', { + panelType: 'testControl', + initialState: {}, + }); + controlsManager.setControlApi('delta', {} as unknown as DefaultControlApi); + await replacePanelPromise; + expect(controlsManager.controlsInOrder$.value.map((element) => element.id)).toEqual([ + 'alpha', + 'delta', + 'charlie', + ]); + }); + + describe('untilInitialized', () => { + test('should not resolve until all controls are initialized', async () => { + const controlsManager = initControlsManager({ + alpha: { type: 'testControl', order: 0 }, + bravo: { type: 'testControl', order: 1 }, + }); + let isDone = false; + controlsManager.api.untilInitialized().then(() => { + isDone = true; + }); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(isDone).toBe(false); + + controlsManager.setControlApi('alpha', {} as unknown as DefaultControlApi); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(isDone).toBe(false); + + controlsManager.setControlApi('bravo', {} as unknown as DefaultControlApi); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(isDone).toBe(true); + }); + + test('should resolve when all control already initialized ', async () => { + const controlsManager = initControlsManager({ + alpha: { type: 'testControl', order: 0 }, + bravo: { type: 'testControl', order: 1 }, + }); + controlsManager.setControlApi('alpha', {} as unknown as DefaultControlApi); + controlsManager.setControlApi('bravo', {} as unknown as DefaultControlApi); + + let isDone = false; + controlsManager.api.untilInitialized().then(() => { + isDone = true; + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(isDone).toBe(true); + }); + }); +}); + +describe('snapshotControlsRuntimeState', () => { + test('should snapshot runtime state for all controls', async () => { + const controlsManager = initControlsManager({ + alpha: { type: 'testControl', order: 1 }, + bravo: { type: 'testControl', order: 0 }, + }); + controlsManager.setControlApi('alpha', { + snapshotRuntimeState: () => { + return { key1: 'alpha value' }; + }, + } as unknown as DefaultControlApi); + controlsManager.setControlApi('bravo', { + snapshotRuntimeState: () => { + return { key1: 'bravo value' }; + }, + } as unknown as DefaultControlApi); + expect(controlsManager.snapshotControlsRuntimeState()).toEqual({ + alpha: { + key1: 'alpha value', + order: 1, + type: 'testControl', + }, + bravo: { + key1: 'bravo value', + order: 0, + type: 'testControl', + }, + }); + }); +}); diff --git a/src/plugins/controls/public/react_controls/control_group/init_controls_manager.ts b/src/plugins/controls/public/react_controls/control_group/init_controls_manager.ts new file mode 100644 index 0000000000000..d9eccfa126a8a --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/init_controls_manager.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { v4 as generateId } from 'uuid'; +import fastIsEqual from 'fast-deep-equal'; +import { + HasSerializedChildState, + PanelPackage, + PresentationContainer, +} from '@kbn/presentation-containers'; +import type { Reference } from '@kbn/content-management-utils'; +import { BehaviorSubject, first, merge } from 'rxjs'; +import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing'; +import { omit } from 'lodash'; +import { apiHasSnapshottableState } from '@kbn/presentation-containers/interfaces/serialized_state'; +import { ControlPanelsState, ControlPanelState } from './types'; +import { DefaultControlApi, DefaultControlState } from '../controls/types'; +import { ControlGroupComparatorState } from './control_group_unsaved_changes_api'; + +export type ControlsInOrder = Array<{ id: string; type: string }>; + +export function getControlsInOrder(initialControlPanelsState: ControlPanelsState) { + return Object.keys(initialControlPanelsState) + .map((key) => ({ + id: key, + order: initialControlPanelsState[key].order, + type: initialControlPanelsState[key].type, + })) + .sort((a, b) => (a.order > b.order ? 1 : -1)) + .map(({ id, type }) => ({ id, type })); // filter out `order` +} + +export function initControlsManager(initialControlPanelsState: ControlPanelsState) { + const lastSavedControlsPanelState$ = new BehaviorSubject(initialControlPanelsState); + const initialControlIds = Object.keys(initialControlPanelsState); + const children$ = new BehaviorSubject<{ [key: string]: DefaultControlApi }>({}); + let controlsPanelState: { [panelId: string]: DefaultControlState } = { + ...initialControlPanelsState, + }; + const controlsInOrder$ = new BehaviorSubject<ControlsInOrder>( + getControlsInOrder(initialControlPanelsState) + ); + + function untilControlLoaded( + id: string + ): DefaultControlApi | Promise<DefaultControlApi | undefined> { + if (children$.value[id]) { + return children$.value[id]; + } + + return new Promise((resolve) => { + const subscription = merge(children$, controlsInOrder$).subscribe(() => { + if (children$.value[id]) { + subscription.unsubscribe(); + resolve(children$.value[id]); + return; + } + + // control removed before the control finished loading. + const controlState = controlsInOrder$.value.find((element) => element.id === id); + if (!controlState) { + subscription.unsubscribe(); + resolve(undefined); + } + }); + }); + } + + function getControlApi(controlUuid: string) { + return children$.value[controlUuid]; + } + + async function addNewPanel( + { panelType, initialState }: PanelPackage<DefaultControlState>, + index: number + ) { + const id = generateId(); + const nextControlsInOrder = [...controlsInOrder$.value]; + nextControlsInOrder.splice(index, 0, { + id, + type: panelType, + }); + controlsInOrder$.next(nextControlsInOrder); + controlsPanelState[id] = initialState ?? {}; + return await untilControlLoaded(id); + } + + function removePanel(panelId: string) { + delete controlsPanelState[panelId]; + controlsInOrder$.next(controlsInOrder$.value.filter(({ id }) => id !== panelId)); + children$.next(omit(children$.value, panelId)); + } + + return { + controlsInOrder$, + getControlApi, + setControlApi: (uuid: string, controlApi: DefaultControlApi) => { + children$.next({ + ...children$.getValue(), + [uuid]: controlApi, + }); + }, + serializeControls: () => { + const references: Reference[] = []; + const explicitInputPanels: { + [panelId: string]: ControlPanelState & { explicitInput: object }; + } = {}; + + controlsInOrder$.getValue().forEach(({ id }, index) => { + const controlApi = getControlApi(id); + if (!controlApi) { + return; + } + + const { + rawState: { grow, width, ...rest }, + references: controlReferences, + } = controlApi.serializeState(); + + if (controlReferences && controlReferences.length > 0) { + references.push(...controlReferences); + } + + explicitInputPanels[id] = { + grow, + order: index, + type: controlApi.type, + width, + /** Re-add the `explicitInput` layer on serialize so control group saved object retains shape */ + explicitInput: rest, + }; + }); + + return { + panelsJSON: JSON.stringify(explicitInputPanels), + references, + }; + }, + snapshotControlsRuntimeState: () => { + const controlsRuntimeState: ControlPanelsState = {}; + controlsInOrder$.getValue().forEach(({ id, type }, index) => { + const controlApi = getControlApi(id); + if (controlApi && apiHasSnapshottableState(controlApi)) { + controlsRuntimeState[id] = { + order: index, + type, + ...controlApi.snapshotRuntimeState(), + }; + } + }); + return controlsRuntimeState; + }, + api: { + getSerializedStateForChild: (childId: string) => { + const controlPanelState = controlsPanelState[childId]; + return controlPanelState ? { rawState: controlPanelState } : undefined; + }, + children$: children$ as PublishingSubject<{ + [key: string]: DefaultControlApi; + }>, + getPanelCount: () => { + return controlsInOrder$.value.length; + }, + addNewPanel: async (panel: PanelPackage<DefaultControlState>) => { + return addNewPanel(panel, controlsInOrder$.value.length); + }, + removePanel, + replacePanel: async (panelId, newPanel) => { + const index = controlsInOrder$.value.findIndex(({ id }) => id === panelId); + removePanel(panelId); + const controlApi = await addNewPanel( + newPanel, + index >= 0 ? index : controlsInOrder$.value.length + ); + return controlApi ? controlApi.uuid : ''; + }, + untilInitialized: () => { + return new Promise((resolve) => { + children$ + .pipe( + first((children) => { + const atLeastOneControlNotInitialized = initialControlIds.some( + (controlId) => !children[controlId] + ); + return !atLeastOneControlNotInitialized; + }) + ) + .subscribe(() => { + resolve(); + }); + }); + }, + } as PresentationContainer & + HasSerializedChildState<ControlPanelState> & { untilInitialized: () => Promise<void> }, + comparators: { + controlsInOrder: [ + controlsInOrder$, + (next: ControlsInOrder) => controlsInOrder$.next(next), + fastIsEqual, + ], + // Control state differences tracked by controlApi comparators + // Control ordering differences tracked by controlsInOrder comparator + // initialChildControlState comparatator exists to reset controls manager to last saved state + initialChildControlState: [ + lastSavedControlsPanelState$, + (lastSavedControlPanelsState: ControlPanelsState) => { + lastSavedControlsPanelState$.next(lastSavedControlPanelsState); + controlsPanelState = { + ...lastSavedControlPanelsState, + }; + controlsInOrder$.next(getControlsInOrder(lastSavedControlPanelsState)); + }, + () => true, + ], + } as StateComparators< + Pick<ControlGroupComparatorState, 'controlsInOrder' | 'initialChildControlState'> + >, + }; +} diff --git a/examples/controls_example/public/react_controls/control_group/open_edit_control_group_flyout.tsx b/src/plugins/controls/public/react_controls/control_group/open_edit_control_group_flyout.tsx similarity index 98% rename from examples/controls_example/public/react_controls/control_group/open_edit_control_group_flyout.tsx rename to src/plugins/controls/public/react_controls/control_group/open_edit_control_group_flyout.tsx index 7ca8b993083c3..06ab4c063b5c0 100644 --- a/examples/controls_example/public/react_controls/control_group/open_edit_control_group_flyout.tsx +++ b/src/plugins/controls/public/react_controls/control_group/open_edit_control_group_flyout.tsx @@ -15,7 +15,7 @@ import { toMountPoint } from '@kbn/react-kibana-mount'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; -import { ControlStateManager } from '../types'; +import { ControlStateManager } from '../controls/types'; import { ControlGroupEditor } from './control_group_editor'; import { ControlGroupApi, ControlGroupEditorState } from './types'; diff --git a/src/plugins/controls/public/react_controls/control_group/register_control_group_embeddable.ts b/src/plugins/controls/public/react_controls/control_group/register_control_group_embeddable.ts new file mode 100644 index 0000000000000..4534c8cb403b0 --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/register_control_group_embeddable.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 { CoreSetup } from '@kbn/core/public'; +import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; +import type { ControlsPluginStartDeps } from '../../types'; +import { CONTROL_GROUP_TYPE } from '../../../common'; + +export function registerControlGroupEmbeddable( + coreSetup: CoreSetup<ControlsPluginStartDeps>, + embeddableSetup: EmbeddableSetup +) { + embeddableSetup.registerReactEmbeddableFactory(CONTROL_GROUP_TYPE, async () => { + const [{ getControlGroupEmbeddableFactory }, [coreStart, depsStart]] = await Promise.all([ + import('./get_control_group_factory'), + coreSetup.getStartServices(), + ]); + return getControlGroupEmbeddableFactory({ + core: coreStart, + dataViews: depsStart.data.dataViews, + }); + }); +} diff --git a/examples/controls_example/public/react_controls/control_group/selections_manager.test.ts b/src/plugins/controls/public/react_controls/control_group/selections_manager.test.ts similarity index 100% rename from examples/controls_example/public/react_controls/control_group/selections_manager.test.ts rename to src/plugins/controls/public/react_controls/control_group/selections_manager.test.ts diff --git a/examples/controls_example/public/react_controls/control_group/selections_manager.ts b/src/plugins/controls/public/react_controls/control_group/selections_manager.ts similarity index 100% rename from examples/controls_example/public/react_controls/control_group/selections_manager.ts rename to src/plugins/controls/public/react_controls/control_group/selections_manager.ts diff --git a/examples/controls_example/public/react_controls/control_group/serialization_utils.ts b/src/plugins/controls/public/react_controls/control_group/serialization_utils.ts similarity index 95% rename from examples/controls_example/public/react_controls/control_group/serialization_utils.ts rename to src/plugins/controls/public/react_controls/control_group/serialization_utils.ts index b38b22fc04249..f57fbd801804f 100644 --- a/examples/controls_example/public/react_controls/control_group/serialization_utils.ts +++ b/src/plugins/controls/public/react_controls/control_group/serialization_utils.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '@kbn/controls-plugin/common'; import { SerializedPanelState } from '@kbn/presentation-containers'; import { omit } from 'lodash'; +import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../common'; import { ControlGroupRuntimeState, ControlGroupSerializedState } from './types'; export const deserializeControlGroup = ( diff --git a/src/plugins/controls/public/react_controls/control_group/types.ts b/src/plugins/controls/public/react_controls/control_group/types.ts new file mode 100644 index 0000000000000..d58a487b86a79 --- /dev/null +++ b/src/plugins/controls/public/react_controls/control_group/types.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 { Observable } from 'rxjs'; + +import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; +import { Filter } from '@kbn/es-query'; +import { + HasSaveNotification, + HasSerializedChildState, + PresentationContainer, +} from '@kbn/presentation-containers'; +import { + HasEditCapabilities, + HasParentApi, + PublishesDataLoading, + PublishesFilters, + PublishesTimeslice, + PublishesUnifiedSearch, + PublishesUnsavedChanges, + PublishingSubject, +} from '@kbn/presentation-publishing'; +import { PublishesDataViews } from '@kbn/presentation-publishing/interfaces/publishes_data_views'; + +import { ParentIgnoreSettings } from '../..'; +import { ControlInputTransform } from '../../../common'; +import { ControlGroupChainingSystem } from '../../../common/control_group/types'; +import { ControlStyle, ControlWidth } from '../../types'; +import { DefaultControlState, PublishesControlDisplaySettings } from '../controls/types'; +import { ControlFetchContext } from './control_fetch/control_fetch'; + +/** The control display settings published by the control group are the "default" */ +type PublishesControlGroupDisplaySettings = PublishesControlDisplaySettings & { + labelPosition: PublishingSubject<ControlStyle>; +}; +export interface ControlPanelsState<ControlState extends ControlPanelState = ControlPanelState> { + [panelId: string]: ControlState; +} + +export type ControlGroupUnsavedChanges = Omit< + ControlGroupRuntimeState, + 'initialChildControlState' | 'defaultControlGrow' | 'defaultControlWidth' +> & { + filters: Filter[] | undefined; +}; + +export type ControlPanelState = DefaultControlState & { type: string; order: number }; + +export type ControlGroupApi = PresentationContainer & + DefaultEmbeddableApi<ControlGroupSerializedState, ControlGroupRuntimeState> & + PublishesFilters & + PublishesDataViews & + HasSerializedChildState<ControlPanelState> & + HasEditCapabilities & + PublishesDataLoading & + Pick<PublishesUnsavedChanges, 'unsavedChanges'> & + PublishesControlGroupDisplaySettings & + PublishesTimeslice & + Partial<HasParentApi<PublishesUnifiedSearch> & HasSaveNotification> & { + asyncResetUnsavedChanges: () => Promise<void>; + autoApplySelections$: PublishingSubject<boolean>; + controlFetch$: (controlUuid: string) => Observable<ControlFetchContext>; + getLastSavedControlState: (controlUuid: string) => object; + ignoreParentSettings$: PublishingSubject<ParentIgnoreSettings | undefined>; + allowExpensiveQueries$: PublishingSubject<boolean>; + untilInitialized: () => Promise<void>; + openAddDataControlFlyout: (settings?: { + controlInputTransform?: ControlInputTransform; + }) => void; + }; + +export interface ControlGroupRuntimeState { + chainingSystem: ControlGroupChainingSystem; + defaultControlGrow?: boolean; + defaultControlWidth?: ControlWidth; + labelPosition: ControlStyle; // TODO: Rename this type to ControlLabelPosition + autoApplySelections: boolean; + ignoreParentSettings?: ParentIgnoreSettings; + + initialChildControlState: ControlPanelsState<ControlPanelState>; + /** TODO: Handle the editor config, which is used with the control group renderer component */ + editorConfig?: { + hideDataViewSelector?: boolean; + hideWidthSettings?: boolean; + hideAdditionalSettings?: boolean; + }; +} + +export type ControlGroupEditorState = Pick< + ControlGroupRuntimeState, + 'chainingSystem' | 'labelPosition' | 'autoApplySelections' | 'ignoreParentSettings' +>; + +export interface ControlGroupSerializedState { + chainingSystem: ControlGroupChainingSystem; + panelsJSON: string; + ignoreParentSettingsJSON: string; + // In runtime state, we refer to this property as `labelPosition`; + // to avoid migrations, we will continue to refer to this property as `controlStyle` in the serialized state + controlStyle: ControlStyle; + // In runtime state, we refer to the inverse of this property as `autoApplySelections` + // to avoid migrations, we will continue to refer to this property as `showApplySelections` in the serialized state + showApplySelections: boolean | undefined; +} diff --git a/examples/controls_example/public/react_controls/constants.ts b/src/plugins/controls/public/react_controls/controls/constants.ts similarity index 100% rename from examples/controls_example/public/react_controls/constants.ts rename to src/plugins/controls/public/react_controls/controls/constants.ts diff --git a/examples/controls_example/public/react_controls/data_controls/data_control_constants.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_constants.tsx similarity index 98% rename from examples/controls_example/public/react_controls/data_controls/data_control_constants.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/data_control_constants.tsx index 9bc9eddbfc0d2..d68309b62ed31 100644 --- a/examples/controls_example/public/react_controls/data_controls/data_control_constants.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_constants.tsx @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { RANGE_SLIDER_CONTROL } from '@kbn/controls-plugin/common'; import { i18n } from '@kbn/i18n'; +import { RANGE_SLIDER_CONTROL } from '../../../../common'; export const DataControlEditorStrings = { manageControl: { diff --git a/examples/controls_example/public/react_controls/data_controls/data_control_editor.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.test.tsx similarity index 75% rename from examples/controls_example/public/react_controls/data_controls/data_control_editor.test.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.test.tsx index d90e5349f36d8..9b5798efdc220 100644 --- a/examples/controls_example/public/react_controls/data_controls/data_control_editor.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.test.tsx @@ -16,23 +16,23 @@ import { TimeRange } from '@kbn/es-query'; import { I18nProvider } from '@kbn/i18n-react'; import { act, fireEvent, render, RenderResult, waitFor } from '@testing-library/react'; -import { getAllControlTypes, getControlFactory } from '../control_factory_registry'; -jest.mock('../control_factory_registry', () => ({ - ...jest.requireActual('../control_factory_registry'), +import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; +jest.mock('../../control_factory_registry', () => ({ + ...jest.requireActual('../../control_factory_registry'), getAllControlTypes: jest.fn(), getControlFactory: jest.fn(), })); -import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '@kbn/controls-plugin/common'; -import { ControlGroupApi } from '../control_group/types'; +import { DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../../common'; +import { ControlGroupApi } from '../../control_group/types'; import { DataControlEditor } from './data_control_editor'; import { DataControlEditorState } from './open_data_control_editor'; import { getMockedOptionsListControlFactory, getMockedRangeSliderControlFactory, getMockedSearchControlFactory, -} from './mocks/data_control_mocks'; +} from './mocks/factory_mocks'; import { ControlFactory } from '../types'; -import { DataControlApi, DefaultDataControlState } from './types'; +import { DataControlApi, DataControlFactory, DefaultDataControlState } from './types'; const mockDataViews = dataViewPluginMocks.createStartContract(); const mockDataView = createStubDataView({ @@ -106,13 +106,13 @@ describe('Data control editor', () => { return controlEditor.getByTestId(testId).getAttribute('aria-pressed'); }; + const mockRegistry: { [key: string]: ControlFactory<DefaultDataControlState, DataControlApi> } = { + search: getMockedSearchControlFactory({ parentApi: controlGroupApi }), + optionsList: getMockedOptionsListControlFactory({ parentApi: controlGroupApi }), + rangeSlider: getMockedRangeSliderControlFactory({ parentApi: controlGroupApi }), + }; + beforeAll(() => { - const mockRegistry: { [key: string]: ControlFactory<DefaultDataControlState, DataControlApi> } = - { - search: getMockedSearchControlFactory({ parentApi: controlGroupApi }), - optionsList: getMockedOptionsListControlFactory({ parentApi: controlGroupApi }), - rangeSlider: getMockedRangeSliderControlFactory({ parentApi: controlGroupApi }), - }; (getAllControlTypes as jest.Mock).mockReturnValue(Object.keys(mockRegistry)); (getControlFactory as jest.Mock).mockImplementation((key) => mockRegistry[key]); }); @@ -133,6 +133,50 @@ describe('Data control editor', () => { expect(saveButton).toBeEnabled(); }); + test('CompatibleControlTypesComponent respects ordering', async () => { + const tempRegistry: { + [key: string]: ControlFactory<DefaultDataControlState, DataControlApi>; + } = { + ...mockRegistry, + alphabeticalFirstControl: { + type: 'alphabeticalFirst', + getIconType: () => 'lettering', + getDisplayName: () => 'Alphabetically first', + isFieldCompatible: () => true, + buildControl: jest.fn().mockReturnValue({ + api: controlGroupApi, + Component: <>Should be first alphabetically</>, + }), + } as DataControlFactory, + supremeControl: { + type: 'supremeControl', + order: 100, // force it first despite alphabetical ordering + getIconType: () => 'starFilled', + getDisplayName: () => 'Supreme leader', + isFieldCompatible: () => true, + buildControl: jest.fn().mockReturnValue({ + api: controlGroupApi, + Component: <>This control is forced first via the factory order</>, + }), + } as DataControlFactory, + }; + (getAllControlTypes as jest.Mock).mockReturnValue(Object.keys(tempRegistry)); + (getControlFactory as jest.Mock).mockImplementation((key) => tempRegistry[key]); + + const controlEditor = await mountComponent({}); + const menu = controlEditor.getByTestId('controlTypeMenu'); + expect(menu.children.length).toEqual(5); + expect(menu.children[0].textContent).toEqual('Supreme leader'); // forced first - ignore alphabetical sorting + // the rest should be alphabetically sorted + expect(menu.children[1].textContent).toEqual('Alphabetically first'); + expect(menu.children[2].textContent).toEqual('Options list'); + expect(menu.children[3].textContent).toEqual('Range slider'); + expect(menu.children[4].textContent).toEqual('Search'); + + (getAllControlTypes as jest.Mock).mockReturnValue(Object.keys(mockRegistry)); + (getControlFactory as jest.Mock).mockImplementation((key) => mockRegistry[key]); + }); + test('selecting a keyword field - can only create an options list control', async () => { const controlEditor = await mountComponent({}); await selectField(controlEditor, 'machine.os.raw'); diff --git a/examples/controls_example/public/react_controls/data_controls/data_control_editor.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.tsx similarity index 85% rename from examples/controls_example/public/react_controls/data_controls/data_control_editor.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.tsx index 6776a24e1874d..701d8c1d85391 100644 --- a/examples/controls_example/public/react_controls/data_controls/data_control_editor.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor.tsx @@ -5,7 +5,7 @@ * Side Public License, v 1. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { @@ -30,13 +30,6 @@ import { EuiTitle, EuiToolTip, } from '@elastic/eui'; -import { - ControlWidth, - DEFAULT_CONTROL_GROW, - DEFAULT_CONTROL_WIDTH, -} from '@kbn/controls-plugin/common'; -import { CONTROL_WIDTH_OPTIONS } from '@kbn/controls-plugin/public'; -import { DataControlFieldRegistry } from '@kbn/controls-plugin/public/types'; import { DataViewField } from '@kbn/data-views-plugin/common'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; @@ -45,13 +38,16 @@ import { LazyFieldPicker, withSuspense, } from '@kbn/presentation-util-plugin/public'; +import { DataControlFieldRegistry } from '../../../types'; +import { CONTROL_WIDTH_OPTIONS } from '../../..'; +import { ControlWidth, DEFAULT_CONTROL_GROW, DEFAULT_CONTROL_WIDTH } from '../../../../common'; -import { getAllControlTypes, getControlFactory } from '../control_factory_registry'; -import { ControlGroupApi } from '../control_group/types'; +import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; +import { ControlGroupApi } from '../../control_group/types'; import { DataControlEditorStrings } from './data_control_constants'; import { getDataControlFieldRegistry } from './data_control_editor_utils'; import { DataControlEditorState } from './open_data_control_editor'; -import { DataControlFactory, isDataControlFactory } from './types'; +import { DataControlFactory, DefaultDataControlState, isDataControlFactory } from './types'; export interface ControlEditorProps<State extends DataControlEditorState = DataControlEditorState> { initialState: State; @@ -80,9 +76,18 @@ const CompatibleControlTypesComponent = ({ const dataControlFactories = useMemo(() => { return getAllControlTypes() .map((type) => getControlFactory(type)) - .filter((factory) => { - return isDataControlFactory(factory); - }); + .filter((factory) => isDataControlFactory(factory)) + .sort( + ( + { order: orderA = 0, getDisplayName: getDisplayNameA }, + { order: orderB = 0, getDisplayName: getDisplayNameB } + ) => { + const orderComparison = orderB - orderA; // sort descending by order + return orderComparison === 0 + ? getDisplayNameA().localeCompare(getDisplayNameB()) // if equal order, compare display names + : orderComparison; + } + ); }, []); return ( @@ -146,7 +151,7 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon const [selectedControlType, setSelectedControlType] = useState<string | undefined>( initialState.controlType ); - const [controlEditorValid, setControlEditorValid] = useState<boolean>(false); + const [controlOptionsValid, setControlOptionsValid] = useState<boolean>(true); /** TODO: Make `editorConfig` work when refactoring the `ControlGroupRenderer` */ // const editorConfig = controlGroup.getEditorConfig(); @@ -181,19 +186,13 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon }; }, [editorState.dataViewId]); - useEffect(() => { - setControlEditorValid( - Boolean(editorState.fieldName) && Boolean(selectedDataView) && Boolean(selectedControlType) - ); - }, [editorState.fieldName, setControlEditorValid, selectedDataView, selectedControlType]); - const CustomSettingsComponent = useMemo(() => { if (!selectedControlType || !editorState.fieldName || !fieldRegistry) return; - const controlFactory = getControlFactory(selectedControlType) as DataControlFactory; const CustomSettings = controlFactory.CustomOptionsComponent; if (!CustomSettings) return; + return ( <EuiDescribedFormGroup ratio="third" @@ -210,13 +209,15 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon data-test-subj="control-editor-custom-settings" > <CustomSettings - currentState={editorState} + initialState={initialState as DefaultDataControlState} + field={fieldRegistry[editorState.fieldName].field} updateState={(newState) => setEditorState({ ...editorState, ...newState })} - setControlEditorValid={setControlEditorValid} + setControlEditorValid={setControlOptionsValid} + parentApi={controlGroup} /> </EuiDescribedFormGroup> ); - }, [fieldRegistry, selectedControlType, editorState]); + }, [fieldRegistry, selectedControlType, initialState, editorState, controlGroup]); return ( <> @@ -287,14 +288,31 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon dataView={selectedDataView} onSelectField={(field) => { setEditorState({ ...editorState, fieldName: field.name }); - setSelectedControlType(fieldRegistry?.[field.name]?.compatibleControlTypes[0]); + /** + * make sure that the new field is compatible with the selected control type and, if it's not, + * reset the selected control type to the **first** compatible control type + */ + const newCompatibleControlTypes = + fieldRegistry?.[field.name]?.compatibleControlTypes ?? []; + if ( + !selectedControlType || + !newCompatibleControlTypes.includes(selectedControlType!) + ) { + setSelectedControlType(newCompatibleControlTypes[0]); + } + + /** + * set the control title (i.e. the one set by the user) + default title (i.e. the field display name) + */ const newDefaultTitle = field.displayName ?? field.name; setDefaultPanelTitle(newDefaultTitle); const currentTitle = editorState.title; if (!currentTitle || currentTitle === newDefaultTitle) { setPanelTitle(newDefaultTitle); } + + setControlOptionsValid(true); // reset options state }} selectableProps={{ isLoading: dataViewListLoading || dataViewLoading }} /> @@ -367,7 +385,6 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon {/* )} */} </EuiDescribedFormGroup> {CustomSettingsComponent} - {/* {!editorConfig?.hideAdditionalSettings ? CustomSettingsComponent : null} */} {initialState.controlId && ( <> <EuiSpacer size="l" /> @@ -407,7 +424,14 @@ export const DataControlEditor = <State extends DataControlEditorState = DataCon data-test-subj="control-editor-save" iconType="check" color="primary" - disabled={!controlEditorValid} + disabled={ + !( + controlOptionsValid && + Boolean(editorState.fieldName) && + Boolean(selectedDataView) && + Boolean(selectedControlType) + ) + } onClick={() => { onSave(editorState, selectedControlType!); }} diff --git a/examples/controls_example/public/react_controls/data_controls/data_control_editor_utils.ts b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor_utils.ts similarity index 91% rename from examples/controls_example/public/react_controls/data_controls/data_control_editor_utils.ts rename to src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor_utils.ts index d4ced0b1aafa6..f621cf24226d2 100644 --- a/examples/controls_example/public/react_controls/data_controls/data_control_editor_utils.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/data_control_editor_utils.ts @@ -8,9 +8,9 @@ import { memoize } from 'lodash'; -import { DataControlFieldRegistry } from '@kbn/controls-plugin/public/types'; import { DataView } from '@kbn/data-views-plugin/common'; -import { getAllControlTypes, getControlFactory } from '../control_factory_registry'; +import { DataControlFieldRegistry } from '../../../types'; +import { getAllControlTypes, getControlFactory } from '../../control_factory_registry'; import { isDataControlFactory } from './types'; /** TODO: This funciton is duplicated from the controls plugin to avoid exporting it */ diff --git a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.test.tsx similarity index 98% rename from examples/controls_example/public/react_controls/data_controls/initialize_data_control.test.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.test.tsx index 97ac9a6977226..5dd6bf745feca 100644 --- a/examples/controls_example/public/react_controls/data_controls/initialize_data_control.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.test.tsx @@ -10,7 +10,7 @@ import { coreMock } from '@kbn/core/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import type { DataView } from '@kbn/data-views-plugin/public'; import { first, skip } from 'rxjs'; -import { ControlGroupApi } from '../control_group/types'; +import { ControlGroupApi } from '../../control_group/types'; import { initializeDataControl } from './initialize_data_control'; describe('initializeDataControl', () => { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.ts b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.ts new file mode 100644 index 0000000000000..12bfba658c9f5 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/initialize_data_control.ts @@ -0,0 +1,251 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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'; +import { BehaviorSubject, combineLatest, debounceTime, first, skip, switchMap, tap } from 'rxjs'; + +import { CoreStart } from '@kbn/core-lifecycle-browser'; +import { + DataView, + DataViewField, + DATA_VIEW_SAVED_OBJECT_TYPE, +} from '@kbn/data-views-plugin/common'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { Filter } from '@kbn/es-query'; +import { SerializedPanelState } from '@kbn/presentation-containers'; +import { StateComparators } from '@kbn/presentation-publishing'; + +import { i18n } from '@kbn/i18n'; +import { ControlGroupApi } from '../../control_group/types'; +import { initializeDefaultControlApi } from '../initialize_default_control_api'; +import { ControlApiInitialization, ControlStateManager, DefaultControlState } from '../types'; +import { openDataControlEditor } from './open_data_control_editor'; +import { DataControlApi, DataControlFieldFormatter, DefaultDataControlState } from './types'; + +export const initializeDataControl = <EditorState extends object = {}>( + controlId: string, + controlType: string, + state: DefaultDataControlState, + /** + * `This state manager` should only include the state that the data control editor is + * responsible for managing + */ + editorStateManager: ControlStateManager<EditorState>, + controlGroup: ControlGroupApi, + services: { + core: CoreStart; + dataViews: DataViewsPublicPluginStart; + } +): { + api: ControlApiInitialization<DataControlApi>; + cleanup: () => void; + comparators: StateComparators<DefaultDataControlState>; + setters: { + onSelectionChange: () => void; + setOutputFilter: (filter: Filter | undefined) => void; + }; + stateManager: ControlStateManager<DefaultDataControlState>; + serialize: () => SerializedPanelState<DefaultControlState>; +} => { + const defaultControl = initializeDefaultControlApi(state); + + const panelTitle = new BehaviorSubject<string | undefined>(state.title); + const defaultPanelTitle = new BehaviorSubject<string | undefined>(undefined); + const dataViewId = new BehaviorSubject<string>(state.dataViewId); + const fieldName = new BehaviorSubject<string>(state.fieldName); + const dataViews = new BehaviorSubject<DataView[] | undefined>(undefined); + const filters$ = new BehaviorSubject<Filter[] | undefined>(undefined); + const filtersReady$ = new BehaviorSubject<boolean>(false); + const field$ = new BehaviorSubject<DataViewField | undefined>(undefined); + const fieldFormatter = new BehaviorSubject<DataControlFieldFormatter>((toFormat: any) => + String(toFormat) + ); + + const stateManager: ControlStateManager<DefaultDataControlState> = { + ...defaultControl.stateManager, + dataViewId, + fieldName, + title: panelTitle, + }; + + const dataViewIdSubscription = dataViewId + .pipe( + tap(() => { + filtersReady$.next(false); + if (defaultControl.api.blockingError.value) { + defaultControl.api.setBlockingError(undefined); + } + }), + switchMap(async (currentDataViewId) => { + let dataView: DataView | undefined; + try { + dataView = await services.dataViews.get(currentDataViewId); + return { dataView }; + } catch (error) { + return { error }; + } + }) + ) + .subscribe(({ dataView, error }) => { + if (error) { + defaultControl.api.setBlockingError(error); + } + dataViews.next(dataView ? [dataView] : undefined); + }); + + const fieldNameSubscription = combineLatest([dataViews, fieldName]) + .pipe( + tap(() => { + filtersReady$.next(false); + }) + ) + .subscribe(([nextDataViews, nextFieldName]) => { + const dataView = nextDataViews + ? nextDataViews.find(({ id }) => dataViewId.value === id) + : undefined; + if (!dataView) { + return; + } + + const field = dataView.getFieldByName(nextFieldName); + if (!field) { + defaultControl.api.setBlockingError( + new Error( + i18n.translate('controls.dataControl.fieldNotFound', { + defaultMessage: 'Could not locate field: {fieldName}', + values: { fieldName: nextFieldName }, + }) + ) + ); + } else if (defaultControl.api.blockingError.value) { + defaultControl.api.setBlockingError(undefined); + } + + field$.next(field); + defaultPanelTitle.next(field ? field.displayName || field.name : nextFieldName); + const spec = field?.toSpec(); + if (spec) { + fieldFormatter.next(dataView.getFormatterForField(spec).getConverterFor('text')); + } + }); + + const onEdit = async () => { + // get the initial state from the state manager + const mergedStateManager = { + ...stateManager, + ...editorStateManager, + } as ControlStateManager<DefaultDataControlState & EditorState>; + + const initialState = ( + Object.keys(mergedStateManager) as Array<keyof DefaultDataControlState & EditorState> + ).reduce((prev, key) => { + return { + ...prev, + [key]: mergedStateManager[key]?.getValue(), + }; + }, {} as DefaultDataControlState & EditorState); + + // open the editor to get the new state + openDataControlEditor<DefaultDataControlState & EditorState>({ + services, + onSave: ({ type: newType, state: newState }) => { + if (newType === controlType) { + // apply the changes from the new state via the state manager + (Object.keys(initialState) as Array<keyof DefaultDataControlState & EditorState>).forEach( + (key) => { + if (!isEqual(mergedStateManager[key].getValue(), newState[key])) { + mergedStateManager[key].next(newState[key]); + } + } + ); + } else { + // replace the control with a new one of the updated type + controlGroup.replacePanel(controlId, { panelType: newType, initialState: newState }); + } + }, + initialState: { + ...initialState, + controlType, + controlId, + defaultPanelTitle: defaultPanelTitle.getValue(), + }, + controlGroupApi: controlGroup, + }); + }; + + const filtersReadySubscription = filters$.pipe(skip(1), debounceTime(0)).subscribe(() => { + // Set filtersReady$.next(true); in filters$ subscription instead of setOutputFilter + // to avoid signaling filters ready until after filters have been emitted + // to avoid timing issues + filtersReady$.next(true); + }); + + const api: ControlApiInitialization<DataControlApi> = { + ...defaultControl.api, + panelTitle, + defaultPanelTitle, + dataViews, + field$, + fieldFormatter, + onEdit, + filters$, + isEditingEnabled: () => true, + untilFiltersReady: async () => { + return new Promise((resolve) => { + combineLatest([defaultControl.api.blockingError, filtersReady$]) + .pipe( + first(([blockingError, filtersReady]) => filtersReady || blockingError !== undefined) + ) + .subscribe(() => { + resolve(); + }); + }); + }, + }; + + return { + api, + cleanup: () => { + dataViewIdSubscription.unsubscribe(); + fieldNameSubscription.unsubscribe(); + filtersReadySubscription.unsubscribe(); + }, + comparators: { + ...defaultControl.comparators, + title: [panelTitle, (value: string | undefined) => panelTitle.next(value)], + dataViewId: [dataViewId, (value: string) => dataViewId.next(value)], + fieldName: [fieldName, (value: string) => fieldName.next(value)], + }, + setters: { + onSelectionChange: () => { + filtersReady$.next(false); + }, + setOutputFilter: (newFilter: Filter | undefined) => { + filters$.next(newFilter ? [newFilter] : undefined); + }, + }, + stateManager, + serialize: () => { + return { + rawState: { + ...defaultControl.serialize().rawState, + dataViewId: dataViewId.getValue(), + fieldName: fieldName.getValue(), + title: panelTitle.getValue(), + }, + references: [ + { + name: `controlGroup_${controlId}:${controlType}DataView`, + type: DATA_VIEW_SAVED_OBJECT_TYPE, + id: dataViewId.getValue(), + }, + ], + }; + }, + }; +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/mocks/api_mocks.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/mocks/api_mocks.tsx new file mode 100644 index 0000000000000..f2696bd293ae8 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/mocks/api_mocks.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 { BehaviorSubject } from 'rxjs'; + +import { DataViewField } from '@kbn/data-views-plugin/common'; + +import { PublishingSubject } from '@kbn/presentation-publishing'; +import { OptionsListSuggestions } from '../../../../../common/options_list/types'; +import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; +import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching'; +import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting'; +import { OptionsListDisplaySettings } from '../options_list_control/types'; + +export const getOptionsListMocks = () => { + const selectedOptions$ = new BehaviorSubject<OptionsListSelection[] | undefined>(undefined); + const exclude$ = new BehaviorSubject<boolean | undefined>(undefined); + const existsSelected$ = new BehaviorSubject<boolean | undefined>(undefined); + return { + api: { + uuid: 'testControl', + field$: new BehaviorSubject<DataViewField | undefined>({ type: 'string' } as DataViewField), + availableOptions$: new BehaviorSubject<OptionsListSuggestions | undefined>(undefined), + invalidSelections$: new BehaviorSubject<Set<OptionsListSelection>>(new Set([])), + totalCardinality$: new BehaviorSubject<number | undefined>(undefined), + dataLoading: new BehaviorSubject<boolean>(false), + parentApi: { + allowExpensiveQueries$: new BehaviorSubject<boolean>(true), + }, + fieldFormatter: new BehaviorSubject((value: string | number) => String(value)), + makeSelection: jest.fn(), + setExclude: (next: boolean | undefined) => exclude$.next(next), + }, + stateManager: { + searchString: new BehaviorSubject<string>(''), + searchStringValid: new BehaviorSubject<boolean>(true), + fieldName: new BehaviorSubject<string>('field'), + exclude: exclude$ as PublishingSubject<boolean | undefined>, + existsSelected: existsSelected$ as PublishingSubject<boolean | undefined>, + sort: new BehaviorSubject<OptionsListSortingType | undefined>(undefined), + selectedOptions: selectedOptions$ as PublishingSubject<OptionsListSelection[] | undefined>, + searchTechnique: new BehaviorSubject<OptionsListSearchTechnique | undefined>(undefined), + }, + displaySettings: {} as OptionsListDisplaySettings, + // setSelectedOptions and setExistsSelected are not exposed via API because + // they are not used by components + // they are needed in tests however so expose them as top level keys + setSelectedOptions: (next: OptionsListSelection[] | undefined) => selectedOptions$.next(next), + setExistsSelected: (next: boolean | undefined) => existsSelected$.next(next), + }; +}; diff --git a/examples/controls_example/public/react_controls/data_controls/mocks/data_control_mocks.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/mocks/factory_mocks.tsx similarity index 100% rename from examples/controls_example/public/react_controls/data_controls/mocks/data_control_mocks.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/mocks/factory_mocks.tsx diff --git a/examples/controls_example/public/react_controls/data_controls/open_data_control_editor.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/open_data_control_editor.tsx similarity index 96% rename from examples/controls_example/public/react_controls/data_controls/open_data_control_editor.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/open_data_control_editor.tsx index 3c16183b18434..fbddf0e7af831 100644 --- a/examples/controls_example/public/react_controls/data_controls/open_data_control_editor.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/open_data_control_editor.tsx @@ -16,11 +16,11 @@ import { tracksOverlays } from '@kbn/presentation-containers'; import { apiHasParentApi } from '@kbn/presentation-publishing'; import { toMountPoint } from '@kbn/react-kibana-mount'; -import { ControlGroupApi } from '../control_group/types'; +import { ControlGroupApi } from '../../control_group/types'; import { DataControlEditor } from './data_control_editor'; import { DefaultDataControlState } from './types'; -export type DataControlEditorState = Omit<DefaultDataControlState, 'fieldName'> & { +export type DataControlEditorState = Partial<DefaultDataControlState> & { fieldName?: string; controlType?: string; controlId?: string; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list.scss b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list.scss new file mode 100644 index 0000000000000..029edd5a8a363 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list.scss @@ -0,0 +1,90 @@ +.optionsList__inputButtonOverride { + max-inline-size: 100% !important; + + .euiButtonEmpty { + border-end-start-radius: 0 !important; + border-start-start-radius: 0 !important; + } +} + +.optionsList--filterBtn { + font-weight: $euiFontWeightRegular !important; + color: $euiTextSubduedColor !important; + + .optionsList--selectionText { + flex-grow: 1; + text-align: left; + } + + .optionsList__selections { + overflow: hidden !important; + } + + .optionsList__filter { + color: $euiTextColor; + font-weight: $euiFontWeightMedium; + } + + .optionsList__filterInvalid { + color: $euiColorWarningText; + } + + .optionsList__negateLabel { + font-weight: $euiFontWeightSemiBold; + font-size: $euiSizeM; + color: $euiColorDanger; + } +} + +.optionsList--sortPopover { + width: $euiSizeXL * 7; +} + +.optionsList__existsFilter { + font-style: italic; + font-weight: $euiFontWeightMedium; +} + +.optionsList__popover { + .optionsList__actions { + padding: 0 $euiSizeS; + border-bottom: $euiBorderThin; + border-color: darken($euiColorLightestShade, 2%); + + .optionsList__searchRow { + padding-top: $euiSizeS + } + + .optionsList__actionsRow { + margin: calc($euiSizeS / 2) 0 !important; + + .optionsList__actionBarDivider { + height: $euiSize; + border-right: $euiBorderThin; + } + } + } + + .optionsList-control-ignored-selection-title { + padding-left: $euiSizeM; + } + + .optionsList__selectionInvalid { + color: $euiColorWarningText; + } + + .optionslist--loadingMoreGroupLabel { + text-align: center; + padding: $euiSizeM; + font-style: italic; + height: $euiSizeXXL !important; + } + + .optionslist--endOfOptionsGroupLabel { + text-align: center; + font-size: $euiSizeM; + height: auto !important; + color: $euiTextSubduedColor; + padding: $euiSizeM; + } +} diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_control.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_control.test.tsx new file mode 100644 index 0000000000000..9cdf976150da7 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_control.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 and the Server Side Public License, v 1; you may not 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 { DataViewField } from '@kbn/data-views-plugin/common'; +import { render } from '@testing-library/react'; +import { getOptionsListMocks } from '../../mocks/api_mocks'; +import { ContextStateManager, OptionsListControlContext } from '../options_list_context_provider'; +import { OptionsListComponentApi } from '../types'; +import { OptionsListControl } from './options_list_control'; + +describe('Options list control', () => { + const mountComponent = ({ + api, + displaySettings, + stateManager, + }: { + api: any; + displaySettings: any; + stateManager: any; + }) => { + return render( + <OptionsListControlContext.Provider + value={{ + api: api as unknown as OptionsListComponentApi, + displaySettings, + stateManager: stateManager as unknown as ContextStateManager, + }} + > + <OptionsListControl controlPanelClassName="controlPanel" /> + </OptionsListControlContext.Provider> + ); + }; + + test('if exclude = false and existsSelected = true, then the option should read "Exists"', async () => { + const mocks = getOptionsListMocks(); + mocks.api.uuid = 'testExists'; + mocks.api.setExclude(false); + mocks.setExistsSelected(true); + const control = mountComponent(mocks); + const existsOption = control.getByTestId('optionsList-control-testExists'); + expect(existsOption).toHaveTextContent('Exists'); + }); + + test('if exclude = true and existsSelected = true, then the option should read "Does not exist"', async () => { + const mocks = getOptionsListMocks(); + mocks.api.uuid = 'testDoesNotExist'; + mocks.api.setExclude(true); + mocks.setExistsSelected(true); + const control = mountComponent(mocks); + const existsOption = control.getByTestId('optionsList-control-testDoesNotExist'); + expect(existsOption).toHaveTextContent('DOES NOT Exist'); + }); + + describe('renders proper delimiter', () => { + test('keyword field', async () => { + const mocks = getOptionsListMocks(); + mocks.api.uuid = 'testDelimiter'; + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 10 }, + { value: 'meow', docCount: 12 }, + ]); + mocks.setSelectedOptions(['woof', 'bark']); + mocks.api.field$.next({ + name: 'Test keyword field', + type: 'keyword', + } as DataViewField); + const control = mountComponent(mocks); + const selections = control.getByTestId('optionsListSelections'); + expect(selections.textContent).toBe('woof, bark '); + }); + }); + + test('number field', async () => { + const mocks = getOptionsListMocks(); + mocks.api.uuid = 'testDelimiter'; + mocks.api.availableOptions$.next([ + { value: 1, docCount: 5 }, + { value: 2, docCount: 10 }, + { value: 3, docCount: 12 }, + ]); + mocks.setSelectedOptions([1, 2]); + mocks.api.field$.next({ + name: 'Test keyword field', + type: 'number', + } as DataViewField); + const control = mountComponent(mocks); + const selections = control.getByTestId('optionsListSelections'); + expect(selections.textContent).toBe('1; 2 '); + }); + + test('should display invalid state', async () => { + const mocks = getOptionsListMocks(); + mocks.api.uuid = 'testInvalid'; + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 10 }, + { value: 'meow', docCount: 12 }, + ]); + mocks.setSelectedOptions(['woof', 'bark']); + mocks.api.invalidSelections$.next(new Set(['woof'])); + mocks.api.field$.next({ + name: 'Test keyword field', + type: 'number', + } as DataViewField); + + const control = mountComponent(mocks); + expect( + control.queryByTestId('optionsList__invalidSelectionsToken-testInvalid') + ).toBeInTheDocument(); + }); +}); diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_control.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_control.tsx new file mode 100644 index 0000000000000..5411b6122ac25 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_control.tsx @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isEmpty } from 'lodash'; +import React, { useMemo, useState } from 'react'; + +import { + EuiFilterButton, + EuiFilterGroup, + EuiFlexGroup, + EuiFlexItem, + EuiInputPopover, + EuiToken, + EuiToolTip, + htmlIdGenerator, +} from '@elastic/eui'; +import { + useBatchedOptionalPublishingSubjects, + useBatchedPublishingSubjects, +} from '@kbn/presentation-publishing'; + +import { OptionsListSelection } from '../../../../../../common/options_list/options_list_selections'; +import { MIN_POPOVER_WIDTH } from '../../../constants'; +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListPopover } from './options_list_popover'; +import { OptionsListStrings } from '../options_list_strings'; + +import './options_list.scss'; + +export const OptionsListControl = ({ + controlPanelClassName, +}: { + controlPanelClassName: string; +}) => { + const popoverId = useMemo(() => htmlIdGenerator()(), []); + const { api, stateManager, displaySettings } = useOptionsListContext(); + + const [isPopoverOpen, setPopoverOpen] = useState<boolean>(false); + const [ + excludeSelected, + existsSelected, + selectedOptions, + invalidSelections, + field, + loading, + panelTitle, + fieldFormatter, + ] = useBatchedPublishingSubjects( + stateManager.exclude, + stateManager.existsSelected, + stateManager.selectedOptions, + api.invalidSelections$, + api.field$, + api.dataLoading, + api.panelTitle, + api.fieldFormatter + ); + + const [defaultPanelTitle] = useBatchedOptionalPublishingSubjects(api.defaultPanelTitle); + + const delimiter = useMemo(() => OptionsListStrings.control.getSeparator(field?.type), [field]); + + const { hasSelections, selectionDisplayNode, selectedOptionsCount } = useMemo(() => { + return { + hasSelections: !isEmpty(selectedOptions), + selectedOptionsCount: selectedOptions?.length, + selectionDisplayNode: ( + <EuiFlexGroup alignItems="center" responsive={false} gutterSize="xs"> + <EuiFlexItem className="optionsList__selections" data-test-subj="optionsListSelections"> + <div className="eui-textTruncate"> + {excludeSelected && ( + <> + <span className="optionsList__negateLabel"> + {existsSelected + ? OptionsListStrings.control.getExcludeExists() + : OptionsListStrings.control.getNegate()} + </span>{' '} + </> + )} + {existsSelected ? ( + <span className={`optionsList__existsFilter`}> + {OptionsListStrings.controlAndPopover.getExists(+Boolean(excludeSelected))} + </span> + ) : ( + <> + {selectedOptions?.length + ? selectedOptions.map((value: OptionsListSelection, i, { length }) => { + const text = `${fieldFormatter(value)}${ + i + 1 === length ? '' : delimiter + } `; + const isInvalid = invalidSelections?.has(value); + return ( + <span + key={value} + className={`optionsList__filter ${ + isInvalid ? 'optionsList__filterInvalid' : '' + }`} + > + {text} + </span> + ); + }) + : null} + </> + )} + </div> + </EuiFlexItem> + {invalidSelections && invalidSelections.size > 0 && ( + <EuiFlexItem grow={false}> + <EuiToolTip + position="top" + content={OptionsListStrings.control.getInvalidSelectionWarningLabel( + invalidSelections.size + )} + delay="long" + > + <EuiToken + tabIndex={0} + iconType="alert" + size="s" + color="euiColorVis5" + shape="square" + fill="dark" + title={OptionsListStrings.control.getInvalidSelectionWarningLabel( + invalidSelections.size + )} + data-test-subj={`optionsList__invalidSelectionsToken-${api.uuid}`} + css={{ verticalAlign: 'text-bottom' }} // Align with the notification badge + /> + </EuiToolTip> + </EuiFlexItem> + )} + </EuiFlexGroup> + ), + }; + }, [ + selectedOptions, + excludeSelected, + existsSelected, + fieldFormatter, + delimiter, + invalidSelections, + api.uuid, + ]); + + const button = ( + <> + <EuiFilterButton + badgeColor="success" + iconType={loading ? 'empty' : 'arrowDown'} + className={'optionsList--filterBtn'} + data-test-subj={`optionsList-control-${api.uuid}`} + onClick={() => setPopoverOpen(!isPopoverOpen)} + isSelected={isPopoverOpen} + numActiveFilters={selectedOptionsCount} + hasActiveFilters={Boolean(selectedOptionsCount)} + textProps={{ className: 'optionsList--selectionText' }} + aria-label={panelTitle ?? defaultPanelTitle} + aria-expanded={isPopoverOpen} + aria-controls={popoverId} + role="combobox" + > + {hasSelections || existsSelected + ? selectionDisplayNode + : displaySettings.placeholder ?? OptionsListStrings.control.getPlaceholder()} + </EuiFilterButton> + </> + ); + + return ( + <EuiFilterGroup fullWidth className={controlPanelClassName}> + <EuiInputPopover + id={popoverId} + ownFocus + input={button} + hasArrow={false} + repositionOnScroll + isOpen={isPopoverOpen} + panelPaddingSize="none" + panelMinWidth={MIN_POPOVER_WIDTH} + className="optionsList__inputButtonOverride" + initialFocus={'[data-test-subj=optionsList-control-search-input]'} + closePopover={() => setPopoverOpen(false)} + panelClassName="optionsList__popoverOverride" + panelProps={{ + title: panelTitle ?? defaultPanelTitle, + 'aria-label': OptionsListStrings.popover.getAriaLabel(panelTitle ?? defaultPanelTitle!), + }} + > + <OptionsListPopover /> + </EuiInputPopover> + </EuiFilterGroup> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_editor_options.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_editor_options.test.tsx new file mode 100644 index 0000000000000..f463400b351c8 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_editor_options.test.tsx @@ -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 and the Server Side Public License, v 1; you may not 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 { DataViewField } from '@kbn/data-views-plugin/common'; +import { act, render } from '@testing-library/react'; + +import { getMockedControlGroupApi } from '../../../mocks/control_mocks'; +import { CustomOptionsComponentProps, DefaultDataControlState } from '../../types'; +import { OptionsListControlState } from '../types'; +import { OptionsListEditorOptions } from './options_list_editor_options'; +import { ControlGroupApi } from '../../../../control_group/types'; +import { BehaviorSubject } from 'rxjs'; + +describe('Options list sorting button', () => { + const getMockedState = <State extends DefaultDataControlState = DefaultDataControlState>( + overwrite?: Partial<OptionsListControlState> + ): State => { + return { + dataViewId: 'testDataViewId', + fieldName: 'fieldName', + ...overwrite, + } as State; + }; + + const updateState = jest.fn(); + const mountComponent = ({ + initialState, + field, + parentApi = getMockedControlGroupApi(), + }: Pick<CustomOptionsComponentProps, 'initialState' | 'field'> & { + parentApi?: ControlGroupApi; + }) => { + const component = render( + <OptionsListEditorOptions + initialState={initialState} + field={field} + updateState={updateState} + setControlEditorValid={jest.fn()} + parentApi={parentApi} + /> + ); + return component; + }; + + test('run past timeout', () => { + const component = mountComponent({ + initialState: getMockedState({ runPastTimeout: false }), + field: { type: 'string' } as DataViewField, + }); + const toggle = component.getByTestId('optionsListControl__runPastTimeoutAdditionalSetting'); + expect(toggle.getAttribute('aria-checked')).toBe('false'); + userEvent.click(toggle); + expect(updateState).toBeCalledWith({ runPastTimeout: true }); + expect(toggle.getAttribute('aria-checked')).toBe('true'); + }); + + test('selection options', () => { + const component = mountComponent({ + initialState: getMockedState({ singleSelect: true }), + field: { type: 'string' } as DataViewField, + }); + + const multiSelect = component.container.querySelector('input#multi'); + expect(multiSelect).not.toBeChecked(); + expect(component.container.querySelector('input#single')).toBeChecked(); + + userEvent.click(multiSelect!); + expect(updateState).toBeCalledWith({ singleSelect: false }); + expect(multiSelect).toBeChecked(); + expect(component.container.querySelector('input#single')).not.toBeChecked(); + }); + + describe('custom search options', () => { + test('do not show custom search options when `allowExpensiveQueries` is false', async () => { + const allowExpensiveQueries$ = new BehaviorSubject<boolean>(false); + const controlGroupApi = getMockedControlGroupApi(undefined, { allowExpensiveQueries$ }); + const component = mountComponent({ + initialState: getMockedState(), + field: { type: 'string' } as DataViewField, + parentApi: controlGroupApi, + }); + expect( + component.queryByTestId('optionsListControl__searchOptionsRadioGroup') + ).not.toBeInTheDocument(); + + act(() => allowExpensiveQueries$.next(true)); + expect( + component.queryByTestId('optionsListControl__searchOptionsRadioGroup') + ).toBeInTheDocument(); + }); + + test('string field has three custom search options', async () => { + const component = mountComponent({ + initialState: getMockedState(), + field: { type: 'string' } as DataViewField, + }); + expect( + component.queryByTestId('optionsListControl__searchOptionsRadioGroup') + ).toBeInTheDocument(); + const validTechniques = ['prefix', 'exact', 'wildcard']; + validTechniques.forEach((technique) => { + expect( + component.queryByTestId(`optionsListControl__${technique}SearchOptionAdditionalSetting`) + ).toBeInTheDocument(); + }); + }); + + test('IP field has two custom search options', async () => { + const component = mountComponent({ + initialState: getMockedState(), + field: { type: 'ip' } as DataViewField, + }); + expect( + component.queryByTestId('optionsListControl__searchOptionsRadioGroup') + ).toBeInTheDocument(); + const validTechniques = ['prefix', 'exact']; + validTechniques.forEach((technique) => { + expect( + component.queryByTestId(`optionsListControl__${technique}SearchOptionAdditionalSetting`) + ).toBeInTheDocument(); + }); + }); + + test('number field does not have custom search options', async () => { + const component = mountComponent({ + initialState: getMockedState(), + field: { type: 'number' } as DataViewField, + }); + expect( + component.queryByTestId('optionsListControl__searchOptionsRadioGroup') + ).not.toBeInTheDocument(); + }); + + test('date field does not have custom search options', async () => { + const component = mountComponent({ + initialState: getMockedState(), + field: { type: 'date' } as DataViewField, + }); + expect( + component.queryByTestId('optionsListControl__searchOptionsRadioGroup') + ).not.toBeInTheDocument(); + }); + + describe('responds to field type changing', () => { + test('reset back to initial state when valid', async () => { + const initialState = getMockedState({ searchTechnique: 'exact' }); + const parentApi = getMockedControlGroupApi(); + const component = render( + <OptionsListEditorOptions + initialState={initialState} + field={{ type: 'string' } as DataViewField} + updateState={updateState} + setControlEditorValid={jest.fn()} + parentApi={parentApi} + /> + ); + + /** loads initial state properly */ + expect(component.container.querySelector('input#prefix')).not.toBeChecked(); + expect(component.container.querySelector('input#exact')).toBeChecked(); + expect(component.container.querySelector('input#wildcard')).not.toBeChecked(); + + /** responds to the field type changing */ + component.rerender( + <OptionsListEditorOptions + initialState={initialState} + field={{ type: 'ip' } as DataViewField} // initial search technique IS valid + updateState={jest.fn()} + setControlEditorValid={jest.fn()} + parentApi={parentApi} + /> + ); + + expect(updateState).toBeCalledWith({ searchTechnique: 'exact' }); + expect(component.container.querySelector('input#prefix')).not.toBeChecked(); + expect(component.container.querySelector('input#exact')).toBeChecked(); + expect(component.container.querySelector('input#wildcard')).toBeNull(); + }); + + test('if the current selection is valid, send that to the parent editor state', async () => { + const initialState = getMockedState(); + const parentApi = getMockedControlGroupApi(); + const component = render( + <OptionsListEditorOptions + initialState={initialState} + field={{ type: 'string' } as DataViewField} + updateState={updateState} + setControlEditorValid={jest.fn()} + parentApi={parentApi} + /> + ); + + /** loads default compatible search technique properly */ + expect(component.container.querySelector('input#prefix')).toBeChecked(); + expect(component.container.querySelector('input#exact')).not.toBeChecked(); + expect(component.container.querySelector('input#wildcard')).not.toBeChecked(); + + /** responds to change in search technique */ + const exactSearch = component.container.querySelector('input#exact'); + userEvent.click(exactSearch!); + expect(updateState).toBeCalledWith({ searchTechnique: 'exact' }); + expect(component.container.querySelector('input#prefix')).not.toBeChecked(); + expect(exactSearch).toBeChecked(); + expect(component.container.querySelector('input#wildcard')).not.toBeChecked(); + + /** responds to the field type changing */ + component.rerender( + <OptionsListEditorOptions + initialState={initialState} + field={{ type: 'number' } as DataViewField} // current selected search technique IS valid, initial state is not + updateState={jest.fn()} + setControlEditorValid={jest.fn()} + parentApi={parentApi} + /> + ); + + expect(updateState).toBeCalledWith({ searchTechnique: 'exact' }); + }); + + test('if neither the initial or current search technique is valid, revert to the default', async () => { + const initialState = getMockedState({ searchTechnique: 'wildcard' }); + const parentApi = getMockedControlGroupApi(); + const component = render( + <OptionsListEditorOptions + initialState={initialState} + field={{ type: 'string' } as DataViewField} + updateState={updateState} + setControlEditorValid={jest.fn()} + parentApi={parentApi} + /> + ); + + /** responds to change in search technique */ + const prefixSearch = component.container.querySelector('input#prefix'); + userEvent.click(prefixSearch!); + expect(updateState).toBeCalledWith({ searchTechnique: 'prefix' }); + + /** responds to the field type changing */ + component.rerender( + <OptionsListEditorOptions + initialState={initialState} + field={{ type: 'number' } as DataViewField} // neither initial nor current search technique is valid + updateState={jest.fn()} + setControlEditorValid={jest.fn()} + parentApi={parentApi} + /> + ); + + expect(updateState).toBeCalledWith({ searchTechnique: 'exact' }); + }); + }); + }); +}); diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_editor_options.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_editor_options.tsx new file mode 100644 index 0000000000000..e749973c99f74 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_editor_options.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, { useEffect, useMemo, useState } from 'react'; + +import { EuiFormRow, EuiRadioGroup, EuiSwitch } from '@elastic/eui'; +import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; + +import { + getCompatibleSearchTechniques, + OptionsListSearchTechnique, +} from '../../../../../../common/options_list/suggestions_searching'; +import { ControlSettingTooltipLabel } from '../../../../control_group/components/control_setting_tooltip_label'; +import { CustomOptionsComponentProps } from '../../types'; +import { DEFAULT_SEARCH_TECHNIQUE } from '../constants'; +import { OptionsListControlState } from '../types'; +import { OptionsListStrings } from '../options_list_strings'; + +const selectionOptions = [ + { + id: 'multi', + label: OptionsListStrings.editor.selectionTypes.multi.getLabel(), + 'data-test-subj': 'optionsListControl__multiSearchOptionAdditionalSetting', + }, + { + id: 'single', + label: OptionsListStrings.editor.selectionTypes.single.getLabel(), + 'data-test-subj': 'optionsListControl__singleSearchOptionAdditionalSetting', + }, +]; + +const allSearchOptions = [ + { + id: 'prefix', + label: ( + <ControlSettingTooltipLabel + label={OptionsListStrings.editor.searchTypes.prefix.getLabel()} + tooltip={OptionsListStrings.editor.searchTypes.prefix.getTooltip()} + /> + ), + 'data-test-subj': 'optionsListControl__prefixSearchOptionAdditionalSetting', + }, + { + id: 'wildcard', + label: ( + <ControlSettingTooltipLabel + label={OptionsListStrings.editor.searchTypes.wildcard.getLabel()} + tooltip={OptionsListStrings.editor.searchTypes.wildcard.getTooltip()} + /> + ), + 'data-test-subj': 'optionsListControl__wildcardSearchOptionAdditionalSetting', + }, + { + id: 'exact', + label: ( + <ControlSettingTooltipLabel + label={OptionsListStrings.editor.searchTypes.exact.getLabel()} + tooltip={OptionsListStrings.editor.searchTypes.exact.getTooltip()} + /> + ), + 'data-test-subj': 'optionsListControl__exactSearchOptionAdditionalSetting', + }, +]; + +export const OptionsListEditorOptions = ({ + initialState, + field, + updateState, + parentApi, +}: CustomOptionsComponentProps<OptionsListControlState>) => { + const allowExpensiveQueries = useStateFromPublishingSubject(parentApi.allowExpensiveQueries$); + + const [singleSelect, setSingleSelect] = useState<boolean>(initialState.singleSelect ?? false); + const [runPastTimeout, setRunPastTimeout] = useState<boolean>( + initialState.runPastTimeout ?? false + ); + const [searchTechnique, setSearchTechnique] = useState<OptionsListSearchTechnique>( + initialState.searchTechnique ?? DEFAULT_SEARCH_TECHNIQUE + ); + + const compatibleSearchTechniques = useMemo( + () => getCompatibleSearchTechniques(field.type), + [field.type] + ); + + const searchOptions = useMemo(() => { + return allSearchOptions.filter((searchOption) => { + return compatibleSearchTechniques.includes(searchOption.id as OptionsListSearchTechnique); + }); + }, [compatibleSearchTechniques]); + + useEffect(() => { + /** + * when field type changes, ensure that the selected search technique is still valid; + * if the selected search technique **isn't** valid, reset it to the default + */ + const initialSearchTechniqueValid = + initialState.searchTechnique && + compatibleSearchTechniques.includes(initialState.searchTechnique); + const currentSearchTechniqueValid = compatibleSearchTechniques.includes(searchTechnique); + + if (initialSearchTechniqueValid) { + // reset back to initial state if possible on field change + setSearchTechnique(initialState.searchTechnique!); + updateState({ searchTechnique: initialState.searchTechnique }); + } else if (currentSearchTechniqueValid) { + // otherwise, if the current selection is valid, send that to the parent editor state + updateState({ searchTechnique }); + } else { + // finally, if neither the initial or current search technique is valid, revert to the default + setSearchTechnique(compatibleSearchTechniques[0]); + updateState({ searchTechnique: compatibleSearchTechniques[0] }); + } + + // Note: We only want to call this when compatible search techniques changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [compatibleSearchTechniques]); + + return ( + <> + <EuiFormRow + label={OptionsListStrings.editor.getSelectionOptionsTitle()} + data-test-subj="optionsListControl__selectionOptionsRadioGroup" + > + <EuiRadioGroup + options={selectionOptions} + idSelected={singleSelect ? 'single' : 'multi'} + onChange={(id) => { + const newSingleSelect = id === 'single'; + setSingleSelect(newSingleSelect); + updateState({ singleSelect: newSingleSelect }); + }} + /> + </EuiFormRow> + {allowExpensiveQueries && compatibleSearchTechniques.length > 1 && ( + <EuiFormRow + label={OptionsListStrings.editor.getSearchOptionsTitle()} + data-test-subj="optionsListControl__searchOptionsRadioGroup" + > + <EuiRadioGroup + options={searchOptions} + idSelected={searchTechnique} + onChange={(id) => { + const newSearchTechnique = id as OptionsListSearchTechnique; + setSearchTechnique(newSearchTechnique); + updateState({ searchTechnique: newSearchTechnique }); + }} + /> + </EuiFormRow> + )} + <EuiFormRow label={OptionsListStrings.editor.getAdditionalSettingsTitle()}> + <EuiSwitch + label={ + <ControlSettingTooltipLabel + label={OptionsListStrings.editor.getRunPastTimeoutTitle()} + tooltip={OptionsListStrings.editor.getRunPastTimeoutTooltip()} + /> + } + checked={runPastTimeout} + onChange={() => { + const newRunPastTimeout = !runPastTimeout; + setRunPastTimeout(newRunPastTimeout); + updateState({ runPastTimeout: newRunPastTimeout }); + }} + data-test-subj={'optionsListControl__runPastTimeoutAdditionalSetting'} + /> + </EuiFormRow> + </> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover.test.tsx new file mode 100644 index 0000000000000..5e22b22077e52 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover.test.tsx @@ -0,0 +1,360 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { DataViewField } from '@kbn/data-views-plugin/common'; +import { act, render, RenderResult, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { BehaviorSubject } from 'rxjs'; +import { getOptionsListMocks } from '../../mocks/api_mocks'; +import { ContextStateManager, OptionsListControlContext } from '../options_list_context_provider'; +import { OptionsListComponentApi, OptionsListDisplaySettings } from '../types'; +import { OptionsListPopover } from './options_list_popover'; + +describe('Options list popover', () => { + const waitOneTick = () => act(() => new Promise((resolve) => setTimeout(resolve, 0))); + + const mountComponent = ({ + api, + displaySettings, + stateManager, + }: { + api: any; + displaySettings: any; + stateManager: any; + }) => { + return render( + <OptionsListControlContext.Provider + value={{ + api: api as unknown as OptionsListComponentApi, + displaySettings, + stateManager: stateManager as unknown as ContextStateManager, + }} + > + <OptionsListPopover /> + </OptionsListControlContext.Provider> + ); + }; + + const clickShowOnlySelections = (popover: RenderResult) => { + const showOnlySelectedButton = popover.getByTestId('optionsList-control-show-only-selected'); + userEvent.click(showOnlySelectedButton); + }; + + test('no available options', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([]); + const popover = mountComponent(mocks); + + const availableOptionsDiv = popover.getByTestId('optionsList-control-available-options'); + const noOptionsDiv = within(availableOptionsDiv).getByTestId( + 'optionsList-control-noSelectionsMessage' + ); + expect(noOptionsDiv).toBeInTheDocument(); + }); + + test('clicking options calls `makeSelection`', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 10 }, + { value: 'meow', docCount: 12 }, + ]); + const popover = mountComponent(mocks); + + const existsOption = popover.getByTestId('optionsList-control-selection-exists'); + userEvent.click(existsOption); + expect(mocks.api.makeSelection).toBeCalledWith('exists-option', false); + + let woofOption = popover.getByTestId('optionsList-control-selection-woof'); + userEvent.click(woofOption); + expect(mocks.api.makeSelection).toBeCalledWith('woof', false); + + // simulate `makeSelection` + mocks.setSelectedOptions(['woof']); + await waitOneTick(); + + clickShowOnlySelections(popover); + woofOption = popover.getByTestId('optionsList-control-selection-woof'); + userEvent.click(woofOption); + expect(mocks.api.makeSelection).toBeCalledWith('woof', true); + }); + + describe('show only selected', () => { + test('show only selected options', async () => { + const mocks = getOptionsListMocks(); + const selections = ['woof', 'bark']; + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 10 }, + { value: 'meow', docCount: 12 }, + ]); + const popover = mountComponent(mocks); + mocks.setSelectedOptions(selections); + await waitOneTick(); + + clickShowOnlySelections(popover); + const availableOptionsDiv = popover.getByTestId('optionsList-control-available-options'); + const availableOptionsList = within(availableOptionsDiv).getByRole('listbox'); + const availableOptions = within(availableOptionsList).getAllByRole('option'); + availableOptions.forEach((child, i) => { + expect(child).toHaveTextContent(`${selections[i]}. Checked option.`); + }); + }); + + test('display error message when the show only selected toggle is true but there are no selections', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 10 }, + { value: 'meow', docCount: 12 }, + ]); + mocks.setSelectedOptions([]); + const popover = mountComponent(mocks); + + clickShowOnlySelections(popover); + const availableOptionsDiv = popover.getByTestId('optionsList-control-available-options'); + const noSelectionsDiv = within(availableOptionsDiv).getByTestId( + 'optionsList-control-selectionsEmptyMessage' + ); + expect(noSelectionsDiv).toBeInTheDocument(); + }); + + test('disable search and sort when show only selected toggle is true', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 10 }, + { value: 'meow', docCount: 12 }, + ]); + mocks.setSelectedOptions(['woof', 'bark']); + const popover = mountComponent(mocks); + + let searchBox = popover.getByTestId('optionsList-control-search-input'); + let sortButton = popover.getByTestId('optionsListControl__sortingOptionsButton'); + expect(searchBox).not.toBeDisabled(); + expect(sortButton).not.toBeDisabled(); + + clickShowOnlySelections(popover); + searchBox = popover.getByTestId('optionsList-control-search-input'); + sortButton = popover.getByTestId('optionsListControl__sortingOptionsButton'); + expect(searchBox).toBeDisabled(); + expect(sortButton).toBeDisabled(); + }); + }); + + describe('invalid selections', () => { + test('test single invalid selection', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 75 }, + ]); + const popover = mountComponent(mocks); + mocks.setSelectedOptions(['woof', 'bark']); + mocks.api.invalidSelections$.next(new Set(['woof'])); + await waitOneTick(); + + const validSelection = popover.getByTestId('optionsList-control-selection-bark'); + expect(validSelection).toHaveTextContent('bark. Checked option.'); + expect( + within(validSelection).getByTestId('optionsList-document-count-badge') + ).toHaveTextContent('75'); + const title = popover.getByTestId('optionList__invalidSelectionLabel'); + expect(title).toHaveTextContent('Invalid selection'); + const invalidSelection = popover.getByTestId('optionsList-control-invalid-selection-woof'); + expect(invalidSelection).toHaveTextContent('woof. Checked option.'); + expect(invalidSelection).toHaveClass('optionsList__selectionInvalid'); + }); + + test('test title when multiple invalid selections', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 75 }, + ]); + mocks.setSelectedOptions(['bark', 'woof', 'meow']); + mocks.api.invalidSelections$.next(new Set(['woof', 'meow'])); + const popover = mountComponent(mocks); + + const title = popover.getByTestId('optionList__invalidSelectionLabel'); + expect(title).toHaveTextContent('Invalid selections'); + }); + }); + + describe('include/exclude toggle', () => { + test('should default to exclude = false', async () => { + const mocks = getOptionsListMocks(); + const popover = mountComponent(mocks); + const includeButton = popover.getByTestId('optionsList__includeResults'); + const excludeButton = popover.getByTestId('optionsList__excludeResults'); + expect(includeButton).toHaveAttribute('aria-pressed', 'true'); + expect(excludeButton).toHaveAttribute('aria-pressed', 'false'); + }); + + test('if exclude = true, select appropriate button in button group', async () => { + const mocks = getOptionsListMocks(); + const popover = mountComponent(mocks); + mocks.api.setExclude(true); + await waitOneTick(); + + const includeButton = popover.getByTestId('optionsList__includeResults'); + const excludeButton = popover.getByTestId('optionsList__excludeResults'); + expect(includeButton).toHaveAttribute('aria-pressed', 'false'); + expect(excludeButton).toHaveAttribute('aria-pressed', 'true'); + }); + }); + + describe('"Exists" option', () => { + test('if existsSelected = false and no suggestions, then "Exists" does not show up', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([]); + const popover = mountComponent(mocks); + + mocks.setExistsSelected(false); + await waitOneTick(); + + const existsOption = popover.queryByTestId('optionsList-control-selection-exists'); + expect(existsOption).toBeNull(); + }); + + test('if existsSelected = true, "Exists" is the only option when "Show only selected options" is toggled', async () => { + const mocks = getOptionsListMocks(); + mocks.api.availableOptions$.next([ + { value: 'woof', docCount: 5 }, + { value: 'bark', docCount: 75 }, + ]); + const popover = mountComponent(mocks); + + mocks.setExistsSelected(true); + await waitOneTick(); + clickShowOnlySelections(popover); + + const availableOptionsDiv = popover.getByTestId('optionsList-control-available-options'); + const availableOptionsList = within(availableOptionsDiv).getByRole('listbox'); + const availableOptions = within(availableOptionsList).getAllByRole('option'); + expect(availableOptions[0]).toHaveTextContent('Exists. Checked option.'); + }); + }); + + describe('field formatter', () => { + const mocks = getOptionsListMocks(); + const mockedFormatter = jest + .fn() + .mockImplementation((value: string | number) => `formatted:${value}`); + mocks.api.fieldFormatter = new BehaviorSubject( + mockedFormatter as (value: string | number) => string + ); + + afterEach(() => { + mockedFormatter.mockClear(); + }); + + test('uses field formatter on suggestions', async () => { + mocks.api.availableOptions$.next([ + { value: 1000, docCount: 1 }, + { value: 123456789, docCount: 4 }, + ]); + mocks.api.field$.next({ + name: 'Test number field', + type: 'number', + } as DataViewField); + const popover = mountComponent(mocks); + + expect(mockedFormatter).toHaveBeenNthCalledWith(1, 1000); + expect(mockedFormatter).toHaveBeenNthCalledWith(2, 123456789); + const options = await popover.findAllByRole('option'); + expect(options[0].textContent).toEqual('Exists'); + expect( + options[1].getElementsByClassName('euiSelectableListItem__text')[0].textContent + ).toEqual('formatted:1000'); + expect( + options[2].getElementsByClassName('euiSelectableListItem__text')[0].textContent + ).toEqual('formatted:123456789'); + }); + + test('converts string to number for date field', async () => { + mocks.api.availableOptions$.next([ + { value: 1721283696000, docCount: 1 }, + { value: 1721295533000, docCount: 2 }, + ]); + mocks.api.field$.next({ + name: 'Test date field', + type: 'date', + } as DataViewField); + + mountComponent(mocks); + expect(mockedFormatter).toHaveBeenNthCalledWith(1, 1721283696000); + expect(mockedFormatter).toHaveBeenNthCalledWith(2, 1721295533000); + }); + }); + + describe('allow expensive queries warning', () => { + test('ensure warning icon does not show up when testAllowExpensiveQueries = true/undefined', async () => { + const mocks = getOptionsListMocks(); + mocks.api.field$.next({ + name: 'Test keyword field', + type: 'keyword', + } as DataViewField); + const popover = mountComponent(mocks); + const warning = popover.queryByTestId('optionsList-allow-expensive-queries-warning'); + expect(warning).toBeNull(); + }); + + test('ensure warning icon shows up when testAllowExpensiveQueries = false', async () => { + const mocks = getOptionsListMocks(); + mocks.api.field$.next({ + name: 'Test keyword field', + type: 'keyword', + } as DataViewField); + mocks.api.parentApi.allowExpensiveQueries$.next(false); + const popover = mountComponent(mocks); + const warning = popover.getByTestId('optionsList-allow-expensive-queries-warning'); + expect(warning).toBeInstanceOf(HTMLDivElement); + }); + }); + + describe('advanced settings', () => { + const ensureComponentIsHidden = async ({ + displaySettings, + testSubject, + }: { + displaySettings: Partial<OptionsListDisplaySettings>; + testSubject: string; + }) => { + const mocks = getOptionsListMocks(); + mocks.displaySettings = displaySettings; + const popover = mountComponent(mocks); + const test = popover.queryByTestId(testSubject); + expect(test).toBeNull(); + }; + + test('can hide exists option', async () => { + ensureComponentIsHidden({ + displaySettings: { hideExists: true }, + testSubject: 'optionsList-control-selection-exists', + }); + }); + + test('can hide include/exclude toggle', async () => { + ensureComponentIsHidden({ + displaySettings: { hideExclude: true }, + testSubject: 'optionsList__includeExcludeButtonGroup', + }); + }); + + test('can hide sorting button', async () => { + ensureComponentIsHidden({ + displaySettings: { hideSort: true }, + testSubject: 'optionsListControl__sortingOptionsButton', + }); + }); + }); +}); diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover.tsx new file mode 100644 index 0000000000000..1f6168e5de1f2 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover.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, { useState } from 'react'; + +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import { OptionsListPopoverActionBar } from './options_list_popover_action_bar'; +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListPopoverFooter } from './options_list_popover_footer'; +import { OptionsListPopoverInvalidSelections } from './options_list_popover_invalid_selections'; +import { OptionsListPopoverSuggestions } from './options_list_popover_suggestions'; + +export const OptionsListPopover = () => { + const { api, displaySettings } = useOptionsListContext(); + + const [field, availableOptions, invalidSelections, loading] = useBatchedPublishingSubjects( + api.field$, + api.availableOptions$, + api.invalidSelections$, + api.dataLoading + ); + const [showOnlySelected, setShowOnlySelected] = useState(false); + + return ( + <div + id={`control-popover-${api.uuid}`} + className={'optionsList__popover'} + data-test-subj={`optionsList-control-popover`} + > + {field?.type !== 'boolean' && !displaySettings.hideActionBar && ( + <OptionsListPopoverActionBar + showOnlySelected={showOnlySelected} + setShowOnlySelected={setShowOnlySelected} + /> + )} + <div + data-test-subj={`optionsList-control-available-options`} + data-option-count={loading ? 0 : Object.keys(availableOptions ?? {}).length} + style={{ width: '100%', height: '100%' }} + > + <OptionsListPopoverSuggestions showOnlySelected={showOnlySelected} /> + {!showOnlySelected && invalidSelections && invalidSelections.size !== 0 && ( + <OptionsListPopoverInvalidSelections /> + )} + </div> + {!displaySettings.hideExclude && <OptionsListPopoverFooter />} + </div> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_action_bar.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_action_bar.tsx new file mode 100644 index 0000000000000..503a32933cec8 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_action_bar.tsx @@ -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 React, { useMemo } from 'react'; + +import { + EuiButtonIcon, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; + +import { getCompatibleSearchTechniques } from '../../../../../../common/options_list/suggestions_searching'; +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListPopoverSortingButton } from './options_list_popover_sorting_button'; +import { OptionsListStrings } from '../options_list_strings'; + +interface OptionsListPopoverProps { + showOnlySelected: boolean; + setShowOnlySelected: (value: boolean) => void; +} + +export const OptionsListPopoverActionBar = ({ + showOnlySelected, + setShowOnlySelected, +}: OptionsListPopoverProps) => { + const { api, stateManager, displaySettings } = useOptionsListContext(); + + const [ + searchString, + searchTechnique, + searchStringValid, + invalidSelections, + totalCardinality, + field, + allowExpensiveQueries, + ] = useBatchedPublishingSubjects( + stateManager.searchString, + stateManager.searchTechnique, + stateManager.searchStringValid, + api.invalidSelections$, + api.totalCardinality$, + api.field$, + api.parentApi.allowExpensiveQueries$ + ); + + const compatibleSearchTechniques = useMemo(() => { + if (!field) return []; + return getCompatibleSearchTechniques(field.type); + }, [field]); + + const defaultSearchTechnique = useMemo( + () => searchTechnique ?? compatibleSearchTechniques[0], + [searchTechnique, compatibleSearchTechniques] + ); + + return ( + <div className="optionsList__actions"> + {compatibleSearchTechniques.length > 0 && ( + <EuiFormRow className="optionsList__searchRow" fullWidth> + <EuiFieldSearch + isInvalid={!searchStringValid} + compressed + disabled={showOnlySelected} + fullWidth + onChange={(event) => { + stateManager.searchString.next(event.target.value); + }} + value={searchString} + data-test-subj="optionsList-control-search-input" + placeholder={OptionsListStrings.popover.getSearchPlaceholder( + allowExpensiveQueries ? defaultSearchTechnique : 'exact' + )} + /> + </EuiFormRow> + )} + <EuiFormRow className="optionsList__actionsRow" fullWidth> + <EuiFlexGroup + justifyContent="spaceBetween" + alignItems="center" + gutterSize="s" + responsive={false} + > + {allowExpensiveQueries && ( + <EuiFlexItem grow={false}> + <EuiText size="xs" color="subdued" data-test-subj="optionsList-cardinality-label"> + {OptionsListStrings.popover.getCardinalityLabel(totalCardinality)} + </EuiText> + </EuiFlexItem> + )} + {invalidSelections && invalidSelections.size > 0 && ( + <> + {allowExpensiveQueries && ( + <EuiFlexItem grow={false}> + <div className="optionsList__actionBarDivider" /> + </EuiFlexItem> + )} + <EuiFlexItem grow={false}> + <EuiText size="xs" color="subdued"> + {OptionsListStrings.popover.getInvalidSelectionsLabel(invalidSelections.size)} + </EuiText> + </EuiFlexItem> + </> + )} + <EuiFlexItem grow={true}> + <EuiFlexGroup + gutterSize="xs" + alignItems="center" + justifyContent="flexEnd" + responsive={false} + > + <EuiFlexItem grow={false}> + <EuiToolTip + position="top" + content={ + showOnlySelected + ? OptionsListStrings.popover.getAllOptionsButtonTitle() + : OptionsListStrings.popover.getSelectedOptionsButtonTitle() + } + > + <EuiButtonIcon + size="xs" + iconType="list" + aria-pressed={showOnlySelected} + color={showOnlySelected ? 'primary' : 'text'} + display={showOnlySelected ? 'base' : 'empty'} + onClick={() => setShowOnlySelected(!showOnlySelected)} + data-test-subj="optionsList-control-show-only-selected" + aria-label={ + showOnlySelected + ? OptionsListStrings.popover.getAllOptionsButtonTitle() + : OptionsListStrings.popover.getSelectedOptionsButtonTitle() + } + /> + </EuiToolTip> + </EuiFlexItem> + {!displaySettings.hideSort && ( + <EuiFlexItem grow={false}> + <OptionsListPopoverSortingButton showOnlySelected={showOnlySelected} /> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFormRow> + </div> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_empty_message.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_empty_message.tsx new file mode 100644 index 0000000000000..a6950d2cfd4b0 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_empty_message.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, { useMemo } from 'react'; + +import { EuiIcon, EuiSelectableMessage, EuiSpacer } from '@elastic/eui'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; + +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListStrings } from '../options_list_strings'; + +export const OptionsListPopoverEmptyMessage = ({ + showOnlySelected, +}: { + showOnlySelected: boolean; +}) => { + const { api, stateManager } = useOptionsListContext(); + + const [searchTechnique, searchStringValid, field] = useBatchedPublishingSubjects( + stateManager.searchTechnique, + stateManager.searchStringValid, + api.field$ + ); + + const noResultsMessage = useMemo(() => { + if (showOnlySelected) { + return OptionsListStrings.popover.getSelectionsEmptyMessage(); + } + if (!searchStringValid && field && searchTechnique) { + return OptionsListStrings.popover.getInvalidSearchMessage(field.type); + } + return OptionsListStrings.popover.getEmptyMessage(); + }, [showOnlySelected, field, searchStringValid, searchTechnique]); + + return ( + <EuiSelectableMessage + tabIndex={0} + data-test-subj={`optionsList-control-${ + showOnlySelected ? 'selectionsEmptyMessage' : 'noSelectionsMessage' + }`} + > + <EuiIcon + type={searchStringValid ? 'minusInCircle' : 'alert'} + color={searchStringValid ? 'default' : 'danger'} + /> + <EuiSpacer size="xs" /> + {noResultsMessage} + </EuiSelectableMessage> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_footer.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_footer.tsx new file mode 100644 index 0000000000000..67f984cd69905 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_footer.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 and the Server Side Public License, v 1; you may not 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 { + EuiButtonGroup, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiPopoverFooter, + EuiProgress, + useEuiBackgroundColor, + useEuiPaddingSize, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; + +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListStrings } from '../options_list_strings'; + +const aggregationToggleButtons = [ + { + id: 'optionsList__includeResults', + key: 'optionsList__includeResults', + label: OptionsListStrings.popover.getIncludeLabel(), + }, + { + id: 'optionsList__excludeResults', + key: 'optionsList__excludeResults', + label: OptionsListStrings.popover.getExcludeLabel(), + }, +]; + +export const OptionsListPopoverFooter = () => { + const { api, stateManager } = useOptionsListContext(); + + const [exclude, loading, allowExpensiveQueries] = useBatchedPublishingSubjects( + stateManager.exclude, + api.dataLoading, + api.parentApi.allowExpensiveQueries$ + ); + + return ( + <> + <EuiPopoverFooter + paddingSize="none" + css={css` + background-color: ${useEuiBackgroundColor('subdued')}; + `} + > + {loading && ( + <div style={{ position: 'absolute', width: '100%' }}> + <EuiProgress + data-test-subj="optionsList-control-popover-loading" + size="xs" + color="accent" + /> + </div> + )} + + <EuiFlexGroup + gutterSize="xs" + responsive={false} + alignItems="center" + css={css` + padding: ${useEuiPaddingSize('s')}; + `} + justifyContent={'spaceBetween'} + > + <EuiFlexItem grow={false}> + <EuiButtonGroup + legend={OptionsListStrings.popover.getIncludeExcludeLegend()} + options={aggregationToggleButtons} + idSelected={exclude ? 'optionsList__excludeResults' : 'optionsList__includeResults'} + onChange={(optionId) => api.setExclude(optionId === 'optionsList__excludeResults')} + buttonSize="compressed" + data-test-subj="optionsList__includeExcludeButtonGroup" + /> + </EuiFlexItem> + {!allowExpensiveQueries && ( + <EuiFlexItem data-test-subj="optionsList-allow-expensive-queries-warning" grow={false}> + <EuiIconTip + type="warning" + color="warning" + content={OptionsListStrings.popover.getAllowExpensiveQueriesWarning()} + aria-label={OptionsListStrings.popover.getAllowExpensiveQueriesWarning()} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + </EuiPopoverFooter> + </> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_invalid_selections.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_invalid_selections.tsx new file mode 100644 index 0000000000000..19443cc9879f1 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_invalid_selections.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 and the Server Side Public License, v 1; you may not 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 { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiScreenReaderOnly, + EuiSelectable, + EuiSelectableOption, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { + useBatchedPublishingSubjects, + useStateFromPublishingSubject, +} from '@kbn/presentation-publishing'; + +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListStrings } from '../options_list_strings'; + +export const OptionsListPopoverInvalidSelections = () => { + const { api } = useOptionsListContext(); + + const [invalidSelections, fieldFormatter] = useBatchedPublishingSubjects( + api.invalidSelections$, + api.fieldFormatter + ); + const defaultPanelTitle = useStateFromPublishingSubject(api.defaultPanelTitle); + + const [selectableOptions, setSelectableOptions] = useState<EuiSelectableOption[]>([]); // will be set in following useEffect + useEffect(() => { + /* This useEffect makes selectableOptions responsive to unchecking options */ + const options: EuiSelectableOption[] = Array.from(invalidSelections).map((key) => { + return { + key: String(key), + label: fieldFormatter(key), + checked: 'on', + className: 'optionsList__selectionInvalid', + 'data-test-subj': `optionsList-control-invalid-selection-${key}`, + prepend: ( + <EuiScreenReaderOnly> + <div> + {OptionsListStrings.popover.getInvalidSelectionScreenReaderText()} + {'" "'} {/* Adds a pause for the screen reader */} + </div> + </EuiScreenReaderOnly> + ), + }; + }); + setSelectableOptions(options); + }, [fieldFormatter, invalidSelections]); + + return ( + <> + <EuiSpacer size="s" /> + <EuiTitle + size="xxs" + className="optionsList-control-ignored-selection-title" + data-test-subj="optionList__invalidSelectionLabel" + > + <EuiFlexGroup gutterSize="s" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiIcon + type="warning" + color="warning" + title={OptionsListStrings.popover.getInvalidSelectionScreenReaderText()} + size="s" + /> + </EuiFlexItem> + <EuiFlexItem> + <label> + {OptionsListStrings.popover.getInvalidSelectionsSectionTitle(invalidSelections.size)} + </label> + </EuiFlexItem> + </EuiFlexGroup> + </EuiTitle> + <EuiSelectable + aria-label={OptionsListStrings.popover.getInvalidSelectionsSectionAriaLabel( + defaultPanelTitle ?? '', + invalidSelections.size + )} + options={selectableOptions} + listProps={{ onFocusBadge: false, isVirtualized: false }} + onChange={(newSuggestions, _, changedOption) => { + setSelectableOptions(newSuggestions); + api.deselectOption(changedOption.key); + }} + > + {(list) => list} + </EuiSelectable> + </> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_sorting_button.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_sorting_button.test.tsx new file mode 100644 index 0000000000000..b7d59a1e91923 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_sorting_button.test.tsx @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { DataViewField } from '@kbn/data-views-plugin/common'; +import { render, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { getOptionsListMocks } from '../../mocks/api_mocks'; +import { ContextStateManager, OptionsListControlContext } from '../options_list_context_provider'; +import { OptionsListComponentApi } from '../types'; +import { OptionsListPopoverSortingButton } from './options_list_popover_sorting_button'; + +describe('Options list sorting button', () => { + const mountComponent = ({ + api, + displaySettings, + stateManager, + }: { + api: any; + displaySettings: any; + stateManager: any; + }) => { + const component = render( + <OptionsListControlContext.Provider + value={{ + api: api as unknown as OptionsListComponentApi, + displaySettings, + stateManager: stateManager as unknown as ContextStateManager, + }} + > + <OptionsListPopoverSortingButton showOnlySelected={false} /> + </OptionsListControlContext.Provider> + ); + + // open the popover for testing by clicking on the button + const sortButton = component.getByTestId('optionsListControl__sortingOptionsButton'); + userEvent.click(sortButton); + + return component; + }; + + test('when sorting suggestions, show both sorting types for keyword field', async () => { + const mocks = getOptionsListMocks(); + mocks.api.field$.next({ + name: 'Test keyword field', + type: 'keyword', + } as DataViewField); + const component = mountComponent(mocks); + + const sortingOptionsDiv = component.getByTestId('optionsListControl__sortingOptions'); + const optionsText = within(sortingOptionsDiv) + .getAllByRole('option') + .map((el) => el.textContent); + expect(optionsText).toEqual(['By document count. Checked option.', 'Alphabetically']); + }); + + test('sorting popover selects appropriate sorting type on load', async () => { + const mocks = getOptionsListMocks(); + mocks.api.field$.next({ + name: 'Test keyword field', + type: 'keyword', + } as DataViewField); + mocks.stateManager.sort.next({ by: '_key', direction: 'asc' }); + const component = mountComponent(mocks); + + const sortingOptionsDiv = component.getByTestId('optionsListControl__sortingOptions'); + const optionsText = within(sortingOptionsDiv) + .getAllByRole('option') + .map((el) => el.textContent); + expect(optionsText).toEqual(['By document count', 'Alphabetically. Checked option.']); + + const ascendingButton = component.getByTestId('optionsList__sortOrder_asc'); + expect(ascendingButton).toHaveClass('euiButtonGroupButton-isSelected'); + const descendingButton = component.getByTestId('optionsList__sortOrder_desc'); + expect(descendingButton).not.toHaveClass('euiButtonGroupButton-isSelected'); + }); + + test('when sorting suggestions, only show document count sorting for IP fields', async () => { + const mocks = getOptionsListMocks(); + mocks.api.field$.next({ name: 'Test IP field', type: 'ip' } as DataViewField); + const component = mountComponent(mocks); + + const sortingOptionsDiv = component.getByTestId('optionsListControl__sortingOptions'); + const optionsText = within(sortingOptionsDiv) + .getAllByRole('option') + .map((el) => el.textContent); + expect(optionsText).toEqual(['By document count. Checked option.']); + }); + + test('when sorting suggestions, show "By date" sorting option for date fields', async () => { + const mocks = getOptionsListMocks(); + mocks.api.field$.next({ name: 'Test date field', type: 'date' } as DataViewField); + const component = mountComponent(mocks); + + const sortingOptionsDiv = component.getByTestId('optionsListControl__sortingOptions'); + const optionsText = within(sortingOptionsDiv) + .getAllByRole('option') + .map((el) => el.textContent); + expect(optionsText).toEqual(['By document count. Checked option.', 'By date']); + }); + + test('when sorting suggestions, show "Numerically" sorting option for number fields', async () => { + const mocks = getOptionsListMocks(); + mocks.api.field$.next({ name: 'Test number field', type: 'number' } as DataViewField); + const component = mountComponent(mocks); + + const sortingOptionsDiv = component.getByTestId('optionsListControl__sortingOptions'); + const optionsText = within(sortingOptionsDiv) + .getAllByRole('option') + .map((el) => el.textContent); + expect(optionsText).toEqual(['By document count. Checked option.', 'Numerically']); + }); +}); diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_sorting_button.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_sorting_button.tsx new file mode 100644 index 0000000000000..e95b7950314c9 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_sorting_button.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 } from 'react'; + +import { + Direction, + EuiButtonGroup, + EuiButtonGroupOptionProps, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiPopoverTitle, + EuiSelectable, + EuiSelectableOption, + EuiToolTip, +} from '@elastic/eui'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; + +import { + getCompatibleSortingTypes, + OptionsListSortBy, + OPTIONS_LIST_DEFAULT_SORT, +} from '../../../../../../common/options_list/suggestions_sorting'; +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListStrings } from '../options_list_strings'; + +type SortByItem = EuiSelectableOption & { + data: { sortBy: OptionsListSortBy }; +}; + +const sortOrderOptions: EuiButtonGroupOptionProps[] = [ + { + id: 'asc', + iconType: `sortAscending`, + 'data-test-subj': `optionsList__sortOrder_asc`, + label: OptionsListStrings.editorAndPopover.sortOrder.asc.getSortOrderLabel(), + }, + { + id: 'desc', + iconType: `sortDescending`, + 'data-test-subj': `optionsList__sortOrder_desc`, + label: OptionsListStrings.editorAndPopover.sortOrder.desc.getSortOrderLabel(), + }, +]; + +export const OptionsListPopoverSortingButton = ({ + showOnlySelected, +}: { + showOnlySelected: boolean; +}) => { + const { api, stateManager } = useOptionsListContext(); + + const [isSortingPopoverOpen, setIsSortingPopoverOpen] = useState(false); + const [sort, field] = useBatchedPublishingSubjects(stateManager.sort, api.field$); + + const selectedSort = useMemo(() => sort ?? OPTIONS_LIST_DEFAULT_SORT, [sort]); + + const [sortByOptions, setSortByOptions] = useState<SortByItem[]>(() => { + return getCompatibleSortingTypes(field?.type).map((key) => { + return { + onFocusBadge: false, + data: { sortBy: key }, + checked: key === selectedSort.by ? 'on' : undefined, + 'data-test-subj': `optionsList__sortBy_${key}`, + label: OptionsListStrings.editorAndPopover.sortBy[key].getSortByLabel(field?.type), + } as SortByItem; + }); + }); + + const onSortByChange = useCallback( + (updatedOptions: SortByItem[]) => { + setSortByOptions(updatedOptions); + const selectedOption = updatedOptions.find(({ checked }) => checked === 'on'); + if (selectedOption) { + stateManager.sort.next({ + ...selectedSort, + by: selectedOption.data.sortBy, + }); + } + }, + [selectedSort, stateManager.sort] + ); + + const SortButton = () => ( + <EuiButtonIcon + size="xs" + display="empty" + color="text" + iconType={selectedSort.direction === 'asc' ? 'sortAscending' : 'sortDescending'} + isDisabled={showOnlySelected} + className="optionsList__sortButton" + data-test-subj="optionsListControl__sortingOptionsButton" + onClick={() => setIsSortingPopoverOpen(!isSortingPopoverOpen)} + aria-label={OptionsListStrings.popover.getSortPopoverDescription()} + /> + ); + + return ( + <EuiPopover + button={ + <EuiToolTip + position="top" + content={ + showOnlySelected + ? OptionsListStrings.popover.getSortDisabledTooltip() + : OptionsListStrings.popover.getSortPopoverTitle() + } + > + <SortButton /> + </EuiToolTip> + } + panelPaddingSize="none" + isOpen={isSortingPopoverOpen} + aria-labelledby="optionsList_sortingOptions" + closePopover={() => setIsSortingPopoverOpen(false)} + panelClassName={'optionsList--sortPopover'} + > + <span data-test-subj="optionsListControl__sortingOptionsPopover"> + <EuiPopoverTitle paddingSize="s"> + <EuiFlexGroup alignItems="center" responsive={false}> + <EuiFlexItem>{OptionsListStrings.popover.getSortPopoverTitle()}</EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonGroup + isIconOnly + buttonSize="compressed" + options={sortOrderOptions} + idSelected={selectedSort.direction ?? OPTIONS_LIST_DEFAULT_SORT.direction} + legend={OptionsListStrings.editorAndPopover.getSortDirectionLegend()} + onChange={(value) => { + stateManager.sort.next({ + ...selectedSort, + direction: value as Direction, + }); + }} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPopoverTitle> + <EuiSelectable + options={sortByOptions} + singleSelection="always" + onChange={onSortByChange} + id="optionsList_sortingOptions" + listProps={{ bordered: false }} + data-test-subj="optionsListControl__sortingOptions" + aria-label={OptionsListStrings.popover.getSortPopoverDescription()} + > + {(list) => list} + </EuiSelectable> + </span> + </EuiPopover> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_suggestion_badge.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_suggestion_badge.tsx new file mode 100644 index 0000000000000..79cb490dbf19b --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_suggestion_badge.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 and the Server Side Public License, v 1; you may not 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 { css } from '@emotion/react'; +import { EuiScreenReaderOnly, EuiText, EuiToolTip, useEuiTheme } from '@elastic/eui'; + +import { OptionsListStrings } from '../options_list_strings'; + +export const OptionsListPopoverSuggestionBadge = ({ documentCount }: { documentCount: number }) => { + const { euiTheme } = useEuiTheme(); + + return ( + <> + <EuiToolTip + content={OptionsListStrings.popover.getDocumentCountTooltip(documentCount)} + position={'right'} + > + <EuiText + size="xs" + aria-hidden={true} + className="eui-textNumber" + color={euiTheme.colors.subduedText} + data-test-subj="optionsList-document-count-badge" + css={css` + font-weight: ${euiTheme.font.weight.medium} !important; + `} + > + {`${documentCount.toLocaleString()}`} + </EuiText> + </EuiToolTip> + <EuiScreenReaderOnly> + <div> + {'" "'} {/* Adds a pause for the screen reader */} + {OptionsListStrings.popover.getDocumentCountScreenReaderText(documentCount)} + </div> + </EuiScreenReaderOnly> + </> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_suggestions.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_suggestions.tsx new file mode 100644 index 0000000000000..4cf010f0473aa --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/components/options_list_popover_suggestions.tsx @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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, useMemo, useRef, useState } from 'react'; + +import { EuiHighlight, EuiSelectable } from '@elastic/eui'; +import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import { euiThemeVars } from '@kbn/ui-theme'; + +import { OptionsListSuggestions } from '../../../../../../common/options_list/types'; +import { OptionsListSelection } from '../../../../../../common/options_list/options_list_selections'; +import { MAX_OPTIONS_LIST_REQUEST_SIZE } from '../constants'; +import { useOptionsListContext } from '../options_list_context_provider'; +import { OptionsListStrings } from '../options_list_strings'; +import { OptionsListPopoverEmptyMessage } from './options_list_popover_empty_message'; +import { OptionsListPopoverSuggestionBadge } from './options_list_popover_suggestion_badge'; + +interface OptionsListPopoverSuggestionsProps { + showOnlySelected: boolean; +} + +export const OptionsListPopoverSuggestions = ({ + showOnlySelected, +}: OptionsListPopoverSuggestionsProps) => { + const { + api, + stateManager, + displaySettings: { hideExists }, + } = useOptionsListContext(); + + const [ + sort, + searchString, + existsSelected, + searchTechnique, + selectedOptions, + fieldName, + invalidSelections, + availableOptions, + totalCardinality, + loading, + fieldFormatter, + allowExpensiveQueries, + ] = useBatchedPublishingSubjects( + stateManager.sort, + stateManager.searchString, + stateManager.existsSelected, + stateManager.searchTechnique, + stateManager.selectedOptions, + stateManager.fieldName, + api.invalidSelections$, + api.availableOptions$, + api.totalCardinality$, + api.dataLoading, + api.fieldFormatter, + api.parentApi.allowExpensiveQueries$ + ); + + const listRef = useRef<HTMLDivElement>(null); + + const canLoadMoreSuggestions = useMemo<boolean>( + () => + allowExpensiveQueries && totalCardinality && !showOnlySelected // && searchString.valid + ? (availableOptions ?? []).length < + Math.min(totalCardinality, MAX_OPTIONS_LIST_REQUEST_SIZE) + : false, + [availableOptions, totalCardinality, showOnlySelected, allowExpensiveQueries] + ); + + const suggestions = useMemo<OptionsListSuggestions | OptionsListSelection[]>(() => { + return (showOnlySelected ? selectedOptions : availableOptions) ?? []; + }, [availableOptions, selectedOptions, showOnlySelected]); + + const existsSelectableOption = useMemo<EuiSelectableOption | undefined>(() => { + if (hideExists || (!existsSelected && (showOnlySelected || suggestions?.length === 0))) return; + + return { + key: 'exists-option', + checked: existsSelected ? 'on' : undefined, + label: OptionsListStrings.controlAndPopover.getExists(), + className: 'optionsList__existsFilter', + 'data-test-subj': 'optionsList-control-selection-exists', + }; + }, [suggestions, existsSelected, showOnlySelected, hideExists]); + + const [selectableOptions, setSelectableOptions] = useState<EuiSelectableOption[]>([]); // will be set in following useEffect + useEffect(() => { + /* This useEffect makes selectableOptions responsive to search, show only selected, and clear selections */ + const options: EuiSelectableOption[] = suggestions.map((suggestion) => { + if (typeof suggestion !== 'object') { + // this means that `showOnlySelected` is true, and doc count is not known when this is the case + suggestion = { value: suggestion }; + } + + return { + key: String(suggestion.value), + label: String(fieldFormatter(suggestion.value) ?? suggestion.value), + checked: (selectedOptions ?? []).includes(suggestion.value) ? 'on' : undefined, + 'data-test-subj': `optionsList-control-selection-${suggestion.value}`, + className: + showOnlySelected && invalidSelections.has(suggestion.value) + ? 'optionsList__selectionInvalid' + : 'optionsList__validSuggestion', + append: + !showOnlySelected && suggestion?.docCount ? ( + <OptionsListPopoverSuggestionBadge documentCount={suggestion.docCount} /> + ) : undefined, + } as EuiSelectableOption; + }); + + if (canLoadMoreSuggestions) { + options.push({ + key: 'loading-option', + className: 'optionslist--loadingMoreGroupLabel', + label: OptionsListStrings.popover.getLoadingMoreMessage(), + isGroupLabel: true, + }); + } else if (options.length === MAX_OPTIONS_LIST_REQUEST_SIZE) { + options.push({ + key: 'no-more-option', + className: 'optionslist--endOfOptionsGroupLabel', + label: OptionsListStrings.popover.getAtEndOfOptionsMessage(), + isGroupLabel: true, + }); + } + setSelectableOptions(existsSelectableOption ? [existsSelectableOption, ...options] : options); + }, [ + suggestions, + availableOptions, + showOnlySelected, + selectedOptions, + invalidSelections, + existsSelectableOption, + canLoadMoreSuggestions, + fieldFormatter, + ]); + + const loadMoreOptions = useCallback(() => { + const listbox = listRef.current?.querySelector('.euiSelectableList__list'); + if (!listbox) return; + + const { scrollTop, scrollHeight, clientHeight } = listbox; + if (scrollTop + clientHeight >= scrollHeight - parseInt(euiThemeVars.euiSizeXXL, 10)) { + // reached the "bottom" of the list, where euiSizeXXL acts as a "margin of error" so that the user doesn't + // have to scroll **all the way** to the bottom in order to load more options + stateManager.requestSize.next(totalCardinality ?? MAX_OPTIONS_LIST_REQUEST_SIZE); + api.loadMoreSubject.next(null); // trigger refetch with loadMoreSubject + } + }, [api.loadMoreSubject, stateManager.requestSize, totalCardinality]); + + const renderOption = useCallback( + (option, searchStringValue) => { + if (!allowExpensiveQueries || searchTechnique === 'exact') return option.label; + + return ( + <EuiHighlight search={option.key === 'exists-option' ? '' : searchStringValue}> + {option.label} + </EuiHighlight> + ); + }, + [searchTechnique, allowExpensiveQueries] + ); + + useEffect(() => { + const container = listRef.current; + if (!loading && canLoadMoreSuggestions) { + container?.addEventListener('scroll', loadMoreOptions, true); + return () => { + container?.removeEventListener('scroll', loadMoreOptions, true); + }; + } + }, [loadMoreOptions, loading, canLoadMoreSuggestions]); + + useEffect(() => { + // scroll back to the top when changing the sorting or the search string + const listbox = listRef.current?.querySelector('.euiSelectableList__list'); + listbox?.scrollTo({ top: 0 }); + }, [sort, searchString]); + + return ( + <> + <div ref={listRef}> + <EuiSelectable + options={selectableOptions} + renderOption={(option) => renderOption(option, searchString)} + listProps={{ onFocusBadge: false }} + aria-label={OptionsListStrings.popover.getSuggestionsAriaLabel( + fieldName, + selectableOptions.length + )} + emptyMessage={<OptionsListPopoverEmptyMessage showOnlySelected={showOnlySelected} />} + onChange={(newSuggestions, _, changedOption) => { + api.makeSelection(changedOption.key, showOnlySelected); + }} + > + {(list) => list} + </EuiSelectable> + </div> + </> + ); +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/constants.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/constants.ts new file mode 100644 index 0000000000000..5d21e2d04a572 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/constants.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching'; +import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting'; + +export const DEFAULT_SEARCH_TECHNIQUE: OptionsListSearchTechnique = 'prefix'; +export const OPTIONS_LIST_DEFAULT_SORT: OptionsListSortingType = { + by: '_count', + direction: 'desc', +}; + +export const MIN_OPTIONS_LIST_REQUEST_SIZE = 10; +export const MAX_OPTIONS_LIST_REQUEST_SIZE = 1000; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/fetch_and_validate.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/fetch_and_validate.tsx new file mode 100644 index 0000000000000..7b6db45a1ba2a --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/fetch_and_validate.tsx @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + BehaviorSubject, + combineLatest, + debounceTime, + Observable, + switchMap, + tap, + withLatestFrom, +} from 'rxjs'; + +import { PublishingSubject } from '@kbn/presentation-publishing'; +import { OptionsListSuccessResponse } from '../../../../../common/options_list/types'; +import { isValidSearch } from '../../../../../common/options_list/is_valid_search'; +import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; +import { ControlFetchContext } from '../../../control_group/control_fetch'; +import { ControlStateManager } from '../../types'; +import { DataControlServices } from '../types'; +import { OptionsListFetchCache } from './options_list_fetch_cache'; +import { OptionsListComponentApi, OptionsListComponentState, OptionsListControlApi } from './types'; + +export function fetchAndValidate$({ + api, + services, + stateManager, +}: { + api: Pick<OptionsListControlApi, 'dataViews' | 'field$' | 'setBlockingError' | 'parentApi'> & + Pick<OptionsListComponentApi, 'loadMoreSubject'> & { + controlFetch$: Observable<ControlFetchContext>; + loadingSuggestions$: BehaviorSubject<boolean>; + debouncedSearchString: Observable<string>; + }; + services: DataControlServices; + stateManager: ControlStateManager< + Pick<OptionsListComponentState, 'requestSize' | 'runPastTimeout' | 'searchTechnique' | 'sort'> + > & { + selectedOptions: PublishingSubject<OptionsListSelection[] | undefined>; + }; +}): Observable<OptionsListSuccessResponse | { error: Error }> { + const requestCache = new OptionsListFetchCache(); + let abortController: AbortController | undefined; + + return combineLatest([ + api.dataViews, + api.field$, + api.controlFetch$, + api.parentApi.allowExpensiveQueries$, + api.debouncedSearchString, + stateManager.sort, + stateManager.searchTechnique, + // cannot use requestSize directly, because we need to be able to reset the size to the default without refetching + api.loadMoreSubject.pipe(debounceTime(100)), // debounce load more so "loading" state briefly shows + ]).pipe( + tap(() => { + // abort any in progress requests + if (abortController) { + abortController.abort(); + abortController = undefined; + } + }), + withLatestFrom( + stateManager.requestSize, + stateManager.runPastTimeout, + stateManager.selectedOptions + ), + switchMap( + async ([ + [ + dataViews, + field, + controlFetchContext, + allowExpensiveQueries, + searchString, + sort, + searchTechnique, + ], + requestSize, + runPastTimeout, + selectedOptions, + ]) => { + const dataView = dataViews?.[0]; + if ( + !dataView || + !field || + !isValidSearch({ searchString, fieldType: field.type, searchTechnique }) + ) { + return { suggestions: [] }; + } + + /** Fetch the suggestions list + perform validation */ + api.loadingSuggestions$.next(true); + + const request = { + sort, + dataView, + searchString, + runPastTimeout, + searchTechnique, + selectedOptions, + field: field.toSpec(), + size: requestSize, + allowExpensiveQueries, + ...controlFetchContext, + }; + + const newAbortController = new AbortController(); + abortController = newAbortController; + try { + return await requestCache.runFetchRequest(request, newAbortController.signal, services); + } catch (error) { + return { error }; + } + } + ), + tap(() => { + api.loadingSuggestions$.next(false); + }) + ); +} diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.test.tsx new file mode 100644 index 0000000000000..d74070b980769 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.test.tsx @@ -0,0 +1,347 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { coreMock } from '@kbn/core/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { createStubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { act, render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks'; +import { getOptionsListControlFactory } from './get_options_list_control_factory'; + +describe('Options List Control Api', () => { + const uuid = 'myControl1'; + const controlGroupApi = getMockedControlGroupApi(); + const mockDataViews = dataViewPluginMocks.createStartContract(); + const mockCore = coreMock.createStart(); + + const waitOneTick = () => act(() => new Promise((resolve) => setTimeout(resolve, 0))); + + mockDataViews.get = jest.fn().mockImplementation(async (id: string): Promise<DataView> => { + if (id !== 'myDataViewId') { + throw new Error(`Simulated error: no data view found for id ${id}`); + } + const stubDataView = createStubDataView({ + spec: { + id: 'myDataViewId', + fields: { + myFieldName: { + name: 'myFieldName', + customLabel: 'My field name', + type: 'string', + esTypes: ['keyword'], + aggregatable: true, + searchable: true, + }, + }, + title: 'logstash-*', + timeFieldName: '@timestamp', + }, + }); + stubDataView.getFormatterForField = jest.fn().mockImplementation(() => { + return { + getConverterFor: () => { + return (value: string) => `${value}:formatted`; + }, + toJSON: (value: any) => JSON.stringify(value), + }; + }); + return stubDataView; + }); + + const factory = getOptionsListControlFactory({ + core: mockCore, + data: dataPluginMock.createStartContract(), + dataViews: mockDataViews, + }); + + describe('filters$', () => { + test('should not set filters$ when selectedOptions is not provided', async () => { + const { api } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + expect(api.filters$.value).toBeUndefined(); + }); + + test('should set filters$ when selectedOptions is provided', async () => { + const { api } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + selectedOptions: ['cool', 'test'], + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + expect(api.filters$.value).toEqual([ + { + meta: { + index: 'myDataViewId', + key: 'myFieldName', + params: ['cool', 'test'], + type: 'phrases', + }, + query: { + bool: { + minimum_should_match: 1, + should: [ + { + match_phrase: { + myFieldName: 'cool', + }, + }, + { + match_phrase: { + myFieldName: 'test', + }, + }, + ], + }, + }, + }, + ]); + }); + + test('should set filters$ when exists is selected', async () => { + const { api } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + existsSelected: true, + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + expect(api.filters$.value).toEqual([ + { + meta: { + index: 'myDataViewId', + key: 'myFieldName', + }, + query: { + exists: { + field: 'myFieldName', + }, + }, + }, + ]); + }); + + test('should set filters$ when exclude is selected', async () => { + const { api } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + existsSelected: true, + exclude: true, + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + expect(api.filters$.value).toEqual([ + { + meta: { + index: 'myDataViewId', + key: 'myFieldName', + negate: true, + }, + query: { + exists: { + field: 'myFieldName', + }, + }, + }, + ]); + }); + }); + + describe('make selection', () => { + beforeAll(() => { + mockCore.http.fetch = jest.fn().mockResolvedValue({ + suggestions: [ + { value: 'woof', docCount: 10 }, + { value: 'bark', docCount: 15 }, + { value: 'meow', docCount: 12 }, + ], + }); + }); + + test('clicking another option unselects "Exists"', async () => { + const { Component } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + existsSelected: true, + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + + const control = render(<Component className={'controlPanel'} />); + userEvent.click(control.getByTestId(`optionsList-control-${uuid}`)); + await waitFor(() => { + expect(control.getAllByRole('option').length).toBe(4); + }); + + expect(control.getByTestId('optionsList-control-selection-exists')).toBeChecked(); + const option = control.getByTestId('optionsList-control-selection-woof'); + userEvent.click(option); + await waitOneTick(); + expect(control.getByTestId('optionsList-control-selection-exists')).not.toBeChecked(); + expect(option).toBeChecked(); + }); + + test('clicking "Exists" unselects all other selections', async () => { + const { Component } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + selectedOptions: ['woof', 'bark'], + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + + const control = render(<Component className={'controlPanel'} />); + userEvent.click(control.getByTestId(`optionsList-control-${uuid}`)); + await waitFor(() => { + expect(control.getAllByRole('option').length).toEqual(4); + }); + + const existsOption = control.getByTestId('optionsList-control-selection-exists'); + expect(existsOption).not.toBeChecked(); + expect(control.getByTestId('optionsList-control-selection-woof')).toBeChecked(); + expect(control.getByTestId('optionsList-control-selection-bark')).toBeChecked(); + expect(control.getByTestId('optionsList-control-selection-meow')).not.toBeChecked(); + + userEvent.click(existsOption); + await waitOneTick(); + expect(existsOption).toBeChecked(); + expect(control.getByTestId('optionsList-control-selection-woof')).not.toBeChecked(); + expect(control.getByTestId('optionsList-control-selection-bark')).not.toBeChecked(); + expect(control.getByTestId('optionsList-control-selection-meow')).not.toBeChecked(); + }); + + test('deselects when showOnlySelected is true', async () => { + const { Component, api } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + selectedOptions: ['woof', 'bark'], + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + + const control = render(<Component className={'controlPanel'} />); + userEvent.click(control.getByTestId(`optionsList-control-${uuid}`)); + await waitFor(() => { + expect(control.getAllByRole('option').length).toEqual(4); + }); + userEvent.click(control.getByTestId('optionsList-control-show-only-selected')); + + expect(control.getByTestId('optionsList-control-selection-woof')).toBeChecked(); + expect(control.getByTestId('optionsList-control-selection-bark')).toBeChecked(); + expect(control.queryByTestId('optionsList-control-selection-meow')).toBeNull(); + + userEvent.click(control.getByTestId('optionsList-control-selection-bark')); + await waitOneTick(); + expect(control.getByTestId('optionsList-control-selection-woof')).toBeChecked(); + expect(control.queryByTestId('optionsList-control-selection-bark')).toBeNull(); + expect(control.queryByTestId('optionsList-control-selection-meow')).toBeNull(); + + expect(api.filters$.value).toEqual([ + { + meta: { + index: 'myDataViewId', + key: 'myFieldName', + }, + query: { + match_phrase: { + myFieldName: 'woof', + }, + }, + }, + ]); + }); + + test('replace selection when singleSelect is true', async () => { + const { Component, api } = await factory.buildControl( + { + dataViewId: 'myDataViewId', + fieldName: 'myFieldName', + singleSelect: true, + selectedOptions: ['woof'], + }, + getMockedBuildApi(uuid, factory, controlGroupApi), + uuid, + controlGroupApi + ); + + const control = render(<Component className={'controlPanel'} />); + + expect(api.filters$.value).toEqual([ + { + meta: { + index: 'myDataViewId', + key: 'myFieldName', + }, + query: { + match_phrase: { + myFieldName: 'woof', + }, + }, + }, + ]); + + userEvent.click(control.getByTestId(`optionsList-control-${uuid}`)); + await waitFor(() => { + expect(control.getAllByRole('option').length).toEqual(4); + }); + expect(control.getByTestId('optionsList-control-selection-woof')).toBeChecked(); + expect(control.queryByTestId('optionsList-control-selection-bark')).not.toBeChecked(); + expect(control.queryByTestId('optionsList-control-selection-meow')).not.toBeChecked(); + userEvent.click(control.getByTestId('optionsList-control-selection-bark')); + await waitOneTick(); + expect(control.getByTestId('optionsList-control-selection-woof')).not.toBeChecked(); + expect(control.queryByTestId('optionsList-control-selection-bark')).toBeChecked(); + expect(control.queryByTestId('optionsList-control-selection-meow')).not.toBeChecked(); + + expect(api.filters$.value).toEqual([ + { + meta: { + index: 'myDataViewId', + key: 'myFieldName', + }, + query: { + match_phrase: { + myFieldName: 'bark', + }, + }, + }, + ]); + }); + }); +}); diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.tsx new file mode 100644 index 0000000000000..12d0de5a3d7d3 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/get_options_list_control_factory.tsx @@ -0,0 +1,407 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 } from 'react'; +import { BehaviorSubject, combineLatest, debounceTime, filter, skip } from 'rxjs'; + +import { buildExistsFilter, buildPhraseFilter, buildPhrasesFilter, Filter } from '@kbn/es-query'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; + +import { + OPTIONS_LIST_CONTROL, + OptionsListSuccessResponse, + OptionsListSuggestions, +} from '../../../../../common/options_list/types'; +import { + getSelectionAsFieldType, + OptionsListSelection, +} from '../../../../../common/options_list/options_list_selections'; +import { isValidSearch } from '../../../../../common/options_list/is_valid_search'; +import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching'; +import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting'; +import { initializeDataControl } from '../initialize_data_control'; +import { DataControlFactory, DataControlServices } from '../types'; +import { OptionsListControl } from './components/options_list_control'; +import { OptionsListEditorOptions } from './components/options_list_editor_options'; +import { + DEFAULT_SEARCH_TECHNIQUE, + MIN_OPTIONS_LIST_REQUEST_SIZE, + OPTIONS_LIST_DEFAULT_SORT, +} from './constants'; +import { fetchAndValidate$ } from './fetch_and_validate'; +import { OptionsListControlContext } from './options_list_context_provider'; +import { OptionsListStrings } from './options_list_strings'; +import { OptionsListControlApi, OptionsListControlState } from './types'; +import { initializeOptionsListSelections } from './options_list_control_selections'; + +export const getOptionsListControlFactory = ( + services: DataControlServices +): DataControlFactory<OptionsListControlState, OptionsListControlApi> => { + return { + type: OPTIONS_LIST_CONTROL, + order: 3, // should always be first, since this is the most popular control + getIconType: () => 'editorChecklist', + getDisplayName: OptionsListStrings.control.getDisplayName, + isFieldCompatible: (field) => { + return ( + !field.spec.scripted && + field.aggregatable && + ['string', 'boolean', 'ip', 'date', 'number'].includes(field.type) + ); + }, + CustomOptionsComponent: OptionsListEditorOptions, + buildControl: async (initialState, buildApi, uuid, controlGroupApi) => { + /** Serializable state - i.e. the state that is saved with the control */ + const searchTechnique$ = new BehaviorSubject<OptionsListSearchTechnique | undefined>( + initialState.searchTechnique ?? DEFAULT_SEARCH_TECHNIQUE + ); + const runPastTimeout$ = new BehaviorSubject<boolean | undefined>(initialState.runPastTimeout); + const singleSelect$ = new BehaviorSubject<boolean | undefined>(initialState.singleSelect); + const sort$ = new BehaviorSubject<OptionsListSortingType | undefined>( + initialState.sort ?? OPTIONS_LIST_DEFAULT_SORT + ); + + /** Creation options state - cannot currently be changed after creation, but need subjects for comparators */ + const placeholder$ = new BehaviorSubject<string | undefined>(initialState.placeholder); + const hideActionBar$ = new BehaviorSubject<boolean | undefined>(initialState.hideActionBar); + const hideExclude$ = new BehaviorSubject<boolean | undefined>(initialState.hideExclude); + const hideExists$ = new BehaviorSubject<boolean | undefined>(initialState.hideExists); + const hideSort$ = new BehaviorSubject<boolean | undefined>(initialState.hideSort); + + /** Runtime / component state - none of this is serialized */ + const searchString$ = new BehaviorSubject<string>(''); + const searchStringValid$ = new BehaviorSubject<boolean>(true); + const requestSize$ = new BehaviorSubject<number>(MIN_OPTIONS_LIST_REQUEST_SIZE); + + const availableOptions$ = new BehaviorSubject<OptionsListSuggestions | undefined>(undefined); + const invalidSelections$ = new BehaviorSubject<Set<OptionsListSelection>>(new Set()); + const totalCardinality$ = new BehaviorSubject<number>(0); + + const dataControl = initializeDataControl< + Pick<OptionsListControlState, 'searchTechnique' | 'singleSelect'> + >( + uuid, + OPTIONS_LIST_CONTROL, + initialState, + { searchTechnique: searchTechnique$, singleSelect: singleSelect$ }, + controlGroupApi, + services + ); + + const selections = initializeOptionsListSelections( + initialState, + dataControl.setters.onSelectionChange + ); + + const stateManager = { + ...dataControl.stateManager, + exclude: selections.exclude$, + existsSelected: selections.existsSelected$, + searchTechnique: searchTechnique$, + selectedOptions: selections.selectedOptions$, + singleSelect: singleSelect$, + sort: sort$, + searchString: searchString$, + searchStringValid: searchStringValid$, + runPastTimeout: runPastTimeout$, + requestSize: requestSize$, + }; + + /** Handle loading state; since suggestion fetching and validation are tied, only need one loading subject */ + const loadingSuggestions$ = new BehaviorSubject<boolean>(false); + const dataLoadingSubscription = loadingSuggestions$ + .pipe( + debounceTime(100) // debounce set loading so that it doesn't flash as the user types + ) + .subscribe((isLoading) => { + dataControl.api.setDataLoading(isLoading); + }); + + /** Debounce the search string changes to reduce the number of fetch requests */ + const debouncedSearchString = stateManager.searchString.pipe(debounceTime(100)); + + /** Validate the search string as the user types */ + const validSearchStringSubscription = combineLatest([ + debouncedSearchString, + dataControl.api.field$, + searchTechnique$, + ]).subscribe(([newSearchString, field, searchTechnique]) => { + searchStringValid$.next( + isValidSearch({ + searchString: newSearchString, + fieldType: field?.type, + searchTechnique, + }) + ); + }); + + /** Clear state when the field changes */ + const fieldChangedSubscription = combineLatest([ + dataControl.stateManager.fieldName, + dataControl.stateManager.dataViewId, + ]) + .pipe( + skip(1) // skip first, since this represents initialization + ) + .subscribe(() => { + searchString$.next(''); + selections.setSelectedOptions(undefined); + selections.setExistsSelected(false); + selections.setExclude(false); + requestSize$.next(MIN_OPTIONS_LIST_REQUEST_SIZE); + sort$.next(OPTIONS_LIST_DEFAULT_SORT); + }); + + /** Fetch the suggestions and perform validation */ + const loadMoreSubject = new BehaviorSubject<null>(null); + const fetchSubscription = fetchAndValidate$({ + services, + api: { + ...dataControl.api, + loadMoreSubject, + loadingSuggestions$, + debouncedSearchString, + parentApi: controlGroupApi, + controlFetch$: controlGroupApi.controlFetch$(uuid), + }, + stateManager, + }).subscribe((result) => { + // if there was an error during fetch, set blocking error and return early + if (Object.hasOwn(result, 'error')) { + dataControl.api.setBlockingError((result as { error: Error }).error); + return; + } else if (dataControl.api.blockingError.getValue()) { + // otherwise, if there was a previous error, clear it + dataControl.api.setBlockingError(undefined); + } + + // fetch was successful so set all attributes from result + const successResponse = result as OptionsListSuccessResponse; + availableOptions$.next(successResponse.suggestions); + totalCardinality$.next(successResponse.totalCardinality ?? 0); + invalidSelections$.next(new Set(successResponse.invalidSelections ?? [])); + + // reset the request size back to the minimum (if it's not already) + if (stateManager.requestSize.getValue() !== MIN_OPTIONS_LIST_REQUEST_SIZE) { + stateManager.requestSize.next(MIN_OPTIONS_LIST_REQUEST_SIZE); + } + }); + + /** Remove all other selections if this control becomes a single select */ + const singleSelectSubscription = singleSelect$ + .pipe(filter((singleSelect) => Boolean(singleSelect))) + .subscribe(() => { + const currentSelections = selections.selectedOptions$.getValue() ?? []; + if (currentSelections.length > 1) selections.setSelectedOptions([currentSelections[0]]); + }); + + /** Output filters when selections change */ + const outputFilterSubscription = combineLatest([ + dataControl.api.dataViews, + dataControl.stateManager.fieldName, + selections.selectedOptions$, + selections.existsSelected$, + selections.exclude$, + ]) + .pipe(debounceTime(0)) + .subscribe(([dataViews, fieldName, selectedOptions, existsSelected, exclude]) => { + const dataView = dataViews?.[0]; + const field = dataView && fieldName ? dataView.getFieldByName(fieldName) : undefined; + + let newFilter: Filter | undefined; + if (dataView && field) { + if (existsSelected) { + newFilter = buildExistsFilter(field, dataView); + } else if (selectedOptions && selectedOptions.length > 0) { + newFilter = + selectedOptions.length === 1 + ? buildPhraseFilter(field, selectedOptions[0], dataView) + : buildPhrasesFilter(field, selectedOptions, dataView); + } + } + if (newFilter) { + newFilter.meta.key = field?.name; + if (exclude) newFilter.meta.negate = true; + } + dataControl.setters.setOutputFilter(newFilter); + }); + + const api = buildApi( + { + ...dataControl.api, + getTypeDisplayName: OptionsListStrings.control.getDisplayName, + serializeState: () => { + const { rawState: dataControlState, references } = dataControl.serialize(); + return { + rawState: { + ...dataControlState, + searchTechnique: searchTechnique$.getValue(), + runPastTimeout: runPastTimeout$.getValue(), + singleSelect: singleSelect$.getValue(), + selections: selections.selectedOptions$.getValue(), + sort: sort$.getValue(), + existsSelected: selections.existsSelected$.getValue(), + exclude: selections.exclude$.getValue(), + + // serialize state that cannot be changed to keep it consistent + placeholder: placeholder$.getValue(), + hideActionBar: hideActionBar$.getValue(), + hideExclude: hideExclude$.getValue(), + hideExists: hideExists$.getValue(), + hideSort: hideSort$.getValue(), + }, + references, // does not have any references other than those provided by the data control serializer + }; + }, + clearSelections: () => { + if (selections.selectedOptions$.getValue()?.length) selections.setSelectedOptions([]); + if (selections.existsSelected$.getValue()) selections.setExistsSelected(false); + if (invalidSelections$.getValue().size) invalidSelections$.next(new Set([])); + }, + }, + { + ...dataControl.comparators, + ...selections.comparators, + runPastTimeout: [runPastTimeout$, (runPast) => runPastTimeout$.next(runPast)], + searchTechnique: [ + searchTechnique$, + (technique) => searchTechnique$.next(technique), + (a, b) => (a ?? DEFAULT_SEARCH_TECHNIQUE) === (b ?? DEFAULT_SEARCH_TECHNIQUE), + ], + singleSelect: [singleSelect$, (selected) => singleSelect$.next(selected)], + sort: [ + sort$, + (sort) => sort$.next(sort), + (a, b) => (a ?? OPTIONS_LIST_DEFAULT_SORT) === (b ?? OPTIONS_LIST_DEFAULT_SORT), + ], + + /** This state cannot currently be changed after the control is created */ + placeholder: [placeholder$, (placeholder) => placeholder$.next(placeholder)], + hideActionBar: [hideActionBar$, (hideActionBar) => hideActionBar$.next(hideActionBar)], + hideExclude: [hideExclude$, (hideExclude) => hideExclude$.next(hideExclude)], + hideExists: [hideExists$, (hideExists) => hideExists$.next(hideExists)], + hideSort: [hideSort$, (hideSort) => hideSort$.next(hideSort)], + } + ); + + const componentApi = { + ...api, + loadMoreSubject, + totalCardinality$, + availableOptions$, + invalidSelections$, + setExclude: selections.setExclude, + deselectOption: (key: string | undefined) => { + const field = api.field$.getValue(); + if (!key || !field) { + api.setBlockingError( + new Error(OptionsListStrings.control.getInvalidSelectionMessage()) + ); + return; + } + + const keyAsType = getSelectionAsFieldType(field, key); + + // delete from selections + const selectedOptions = selections.selectedOptions$.getValue() ?? []; + const itemIndex = (selections.selectedOptions$.getValue() ?? []).indexOf(keyAsType); + if (itemIndex !== -1) { + const newSelections = [...selectedOptions]; + newSelections.splice(itemIndex, 1); + selections.setSelectedOptions(newSelections); + } + // delete from invalid selections + const currentInvalid = invalidSelections$.getValue(); + if (currentInvalid.has(keyAsType)) { + currentInvalid.delete(keyAsType); + invalidSelections$.next(new Set(currentInvalid)); + } + }, + makeSelection: (key: string | undefined, showOnlySelected: boolean) => { + const field = api.field$.getValue(); + if (!key || !field) { + api.setBlockingError( + new Error(OptionsListStrings.control.getInvalidSelectionMessage()) + ); + return; + } + + const existsSelected = Boolean(selections.existsSelected$.getValue()); + const selectedOptions = selections.selectedOptions$.getValue() ?? []; + const singleSelect = singleSelect$.getValue(); + + // the order of these checks matters, so be careful if rearranging them + const keyAsType = getSelectionAsFieldType(field, key); + if (key === 'exists-option') { + // if selecting exists, then deselect everything else + selections.setExistsSelected(!existsSelected); + if (!existsSelected) { + selections.setSelectedOptions([]); + invalidSelections$.next(new Set([])); + } + } else if (showOnlySelected || selectedOptions.includes(keyAsType)) { + componentApi.deselectOption(key); + } else if (singleSelect) { + // replace selection + selections.setSelectedOptions([keyAsType]); + if (existsSelected) selections.setExistsSelected(false); + } else { + // select option + if (existsSelected) selections.setExistsSelected(false); + selections.setSelectedOptions( + selectedOptions ? [...selectedOptions, keyAsType] : [keyAsType] + ); + } + }, + }; + + if (selections.hasInitialSelections) { + await dataControl.api.untilFiltersReady(); + } + + return { + api, + Component: ({ className: controlPanelClassName }) => { + useEffect(() => { + return () => { + // on unmount, clean up all subscriptions + dataLoadingSubscription.unsubscribe(); + fetchSubscription.unsubscribe(); + fieldChangedSubscription.unsubscribe(); + outputFilterSubscription.unsubscribe(); + singleSelectSubscription.unsubscribe(); + validSearchStringSubscription.unsubscribe(); + }; + }, []); + + /** Get display settings - if these are ever made editable, should be part of stateManager instead */ + const [placeholder, hideActionBar, hideExclude, hideExists, hideSort] = + useBatchedPublishingSubjects( + placeholder$, + hideActionBar$, + hideExclude$, + hideExists$, + hideSort$ + ); + + return ( + <OptionsListControlContext.Provider + value={{ + stateManager, + api: componentApi, + displaySettings: { placeholder, hideActionBar, hideExclude, hideExists, hideSort }, + }} + > + <OptionsListControl controlPanelClassName={controlPanelClassName} /> + </OptionsListControlContext.Provider> + ); + }, + }; + }, + }; +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_context_provider.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_context_provider.tsx new file mode 100644 index 0000000000000..b9e5852b7c699 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_context_provider.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 and the Server Side Public License, v 1; you may not 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 { PublishingSubject } from '@kbn/presentation-publishing'; +import { ControlStateManager } from '../../types'; +import { + OptionsListComponentApi, + OptionsListComponentState, + OptionsListDisplaySettings, +} from './types'; +import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; + +export type ContextStateManager = ControlStateManager< + Omit<OptionsListComponentState, 'exclude' | 'existsSelected' | 'selectedOptions'> +> & { + selectedOptions: PublishingSubject<OptionsListSelection[] | undefined>; + existsSelected: PublishingSubject<boolean | undefined>; + exclude: PublishingSubject<boolean | undefined>; +}; + +export const OptionsListControlContext = React.createContext< + | { + api: OptionsListComponentApi; + stateManager: ContextStateManager; + displaySettings: OptionsListDisplaySettings; + } + | undefined +>(undefined); + +export const useOptionsListContext = () => { + const optionsListContext = useContext(OptionsListControlContext); + if (!optionsListContext) + throw new Error( + 'No OptionsListControlContext.Provider found when calling useOptionsListContext.' + ); + return optionsListContext; +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_control_selections.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_control_selections.ts new file mode 100644 index 0000000000000..3b3f0aea7ed38 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_control_selections.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 { BehaviorSubject } from 'rxjs'; +import deepEqual from 'react-fast-compare'; +import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing'; +import { OptionsListControlState } from './types'; +import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; + +export function initializeOptionsListSelections( + initialState: OptionsListControlState, + onSelectionChange: () => void +) { + const selectedOptions$ = new BehaviorSubject<OptionsListSelection[] | undefined>( + initialState.selectedOptions ?? [] + ); + const selectedOptionsComparatorFunction = ( + a: OptionsListSelection[] | undefined, + b: OptionsListSelection[] | undefined + ) => deepEqual(a ?? [], b ?? []); + function setSelectedOptions(next: OptionsListSelection[] | undefined) { + if (!selectedOptionsComparatorFunction(selectedOptions$.value, next)) { + selectedOptions$.next(next); + onSelectionChange(); + } + } + + const existsSelected$ = new BehaviorSubject<boolean | undefined>(initialState.existsSelected); + function setExistsSelected(next: boolean | undefined) { + if (existsSelected$.value !== next) { + existsSelected$.next(next); + onSelectionChange(); + } + } + + const exclude$ = new BehaviorSubject<boolean | undefined>(initialState.exclude); + function setExclude(next: boolean | undefined) { + if (exclude$.value !== next) { + exclude$.next(next); + onSelectionChange(); + } + } + + return { + comparators: { + exclude: [exclude$, setExclude], + existsSelected: [existsSelected$, setExistsSelected], + selectedOptions: [selectedOptions$, setSelectedOptions, selectedOptionsComparatorFunction], + } as StateComparators< + Pick<OptionsListControlState, 'exclude' | 'existsSelected' | 'selectedOptions'> + >, + hasInitialSelections: initialState.selectedOptions?.length || initialState.existsSelected, + selectedOptions$: selectedOptions$ as PublishingSubject<OptionsListSelection[] | undefined>, + setSelectedOptions, + existsSelected$: existsSelected$ as PublishingSubject<boolean | undefined>, + setExistsSelected, + exclude$: exclude$ as PublishingSubject<boolean | undefined>, + setExclude, + }; +} diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_fetch_cache.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_fetch_cache.ts new file mode 100644 index 0000000000000..7b30504e3ab1c --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_fetch_cache.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 LRUCache from 'lru-cache'; +import hash from 'object-hash'; + +import dateMath from '@kbn/datemath'; + +import { getEsQueryConfig } from '@kbn/data-plugin/public'; +import { buildEsQuery } from '@kbn/es-query'; +import { + type OptionsListFailureResponse, + type OptionsListRequest, + type OptionsListResponse, + type OptionsListSuccessResponse, +} from '../../../../../common/options_list/types'; +import { DataControlServices } from '../types'; + +const REQUEST_CACHE_SIZE = 50; // only store a max of 50 responses +const REQUEST_CACHE_TTL = 1000 * 60; // time to live = 1 minute + +const optionsListResponseWasFailure = ( + response: OptionsListResponse +): response is OptionsListFailureResponse => { + return (response as OptionsListFailureResponse).error !== undefined; +}; + +export class OptionsListFetchCache { + private cache: LRUCache<string, OptionsListSuccessResponse>; + + constructor() { + this.cache = new LRUCache<string, OptionsListSuccessResponse>({ + max: REQUEST_CACHE_SIZE, + maxAge: REQUEST_CACHE_TTL, + }); + } + + private getRequestHash = (request: OptionsListRequest) => { + const { + size, + sort, + query, + filters, + timeRange, + searchString, + runPastTimeout, + selectedOptions, + searchTechnique, + field: { name: fieldName }, + dataView: { title: dataViewTitle }, + } = request; + return hash({ + // round timeRange to the minute to avoid cache misses + timeRange: timeRange + ? JSON.stringify({ + from: dateMath.parse(timeRange.from)!.startOf('minute').toISOString(), + to: dateMath.parse(timeRange.to)!.endOf('minute').toISOString(), + }) + : [], + selectedOptions, + filters, + query, + sort, + searchTechnique, + runPastTimeout, + dataViewTitle, + searchString: searchString ?? '', + fieldName, + size, + }); + }; + + public async runFetchRequest( + request: OptionsListRequest, + abortSignal: AbortSignal, + services: DataControlServices + ): Promise<OptionsListResponse> { + const requestHash = this.getRequestHash(request); + + if (this.cache.has(requestHash)) { + return Promise.resolve(this.cache.get(requestHash)!); + } else { + const index = request.dataView.getIndexPattern(); + + const timeService = services.data.query.timefilter.timefilter; + const { query, filters, dataView, timeRange, field, ...passThroughProps } = request; + const timeFilter = timeRange ? timeService.createFilter(dataView, timeRange) : undefined; + const filtersToUse = [...(filters ?? []), ...(timeFilter ? [timeFilter] : [])]; + const config = getEsQueryConfig(services.core.uiSettings); + const esFilters = [buildEsQuery(dataView, query ?? [], filtersToUse ?? [], config)]; + + const requestBody = { + ...passThroughProps, + filters: esFilters, + fieldName: field.name, + fieldSpec: field, + runtimeFieldMap: dataView.toSpec?.().runtimeFieldMap, + }; + + const result = await services.core.http.fetch<OptionsListResponse>( + `/internal/controls/optionsList/${index}`, + { + version: '1', + body: JSON.stringify(requestBody), + signal: abortSignal, + method: 'POST', + } + ); + + if (!optionsListResponseWasFailure(result)) { + // only add the success responses to the cache + this.cache.set(requestHash, result); + } + return result; + } + } +} diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_strings.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_strings.ts new file mode 100644 index 0000000000000..4ed8f72ed4cda --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/options_list_strings.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 and the Server Side Public License, v 1; you may not 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 { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching'; + +export const OptionsListStrings = { + control: { + getDisplayName: () => + i18n.translate('controls.optionsList.displayName', { + defaultMessage: 'Options list', + }), + getSeparator: (type?: string) => { + if (['date', 'number'].includes(type ?? '')) { + return i18n.translate('controls.optionsList.control.dateSeparator', { + defaultMessage: '; ', + }); + } + return i18n.translate('controls.optionsList.control.separator', { + defaultMessage: ', ', + }); + }, + getPlaceholder: () => + i18n.translate('controls.optionsList.control.placeholder', { + defaultMessage: 'Any', + }), + getNegate: () => + i18n.translate('controls.optionsList.control.negate', { + defaultMessage: 'NOT', + }), + getExcludeExists: () => + i18n.translate('controls.optionsList.control.excludeExists', { + defaultMessage: 'DOES NOT', + }), + getInvalidSelectionWarningLabel: (invalidSelectionCount: number) => + i18n.translate('controls.optionsList.control.invalidSelectionWarningLabel', { + defaultMessage: + '{invalidSelectionCount} {invalidSelectionCount, plural, one {selection returns} other {selections return}} no results.', + values: { + invalidSelectionCount, + }, + }), + getInvalidSelectionMessage: () => + i18n.translate('controls.optionsList.popover.selectionError', { + defaultMessage: 'There was an error when making your selection', + }), + }, + editor: { + getSelectionOptionsTitle: () => + i18n.translate('controls.optionsList.editor.selectionOptionsTitle', { + defaultMessage: 'Selections', + }), + selectionTypes: { + multi: { + getLabel: () => + i18n.translate('controls.optionsList.editor.multiSelectLabel', { + defaultMessage: 'Allow multiple selections', + }), + }, + single: { + getLabel: () => + i18n.translate('controls.optionsList.editor.singleSelectLabel', { + defaultMessage: 'Only allow a single selection', + }), + }, + }, + getSearchOptionsTitle: () => + i18n.translate('controls.optionsList.editor.searchOptionsTitle', { + defaultMessage: `Searching`, + }), + searchTypes: { + prefix: { + getLabel: () => + i18n.translate('controls.optionsList.editor.prefixSearchLabel', { + defaultMessage: 'Prefix', + }), + getTooltip: () => + i18n.translate('controls.optionsList.editor.prefixSearchTooltip', { + defaultMessage: 'Matches values that begin with the given search string.', + }), + }, + wildcard: { + getLabel: () => + i18n.translate('controls.optionsList.editor.wildcardSearchLabel', { + defaultMessage: 'Contains', + }), + getTooltip: () => + i18n.translate('controls.optionsList.editor.wildcardSearchTooltip', { + defaultMessage: + 'Matches values that contain the given search string. Results might take longer to populate.', + }), + }, + exact: { + getLabel: () => + i18n.translate('controls.optionsList.editor.exactSearchLabel', { + defaultMessage: 'Exact', + }), + getTooltip: () => + i18n.translate('controls.optionsList.editor.exactSearchTooltip', { + defaultMessage: + 'Matches values that are equal to the given search string. Returns results quickly.', + }), + }, + }, + getAdditionalSettingsTitle: () => + i18n.translate('controls.optionsList.editor.additionalSettingsTitle', { + defaultMessage: `Additional settings`, + }), + getRunPastTimeoutTitle: () => + i18n.translate('controls.optionsList.editor.runPastTimeout', { + defaultMessage: 'Ignore timeout for results', + }), + getRunPastTimeoutTooltip: () => + i18n.translate('controls.optionsList.editor.runPastTimeout.tooltip', { + defaultMessage: + 'Wait to display results until the list is complete. This setting is useful for large data sets, but the results might take longer to populate.', + }), + }, + popover: { + getAriaLabel: (fieldName: string) => + i18n.translate('controls.optionsList.popover.ariaLabel', { + defaultMessage: 'Popover for {fieldName} control', + values: { fieldName }, + }), + getSuggestionsAriaLabel: (fieldName: string, optionCount: number) => + i18n.translate('controls.optionsList.popover.suggestionsAriaLabel', { + defaultMessage: + 'Available {optionCount, plural, one {option} other {options}} for {fieldName}', + values: { fieldName, optionCount }, + }), + getAllowExpensiveQueriesWarning: () => + i18n.translate('controls.optionsList.popover.allowExpensiveQueriesWarning', { + defaultMessage: + 'The cluster setting to allow expensive queries is off, so some features are disabled.', + }), + getLoadingMoreMessage: () => + i18n.translate('controls.optionsList.popover.loadingMore', { + defaultMessage: 'Loading more options...', + }), + getAtEndOfOptionsMessage: () => + i18n.translate('controls.optionsList.popover.endOfOptions', { + defaultMessage: + 'The top 1,000 available options are displayed. View more options by searching for the name.', + }), + getEmptyMessage: () => + i18n.translate('controls.optionsList.popover.empty', { + defaultMessage: 'No options found', + }), + getSelectionsEmptyMessage: () => + i18n.translate('controls.optionsList.popover.selectionsEmpty', { + defaultMessage: 'You have no selections', + }), + getInvalidSearchMessage: (fieldType: string) => { + switch (fieldType) { + case 'ip': { + return i18n.translate('controls.optionsList.popover.invalidSearch.ip', { + defaultMessage: 'Your search is not a valid IP address.', + }); + } + case 'number': { + return i18n.translate('controls.optionsList.popover.invalidSearch.number', { + defaultMessage: 'Your search is not a valid number.', + }); + } + default: { + // this shouldn't happen, but giving a fallback error message just in case + return i18n.translate('controls.optionsList.popover.invalidSearch.invalidCharacters', { + defaultMessage: 'Your search contains invalid characters.', + }); + } + } + }, + getAllOptionsButtonTitle: () => + i18n.translate('controls.optionsList.popover.allOptionsTitle', { + defaultMessage: 'Show all options', + }), + getSelectedOptionsButtonTitle: () => + i18n.translate('controls.optionsList.popover.selectedOptionsTitle', { + defaultMessage: 'Show only selected options', + }), + getSearchPlaceholder: (searchTechnique?: OptionsListSearchTechnique) => { + switch (searchTechnique) { + case 'prefix': { + return i18n.translate('controls.optionsList.popover.prefixSearchPlaceholder', { + defaultMessage: 'Starts with...', + }); + } + case 'wildcard': { + return i18n.translate('controls.optionsList.popover.wildcardSearchPlaceholder', { + defaultMessage: 'Contains...', + }); + } + case 'exact': { + return i18n.translate('controls.optionsList.popover.exactSearchPlaceholder', { + defaultMessage: 'Equals...', + }); + } + } + }, + getCardinalityLabel: (totalOptions: number) => + i18n.translate('controls.optionsList.popover.cardinalityLabel', { + defaultMessage: + '{totalOptions, number} {totalOptions, plural, one {option} other {options}}', + values: { totalOptions }, + }), + getInvalidSelectionsSectionAriaLabel: (fieldName: string, invalidSelectionCount: number) => + i18n.translate('controls.optionsList.popover.invalidSelectionsAriaLabel', { + defaultMessage: + 'Invalid {invalidSelectionCount, plural, one {selection} other {selections}} for {fieldName}', + values: { fieldName, invalidSelectionCount }, + }), + getInvalidSelectionsSectionTitle: (invalidSelectionCount: number) => + i18n.translate('controls.optionsList.popover.invalidSelectionsSectionTitle', { + defaultMessage: + 'Invalid {invalidSelectionCount, plural, one {selection} other {selections}}', + values: { invalidSelectionCount }, + }), + getInvalidSelectionsLabel: (selectedOptions: number) => + i18n.translate('controls.optionsList.popover.invalidSelectionsLabel', { + defaultMessage: + '{selectedOptions} {selectedOptions, plural, one {selection} other {selections}} invalid', + values: { selectedOptions }, + }), + getInvalidSelectionScreenReaderText: () => + i18n.translate('controls.optionsList.popover.invalidSelectionScreenReaderText', { + defaultMessage: 'Invalid selection.', + }), + getIncludeLabel: () => + i18n.translate('controls.optionsList.popover.includeLabel', { + defaultMessage: 'Include', + }), + getExcludeLabel: () => + i18n.translate('controls.optionsList.popover.excludeLabel', { + defaultMessage: 'Exclude', + }), + getIncludeExcludeLegend: () => + i18n.translate('controls.optionsList.popover.excludeOptionsLegend', { + defaultMessage: 'Include or exclude selections', + }), + getSortPopoverTitle: () => + i18n.translate('controls.optionsList.popover.sortTitle', { + defaultMessage: 'Sort', + }), + getSortPopoverDescription: () => + i18n.translate('controls.optionsList.popover.sortDescription', { + defaultMessage: 'Define the sort order', + }), + getSortDisabledTooltip: () => + i18n.translate('controls.optionsList.popover.sortDisabledTooltip', { + defaultMessage: 'Sorting is ignored when “Show only selected” is true', + }), + getDocumentCountTooltip: (documentCount: number) => + i18n.translate('controls.optionsList.popover.documentCountTooltip', { + defaultMessage: + 'This value appears in {documentCount, number} {documentCount, plural, one {document} other {documents}}', + values: { documentCount }, + }), + getDocumentCountScreenReaderText: (documentCount: number) => + i18n.translate('controls.optionsList.popover.documentCountScreenReaderText', { + defaultMessage: + 'Appears in {documentCount, number} {documentCount, plural, one {document} other {documents}}', + values: { documentCount }, + }), + }, + controlAndPopover: { + getExists: (negate: number = +false) => + i18n.translate('controls.optionsList.controlAndPopover.exists', { + defaultMessage: '{negate, plural, one {Exist} other {Exists}}', + values: { negate }, + }), + }, + editorAndPopover: { + getSortDirectionLegend: () => + i18n.translate('controls.optionsList.popover.sortDirections', { + defaultMessage: 'Sort directions', + }), + sortBy: { + _count: { + getSortByLabel: () => + i18n.translate('controls.optionsList.popover.sortBy.docCount', { + defaultMessage: 'By document count', + }), + }, + _key: { + getSortByLabel: (type?: string) => { + switch (type) { + case 'date': + return i18n.translate('controls.optionsList.popover.sortBy.date', { + defaultMessage: 'By date', + }); + case 'number': + return i18n.translate('controls.optionsList.popover.sortBy.numeric', { + defaultMessage: 'Numerically', + }); + default: + return i18n.translate('controls.optionsList.popover.sortBy.alphabetical', { + defaultMessage: 'Alphabetically', + }); + } + }, + }, + }, + sortOrder: { + asc: { + getSortOrderLabel: () => + i18n.translate('controls.optionsList.popover.sortOrder.asc', { + defaultMessage: 'Ascending', + }), + }, + desc: { + getSortOrderLabel: () => + i18n.translate('controls.optionsList.popover.sortOrder.desc', { + defaultMessage: 'Descending', + }), + }, + }, + }, +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/register_options_list_control.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/register_options_list_control.ts new file mode 100644 index 0000000000000..c3b7723f69bb7 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/register_options_list_control.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 { CoreSetup } from '@kbn/core/public'; +import type { ControlsPluginStartDeps } from '../../../../types'; +import { registerControlFactory } from '../../../control_factory_registry'; +import { OPTIONS_LIST_CONTROL } from '../../../../../common'; + +export function registerOptionsListControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) { + registerControlFactory(OPTIONS_LIST_CONTROL, async () => { + const [{ getOptionsListControlFactory }, [coreStart, depsStart]] = await Promise.all([ + import('./get_options_list_control_factory'), + coreSetup.getStartServices(), + ]); + return getOptionsListControlFactory({ + core: coreStart, + data: depsStart.data, + dataViews: depsStart.data.dataViews, + }); + }); +} diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/types.ts b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/types.ts new file mode 100644 index 0000000000000..c564c583df5aa --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/options_list_control/types.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 { BehaviorSubject } from 'rxjs'; + +import { PublishingSubject } from '@kbn/presentation-publishing'; + +import { OptionsListSearchTechnique } from '../../../../../common/options_list/suggestions_searching'; +import { OptionsListSortingType } from '../../../../../common/options_list/suggestions_sorting'; +import { OptionsListSuggestions } from '../../../../../common/options_list/types'; +import { OptionsListSelection } from '../../../../../common/options_list/options_list_selections'; +import { DataControlApi, DefaultDataControlState } from '../types'; + +export interface OptionsListDisplaySettings { + placeholder?: string; + hideActionBar?: boolean; + hideExclude?: boolean; + hideExists?: boolean; + hideSort?: boolean; +} + +export interface OptionsListControlState + extends DefaultDataControlState, + OptionsListDisplaySettings { + searchTechnique?: OptionsListSearchTechnique; + sort?: OptionsListSortingType; + selectedOptions?: OptionsListSelection[]; + existsSelected?: boolean; + runPastTimeout?: boolean; + singleSelect?: boolean; + exclude?: boolean; +} + +export type OptionsListControlApi = DataControlApi; + +export interface OptionsListComponentState + extends Omit<OptionsListControlState, keyof OptionsListDisplaySettings> { + searchString: string; + searchStringValid: boolean; + requestSize: number; +} + +interface PublishesOptions { + availableOptions$: PublishingSubject<OptionsListSuggestions | undefined>; + invalidSelections$: PublishingSubject<Set<OptionsListSelection>>; + totalCardinality$: PublishingSubject<number>; +} + +export type OptionsListComponentApi = OptionsListControlApi & + PublishesOptions & { + deselectOption: (key: string | undefined) => void; + makeSelection: (key: string | undefined, showOnlySelected: boolean) => void; + loadMoreSubject: BehaviorSubject<null>; + setExclude: (next: boolean | undefined) => void; + }; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/publishes_async_filters.ts b/src/plugins/controls/public/react_controls/controls/data_controls/publishes_async_filters.ts new file mode 100644 index 0000000000000..18e94d048f98f --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/publishes_async_filters.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 { PublishesFilters, apiPublishesFilters } from '@kbn/presentation-publishing'; + +/** + * Data control filter generation is async because + * 1) filter generation requires a DataView + * 2) filter generation is a subscription + */ +export type PublishesAsyncFilters = PublishesFilters & { + untilFiltersReady: () => Promise<void>; +}; + +export const apiPublishesAsyncFilters = ( + unknownApi: unknown +): unknownApi is PublishesAsyncFilters => { + return Boolean( + unknownApi && + apiPublishesFilters(unknownApi) && + (unknownApi as PublishesAsyncFilters)?.untilFiltersReady !== undefined + ); +}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/components/range_slider.scss b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/components/range_slider.scss similarity index 100% rename from examples/controls_example/public/react_controls/data_controls/range_slider/components/range_slider.scss rename to src/plugins/controls/public/react_controls/controls/data_controls/range_slider/components/range_slider.scss diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/components/range_slider_control.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/components/range_slider_control.tsx similarity index 99% rename from examples/controls_example/public/react_controls/data_controls/range_slider/components/range_slider_control.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/range_slider/components/range_slider_control.tsx index f52dd0c3e528b..b653cc9542588 100644 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/components/range_slider_control.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/components/range_slider_control.tsx @@ -21,7 +21,7 @@ interface Props { max: number | undefined; min: number | undefined; onChange: (value: RangeValue | undefined) => void; - step: number | undefined; + step: number; value: RangeValue | undefined; uuid: string; controlPanelClassName?: string; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx similarity index 84% rename from examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx rename to src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx index 3746d81f44473..110de033520de 100644 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.test.tsx @@ -7,22 +7,19 @@ */ import React from 'react'; -import { BehaviorSubject, of } from 'rxjs'; +import { of } from 'rxjs'; import { estypes } from '@elastic/elasticsearch'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { DataViewField } from '@kbn/data-views-plugin/common'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import { TimeRange } from '@kbn/es-query'; import { SerializedPanelState } from '@kbn/presentation-containers'; -import { StateComparators } from '@kbn/presentation-publishing'; import { fireEvent, render, waitFor } from '@testing-library/react'; -import { ControlFetchContext } from '../../control_group/control_fetch'; -import { ControlGroupApi } from '../../control_group/types'; -import { ControlApiRegistration } from '../../types'; +import { getMockedBuildApi, getMockedControlGroupApi } from '../../mocks/control_mocks'; import { getRangesliderControlFactory } from './get_range_slider_control_factory'; -import { RangesliderControlApi, RangesliderControlState } from './types'; +import { RangesliderControlState } from './types'; const DEFAULT_TOTAL_RESULTS = 20; const DEFAULT_MIN = 0; @@ -30,14 +27,9 @@ const DEFAULT_MAX = 1000; describe('RangesliderControlApi', () => { const uuid = 'myControl1'; - const dashboardApi = { - timeRange$: new BehaviorSubject<TimeRange | undefined>(undefined), - }; - const controlGroupApi = { - controlFetch$: () => new BehaviorSubject<ControlFetchContext>({}), - ignoreParentSettings$: new BehaviorSubject(undefined), - parentApi: dashboardApi, - } as unknown as ControlGroupApi; + + const controlGroupApi = getMockedControlGroupApi(); + const dataStartServiceMock = dataPluginMock.createStartContract(); let totalResults = DEFAULT_TOTAL_RESULTS; let min: estypes.AggregationsSingleMetricAggregateBase['value'] = DEFAULT_MIN; @@ -62,8 +54,8 @@ describe('RangesliderControlApi', () => { }; }); const mockDataViews = dataViewPluginMocks.createStartContract(); - // @ts-ignore - mockDataViews.get = async (id: string): Promise<DataView> => { + + mockDataViews.get = jest.fn().mockImplementation(async (id: string): Promise<DataView> => { if (id !== 'myDataViewId') { throw new Error(`no data view found for id ${id}`); } @@ -74,7 +66,8 @@ describe('RangesliderControlApi', () => { { displayName: 'My field name', name: 'myFieldName', - type: 'string', + type: 'number', + toSpec: jest.fn(), }, ].find((field) => fieldName === field.name); }, @@ -86,7 +79,8 @@ describe('RangesliderControlApi', () => { }; }, } as unknown as DataView; - }; + }); + const factory = getRangesliderControlFactory({ core: coreMock.createStart(), data: dataStartServiceMock, @@ -99,28 +93,14 @@ describe('RangesliderControlApi', () => { max = DEFAULT_MAX; }); - function buildApiMock( - api: ControlApiRegistration<RangesliderControlApi>, - nextComparitors: StateComparators<RangesliderControlState> - ) { - return { - ...api, - uuid, - parentApi: controlGroupApi, - unsavedChanges: new BehaviorSubject<Partial<RangesliderControlState> | undefined>(undefined), - resetUnsavedChanges: () => {}, - type: factory.type, - }; - } - - describe('on initialize', () => { + describe('filters$', () => { test('should not set filters$ when value is not provided', async () => { const { api } = await factory.buildControl( { dataViewId: 'myDataView', fieldName: 'myFieldName', }, - buildApiMock, + getMockedBuildApi(uuid, factory, controlGroupApi), uuid, controlGroupApi ); @@ -134,7 +114,7 @@ describe('RangesliderControlApi', () => { fieldName: 'myFieldName', value: ['5', '10'], }, - buildApiMock, + getMockedBuildApi(uuid, factory, controlGroupApi), uuid, controlGroupApi ); @@ -169,7 +149,7 @@ describe('RangesliderControlApi', () => { fieldName: 'myFieldName', value: ['5', '10'], }, - buildApiMock, + getMockedBuildApi(uuid, factory, controlGroupApi), uuid, controlGroupApi ); @@ -191,7 +171,7 @@ describe('RangesliderControlApi', () => { fieldName: 'myFieldName', value: ['5', '10'], }, - buildApiMock, + getMockedBuildApi(uuid, factory, controlGroupApi), uuid, controlGroupApi ); @@ -209,7 +189,7 @@ describe('RangesliderControlApi', () => { dataViewId: 'myDataViewId', fieldName: 'myFieldName', }, - buildApiMock, + getMockedBuildApi(uuid, factory, controlGroupApi), uuid, controlGroupApi ); @@ -230,7 +210,7 @@ describe('RangesliderControlApi', () => { dataViewId: 'myDataViewId', fieldName: 'myFieldName', }, - buildApiMock, + getMockedBuildApi(uuid, factory, controlGroupApi), uuid, controlGroupApi ); @@ -245,7 +225,7 @@ describe('RangesliderControlApi', () => { fieldName: 'myFieldName', step: 1024, }, - buildApiMock, + getMockedBuildApi(uuid, factory, controlGroupApi), uuid, controlGroupApi ); @@ -259,9 +239,11 @@ describe('RangesliderControlApi', () => { const CustomSettings = factory.CustomOptionsComponent!; const component = render( <CustomSettings - currentState={{}} + initialState={{} as RangesliderControlState} + field={{} as DataViewField} updateState={jest.fn()} setControlEditorValid={jest.fn()} + parentApi={controlGroupApi} /> ); expect( @@ -274,9 +256,11 @@ describe('RangesliderControlApi', () => { const CustomSettings = factory.CustomOptionsComponent!; const component = render( <CustomSettings - currentState={{}} + initialState={{} as RangesliderControlState} + field={{} as DataViewField} updateState={jest.fn()} setControlEditorValid={setControlEditorValid} + parentApi={controlGroupApi} /> ); @@ -285,7 +269,7 @@ describe('RangesliderControlApi', () => { }); expect(setControlEditorValid).toBeCalledWith(false); fireEvent.change(component.getByTestId('rangeSliderControl__stepAdditionalSetting'), { - target: { value: '' }, + target: { value: undefined }, }); expect(setControlEditorValid).toBeCalledWith(false); fireEvent.change(component.getByTestId('rangeSliderControl__stepAdditionalSetting'), { diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.tsx b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.tsx new file mode 100644 index 0000000000000..cc953e3109a07 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/get_range_slider_control_factory.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 React, { useEffect, useState } from 'react'; +import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; +import { buildRangeFilter, Filter, RangeFilterParams } from '@kbn/es-query'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; +import { BehaviorSubject, combineLatest, debounceTime, map, skip } from 'rxjs'; +import { initializeDataControl } from '../initialize_data_control'; +import { DataControlFactory, DataControlServices } from '../types'; +import { RangeSliderControl } from './components/range_slider_control'; +import { hasNoResults$ } from './has_no_results'; +import { minMax$ } from './min_max'; +import { RangeSliderStrings } from './range_slider_strings'; +import { RangesliderControlApi, RangesliderControlState } from './types'; +import { initializeRangeControlSelections } from './range_control_selections'; +import { RANGE_SLIDER_CONTROL } from '../../../../../common'; + +export const getRangesliderControlFactory = ( + services: DataControlServices +): DataControlFactory<RangesliderControlState, RangesliderControlApi> => { + return { + type: RANGE_SLIDER_CONTROL, + getIconType: () => 'controlsHorizontal', + getDisplayName: RangeSliderStrings.control.getDisplayName, + isFieldCompatible: (field) => { + return field.aggregatable && field.type === 'number'; + }, + CustomOptionsComponent: ({ initialState, updateState, setControlEditorValid }) => { + const [step, setStep] = useState(initialState.step ?? 1); + + return ( + <> + <EuiFormRow fullWidth label={RangeSliderStrings.editor.getStepTitle()}> + <EuiFieldNumber + value={step} + onChange={(event) => { + const newStep = event.target.valueAsNumber; + setStep(newStep); + updateState({ step: newStep }); + setControlEditorValid(newStep > 0); + }} + min={0} + isInvalid={step === undefined || step <= 0} + data-test-subj="rangeSliderControl__stepAdditionalSetting" + /> + </EuiFormRow> + </> + ); + }, + buildControl: async (initialState, buildApi, uuid, controlGroupApi) => { + const controlFetch$ = controlGroupApi.controlFetch$(uuid); + const loadingMinMax$ = new BehaviorSubject<boolean>(false); + const loadingHasNoResults$ = new BehaviorSubject<boolean>(false); + const dataLoading$ = new BehaviorSubject<boolean | undefined>(undefined); + const step$ = new BehaviorSubject<number | undefined>(initialState.step ?? 1); + + const dataControl = initializeDataControl<Pick<RangesliderControlState, 'step'>>( + uuid, + RANGE_SLIDER_CONTROL, + initialState, + { + step: step$, + }, + controlGroupApi, + services + ); + + const selections = initializeRangeControlSelections( + initialState, + dataControl.setters.onSelectionChange + ); + + const api = buildApi( + { + ...dataControl.api, + dataLoading: dataLoading$, + getTypeDisplayName: RangeSliderStrings.control.getDisplayName, + serializeState: () => { + const { rawState: dataControlState, references } = dataControl.serialize(); + return { + rawState: { + ...dataControlState, + step: step$.getValue(), + value: selections.value$.getValue(), + }, + references, // does not have any references other than those provided by the data control serializer + }; + }, + clearSelections: () => { + selections.setValue(undefined); + }, + }, + { + ...dataControl.comparators, + ...selections.comparators, + step: [ + step$, + (nextStep: number | undefined) => step$.next(nextStep), + (a, b) => (a ?? 1) === (b ?? 1), + ], + } + ); + + const dataLoadingSubscription = combineLatest([loadingMinMax$, loadingHasNoResults$]) + .pipe( + map((values) => { + return values.some((value) => { + return value; + }); + }) + ) + .subscribe((isLoading) => { + dataLoading$.next(isLoading); + }); + + // Clear state when the field changes + const fieldChangedSubscription = combineLatest([ + dataControl.stateManager.fieldName, + dataControl.stateManager.dataViewId, + ]) + .pipe(skip(1)) + .subscribe(() => { + step$.next(1); + selections.setValue(undefined); + }); + + const max$ = new BehaviorSubject<number | undefined>(undefined); + const min$ = new BehaviorSubject<number | undefined>(undefined); + const minMaxSubscription = minMax$({ + controlFetch$, + data: services.data, + dataViews$: dataControl.api.dataViews, + fieldName$: dataControl.stateManager.fieldName, + setIsLoading: (isLoading: boolean) => { + // clear previous loading error on next loading start + if (isLoading && dataControl.api.blockingError.value) { + dataControl.api.setBlockingError(undefined); + } + loadingMinMax$.next(isLoading); + }, + }).subscribe( + ({ + error, + min, + max, + }: { + error?: Error; + min: number | undefined; + max: number | undefined; + }) => { + if (error) { + dataControl.api.setBlockingError(error); + } + max$.next(max); + min$.next(min); + } + ); + + const outputFilterSubscription = combineLatest([ + dataControl.api.dataViews, + dataControl.stateManager.fieldName, + selections.value$, + ]) + .pipe(debounceTime(0)) + .subscribe(([dataViews, fieldName, value]) => { + const dataView = dataViews?.[0]; + const dataViewField = + dataView && fieldName ? dataView.getFieldByName(fieldName) : undefined; + const gte = parseFloat(value?.[0] ?? ''); + const lte = parseFloat(value?.[1] ?? ''); + + let rangeFilter: Filter | undefined; + if (value && dataView && dataViewField && !isNaN(gte) && !isNaN(lte)) { + const params = { + gte, + lte, + } as RangeFilterParams; + + rangeFilter = buildRangeFilter(dataViewField, params, dataView); + rangeFilter.meta.key = fieldName; + rangeFilter.meta.type = 'range'; + rangeFilter.meta.params = params; + } + dataControl.setters.setOutputFilter(rangeFilter); + }); + + const selectionHasNoResults$ = new BehaviorSubject(false); + const hasNotResultsSubscription = hasNoResults$({ + controlFetch$, + data: services.data, + dataViews$: dataControl.api.dataViews, + rangeFilters$: dataControl.api.filters$, + ignoreParentSettings$: controlGroupApi.ignoreParentSettings$, + setIsLoading: (isLoading: boolean) => { + loadingHasNoResults$.next(isLoading); + }, + }).subscribe((hasNoResults) => { + selectionHasNoResults$.next(hasNoResults); + }); + + if (selections.hasInitialSelections) { + await dataControl.api.untilFiltersReady(); + } + + return { + api, + Component: ({ className: controlPanelClassName }) => { + const [dataLoading, fieldFormatter, max, min, selectionHasNotResults, step, value] = + useBatchedPublishingSubjects( + dataLoading$, + dataControl.api.fieldFormatter, + max$, + min$, + selectionHasNoResults$, + step$, + selections.value$ + ); + + useEffect(() => { + return () => { + dataLoadingSubscription.unsubscribe(); + fieldChangedSubscription.unsubscribe(); + hasNotResultsSubscription.unsubscribe(); + minMaxSubscription.unsubscribe(); + outputFilterSubscription.unsubscribe(); + }; + }, []); + + return ( + <RangeSliderControl + controlPanelClassName={controlPanelClassName} + fieldFormatter={fieldFormatter} + isInvalid={selectionHasNotResults} + isLoading={typeof dataLoading === 'boolean' ? dataLoading : false} + max={max} + min={min} + onChange={selections.setValue} + step={step ?? 1} + value={value} + uuid={uuid} + /> + ); + }, + }; + }, + }; +}; diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/has_no_results.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/has_no_results.ts similarity index 96% rename from examples/controls_example/public/react_controls/data_controls/range_slider/has_no_results.ts rename to src/plugins/controls/public/react_controls/controls/data_controls/range_slider/has_no_results.ts index 1a1e62dfde209..3b7b21eadc5da 100644 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/has_no_results.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/has_no_results.ts @@ -12,8 +12,8 @@ import { DataView } from '@kbn/data-views-plugin/public'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { PublishesDataViews } from '@kbn/presentation-publishing'; import { combineLatest, lastValueFrom, Observable, switchMap, tap } from 'rxjs'; -import { ControlFetchContext } from '../../control_group/control_fetch'; -import { ControlGroupApi } from '../../control_group/types'; +import { ControlFetchContext } from '../../../control_group/control_fetch'; +import { ControlGroupApi } from '../../../control_group/types'; import { DataControlApi } from '../types'; export function hasNoResults$({ diff --git a/examples/controls_example/public/react_controls/data_controls/range_slider/min_max.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/min_max.ts similarity index 98% rename from examples/controls_example/public/react_controls/data_controls/range_slider/min_max.ts rename to src/plugins/controls/public/react_controls/controls/data_controls/range_slider/min_max.ts index 2f65e523c7441..54ee8bf68031d 100644 --- a/examples/controls_example/public/react_controls/data_controls/range_slider/min_max.ts +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/min_max.ts @@ -12,7 +12,7 @@ import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import { PublishesDataViews, PublishingSubject } from '@kbn/presentation-publishing'; import { combineLatest, lastValueFrom, Observable, switchMap, tap } from 'rxjs'; -import { ControlFetchContext } from '../../control_group/control_fetch'; +import { ControlFetchContext } from '../../../control_group/control_fetch'; export function minMax$({ controlFetch$, diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/range_control_selections.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/range_control_selections.ts new file mode 100644 index 0000000000000..b8c88b249c799 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/range_control_selections.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 { BehaviorSubject } from 'rxjs'; +import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing'; +import { RangeValue, RangesliderControlState } from './types'; + +export function initializeRangeControlSelections( + initialState: RangesliderControlState, + onSelectionChange: () => void +) { + const value$ = new BehaviorSubject<RangeValue | undefined>(initialState.value); + function setValue(next: RangeValue | undefined) { + if (value$.value !== next) { + value$.next(next); + onSelectionChange(); + } + } + + return { + comparators: { + value: [value$, setValue], + } as StateComparators<Pick<RangesliderControlState, 'value'>>, + hasInitialSelections: initialState.value !== undefined, + value$: value$ as PublishingSubject<RangeValue | undefined>, + setValue, + }; +} diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/range_slider_strings.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/range_slider_strings.ts new file mode 100644 index 0000000000000..155a4713f5bb8 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/range_slider_strings.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 and the Server Side Public License, v 1; you may not 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 const RangeSliderStrings = { + control: { + getDisplayName: () => + i18n.translate('controls.rangeSliderControl.displayName', { + defaultMessage: 'Range slider', + }), + getInvalidSelectionWarningLabel: () => + i18n.translate('controls.rangeSlider.control.invalidSelectionWarningLabel', { + defaultMessage: 'Selected range returns no results.', + }), + }, + editor: { + getStepTitle: () => + i18n.translate('controls.rangeSlider.editor.stepSizeTitle', { + defaultMessage: 'Step size', + }), + }, + popover: { + getNoAvailableDataHelpText: () => + i18n.translate('controls.rangeSlider.popover.noAvailableDataHelpText', { + defaultMessage: 'There is no data to display. Adjust the time range and filters.', + }), + }, +}; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/register_range_slider_control.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/register_range_slider_control.ts new file mode 100644 index 0000000000000..4a01a10aeaa30 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/register_range_slider_control.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 type { CoreSetup } from '@kbn/core/public'; +import type { ControlsPluginStartDeps } from '../../../../types'; +import { registerControlFactory } from '../../../control_factory_registry'; +import { RANGE_SLIDER_CONTROL } from '../../../../../common'; + +export function registerRangeSliderControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) { + registerControlFactory(RANGE_SLIDER_CONTROL, async () => { + const [{ getRangesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ + import('./get_range_slider_control_factory'), + coreSetup.getStartServices(), + ]); + + return getRangesliderControlFactory({ + core: coreStart, + data: depsStart.data, + dataViews: depsStart.data.dataViews, + }); + }); +} diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/types.ts b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/types.ts new file mode 100644 index 0000000000000..f32dab087f5d6 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/range_slider/types.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataControlApi, DefaultDataControlState } from '../types'; + +export type RangeValue = [string, string]; + +export interface RangesliderControlState extends DefaultDataControlState { + value?: RangeValue; + step?: number; +} + +export type RangesliderControlApi = DataControlApi; diff --git a/src/plugins/controls/public/react_controls/controls/data_controls/types.ts b/src/plugins/controls/public/react_controls/controls/data_controls/types.ts new file mode 100644 index 0000000000000..07025d6380071 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/data_controls/types.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 { CoreStart } from '@kbn/core/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataViewField } from '@kbn/data-views-plugin/common'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { FieldFormatConvertFunction } from '@kbn/field-formats-plugin/common'; +import { + HasEditCapabilities, + PublishesDataViews, + PublishesPanelTitle, + PublishingSubject, +} from '@kbn/presentation-publishing'; +import { ControlGroupApi } from '../../control_group/types'; +import { ControlFactory, DefaultControlApi, DefaultControlState } from '../types'; +import { PublishesAsyncFilters } from './publishes_async_filters'; + +export type DataControlFieldFormatter = FieldFormatConvertFunction | ((toFormat: any) => string); + +export interface PublishesField { + field$: PublishingSubject<DataViewField | undefined>; + fieldFormatter: PublishingSubject<DataControlFieldFormatter>; +} + +export type DataControlApi = DefaultControlApi & + Omit<PublishesPanelTitle, 'hidePanelTitle'> & // control titles cannot be hidden + HasEditCapabilities & + PublishesDataViews & + PublishesField & + PublishesAsyncFilters; + +export interface CustomOptionsComponentProps< + State extends DefaultDataControlState = DefaultDataControlState +> { + initialState: Omit<State, 'fieldName'>; + field: DataViewField; + updateState: (newState: Partial<State>) => void; + setControlEditorValid: (valid: boolean) => void; + parentApi: ControlGroupApi; +} + +export interface DataControlFactory< + State extends DefaultDataControlState = DefaultDataControlState, + Api extends DataControlApi = DataControlApi +> extends ControlFactory<State, Api> { + isFieldCompatible: (field: DataViewField) => boolean; + CustomOptionsComponent?: React.FC<CustomOptionsComponentProps<State>>; +} + +export const isDataControlFactory = ( + factory: ControlFactory<object, any> +): factory is DataControlFactory<any, any> => { + return typeof (factory as DataControlFactory).isFieldCompatible === 'function'; +}; + +export interface DefaultDataControlState extends DefaultControlState { + dataViewId: string; + fieldName: string; + title?: string; // custom control label +} + +export interface DataControlServices { + core: CoreStart; + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; +} diff --git a/examples/controls_example/public/react_controls/initialize_default_control_api.tsx b/src/plugins/controls/public/react_controls/controls/initialize_default_control_api.tsx similarity index 96% rename from examples/controls_example/public/react_controls/initialize_default_control_api.tsx rename to src/plugins/controls/public/react_controls/controls/initialize_default_control_api.tsx index cab0f2c46b003..d70cb822c346c 100644 --- a/examples/controls_example/public/react_controls/initialize_default_control_api.tsx +++ b/src/plugins/controls/public/react_controls/controls/initialize_default_control_api.tsx @@ -8,9 +8,9 @@ import { BehaviorSubject } from 'rxjs'; -import { ControlWidth } from '@kbn/controls-plugin/common'; import { SerializedPanelState } from '@kbn/presentation-containers'; import { StateComparators } from '@kbn/presentation-publishing'; +import { ControlWidth } from '../../../common'; import { ControlApiInitialization, diff --git a/src/plugins/controls/public/react_controls/controls/mocks/control_mocks.ts b/src/plugins/controls/public/react_controls/controls/mocks/control_mocks.ts new file mode 100644 index 0000000000000..d9aefc5331216 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/mocks/control_mocks.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 { TimeRange } from '@kbn/es-query'; +import { PublishesUnifiedSearch, StateComparators } from '@kbn/presentation-publishing'; +import { BehaviorSubject } from 'rxjs'; +import { ControlFetchContext } from '../../control_group/control_fetch/control_fetch'; +import { ControlGroupApi } from '../../control_group/types'; +import { ControlApiRegistration, ControlFactory, DefaultControlApi } from '../types'; + +export const getMockedControlGroupApi = ( + dashboardApi: Partial<PublishesUnifiedSearch> = { + timeRange$: new BehaviorSubject<TimeRange | undefined>(undefined), + }, + overwriteApi?: Partial<ControlGroupApi> +) => { + return { + parentApi: dashboardApi, + autoApplySelections$: new BehaviorSubject(true), + ignoreParentSettings$: new BehaviorSubject(undefined), + controlFetch$: () => new BehaviorSubject<ControlFetchContext>({}), + allowExpensiveQueries$: new BehaviorSubject(true), + ...overwriteApi, + } as unknown as ControlGroupApi; +}; + +export const getMockedBuildApi = + <StateType extends object = object, ApiType extends DefaultControlApi = DefaultControlApi>( + uuid: string, + factory: ControlFactory<StateType, ApiType>, + controlGroupApi?: ControlGroupApi + ) => + (api: ControlApiRegistration<ApiType>, nextComparators: StateComparators<StateType>) => { + return { + ...api, + uuid, + parentApi: controlGroupApi ?? getMockedControlGroupApi(), + unsavedChanges: new BehaviorSubject<Partial<StateType> | undefined>(undefined), + resetUnsavedChanges: () => {}, + type: factory.type, + }; + }; diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/index.scss b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/index.scss similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/index.scss rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/index.scss diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/play_button.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/play_button.tsx similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/play_button.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/play_button.tsx diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/time_slider_anchored_range.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_anchored_range.tsx similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/time_slider_anchored_range.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_anchored_range.tsx diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/time_slider_popover_button.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_popover_button.tsx similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/time_slider_popover_button.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_popover_button.tsx diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/time_slider_popover_content.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_popover_content.tsx similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/time_slider_popover_content.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_popover_content.tsx diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/time_slider_prepend.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_prepend.tsx similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/time_slider_prepend.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_prepend.tsx diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/time_slider_sliding_window_range.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_sliding_window_range.tsx similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/time_slider_sliding_window_range.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_sliding_window_range.tsx diff --git a/examples/controls_example/public/react_controls/timeslider_control/components/time_slider_strings.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_strings.ts similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/components/time_slider_strings.ts rename to src/plugins/controls/public/react_controls/controls/timeslider_control/components/time_slider_strings.ts diff --git a/examples/controls_example/public/react_controls/timeslider_control/get_time_range_meta.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_time_range_meta.ts similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/get_time_range_meta.ts rename to src/plugins/controls/public/react_controls/controls/timeslider_control/get_time_range_meta.ts diff --git a/examples/controls_example/public/react_controls/timeslider_control/get_timeslider_control_factory.test.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.test.tsx similarity index 97% rename from examples/controls_example/public/react_controls/timeslider_control/get_timeslider_control_factory.test.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.test.tsx index ff32abe441a0c..9bf8ade2ebe32 100644 --- a/examples/controls_example/public/react_controls/timeslider_control/get_timeslider_control_factory.test.tsx +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.test.tsx @@ -6,28 +6,27 @@ * Side Public License, v 1. */ -import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; -import { TimeRange } from '@kbn/es-query'; -import { StateComparators } from '@kbn/presentation-publishing'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import dateMath from '@kbn/datemath'; +import { TimeRange } from '@kbn/es-query'; +import { StateComparators } from '@kbn/presentation-publishing'; +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; import { BehaviorSubject } from 'rxjs'; -import { ControlGroupApi } from '../control_group/types'; +import { getMockedControlGroupApi } from '../mocks/control_mocks'; import { ControlApiRegistration } from '../types'; import { getTimesliderControlFactory } from './get_timeslider_control_factory'; import { TimesliderControlApi, TimesliderControlState } from './types'; describe('TimesliderControlApi', () => { const uuid = 'myControl1'; + const dashboardApi = { timeRange$: new BehaviorSubject<TimeRange | undefined>(undefined), }; - const controlGroupApi = { - autoApplySelections$: new BehaviorSubject(true), - parentApi: dashboardApi, - } as unknown as ControlGroupApi; + const controlGroupApi = getMockedControlGroupApi(dashboardApi); + const dataStartServiceMock = dataPluginMock.createStartContract(); dataStartServiceMock.query.timefilter.timefilter.calculateBounds = (timeRange: TimeRange) => { const now = new Date(); diff --git a/examples/controls_example/public/react_controls/timeslider_control/get_timeslider_control_factory.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.tsx similarity index 97% rename from examples/controls_example/public/react_controls/timeslider_control/get_timeslider_control_factory.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.tsx index 4f086444da56b..f3d1b43de8fa2 100644 --- a/examples/controls_example/public/react_controls/timeslider_control/get_timeslider_control_factory.tsx +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/get_timeslider_control_factory.tsx @@ -13,18 +13,13 @@ import { EuiInputPopover } from '@elastic/eui'; import { apiHasParentApi, apiPublishesDataLoading, + getUnchangingComparator, getViewModeSubject, useBatchedPublishingSubjects, ViewMode, } from '@kbn/presentation-publishing'; import { ControlFactory } from '../types'; -import { - TimesliderControlState, - TimesliderControlApi, - TIMESLIDER_CONTROL_TYPE, - Services, - Timeslice, -} from './types'; +import { TimesliderControlState, TimesliderControlApi, Services, Timeslice } from './types'; import { initializeDefaultControlApi } from '../initialize_default_control_api'; import { TimeSliderPopoverButton } from './components/time_slider_popover_button'; import { TimeSliderPopoverContent } from './components/time_slider_popover_content'; @@ -38,15 +33,16 @@ import { import { initTimeRangePercentage } from './init_time_range_percentage'; import './components/index.scss'; import { TimeSliderPrepend } from './components/time_slider_prepend'; +import { TIME_SLIDER_CONTROL } from '../../../../common'; export const getTimesliderControlFactory = ( services: Services ): ControlFactory<TimesliderControlState, TimesliderControlApi> => { return { - type: TIMESLIDER_CONTROL_TYPE, + type: TIME_SLIDER_CONTROL, getIconType: () => 'search', getDisplayName: () => - i18n.translate('controlsExamples.timesliderControl.displayName', { + i18n.translate('controls.timesliderControl.displayName', { defaultMessage: 'Time slider', }), buildControl: async (initialState, buildApi, uuid, controlGroupApi) => { @@ -185,7 +181,6 @@ export const getTimesliderControlFactory = ( const viewModeSubject = getViewModeSubject(controlGroupApi) ?? new BehaviorSubject('view' as ViewMode); - // overwrite the `width` attribute because time slider should always have a width of large const defaultControl = initializeDefaultControlApi({ ...initialState, width: 'large' }); const dashboardDataLoading$ = @@ -243,6 +238,7 @@ export const getTimesliderControlFactory = ( }, { ...defaultControl.comparators, + width: getUnchangingComparator(), ...timeRangePercentage.comparators, isAnchored: [isAnchored$, setIsAnchored], } diff --git a/examples/controls_example/public/react_controls/timeslider_control/init_time_range_percentage.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_percentage.ts similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/init_time_range_percentage.ts rename to src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_percentage.ts diff --git a/examples/controls_example/public/react_controls/timeslider_control/init_time_range_subscription.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_subscription.ts similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/init_time_range_subscription.ts rename to src/plugins/controls/public/react_controls/controls/timeslider_control/init_time_range_subscription.ts diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/register_timeslider_control.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/register_timeslider_control.ts new file mode 100644 index 0000000000000..6c74795051364 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/register_timeslider_control.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 type { CoreSetup } from '@kbn/core/public'; +import type { ControlsPluginStartDeps } from '../../../types'; +import { registerControlFactory } from '../../control_factory_registry'; +import { TIME_SLIDER_CONTROL } from '../../../../common'; + +export function registerTimeSliderControl(coreSetup: CoreSetup<ControlsPluginStartDeps>) { + registerControlFactory(TIME_SLIDER_CONTROL, async () => { + const [{ getTimesliderControlFactory }, [coreStart, depsStart]] = await Promise.all([ + import('./get_timeslider_control_factory'), + coreSetup.getStartServices(), + ]); + return getTimesliderControlFactory({ + core: coreStart, + data: depsStart.data, + }); + }); +} diff --git a/examples/controls_example/public/react_controls/timeslider_control/time_utils.tsx b/src/plugins/controls/public/react_controls/controls/timeslider_control/time_utils.tsx similarity index 100% rename from examples/controls_example/public/react_controls/timeslider_control/time_utils.tsx rename to src/plugins/controls/public/react_controls/controls/timeslider_control/time_utils.tsx diff --git a/src/plugins/controls/public/react_controls/controls/timeslider_control/types.ts b/src/plugins/controls/public/react_controls/controls/timeslider_control/types.ts new file mode 100644 index 0000000000000..bc5fcd67829c2 --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/timeslider_control/types.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 { CoreStart } from '@kbn/core/public'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { PublishesTimeslice } from '@kbn/presentation-publishing'; +import type { DefaultControlApi, DefaultControlState } from '../types'; + +export type Timeslice = [number, number]; + +export interface TimesliderControlState extends DefaultControlState { + isAnchored?: boolean; + // Encode value as percentage of time range to support relative time ranges. + timesliceStartAsPercentageOfTimeRange?: number; + timesliceEndAsPercentageOfTimeRange?: number; +} + +export type TimesliderControlApi = DefaultControlApi & PublishesTimeslice; + +export interface Services { + core: CoreStart; + data: DataPublicPluginStart; +} diff --git a/src/plugins/controls/public/react_controls/controls/types.ts b/src/plugins/controls/public/react_controls/controls/types.ts new file mode 100644 index 0000000000000..d89db77e1f53b --- /dev/null +++ b/src/plugins/controls/public/react_controls/controls/types.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { BehaviorSubject } from 'rxjs'; + +import { SerializedPanelState } from '@kbn/presentation-containers'; +import { PanelCompatibleComponent } from '@kbn/presentation-panel-plugin/public/panel_component/types'; +import { + HasParentApi, + HasType, + HasUniqueId, + PublishesBlockingError, + PublishesDataLoading, + PublishesDisabledActionIds, + PublishesPanelTitle, + PublishesUnsavedChanges, + PublishingSubject, + StateComparators, +} from '@kbn/presentation-publishing'; +import { CanClearSelections, ControlWidth } from '../../types'; + +import { ControlGroupApi } from '../control_group/types'; + +export interface PublishesControlDisplaySettings { + grow: PublishingSubject<boolean | undefined>; + width: PublishingSubject<ControlWidth | undefined>; +} + +export interface HasCustomPrepend { + CustomPrependComponent: React.FC<{}>; +} + +export type DefaultControlApi = PublishesDataLoading & + PublishesBlockingError & + PublishesUnsavedChanges & + PublishesControlDisplaySettings & + Partial<PublishesPanelTitle & PublishesDisabledActionIds & HasCustomPrepend> & + CanClearSelections & + HasType & + HasUniqueId & + HasParentApi<ControlGroupApi> & { + // Can not use HasSerializableState interface + // HasSerializableState types serializeState as function returning 'MaybePromise' + // Controls serializeState is sync + serializeState: () => SerializedPanelState<DefaultControlState>; + /** TODO: Make these non-public as part of https://github.com/elastic/kibana/issues/174961 */ + setDataLoading: (loading: boolean) => void; + setBlockingError: (error: Error | undefined) => void; + }; + +export interface DefaultControlState { + grow?: boolean; + width?: ControlWidth; +} + +export type ControlApiRegistration<ControlApi extends DefaultControlApi = DefaultControlApi> = Omit< + ControlApi, + 'uuid' | 'parentApi' | 'type' | 'unsavedChanges' | 'resetUnsavedChanges' +>; + +export type ControlApiInitialization<ControlApi extends DefaultControlApi = DefaultControlApi> = + Omit< + ControlApiRegistration<ControlApi>, + 'serializeState' | 'getTypeDisplayName' | 'clearSelections' + >; + +// TODO: Move this to the Control plugin's setup contract +export interface ControlFactory< + State extends DefaultControlState = DefaultControlState, + ControlApi extends DefaultControlApi = DefaultControlApi +> { + type: string; + order?: number; + getIconType: () => string; + getDisplayName: () => string; + buildControl: ( + initialState: State, + buildApi: ( + apiRegistration: ControlApiRegistration<ControlApi>, + comparators: StateComparators<State> + ) => ControlApi, + uuid: string, + parentApi: ControlGroupApi + ) => Promise<{ api: ControlApi; Component: React.FC<{ className: string }> }>; +} + +export type ControlStateManager<State extends object = object> = { + [key in keyof Required<State>]: BehaviorSubject<State[key]>; +}; + +export interface ControlPanelProps< + ApiType extends DefaultControlApi = DefaultControlApi, + PropsType extends {} = { className: string } +> { + uuid: string; + Component: PanelCompatibleComponent<ApiType, PropsType>; +} diff --git a/src/plugins/controls/tsconfig.json b/src/plugins/controls/tsconfig.json index 2fc9038dafe6c..053e17696dc32 100644 --- a/src/plugins/controls/tsconfig.json +++ b/src/plugins/controls/tsconfig.json @@ -40,7 +40,11 @@ "@kbn/shared-ux-markdown", "@kbn/react-kibana-context-render", "@kbn/presentation-containers", - "@kbn/presentation-publishing" + "@kbn/presentation-publishing", + "@kbn/content-management-utils", + "@kbn/core-lifecycle-browser", + "@kbn/field-formats-plugin", + "@kbn/presentation-panel-plugin" ], "exclude": ["target/**/*"] } diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts index 4fe3184f619c9..89f71c074d9fd 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts @@ -6,12 +6,10 @@ * Side Public License, v 1. */ import { PersistableControlGroupInput } from '@kbn/controls-plugin/common'; -import { apiPublishesUnsavedChanges, PublishesUnsavedChanges } from '@kbn/presentation-publishing'; -import deepEqual from 'fast-deep-equal'; +import { childrenUnsavedChanges$ } from '@kbn/presentation-containers'; import { omit } from 'lodash'; import { AnyAction, Middleware } from 'redux'; import { combineLatest, debounceTime, Observable, of, startWith, switchMap } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs'; import { DashboardContainer, DashboardCreationOptions } from '../..'; import { DashboardContainerInput } from '../../../../common'; import { CHANGE_CHECK_DEBOUNCE } from '../../../dashboard_constants'; @@ -84,32 +82,6 @@ export function startDiffingDashboardState( this: DashboardContainer, creationOptions?: DashboardCreationOptions ) { - /** - * Create an observable stream of unsaved changes from all react embeddable children - */ - const reactEmbeddableUnsavedChanges = this.children$.pipe( - map((children) => Object.keys(children)), - distinctUntilChanged(deepEqual), - - // children may change, so make sure we subscribe/unsubscribe with switchMap - switchMap((newChildIds: string[]) => { - if (newChildIds.length === 0) return of([]); - const childrenThatPublishUnsavedChanges = Object.entries(this.children$.value).filter( - ([childId, child]) => apiPublishesUnsavedChanges(child) - ) as Array<[string, PublishesUnsavedChanges]>; - - if (childrenThatPublishUnsavedChanges.length === 0) return of([]); - - return combineLatest( - childrenThatPublishUnsavedChanges.map(([childId, child]) => - child.unsavedChanges.pipe(map((unsavedChanges) => ({ childId, unsavedChanges }))) - ) - ); - }), - debounceTime(CHANGE_CHECK_DEBOUNCE), - map((children) => children.filter((child) => Boolean(child.unsavedChanges))) - ); - /** * Create an observable stream that checks for unsaved changes in the Dashboard state * and the state of all of its legacy embeddable children. @@ -138,30 +110,26 @@ export function startDiffingDashboardState( this.diffingSubscription.add( combineLatest([ dashboardUnsavedChanges, - reactEmbeddableUnsavedChanges, + childrenUnsavedChanges$(this.children$), this.controlGroup?.unsavedChanges ?? (of(undefined) as Observable<PersistableControlGroupInput | undefined>), - ]).subscribe(([dashboardChanges, reactEmbeddableChanges, controlGroupChanges]) => { + ]).subscribe(([dashboardChanges, unsavedPanelState, controlGroupChanges]) => { // calculate unsaved changes const hasUnsavedChanges = Object.keys(omit(dashboardChanges, keysNotConsideredUnsavedChanges)).length > 0 || - reactEmbeddableChanges.length > 0 || + unsavedPanelState !== undefined || controlGroupChanges !== undefined; if (hasUnsavedChanges !== this.getState().componentState.hasUnsavedChanges) { this.dispatch.setHasUnsavedChanges(hasUnsavedChanges); } - const unsavedPanelState = reactEmbeddableChanges.reduce<UnsavedPanelState>( - (acc, { childId, unsavedChanges }) => { - acc[childId] = unsavedChanges; - return acc; - }, - {} as UnsavedPanelState - ); - // backup unsaved changes if configured to do so if (creationOptions?.useSessionStorageIntegration) { - backupUnsavedChanges.bind(this)(dashboardChanges, unsavedPanelState, controlGroupChanges); + backupUnsavedChanges.bind(this)( + dashboardChanges, + unsavedPanelState ? unsavedPanelState : {}, + controlGroupChanges + ); } }) ); diff --git a/src/plugins/dashboard/config.ts b/src/plugins/dashboard/server/config.ts similarity index 100% rename from src/plugins/dashboard/config.ts rename to src/plugins/dashboard/server/config.ts diff --git a/src/plugins/dashboard/server/index.ts b/src/plugins/dashboard/server/index.ts index a1ac1e5d0fd9c..5e1c0f8bf5f6b 100644 --- a/src/plugins/dashboard/server/index.ts +++ b/src/plugins/dashboard/server/index.ts @@ -7,7 +7,7 @@ */ import { PluginInitializerContext, PluginConfigDescriptor } from '@kbn/core/server'; -import { configSchema, ConfigSchema } from '../config'; +import { configSchema, ConfigSchema } from './config'; export const config: PluginConfigDescriptor<ConfigSchema> = { exposeToBrowser: { diff --git a/src/plugins/data_view_editor/public/data_view_editor_service.ts b/src/plugins/data_view_editor/public/data_view_editor_service.ts index 01c1842ee9425..76d5870cb094c 100644 --- a/src/plugins/data_view_editor/public/data_view_editor_service.ts +++ b/src/plugins/data_view_editor/public/data_view_editor_service.ts @@ -350,6 +350,7 @@ export class DataViewEditorService { const getFieldsOptions: GetFieldsOptions = { pattern: this.indexPattern, allowHidden: this.allowHidden, + allowNoIndex: true, }; if (this.type === INDEX_PATTERN_TYPE.ROLLUP) { getFieldsOptions.type = INDEX_PATTERN_TYPE.ROLLUP; diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts index f1cf9b862ebaa..d71ced1cebd4b 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts @@ -20,15 +20,22 @@ export const defaultProps: Props = { export type FieldEditorTestBed = TestBed & { actions: ReturnType<typeof getCommonActions> }; -export const setup = async (props?: Partial<Props>, deps?: Partial<Context>) => { +export const setup = async ( + props?: Partial<Props>, + deps?: Partial<Context>, + getByNameOverride?: () => any +) => { let testBed: TestBed<string>; await act(async () => { - testBed = await registerTestBed(WithFieldEditorDependencies(FieldEditor, deps), { - memoryRouter: { - wrapComponent: false, - }, - })({ ...defaultProps, ...props }); + testBed = await registerTestBed( + WithFieldEditorDependencies(FieldEditor, deps, getByNameOverride), + { + memoryRouter: { + wrapComponent: false, + }, + } + )({ ...defaultProps, ...props }); }); testBed!.component.update(); diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx index 37b876b327388..8be6c633cda94 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx @@ -121,19 +121,22 @@ describe('<FieldEditor />', () => { }); describe('validation', () => { - test('should accept an optional list of existing fields and prevent creating duplicates', async () => { + test('should prevent creating duplicates', async () => { const existingFields = ['myRuntimeField']; testBed = await setup( { onChange, }, { - namesNotAllowed: { - fields: existingFields, - runtimeComposites: [], - }, - existingConcreteFields: [], fieldTypeToProcess: 'runtime', + }, + // getByName returns a value, which means that the field already exists + () => { + return { + name: 'myRuntimeField', + type: 'boolean', + script: { source: 'emit("hello")' }, + }; } ); @@ -155,7 +158,6 @@ describe('<FieldEditor />', () => { }); test('should not count the default value as a duplicate', async () => { - const existingRuntimeFieldNames = ['myRuntimeField']; const field: Field = { name: 'myRuntimeField', type: 'boolean', @@ -168,11 +170,6 @@ describe('<FieldEditor />', () => { onChange, }, { - namesNotAllowed: { - fields: existingRuntimeFieldNames, - runtimeComposites: [], - }, - existingConcreteFields: [], fieldTypeToProcess: 'runtime', } ); diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts index 7251199af5621..719b717521ca7 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts @@ -18,7 +18,7 @@ import { import { WithFieldEditorDependencies, getCommonActions, - spyIndexPatternGetAllFields, + spyIndexPatternGetByName, spySearchQuery, spySearchQueryResponse, TestDoc, @@ -34,7 +34,7 @@ const defaultProps: Props = { * @param fields The fields of the index pattern */ export const setIndexPatternFields = (fields: Array<{ name: string; displayName: string }>) => { - spyIndexPatternGetAllFields.mockReturnValue(fields); + spyIndexPatternGetByName.mockReturnValue(fields); }; export const getSearchCallMeta = () => { diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts index bbe2a00f31e76..9051a76808cac 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts @@ -21,6 +21,7 @@ import { setSearchResponse, FieldEditorFlyoutContentTestBed, } from './field_editor_flyout_preview.helpers'; +import { spyGetFieldsForWildcard } from './helpers/setup_environment'; import { mockDocuments, createPreviewError } from './helpers/mocks'; describe('Field editor Preview panel', () => { @@ -40,22 +41,23 @@ describe('Field editor Preview panel', () => { const indexPatternFields: Array<{ name: string; displayName: string }> = [ { - name: 'title', - displayName: 'title', + name: 'description', + displayName: 'description', }, { name: 'subTitle', displayName: 'subTitle', }, { - name: 'description', - displayName: 'description', + name: 'title', + displayName: 'title', }, ]; beforeEach(async () => { httpRequestsMockHelpers.setFieldPreviewResponse({ values: ['mockedScriptValue'] }); setIndexPatternFields(indexPatternFields); + spyGetFieldsForWildcard.mockResolvedValue({ fields: indexPatternFields }); setSearchResponse(mockDocuments); setSearchResponseLatency(0); @@ -91,16 +93,16 @@ describe('Field editor Preview panel', () => { expect(getRenderedIndexPatternFields()).toEqual([ { - key: 'title', - value: mockDocuments[0].fields.title, + key: 'description', + value: mockDocuments[0].fields.description, }, { key: 'subTitle', value: mockDocuments[0].fields.subTitle, }, { - key: 'description', - value: mockDocuments[0].fields.description, + key: 'title', + value: mockDocuments[0].fields.title, }, ]); }); @@ -126,8 +128,8 @@ describe('Field editor Preview panel', () => { await setFilterFieldsValue('title'); expect(exists('emptySearchResult')).toBe(false); expect(getRenderedIndexPatternFields()).toEqual([ - { key: 'title', value: 'First doc - title' }, { key: 'subTitle', value: 'First doc - subTitle' }, + { key: 'title', value: 'First doc - title' }, ]); // Should display an empty search result with a button to clear @@ -140,16 +142,16 @@ describe('Field editor Preview panel', () => { component.update(); expect(getRenderedIndexPatternFields()).toEqual([ { - key: 'title', - value: mockDocuments[0].fields.title, + key: 'description', + value: mockDocuments[0].fields.description, }, { key: 'subTitle', value: mockDocuments[0].fields.subTitle, }, { - key: 'description', - value: mockDocuments[0].fields.description, + key: 'title', + value: mockDocuments[0].fields.title, }, ]); }); @@ -174,7 +176,7 @@ describe('Field editor Preview panel', () => { expect(fieldsRendered).not.toBe(null); expect(fieldsRendered!.length).toBe(Object.keys(doc1.fields).length); // make sure that the last one if the "description" field - expect(fieldsRendered!.at(2).text()).toBe('descriptionFirst doc - description'); + expect(fieldsRendered!.at(0).text()).toBe('descriptionFirst doc - description'); // Click the third field in the list ("description") const descriptionField = fieldsRendered!.at(2); @@ -182,8 +184,8 @@ describe('Field editor Preview panel', () => { component.update(); expect(getRenderedIndexPatternFields()).toEqual([ - { key: 'description', value: 'First doc - description' }, // Pinned! { key: 'title', value: 'First doc - title' }, + { key: 'description', value: 'First doc - description' }, // Pinned! { key: 'subTitle', value: 'First doc - subTitle' }, ]); }); @@ -548,39 +550,39 @@ describe('Field editor Preview panel', () => { await fields.updateName('myRuntimeField'); // Give a name to remove empty prompt - expect(getRenderedIndexPatternFields()[0]).toEqual({ + expect(getRenderedIndexPatternFields()[2]).toEqual({ key: 'title', value: doc1.fields.title, }); await goToNextDocument(); - expect(getRenderedIndexPatternFields()[0]).toEqual({ + expect(getRenderedIndexPatternFields()[2]).toEqual({ key: 'title', value: doc2.fields.title, }); await goToNextDocument(); - expect(getRenderedIndexPatternFields()[0]).toEqual({ + expect(getRenderedIndexPatternFields()[2]).toEqual({ key: 'title', value: doc3.fields.title, }); // Going next we circle back to the first document of the list await goToNextDocument(); - expect(getRenderedIndexPatternFields()[0]).toEqual({ + expect(getRenderedIndexPatternFields()[2]).toEqual({ key: 'title', value: doc1.fields.title, }); // Let's go backward await goToPreviousDocument(); - expect(getRenderedIndexPatternFields()[0]).toEqual({ + expect(getRenderedIndexPatternFields()[2]).toEqual({ key: 'title', value: doc3.fields.title, }); await goToPreviousDocument(); - expect(getRenderedIndexPatternFields()[0]).toEqual({ + expect(getRenderedIndexPatternFields()[2]).toEqual({ key: 'title', value: doc2.fields.title, }); @@ -618,7 +620,7 @@ describe('Field editor Preview panel', () => { // First make sure that we have the original cluster data is loaded // and the preview value rendered. - expect(getRenderedIndexPatternFields()[0]).toEqual({ + expect(getRenderedIndexPatternFields()[2]).toEqual({ key: 'title', value: doc1.fields.title, }); @@ -636,16 +638,16 @@ describe('Field editor Preview panel', () => { expect(getRenderedIndexPatternFields()).toEqual([ { - key: 'title', - value: 'loaded doc - title', + key: 'description', + value: 'loaded doc - description', }, { key: 'subTitle', value: 'loaded doc - subTitle', }, { - key: 'description', - value: 'loaded doc - description', + key: 'title', + value: 'loaded doc - title', }, ]); @@ -734,8 +736,8 @@ describe('Field editor Preview panel', () => { expect(exists('documentsNav')).toBe(false); expect(exists('loadDocsFromClusterButton')).toBe(true); expect(getRenderedIndexPatternFields()[0]).toEqual({ - key: 'title', - value: 'loaded doc - title', + key: 'description', + value: 'loaded doc - description', }); }); diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts index b29b76a9daf23..6695c6a094834 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts @@ -14,7 +14,7 @@ export { WithFieldEditorDependencies, spySearchQuery, spySearchQueryResponse, - spyIndexPatternGetAllFields, + spyIndexPatternGetByName, fieldFormatsOptions, indexPatternNameForTest, setSearchResponseLatency, diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx index 57bbc5edc06ca..2cefb86b1570a 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/setup_environment.tsx @@ -18,7 +18,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { fieldFormatsMock as fieldFormats } from '@kbn/field-formats-plugin/common/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { FieldFormat } from '@kbn/field-formats-plugin/common'; -import { createStubDataView } from '@kbn/data-views-plugin/common/data_views/data_view.stub'; +import { createStubDataViewLazy } from '@kbn/data-views-plugin/common/data_views/data_view_lazy.stub'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { PreviewController } from '../../../public/components/preview/preview_controller'; import { FieldEditorProvider, Context } from '../../../public/components/field_editor_context'; @@ -32,7 +32,8 @@ const { search } = dataStart; export const spySearchQuery = jest.fn(); export const spySearchQueryResponse = jest.fn(() => Promise.resolve({})); -export const spyIndexPatternGetAllFields = jest.fn().mockImplementation(() => []); +export const spyIndexPatternGetByName = jest.fn().mockImplementation(() => {}); +export const spyGetFieldsForWildcard = jest.fn().mockResolvedValue({ fields: [] }); let searchResponseDelay = 0; @@ -91,7 +92,8 @@ export const indexPatternNameForTest = 'testIndexPattern'; export const WithFieldEditorDependencies = <T extends object = { [key: string]: unknown }>( Comp: FunctionComponent<T>, - overridingDependencies?: Partial<Context> + overridingDependencies?: Partial<Context>, + getByNameOverride?: () => any ) => (props: T) => { // Setup mocks @@ -119,20 +121,25 @@ export const WithFieldEditorDependencies = return new MockDefaultFieldFormat(); }); - const dataView = createStubDataView({ + const dataView = createStubDataViewLazy({ spec: { title: indexPatternNameForTest, }, + deps: { + apiClient: { + getFieldsForWildcard: spyGetFieldsForWildcard, + }, + }, }); - jest.spyOn(dataView.fields, 'getAll').mockImplementation(spyIndexPatternGetAllFields); + jest + .spyOn(dataView, 'getFieldByName') + .mockImplementation(getByNameOverride || spyIndexPatternGetByName); const dependencies: Context = { dataView, uiSettings: uiSettingsServiceMock.createStartContract(), fieldTypeToProcess: 'runtime', - existingConcreteFields: [], - namesNotAllowed: { fields: [], runtimeComposites: [] }, links: { runtimePainless: 'https://elastic.co', }, @@ -162,6 +169,7 @@ export const WithFieldEditorDependencies = notifications: notificationServiceMock.createStartContract(), }, dataView, + dataViewToUpdate: dataView, onSave: jest.fn(), fieldTypeToProcess: 'runtime', }); diff --git a/src/plugins/data_view_field_editor/public/components/delete_field_provider.tsx b/src/plugins/data_view_field_editor/public/components/delete_field_provider.tsx index fc09b860f705a..d749a1893d647 100644 --- a/src/plugins/data_view_field_editor/public/components/delete_field_provider.tsx +++ b/src/plugins/data_view_field_editor/public/components/delete_field_provider.tsx @@ -27,7 +27,7 @@ export interface Props { } export const getDeleteFieldProvider = ( - modalOpener: (options: OpenFieldDeleteModalOptions) => CloseEditor + modalOpener: (options: OpenFieldDeleteModalOptions) => Promise<CloseEditor> ): React.FunctionComponent<Props> => { return React.memo(({ dataView, children, onDelete }: Props) => { const closeModal = useRef<CloseEditor | null>(null); @@ -36,7 +36,7 @@ export const getDeleteFieldProvider = ( if (closeModal.current) { closeModal.current(); } - closeModal.current = modalOpener({ + closeModal.current = await modalOpener({ ctx: { dataView, }, diff --git a/src/plugins/data_view_field_editor/public/components/field_editor/composite_editor.tsx b/src/plugins/data_view_field_editor/public/components/field_editor/composite_editor.tsx index 53682b17a813e..464446a443d19 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor/composite_editor.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor/composite_editor.tsx @@ -32,16 +32,12 @@ export interface CompositeEditorProps { } export const CompositeEditor = ({ onReset }: CompositeEditorProps) => { - const { links, existingConcreteFields, subfields$ } = useFieldEditorContext(); + const { links, subfields$ } = useFieldEditorContext(); const subfields = useObservable(subfields$) || {}; return ( <div data-test-subj="compositeEditor"> - <ScriptField - existingConcreteFields={existingConcreteFields} - links={links} - placeholder={"emit('field_name', 'hello world');"} - /> + <ScriptField links={links} placeholder={"emit('field_name', 'hello world');"} /> <EuiSpacer size="xl" /> <> <EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="spaceBetween"> diff --git a/src/plugins/data_view_field_editor/public/components/field_editor/field_detail.tsx b/src/plugins/data_view_field_editor/public/components/field_editor/field_detail.tsx index 4914ff82c5bfb..eeb49ccbb72f4 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor/field_detail.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor/field_detail.tsx @@ -79,7 +79,7 @@ const geti18nTexts = (): { }); export const FieldDetail = ({}) => { - const { links, existingConcreteFields, fieldTypeToProcess } = useFieldEditorContext(); + const { links, fieldTypeToProcess } = useFieldEditorContext(); const i18nTexts = geti18nTexts(); return ( <> @@ -114,7 +114,7 @@ export const FieldDetail = ({}) => { data-test-subj="valueRow" withDividerRule > - <ScriptField existingConcreteFields={existingConcreteFields} links={links} /> + <ScriptField links={links} /> </FormRow> )} diff --git a/src/plugins/data_view_field_editor/public/components/field_editor/field_editor.tsx b/src/plugins/data_view_field_editor/public/components/field_editor/field_editor.tsx index 790b44f03701e..b7725f6d105bc 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor/field_editor.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor/field_editor.tsx @@ -107,7 +107,7 @@ const formSerializer = (field: FieldFormInternal): Field => { }; const FieldEditorComponent = ({ field, onChange, onFormModifiedChange }: Props) => { - const { namesNotAllowed, fieldTypeToProcess, fieldName$, subfields$ } = useFieldEditorContext(); + const { fieldTypeToProcess, fieldName$, subfields$, dataView } = useFieldEditorContext(); const { params: { update: updatePreviewParams }, fieldPreview$, @@ -121,7 +121,7 @@ const FieldEditorComponent = ({ field, onChange, onFormModifiedChange }: Props) const { submit, isValid: isFormValid, isSubmitted, getFields, isSubmitting } = form; - const nameFieldConfig = getNameFieldConfig(namesNotAllowed, field); + const nameFieldConfig = getNameFieldConfig(dataView, field); const [formData] = useFormData<FieldFormInternal>({ form }); const isFormModified = useFormIsModified({ diff --git a/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/format_field.tsx b/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/format_field.tsx index 9518ba6cc89ed..a9d63ca5d5b88 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/format_field.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/format_field.tsx @@ -20,7 +20,7 @@ import { FormatSelectEditor } from '../../field_format_editor'; import type { FieldFormInternal } from '../field_editor'; export const FormatField = () => { - const { dataView, uiSettings, fieldFormats, fieldFormatEditors } = useFieldEditorContext(); + const { uiSettings, fieldFormats, fieldFormatEditors } = useFieldEditorContext(); const isMounted = useRef(false); const [{ type }] = useFormData<FieldFormInternal>({ watch: ['name', 'type'] }); const { getFields, isSubmitted } = useFormContext(); @@ -67,7 +67,6 @@ export const FormatField = () => { <FormatSelectEditor esTypes={typeValue || (['keyword'] as ES_FIELD_TYPES[])} - indexPattern={dataView} fieldFormatEditors={fieldFormatEditors} fieldFormats={fieldFormats} uiSettings={uiSettings} diff --git a/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/script_field.tsx b/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/script_field.tsx index 7d57a1828f822..d353c8e31988f 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/script_field.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor/form_fields/script_field.tsx @@ -32,7 +32,6 @@ import { PreviewState } from '../../preview/types'; interface Props { links: { runtimePainless: string }; - existingConcreteFields?: Array<{ name: string; type: string }>; placeholder?: string; } @@ -60,8 +59,9 @@ const currentDocumentIsLoadingSelector = (state: PreviewState) => state.isLoadin const currentErrorSelector = (state: PreviewState) => state.previewResponse?.error; const isLoadingPreviewSelector = (state: PreviewState) => state.isLoadingPreview; const isPreviewAvailableSelector = (state: PreviewState) => state.isPreviewAvailable; +const concreteFieldsSelector = (state: PreviewState) => state.concreteFields; -const ScriptFieldComponent = ({ existingConcreteFields, links, placeholder }: Props) => { +const ScriptFieldComponent = ({ links, placeholder }: Props) => { const { validation: { setScriptEditorValidation }, } = useFieldPreviewContext(); @@ -75,6 +75,13 @@ const ScriptFieldComponent = ({ existingConcreteFields, links, placeholder }: Pr const isFetchingDoc = useStateSelector(controller.state$, currentDocumentIsLoadingSelector); const isLoadingPreview = useStateSelector(controller.state$, isLoadingPreviewSelector); const isPreviewAvailable = useStateSelector(controller.state$, isPreviewAvailableSelector); + /** + * An array of existing concrete fields. If the user gives a name to the runtime + * field that matches one of the concrete fields, a callout will be displayed + * to indicate that this runtime field will shadow the concrete field. + * It is also used to provide the list of field autocomplete suggestions to the code editor. + */ + const concreteFields = useStateSelector(controller.state$, concreteFieldsSelector); const [validationData$, nextValidationData$] = useBehaviorSubject< | { isFetchingDoc: boolean; @@ -91,8 +98,8 @@ const ScriptFieldComponent = ({ existingConcreteFields, links, placeholder }: Pr const currentDocId = currentDocument?._id; const suggestionProvider = useMemo( - () => PainlessLang.getSuggestionProvider(painlessContext, existingConcreteFields), - [painlessContext, existingConcreteFields] + () => PainlessLang.getSuggestionProvider(painlessContext, concreteFields), + [painlessContext, concreteFields] ); const { validateFields } = useFormContext(); diff --git a/src/plugins/data_view_field_editor/public/components/field_editor/lib.ts b/src/plugins/data_view_field_editor/public/components/field_editor/lib.ts index 4defeab116047..95971161b549b 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor/lib.ts +++ b/src/plugins/data_view_field_editor/public/components/field_editor/lib.ts @@ -28,9 +28,13 @@ export interface Change { export type ChangeSet = Record<string, Change>; const createNameNotAllowedValidator = - (namesNotAllowed: Context['namesNotAllowed']): ValidationFunc<{}, string, string> => - ({ value }) => { - if (namesNotAllowed.fields.includes(value)) { + (dataView: Context['dataView'], fieldName?: string): ValidationFunc<{}, string, string> => + async ({ value }) => { + const runtimeComposites = Object.entries(dataView.getAllRuntimeFields()) + .filter(([, _runtimeField]) => _runtimeField.type === 'composite') + .map(([_runtimeFieldName]) => _runtimeFieldName); + + if (value !== fieldName && (await dataView.getFieldByName(value, true))) { return { message: i18n.translate( 'indexPatternFieldEditor.editor.runtimeFieldsEditor.existRuntimeFieldNamesValidationErrorMessage', @@ -39,7 +43,7 @@ const createNameNotAllowedValidator = } ), }; - } else if (namesNotAllowed.runtimeComposites.includes(value)) { + } else if (value !== fieldName && runtimeComposites.includes(value)) { return { message: i18n.translate( 'indexPatternFieldEditor.editor.runtimeFieldsEditor.existCompositeNamesValidationErrorMessage', @@ -55,31 +59,21 @@ const createNameNotAllowedValidator = * Dynamically retrieve the config for the "name" field, adding * a validator to avoid duplicated runtime fields to be created. * - * @param namesNotAllowed Array of names not allowed for the field "name" * @param field Initial value of the form */ export const getNameFieldConfig = ( - namesNotAllowed?: Context['namesNotAllowed'], + dataView: Context['dataView'], field?: Props['field'] ): FieldConfig<string, Field> => { const nameFieldConfig = schema.name as FieldConfig<string, Field>; - if (!namesNotAllowed) { - return nameFieldConfig; - } - - const filterOutCurrentFieldName = (name: string) => name !== field?.name; - // Add validation to not allow duplicates return { ...nameFieldConfig!, validations: [ ...(nameFieldConfig.validations ?? []), { - validator: createNameNotAllowedValidator({ - fields: namesNotAllowed.fields.filter(filterOutCurrentFieldName), - runtimeComposites: namesNotAllowed.runtimeComposites.filter(filterOutCurrentFieldName), - }), + validator: createNameNotAllowedValidator(dataView, field?.name), }, ], }; diff --git a/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx b/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx index 98d8b7ecd215a..52bb6bf0ab1e2 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor_context.tsx @@ -16,7 +16,7 @@ import React, { import { NotificationsStart, CoreStart } from '@kbn/core/public'; import type { BehaviorSubject } from 'rxjs'; import type { - DataView, + DataViewLazy, DataPublicPluginStart, FieldFormatsStart, RuntimeFieldSubFields, @@ -25,7 +25,7 @@ import { ApiService } from '../lib/api'; import type { InternalFieldType, PluginStart } from '../types'; export interface Context { - dataView: DataView; + dataView: DataViewLazy; fieldTypeToProcess: InternalFieldType; uiSettings: CoreStart['uiSettings']; links: { @@ -38,22 +38,7 @@ export interface Context { }; fieldFormatEditors: PluginStart['fieldFormatEditors']; fieldFormats: FieldFormatsStart; - /** - * An array of field names not allowed. - * e.g we probably don't want a user to give a name of an existing - * runtime field (for that the user should edit the existing runtime field). - */ - namesNotAllowed: { - fields: string[]; - runtimeComposites: string[]; - }; - /** - * An array of existing concrete fields. If the user gives a name to the runtime - * field that matches one of the concrete fields, a callout will be displayed - * to indicate that this runtime field will shadow the concrete field. - * It is also used to provide the list of field autocomplete suggestions to the code editor. - */ - existingConcreteFields: Array<{ name: string; type: string }>; + fieldName$: BehaviorSubject<string>; subfields$: BehaviorSubject<RuntimeFieldSubFields | undefined>; } @@ -68,8 +53,6 @@ export const FieldEditorProvider: FunctionComponent<PropsWithChildren<Context>> fieldTypeToProcess, fieldFormats, fieldFormatEditors, - namesNotAllowed, - existingConcreteFields, children, fieldName$, subfields$, @@ -83,8 +66,6 @@ export const FieldEditorProvider: FunctionComponent<PropsWithChildren<Context>> services, fieldFormats, fieldFormatEditors, - namesNotAllowed, - existingConcreteFields, fieldName$, subfields$, }), @@ -96,8 +77,6 @@ export const FieldEditorProvider: FunctionComponent<PropsWithChildren<Context>> uiSettings, fieldFormats, fieldFormatEditors, - namesNotAllowed, - existingConcreteFields, fieldName$, subfields$, ] diff --git a/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx b/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx index b8a1911025292..7f484c35b20f4 100644 --- a/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_editor_flyout_content_container.tsx @@ -11,8 +11,9 @@ import { DocLinksStart, NotificationsStart, CoreStart } from '@kbn/core/public'; import { BehaviorSubject } from 'rxjs'; import { - DataViewField, DataView, + DataViewField, + DataViewLazy, DataPublicPluginStart, UsageCollectionStart, DataViewsPublicPluginStart, @@ -37,7 +38,8 @@ export interface Props { /** The docLinks start service from core */ docLinks: DocLinksStart; /** The index pattern where the field will be added */ - dataView: DataView; + dataView: DataViewLazy; + dataViewToUpdate: DataView | DataViewLazy; /** The Kibana field type of the field to create or edit (default: "runtime") */ fieldTypeToProcess: InternalFieldType; /** Optional field to edit */ @@ -72,6 +74,7 @@ export const FieldEditorFlyoutContentContainer = ({ docLinks, fieldTypeToProcess, dataView, + dataViewToUpdate, dataViews, search, notifications, @@ -92,6 +95,7 @@ export const FieldEditorFlyoutContentContainer = ({ notifications, }, dataView, + dataViewToUpdate, onSave, fieldToEdit, fieldTypeToProcess, @@ -116,8 +120,6 @@ export const FieldEditorFlyoutContentContainer = ({ services={services} fieldFormatEditors={fieldFormatEditors} fieldFormats={fieldFormats} - namesNotAllowed={controller.getNamesNotAllowed()} - existingConcreteFields={controller.getExistingConcreteFields()} fieldName$={new BehaviorSubject(fieldToEdit?.name || '')} subfields$={new BehaviorSubject(fieldToEdit?.fields)} > diff --git a/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx b/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx index de90fcb73abe7..34c7b54ec4c4e 100644 --- a/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_format_editor/field_format_editor.tsx @@ -9,7 +9,6 @@ import { EuiCode, EuiFormRow, EuiSelect } from '@elastic/eui'; import { CoreStart } from '@kbn/core/public'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/data-plugin/public'; -import { DataView } from '@kbn/data-views-plugin/public'; import type { FieldFormatInstanceType, FieldFormatParams, @@ -25,7 +24,6 @@ import { FormatEditor } from './format_editor'; export interface FormatSelectEditorProps { esTypes: ES_FIELD_TYPES[]; - indexPattern: DataView; fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors']; fieldFormats: FieldFormatsStart; uiSettings: CoreStart['uiSettings']; diff --git a/src/plugins/data_view_field_editor/public/components/preview/field_list/field_list.tsx b/src/plugins/data_view_field_editor/public/components/preview/field_list/field_list.tsx index 598b6c5b1ab34..6011681586333 100644 --- a/src/plugins/data_view_field_editor/public/components/preview/field_list/field_list.tsx +++ b/src/plugins/data_view_field_editor/public/components/preview/field_list/field_list.tsx @@ -50,6 +50,7 @@ function fuzzyMatch(searchValue: string, text: string) { const pinnedFieldsSelector = (s: PreviewState) => s.pinnedFields; const currentDocumentSelector = (s: PreviewState) => s.documents[s.currentIdx]; +const fieldMapSelector = (s: PreviewState) => s.fieldMap; interface RowProps { index: number; @@ -74,13 +75,13 @@ export const PreviewFieldList: React.FC<Props> = ({ height, clearSearch, searchV const { controller } = useFieldPreviewContext(); const pinnedFields = useStateSelector(controller.state$, pinnedFieldsSelector, isEqual); const currentDocument = useStateSelector(controller.state$, currentDocumentSelector); + const fieldMap = useStateSelector(controller.state$, fieldMapSelector); const [showAllFields, setShowAllFields] = useState(false); const fieldList: DocumentField[] = useMemo( () => - dataView.fields - .getAll() + Object.values(fieldMap) .map((field) => { const { name, displayName } = field; const formatter = dataView.getFormatterForField(field); @@ -95,7 +96,7 @@ export const PreviewFieldList: React.FC<Props> = ({ height, clearSearch, searchV }; }) .filter(({ value }) => value !== undefined), - [dataView, currentDocument?.fields] + [dataView, fieldMap, currentDocument?.fields] ); const fieldListWithPinnedFields: DocumentField[] = useMemo(() => { diff --git a/src/plugins/data_view_field_editor/public/components/preview/preview_controller.tsx b/src/plugins/data_view_field_editor/public/components/preview/preview_controller.tsx index ee510a3a0791f..f8226374d4086 100644 --- a/src/plugins/data_view_field_editor/public/components/preview/preview_controller.tsx +++ b/src/plugins/data_view_field_editor/public/components/preview/preview_controller.tsx @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { DataView, + DataViewLazy, DataViewField, DataViewsPublicPluginStart, } from '@kbn/data-views-plugin/public'; @@ -36,7 +37,8 @@ export const defaultValueFormatter = (value: unknown) => { }; interface PreviewControllerArgs { - dataView: DataView; + dataView: DataViewLazy; + dataViewToUpdate: DataView | DataViewLazy; onSave: (field: DataViewField[]) => void; fieldToEdit?: Field; fieldTypeToProcess: InternalFieldType; @@ -79,13 +81,25 @@ const previewStateDefault: PreviewState = { /** Flag to show/hide the preview panel */ isPanelVisible: true, isSaving: false, + concreteFields: [], + fieldMap: {}, }; export class PreviewController { - constructor({ deps, dataView, onSave, fieldToEdit, fieldTypeToProcess }: PreviewControllerArgs) { + constructor({ + deps, + // using two different data view references while API consumers might be passing in + // dataView or dataViewLazy. Don't want to rely on DataView with full field list. + dataView, + dataViewToUpdate, + onSave, + fieldToEdit, + fieldTypeToProcess, + }: PreviewControllerArgs) { this.deps = deps; this.dataView = dataView; + this.dataViewToUpdate = dataViewToUpdate; this.onSave = onSave; this.fieldToEdit = fieldToEdit; @@ -98,10 +112,14 @@ export class PreviewController { this.state$ = this.internalState$ as BehaviorObservable<PreviewState>; this.fetchSampleDocuments(); + + this.setExistingConcreteFields(); + this.setFieldList(); } // dependencies - private dataView: DataView; + private dataView: DataViewLazy; + private dataViewToUpdate: DataView | DataViewLazy; private deps: { search: ISearchStart; @@ -120,11 +138,6 @@ export class PreviewController { private previewCount = 0; - private namesNotAllowed?: { - fields: string[]; - runtimeComposites: string[]; - }; - private updateState = (newState: Partial<PreviewState>) => { this.internalState$.next({ ...this.state$.getValue(), ...newState }); }; @@ -139,68 +152,81 @@ export class PreviewController { documentId: undefined, }; - getNamesNotAllowed = () => { - if (!this.namesNotAllowed) { - const fieldNames = this.dataView.fields.map((fld) => fld.name); - const runtimeCompositeNames = Object.entries(this.dataView.getAllRuntimeFields()) - .filter(([, _runtimeField]) => _runtimeField.type === 'composite') - .map(([_runtimeFieldName]) => _runtimeFieldName); - this.namesNotAllowed = { - fields: fieldNames, - runtimeComposites: runtimeCompositeNames, - }; - } + private setFieldList = async () => { + const fieldMap = ( + await this.dataView.getFields({ + fieldName: ['*'], + scripted: false, + runtime: false, + }) + ).getFieldMapSorted(); - return this.namesNotAllowed; + this.updateState({ fieldMap }); }; - getExistingConcreteFields = () => { + private setExistingConcreteFields = async () => { const existing: Array<{ name: string; type: string }> = []; - this.dataView.fields - .filter((fld) => { - const isFieldBeingEdited = this.fieldToEdit?.name === fld.name; - return !isFieldBeingEdited && fld.isMapped; + const fieldMap = ( + await this.dataView.getFields({ + fieldName: ['*'], + scripted: false, + runtime: false, }) - .forEach((fld) => { - existing.push({ - name: fld.name, - type: (fld.esTypes && fld.esTypes[0]) || '', - }); + ).getFieldMap(); + + // remove name of currently edited field + if (this.fieldToEdit?.name) { + delete fieldMap[this.fieldToEdit?.name]; + } + + Object.values(fieldMap).forEach((fld) => { + existing.push({ + name: fld.name, + type: (fld.esTypes && fld.esTypes[0]) || '', }); + }); - return existing; + this.updateState({ concreteFields: existing }); }; - updateConcreteField = (updatedField: Field): DataViewField[] => { - const editedField = this.dataView.getFieldByName(updatedField.name); + updateConcreteField = async (updatedField: Field): Promise<DataViewField[]> => { + const editedField = await this.dataViewToUpdate.getFieldByName(updatedField.name); if (!editedField) { throw new Error( `Unable to find field named '${ updatedField.name - }' on index pattern '${this.dataView.getIndexPattern()}'` + }' on index pattern '${this.dataViewToUpdate.getIndexPattern()}'` ); } // Update custom label, popularity and format + this.dataViewToUpdate.setFieldCustomLabel(updatedField.name, updatedField.customLabel); this.dataView.setFieldCustomLabel(updatedField.name, updatedField.customLabel); + this.dataViewToUpdate.setFieldCustomDescription( + updatedField.name, + updatedField.customDescription + ); this.dataView.setFieldCustomDescription(updatedField.name, updatedField.customDescription); if (updatedField.popularity !== undefined) { + this.dataViewToUpdate.setFieldCount(updatedField.name, updatedField.popularity || 0); this.dataView.setFieldCount(updatedField.name, updatedField.popularity || 0); } if (updatedField.format) { + this.dataViewToUpdate.setFieldFormat(updatedField.name, updatedField.format!); this.dataView.setFieldFormat(updatedField.name, updatedField.format!); } else { + this.dataViewToUpdate.deleteFieldFormat(updatedField.name); this.dataView.deleteFieldFormat(updatedField.name); } return [editedField]; }; - updateRuntimeField = (updatedField: Field): DataViewField[] => { + updateRuntimeField = async (updatedField: Field): Promise<DataViewField[]> => { const nameHasChanged = Boolean(this.fieldToEdit) && this.fieldToEdit!.name !== updatedField.name; const typeHasChanged = @@ -211,6 +237,7 @@ export class PreviewController { const { script } = updatedField; + // this seems a bit convoluted if (this.fieldTypeToProcess === 'runtime') { try { this.deps.usageCollection.reportUiCounter(pluginName, METRIC_TYPE.COUNT, 'save_runtime'); @@ -218,9 +245,15 @@ export class PreviewController { } catch {} // rename an existing runtime field if (nameHasChanged || hasChangeToOrFromComposite) { + this.dataViewToUpdate.removeRuntimeField(this.fieldToEdit!.name); this.dataView.removeRuntimeField(this.fieldToEdit!.name); } + this.dataViewToUpdate.addRuntimeField(updatedField.name, { + type: updatedField.type as RuntimeType, + script, + fields: updatedField.fields, + }); this.dataView.addRuntimeField(updatedField.name, { type: updatedField.type as RuntimeType, script, @@ -233,7 +266,8 @@ export class PreviewController { } catch {} } - return this.dataView.addRuntimeField(updatedField.name, updatedField); + this.dataView.addRuntimeField(updatedField.name, updatedField); + return this.dataViewToUpdate.addRuntimeField(updatedField.name, updatedField); }; saveField = async (updatedField: Field) => { @@ -251,8 +285,8 @@ export class PreviewController { try { const editedFields: DataViewField[] = this.fieldTypeToProcess === 'runtime' - ? this.updateRuntimeField(updatedField) - : this.updateConcreteField(updatedField as Field); + ? await this.updateRuntimeField(updatedField) + : await this.updateConcreteField(updatedField as Field); const afterSave = () => { const message = i18n.translate('indexPatternFieldEditor.deleteField.savedHeader', { @@ -264,8 +298,8 @@ export class PreviewController { this.onSave(editedFields); }; - if (this.dataView.isPersisted()) { - await this.deps.dataViews.updateSavedObject(this.dataView); + if (this.dataViewToUpdate.isPersisted()) { + await this.deps.dataViews.updateSavedObject(this.dataViewToUpdate); } afterSave(); diff --git a/src/plugins/data_view_field_editor/public/components/preview/types.ts b/src/plugins/data_view_field_editor/public/components/preview/types.ts index 143f25f0c90ac..63f7da71abfb1 100644 --- a/src/plugins/data_view_field_editor/public/components/preview/types.ts +++ b/src/plugins/data_view_field_editor/public/components/preview/types.ts @@ -12,6 +12,7 @@ import type { RuntimeField, SerializedFieldFormat, RuntimePrimitiveTypes, + DataViewField, } from '../../shared_imports'; import type { RuntimeFieldPainlessError } from '../../types'; import type { PreviewController } from './preview_controller'; @@ -69,6 +70,8 @@ export interface PreviewState { isPreviewAvailable: boolean; isPanelVisible: boolean; isSaving: boolean; + concreteFields: Array<{ name: string; type: string }>; + fieldMap: Record<string, DataViewField>; } export interface FetchDocError { diff --git a/src/plugins/data_view_field_editor/public/lib/remove_fields.ts b/src/plugins/data_view_field_editor/public/lib/remove_fields.ts index e3649569d5eb9..f2245c8eec34a 100644 --- a/src/plugins/data_view_field_editor/public/lib/remove_fields.ts +++ b/src/plugins/data_view_field_editor/public/lib/remove_fields.ts @@ -10,18 +10,28 @@ import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { NotificationsStart } from '@kbn/core/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { DataView, UsageCollectionStart } from '../shared_imports'; +import { DataView, DataViewLazy, UsageCollectionStart } from '../shared_imports'; import { pluginName } from '../constants'; export async function removeFields( fieldNames: string[], - dataView: DataView, + dataView: DataView | DataViewLazy, services: { dataViews: DataViewsPublicPluginStart; usageCollection: UsageCollectionStart; notifications: NotificationsStart; } ) { + // if we're not handed a DataViewLazy, then check to see if there's one in use + // it'll be in the cache + if (dataView.id && !(dataView instanceof DataViewLazy)) { + const lazy = await services.dataViews.getDataViewLazyFromCache(dataView.id); + if (lazy) { + fieldNames.forEach((fieldName) => { + lazy.removeRuntimeField(fieldName); + }); + } + } fieldNames.forEach((fieldName) => { dataView.removeRuntimeField(fieldName); }); diff --git a/src/plugins/data_view_field_editor/public/open_delete_modal.tsx b/src/plugins/data_view_field_editor/public/open_delete_modal.tsx index 0277563631250..139569275d16c 100644 --- a/src/plugins/data_view_field_editor/public/open_delete_modal.tsx +++ b/src/plugins/data_view_field_editor/public/open_delete_modal.tsx @@ -13,6 +13,7 @@ import { toMountPoint, DataViewsPublicPluginStart, DataView, + DataViewLazy, UsageCollectionStart, } from './shared_imports'; @@ -29,7 +30,7 @@ export interface OpenFieldDeleteModalOptions { * Config for the delete modal */ ctx: { - dataView: DataView; + dataView: DataView | DataViewLazy; }; /** * Callback fired when fields are deleted @@ -60,9 +61,9 @@ export class DeleteCompositeSubfield extends Error { export const getFieldDeleteModalOpener = ({ core, dataViews, usageCollection }: Dependencies) => - (options: OpenFieldDeleteModalOptions): CloseEditor => { + async (options: OpenFieldDeleteModalOptions): Promise<CloseEditor> => { if (typeof options.fieldName === 'string') { - const fieldToDelete = options.ctx.dataView.getFieldByName(options.fieldName); + const fieldToDelete = await options.ctx.dataView.getFieldByName(options.fieldName); // we can check for composite type since composite runtime field definitions themselves don't become fields const doesBelongToCompositeField = fieldToDelete?.runtimeField?.type === 'composite'; diff --git a/src/plugins/data_view_field_editor/public/open_editor.tsx b/src/plugins/data_view_field_editor/public/open_editor.tsx index e6caeacdfa7d9..3cdbbd7dc6224 100644 --- a/src/plugins/data_view_field_editor/public/open_editor.tsx +++ b/src/plugins/data_view_field_editor/public/open_editor.tsx @@ -20,9 +20,8 @@ import type { DataViewsPublicPluginStart, FieldFormatsStart, DataViewField, - DataViewLazy, } from './shared_imports'; -import { DataView } from './shared_imports'; +import { DataView, DataViewLazy } from './shared_imports'; import { createKibanaReactContext } from './shared_imports'; import type { CloseEditor, Field, InternalFieldType, PluginStart } from './types'; @@ -130,13 +129,14 @@ export const getFieldEditorOpener = }; }; - const dataView = - dataViewLazyOrNot instanceof DataView + const dataViewLazy = + dataViewLazyOrNot instanceof DataViewLazy ? dataViewLazyOrNot - : await dataViews.toDataView(dataViewLazyOrNot); + : await dataViews.toDataViewLazy(dataViewLazyOrNot); const dataViewField = fieldNameToEdit - ? dataView.getFieldByName(fieldNameToEdit) || getRuntimeField(fieldNameToEdit) + ? (await dataViewLazy.getFieldByName(fieldNameToEdit, true)) || + getRuntimeField(fieldNameToEdit) : undefined; if (fieldNameToEdit && !dataViewField) { @@ -168,8 +168,8 @@ export const getFieldEditorOpener = customLabel: dataViewField.customLabel, customDescription: dataViewField.customDescription, popularity: dataViewField.count, - format: dataView.getFormatterForFieldNoDefault(fieldNameToEdit!)?.toJSON(), - ...dataView.getRuntimeField(fieldNameToEdit!)!, + format: dataViewLazy.getFormatterForFieldNoDefault(fieldNameToEdit!)?.toJSON(), + ...dataViewLazy.getRuntimeField(fieldNameToEdit!)!, }; } else { // Concrete field @@ -179,7 +179,7 @@ export const getFieldEditorOpener = customLabel: dataViewField.customLabel, customDescription: dataViewField.customDescription, popularity: dataViewField.count, - format: dataView.getFormatterForFieldNoDefault(fieldNameToEdit!)?.toJSON(), + format: dataViewLazy.getFormatterForFieldNoDefault(fieldNameToEdit!)?.toJSON(), parentName: dataViewField.spec.parentName, }; } @@ -195,7 +195,11 @@ export const getFieldEditorOpener = fieldToEdit={field} fieldToCreate={fieldToCreate} fieldTypeToProcess={fieldTypeToProcess} - dataView={dataView} + // currently using two dataView versions since API consumer is still potentially using legacy dataView + // this is what is used internally + dataView={dataViewLazy} + // this is what has been passed by API consumer + dataViewToUpdate={dataViewLazyOrNot} search={search} dataViews={dataViews} notifications={notifications} diff --git a/src/plugins/data_view_field_editor/public/plugin.test.tsx b/src/plugins/data_view_field_editor/public/plugin.test.tsx index 317faa7bf5c63..4f5c773dd7a4e 100644 --- a/src/plugins/data_view_field_editor/public/plugin.test.tsx +++ b/src/plugins/data_view_field_editor/public/plugin.test.tsx @@ -118,7 +118,7 @@ describe('DataViewFieldEditorPlugin', () => { isPersisted: () => true, } as unknown as DataView; - openDeleteModal({ + await openDeleteModal({ onDelete: onDeleteSpy, ctx: { dataView: indexPatternMock }, fieldName: ['a', 'b', 'c'], @@ -147,7 +147,7 @@ describe('DataViewFieldEditorPlugin', () => { test('should return a handler to close the modal', async () => { const { openDeleteModal } = plugin.start(coreStart, pluginStart); - const closeModal = openDeleteModal({ fieldName: ['a'], ctx: { dataView: {} as any } }); + const closeModal = await openDeleteModal({ fieldName: ['a'], ctx: { dataView: {} as any } }); expect(typeof closeModal).toBe('function'); }); diff --git a/src/plugins/data_view_field_editor/public/shared_imports.ts b/src/plugins/data_view_field_editor/public/shared_imports.ts index 8042b1594c618..295e15d383c83 100644 --- a/src/plugins/data_view_field_editor/public/shared_imports.ts +++ b/src/plugins/data_view_field_editor/public/shared_imports.ts @@ -9,7 +9,7 @@ export type { DataPublicPluginStart } from '@kbn/data-plugin/public'; export type { DataViewsPublicPluginStart, DataViewField } from '@kbn/data-views-plugin/public'; -export { DataView } from '@kbn/data-views-plugin/public'; +export { DataView, DataViewLazy } from '@kbn/data-views-plugin/public'; export type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; export type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; @@ -21,7 +21,6 @@ export type { RuntimeFieldSubField, RuntimeFieldSubFields, RuntimePrimitiveTypes, - DataViewLazy, } from '@kbn/data-views-plugin/common'; export { KBN_FIELD_TYPES, ES_FIELD_TYPES } from '@kbn/data-plugin/common'; diff --git a/src/plugins/data_view_field_editor/public/types.ts b/src/plugins/data_view_field_editor/public/types.ts index 46c0725d78339..9d872694a21e8 100644 --- a/src/plugins/data_view_field_editor/public/types.ts +++ b/src/plugins/data_view_field_editor/public/types.ts @@ -43,7 +43,7 @@ export interface PluginStart { * Method to open the data view field delete fly-out * @param options Configuration options for the fly-out */ - openDeleteModal(options: OpenFieldDeleteModalOptions): CloseEditor; + openDeleteModal(options: OpenFieldDeleteModalOptions): Promise<CloseEditor>; fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors']; /** * Convenience method for user permissions checks diff --git a/src/plugins/data_views/common/data_views/data_view_lazy.stub.ts b/src/plugins/data_views/common/data_views/data_view_lazy.stub.ts new file mode 100644 index 0000000000000..a6be3083e63fd --- /dev/null +++ b/src/plugins/data_views/common/data_views/data_view_lazy.stub.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 { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common'; +import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; +import { DataViewLazy } from './data_view_lazy'; +import { DataViewSpec } from '../types'; + +/** + * Create a custom stub index pattern. Use it in your unit tests where an {@link DataViewLazy} expected. + * @param spec - Serialized index pattern object + * @param opts - Specify index pattern options + * @param deps - Optionally provide dependencies, you can provide a custom field formats implementation, by default a dummy mock is used + * + * @returns - an {@link DataViewLazy} instance + * + * + * @example + * + * You can provide a custom implementation or assert calls using jest.spyOn: + * + * ```ts + * const indexPattern = createStubIndexPattern({spec: {title: 'logs-*'}}); + * const spy = jest.spyOn(indexPattern, 'getFormatterForField'); + * + * // use `spy` as a regular jest mock + * + * ``` + */ +export const createStubDataViewLazy = ({ + spec, + opts, + deps, +}: { + spec: DataViewSpec; + opts?: { + shortDotsEnable?: boolean; + metaFields?: string[]; + }; + deps?: { + fieldFormats?: FieldFormatsStartCommon; + apiClient?: any; + }; +}): DataViewLazy => { + return new DataViewLazy({ + spec: { version: '1', ...spec }, + metaFields: opts?.metaFields ?? ['_id', '_type', '_source'], + shortDotsEnable: opts?.shortDotsEnable, + fieldFormats: deps?.fieldFormats ?? fieldFormatsMock, + apiClient: { + getFieldsForWildcard: jest.fn().mockResolvedValue({ fields: [] }), + ...deps?.apiClient, + }, + scriptedFieldsEnabled: true, + }); +}; diff --git a/src/plugins/data_views/common/data_views/data_view_lazy.test.ts b/src/plugins/data_views/common/data_views/data_view_lazy.test.ts index 836ef451dafbe..1ff5ae29f490e 100644 --- a/src/plugins/data_views/common/data_views/data_view_lazy.test.ts +++ b/src/plugins/data_views/common/data_views/data_view_lazy.test.ts @@ -491,6 +491,9 @@ describe('DataViewLazy', () => { Object.values((await dataViewLazy.getFields({ fieldName: ['*'] })).getFieldMap()).length - fieldCount ).toEqual(2); + expect(Object.keys(dataViewLazy.getRuntimeFields({ fieldName: ['new_field.a'] }))).toEqual([ + 'new_field.a', + ]); expect(dataViewLazy.getRuntimeField('new_field')).toMatchSnapshot(); expect((await dataViewLazy.toSpec(toSpecGetAllFields))!.fields!['new_field.a']).toBeDefined(); expect((await dataViewLazy.toSpec(toSpecGetAllFields))!.fields!['new_field.b']).toBeDefined(); diff --git a/src/plugins/data_views/common/data_views/data_view_lazy.ts b/src/plugins/data_views/common/data_views/data_view_lazy.ts index f991fbf4b44d8..95bbc21937cf1 100644 --- a/src/plugins/data_views/common/data_views/data_view_lazy.ts +++ b/src/plugins/data_views/common/data_views/data_view_lazy.ts @@ -120,7 +120,7 @@ export class DataViewLazy extends AbstractDataView { public getRuntimeFields = ({ fieldName = ['*'] }: Pick<GetFieldsParams, 'fieldName'>) => // getRuntimeFieldSpecMap flattens composites into a list of fields - Object.values(this.getRuntimeFieldSpecMap({ fieldName })).reduce<DataViewFieldMap>( + Object.values(this.getRuntimeFieldSpecMap({ fieldName: ['*'] })).reduce<DataViewFieldMap>( (col, field) => { if (!fieldMatchesFieldsRequested(field.name, fieldName)) { return col; diff --git a/src/plugins/data_views/common/data_views/data_views.ts b/src/plugins/data_views/common/data_views/data_views.ts index 099059a1ec335..4647bc47236ac 100644 --- a/src/plugins/data_views/common/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -294,7 +294,7 @@ export interface DataViewsServicePublicMethods { * @param displayErrors - If set false, API consumer is responsible for displaying and handling errors. */ updateSavedObject: ( - indexPattern: DataView, + indexPattern: AbstractDataView, saveAttempts?: number, ignoreErrors?: boolean, displayErrors?: boolean @@ -315,6 +315,7 @@ export interface DataViewsServicePublicMethods { getAllDataViewLazy: () => Promise<DataViewLazy[]>; getDataViewLazy: (id: string) => Promise<DataViewLazy>; + getDataViewLazyFromCache: (id: string) => Promise<DataViewLazy | undefined>; createDataViewLazy: (spec: DataViewSpec) => Promise<DataViewLazy>; @@ -512,8 +513,10 @@ export class DataViewsService { */ clearInstanceCache = (id?: string) => { if (id) { + this.dataViewLazyCache.delete(id); this.dataViewCache.delete(id); } else { + this.dataViewLazyCache.clear(); this.dataViewCache.clear(); } }; @@ -1011,6 +1014,10 @@ export class DataViewsService { } }; + getDataViewLazyFromCache = async (id: string) => { + return this.dataViewLazyCache.get(id); + }; + /** * Get an index pattern by id, cache optimized. * @param id @@ -1436,7 +1443,7 @@ export class DataViewsService { // unsaved DataViewLazy changes will not be reflected in the returned DataView async toDataView(dataViewLazy: DataViewLazy) { // if persisted - if (dataViewLazy.id) { + if (dataViewLazy.id && dataViewLazy.isPersisted()) { return this.get(dataViewLazy.id); } @@ -1460,7 +1467,7 @@ export class DataViewsService { // unsaved DataView changes will not be reflected in the returned DataViewLazy async toDataViewLazy(dataView: DataView) { // if persisted - if (dataView.id) { + if (dataView.id && dataView.isPersisted()) { const dataViewLazy = await this.getDataViewLazy(dataView.id); return dataViewLazy!; } diff --git a/src/plugins/data_views/public/index.ts b/src/plugins/data_views/public/index.ts index f690552b5a147..973205b634e24 100644 --- a/src/plugins/data_views/public/index.ts +++ b/src/plugins/data_views/public/index.ts @@ -47,7 +47,7 @@ export type { DataViewsServicePublic, DataViewsServicePublicDeps, } from './data_views_service_public'; -export { DataViewsApiClient, DataViewsService, DataView } from './data_views'; +export { DataViewsApiClient, DataViewsService, DataView, DataViewLazy } from './data_views'; export type { DataViewListItem } from './data_views'; export { UiSettingsPublicToCommon } from './ui_settings_wrapper'; diff --git a/src/plugins/data_views/public/mocks.ts b/src/plugins/data_views/public/mocks.ts index 47b1eb8f8d2e3..d4492664adea9 100644 --- a/src/plugins/data_views/public/mocks.ts +++ b/src/plugins/data_views/public/mocks.ts @@ -41,6 +41,7 @@ const createStartContract = (): Start => { getFieldsForIndexPattern: jest.fn(), create: jest.fn().mockReturnValue(Promise.resolve({})), toDataView: jest.fn().mockReturnValue(Promise.resolve({})), + toDataViewLazy: jest.fn().mockReturnValue(Promise.resolve({})), } as unknown as jest.Mocked<DataViewsContract>; }; diff --git a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts index 7ce9c50977cd1..4c9c434007e23 100644 --- a/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data_views/server/fetcher/index_patterns_fetcher.ts @@ -93,9 +93,7 @@ export class IndexPatternsFetcher { fieldTypes, includeEmptyFields, } = options; - const allowNoIndices = fieldCapsOptions - ? fieldCapsOptions.allow_no_indices - : this.allowNoIndices; + const allowNoIndices = fieldCapsOptions?.allow_no_indices || this.allowNoIndices; const expandWildcards = allowHidden ? 'all' : 'open'; diff --git a/src/plugins/data_views/server/rest_api_routes/public/test_utils.ts b/src/plugins/data_views/server/rest_api_routes/public/test_utils.ts index 3fea6a01a722a..680377161f634 100644 --- a/src/plugins/data_views/server/rest_api_routes/public/test_utils.ts +++ b/src/plugins/data_views/server/rest_api_routes/public/test_utils.ts @@ -7,5 +7,6 @@ */ export const getUsageCollection = () => ({ + domainId: 'abc123', incrementCounter: jest.fn(), }); diff --git a/src/plugins/discover/common/constants.ts b/src/plugins/discover/common/constants.ts index 1083257175d10..67f0bfc35e578 100644 --- a/src/plugins/discover/common/constants.ts +++ b/src/plugins/discover/common/constants.ts @@ -21,3 +21,6 @@ export enum VIEW_MODE { export const getDefaultRowsPerPage = (uiSettings: IUiSettingsClient): number => { return parseInt(uiSettings.get(SAMPLE_ROWS_PER_PAGE_SETTING), 10) || DEFAULT_ROWS_PER_PAGE; }; + +// local storage key for the ES|QL to Dataviews transition modal +export const ESQL_TRANSITION_MODAL_KEY = 'data.textLangTransitionModal'; diff --git a/src/plugins/discover/common/esql_locator.ts b/src/plugins/discover/common/esql_locator.ts index fa7ca618e5bf9..ce4c772cc676c 100644 --- a/src/plugins/discover/common/esql_locator.ts +++ b/src/plugins/discover/common/esql_locator.ts @@ -9,14 +9,14 @@ import { DISCOVER_ESQL_LOCATOR } from '@kbn/deeplinks-analytics'; import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/common'; import { SerializableRecord } from '@kbn/utility-types'; -import { getIndexForESQLQuery, getInitialESQLQuery } from '@kbn/esql-utils'; +import { getIndexForESQLQuery, getInitialESQLQuery, getESQLAdHocDataview } from '@kbn/esql-utils'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; export type DiscoverESQLLocatorParams = SerializableRecord; export interface DiscoverESQLLocatorDependencies { discoverAppLocator: LocatorPublic<SerializableRecord>; - getIndices: DataViewsPublicPluginStart['getIndices']; + dataViews: DataViewsPublicPluginStart; } export type DiscoverESQLLocator = LocatorPublic<DiscoverESQLLocatorParams>; @@ -27,10 +27,10 @@ export class DiscoverESQLLocatorDefinition implements LocatorDefinition<Discover constructor(protected readonly deps: DiscoverESQLLocatorDependencies) {} public readonly getLocation = async () => { - const { discoverAppLocator, getIndices } = this.deps; - - const indexName = await getIndexForESQLQuery({ dataViews: { getIndices } }); - const esql = getInitialESQLQuery(indexName ?? '*'); + const { discoverAppLocator, dataViews } = this.deps; + const indexName = (await getIndexForESQLQuery({ dataViews })) ?? '*'; + const dataView = await getESQLAdHocDataview(`from ${indexName}`, dataViews); + const esql = getInitialESQLQuery(dataView); const params = { query: { esql }, diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx index 85c2dd581eecb..458d3dcb6318c 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx @@ -114,15 +114,10 @@ describe('Discover documents layout', () => { }); test('should render customisations', async () => { - const customControlColumnsConfiguration = () => ({ - leadingControlColumns: [], - trailingControlColumns: [], - }); - const customization: DiscoverCustomization = { id: 'data_table', logsEnabled: true, - customControlColumnsConfiguration, + rowAdditionalLeadingControls: [], }; customisationService.set(customization); @@ -130,8 +125,8 @@ describe('Discover documents layout', () => { const discoverGridComponent = component.find(DiscoverGrid); expect(discoverGridComponent.exists()).toBeTruthy(); - expect(discoverGridComponent.prop('customControlColumnsConfiguration')).toEqual( - customControlColumnsConfiguration + expect(discoverGridComponent.prop('rowAdditionalLeadingControls')).toBe( + customization.rowAdditionalLeadingControls ); expect(discoverGridComponent.prop('externalCustomRenderers')).toBeDefined(); expect(discoverGridComponent.prop('customGridColumnsConfiguration')).toBeDefined(); 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 9e061beffc4fc..e1b5636d010b1 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 @@ -259,7 +259,7 @@ function DiscoverDocumentsComponent({ [dataView, onAddColumn, onAddFilter, onRemoveColumn, query, savedSearch.id, setExpandedDoc] ); - const { customControlColumnsConfiguration } = useDiscoverCustomization('data_table') || {}; + const { rowAdditionalLeadingControls } = useDiscoverCustomization('data_table') || {}; const { customCellRenderer, customGridColumnsConfiguration } = useContextualGridCustomisations() || {}; const additionalFieldGroups = useAdditionalFieldGroups(); @@ -417,6 +417,7 @@ function DiscoverDocumentsComponent({ onUpdateRowHeight={onUpdateRowHeight} isSortEnabled={true} isPlainRecord={isEsqlMode} + isPaginationEnabled={!isEsqlMode} rowsPerPageState={rowsPerPage ?? getDefaultRowsPerPage(services.uiSettings)} onUpdateRowsPerPage={onUpdateRowsPerPage} maxAllowedSampleSize={getMaxAllowedSampleSize(services.uiSettings)} @@ -434,7 +435,7 @@ function DiscoverDocumentsComponent({ componentsTourSteps={TOUR_STEPS} externalCustomRenderers={cellRenderers} customGridColumnsConfiguration={customGridColumnsConfiguration} - customControlColumnsConfiguration={customControlColumnsConfiguration} + rowAdditionalLeadingControls={rowAdditionalLeadingControls} additionalFieldGroups={additionalFieldGroups} /> </CellActionsProvider> 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 55fe8825477d9..8205c682cbc58 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 @@ -11,11 +11,8 @@ import { type DataView, DataViewType } from '@kbn/data-views-plugin/public'; import { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; import { ENABLE_ESQL } from '@kbn/esql-utils'; import { TextBasedLanguages } from '@kbn/esql-utils'; -import { - useSavedSearch, - useSavedSearchHasChanged, - useSavedSearchInitial, -} from '../../state_management/discover_state_provider'; +import { useSavedSearchInitial } from '../../state_management/discover_state_provider'; +import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants'; import { useInternalStateSelector } from '../../state_management/discover_internal_state_container'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import type { DiscoverStateContainer } from '../../state_management/discover_state'; @@ -25,6 +22,7 @@ import { addLog } from '../../../../utils/add_log'; import { useAppStateSelector } from '../../state_management/discover_app_state_container'; import { useDiscoverTopNav } from './use_discover_topnav'; import { useIsEsqlMode } from '../../hooks/use_is_esql_mode'; +import { ESQLToDataViewTransitionModal } from './esql_dataview_transition'; export interface DiscoverTopNavProps { savedQuery?: string; @@ -59,6 +57,9 @@ export const DiscoverTopNav = ({ const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews); const dataView = useInternalStateSelector((state) => state.dataView!); const savedDataViews = useInternalStateSelector((state) => state.savedDataViews); + const isESQLToDataViewTransitionModalVisible = useInternalStateSelector( + (state) => state.isESQLToDataViewTransitionModalVisible + ); const savedSearch = useSavedSearchInitial(); const isEsqlMode = useIsEsqlMode(); const showDatePicker = useMemo(() => { @@ -150,17 +151,34 @@ export const DiscoverTopNav = ({ } }; - const onEsqlSavedAndExit = useCallback( - ({ onSave, onCancel }) => { - onSaveSearch({ - savedSearch: stateContainer.savedSearchState.getState(), - services, - state: stateContainer, - onClose: onCancel, - onSaveCb: onSave, - }); + const onESQLToDataViewTransitionModalClose = useCallback( + (shouldDismissModal?: boolean, needsSave?: boolean) => { + if (shouldDismissModal) { + services.storage.set(ESQL_TRANSITION_MODAL_KEY, true); + } + stateContainer.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(false); + // the user dismissed the modal, we don't need to save the search or switch to the data view mode + if (needsSave == null) { + return; + } + if (needsSave) { + onSaveSearch({ + savedSearch: stateContainer.savedSearchState.getState(), + services, + state: stateContainer, + onClose: () => + stateContainer.internalState.transitions.setIsESQLToDataViewTransitionModalVisible( + false + ), + onSaveCb: () => { + stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? ''); + }, + }); + } else { + stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? ''); + } }, - [services, stateContainer] + [dataView.id, services, stateContainer] ); const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer }); @@ -181,8 +199,6 @@ export const DiscoverTopNav = ({ topNavMenu, ]); - const savedSearchId = useSavedSearch().id; - const savedSearchHasChanged = useSavedSearchHasChanged(); const dataViewPickerProps: DataViewPickerProps = useMemo(() => { const isESQLModeEnabled = uiSettings.get(ENABLE_ESQL); const supportedTextBasedLanguages: DataViewPickerProps['textBasedLanguages'] = isESQLModeEnabled @@ -201,7 +217,6 @@ export const DiscoverTopNav = ({ onCreateDefaultAdHocDataView: stateContainer.actions.createAndAppendAdHocDataView, onChangeDataView: stateContainer.actions.onChangeDataView, textBasedLanguages: supportedTextBasedLanguages, - shouldShowTextBasedLanguageTransitionModal: !savedSearchId || savedSearchHasChanged, adHocDataViews, savedDataViews, onEditDataView, @@ -213,8 +228,6 @@ export const DiscoverTopNav = ({ dataView, onEditDataView, savedDataViews, - savedSearchHasChanged, - savedSearchId, stateContainer, uiSettings, ]); @@ -230,40 +243,44 @@ export const DiscoverTopNav = ({ !!searchBarCustomization?.CustomDataViewPicker || !!searchBarCustomization?.hideDataViewPicker; return ( - <SearchBar - {...topNavProps} - appName="discover" - indexPatterns={[dataView]} - onQuerySubmit={stateContainer.actions.onUpdateQuery} - onCancel={onCancelClick} - isLoading={isLoading} - onSavedQueryIdChange={updateSavedQueryId} - query={query} - savedQueryId={savedQuery} - screenTitle={savedSearch.title} - showDatePicker={showDatePicker} - saveQueryMenuVisibility={ - services.capabilities.discover.saveQuery ? 'allowed_by_app_privilege' : 'globally_managed' - } - showSearchBar={true} - useDefaultBehaviors={true} - dataViewPickerOverride={ - searchBarCustomization?.CustomDataViewPicker ? ( - <searchBarCustomization.CustomDataViewPicker /> - ) : undefined - } - dataViewPickerComponentProps={ - shouldHideDefaultDataviewPicker ? undefined : dataViewPickerProps - } - displayStyle="detached" - textBasedLanguageModeErrors={esqlModeErrors ? [esqlModeErrors] : undefined} - textBasedLanguageModeWarning={esqlModeWarning} - onTextBasedSavedAndExit={onEsqlSavedAndExit} - prependFilterBar={ - searchBarCustomization?.PrependFilterBar ? ( - <searchBarCustomization.PrependFilterBar /> - ) : undefined - } - /> + <> + <SearchBar + {...topNavProps} + appName="discover" + indexPatterns={[dataView]} + onQuerySubmit={stateContainer.actions.onUpdateQuery} + onCancel={onCancelClick} + isLoading={isLoading} + onSavedQueryIdChange={updateSavedQueryId} + query={query} + savedQueryId={savedQuery} + screenTitle={savedSearch.title} + showDatePicker={showDatePicker} + saveQueryMenuVisibility={ + services.capabilities.discover.saveQuery ? 'allowed_by_app_privilege' : 'globally_managed' + } + showSearchBar={true} + useDefaultBehaviors={true} + dataViewPickerOverride={ + searchBarCustomization?.CustomDataViewPicker ? ( + <searchBarCustomization.CustomDataViewPicker /> + ) : undefined + } + dataViewPickerComponentProps={ + shouldHideDefaultDataviewPicker ? undefined : dataViewPickerProps + } + displayStyle="detached" + textBasedLanguageModeErrors={esqlModeErrors ? [esqlModeErrors] : undefined} + textBasedLanguageModeWarning={esqlModeWarning} + prependFilterBar={ + searchBarCustomization?.PrependFilterBar ? ( + <searchBarCustomization.PrependFilterBar /> + ) : undefined + } + /> + {isESQLToDataViewTransitionModalVisible && ( + <ESQLToDataViewTransitionModal onClose={onESQLToDataViewTransitionModalClose} /> + )} + </> ); }; diff --git a/src/plugins/discover/public/application/main/components/top_nav/esql_dataview_transition/esql_dataview_transition_modal.tsx b/src/plugins/discover/public/application/main/components/top_nav/esql_dataview_transition/esql_dataview_transition_modal.tsx new file mode 100644 index 0000000000000..481647e18c156 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/top_nav/esql_dataview_transition/esql_dataview_transition_modal.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, useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FEEDBACK_LINK } from '@kbn/esql-utils'; +import { + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiButton, + EuiButtonEmpty, + EuiText, + EuiCheckbox, + EuiFlexItem, + EuiFlexGroup, + EuiLink, + EuiHorizontalRule, +} from '@elastic/eui'; + +export interface ESQLToDataViewTransitionModalProps { + onClose: (dismissFlag?: boolean, needsSave?: boolean) => void; +} +// Needed for React.lazy +// eslint-disable-next-line import/no-default-export +export default function ESQLToDataViewTransitionModal({ + onClose, +}: ESQLToDataViewTransitionModalProps) { + const [dismissModalChecked, setDismissModalChecked] = useState(false); + const onTransitionModalDismiss = useCallback((e) => { + setDismissModalChecked(e.target.checked); + }, []); + + return ( + <EuiModal + onClose={() => onClose()} + style={{ width: 700 }} + data-test-subj="discover-esql-to-dataview-modal" + > + <EuiModalHeader> + <EuiModalHeaderTitle> + {i18n.translate('discover.esqlToDataViewTransitionModal.title', { + defaultMessage: 'Unsaved changes', + })} + </EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <EuiText size="m"> + {i18n.translate('discover.esqlToDataviewTransitionModalBody', { + defaultMessage: + 'Switching data views removes the current ES|QL query. Save this search to avoid losing work.', + })} + </EuiText> + <EuiFlexGroup alignItems="center" justifyContent="flexEnd" gutterSize="xs"> + <EuiFlexItem grow={false}> + <EuiLink external href={FEEDBACK_LINK} target="_blank"> + {i18n.translate('discover.esqlToDataViewTransitionModal.feedbackLink', { + defaultMessage: 'Submit ES|QL feedback', + })} + </EuiLink> + </EuiFlexItem> + </EuiFlexGroup> + <EuiHorizontalRule margin="s" /> + </EuiModalBody> + <EuiModalFooter css={{ paddingBlockStart: 0 }}> + <EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="none"> + <EuiFlexItem grow={false}> + <EuiCheckbox + id="dismiss-text-based-languages-transition-modal" + label={i18n.translate('discover.esqlToDataViewTransitionModal.dismissButtonLabel', { + defaultMessage: "Don't ask me again", + })} + checked={dismissModalChecked} + onChange={onTransitionModalDismiss} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiFlexGroup gutterSize="m"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty + onClick={() => onClose(dismissModalChecked, false)} + color="danger" + iconType="trash" + data-test-subj="discover-esql-to-dataview-no-save-btn" + > + {i18n.translate('discover.esqlToDataViewTransitionModal.closeButtonLabel', { + defaultMessage: 'Discard and switch', + })} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton + onClick={() => onClose(dismissModalChecked, true)} + fill + color="primary" + iconType="save" + data-test-subj="discover-esql-to-dataview-save-btn" + > + {i18n.translate('discover.esqlToDataViewTransitionModal.saveButtonLabel', { + defaultMessage: 'Save and switch', + })} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiModalFooter> + </EuiModal> + ); +} diff --git a/src/plugins/discover/public/application/main/components/top_nav/esql_dataview_transition/index.tsx b/src/plugins/discover/public/application/main/components/top_nav/esql_dataview_transition/index.tsx new file mode 100644 index 0000000000000..af751b037b54a --- /dev/null +++ b/src/plugins/discover/public/application/main/components/top_nav/esql_dataview_transition/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 and the Server Side Public License, v 1; you may not 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 { ESQLToDataViewTransitionModalProps } from './esql_dataview_transition_modal'; + +const Fallback = () => <div />; + +const LazyESQLToDataViewTransitionModal = React.lazy( + () => import('./esql_dataview_transition_modal') +); +export const ESQLToDataViewTransitionModal = (props: ESQLToDataViewTransitionModalProps) => ( + <React.Suspense fallback={<Fallback />}> + <LazyESQLToDataViewTransitionModal {...props} /> + </React.Suspense> +); 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 2faf12d62bf0a..79ef9fe6b3dc9 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 @@ -17,6 +17,9 @@ const services = { save: true, }, }, + uiSettings: { + get: jest.fn(() => true), + }, } as unknown as DiscoverServices; const state = {} as unknown as DiscoverStateContainer; @@ -30,9 +33,21 @@ test('getTopNavLinks result', () => { isEsqlMode: false, adHocDataViews: [], topNavCustomization: undefined, + shouldShowESQLToDataViewTransitionModal: false, }); expect(topNavLinks).toMatchInlineSnapshot(` Array [ + Object { + "color": "text", + "emphasize": true, + "fill": false, + "iconType": "editorRedo", + "id": "esql", + "label": "Try ES|QL", + "run": [Function], + "testId": "select-text-based-language-btn", + "tooltip": "ES|QL is Elastic's powerful new piped query language.", + }, Object { "description": "New Search", "id": "new", @@ -83,9 +98,21 @@ test('getTopNavLinks result for ES|QL mode', () => { isEsqlMode: true, adHocDataViews: [], topNavCustomization: undefined, + shouldShowESQLToDataViewTransitionModal: false, }); expect(topNavLinks).toMatchInlineSnapshot(` Array [ + Object { + "color": "text", + "emphasize": true, + "fill": false, + "iconType": "editorRedo", + "id": "esql", + "label": "Switch to classic", + "run": [Function], + "testId": "switch-to-dataviews", + "tooltip": "Switch to KQL or Lucene syntax.", + }, Object { "description": "New Search", "id": "new", 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 a25e10ce3b0be..72cd9d71d12df 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 @@ -11,7 +11,10 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; import { omit } from 'lodash'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { ENABLE_ESQL } from '@kbn/esql-utils'; import type { DiscoverAppLocatorParams } from '../../../../../common'; +import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants'; import { showOpenSearchPanel } from './show_open_search_panel'; import { getSharingData, showPublicUrlSwitch } from '../../../../utils/get_sharing_data'; import { DiscoverServices } from '../../../../build_services'; @@ -31,6 +34,7 @@ export const getTopNavLinks = ({ isEsqlMode, adHocDataViews, topNavCustomization, + shouldShowESQLToDataViewTransitionModal, }: { dataView: DataView | undefined; services: DiscoverServices; @@ -39,6 +43,7 @@ export const getTopNavLinks = ({ isEsqlMode: boolean; adHocDataViews: DataView[]; topNavCustomization: TopNavCustomization | undefined; + shouldShowESQLToDataViewTransitionModal: boolean; }): TopNavMenuData[] => { const alerts = { id: 'alerts', @@ -60,6 +65,55 @@ export const getTopNavLinks = ({ testId: 'discoverAlertsButton', }; + /** + * Switches from ES|QL to classic mode and vice versa + */ + const esqLDataViewTransitionToggle = { + id: 'esql', + label: isEsqlMode + ? i18n.translate('discover.localMenu.switchToClassicTitle', { + defaultMessage: 'Switch to classic', + }) + : i18n.translate('discover.localMenu.tryESQLTitle', { + defaultMessage: 'Try ES|QL', + }), + emphasize: true, + iconType: 'editorRedo', + fill: false, + color: 'text', + tooltip: isEsqlMode + ? i18n.translate('discover.localMenu.switchToClassicTooltipLabel', { + defaultMessage: 'Switch to KQL or Lucene syntax.', + }) + : i18n.translate('discover.localMenu.esqlTooltipLabel', { + defaultMessage: `ES|QL is Elastic's powerful new piped query language.`, + }), + run: () => { + if (dataView) { + if (isEsqlMode) { + services.trackUiMetric?.(METRIC_TYPE.CLICK, `esql:back_to_classic_clicked`); + /** + * Display the transition modal if: + * - the user has not dismissed the modal + * - the user has opened and applied changes to the saved search + */ + if ( + shouldShowESQLToDataViewTransitionModal && + !services.storage.get(ESQL_TRANSITION_MODAL_KEY) + ) { + state.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(true); + } else { + state.actions.transitionFromESQLToDataView(dataView.id ?? ''); + } + } else { + state.actions.transitionFromDataViewToESQL(dataView); + services.trackUiMetric?.(METRIC_TYPE.CLICK, `esql:try_btn_clicked`); + } + } + }, + testId: isEsqlMode ? 'switch-to-dataviews' : 'select-text-based-language-btn', + }; + const newSearch = { id: 'new', label: i18n.translate('discover.localMenu.localMenu.newSearchTitle', { @@ -226,6 +280,10 @@ export const getTopNavLinks = ({ const defaultMenu = topNavCustomization?.defaultMenu; const entries = [...(topNavCustomization?.getMenuItems?.() ?? [])]; + if (services.uiSettings.get(ENABLE_ESQL)) { + entries.push({ data: esqLDataViewTransitionToggle, order: 0 }); + } + if (!defaultMenu?.newItem?.disabled) { entries.push({ data: newSearch, order: defaultMenu?.newItem?.order ?? 100 }); } diff --git a/src/plugins/discover/public/application/main/components/top_nav/use_discover_topnav.ts b/src/plugins/discover/public/application/main/components/top_nav/use_discover_topnav.ts index 06efd3205d1be..3c47ae0625433 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/use_discover_topnav.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/use_discover_topnav.ts @@ -13,6 +13,10 @@ import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { useInspector } from '../../hooks/use_inspector'; import { useIsEsqlMode } from '../../hooks/use_is_esql_mode'; import { useInternalStateSelector } from '../../state_management/discover_internal_state_container'; +import { + useSavedSearch, + useSavedSearchHasChanged, +} from '../../state_management/discover_state_provider'; import type { DiscoverStateContainer } from '../../state_management/discover_state'; import { getTopNavBadges } from './get_top_nav_badges'; import { getTopNavLinks } from './get_top_nav_links'; @@ -39,7 +43,9 @@ export const useDiscoverTopNav = ({ }), [stateContainer, services, hasUnsavedChanges, topNavCustomization] ); - + const savedSearchId = useSavedSearch().id; + const savedSearchHasChanged = useSavedSearchHasChanged(); + const shouldShowESQLToDataViewTransitionModal = !savedSearchId || savedSearchHasChanged; const dataView = useInternalStateSelector((state) => state.dataView); const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews); const isEsqlMode = useIsEsqlMode(); @@ -58,6 +64,7 @@ export const useDiscoverTopNav = ({ isEsqlMode, adHocDataViews, topNavCustomization, + shouldShowESQLToDataViewTransitionModal, }), [ adHocDataViews, @@ -67,6 +74,7 @@ export const useDiscoverTopNav = ({ services, stateContainer, topNavCustomization, + shouldShowESQLToDataViewTransitionModal, ] ); diff --git a/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts b/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts index 864c44fd6ce09..7d71e73d1b704 100644 --- a/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts +++ b/src/plugins/discover/public/application/main/data_fetching/fetch_all.test.ts @@ -73,6 +73,10 @@ describe('test fetchAll', () => { expandedDoc: undefined, customFilters: [], overriddenVisContextAfterInvalidation: undefined, + resetDefaultProfileState: { + columns: false, + rowHeight: false, + }, }), searchSessionId: '123', initialFetchStatus: FetchStatus.UNINITIALIZED, @@ -261,6 +265,10 @@ describe('test fetchAll', () => { expandedDoc: undefined, customFilters: [], overriddenVisContextAfterInvalidation: undefined, + resetDefaultProfileState: { + columns: false, + rowHeight: false, + }, }), }; fetchAll(subjects, false, deps); @@ -379,6 +387,10 @@ describe('test fetchAll', () => { expandedDoc: undefined, customFilters: [], overriddenVisContextAfterInvalidation: undefined, + resetDefaultProfileState: { + columns: false, + rowHeight: false, + }, }), }; fetchAll(subjects, false, deps); diff --git a/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts b/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts index aed3e6f9a0222..2f7134e810611 100644 --- a/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts +++ b/src/plugins/discover/public/application/main/data_fetching/fetch_all.ts @@ -8,7 +8,7 @@ import { Adapters } from '@kbn/inspector-plugin/common'; import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public'; -import { BehaviorSubject, filter, firstValueFrom, map, merge, scan } from 'rxjs'; +import { BehaviorSubject, combineLatest, filter, firstValueFrom, switchMap } from 'rxjs'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { isEqual } from 'lodash'; import { isOfAggregateQueryType } from '@kbn/es-query'; @@ -53,7 +53,8 @@ export interface FetchDeps { export function fetchAll( dataSubjects: SavedSearchData, reset = false, - fetchDeps: FetchDeps + fetchDeps: FetchDeps, + onFetchRecordsComplete?: () => Promise<void> ): Promise<void> { const { initialFetchStatus, @@ -177,10 +178,10 @@ export function fetchAll( // Return a promise that will resolve once all the requests have finished or failed return firstValueFrom( - merge( - fetchStatusByType(dataSubjects.documents$, 'documents'), - fetchStatusByType(dataSubjects.totalHits$, 'totalHits') - ).pipe(scan(toRequestFinishedMap, {}), filter(allRequestsFinished)) + combineLatest([ + isComplete(dataSubjects.documents$).pipe(switchMap(async () => onFetchRecordsComplete?.())), + isComplete(dataSubjects.totalHits$), + ]) ).then(() => { // Send a complete message to main$ once all queries are done and if main$ // is not already in an ERROR state, e.g. because the document query has failed. @@ -250,16 +251,8 @@ export async function fetchMoreDocuments( } } -const fetchStatusByType = <T extends DataMsg>(subject: BehaviorSubject<T>, type: string) => - subject.pipe(map(({ fetchStatus }) => ({ type, fetchStatus }))); - -const toRequestFinishedMap = ( - currentMap: Record<string, boolean>, - { type, fetchStatus }: { type: string; fetchStatus: FetchStatus } -) => ({ - ...currentMap, - [type]: [FetchStatus.COMPLETE, FetchStatus.ERROR].includes(fetchStatus), -}); - -const allRequestsFinished = (requests: Record<string, boolean>) => - Object.values(requests).every((finished) => finished); +const isComplete = <T extends DataMsg>(subject: BehaviorSubject<T>) => { + return subject.pipe( + filter(({ fetchStatus }) => [FetchStatus.COMPLETE, FetchStatus.ERROR].includes(fetchStatus)) + ); +}; diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index f37487b6b93b7..0e1c1472ac556 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -377,7 +377,7 @@ function getLoadParamsForNewSearch(stateContainer: DiscoverStateContainer): { ? { // reset to a default ES|QL query query: { - esql: getInitialESQLQuery(prevDataView.getIndexPattern()), + esql: getInitialESQLQuery(prevDataView), }, } : undefined; diff --git a/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx b/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx index 5f6d35afe8434..10fe94f824681 100644 --- a/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx +++ b/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx @@ -23,6 +23,7 @@ import { DiscoverAppState } from '../state_management/discover_app_state_contain import { DiscoverStateContainer } from '../state_management/discover_state'; import { VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { dataViewAdHoc } from '../../../__mocks__/data_view_complex'; +import { buildDataTableRecord, EsHitRecord } from '@kbn/discover-utils'; function getHookProps( query: AggregateQuery | Query | undefined, @@ -487,4 +488,95 @@ describe('useEsqlMode', () => { }); }); }); + + it('should call setResetDefaultProfileState correctly when index pattern changes', async () => { + const { stateContainer } = renderHookWithContext(false); + const documents$ = stateContainer.dataState.data$.documents$; + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + query: { esql: 'from pattern1' }, + }); + await waitFor(() => + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: true, + rowHeight: true, + }) + ); + stateContainer.internalState.transitions.setResetDefaultProfileState({ + columns: false, + rowHeight: false, + }); + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + query: { esql: 'from pattern1' }, + }); + await waitFor(() => + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }) + ); + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + query: { esql: 'from pattern2' }, + }); + await waitFor(() => + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: true, + rowHeight: true, + }) + ); + }); + + it('should call setResetDefaultProfileState correctly when columns change', async () => { + const { stateContainer } = renderHookWithContext(false); + const documents$ = stateContainer.dataState.data$.documents$; + const result1 = [buildDataTableRecord({ message: 'foo' } as EsHitRecord)]; + const result2 = [buildDataTableRecord({ message: 'foo', extension: 'bar' } as EsHitRecord)]; + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + query: { esql: 'from pattern' }, + result: result1, + }); + await waitFor(() => + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: true, + rowHeight: true, + }) + ); + stateContainer.internalState.transitions.setResetDefaultProfileState({ + columns: false, + rowHeight: false, + }); + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + query: { esql: 'from pattern' }, + result: result1, + }); + await waitFor(() => + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }) + ); + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + query: { esql: 'from pattern' }, + result: result2, + }); + await waitFor(() => + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: true, + rowHeight: false, + }) + ); + }); }); diff --git a/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts b/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts index 841badc11537c..599b1ccd88ce5 100644 --- a/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts +++ b/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts @@ -18,6 +18,7 @@ import { getValidViewMode } from '../utils/get_valid_view_mode'; import { FetchStatus } from '../../types'; const MAX_NUM_OF_COLUMNS = 50; + /** * Hook to take care of ES|QL state transformations when a new result is returned * If necessary this is setting displayed columns and selected data view @@ -29,106 +30,122 @@ export function useEsqlMode({ stateContainer: DiscoverStateContainer; dataViews: DataViewsContract; }) { + const savedSearch = useSavedSearchInitial(); const prev = useRef<{ + initialFetch: boolean; query: string; - recentlyUpdatedToColumns: string[]; + allColumns: string[]; + defaultColumns: string[]; }>({ - recentlyUpdatedToColumns: [], + initialFetch: true, query: '', + allColumns: [], + defaultColumns: [], }); - const initialFetch = useRef<boolean>(true); - const savedSearch = useSavedSearchInitial(); const cleanup = useCallback(() => { - if (prev.current.query) { - // cleanup when it's not an ES|QL query - prev.current = { - recentlyUpdatedToColumns: [], - query: '', - }; - initialFetch.current = true; + if (!prev.current.query) { + return; } + + // cleanup when it's not an ES|QL query + prev.current = { + initialFetch: true, + query: '', + allColumns: [], + defaultColumns: [], + }; }, []); useEffect(() => { const subscription = stateContainer.dataState.data$.documents$ .pipe( switchMap(async (next) => { - const { query } = next; - if (!query || next.fetchStatus === FetchStatus.ERROR) { + const { query: nextQuery } = next; + + if (!nextQuery || next.fetchStatus === FetchStatus.ERROR) { return; } - const sendComplete = () => { - stateContainer.dataState.data$.documents$.next({ - ...next, - fetchStatus: FetchStatus.COMPLETE, - }); - }; + if (!isOfAggregateQueryType(nextQuery)) { + // cleanup for a "regular" query + cleanup(); + return; + } - const { viewMode } = stateContainer.appState.getState(); - const isEsqlQuery = isOfAggregateQueryType(query); + if (next.fetchStatus !== FetchStatus.PARTIAL) { + return; + } - if (isEsqlQuery) { - const hasResults = Boolean(next.result?.length); + let nextAllColumns = prev.current.allColumns; + let nextDefaultColumns = prev.current.defaultColumns; - if (next.fetchStatus !== FetchStatus.PARTIAL) { - return; - } + if (next.result?.length) { + nextAllColumns = Object.keys(next.result[0].raw); - let nextColumns: string[] = prev.current.recentlyUpdatedToColumns; + if (hasTransformationalCommand(nextQuery.esql)) { + nextDefaultColumns = nextAllColumns.slice(0, MAX_NUM_OF_COLUMNS); + } else { + nextDefaultColumns = []; + } + } - if (hasResults) { - const firstRow = next.result![0]; - const firstRowColumns = Object.keys(firstRow.raw); + if (prev.current.initialFetch) { + prev.current.initialFetch = false; + prev.current.query = nextQuery.esql; + prev.current.allColumns = nextAllColumns; + prev.current.defaultColumns = nextDefaultColumns; + } - if (hasTransformationalCommand(query.esql)) { - nextColumns = firstRowColumns.slice(0, MAX_NUM_OF_COLUMNS); - } else { - nextColumns = []; - } - } + const indexPatternChanged = + getIndexPatternFromESQLQuery(nextQuery.esql) !== + getIndexPatternFromESQLQuery(prev.current.query); - if (initialFetch.current) { - initialFetch.current = false; - prev.current.query = query.esql; - prev.current.recentlyUpdatedToColumns = nextColumns; - } + const allColumnsChanged = !isEqual(nextAllColumns, prev.current.allColumns); - const indexPatternChanged = - getIndexPatternFromESQLQuery(query.esql) !== - getIndexPatternFromESQLQuery(prev.current.query); + const changeDefaultColumns = + indexPatternChanged || !isEqual(nextDefaultColumns, prev.current.defaultColumns); - const addColumnsToState = - indexPatternChanged || !isEqual(nextColumns, prev.current.recentlyUpdatedToColumns); + const { viewMode } = stateContainer.appState.getState(); + const changeViewMode = viewMode !== getValidViewMode({ viewMode, isEsqlMode: true }); - const changeViewMode = viewMode !== getValidViewMode({ viewMode, isEsqlMode: true }); + if (indexPatternChanged) { + stateContainer.internalState.transitions.setResetDefaultProfileState({ + columns: true, + rowHeight: true, + }); + } else if (allColumnsChanged) { + stateContainer.internalState.transitions.setResetDefaultProfileState({ + columns: true, + rowHeight: false, + }); + } - if (!indexPatternChanged && !addColumnsToState && !changeViewMode) { - sendComplete(); - return; - } + prev.current.allColumns = nextAllColumns; - prev.current.query = query.esql; - prev.current.recentlyUpdatedToColumns = nextColumns; + if (indexPatternChanged || changeDefaultColumns || changeViewMode) { + prev.current.query = nextQuery.esql; + prev.current.defaultColumns = nextDefaultColumns; // just change URL state if necessary - if (addColumnsToState || changeViewMode) { + if (changeDefaultColumns || changeViewMode) { const nextState = { - ...(addColumnsToState && { columns: nextColumns }), + ...(changeDefaultColumns && { columns: nextDefaultColumns }), ...(changeViewMode && { viewMode: undefined }), }; + await stateContainer.appState.replaceUrlState(nextState); } - - sendComplete(); - } else { - // cleanup for a "regular" query - cleanup(); } + + stateContainer.dataState.data$.documents$.next({ + ...next, + fetchStatus: FetchStatus.COMPLETE, + }); }) ) .subscribe(); + return () => { // cleanup for e.g. when savedSearch is switched cleanup(); diff --git a/src/plugins/discover/public/application/main/state_management/discover_app_state_container.test.ts b/src/plugins/discover/public/application/main/state_management/discover_app_state_container.test.ts index 75ae6208be871..ffc566d10f316 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_app_state_container.test.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_app_state_container.test.ts @@ -8,39 +8,55 @@ import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; +import { + createKbnUrlStateStorage, + IKbnUrlStateStorage, + withNotifyOnErrors, +} from '@kbn/kibana-utils-plugin/public'; import type { Filter } from '@kbn/es-query'; import { History } from 'history'; -import { savedSearchMock } from '../../../__mocks__/saved_search'; import { discoverServiceMock } from '../../../__mocks__/services'; -import { - DiscoverAppStateContainer, - getDiscoverAppStateContainer, - isEqualState, -} from './discover_app_state_container'; +import { getDiscoverAppStateContainer, isEqualState } from './discover_app_state_container'; import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/common'; import { createDataViewDataSource } from '../../../../common/data_sources'; +import { getInternalStateContainer } from './discover_internal_state_container'; +import { + DiscoverSavedSearchContainer, + getSavedSearchContainer, +} from './discover_saved_search_container'; +import { getDiscoverGlobalStateContainer } from './discover_global_state_container'; let history: History; -let state: DiscoverAppStateContainer; +let stateStorage: IKbnUrlStateStorage; +let internalState: ReturnType<typeof getInternalStateContainer>; +let savedSearchState: DiscoverSavedSearchContainer; describe('Test discover app state container', () => { beforeEach(async () => { const storeInSessionStorage = discoverServiceMock.uiSettings.get('state:storeInSessionStorage'); const toasts = discoverServiceMock.core.notifications.toasts; - const stateStorage = createKbnUrlStateStorage({ + stateStorage = createKbnUrlStateStorage({ useHash: storeInSessionStorage, history, ...(toasts && withNotifyOnErrors(toasts)), }); - state = getDiscoverAppStateContainer({ - stateStorage, - savedSearch: savedSearchMock, + internalState = getInternalStateContainer(); + savedSearchState = getSavedSearchContainer({ services: discoverServiceMock, + globalStateContainer: getDiscoverGlobalStateContainer(stateStorage), }); }); + const getStateContainer = () => + getDiscoverAppStateContainer({ + stateStorage, + internalStateContainer: internalState, + savedSearchContainer: savedSearchState, + services: discoverServiceMock, + }); + test('hasChanged returns whether the current state has changed', async () => { + const state = getStateContainer(); state.set({ dataSource: createDataViewDataSource({ dataViewId: 'modified' }), }); @@ -50,6 +66,7 @@ describe('Test discover app state container', () => { }); test('getPrevious returns the state before the current', async () => { + const state = getStateContainer(); state.set({ dataSource: createDataViewDataSource({ dataViewId: 'first' }), }); @@ -110,6 +127,7 @@ describe('Test discover app state container', () => { } as SavedSearch; test('should return correct output', () => { + const state = getStateContainer(); const appState = state.getAppStateFromSavedSearch(localSavedSearchMock); expect(appState).toMatchObject( expect.objectContaining({ @@ -133,6 +151,7 @@ describe('Test discover app state container', () => { }); test('should return default query if query is undefined', () => { + const state = getStateContainer(); discoverServiceMock.data.query.queryString.getDefaultQuery = jest .fn() .mockReturnValue(defaultQuery); @@ -233,6 +252,7 @@ describe('Test discover app state container', () => { }); test('should automatically set ES|QL data source when query is ES|QL', () => { + const state = getStateContainer(); state.update({ dataSource: createDataViewDataSource({ dataViewId: 'test' }), }); @@ -244,4 +264,70 @@ describe('Test discover app state container', () => { }); expect(state.get().dataSource?.type).toBe('esql'); }); + + describe('initAndSync', () => { + it('should call setResetDefaultProfileState correctly with no initial state', () => { + const state = getStateContainer(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + state.initAndSync(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: true, + rowHeight: true, + }); + }); + + it('should call setResetDefaultProfileState correctly with initial columns', () => { + const stateStorageGetSpy = jest.spyOn(stateStorage, 'get'); + stateStorageGetSpy.mockReturnValue({ columns: ['test'] }); + const state = getStateContainer(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + state.initAndSync(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: true, + }); + }); + + it('should call setResetDefaultProfileState correctly with initial rowHeight', () => { + const stateStorageGetSpy = jest.spyOn(stateStorage, 'get'); + stateStorageGetSpy.mockReturnValue({ rowHeight: 5 }); + const state = getStateContainer(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + state.initAndSync(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: true, + rowHeight: false, + }); + }); + + it('should call setResetDefaultProfileState correctly with saved search', () => { + const stateStorageGetSpy = jest.spyOn(stateStorage, 'get'); + stateStorageGetSpy.mockReturnValue({ columns: ['test'], rowHeight: 5 }); + const savedSearchGetSpy = jest.spyOn(savedSearchState, 'getState'); + savedSearchGetSpy.mockReturnValue({ + id: 'test', + searchSource: createSearchSourceMock(), + managed: false, + }); + const state = getStateContainer(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + state.initAndSync(); + expect(internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + }); + }); }); diff --git a/src/plugins/discover/public/application/main/state_management/discover_app_state_container.ts b/src/plugins/discover/public/application/main/state_management/discover_app_state_container.ts index 5ec9ca4d7215c..f364530faba5e 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_app_state_container.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_app_state_container.ts @@ -38,6 +38,8 @@ import { DiscoverDataSource, isDataSourceType, } from '../../../../common/data_sources'; +import type { DiscoverInternalStateContainer } from './discover_internal_state_container'; +import type { DiscoverSavedSearchContainer } from './discover_saved_search_container'; export const APP_STATE_URL_KEY = '_a'; export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<DiscoverAppState> { @@ -54,10 +56,9 @@ export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<Disco */ hasChanged: () => boolean; /** - * Initializes the state by the given saved search and starts syncing the state with the URL - * @param currentSavedSearch + * Initializes the app state and starts syncing it with the URL */ - initAndSync: (currentSavedSearch: SavedSearch) => () => void; + initAndSync: () => () => void; /** * Replaces the current state in URL with the given state * @param newState @@ -82,11 +83,10 @@ export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<Disco * @param replace */ update: (newPartial: DiscoverAppState, replace?: boolean) => void; - /* * Get updated AppState when given a saved search * - * */ + */ getAppStateFromSavedSearch: (newSavedSearch: SavedSearch) => DiscoverAppState; } @@ -157,6 +157,17 @@ export interface DiscoverAppState { breakdownField?: string; } +export interface AppStateUrl extends Omit<DiscoverAppState, 'sort'> { + /** + * Necessary to take care of legacy links [fieldName,direction] + */ + sort?: string[][] | [string, string]; + /** + * Legacy data view ID prop + */ + index?: string; +} + export const { Provider: DiscoverAppStateProvider, useSelector: useAppStateSelector } = createStateContainerReactHelpers<ReduxLikeStateContainer<DiscoverAppState>>(); @@ -168,14 +179,20 @@ export const { Provider: DiscoverAppStateProvider, useSelector: useAppStateSelec */ export const getDiscoverAppStateContainer = ({ stateStorage, - savedSearch, + internalStateContainer, + savedSearchContainer, services, }: { stateStorage: IKbnUrlStateStorage; - savedSearch: SavedSearch; + internalStateContainer: DiscoverInternalStateContainer; + savedSearchContainer: DiscoverSavedSearchContainer; services: DiscoverServices; }): DiscoverAppStateContainer => { - let initialState = getInitialState(stateStorage, savedSearch, services); + let initialState = getInitialState( + getCurrentUrlState(stateStorage, services), + savedSearchContainer.getState(), + services + ); let previousState = initialState; const appStateContainer = createStateContainer<DiscoverAppState>(initialState); @@ -234,9 +251,20 @@ export const getDiscoverAppStateContainer = ({ }); }; - const initializeAndSync = (currentSavedSearch: SavedSearch) => { + const initializeAndSync = () => { + const currentSavedSearch = savedSearchContainer.getState(); + addLog('[appState] initialize state and sync with URL', currentSavedSearch); + if (!currentSavedSearch.id) { + const { columns, rowHeight } = getCurrentUrlState(stateStorage, services); + + internalStateContainer.transitions.setResetDefaultProfileState({ + columns: columns === undefined, + rowHeight: rowHeight === undefined, + }); + } + const { data } = services; const savedSearchDataView = currentSavedSearch.searchSource.getField('index'); const appState = enhancedAppContainer.getState(); @@ -314,34 +342,24 @@ export const getDiscoverAppStateContainer = ({ }; }; -export interface AppStateUrl extends Omit<DiscoverAppState, 'sort'> { - /** - * Necessary to take care of legacy links [fieldName,direction] - */ - sort?: string[][] | [string, string]; - /** - * Legacy data view ID prop - */ - index?: string; +function getCurrentUrlState(stateStorage: IKbnUrlStateStorage, services: DiscoverServices) { + return cleanupUrlState( + stateStorage.get<AppStateUrl>(APP_STATE_URL_KEY) ?? {}, + services.uiSettings + ); } export function getInitialState( - stateStorage: IKbnUrlStateStorage | undefined, + initialUrlState: DiscoverAppState | undefined, savedSearch: SavedSearch, services: DiscoverServices ) { - const appStateFromUrl = stateStorage?.get<AppStateUrl>(APP_STATE_URL_KEY); const defaultAppState = getStateDefaults({ savedSearch, services, }); return handleSourceColumnState( - appStateFromUrl == null - ? defaultAppState - : { - ...defaultAppState, - ...cleanupUrlState(appStateFromUrl, services.uiSettings), - }, + initialUrlState === undefined ? defaultAppState : { ...defaultAppState, ...initialUrlState }, services.uiSettings ); } diff --git a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.test.ts b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.test.ts index 05668e0406f9c..1c300aaf7cc15 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.test.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.test.ts @@ -165,4 +165,62 @@ describe('test getDataStateContainer', () => { dataState.refetch$.next('fetch_more'); }); + + it('should update app state from default profile state', async () => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + const dataState = stateContainer.dataState; + const dataUnsub = dataState.subscribe(); + const appUnsub = stateContainer.appState.initAndSync(); + discoverServiceMock.profilesManager.resolveDataSourceProfile({}); + stateContainer.actions.setDataView(dataViewMock); + stateContainer.internalState.transitions.setResetDefaultProfileState({ + columns: true, + rowHeight: true, + }); + dataState.data$.totalHits$.next({ + fetchStatus: FetchStatus.COMPLETE, + result: 0, + }); + dataState.refetch$.next(undefined); + await waitFor(() => { + expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE); + }); + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + expect(stateContainer.appState.get().columns).toEqual(['message', 'extension']); + expect(stateContainer.appState.get().rowHeight).toEqual(3); + dataUnsub(); + appUnsub(); + }); + + it('should not update app state from default profile state', async () => { + const stateContainer = getDiscoverStateMock({ isTimeBased: true }); + const dataState = stateContainer.dataState; + const dataUnsub = dataState.subscribe(); + const appUnsub = stateContainer.appState.initAndSync(); + discoverServiceMock.profilesManager.resolveDataSourceProfile({}); + stateContainer.actions.setDataView(dataViewMock); + stateContainer.internalState.transitions.setResetDefaultProfileState({ + columns: false, + rowHeight: false, + }); + dataState.data$.totalHits$.next({ + fetchStatus: FetchStatus.COMPLETE, + result: 0, + }); + dataState.refetch$.next(undefined); + await waitFor(() => { + expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE); + }); + expect(stateContainer.internalState.get().resetDefaultProfileState).toEqual({ + columns: false, + rowHeight: false, + }); + expect(stateContainer.appState.get().columns).toEqual(['default_column']); + expect(stateContainer.appState.get().rowHeight).toBeUndefined(); + dataUnsub(); + appUnsub(); + }); }); diff --git a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts index d467d965f012d..6e34982dc91ae 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts @@ -10,24 +10,29 @@ import { BehaviorSubject, filter, map, mergeMap, Observable, share, Subject, tap import type { AutoRefreshDoneFn } from '@kbn/data-plugin/public'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { RequestAdapter } from '@kbn/inspector-plugin/common'; -import { SavedSearch } from '@kbn/saved-search-plugin/public'; +import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { AggregateQuery, isOfAggregateQueryType, Query } from '@kbn/es-query'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import { DataView } from '@kbn/data-views-plugin/common'; +import type { DataView } from '@kbn/data-views-plugin/common'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import type { DataTableRecord } from '@kbn/discover-utils/types'; -import { SEARCH_FIELDS_FROM_SOURCE, SEARCH_ON_PAGE_LOAD_SETTING } from '@kbn/discover-utils'; +import { + DEFAULT_COLUMNS_SETTING, + SEARCH_FIELDS_FROM_SOURCE, + SEARCH_ON_PAGE_LOAD_SETTING, +} from '@kbn/discover-utils'; import { getEsqlDataView } from './utils/get_esql_data_view'; -import { DiscoverAppState } from './discover_app_state_container'; -import { DiscoverServices } from '../../../build_services'; -import { DiscoverSearchSessionManager } from './discover_search_session'; +import type { DiscoverAppStateContainer } from './discover_app_state_container'; +import type { DiscoverServices } from '../../../build_services'; +import type { DiscoverSearchSessionManager } from './discover_search_session'; import { FetchStatus } from '../../types'; import { validateTimeRange } from './utils/validate_time_range'; import { fetchAll, fetchMoreDocuments } from '../data_fetching/fetch_all'; import { sendResetMsg } from '../hooks/use_saved_search_messages'; import { getFetch$ } from '../data_fetching/get_fetch_observable'; -import { InternalState } from './discover_internal_state_container'; +import type { DiscoverInternalStateContainer } from './discover_internal_state_container'; +import { getDefaultProfileState } from './utils/get_default_profile_state'; export interface SavedSearchData { main$: DataMain$; @@ -138,15 +143,15 @@ export interface DiscoverDataStateContainer { export function getDataStateContainer({ services, searchSessionManager, - getAppState, - getInternalState, + appStateContainer, + internalStateContainer, getSavedSearch, setDataView, }: { services: DiscoverServices; searchSessionManager: DiscoverSearchSessionManager; - getAppState: () => DiscoverAppState; - getInternalState: () => InternalState; + appStateContainer: DiscoverAppStateContainer; + internalStateContainer: DiscoverInternalStateContainer; getSavedSearch: () => SavedSearch; setDataView: (dataView: DataView) => void; }): DiscoverDataStateContainer { @@ -221,8 +226,8 @@ export function getDataStateContainer({ inspectorAdapters, searchSessionId, services, - getAppState, - getInternalState, + getAppState: appStateContainer.getState, + getInternalState: internalStateContainer.getState, savedSearch: getSavedSearch(), useNewFieldsApi: !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE), }; @@ -232,34 +237,65 @@ export function getDataStateContainer({ if (options.fetchMore) { abortControllerFetchMore = new AbortController(); - const fetchMoreStartTime = window.performance.now(); + await fetchMoreDocuments(dataSubjects, { abortController: abortControllerFetchMore, ...commonFetchDeps, }); + const fetchMoreDuration = window.performance.now() - fetchMoreStartTime; reportPerformanceMetricEvent(services.analytics, { eventName: 'discoverFetchMore', duration: fetchMoreDuration, }); + return; } await profilesManager.resolveDataSourceProfile({ - dataSource: getAppState().dataSource, + dataSource: appStateContainer.getState().dataSource, dataView: getSavedSearch().searchSource.getField('index'), - query: getAppState().query, + query: appStateContainer.getState().query, }); abortController = new AbortController(); const prevAutoRefreshDone = autoRefreshDone; - const fetchAllStartTime = window.performance.now(); - await fetchAll(dataSubjects, options.reset, { - abortController, - ...commonFetchDeps, - }); + + await fetchAll( + dataSubjects, + options.reset, + { + abortController, + ...commonFetchDeps, + }, + async () => { + const { resetDefaultProfileState, dataView } = internalStateContainer.getState(); + const { esqlQueryColumns } = dataSubjects.documents$.getValue(); + const defaultColumns = uiSettings.get<string[]>(DEFAULT_COLUMNS_SETTING, []); + + if (dataView) { + const stateUpdate = getDefaultProfileState({ + profilesManager, + resetDefaultProfileState, + defaultColumns, + dataView, + esqlQueryColumns, + }); + + if (stateUpdate) { + await appStateContainer.replaceUrlState(stateUpdate); + } + } + + internalStateContainer.transitions.setResetDefaultProfileState({ + columns: false, + rowHeight: false, + }); + } + ); + const fetchAllDuration = window.performance.now() - fetchAllStartTime; reportPerformanceMetricEvent(services.analytics, { eventName: 'discoverFetchAll', @@ -286,7 +322,7 @@ export function getDataStateContainer({ } const fetchQuery = async (resetQuery?: boolean) => { - const query = getAppState().query; + const query = appStateContainer.getState().query; const currentDataView = getSavedSearch().searchSource.getField('index'); if (isOfAggregateQueryType(query)) { @@ -301,6 +337,7 @@ export function getDataStateContainer({ } else { refetch$.next(undefined); } + return refetch$; }; diff --git a/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts b/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts index 4ebbe94832d0c..61e4754a181ca 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts @@ -24,6 +24,8 @@ export interface InternalState { expandedDoc: DataTableRecord | undefined; customFilters: Filter[]; overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined; // it will be used during saved search saving + isESQLToDataViewTransitionModalVisible?: boolean; + resetDefaultProfileState: { columns: boolean; rowHeight: boolean }; } export interface InternalStateTransitions { @@ -48,6 +50,12 @@ export interface InternalStateTransitions { overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined ) => InternalState; resetOnSavedSearchChange: (state: InternalState) => () => InternalState; + setIsESQLToDataViewTransitionModalVisible: ( + state: InternalState + ) => (isVisible: boolean) => InternalState; + setResetDefaultProfileState: ( + state: InternalState + ) => (resetDefaultProfileState: InternalState['resetDefaultProfileState']) => InternalState; } export type DiscoverInternalStateContainer = ReduxLikeStateContainer< @@ -68,6 +76,7 @@ export function getInternalStateContainer() { expandedDoc: undefined, customFilters: [], overriddenVisContextAfterInvalidation: undefined, + resetDefaultProfileState: { columns: false, rowHeight: false }, }, { setDataView: (prevState: InternalState) => (nextDataView: DataView) => ({ @@ -80,6 +89,11 @@ export function getInternalStateContainer() { ...prevState, isDataViewLoading: loading, }), + setIsESQLToDataViewTransitionModalVisible: + (prevState: InternalState) => (isVisible: boolean) => ({ + ...prevState, + isESQLToDataViewTransitionModalVisible: isVisible, + }), setSavedDataViews: (prevState: InternalState) => (nextDataViewList: DataViewListItem[]) => ({ ...prevState, savedDataViews: nextDataViewList, @@ -134,6 +148,12 @@ export function getInternalStateContainer() { overriddenVisContextAfterInvalidation: undefined, expandedDoc: undefined, }), + setResetDefaultProfileState: + (prevState: InternalState) => + (resetDefaultProfileState: InternalState['resetDefaultProfileState']) => ({ + ...prevState, + resetDefaultProfileState, + }), }, {}, { freeze: (state) => state } diff --git a/src/plugins/discover/public/application/main/state_management/discover_state.test.ts b/src/plugins/discover/public/application/main/state_management/discover_state.test.ts index b56600b196944..f928c6585e631 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_state.test.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_state.test.ts @@ -753,6 +753,28 @@ describe('Test discover state actions', () => { expect(persistedDataViewId).toBe(nextSavedSearch?.searchSource.getField('index')!.id); }); + test('transitionFromDataViewToESQL', async () => { + const savedSearchWithQuery = copySavedSearch(savedSearchMock); + const query = { query: "foo: 'bar'", language: 'kuery' }; + savedSearchWithQuery.searchSource.setField('query', query); + const { state } = await getState('/', { savedSearch: savedSearchWithQuery }); + await state.actions.transitionFromDataViewToESQL(dataViewMock); + expect(state.appState.getState().query).toStrictEqual({ + esql: 'FROM the-data-view-title | LIMIT 10', + }); + }); + + test('transitionFromESQLToDataView', async () => { + const savedSearchWithQuery = copySavedSearch(savedSearchMock); + const query = { + esql: 'FROM the-data-view-title | LIMIT 10', + }; + savedSearchWithQuery.searchSource.setField('query', query); + const { state } = await getState('/', { savedSearch: savedSearchWithQuery }); + await state.actions.transitionFromESQLToDataView('the-data-view-id'); + expect(state.appState.getState().query).toStrictEqual({ query: '', language: 'kuery' }); + }); + test('onChangeDataView', async () => { const { state, getCurrentUrl } = await getState('/', { savedSearch: savedSearchMock }); const { actions, savedSearchState, dataState } = state; diff --git a/src/plugins/discover/public/application/main/state_management/discover_state.ts b/src/plugins/discover/public/application/main/state_management/discover_state.ts index 169e596a2cf93..2a50801a88aa4 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_state.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_state.ts @@ -23,6 +23,7 @@ import { DataView, DataViewSpec, DataViewType } from '@kbn/data-views-plugin/pub import type { SavedSearch } from '@kbn/saved-search-plugin/public'; import { v4 as uuidv4 } from 'uuid'; import { merge } from 'rxjs'; +import { getInitialESQLQuery } from '@kbn/esql-utils'; import { AggregateQuery, Query, TimeRange } from '@kbn/es-query'; import { loadSavedSearch as loadSavedSearchFn } from './utils/load_saved_search'; import { restoreStateFromSavedSearch } from '../../../services/saved_searches/restore_from_saved_search'; @@ -174,6 +175,16 @@ export interface DiscoverStateContainer { * @param dataView */ onDataViewEdited: (dataView: DataView) => Promise<void>; + /** + * Triggered when transitioning from ESQL to Dataview + * Clean ups the ES|QL query and moves to the dataview mode + */ + transitionFromESQLToDataView: (dataViewId: string) => void; + /** + * Triggered when transitioning from ESQL to Dataview + * Clean ups the ES|QL query and moves to the dataview mode + */ + transitionFromDataViewToESQL: (dataView: DataView) => void; /** * Triggered when a saved search is opened in the savedObject finder * @param savedSearchId @@ -256,20 +267,21 @@ export function getDiscoverStateContainer({ globalStateContainer, }); + /** + * Internal State Container, state that's not persisted and not part of the URL + */ + const internalStateContainer = getInternalStateContainer(); + /** * App State Container, synced with the _a part URL */ const appStateContainer = getDiscoverAppStateContainer({ stateStorage, - savedSearch: savedSearchContainer.getState(), + internalStateContainer, + savedSearchContainer, services, }); - /** - * Internal State Container, state that's not persisted and not part of the URL - */ - const internalStateContainer = getInternalStateContainer(); - const pauseAutoRefreshInterval = async (dataView: DataView) => { if (dataView && (!dataView.isTimeBased() || dataView.type === DataViewType.ROLLUP)) { const state = globalStateContainer.get(); @@ -281,6 +293,7 @@ export function getDiscoverStateContainer({ } } }; + const setDataView = (dataView: DataView) => { internalStateContainer.transitions.setDataView(dataView); pauseAutoRefreshInterval(dataView); @@ -290,8 +303,8 @@ export function getDiscoverStateContainer({ const dataStateContainer = getDataStateContainer({ services, searchSessionManager, - getAppState: appStateContainer.getState, - getInternalState: internalStateContainer.getState, + appStateContainer, + internalStateContainer, getSavedSearch: savedSearchContainer.getState, setDataView, }); @@ -352,6 +365,31 @@ export function getDiscoverStateContainer({ } }; + const transitionFromESQLToDataView = (dataViewId: string) => { + appStateContainer.update({ + query: { + language: 'kuery', + query: '', + }, + columns: [], + dataSource: { + type: DataSourceType.DataView, + dataViewId, + }, + }); + }; + + const transitionFromDataViewToESQL = (dataView: DataView) => { + const queryString = getInitialESQLQuery(dataView); + appStateContainer.update({ + query: { esql: queryString }, + dataSource: { + type: DataSourceType.Esql, + }, + columns: [], + }); + }; + const onDataViewCreated = async (nextDataView: DataView) => { if (!nextDataView.isPersisted()) { internalStateContainer.transitions.appendAdHocDataViews(nextDataView); @@ -403,9 +441,8 @@ export function getDiscoverStateContainer({ }); // initialize app state container, syncing with _g and _a part of the URL - const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync( - savedSearchContainer.getState() - ); + const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync(); + // subscribing to state changes of appStateContainer, triggering data fetching const appStateUnsubscribe = appStateContainer.subscribe( buildStateSubscribe({ @@ -417,6 +454,7 @@ export function getDiscoverStateContainer({ setDataView, }) ); + // start subscribing to dataStateContainer, triggering data fetching const unsubscribeData = dataStateContainer.subscribe(); @@ -467,6 +505,7 @@ export function getDiscoverStateContainer({ await onChangeDataView(newDataView); return newDataView; }; + /** * Triggered when a user submits a query in the search bar */ @@ -492,6 +531,7 @@ export function getDiscoverStateContainer({ appState: appStateContainer, }); }; + /** * Undo all changes to the current saved search */ @@ -518,6 +558,7 @@ export function getDiscoverStateContainer({ await appStateContainer.replaceUrlState(newAppState); return nextSavedSearch; }; + const fetchData = (initial: boolean = false) => { addLog('fetchData', { initial }); if (!initial || dataStateContainer.getInitialFetchStatus() === FetchStatus.LOADING) { @@ -544,6 +585,8 @@ export function getDiscoverStateContainer({ onDataViewCreated, onDataViewEdited, onOpenSavedSearch, + transitionFromESQLToDataView, + transitionFromDataViewToESQL, onUpdateQuery, setDataView, undoSavedSearchChanges, diff --git a/src/plugins/discover/public/application/main/state_management/utils/change_data_view.test.ts b/src/plugins/discover/public/application/main/state_management/utils/change_data_view.test.ts index 4e486e588b8eb..59a5010e178f8 100644 --- a/src/plugins/discover/public/application/main/state_management/utils/change_data_view.test.ts +++ b/src/plugins/discover/public/application/main/state_management/utils/change_data_view.test.ts @@ -31,6 +31,7 @@ const setupTestParams = (dataView: DataView | undefined) => { discoverState.appState.update = jest.fn(); discoverState.internalState.transitions = { setIsDataViewLoading: jest.fn(), + setResetDefaultProfileState: jest.fn(), } as unknown as Readonly<PureTransitionsToTransitions<InternalStateTransitions>>; return { services, @@ -71,4 +72,14 @@ describe('changeDataView', () => { expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true); expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false); }); + + it('should call setResetDefaultProfileState correctly when switching data view', async () => { + const params = setupTestParams(dataViewComplexMock); + expect(params.internalState.transitions.setResetDefaultProfileState).not.toHaveBeenCalled(); + await changeDataView(dataViewComplexMock.id!, params); + expect(params.internalState.transitions.setResetDefaultProfileState).toHaveBeenCalledWith({ + columns: true, + rowHeight: true, + }); + }); }); diff --git a/src/plugins/discover/public/application/main/state_management/utils/change_data_view.ts b/src/plugins/discover/public/application/main/state_management/utils/change_data_view.ts index 65e029260130c..df48b56922ac0 100644 --- a/src/plugins/discover/public/application/main/state_management/utils/change_data_view.ts +++ b/src/plugins/discover/public/application/main/state_management/utils/change_data_view.ts @@ -35,10 +35,12 @@ export async function changeDataView( } ) { addLog('[ui] changeDataView', { id }); + const { dataViews, uiSettings } = services; const dataView = internalState.getState().dataView; const state = appState.getState(); let nextDataView: DataView | null = null; + internalState.transitions.setIsDataViewLoading(true); try { @@ -60,9 +62,12 @@ export async function changeDataView( ); appState.update(nextAppState); + if (internalState.getState().expandedDoc) { internalState.transitions.setExpandedDoc(undefined); } } + internalState.transitions.setIsDataViewLoading(false); + internalState.transitions.setResetDefaultProfileState({ columns: true, rowHeight: true }); } diff --git a/src/plugins/discover/public/application/main/state_management/utils/get_default_profile_state.test.ts b/src/plugins/discover/public/application/main/state_management/utils/get_default_profile_state.test.ts new file mode 100644 index 0000000000000..e36a753bd4c25 --- /dev/null +++ b/src/plugins/discover/public/application/main/state_management/utils/get_default_profile_state.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fieldList } from '@kbn/data-views-plugin/common'; +import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { createContextAwarenessMocks } from '../../../../context_awareness/__mocks__'; +import { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { getDefaultProfileState } from './get_default_profile_state'; + +const emptyDataView = buildDataViewMock({ + name: 'emptyDataView', + fields: fieldList(), +}); +const { profilesManagerMock } = createContextAwarenessMocks(); + +profilesManagerMock.resolveDataSourceProfile({}); + +describe('getDefaultProfileState', () => { + it('should return expected columns', () => { + let appState = getDefaultProfileState({ + profilesManager: profilesManagerMock, + resetDefaultProfileState: { + columns: true, + rowHeight: false, + }, + defaultColumns: ['messsage', 'bytes'], + dataView: dataViewWithTimefieldMock, + esqlQueryColumns: undefined, + }); + expect(appState).toEqual({ + columns: ['message', 'extension', 'bytes'], + grid: { + columns: { + extension: { + width: 200, + }, + message: { + width: 100, + }, + }, + }, + }); + appState = getDefaultProfileState({ + profilesManager: profilesManagerMock, + resetDefaultProfileState: { + columns: true, + rowHeight: false, + }, + defaultColumns: ['messsage', 'bytes'], + dataView: emptyDataView, + esqlQueryColumns: [{ id: '1', name: 'foo', meta: { type: 'string' } }], + }); + expect(appState).toEqual({ + columns: ['foo'], + grid: { + columns: { + foo: { + width: 300, + }, + }, + }, + }); + }); + + it('should return expected rowHeight', () => { + const appState = getDefaultProfileState({ + profilesManager: profilesManagerMock, + resetDefaultProfileState: { + columns: false, + rowHeight: true, + }, + defaultColumns: [], + dataView: dataViewWithTimefieldMock, + esqlQueryColumns: undefined, + }); + expect(appState).toEqual({ + rowHeight: 3, + }); + }); + + it('should return undefined', () => { + const appState = getDefaultProfileState({ + profilesManager: profilesManagerMock, + resetDefaultProfileState: { + columns: false, + rowHeight: false, + }, + defaultColumns: [], + dataView: dataViewWithTimefieldMock, + esqlQueryColumns: undefined, + }); + expect(appState).toEqual(undefined); + }); +}); diff --git a/src/plugins/discover/public/application/main/state_management/utils/get_default_profile_state.ts b/src/plugins/discover/public/application/main/state_management/utils/get_default_profile_state.ts new file mode 100644 index 0000000000000..b1bc2bc3e3f92 --- /dev/null +++ b/src/plugins/discover/public/application/main/state_management/utils/get_default_profile_state.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. + */ + +import type { DataView } from '@kbn/data-views-plugin/common'; +import type { DiscoverGridSettings } from '@kbn/saved-search-plugin/common'; +import { uniqBy } from 'lodash'; +import type { DiscoverAppState } from '../discover_app_state_container'; +import { + DefaultAppStateColumn, + getMergedAccessor, + ProfilesManager, +} from '../../../../context_awareness'; +import type { InternalState } from '../discover_internal_state_container'; +import type { DataDocumentsMsg } from '../discover_data_state_container'; + +export const getDefaultProfileState = ({ + profilesManager, + resetDefaultProfileState, + defaultColumns, + dataView, + esqlQueryColumns, +}: { + profilesManager: ProfilesManager; + resetDefaultProfileState: InternalState['resetDefaultProfileState']; + defaultColumns: string[]; + dataView: DataView; + esqlQueryColumns: DataDocumentsMsg['esqlQueryColumns']; +}) => { + const stateUpdate: DiscoverAppState = {}; + const defaultState = getDefaultState(profilesManager, dataView); + + if (resetDefaultProfileState.columns) { + const mappedDefaultColumns = defaultColumns.map((name) => ({ name })); + const isValidColumn = getIsValidColumn(dataView, esqlQueryColumns); + const validColumns = uniqBy( + defaultState.columns?.concat(mappedDefaultColumns).filter(isValidColumn), + 'name' + ); + + if (validColumns?.length) { + const columns = validColumns.reduce<DiscoverGridSettings['columns']>( + (acc, { name, width }) => (width ? { ...acc, [name]: { width } } : acc), + undefined + ); + + stateUpdate.grid = columns ? { columns } : undefined; + stateUpdate.columns = validColumns.map(({ name }) => name); + } + } + + if (resetDefaultProfileState.rowHeight && defaultState.rowHeight !== undefined) { + stateUpdate.rowHeight = defaultState.rowHeight; + } + + return Object.keys(stateUpdate).length ? stateUpdate : undefined; +}; + +const getDefaultState = (profilesManager: ProfilesManager, dataView: DataView) => { + const getDefaultAppState = getMergedAccessor( + profilesManager.getProfiles(), + 'getDefaultAppState', + () => ({}) + ); + + return getDefaultAppState({ dataView }); +}; + +const getIsValidColumn = + (dataView: DataView, esqlQueryColumns: DataDocumentsMsg['esqlQueryColumns']) => + (column: DefaultAppStateColumn) => { + const isValid = esqlQueryColumns + ? esqlQueryColumns.some((esqlColumn) => esqlColumn.name === column.name) + : dataView.fields.getByName(column.name); + + return Boolean(isValid); + }; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx index 79ca8afd005e3..986f66e9680c4 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.tsx @@ -16,21 +16,33 @@ import { useProfileAccessor } from '../../context_awareness'; /** * Customized version of the UnifiedDataTable - * @param props * @constructor */ -export const DiscoverGrid: React.FC<UnifiedDataTableProps> = (props) => { +export const DiscoverGrid: React.FC<UnifiedDataTableProps> = ({ + rowAdditionalLeadingControls: customRowAdditionalLeadingControls, + ...props +}) => { const getRowIndicatorProvider = useProfileAccessor('getRowIndicatorProvider'); const getRowIndicator = useMemo(() => { return getRowIndicatorProvider(() => undefined)({ dataView: props.dataView }); }, [getRowIndicatorProvider, props.dataView]); + const getRowAdditionalLeadingControlsAccessor = useProfileAccessor( + 'getRowAdditionalLeadingControls' + ); + const rowAdditionalLeadingControls = useMemo(() => { + return getRowAdditionalLeadingControlsAccessor(() => customRowAdditionalLeadingControls)({ + dataView: props.dataView, + }); + }, [getRowAdditionalLeadingControlsAccessor, props.dataView, customRowAdditionalLeadingControls]); + return ( <UnifiedDataTable showColumnTokens enableComparisonMode renderCustomToolbar={renderCustomToolbar} getRowIndicator={getRowIndicator} + rowAdditionalLeadingControls={rowAdditionalLeadingControls} {...props} /> ); diff --git a/src/plugins/discover/public/context_awareness/__mocks__/index.tsx b/src/plugins/discover/public/context_awareness/__mocks__/index.tsx index 9e94c4b9b4857..5d55dfa306b8e 100644 --- a/src/plugins/discover/public/context_awareness/__mocks__/index.tsx +++ b/src/plugins/discover/public/context_awareness/__mocks__/index.tsx @@ -49,6 +49,23 @@ export const createContextAwarenessMocks = ({ ...prev(), rootProfile: () => <>data-source-profile</>, })), + getDefaultAppState: jest.fn(() => () => ({ + columns: [ + { + name: 'message', + width: 100, + }, + { + name: 'extension', + width: 200, + }, + { + name: 'foo', + width: 300, + }, + ], + rowHeight: 3, + })), }, resolve: jest.fn(() => ({ isMatch: true, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx index f9cba1592dc30..747bada5b0284 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx @@ -8,6 +8,7 @@ import { EuiBadge } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils'; +import type { RowControlColumn } from '@kbn/unified-data-table'; import { isOfAggregateQueryType } from '@kbn/es-query'; import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; import { euiThemeVars } from '@kbn/ui-theme'; @@ -71,6 +72,47 @@ export const exampleDataSourceProfileProvider: DataSourceProfileProvider = { }, }; }, + getRowAdditionalLeadingControls: (prev) => (params) => { + const additionalControls = prev(params) || []; + + return [ + ...additionalControls, + ...['visBarVerticalStacked', 'heart', 'inspect'].map( + (iconType, index): RowControlColumn => ({ + id: `exampleControl_${iconType}`, + headerAriaLabel: `Example Row Control ${iconType}`, + renderControl: (Control, rowProps) => { + return ( + <Control + data-test-subj={`exampleLogsControl_${iconType}`} + label={`Example ${iconType}`} + iconType={iconType} + onClick={() => { + alert(`Example "${iconType}" control clicked. Row index: ${rowProps.rowIndex}`); + }} + /> + ); + }, + }) + ), + ]; + }, + getDefaultAppState: () => () => ({ + columns: [ + { + name: '@timestamp', + width: 212, + }, + { + name: 'log.level', + width: 150, + }, + { + name: 'message', + }, + ], + rowHeight: 5, + }), }, resolve: (params) => { let indexPattern: string | undefined; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.test.ts new file mode 100644 index 0000000000000..0bde960cf0020 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createContextAwarenessMocks } from '../__mocks__'; +import { extendProfileProvider } from './extend_profile_provider'; + +const { dataSourceProfileProviderMock } = createContextAwarenessMocks(); + +describe('extendProfileProvider', () => { + it('should merge profiles and overwrite other properties', () => { + const resolve = jest.fn(); + const getDefaultAppState = jest.fn(); + const extendedProfileProvider = extendProfileProvider(dataSourceProfileProviderMock, { + profileId: 'extended-profile', + profile: { getDefaultAppState }, + resolve, + }); + + expect(extendedProfileProvider).toEqual({ + ...dataSourceProfileProviderMock, + profileId: 'extended-profile', + profile: { + ...dataSourceProfileProviderMock.profile, + getDefaultAppState, + }, + resolve, + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.ts new file mode 100644 index 0000000000000..e0165a4a101cd --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/extend_profile_provider.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 type { BaseProfileProvider } from '../profile_service'; + +export const extendProfileProvider = <TProvider extends BaseProfileProvider<{}>>( + baseProvider: TProvider, + extension: Partial<TProvider> & Pick<TProvider, 'profileId'> +): TProvider => ({ + ...baseProvider, + ...extension, + profile: { + ...baseProvider.profile, + ...extension.profile, + }, +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/extract_index_pattern_from.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/extract_index_pattern_from.test.ts new file mode 100644 index 0000000000000..6d57c342aa41e --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/extract_index_pattern_from.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createDataViewDataSource, createEsqlDataSource } from '../../../common/data_sources'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { extractIndexPatternFrom } from './extract_index_pattern_from'; + +describe('extractIndexPatternFrom', () => { + it('should return index pattern from data view', () => { + const indexPattern = extractIndexPatternFrom({ + dataSource: createDataViewDataSource({ dataViewId: dataViewWithTimefieldMock.id! }), + dataView: dataViewWithTimefieldMock, + }); + expect(indexPattern).toBe(dataViewWithTimefieldMock.getIndexPattern()); + }); + + it('should return index pattern from ES|QL query', () => { + const indexPattern = extractIndexPatternFrom({ + dataSource: createEsqlDataSource(), + query: { esql: 'FROM index-pattern' }, + }); + expect(indexPattern).toBe('index-pattern'); + }); + + it('should return null if no data view or ES|QL query', () => { + let indexPattern = extractIndexPatternFrom({ + dataSource: createDataViewDataSource({ dataViewId: dataViewWithTimefieldMock.id! }), + }); + expect(indexPattern).toBeNull(); + indexPattern = extractIndexPatternFrom({ + dataSource: createEsqlDataSource(), + }); + expect(indexPattern).toBeNull(); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/extract_index_pattern_from.ts b/src/plugins/discover/public/context_awareness/profile_providers/extract_index_pattern_from.ts new file mode 100644 index 0000000000000..63903953f732d --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/extract_index_pattern_from.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 { isOfAggregateQueryType } from '@kbn/es-query'; +import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import { isDataViewSource, isEsqlSource } from '../../../common/data_sources'; +import type { DataSourceProfileProviderParams } from '../profiles'; + +export const extractIndexPatternFrom = ({ + dataSource, + dataView, + query, +}: Pick<DataSourceProfileProviderParams, 'dataSource' | 'dataView' | 'query'>) => { + if (isEsqlSource(dataSource) && isOfAggregateQueryType(query)) { + return getIndexPatternFromESQLQuery(query.esql); + } else if (isDataViewSource(dataSource) && dataView) { + return dataView.getIndexPattern(); + } + + return null; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/accessors/get_default_app_state.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/accessors/get_default_app_state.ts new file mode 100644 index 0000000000000..b8d24fed9ae56 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/accessors/get_default_app_state.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 and the Server Side Public License, v 1; you may not 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 { DataSourceProfileProvider } from '../../../profiles'; +import { DefaultAppStateColumn } from '../../../types'; + +export const createGetDefaultAppState = ({ + defaultColumns, +}: { + defaultColumns?: DefaultAppStateColumn[]; +}): DataSourceProfileProvider['profile']['getDefaultAppState'] => { + return (prev) => (params) => { + const appState = { ...prev(params) }; + + if (defaultColumns) { + appState.columns = []; + + if (params.dataView.isTimeBased()) { + appState.columns.push({ name: params.dataView.timeFieldName, width: 212 }); + } + + appState.columns.push(...defaultColumns); + } + + return appState; + }; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/accessors/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/accessors/index.ts index 12522bbf915c6..a1cefc04aa486 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/accessors/index.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/accessors/index.ts @@ -7,4 +7,5 @@ */ export { getRowIndicatorProvider } from './get_row_indicator_provider'; +export { createGetDefaultAppState } from './get_default_app_state'; export { getCellRenderers } from './get_cell_renderers'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/consts.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/consts.ts new file mode 100644 index 0000000000000..c7e2536751a98 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/consts.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 { DefaultAppStateColumn } from '../../types'; + +export const LOG_LEVEL_COLUMN: DefaultAppStateColumn = { name: 'log.level', width: 150 }; +export const MESSAGE_COLUMN: DefaultAppStateColumn = { name: 'message' }; +export const CLIENT_IP_COLUMN: DefaultAppStateColumn = { name: 'client.ip', width: 150 }; +export const HOST_NAME_COLUMN: DefaultAppStateColumn = { name: 'host.name', width: 250 }; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/create_profile_providers.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/create_profile_providers.ts new file mode 100644 index 0000000000000..6fbc5b60a0ea5 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/create_profile_providers.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 and the Server Side Public License, v 1; you may not 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 { ProfileProviderServices } from '../profile_provider_services'; +import { createLogsDataSourceProfileProvider } from './profile'; +import { + createApacheErrorLogsDataSourceProfileProvider, + createAwsS3accessLogsDataSourceProfileProvider, + createKubernetesContainerLogsDataSourceProfileProvider, + createNginxAccessLogsDataSourceProfileProvider, + createNginxErrorLogsDataSourceProfileProvider, + createSystemLogsDataSourceProfileProvider, + createWindowsLogsDataSourceProfileProvider, +} from './sub_profiles'; + +export const createLogsDataSourceProfileProviders = (providerServices: ProfileProviderServices) => { + const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(providerServices); + + return [ + createSystemLogsDataSourceProfileProvider(logsDataSourceProfileProvider), + createKubernetesContainerLogsDataSourceProfileProvider(logsDataSourceProfileProvider), + createWindowsLogsDataSourceProfileProvider(logsDataSourceProfileProvider), + createAwsS3accessLogsDataSourceProfileProvider(logsDataSourceProfileProvider), + createNginxErrorLogsDataSourceProfileProvider(logsDataSourceProfileProvider), + createNginxAccessLogsDataSourceProfileProvider(logsDataSourceProfileProvider), + createApacheErrorLogsDataSourceProfileProvider(logsDataSourceProfileProvider), + logsDataSourceProfileProvider, + ]; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/index.ts index f7d780da6ef02..0f1024cf2b2b8 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/index.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { createLogsDataSourceProfileProvider } from './profile'; +export { createLogsDataSourceProfileProviders } from './create_profile_providers'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.ts index 17f53f30f397b..7e1a6874466ec 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.ts @@ -6,16 +6,10 @@ * Side Public License, v 1. */ -import { isOfAggregateQueryType } from '@kbn/es-query'; -import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; -import { isDataViewSource, isEsqlSource } from '../../../../common/data_sources'; -import { - DataSourceCategory, - DataSourceProfileProvider, - DataSourceProfileProviderParams, -} from '../../profiles'; +import { DataSourceCategory, DataSourceProfileProvider } from '../../profiles'; import { ProfileProviderServices } from '../profile_provider_services'; import { getRowIndicatorProvider } from './accessors'; +import { extractIndexPatternFrom } from '../extract_index_pattern_from'; import { getCellRenderers } from './accessors'; export const createLogsDataSourceProfileProvider = ( @@ -39,17 +33,3 @@ export const createLogsDataSourceProfileProvider = ( }; }, }); - -const extractIndexPatternFrom = ({ - dataSource, - dataView, - query, -}: DataSourceProfileProviderParams) => { - if (isEsqlSource(dataSource) && isOfAggregateQueryType(query)) { - return getIndexPatternFromESQLQuery(query.esql); - } else if (isDataViewSource(dataSource) && dataView) { - return dataView.getIndexPattern(); - } - - return null; -}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/apache_error_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/apache_error_logs.test.ts new file mode 100644 index 0000000000000..adc84f24949b1 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/apache_error_logs.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 { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { createEsqlDataSource } from '../../../../../common/data_sources'; +import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createLogsDataSourceProfileProvider } from '../profile'; +import { createApacheErrorLogsDataSourceProfileProvider } from './apache_error_logs'; + +const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default }; +const { profileProviderServices } = createContextAwarenessMocks(); +const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices); +const dataSourceProfileProvider = createApacheErrorLogsDataSourceProfileProvider( + logsDataSourceProfileProvider +); + +describe('createApacheErrorLogsDataSourceProfileProvider', () => { + it('should match a valid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-apache.error-*' }, + }); + expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } }); + }); + + it('should not match an invalid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-apache.access-*' }, + }); + expect(result).toEqual({ isMatch: false }); + }); + + it('should return default app state', () => { + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ + columns: [ + { name: 'timestamp', width: 212 }, + { name: 'log.level', width: 150 }, + { name: 'client.ip', width: 150 }, + { name: 'message' }, + ], + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/apache_error_logs.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/apache_error_logs.ts new file mode 100644 index 0000000000000..8ed7f5c406395 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/apache_error_logs.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 { DataSourceProfileProvider } from '../../../profiles'; +import { extendProfileProvider } from '../../extend_profile_provider'; +import { createGetDefaultAppState } from '../accessors'; +import { CLIENT_IP_COLUMN, LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts'; +import { createResolve } from './create_resolve'; + +export const createApacheErrorLogsDataSourceProfileProvider = ( + logsDataSourceProfileProvider: DataSourceProfileProvider +): DataSourceProfileProvider => + extendProfileProvider(logsDataSourceProfileProvider, { + profileId: 'apache-error-logs-data-source', + profile: { + getDefaultAppState: createGetDefaultAppState({ + defaultColumns: [LOG_LEVEL_COLUMN, CLIENT_IP_COLUMN, MESSAGE_COLUMN], + }), + }, + resolve: createResolve('logs-apache.error'), + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/aws_s3access_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/aws_s3access_logs.test.ts new file mode 100644 index 0000000000000..9d1cd1d6c166f --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/aws_s3access_logs.test.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 { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { createEsqlDataSource } from '../../../../../common/data_sources'; +import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createLogsDataSourceProfileProvider } from '../profile'; +import { createAwsS3accessLogsDataSourceProfileProvider } from './aws_s3access_logs'; + +const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default }; +const { profileProviderServices } = createContextAwarenessMocks(); +const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices); +const dataSourceProfileProvider = createAwsS3accessLogsDataSourceProfileProvider( + logsDataSourceProfileProvider +); + +describe('createAwsS3accessLogsDataSourceProfileProvider', () => { + it('should match a valid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-aws.s3access-*' }, + }); + expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } }); + }); + + it('should not match an invalid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-aws.s3noaccess-*' }, + }); + expect(result).toEqual({ isMatch: false }); + }); + + it('should return default app state', () => { + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ + columns: [ + { name: 'timestamp', width: 212 }, + { name: 'aws.s3.bucket.name', width: 200 }, + { name: 'aws.s3.object.key', width: 200 }, + { name: 'aws.s3access.operation', width: 200 }, + { name: 'client.ip', width: 150 }, + { name: 'message' }, + ], + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/aws_s3access_logs.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/aws_s3access_logs.ts new file mode 100644 index 0000000000000..d517f670d074e --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/aws_s3access_logs.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataSourceProfileProvider } from '../../../profiles'; +import { extendProfileProvider } from '../../extend_profile_provider'; +import { createGetDefaultAppState } from '../accessors'; +import { CLIENT_IP_COLUMN, MESSAGE_COLUMN } from '../consts'; +import { createResolve } from './create_resolve'; + +export const createAwsS3accessLogsDataSourceProfileProvider = ( + logsDataSourceProfileProvider: DataSourceProfileProvider +): DataSourceProfileProvider => + extendProfileProvider(logsDataSourceProfileProvider, { + profileId: 'aws-s3access-logs-data-source', + profile: { + getDefaultAppState: createGetDefaultAppState({ + defaultColumns: [ + { name: 'aws.s3.bucket.name', width: 200 }, + { name: 'aws.s3.object.key', width: 200 }, + { name: 'aws.s3access.operation', width: 200 }, + CLIENT_IP_COLUMN, + MESSAGE_COLUMN, + ], + }), + }, + resolve: createResolve('logs-aws.s3access'), + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/create_resolve.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/create_resolve.ts new file mode 100644 index 0000000000000..98be1f003db7c --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/create_resolve.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 { createRegExpPatternFrom, testPatternAgainstAllowedList } from '@kbn/data-view-utils'; +import { DataSourceCategory, DataSourceProfileProvider } from '../../../profiles'; +import { extractIndexPatternFrom } from '../../extract_index_pattern_from'; + +export const createResolve = (baseIndexPattern: string): DataSourceProfileProvider['resolve'] => { + const testIndexPattern = testPatternAgainstAllowedList([ + createRegExpPatternFrom(baseIndexPattern), + ]); + + return (params) => { + const indexPattern = extractIndexPatternFrom(params); + + if (!indexPattern || !testIndexPattern(indexPattern)) { + return { isMatch: false }; + } + + return { + isMatch: true, + context: { category: DataSourceCategory.Logs }, + }; + }; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/index.ts new file mode 100644 index 0000000000000..3c1f5cfa114b1 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/index.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 and the Server Side Public License, v 1; you may 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 { createApacheErrorLogsDataSourceProfileProvider } from './apache_error_logs'; +export { createAwsS3accessLogsDataSourceProfileProvider } from './aws_s3access_logs'; +export { createKubernetesContainerLogsDataSourceProfileProvider } from './kubernetes_container_logs'; +export { createNginxAccessLogsDataSourceProfileProvider } from './nginx_access_logs'; +export { createNginxErrorLogsDataSourceProfileProvider } from './nginx_error_logs'; +export { createSystemLogsDataSourceProfileProvider } from './system_logs'; +export { createWindowsLogsDataSourceProfileProvider } from './windows_logs'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/kubernetes_container_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/kubernetes_container_logs.test.ts new file mode 100644 index 0000000000000..dcad9eea0f7c8 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/kubernetes_container_logs.test.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 { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { createEsqlDataSource } from '../../../../../common/data_sources'; +import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createLogsDataSourceProfileProvider } from '../profile'; +import { createKubernetesContainerLogsDataSourceProfileProvider } from './kubernetes_container_logs'; + +const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default }; +const { profileProviderServices } = createContextAwarenessMocks(); +const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices); +const dataSourceProfileProvider = createKubernetesContainerLogsDataSourceProfileProvider( + logsDataSourceProfileProvider +); + +describe('createKubernetesContainerLogsDataSourceProfileProvider', () => { + it('should match a valid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-kubernetes.container_logs-*' }, + }); + expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } }); + }); + + it('should not match an invalid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-kubernetes.access_logs-*' }, + }); + expect(result).toEqual({ isMatch: false }); + }); + + it('should return default app state', () => { + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ + columns: [ + { name: 'timestamp', width: 212 }, + { name: 'log.level', width: 150 }, + { name: 'kubernetes.pod.name', width: 200 }, + { name: 'kubernetes.namespace', width: 200 }, + { name: 'orchestrator.cluster.name', width: 200 }, + { name: 'message' }, + ], + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/kubernetes_container_logs.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/kubernetes_container_logs.ts new file mode 100644 index 0000000000000..506d2d924d105 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/kubernetes_container_logs.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataSourceProfileProvider } from '../../../profiles'; +import { extendProfileProvider } from '../../extend_profile_provider'; +import { createGetDefaultAppState } from '../accessors'; +import { LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts'; +import { createResolve } from './create_resolve'; + +export const createKubernetesContainerLogsDataSourceProfileProvider = ( + logsDataSourceProfileProvider: DataSourceProfileProvider +): DataSourceProfileProvider => + extendProfileProvider(logsDataSourceProfileProvider, { + profileId: 'kubernetes-container-logs-data-source', + profile: { + getDefaultAppState: createGetDefaultAppState({ + defaultColumns: [ + LOG_LEVEL_COLUMN, + { name: 'kubernetes.pod.name', width: 200 }, + { name: 'kubernetes.namespace', width: 200 }, + { name: 'orchestrator.cluster.name', width: 200 }, + MESSAGE_COLUMN, + ], + }), + }, + resolve: createResolve('logs-kubernetes.container_logs'), + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_access_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_access_logs.test.ts new file mode 100644 index 0000000000000..6c04777873dce --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_access_logs.test.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 { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { createEsqlDataSource } from '../../../../../common/data_sources'; +import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createLogsDataSourceProfileProvider } from '../profile'; +import { createNginxAccessLogsDataSourceProfileProvider } from './nginx_access_logs'; + +const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default }; +const { profileProviderServices } = createContextAwarenessMocks(); +const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices); +const dataSourceProfileProvider = createNginxAccessLogsDataSourceProfileProvider( + logsDataSourceProfileProvider +); + +describe('createNginxAccessLogsDataSourceProfileProvider', () => { + it('should match a valid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-nginx.access-*' }, + }); + expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } }); + }); + + it('should not match an invalid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-nginx.error-*' }, + }); + expect(result).toEqual({ isMatch: false }); + }); + + it('should return default app state', () => { + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ + columns: [ + { name: 'timestamp', width: 212 }, + { name: 'url.path', width: 150 }, + { name: 'http.response.status_code', width: 200 }, + { name: 'client.ip', width: 150 }, + { name: 'host.name', width: 250 }, + { name: 'message' }, + ], + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_access_logs.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_access_logs.ts new file mode 100644 index 0000000000000..874bf561bdb4f --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_access_logs.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataSourceProfileProvider } from '../../../profiles'; +import { extendProfileProvider } from '../../extend_profile_provider'; +import { createGetDefaultAppState } from '../accessors'; +import { CLIENT_IP_COLUMN, HOST_NAME_COLUMN, MESSAGE_COLUMN } from '../consts'; +import { createResolve } from './create_resolve'; + +export const createNginxAccessLogsDataSourceProfileProvider = ( + logsDataSourceProfileProvider: DataSourceProfileProvider +): DataSourceProfileProvider => + extendProfileProvider(logsDataSourceProfileProvider, { + profileId: 'nginx-access-logs-data-source', + profile: { + getDefaultAppState: createGetDefaultAppState({ + defaultColumns: [ + { name: 'url.path', width: 150 }, + { name: 'http.response.status_code', width: 200 }, + CLIENT_IP_COLUMN, + HOST_NAME_COLUMN, + MESSAGE_COLUMN, + ], + }), + }, + resolve: createResolve('logs-nginx.access'), + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_error_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_error_logs.test.ts new file mode 100644 index 0000000000000..3959b27eb96ed --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_error_logs.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 { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { createEsqlDataSource } from '../../../../../common/data_sources'; +import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createLogsDataSourceProfileProvider } from '../profile'; +import { createNginxErrorLogsDataSourceProfileProvider } from './nginx_error_logs'; + +const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default }; +const { profileProviderServices } = createContextAwarenessMocks(); +const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices); +const dataSourceProfileProvider = createNginxErrorLogsDataSourceProfileProvider( + logsDataSourceProfileProvider +); + +describe('createNginxErrorLogsDataSourceProfileProvider', () => { + it('should match a valid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-nginx.error-*' }, + }); + expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } }); + }); + + it('should not match an invalid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-nginx.access-*' }, + }); + expect(result).toEqual({ isMatch: false }); + }); + + it('should return default app state', () => { + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ + columns: [ + { name: 'timestamp', width: 212 }, + { name: 'log.level', width: 150 }, + { name: 'message' }, + ], + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_error_logs.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_error_logs.ts new file mode 100644 index 0000000000000..9123d00f6b948 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/nginx_error_logs.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 { DataSourceProfileProvider } from '../../../profiles'; +import { extendProfileProvider } from '../../extend_profile_provider'; +import { createGetDefaultAppState } from '../accessors'; +import { LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts'; +import { createResolve } from './create_resolve'; + +export const createNginxErrorLogsDataSourceProfileProvider = ( + logsDataSourceProfileProvider: DataSourceProfileProvider +): DataSourceProfileProvider => + extendProfileProvider(logsDataSourceProfileProvider, { + profileId: 'nginx-error-logs-data-source', + profile: { + getDefaultAppState: createGetDefaultAppState({ + defaultColumns: [LOG_LEVEL_COLUMN, MESSAGE_COLUMN], + }), + }, + resolve: createResolve('logs-nginx.error'), + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/system_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/system_logs.test.ts new file mode 100644 index 0000000000000..ee34cf12f2047 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/system_logs.test.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 { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { createEsqlDataSource } from '../../../../../common/data_sources'; +import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createLogsDataSourceProfileProvider } from '../profile'; +import { createSystemLogsDataSourceProfileProvider } from './system_logs'; + +const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default }; +const { profileProviderServices } = createContextAwarenessMocks(); +const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices); +const dataSourceProfileProvider = createSystemLogsDataSourceProfileProvider( + logsDataSourceProfileProvider +); + +describe('createSystemLogsDataSourceProfileProvider', () => { + it('should match a valid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-system.syslog-*' }, + }); + expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } }); + }); + + it('should not match an invalid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-notsystem.syslog-*' }, + }); + expect(result).toEqual({ isMatch: false }); + }); + + it('should return default app state', () => { + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ + columns: [ + { name: 'timestamp', width: 212 }, + { name: 'log.level', width: 150 }, + { name: 'process.name', width: 150 }, + { name: 'host.name', width: 250 }, + { name: 'message' }, + ], + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/system_logs.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/system_logs.ts new file mode 100644 index 0000000000000..8b7fca5fdae13 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/system_logs.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 { DataSourceProfileProvider } from '../../../profiles'; +import { extendProfileProvider } from '../../extend_profile_provider'; +import { createGetDefaultAppState } from '../accessors'; +import { HOST_NAME_COLUMN, LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts'; +import { createResolve } from './create_resolve'; + +export const createSystemLogsDataSourceProfileProvider = ( + logsDataSourceProfileProvider: DataSourceProfileProvider +): DataSourceProfileProvider => + extendProfileProvider(logsDataSourceProfileProvider, { + profileId: 'system-logs-data-source', + profile: { + getDefaultAppState: createGetDefaultAppState({ + defaultColumns: [ + LOG_LEVEL_COLUMN, + { name: 'process.name', width: 150 }, + HOST_NAME_COLUMN, + MESSAGE_COLUMN, + ], + }), + }, + resolve: createResolve('logs-system'), + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/windows_logs.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/windows_logs.test.ts new file mode 100644 index 0000000000000..b6ad5234ab9d0 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/windows_logs.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 { dataViewWithTimefieldMock } from '../../../../__mocks__/data_view_with_timefield'; +import { createEsqlDataSource } from '../../../../../common/data_sources'; +import { DataSourceCategory, RootContext, SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createLogsDataSourceProfileProvider } from '../profile'; +import { createWindowsLogsDataSourceProfileProvider } from './windows_logs'; + +const ROOT_CONTEXT: RootContext = { solutionType: SolutionType.Default }; +const { profileProviderServices } = createContextAwarenessMocks(); +const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(profileProviderServices); +const dataSourceProfileProvider = createWindowsLogsDataSourceProfileProvider( + logsDataSourceProfileProvider +); + +describe('createWindowsLogsDataSourceProfileProvider', () => { + it('should match a valid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-windows.powershell-*' }, + }); + expect(result).toEqual({ isMatch: true, context: { category: DataSourceCategory.Logs } }); + }); + + it('should not match an invalid index pattern', async () => { + const result = await dataSourceProfileProvider.resolve({ + rootContext: ROOT_CONTEXT, + dataSource: createEsqlDataSource(), + query: { esql: 'FROM logs-notwindows.powershell-*' }, + }); + expect(result).toEqual({ isMatch: false }); + }); + + it('should return default app state', () => { + const getDefaultAppState = dataSourceProfileProvider.profile.getDefaultAppState?.(() => ({})); + expect(getDefaultAppState?.({ dataView: dataViewWithTimefieldMock })).toEqual({ + columns: [ + { name: 'timestamp', width: 212 }, + { name: 'log.level', width: 150 }, + { name: 'host.name', width: 250 }, + { name: 'message' }, + ], + }); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/windows_logs.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/windows_logs.ts new file mode 100644 index 0000000000000..0d2e4c0e1652b --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/sub_profiles/windows_logs.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 { DataSourceProfileProvider } from '../../../profiles'; +import { extendProfileProvider } from '../../extend_profile_provider'; +import { createGetDefaultAppState } from '../accessors'; +import { HOST_NAME_COLUMN, LOG_LEVEL_COLUMN, MESSAGE_COLUMN } from '../consts'; +import { createResolve } from './create_resolve'; + +export const createWindowsLogsDataSourceProfileProvider = ( + logsDataSourceProfileProvider: DataSourceProfileProvider +): DataSourceProfileProvider => + extendProfileProvider(logsDataSourceProfileProvider, { + profileId: 'windows-logs-data-source', + profile: { + getDefaultAppState: createGetDefaultAppState({ + defaultColumns: [LOG_LEVEL_COLUMN, HOST_NAME_COLUMN, MESSAGE_COLUMN], + }), + }, + resolve: createResolve('logs-windows'), + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts index 1f4f2fbb7d931..22ed673cd9558 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -10,15 +10,19 @@ import { uniq } from 'lodash'; import type { DataSourceProfileService, DocumentProfileService, + RootProfileProvider, RootProfileService, } from '../profiles'; import type { BaseProfileProvider, BaseProfileService } from '../profile_service'; import { exampleDataSourceProfileProvider } from './example_data_source_profile'; import { exampleDocumentProfileProvider } from './example_document_profile'; import { exampleRootProfileProvider } from './example_root_pofile'; -import { createLogsDataSourceProfileProvider } from './logs_data_source_profile'; +import { createLogsDataSourceProfileProviders } from './logs_data_source_profile'; import { createLogDocumentProfileProvider } from './log_document_profile'; -import { createProfileProviderServices } from './profile_provider_services'; +import { + createProfileProviderServices, + ProfileProviderServices, +} from './profile_provider_services'; export const registerProfileProviders = ({ rootProfileService, @@ -32,35 +36,31 @@ export const registerProfileProviders = ({ experimentalProfileIds: string[]; }) => { const providerServices = createProfileProviderServices(); - const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(providerServices); - const logsDocumentProfileProvider = createLogDocumentProfileProvider(providerServices); - const rootProfileProviders = [exampleRootProfileProvider]; - const dataSourceProfileProviders = [ - exampleDataSourceProfileProvider, - logsDataSourceProfileProvider, - ]; - const documentProfileProviders = [exampleDocumentProfileProvider, logsDocumentProfileProvider]; + const rootProfileProviders = createRootProfileProviders(providerServices); + const dataSourceProfileProviders = createDataSourceProfileProviders(providerServices); + const documentProfileProviders = createDocumentProfileProviders(providerServices); const enabledProfileIds = uniq([ - logsDataSourceProfileProvider.profileId, - logsDocumentProfileProvider.profileId, + ...extractProfileIds(rootProfileProviders), + ...extractProfileIds(dataSourceProfileProviders), + ...extractProfileIds(documentProfileProviders), ...experimentalProfileIds, ]); registerEnabledProfileProviders({ profileService: rootProfileService, - availableProviders: rootProfileProviders, + availableProviders: [exampleRootProfileProvider, ...rootProfileProviders], enabledProfileIds, }); registerEnabledProfileProviders({ profileService: dataSourceProfileService, - availableProviders: dataSourceProfileProviders, + availableProviders: [exampleDataSourceProfileProvider, ...dataSourceProfileProviders], enabledProfileIds, }); registerEnabledProfileProviders({ profileService: documentProfileService, - availableProviders: documentProfileProviders, + availableProviders: [exampleDocumentProfileProvider, ...documentProfileProviders], enabledProfileIds, }); }; @@ -77,9 +77,23 @@ export const registerEnabledProfileProviders = < availableProviders: TProvider[]; enabledProfileIds: string[]; }) => { - for (const profile of availableProviders) { - if (enabledProfileIds.includes(profile.profileId)) { - profileService.registerProvider(profile); + for (const provider of availableProviders) { + if (enabledProfileIds.includes(provider.profileId)) { + profileService.registerProvider(provider); } } }; + +const extractProfileIds = (providers: Array<BaseProfileProvider<{}>>) => + providers.map(({ profileId }) => profileId); + +const createRootProfileProviders = (_providerServices: ProfileProviderServices) => + [] as RootProfileProvider[]; + +const createDataSourceProfileProviders = (providerServices: ProfileProviderServices) => [ + ...createLogsDataSourceProfileProviders(providerServices), +]; + +const createDocumentProfileProviders = (providerServices: ProfileProviderServices) => [ + createLogDocumentProfileProvider(providerServices), +]; diff --git a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts index f2555eae52aad..54a29704c91b9 100644 --- a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts @@ -17,7 +17,7 @@ export enum DocumentType { Default = 'default', } -export type DocumentProfile = Omit<Profile, 'getCellRenderers'>; +export type DocumentProfile = Pick<Profile, 'getDocViewer'>; export interface DocumentProfileProviderParams { rootContext: RootContext; diff --git a/src/plugins/discover/public/context_awareness/types.ts b/src/plugins/discover/public/context_awareness/types.ts index 19e51decc62f6..b6e4d4558162d 100644 --- a/src/plugins/discover/public/context_awareness/types.ts +++ b/src/plugins/discover/public/context_awareness/types.ts @@ -24,10 +24,34 @@ export interface RowIndicatorExtensionParams { dataView: DataView; } +export interface DefaultAppStateColumn { + name: string; + width?: number; +} + +export interface DefaultAppStateExtensionParams { + dataView: DataView; +} + +export interface DefaultAppStateExtension { + columns?: DefaultAppStateColumn[]; + rowHeight?: number; +} + +export interface RowControlsExtensionParams { + dataView: DataView; +} + export interface Profile { + getDefaultAppState: (params: DefaultAppStateExtensionParams) => DefaultAppStateExtension; + // Data grid getCellRenderers: () => CustomCellRenderer; - getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; getRowIndicatorProvider: ( params: RowIndicatorExtensionParams ) => UnifiedDataTableProps['getRowIndicator'] | undefined; + getRowAdditionalLeadingControls: ( + params: RowControlsExtensionParams + ) => UnifiedDataTableProps['rowAdditionalLeadingControls'] | undefined; + // Doc viewer + getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; } diff --git a/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts b/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts index 3e6e510488fbd..d53485911d12c 100644 --- a/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts +++ b/src/plugins/discover/public/customizations/customization_types/data_table_customisation.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { CustomControlColumnConfiguration } from '@kbn/unified-data-table'; +import type { UnifiedDataTableProps } from '@kbn/unified-data-table'; export interface DataTableCustomization { id: 'data_table'; logsEnabled: boolean; // TODO / NOTE: Just temporary until Discover's data type contextual awareness lands. - customControlColumnsConfiguration?: CustomControlColumnConfiguration; + rowAdditionalLeadingControls?: UnifiedDataTableProps['rowAdditionalLeadingControls']; } diff --git a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx index 8f8a94fdb7bda..77d6f44de126b 100644 --- a/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx +++ b/src/plugins/discover/public/embeddable/components/saved_search_grid.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import { AggregateQuery, Query } from '@kbn/es-query'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; -import { MAX_DOC_FIELDS_DISPLAYED, ROW_HEIGHT_OPTION, SHOW_MULTIFIELDS } from '@kbn/discover-utils'; +import { MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS } from '@kbn/discover-utils'; import { type UnifiedDataTableProps, type DataTableColumnsMeta, @@ -104,10 +104,10 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { > <DiscoverGridMemoized {...gridProps} + isPaginationEnabled={!gridProps.isPlainRecord} totalHits={props.totalHitCount} setExpandedDoc={setExpandedDoc} expandedDoc={expandedDoc} - configRowHeight={props.services.uiSettings.get(ROW_HEIGHT_OPTION)} showMultiFields={props.services.uiSettings.get(SHOW_MULTIFIELDS)} maxDocFieldsDisplayed={props.services.uiSettings.get(MAX_DOC_FIELDS_DISPLAYED)} renderDocumentView={renderDocumentView} @@ -115,7 +115,6 @@ export function DiscoverGridEmbeddable(props: DiscoverGridEmbeddableProps) { externalCustomRenderers={cellRenderers} enableComparisonMode showColumnTokens - configHeaderRowHeight={3} showFullScreenButton={false} className="unifiedDataTable" /> diff --git a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx index fe511f5887dd5..55ed59d30f1ae 100644 --- a/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx +++ b/src/plugins/discover/public/embeddable/components/search_embeddable_grid_component.tsx @@ -12,8 +12,8 @@ import { BehaviorSubject } from 'rxjs'; import type { DataView } from '@kbn/data-views-plugin/common'; import { DOC_HIDE_TIME_COLUMN_SETTING, - isLegacyTableEnabled, SEARCH_FIELDS_FROM_SOURCE, + isLegacyTableEnabled, } from '@kbn/discover-utils'; import { Filter } from '@kbn/es-query'; import { @@ -33,6 +33,7 @@ import { SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID } from '../constants'; import { isEsqlMode } from '../initialize_fetch'; import type { SearchEmbeddableApi, SearchEmbeddableStateManager } from '../types'; import { DiscoverGridEmbeddable } from './saved_search_grid'; +import { getSearchEmbeddableDefaults } from '../get_search_embeddable_defaults'; interface SavedSearchEmbeddableComponentProps { api: SearchEmbeddableApi & { fetchWarnings$: BehaviorSubject<SearchResponseIncompleteWarning[]> }; @@ -144,13 +145,15 @@ export function SearchEmbeddableGridComponent({ return getAllowedSampleSize(savedSearch.sampleSize, discoverServices.uiSettings); }, [savedSearch.sampleSize, discoverServices]); + const defaults = getSearchEmbeddableDefaults(discoverServices.uiSettings); + const sharedProps = { columns: savedSearch.columns ?? [], dataView, interceptedWarnings, onFilter: onAddFilter, rows, - rowsPerPageState: savedSearch.rowsPerPage, + rowsPerPageState: savedSearch.rowsPerPage ?? defaults.rowsPerPage, sampleSizeState: fetchedSampleSize, searchDescription: panelDescription || savedSearchDescription, sort, @@ -179,12 +182,14 @@ export function SearchEmbeddableGridComponent({ ariaLabelledBy={'documentsAriaLabel'} cellActionsTriggerId={SEARCH_EMBEDDABLE_CELL_ACTIONS_TRIGGER_ID} columnsMeta={columnsMeta} + configHeaderRowHeight={defaults.headerRowHeight} + configRowHeight={defaults.rowHeight} headerRowHeightState={savedSearch.headerRowHeight} + rowHeightState={savedSearch.rowHeight} isPlainRecord={isEsql} loadingState={Boolean(loading) ? DataLoadingState.loading : DataLoadingState.loaded} maxAllowedSampleSize={getMaxAllowedSampleSize(discoverServices.uiSettings)} query={savedSearch.searchSource.getField('query')} - rowHeightState={savedSearch.rowHeight} savedSearchId={savedSearchId} searchTitle={panelTitle || savedSearchTitle} services={discoverServices} diff --git a/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts b/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.ts new file mode 100644 index 0000000000000..e820f2ec38e21 --- /dev/null +++ b/src/plugins/discover/public/embeddable/get_search_embeddable_defaults.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 type { IUiSettingsClient } from '@kbn/core/public'; +import { ROW_HEIGHT_OPTION, SAMPLE_SIZE_SETTING } from '@kbn/discover-utils'; +import { getDefaultRowsPerPage } from '../../common/constants'; +import { DEFAULT_HEADER_ROW_HEIGHT_LINES } from './constants'; + +export interface SearchEmbeddableDefaults { + rowHeight: number | undefined; + headerRowHeight: number | undefined; + rowsPerPage: number | undefined; + sampleSize: number | undefined; +} + +export const getSearchEmbeddableDefaults = ( + uiSettings: IUiSettingsClient +): SearchEmbeddableDefaults => { + return { + rowHeight: uiSettings.get(ROW_HEIGHT_OPTION), + headerRowHeight: DEFAULT_HEADER_ROW_HEIGHT_LINES, + rowsPerPage: getDefaultRowsPerPage(uiSettings), + sampleSize: uiSettings.get(SAMPLE_SIZE_SETTING), + }; +}; diff --git a/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx b/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx index 3d909ae287177..085798658cc75 100644 --- a/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx +++ b/src/plugins/discover/public/embeddable/get_search_embeddable_factory.test.tsx @@ -123,12 +123,14 @@ describe('saved search embeddable', () => { describe('search embeddable component', () => { it('should render empty grid when empty data is returned', async () => { const { search, resolveSearch } = createSearchFnMock(0); + const initialRuntimeState = getInitialRuntimeState({ searchMock: search }); const { Component, api } = await factory.buildEmbeddable( - getInitialRuntimeState({ searchMock: search }), + initialRuntimeState, buildApiMock, uuid, mockedDashboardApi, - jest.fn().mockImplementation((newApi) => newApi) + jest.fn().mockImplementation((newApi) => newApi), + initialRuntimeState // initialRuntimeState only contains lastSavedRuntimeState ); await waitOneTick(); // wait for build to complete const discoverComponent = render(<Component />); @@ -148,15 +150,17 @@ describe('saved search embeddable', () => { it('should render field stats table in AGGREGATED_LEVEL view mode', async () => { const { search, resolveSearch } = createSearchFnMock(0); + const initialRuntimeState = getInitialRuntimeState({ + searchMock: search, + partialState: { viewMode: VIEW_MODE.AGGREGATED_LEVEL }, + }); const { Component, api } = await factory.buildEmbeddable( - getInitialRuntimeState({ - searchMock: search, - partialState: { viewMode: VIEW_MODE.AGGREGATED_LEVEL }, - }), + initialRuntimeState, buildApiMock, uuid, mockedDashboardApi, - jest.fn().mockImplementation((newApi) => newApi) + jest.fn().mockImplementation((newApi) => newApi), + initialRuntimeState // initialRuntimeState only contains lastSavedRuntimeState ); await waitOneTick(); // wait for build to complete @@ -178,15 +182,17 @@ describe('saved search embeddable', () => { describe('search embeddable api', () => { it('should not fetch data if only a new input title is set', async () => { const { search, resolveSearch } = createSearchFnMock(1); + const initialRuntimeState = getInitialRuntimeState({ + searchMock: search, + partialState: { viewMode: VIEW_MODE.AGGREGATED_LEVEL }, + }); const { api } = await factory.buildEmbeddable( - getInitialRuntimeState({ - searchMock: search, - partialState: { viewMode: VIEW_MODE.AGGREGATED_LEVEL }, - }), + initialRuntimeState, buildApiMock, uuid, mockedDashboardApi, - jest.fn().mockImplementation((newApi) => newApi) + jest.fn().mockImplementation((newApi) => newApi), + initialRuntimeState // initialRuntimeState only contains lastSavedRuntimeState ); await waitOneTick(); // wait for build to complete @@ -219,12 +225,14 @@ describe('saved search embeddable', () => { discoverServiceMock.profilesManager, 'resolveRootProfile' ); + const initialRuntimeState = getInitialRuntimeState(); await factory.buildEmbeddable( - getInitialRuntimeState(), + initialRuntimeState, buildApiMock, uuid, mockedDashboardApi, - jest.fn().mockImplementation((newApi) => newApi) + jest.fn().mockImplementation((newApi) => newApi), + initialRuntimeState // initialRuntimeState only contains lastSavedRuntimeState ); await waitOneTick(); // wait for build to complete @@ -238,12 +246,14 @@ describe('saved search embeddable', () => { discoverServiceMock.profilesManager, 'resolveDataSourceProfile' ); + const initialRuntimeState = getInitialRuntimeState(); const { api } = await factory.buildEmbeddable( - getInitialRuntimeState(), + initialRuntimeState, buildApiMock, uuid, mockedDashboardApi, - jest.fn().mockImplementation((newApi) => newApi) + jest.fn().mockImplementation((newApi) => newApi), + initialRuntimeState // initialRuntimeState only contains lastSavedRuntimeState ); await waitOneTick(); // wait for build to complete @@ -263,15 +273,17 @@ describe('saved search embeddable', () => { it('should pass cell renderers from profile', async () => { const { search, resolveSearch } = createSearchFnMock(1); + const initialRuntimeState = getInitialRuntimeState({ + searchMock: search, + partialState: { columns: ['rootProfile', 'message', 'extension'] }, + }); const { Component, api } = await factory.buildEmbeddable( - getInitialRuntimeState({ - searchMock: search, - partialState: { columns: ['rootProfile', 'message', 'extension'] }, - }), + initialRuntimeState, buildApiMock, uuid, mockedDashboardApi, - jest.fn().mockImplementation((newApi) => newApi) + jest.fn().mockImplementation((newApi) => newApi), + initialRuntimeState // initialRuntimeState only contains lastSavedRuntimeState ); await waitOneTick(); // wait for build to complete diff --git a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx index 02f64d34fac2c..cc6711e3f5015 100644 --- a/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx +++ b/src/plugins/discover/public/embeddable/initialize_search_embeddable_api.tsx @@ -12,7 +12,6 @@ import { BehaviorSubject, combineLatest, map, Observable, skip } from 'rxjs'; import { ISearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { DataView } from '@kbn/data-views-plugin/common'; -import { ROW_HEIGHT_OPTION, SAMPLE_SIZE_SETTING } from '@kbn/discover-utils'; import { DataTableRecord } from '@kbn/discover-utils/types'; import type { PublishesDataViews, @@ -24,9 +23,9 @@ import { SortOrder, VIEW_MODE } from '@kbn/saved-search-plugin/public'; import { DataTableColumnsMeta } from '@kbn/unified-data-table'; import { AggregateQuery, Filter, Query } from '@kbn/es-query'; -import { getDefaultRowsPerPage } from '../../common/constants'; import { DiscoverServices } from '../build_services'; -import { DEFAULT_HEADER_ROW_HEIGHT_LINES, EDITABLE_SAVED_SEARCH_KEYS } from './constants'; +import { EDITABLE_SAVED_SEARCH_KEYS } from './constants'; +import { getSearchEmbeddableDefaults } from './get_search_embeddable_defaults'; import { PublishesSavedSearch, SearchEmbeddableRuntimeState, @@ -85,14 +84,16 @@ export const initializeSearchEmbeddableApi = async ( const searchSource$ = new BehaviorSubject<ISearchSource>(searchSource); const dataViews = new BehaviorSubject<DataView[] | undefined>(dataView ? [dataView] : undefined); + const defaults = getSearchEmbeddableDefaults(discoverServices.uiSettings); + /** This is the state that can be initialized from the saved initial state */ const columns$ = new BehaviorSubject<string[] | undefined>(initialState.columns); const grid$ = new BehaviorSubject<DiscoverGridSettings | undefined>(initialState.grid); + const headerRowHeight$ = new BehaviorSubject<number | undefined>(initialState.headerRowHeight); const rowHeight$ = new BehaviorSubject<number | undefined>(initialState.rowHeight); const rowsPerPage$ = new BehaviorSubject<number | undefined>(initialState.rowsPerPage); - const headerRowHeight$ = new BehaviorSubject<number | undefined>(initialState.headerRowHeight); - const sort$ = new BehaviorSubject<SortOrder[] | undefined>(initialState.sort); const sampleSize$ = new BehaviorSubject<number | undefined>(initialState.sampleSize); + const sort$ = new BehaviorSubject<SortOrder[] | undefined>(initialState.sort); const savedSearchViewMode$ = new BehaviorSubject<VIEW_MODE | undefined>(initialState.viewMode); /** @@ -112,10 +113,6 @@ export const initializeSearchEmbeddableApi = async ( const columnsMeta$ = new BehaviorSubject<DataTableColumnsMeta | undefined>(undefined); const totalHitCount$ = new BehaviorSubject<number | undefined>(undefined); - const defaultRowHeight = discoverServices.uiSettings.get(ROW_HEIGHT_OPTION); - const defaultRowsPerPage = getDefaultRowsPerPage(discoverServices.uiSettings); - const defaultSampleSize = discoverServices.uiSettings.get(SAMPLE_SIZE_SETTING); - /** * The state manager is used to modify the state of the saved search - this should never be * treated as the source of truth @@ -175,22 +172,22 @@ export const initializeSearchEmbeddableApi = async ( sampleSize: [ sampleSize$, (value) => sampleSize$.next(value), - (a, b) => (a ?? defaultSampleSize) === (b ?? defaultSampleSize), + (a, b) => (a ?? defaults.sampleSize) === (b ?? defaults.sampleSize), ], rowsPerPage: [ rowsPerPage$, (value) => rowsPerPage$.next(value), - (a, b) => (a ?? defaultRowsPerPage) === (b ?? defaultRowsPerPage), + (a, b) => (a ?? defaults.rowsPerPage) === (b ?? defaults.rowsPerPage), ], rowHeight: [ rowHeight$, (value) => rowHeight$.next(value), - (a, b) => (a ?? defaultRowHeight) === (b ?? defaultRowHeight), + (a, b) => (a ?? defaults.rowHeight) === (b ?? defaults.rowHeight), ], headerRowHeight: [ headerRowHeight$, (value) => headerRowHeight$.next(value), - (a, b) => (a ?? DEFAULT_HEADER_ROW_HEIGHT_LINES) === (b ?? DEFAULT_HEADER_ROW_HEIGHT_LINES), + (a, b) => (a ?? defaults.headerRowHeight) === (b ?? defaults.headerRowHeight), ], /** The following can't currently be changed from the dashboard */ diff --git a/src/plugins/discover/public/global_search/search_provider.ts b/src/plugins/discover/public/global_search/search_provider.ts index d4a21e9603a3c..e9b6bb6f048a5 100644 --- a/src/plugins/discover/public/global_search/search_provider.ts +++ b/src/plugins/discover/public/global_search/search_provider.ts @@ -50,7 +50,7 @@ export const getESQLSearchProvider: ( const params = { query: { - esql: getInitialESQLQuery(defaultDataView?.getIndexPattern()), + esql: getInitialESQLQuery(defaultDataView), }, dataViewSpec: defaultDataView?.toSpec(), }; diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index fcce348a319d1..d81bacb958d38 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -278,7 +278,7 @@ export class DiscoverPlugin plugins.share?.url.locators.create( new DiscoverESQLLocatorDefinition({ discoverAppLocator: this.locator, - getIndices: plugins.dataViews.getIndices, + dataViews: plugins.dataViews, }) ); } diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx index 5c753777eae9b..b92556bfc6b55 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx @@ -94,7 +94,8 @@ describe('react embeddable renderer', () => { expect.any(Function), expect.any(String), expect.any(Object), - expect.any(Function) + expect.any(Function), + { bork: 'blorp?' } ); }); }); @@ -120,7 +121,8 @@ describe('react embeddable renderer', () => { expect.any(Function), '12345', expect.any(Object), - expect.any(Function) + expect.any(Function), + { bork: 'blorp?' } ); }); }); @@ -142,7 +144,8 @@ describe('react embeddable renderer', () => { expect.any(Function), expect.any(String), parentApi, - expect.any(Function) + expect.any(Function), + { bork: 'blorp?' } ); }); }); diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx index 953b57e4b207c..f538c7b1164b1 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx @@ -7,9 +7,11 @@ */ import { + apiHasRuntimeChildState, apiIsPresentationContainer, HasSerializedChildState, HasSnapshottableState, + initializeUnsavedChanges, SerializedPanelState, } from '@kbn/presentation-containers'; import { PresentationPanel, PresentationPanelProps } from '@kbn/presentation-panel-plugin/public'; @@ -23,7 +25,6 @@ import React, { useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import { BehaviorSubject, combineLatest, debounceTime, skip, Subscription, switchMap } from 'rxjs'; import { v4 as generateId } from 'uuid'; import { getReactEmbeddableFactory } from './react_embeddable_registry'; -import { initializeReactEmbeddableState } from './react_embeddable_state'; import { BuildReactEmbeddableApiRegistration, DefaultEmbeddableApi, @@ -115,11 +116,18 @@ export const ReactEmbeddableRenderer = < }; const buildEmbeddable = async () => { - const { initialState, startStateDiffing } = await initializeReactEmbeddableState< - SerializedState, - RuntimeState, - Api - >(uuid, factory, parentApi); + const serializedState = parentApi.getSerializedStateForChild(uuid); + const lastSavedRuntimeState = serializedState + ? await factory.deserializeState(serializedState) + : ({} as RuntimeState); + + // If the parent provides runtime state for the child (usually as a state backup or cache), + // we merge it with the last saved runtime state. + const partialRuntimeState = apiHasRuntimeChildState<RuntimeState>(parentApi) + ? parentApi.getRuntimeStateForChild(uuid) ?? ({} as Partial<RuntimeState>) + : ({} as Partial<RuntimeState>); + + const initialRuntimeState = { ...lastSavedRuntimeState, ...partialRuntimeState }; const buildApi = ( apiRegistration: BuildReactEmbeddableApiRegistration< @@ -152,32 +160,34 @@ export const ReactEmbeddableRenderer = < : Promise.resolve(apiRegistration.serializeState()); }) ) - .subscribe((serializedState) => { - onAnyStateChange(serializedState); + .subscribe((nextSerializedState) => { + onAnyStateChange(nextSerializedState); }) ); } - const { unsavedChanges, resetUnsavedChanges, cleanup, snapshotRuntimeState } = - startStateDiffing(comparators); + const unsavedChanges = initializeUnsavedChanges<RuntimeState>( + lastSavedRuntimeState, + parentApi, + comparators + ); const fullApi = setApi({ ...apiRegistration, - unsavedChanges, - resetUnsavedChanges, - snapshotRuntimeState, + ...unsavedChanges.api, } as unknown as SetReactEmbeddableApiRegistration<SerializedState, RuntimeState, Api>); - cleanupFunction.current = () => cleanup(); + cleanupFunction.current = () => unsavedChanges.cleanup(); return fullApi as Api & HasSnapshottableState<RuntimeState>; }; const { api, Component } = await factory.buildEmbeddable( - initialState, + initialRuntimeState, buildApi, uuid, parentApi, - setApi + setApi, + lastSavedRuntimeState ); if (apiPublishesDataLoading(api)) { diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.test.ts b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.test.ts deleted file mode 100644 index 6f34b4f04bffd..0000000000000 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.test.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - HasRuntimeChildState, - HasSaveNotification, - HasSerializedChildState, - PresentationContainer, -} from '@kbn/presentation-containers'; -import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks'; -import { StateComparators } from '@kbn/presentation-publishing'; -import { waitFor } from '@testing-library/react'; -import { BehaviorSubject, Subject } from 'rxjs'; -import { initializeReactEmbeddableState } from './react_embeddable_state'; -import { ReactEmbeddableFactory } from './types'; - -interface SuperTestStateType { - name: string; - age: number; - tagline: string; -} - -describe('react embeddable unsaved changes', () => { - let serializedStateForChild: SuperTestStateType; - - let comparators: StateComparators<SuperTestStateType>; - let parentApi: PresentationContainer & - HasSerializedChildState<SuperTestStateType> & - Partial<HasRuntimeChildState<SuperTestStateType>> & - HasSaveNotification; - - beforeEach(() => { - serializedStateForChild = { - name: 'Sir Testsalot', - age: 42, - tagline: `Oh he's a glutton for testing!`, - }; - parentApi = { - saveNotification$: new Subject<void>(), - ...getMockPresentationContainer(), - getSerializedStateForChild: () => ({ rawState: serializedStateForChild }), - getRuntimeStateForChild: () => undefined, - }; - }); - - const initializeDefaultComparators = () => { - const latestState: SuperTestStateType = { - ...serializedStateForChild, - ...(parentApi.getRuntimeStateForChild?.('uuid') ?? {}), - }; - const nameSubject = new BehaviorSubject<string>(latestState.name); - const ageSubject = new BehaviorSubject<number>(latestState.age); - const taglineSubject = new BehaviorSubject<string>(latestState.tagline); - const defaultComparators: StateComparators<SuperTestStateType> = { - name: [nameSubject, jest.fn((nextName) => nameSubject.next(nextName))], - age: [ageSubject, jest.fn((nextAge) => ageSubject.next(nextAge))], - tagline: [taglineSubject, jest.fn((nextTagline) => taglineSubject.next(nextTagline))], - }; - return defaultComparators; - }; - - const startTrackingUnsavedChanges = async ( - customComparators?: StateComparators<SuperTestStateType> - ) => { - comparators = customComparators ?? initializeDefaultComparators(); - - const factory: ReactEmbeddableFactory<SuperTestStateType> = { - type: 'superTest', - deserializeState: jest.fn().mockImplementation((state) => state.rawState), - buildEmbeddable: async (runtimeState, buildApi) => { - const api = buildApi({ serializeState: jest.fn() }, comparators); - return { api, Component: () => null }; - }, - }; - const { startStateDiffing } = await initializeReactEmbeddableState('uuid', factory, parentApi); - return startStateDiffing(comparators); - }; - - it('should return undefined unsaved changes when parent API does not provide runtime state', async () => { - const unsavedChangesApi = await startTrackingUnsavedChanges(); - parentApi.getRuntimeStateForChild = undefined; - expect(unsavedChangesApi).toBeDefined(); - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - }); - - it('should return undefined unsaved changes when parent API does not have runtime state for this child', async () => { - const unsavedChangesApi = await startTrackingUnsavedChanges(); - // no change here becuase getRuntimeStateForChild already returns undefined - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - }); - - it('should return unsaved changes subject initialized to undefined when no unsaved changes are detected', async () => { - parentApi.getRuntimeStateForChild = () => ({ - name: 'Sir Testsalot', - age: 42, - tagline: `Oh he's a glutton for testing!`, - }); - const unsavedChangesApi = await startTrackingUnsavedChanges(); - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - }); - - it('should return unsaved changes subject initialized with diff when unsaved changes are detected', async () => { - parentApi.getRuntimeStateForChild = () => ({ - tagline: 'Testing is my speciality!', - }); - const unsavedChangesApi = await startTrackingUnsavedChanges(); - expect(unsavedChangesApi.unsavedChanges.value).toEqual({ - tagline: 'Testing is my speciality!', - }); - }); - - it('should detect unsaved changes when state changes during the lifetime of the component', async () => { - const unsavedChangesApi = await startTrackingUnsavedChanges(); - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - - comparators.tagline[1]('Testing is my speciality!'); - await waitFor(() => { - expect(unsavedChangesApi.unsavedChanges.value).toEqual({ - tagline: 'Testing is my speciality!', - }); - }); - }); - - it('current runtime state should become last saved state when parent save notification is triggered', async () => { - const unsavedChangesApi = await startTrackingUnsavedChanges(); - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - - comparators.tagline[1]('Testing is my speciality!'); - await waitFor(() => { - expect(unsavedChangesApi.unsavedChanges.value).toEqual({ - tagline: 'Testing is my speciality!', - }); - }); - - parentApi.saveNotification$.next(); - await waitFor(() => { - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - }); - }); - - it('should reset unsaved changes, calling given setters with last saved values. This should remove all unsaved state', async () => { - const unsavedChangesApi = await startTrackingUnsavedChanges(); - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - - comparators.tagline[1]('Testing is my speciality!'); - await waitFor(() => { - expect(unsavedChangesApi.unsavedChanges.value).toEqual({ - tagline: 'Testing is my speciality!', - }); - }); - - unsavedChangesApi.resetUnsavedChanges(); - expect(comparators.tagline[1]).toHaveBeenCalledWith(`Oh he's a glutton for testing!`); - await waitFor(() => { - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - }); - }); - - it('uses a custom comparator when supplied', async () => { - serializedStateForChild.age = 20; - parentApi.getRuntimeStateForChild = () => ({ - age: 50, - }); - const ageSubject = new BehaviorSubject(50); - const customComparators: StateComparators<SuperTestStateType> = { - ...initializeDefaultComparators(), - age: [ - ageSubject, - jest.fn((nextAge) => ageSubject.next(nextAge)), - (lastAge, currentAge) => lastAge?.toString().length === currentAge?.toString().length, - ], - }; - - const unsavedChangesApi = await startTrackingUnsavedChanges(customComparators); - - // here we expect there to be no unsaved changes, both unsaved state and last saved state have two digits. - expect(unsavedChangesApi.unsavedChanges.value).toBe(undefined); - - comparators.age[1](101); - - await waitFor(() => { - // here we expect there to be unsaved changes, because now the latest state has three digits. - expect(unsavedChangesApi.unsavedChanges.value).toEqual({ - age: 101, - }); - }); - }); -}); diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.ts b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.ts deleted file mode 100644 index f29d418bd3963..0000000000000 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.ts +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - apiHasRuntimeChildState, - apiHasSaveNotification, - HasSerializedChildState, -} from '@kbn/presentation-containers'; -import { - getInitialValuesFromComparators, - PublishingSubject, - runComparators, - StateComparators, -} from '@kbn/presentation-publishing'; -import { - BehaviorSubject, - combineLatest, - combineLatestWith, - debounceTime, - map, - Subscription, -} from 'rxjs'; -import { DefaultEmbeddableApi, ReactEmbeddableFactory } from './types'; - -export const initializeReactEmbeddableState = async < - SerializedState extends object = object, - RuntimeState extends object = SerializedState, - Api extends DefaultEmbeddableApi<SerializedState, RuntimeState> = DefaultEmbeddableApi< - SerializedState, - RuntimeState - > ->( - uuid: string, - factory: ReactEmbeddableFactory<SerializedState, RuntimeState, Api>, - parentApi: HasSerializedChildState<SerializedState> -) => { - const serializedState = parentApi.getSerializedStateForChild(uuid); - const lastSavedRuntimeState = serializedState - ? await factory.deserializeState(serializedState) - : ({} as RuntimeState); - - // If the parent provides runtime state for the child (usually as a state backup or cache), - // we merge it with the last saved runtime state. - const partialRuntimeState = apiHasRuntimeChildState<RuntimeState>(parentApi) - ? parentApi.getRuntimeStateForChild(uuid) ?? ({} as Partial<RuntimeState>) - : ({} as Partial<RuntimeState>); - - const initialRuntimeState = { ...lastSavedRuntimeState, ...partialRuntimeState }; - - const startStateDiffing = (comparators: StateComparators<RuntimeState>) => { - const subscription = new Subscription(); - const snapshotRuntimeState = () => { - const comparatorKeys = Object.keys(comparators) as Array<keyof RuntimeState>; - return comparatorKeys.reduce((acc, key) => { - acc[key] = comparators[key][0].value as RuntimeState[typeof key]; - return acc; - }, {} as RuntimeState); - }; - - // the last saved state subject is always initialized with the deserialized state from the parent. - const lastSavedState$ = new BehaviorSubject<RuntimeState | undefined>(lastSavedRuntimeState); - if (apiHasSaveNotification(parentApi)) { - subscription.add( - // any time the parent saves, the current state becomes the last saved state... - parentApi.saveNotification$.subscribe(() => { - lastSavedState$.next(snapshotRuntimeState()); - }) - ); - } - - const comparatorSubjects: Array<PublishingSubject<unknown>> = []; - const comparatorKeys: Array<keyof RuntimeState> = []; - for (const key of Object.keys(comparators) as Array<keyof RuntimeState>) { - const comparatorSubject = comparators[key][0]; // 0th element of tuple is the subject - comparatorSubjects.push(comparatorSubject as PublishingSubject<unknown>); - comparatorKeys.push(key); - } - - const unsavedChanges = new BehaviorSubject<Partial<RuntimeState> | undefined>( - runComparators( - comparators, - comparatorKeys, - lastSavedState$.getValue() as RuntimeState, - getInitialValuesFromComparators(comparators, comparatorKeys) - ) - ); - - subscription.add( - combineLatest(comparatorSubjects) - .pipe( - debounceTime(100), - map((latestStates) => - comparatorKeys.reduce((acc, key, index) => { - acc[key] = latestStates[index] as RuntimeState[typeof key]; - return acc; - }, {} as Partial<RuntimeState>) - ), - combineLatestWith(lastSavedState$) - ) - .subscribe(([latest, last]) => { - unsavedChanges.next(runComparators(comparators, comparatorKeys, last, latest)); - }) - ); - return { - unsavedChanges, - resetUnsavedChanges: () => { - const lastSaved = lastSavedState$.getValue(); - for (const key of comparatorKeys) { - const setter = comparators[key][1]; // setter function is the 1st element of the tuple - setter(lastSaved?.[key] as RuntimeState[typeof key]); - } - }, - snapshotRuntimeState, - cleanup: () => subscription.unsubscribe(), - }; - }; - - return { initialState: initialRuntimeState, startStateDiffing }; -}; diff --git a/src/plugins/embeddable/public/react_embeddable_system/types.ts b/src/plugins/embeddable/public/react_embeddable_system/types.ts index e9a5a697f07e5..5860737b13fc8 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/types.ts +++ b/src/plugins/embeddable/public/react_embeddable_system/types.ts @@ -31,7 +31,7 @@ export interface DefaultEmbeddableApi< > extends DefaultPresentationPanelApi, HasType, PublishesPhaseEvents, - PublishesUnsavedChanges, + Partial<PublishesUnsavedChanges>, HasSerializableState<SerializedState>, HasSnapshottableState<RuntimeState> {} @@ -106,7 +106,10 @@ export interface ReactEmbeddableFactory< * function. */ buildEmbeddable: ( - initialState: RuntimeState, + /** + * Initial runtime state. Composed from last saved state and previous sessions's unsaved changes + */ + initialRuntimeState: RuntimeState, /** * `buildApi` should be used by most embeddables that are used in dashboards, since it implements the unsaved * changes logic that the dashboard expects using the provided comparators @@ -118,6 +121,11 @@ export interface ReactEmbeddableFactory< uuid: string, parentApi: unknown | undefined, /** `setApi` should be used when the unsaved changes logic in `buildApi` is unnecessary */ - setApi: (api: SetReactEmbeddableApiRegistration<SerializedState, RuntimeState, Api>) => Api + setApi: (api: SetReactEmbeddableApiRegistration<SerializedState, RuntimeState, Api>) => Api, + /** + * Last saved runtime state. Different from initialRuntimeState in that it does not contain previous sessions's unsaved changes + * Compare with initialRuntimeState to flag unsaved changes on load + */ + lastSavedRuntimeState: RuntimeState ) => Promise<{ Component: React.FC<{}>; api: Api }>; } diff --git a/src/plugins/esql/README.md b/src/plugins/esql/README.md index 05a7406e06a3b..0eb86e9ed29dc 100644 --- a/src/plugins/esql/README.md +++ b/src/plugins/esql/README.md @@ -4,7 +4,6 @@ The editor accepts the following properties: - query: This is the **AggregateQuery** query. i.e. (`{esql: from index1 | limit 10}`) - onTextLangQueryChange: callback that is called every time the query is updated -- expandCodeEditor: flag that opens the editor on the expanded mode - errors: array of `Error`. - warning: A string for visualizing warnings - onTextLangQuerySubmit: callback that is called when the user submits the query @@ -17,8 +16,6 @@ import { TextBasedLangEditor } from '@kbn/esql/public'; <TextBasedLangEditor query={query} onTextLangQueryChange={onTextLangQueryChange} - expandCodeEditor={(status: boolean) => setCodeEditorIsExpanded(status)} - isCodeEditorExpanded={codeEditorIsExpandedFlag} errors={props.textBasedLanguageModeErrors} isDisabled={false} onTextLangQuerySubmit={onTextLangQuerySubmit} diff --git a/src/plugins/esql/public/create_editor.tsx b/src/plugins/esql/public/create_editor.tsx index 534d86420b74d..1943601b5fb7c 100644 --- a/src/plugins/esql/public/create_editor.tsx +++ b/src/plugins/esql/public/create_editor.tsx @@ -30,7 +30,7 @@ export const TextBasedLangEditor = (props: TextBasedLanguagesEditorProps) => { ...deps, }} > - <TextBasedLanguagesEditor {...props} isDarkMode={deps.darkMode} /> + <TextBasedLanguagesEditor {...props} /> </KibanaContextProvider> ); }; diff --git a/src/plugins/esql/public/kibana_services.ts b/src/plugins/esql/public/kibana_services.ts index ab98f07a0fdef..57b314dae2899 100644 --- a/src/plugins/esql/public/kibana_services.ts +++ b/src/plugins/esql/public/kibana_services.ts @@ -17,7 +17,6 @@ export let core: CoreStart; interface ServiceDeps { core: CoreStart; - darkMode: boolean; dataViews: DataViewsPublicPluginStart; expressions: ExpressionsStart; indexManagementApiService?: IndexManagementPluginSetup['apiService']; @@ -45,14 +44,11 @@ export const setKibanaServices = ( fieldsMetadata?: FieldsMetadataPublicStart ) => { core = kibanaCore; - core.theme.theme$.subscribe(({ darkMode }) => { - servicesReady$.next({ - core, - darkMode, - dataViews, - expressions, - indexManagementApiService: indexManagement?.apiService, - fieldsMetadata, - }); + servicesReady$.next({ + core, + dataViews, + expressions, + indexManagementApiService: indexManagement?.apiService, + fieldsMetadata, }); }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/common/saved_objects.test.ts b/src/plugins/kibana_usage_collection/server/collectors/common/saved_objects.test.ts deleted file mode 100644 index 173f332c9f391..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/common/saved_objects.test.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 moment from 'moment'; -import type { SavedObjectsFindResult } from '@kbn/core/server'; -import { - type UsageCountersSavedObjectAttributes, - USAGE_COUNTERS_SAVED_OBJECT_TYPE, -} from '@kbn/usage-collection-plugin/server'; - -import { isSavedObjectOlderThan } from './saved_objects'; - -export const createMockSavedObjectDoc = ( - updatedAt: moment.Moment, - id: string, - namespace?: string -) => - ({ - id, - type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, - ...(namespace && { namespaces: [namespace] }), - attributes: { - count: 3, - counterName: 'testName', - counterType: 'count', - domainId: 'testDomain', - source: 'server', - }, - references: [], - updated_at: updatedAt.format(), - version: 'WzI5LDFd', - score: 0, - } as SavedObjectsFindResult<UsageCountersSavedObjectAttributes>); - -describe('isSavedObjectOlderThan', () => { - it(`returns true if doc is older than x days`, () => { - const numberOfDays = 1; - const startDate = moment().format(); - const doc = createMockSavedObjectDoc(moment().subtract(2, 'days'), 'some-id'); - const result = isSavedObjectOlderThan({ - numberOfDays, - startDate, - doc, - }); - expect(result).toBe(true); - }); - - it(`returns false if doc is exactly x days old`, () => { - const numberOfDays = 1; - const startDate = moment().format(); - const doc = createMockSavedObjectDoc(moment().subtract(1, 'days'), 'some-id'); - const result = isSavedObjectOlderThan({ - numberOfDays, - startDate, - doc, - }); - expect(result).toBe(false); - }); - - it(`returns false if doc is younger than x days`, () => { - const numberOfDays = 2; - const startDate = moment().format(); - const doc = createMockSavedObjectDoc(moment().subtract(1, 'days'), 'some-id'); - const result = isSavedObjectOlderThan({ - numberOfDays, - startDate, - doc, - }); - expect(result).toBe(false); - }); -}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/common/saved_objects.ts b/src/plugins/kibana_usage_collection/server/collectors/common/saved_objects.ts deleted file mode 100644 index 7b42b528b266b..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/common/saved_objects.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import moment from 'moment'; -import { SavedObject } from '@kbn/core-saved-objects-api-server'; - -export function isSavedObjectOlderThan({ - numberOfDays, - startDate, - doc, -}: { - numberOfDays: number; - startDate: moment.Moment | string | number; - doc: Pick<SavedObject, 'updated_at'>; -}): boolean { - const { updated_at: updatedAt } = doc; - const today = moment(startDate).startOf('day'); - const updateDay = moment(updatedAt).startOf('day'); - - const diffInDays = today.diff(updateDay, 'days'); - if (diffInDays > numberOfDays) { - return true; - } - - return false; -} diff --git a/src/plugins/kibana_usage_collection/server/collectors/index.ts b/src/plugins/kibana_usage_collection/server/collectors/index.ts index 6de234b5de434..3408e1b484e46 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/index.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/index.ts @@ -20,8 +20,5 @@ export { registerCoreUsageCollector } from './core'; export { registerLocalizationUsageCollector } from './localization'; export { registerConfigUsageCollector } from './config_usage'; export { registerUiCountersUsageCollector } from './ui_counters'; -export { - registerUsageCountersRollups, - registerUsageCountersUsageCollector, -} from './usage_counters'; +export { registerUsageCountersUsageCollector } from './usage_counters'; export { registerEventLoopDelaysCollector } from './event_loop_delays'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/index.ts b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/index.ts index 1873fae42e54a..65d1c8e0ae6e8 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/index.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/index.ts @@ -7,4 +7,3 @@ */ export { registerUsageCountersUsageCollector } from './register_usage_counters_collector'; -export { registerUsageCountersRollups } from './rollups'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/integration_tests/rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/integration_tests/rollups.test.ts deleted file mode 100644 index bde078e234fb3..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/integration_tests/rollups.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import moment from 'moment'; - -/** - * Mocking methods that are used to retrieve current time. This allows: - * 1) introducing OLD counters that can be rolled up - * 2) Removing flakiness for tests that are executed on a 2 day span (close to midnight) - * getCurrentTime => used by `SOR.incrementCounter` to determine 'updated_at' - * isSavedObjectOlderThan => used by `rollUsageCountersIndices` to determine if a counter is beyond the retention period - */ -jest.mock('@kbn/core-saved-objects-api-server-internal/src/lib/apis/utils', () => ({ - ...jest.requireActual('@kbn/core-saved-objects-api-server-internal/src/lib/apis/utils'), - getCurrentTime: jest.fn(), -})); - -jest.mock('../../common/saved_objects', () => ({ - ...jest.requireActual('../../common/saved_objects'), - isSavedObjectOlderThan: jest.fn(), -})); - -import { getCurrentTime } from '@kbn/core-saved-objects-api-server-internal/src/lib/apis/utils'; -import type { Logger, ISavedObjectsRepository, SavedObject } from '@kbn/core/server'; -import { - type TestElasticsearchUtils, - type TestKibanaUtils, - createTestServers, - createRootWithCorePlugins, -} from '@kbn/core-test-helpers-kbn-server'; - -import { - serializeCounterKey, - type UsageCountersSavedObjectAttributes, - USAGE_COUNTERS_SAVED_OBJECT_TYPE, -} from '@kbn/usage-collection-plugin/server'; -import { rollUsageCountersIndices } from '../rollups/rollups'; -import { USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS } from '../rollups/constants'; -import { isSavedObjectOlderThan } from '../../common/saved_objects'; - -const getCurrentTimeMock = getCurrentTime as jest.MockedFunction<typeof getCurrentTime>; -const isSavedObjectOlderThanMock = isSavedObjectOlderThan as jest.MockedFunction< - typeof isSavedObjectOlderThan ->; - -const NOW = '2024-06-30T10:00:00.000Z'; -const OLD = moment(NOW).subtract(USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS + 1, 'days'); -const RECENT = moment(NOW).subtract(USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS - 1, 'days'); -const OLD_YMD = OLD.format('YYYYMMDD'); -const RECENT_YMD = RECENT.format('YYYYMMDD'); -const OLD_ISO = OLD.toISOString(); -const RECENT_ISO = RECENT.toISOString(); - -const ALL_COUNTERS = [ - `domain1:a:count:server:${OLD_YMD}:default`, - `domain1:a:count:server:${RECENT_YMD}:default`, - `domain1:b:count:server:${OLD_YMD}:one`, - `domain1:b:count:server:${OLD_YMD}:two`, - `domain1:b:count:server:${RECENT_YMD}:one`, - `domain1:b:count:server:${RECENT_YMD}:two`, - `domain2:a:count:server:${OLD_YMD}:default`, - `domain2:a:count:server:${RECENT_YMD}:default`, - `domain2:c:count:server:${RECENT_YMD}:default`, -]; - -const RECENT_COUNTERS = ALL_COUNTERS.filter((key) => key.includes(RECENT_YMD)); - -describe('usage-counters', () => { - let esServer: TestElasticsearchUtils; - let root: TestKibanaUtils['root']; - let internalRepository: ISavedObjectsRepository; - let logger: Logger; - - beforeAll(async () => { - const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), - }); - - esServer = await startES(); - root = createRootWithCorePlugins(); - - await root.preboot(); - await root.setup(); - const start = await root.start(); - - logger = root.logger.get('test daily rollups'); - internalRepository = start.savedObjects.createInternalRepository([ - USAGE_COUNTERS_SAVED_OBJECT_TYPE, - ]); - - // insert a bunch of usage counters in multiple namespaces - await createTestCounters(internalRepository); - }); - - it('deletes documents older that the retention period, from all namespaces', async () => { - // check that all documents are there - const beforeRollup = await internalRepository.find<UsageCountersSavedObjectAttributes>({ - type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, - namespaces: ['*'], - }); - expect( - beforeRollup.saved_objects - .map(({ attributes, updated_at: updatedAt, namespaces }) => - serializeCounterKey({ ...attributes, date: updatedAt, namespace: namespaces?.[0] }) - ) - .sort() - ).toEqual(ALL_COUNTERS); - - // run the rollup logic - isSavedObjectOlderThanMock.mockImplementation(({ doc }) => doc.updated_at === OLD_ISO); - await rollUsageCountersIndices(logger, internalRepository); - - // check only recent counters are present - const afterRollup = await internalRepository.find<UsageCountersSavedObjectAttributes>({ - type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, - namespaces: ['*'], - }); - expect( - afterRollup.saved_objects - .map(({ attributes, updated_at: updatedAt, namespaces }) => - serializeCounterKey({ ...attributes, date: updatedAt, namespace: namespaces?.[0] }) - ) - .sort() - ).toEqual(RECENT_COUNTERS); - }); - - afterAll(async () => { - await esServer.stop(); - await root.shutdown(); - }); -}); - -async function createTestCounters(internalRepository: ISavedObjectsRepository) { - await createCounters(internalRepository, OLD_ISO, [ - // domainId, counterName, counterType, source, count, namespace? - ['domain1', 'a', 'count', 'server', 28], - ['domain1', 'b', 'count', 'server', 29, 'one'], - ['domain1', 'b', 'count', 'server', 30, 'two'], - ['domain2', 'a', 'count', 'server', 31], - ]); - - await createCounters(internalRepository, RECENT_ISO, [ - // domainId, counterName, counterType, source, count, namespace? - ['domain1', 'a', 'count', 'server', 32], - ['domain1', 'b', 'count', 'server', 33, 'one'], - ['domain1', 'b', 'count', 'server', 34, 'two'], - ['domain2', 'a', 'count', 'server', 35], - ['domain2', 'c', 'count', 'server', 36], - ]); -} - -// domainId, counterName, counterType, source, count, namespace? -type CounterAttributes = [string, string, string, 'ui' | 'server', number, string?]; - -async function createCounters( - internalRepository: ISavedObjectsRepository, - isoDate: string, - countersAttributes: CounterAttributes[] -) { - // tamper SO `updated_at` - getCurrentTimeMock.mockReturnValue(isoDate); - - await Promise.all( - countersAttributes - .map((attrs) => createCounter(isoDate, ...attrs)) - .map((counter) => incrementCounter(internalRepository, counter)) - ); -} - -function createCounter( - date: string, - domainId: string, - counterName: string, - counterType: string, - source: 'server' | 'ui', - count: number, - namespace?: string -): SavedObject<UsageCountersSavedObjectAttributes> { - const id = serializeCounterKey({ - domainId, - counterName, - counterType, - namespace, - source, - date, - }); - return { - type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, - id, - ...(namespace && { namespaces: [namespace] }), - attributes: { - domainId, - counterName, - counterType, - source, - count, - }, - references: [], - }; -} - -async function incrementCounter( - internalRepository: ISavedObjectsRepository, - counter: SavedObject<UsageCountersSavedObjectAttributes> -) { - const namespace = counter.namespaces?.[0]; - return await internalRepository.incrementCounter( - USAGE_COUNTERS_SAVED_OBJECT_TYPE, - counter.id, - [{ fieldName: 'count', incrementBy: counter.attributes.count }], - { - ...(namespace && { namespace }), - upsertAttributes: { - domainId: counter.attributes.domainId, - counterName: counter.attributes.counterName, - counterType: counter.attributes.counterType, - source: counter.attributes.source, - }, - } - ); -} diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/constants.ts b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/constants.ts deleted file mode 100644 index 1c1ca3f466df2..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/constants.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. - */ - -/** - * Roll indices every 24h - */ -export const ROLL_INDICES_INTERVAL = 24 * 60 * 60 * 1000; - -/** - * Start rolling indices after 5 minutes up - */ -export const ROLL_INDICES_START = 5 * 60 * 1000; - -/** - * Number of days to keep the Usage counters saved object documents - */ -export const USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS = 5; diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/register_rollups.ts b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/register_rollups.ts deleted file mode 100644 index 9b429ba8c944b..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/register_rollups.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 { type Observable, timer, takeUntil } from 'rxjs'; -import { Logger, ISavedObjectsRepository } from '@kbn/core/server'; -import { ROLL_INDICES_INTERVAL, ROLL_INDICES_START } from './constants'; -import { rollUsageCountersIndices } from './rollups'; - -export function registerUsageCountersRollups( - logger: Logger, - getSavedObjectsClient: () => ISavedObjectsRepository | undefined, - pluginStop$: Observable<void> -) { - timer(ROLL_INDICES_START, ROLL_INDICES_INTERVAL) - .pipe(takeUntil(pluginStop$)) - .subscribe(() => rollUsageCountersIndices(logger, getSavedObjectsClient())); -} diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/rollups.test.ts deleted file mode 100644 index 37c68021c9c73..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/rollups.test.ts +++ /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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import moment from 'moment'; -import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; -import { USAGE_COUNTERS_SAVED_OBJECT_TYPE } from '@kbn/usage-collection-plugin/server'; -import { USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS } from './constants'; -import { createMockSavedObjectDoc } from '../../common/saved_objects.test'; -import { rollUsageCountersIndices } from './rollups'; - -describe('rollUsageCountersIndices', () => { - let logger: ReturnType<typeof loggingSystemMock.createLogger>; - let savedObjectClient: ReturnType<typeof savedObjectsRepositoryMock.create>; - - beforeEach(() => { - logger = loggingSystemMock.createLogger(); - savedObjectClient = savedObjectsRepositoryMock.create(); - }); - - it('returns undefined if no savedObjectsClient initialised yet', async () => { - await expect(rollUsageCountersIndices(logger, undefined)).resolves.toBe(undefined); - expect(logger.warn).toHaveBeenCalledTimes(0); - }); - - it('does not delete any documents on empty saved objects', async () => { - savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => { - switch (type) { - case USAGE_COUNTERS_SAVED_OBJECT_TYPE: - return { saved_objects: [], total: 0, page, per_page: perPage }; - default: - throw new Error(`Unexpected type [${type}]`); - } - }); - await expect(rollUsageCountersIndices(logger, savedObjectClient)).resolves.toEqual([]); - expect(savedObjectClient.find).toBeCalled(); - expect(savedObjectClient.delete).not.toBeCalled(); - expect(logger.warn).toHaveBeenCalledTimes(0); - }); - - it(`deletes documents older than ${USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS} days`, async () => { - const mockSavedObjects = [ - createMockSavedObjectDoc(moment().subtract(5, 'days'), 'doc-id-1'), - createMockSavedObjectDoc(moment().subtract(9, 'days'), 'doc-id-1'), - createMockSavedObjectDoc(moment().subtract(1, 'days'), 'doc-id-2'), - createMockSavedObjectDoc(moment().subtract(6, 'days'), 'doc-id-3', 'secondary'), - ]; - - savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => { - switch (type) { - case USAGE_COUNTERS_SAVED_OBJECT_TYPE: - return { saved_objects: mockSavedObjects, total: 0, page, per_page: perPage }; - default: - throw new Error(`Unexpected type [${type}]`); - } - }); - await expect(rollUsageCountersIndices(logger, savedObjectClient)).resolves.toHaveLength(2); - expect(savedObjectClient.find).toBeCalled(); - expect(savedObjectClient.delete).toHaveBeenCalledTimes(2); - expect(savedObjectClient.delete).toHaveBeenNthCalledWith( - 1, - USAGE_COUNTERS_SAVED_OBJECT_TYPE, - 'doc-id-1' - ); - expect(savedObjectClient.delete).toHaveBeenNthCalledWith( - 2, - USAGE_COUNTERS_SAVED_OBJECT_TYPE, - 'doc-id-3', - { namespace: 'secondary' } - ); - expect(logger.warn).toHaveBeenCalledTimes(0); - }); - - it(`logs warnings on savedObject.find failure`, async () => { - savedObjectClient.find.mockImplementation(async () => { - throw new Error(`Expected error!`); - }); - await expect(rollUsageCountersIndices(logger, savedObjectClient)).resolves.toEqual(undefined); - expect(savedObjectClient.find).toBeCalled(); - expect(savedObjectClient.delete).not.toBeCalled(); - expect(logger.warn).toHaveBeenCalledTimes(2); - }); - - it(`logs warnings on savedObject.delete failure`, async () => { - const mockSavedObjects = [createMockSavedObjectDoc(moment().subtract(7, 'days'), 'doc-id-1')]; - - savedObjectClient.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => { - switch (type) { - case USAGE_COUNTERS_SAVED_OBJECT_TYPE: - return { saved_objects: mockSavedObjects, total: 0, page, per_page: perPage }; - default: - throw new Error(`Unexpected type [${type}]`); - } - }); - savedObjectClient.delete.mockImplementation(async () => { - throw new Error(`Expected error!`); - }); - await expect(rollUsageCountersIndices(logger, savedObjectClient)).resolves.toEqual(undefined); - expect(savedObjectClient.find).toBeCalled(); - expect(savedObjectClient.delete).toHaveBeenCalledTimes(1); - expect(savedObjectClient.delete).toHaveBeenNthCalledWith( - 1, - USAGE_COUNTERS_SAVED_OBJECT_TYPE, - 'doc-id-1' - ); - expect(logger.warn).toHaveBeenCalledTimes(2); - }); -}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/rollups.ts b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/rollups.ts deleted file mode 100644 index a8cdfd92372d7..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/rollups.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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import moment from 'moment'; -import type { ISavedObjectsRepository, Logger } from '@kbn/core/server'; - -import { - type UsageCountersSavedObject, - USAGE_COUNTERS_SAVED_OBJECT_TYPE, -} from '@kbn/usage-collection-plugin/server'; -import { USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS } from './constants'; -import { isSavedObjectOlderThan } from '../../common/saved_objects'; - -export async function rollUsageCountersIndices( - logger: Logger, - savedObjectsClient?: ISavedObjectsRepository -) { - if (!savedObjectsClient) { - return; - } - - const now = moment(); - - try { - const { saved_objects: rawUiCounterDocs } = - await savedObjectsClient.find<UsageCountersSavedObject>({ - type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, - namespaces: ['*'], - perPage: 1000, // Process 1000 at a time as a compromise of speed and overload - }); - - const docsToDelete = rawUiCounterDocs.filter((doc) => - isSavedObjectOlderThan({ - numberOfDays: USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS, - startDate: now, - doc, - }) - ); - - return await Promise.all( - docsToDelete.map(({ id, type, namespaces }) => - namespaces?.[0] - ? savedObjectsClient.delete(type, id, { namespace: namespaces[0] }) - : savedObjectsClient.delete(type, id) - ) - ); - } catch (err) { - logger.warn(`Failed to rollup Usage Counters saved objects.`); - logger.warn(err); - } -} diff --git a/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts b/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts index 1d8dd25786083..465aa7c7a2b9d 100644 --- a/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts +++ b/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts @@ -53,6 +53,7 @@ describe('registerEbtCounters', () => { test('it reuses the usageCounter when it already exists', () => { const incrementCounterMock = jest.fn(); usageCollection.getUsageCounterByDomainId.mockReturnValue({ + domainId: 'abc123', incrementCounter: incrementCounterMock, }); registerEbtCounters(core.analytics, usageCollection); diff --git a/src/plugins/kibana_usage_collection/server/plugin.ts b/src/plugins/kibana_usage_collection/server/plugin.ts index e2b88b8881f66..dfda2258dda6a 100644 --- a/src/plugins/kibana_usage_collection/server/plugin.ts +++ b/src/plugins/kibana_usage_collection/server/plugin.ts @@ -39,7 +39,6 @@ import { registerLocalizationUsageCollector, registerUiCountersUsageCollector, registerConfigUsageCollector, - registerUsageCountersRollups, registerUsageCountersUsageCollector, registerSavedObjectsCountUsageCollector, registerEventLoopDelaysCollector, @@ -128,11 +127,6 @@ export class KibanaUsageCollectionPlugin implements Plugin { registerUiCountersUsageCollector(usageCollection, this.logger); - registerUsageCountersRollups( - this.logger.get('usage-counters-rollup'), - getSavedObjectsClient, - pluginStop$ - ); registerUsageCountersUsageCollector(usageCollection, this.logger); registerOpsStatsCollector(usageCollection, metric$); diff --git a/src/plugins/kibana_usage_collection/tsconfig.json b/src/plugins/kibana_usage_collection/tsconfig.json index 84e9a38f3e970..2fb915d541052 100644 --- a/src/plugins/kibana_usage_collection/tsconfig.json +++ b/src/plugins/kibana_usage_collection/tsconfig.json @@ -18,7 +18,6 @@ "@kbn/core-test-helpers-kbn-server", "@kbn/core-usage-data-server", "@kbn/core-saved-objects-api-server", - "@kbn/core-saved-objects-api-server-internal", ], "exclude": [ "target/**/*", diff --git a/src/plugins/links/public/actions/compatibility_check.ts b/src/plugins/links/public/actions/compatibility_check.ts new file mode 100644 index 0000000000000..986c91368abfa --- /dev/null +++ b/src/plugins/links/public/actions/compatibility_check.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. + */ + +import { apiIsPresentationContainer, PresentationContainer } from '@kbn/presentation-containers'; +import { EmbeddableApiContext } from '@kbn/presentation-publishing'; + +export const compatibilityCheck = ( + api: EmbeddableApiContext['embeddable'] +): api is PresentationContainer => { + return apiIsPresentationContainer(api); +}; diff --git a/src/plugins/links/public/actions/create_links_panel_action.ts b/src/plugins/links/public/actions/create_links_panel_action.ts index eb8f2c11d8682..02d157055f758 100644 --- a/src/plugins/links/public/actions/create_links_panel_action.ts +++ b/src/plugins/links/public/actions/create_links_panel_action.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { apiIsPresentationContainer } from '@kbn/presentation-containers'; import { EmbeddableApiContext } from '@kbn/presentation-publishing'; import { ADD_PANEL_TRIGGER, IncompatibleActionError } from '@kbn/ui-actions-plugin/public'; import { COMMON_EMBEDDABLE_GROUPING } from '@kbn/embeddable-plugin/public'; @@ -21,12 +20,12 @@ export const registerCreateLinksPanelAction = () => { getIconType: () => APP_ICON, order: 10, isCompatible: async ({ embeddable }) => { - return apiIsPresentationContainer(embeddable); + const { compatibilityCheck } = await import('./compatibility_check'); + return compatibilityCheck(embeddable); }, execute: async ({ embeddable }) => { - if (!apiIsPresentationContainer(embeddable)) { - throw new IncompatibleActionError(); - } + const { compatibilityCheck } = await import('./compatibility_check'); + if (!compatibilityCheck(embeddable)) throw new IncompatibleActionError(); const { openEditorFlyout } = await import('../editor/open_editor_flyout'); const runtimeState = await openEditorFlyout({ parentDashboard: embeddable, diff --git a/src/plugins/links/public/editor/open_editor_flyout.tsx b/src/plugins/links/public/editor/open_editor_flyout.tsx index cb3ce3a5d7591..08434f6631ae5 100644 --- a/src/plugins/links/public/editor/open_editor_flyout.tsx +++ b/src/plugins/links/public/editor/open_editor_flyout.tsx @@ -62,7 +62,7 @@ export async function openEditorFlyout({ ? parentDashboard.savedObjectId.value : undefined; - return new Promise((resolve, reject) => { + return new Promise<LinksRuntimeState | undefined>((resolve) => { const flyoutId = `linksEditorFlyout-${uuidv4()}`; const closeEditorFlyout = (editorFlyout: OverlayRef) => { @@ -116,7 +116,7 @@ export async function openEditorFlyout({ }; const onCancel = () => { - reject(); + resolve(undefined); closeEditorFlyout(editorFlyout); }; diff --git a/src/plugins/links/public/editor/open_link_editor_flyout.tsx b/src/plugins/links/public/editor/open_link_editor_flyout.tsx index e6c931be5aee0..eac3135d8f089 100644 --- a/src/plugins/links/public/editor/open_link_editor_flyout.tsx +++ b/src/plugins/links/public/editor/open_link_editor_flyout.tsx @@ -49,14 +49,14 @@ export async function openLinkEditorFlyout({ }); }; - return new Promise<UnorderedLink | undefined>((resolve, reject) => { + return new Promise<UnorderedLink | undefined>((resolve) => { const onSave = async (newLink: UnorderedLink) => { resolve(newLink); await unmountFlyout(); }; const onCancel = async () => { - reject(); + resolve(undefined); await unmountFlyout(); }; @@ -71,8 +71,5 @@ export async function openLinkEditorFlyout({ </KibanaRenderContextProvider>, ref.current ); - }).catch(() => { - // on reject (i.e. on cancel), just return the original list of links - return undefined; }); } diff --git a/src/plugins/links/public/embeddable/links_embeddable.test.tsx b/src/plugins/links/public/embeddable/links_embeddable.test.tsx index 39de26a6a77c9..e6dbe32e86730 100644 --- a/src/plugins/links/public/embeddable/links_embeddable.test.tsx +++ b/src/plugins/links/public/embeddable/links_embeddable.test.tsx @@ -153,6 +153,7 @@ describe('getLinksEmbeddableFactory', () => { savedObjectId: '123', title: 'my links', description: 'just a few links', + hidePanelTitles: false, } as LinksSerializedState; const expectedRuntimeState = { @@ -163,6 +164,7 @@ describe('getLinksEmbeddableFactory', () => { description: 'just a few links', title: 'my links', savedObjectId: '123', + hidePanelTitles: false, }; let parent: LinksParentApi; @@ -210,7 +212,7 @@ describe('getLinksEmbeddableFactory', () => { savedObjectId: '123', title: 'my links', description: 'just a few links', - hidePanelTitles: undefined, + hidePanelTitles: false, }, references: [], }); @@ -238,7 +240,7 @@ describe('getLinksEmbeddableFactory', () => { rawState: { title: 'my links', description: 'just a few links', - hidePanelTitles: undefined, + hidePanelTitles: false, attributes: { description: 'some links', title: 'links 001', @@ -261,6 +263,7 @@ describe('getLinksEmbeddableFactory', () => { }, description: 'just a few links', title: 'my links', + hidePanelTitles: true, } as LinksSerializedState; const expectedRuntimeState = { @@ -271,6 +274,7 @@ describe('getLinksEmbeddableFactory', () => { description: 'just a few links', title: 'my links', savedObjectId: undefined, + hidePanelTitles: true, }; let parent: LinksParentApi; @@ -315,7 +319,7 @@ describe('getLinksEmbeddableFactory', () => { rawState: { title: 'my links', description: 'just a few links', - hidePanelTitles: undefined, + hidePanelTitles: true, attributes: { links: getLinks(), layout: 'horizontal', @@ -356,7 +360,7 @@ describe('getLinksEmbeddableFactory', () => { savedObjectId: '333', title: 'my links', description: 'just a few links', - hidePanelTitles: undefined, + hidePanelTitles: true, }, references: [], }); diff --git a/src/plugins/links/public/embeddable/links_embeddable.tsx b/src/plugins/links/public/embeddable/links_embeddable.tsx index 23f9d82b7cb03..8fc6d464cd50f 100644 --- a/src/plugins/links/public/embeddable/links_embeddable.tsx +++ b/src/plugins/links/public/embeddable/links_embeddable.tsx @@ -71,7 +71,7 @@ export const getLinksEmbeddableFactory = () => { deserializeState: async (serializedState) => { // Clone the state to avoid an object not extensible error when injecting references const state = cloneDeep(serializedState.rawState); - const { title, description } = serializedState.rawState; + const { title, description, hidePanelTitles } = serializedState.rawState; if (linksSerializeStateIsByReference(state)) { const linksSavedObject = await linksClient.get(state.savedObjectId); @@ -80,6 +80,7 @@ export const getLinksEmbeddableFactory = () => { ...runtimeState, title, description, + hidePanelTitles, }; } @@ -93,6 +94,7 @@ export const getLinksEmbeddableFactory = () => { return { title, description, + hidePanelTitles, links: resolvedLinks, layout: attributesWithInjectedIds.layout, defaultPanelTitle: attributesWithInjectedIds.title, @@ -175,21 +177,18 @@ export const getLinksEmbeddableFactory = () => { savedObjectId$.next(undefined); }, onEdit: async () => { - try { - const { openEditorFlyout } = await import('../editor/open_editor_flyout'); - const newState = await openEditorFlyout({ - initialState: api.snapshotRuntimeState(), - parentDashboard: parentApi, - }); - if (newState) { - links$.next(newState.links); - layout$.next(newState.layout); - defaultPanelTitle.next(newState.defaultPanelTitle); - defaultPanelDescription.next(newState.defaultPanelDescription); - savedObjectId$.next(newState.savedObjectId); - } - } catch { - // do nothing, user cancelled + const { openEditorFlyout } = await import('../editor/open_editor_flyout'); + const newState = await openEditorFlyout({ + initialState: api.snapshotRuntimeState(), + parentDashboard: parentApi, + }); + + if (newState) { + links$.next(newState.links); + layout$.next(newState.layout); + defaultPanelTitle.next(newState.defaultPanelTitle); + defaultPanelDescription.next(newState.defaultPanelDescription); + savedObjectId$.next(newState.savedObjectId); } }, }, diff --git a/src/plugins/navigation/common/constants.ts b/src/plugins/navigation/common/constants.ts index f44ed035fe8f4..8b5bf7ff8901f 100644 --- a/src/plugins/navigation/common/constants.ts +++ b/src/plugins/navigation/common/constants.ts @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -export const SOLUTION_NAV_FEATURE_FLAG_NAME = 'solutionNavEnabled'; - export const DEFAULT_ROUTE_UI_SETTING_ID = 'defaultRoute'; export const DEFAULT_ROUTES = { diff --git a/src/plugins/navigation/common/index.ts b/src/plugins/navigation/common/index.ts index af16410a54fda..77288b9e4f49f 100644 --- a/src/plugins/navigation/common/index.ts +++ b/src/plugins/navigation/common/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { SOLUTION_NAV_FEATURE_FLAG_NAME } from './constants'; +export { DEFAULT_ROUTES, DEFAULT_ROUTE_UI_SETTING_ID } from './constants'; diff --git a/src/plugins/navigation/kibana.jsonc b/src/plugins/navigation/kibana.jsonc index 96980c3c983f5..92cb0d492572d 100644 --- a/src/plugins/navigation/kibana.jsonc +++ b/src/plugins/navigation/kibana.jsonc @@ -6,7 +6,7 @@ "id": "navigation", "server": true, "browser": true, - "optionalPlugins": ["cloud", "cloudExperiments", "spaces"], + "optionalPlugins": ["cloud", "spaces"], "requiredPlugins": ["unifiedSearch"], "requiredBundles": [] } diff --git a/src/plugins/navigation/public/analytics/index.ts b/src/plugins/navigation/public/analytics/index.ts new file mode 100644 index 0000000000000..2c51c4e994a95 --- /dev/null +++ b/src/plugins/navigation/public/analytics/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 { registerNavigationEventTypes } from './register_event_types'; diff --git a/src/plugins/navigation/public/analytics/register_event_types.ts b/src/plugins/navigation/public/analytics/register_event_types.ts new file mode 100644 index 0000000000000..d1571f8ff0245 --- /dev/null +++ b/src/plugins/navigation/public/analytics/register_event_types.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 type { CoreSetup, EventTypeOpts, RootSchema } from '@kbn/core/public'; +import { + FieldType as NavigationFieldType, + EventType as NavigationEventType, +} from '@kbn/shared-ux-chrome-navigation'; + +const fields: Record<NavigationFieldType, RootSchema<Record<string, unknown>>> = { + [NavigationFieldType.ID]: { + [NavigationFieldType.ID]: { + type: 'keyword', + _meta: { + description: 'The ID of navigation node.', + }, + }, + }, + [NavigationFieldType.HREF]: { + [NavigationFieldType.HREF]: { + type: 'keyword', + _meta: { + description: 'The href of the navigation node.', + optional: true, + }, + }, + }, + [NavigationFieldType.HREF_PREV]: { + [NavigationFieldType.HREF_PREV]: { + type: 'keyword', + _meta: { + description: 'The previous href before clicking on a navigation node.', + optional: true, + }, + }, + }, +}; + +const eventTypes: Array<EventTypeOpts<Record<string, unknown>>> = [ + { + eventType: NavigationEventType.CLICK_NAVLINK, + schema: { + ...fields[NavigationFieldType.ID], + ...fields[NavigationFieldType.HREF], + ...fields[NavigationFieldType.HREF_PREV], + }, + }, +]; + +export function registerNavigationEventTypes(core: CoreSetup) { + const { analytics } = core; + for (const eventType of eventTypes) { + analytics.registerEventType(eventType); + } +} diff --git a/src/plugins/navigation/public/plugin.test.ts b/src/plugins/navigation/public/plugin.test.ts index e5c3a88babaf1..1d9abb6f85dbc 100644 --- a/src/plugins/navigation/public/plugin.test.ts +++ b/src/plugins/navigation/public/plugin.test.ts @@ -10,11 +10,9 @@ import { firstValueFrom, of } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; -import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; import type { Space } from '@kbn/spaces-plugin/public'; import type { BuildFlavor } from '@kbn/config'; -import { SOLUTION_NAV_FEATURE_FLAG_NAME } from '../common'; import { NavigationPublicPlugin } from './plugin'; jest.mock('rxjs', () => { @@ -25,16 +23,11 @@ jest.mock('rxjs', () => { }; }); -const setup = ( - config: { - featureOn: boolean; - }, - { - buildFlavor = 'traditional', - }: { - buildFlavor?: BuildFlavor; - } = {} -) => { +const setup = ({ + buildFlavor = 'traditional', +}: { + buildFlavor?: BuildFlavor; +} = {}) => { const initializerContext = coreMock.createPluginInitializerContext({}, { buildFlavor }); const plugin = new NavigationPublicPlugin(initializerContext); @@ -42,14 +35,7 @@ const setup = ( const coreStart = coreMock.createStart(); const unifiedSearch = unifiedSearchPluginMock.createStartContract(); const cloud = cloudMock.createStart(); - const cloudExperiments = cloudExperimentsMock.createStartMock(); const spaces = spacesPluginMock.createStartContract(); - cloudExperiments.getVariation.mockImplementation((key) => { - if (key === SOLUTION_NAV_FEATURE_FLAG_NAME) { - return Promise.resolve(config.featureOn); - } - return Promise.resolve(false); - }); const getGlobalSetting$ = jest.fn(); const settingsGlobalClient = { @@ -64,253 +50,189 @@ const setup = ( coreStart, unifiedSearch, cloud, - cloudExperiments, spaces, - config, setChromeStyle, }; }; describe('Navigation Plugin', () => { - describe('feature flag disabled', () => { - const featureOn = false; - - it('should not add the default solutions nor set the active nav if the feature is disabled', () => { - const { plugin, coreStart, unifiedSearch } = setup({ featureOn }); - plugin.start(coreStart, { unifiedSearch }); - expect(coreStart.chrome.project.updateSolutionNavigations).not.toHaveBeenCalled(); - expect(coreStart.chrome.project.changeActiveSolutionNavigation).not.toHaveBeenCalled(); - }); + it('should change the active solution navigation', async () => { + const { plugin, coreStart, unifiedSearch, cloud, spaces } = setup(); - it('should return flag to indicate that the solution navigation is disabled', async () => { - const { plugin, coreStart, unifiedSearch } = setup({ featureOn }); - const isEnabled = await firstValueFrom( - plugin.start(coreStart, { unifiedSearch }).isSolutionNavEnabled$ - ); - expect(isEnabled).toBe(false); - }); + spaces.getActiveSpace$ = jest + .fn() + .mockReturnValue(of({ solution: 'es' } as Pick<Space, 'solution'>)); + + plugin.start(coreStart, { unifiedSearch, cloud, spaces }); + await new Promise((resolve) => setTimeout(resolve)); + + expect(coreStart.chrome.project.changeActiveSolutionNavigation).toHaveBeenCalledWith('es'); }); - describe('feature flag enabled', () => { - const featureOn = true; + describe('addSolutionNavigation()', () => { + it('should update the solution navigation definitions', async () => { + const { plugin, coreStart, unifiedSearch, cloud } = setup(); - it('should change the active solution navigation', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ - featureOn, + const { addSolutionNavigation } = plugin.start(coreStart, { + unifiedSearch, + cloud, }); + await new Promise((resolve) => setTimeout(resolve)); - spaces.getActiveSpace$ = jest - .fn() - .mockReturnValue(of({ solution: 'es' } as Pick<Space, 'solution'>)); + const definition = { + id: 'es', + title: 'Elasticsearch', + navigationTree$: of({ body: [] }), + }; + addSolutionNavigation(definition); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.project.changeActiveSolutionNavigation).toHaveBeenCalledWith('es'); + expect(coreStart.chrome.project.updateSolutionNavigations).toHaveBeenCalledWith({ + es: { + ...definition, + sideNavComponent: expect.any(Function), + }, + }); }); + }); - describe('addSolutionNavigation()', () => { - it('should update the solution navigation definitions', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup({ - featureOn, - }); - - const { addSolutionNavigation } = plugin.start(coreStart, { - unifiedSearch, - cloud, - cloudExperiments, - }); - await new Promise((resolve) => setTimeout(resolve)); - - const definition = { - id: 'es', - title: 'Elasticsearch', - navigationTree$: of({ body: [] }), - }; - addSolutionNavigation(definition); + describe('set Chrome style', () => { + it('should set the Chrome style to "classic" when spaces plugin is not available', async () => { + const { plugin, coreStart, unifiedSearch, cloud } = setup(); - await new Promise((resolve) => setTimeout(resolve)); - - expect(coreStart.chrome.project.updateSolutionNavigations).toHaveBeenCalledWith({ - es: { - ...definition, - sideNavComponent: expect.any(Function), - }, - }); - }); + plugin.start(coreStart, { unifiedSearch, cloud }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); }); - describe('set Chrome style', () => { - it('should set the Chrome style to "classic" when the feature is not enabled', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup( - { featureOn: false } // feature not enabled - ); + it('should set the Chrome style to "classic" when active space solution is "classic"', async () => { + const { plugin, coreStart, unifiedSearch, cloud, spaces } = setup(); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments }); - await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); - }); + // Spaces plugin is available but activeSpace is undefined + spaces.getActiveSpace$ = jest.fn().mockReturnValue(of(undefined)); + plugin.start(coreStart, { unifiedSearch, cloud, spaces }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); - it('should set the Chrome style to "classic" when spaces plugin is not available', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup( - { featureOn: true } // feature not enabled but no spaces plugin - ); + // Spaces plugin is available and activeSpace has solution "classic" + coreStart.chrome.setChromeStyle.mockReset(); + spaces.getActiveSpace$ = jest + .fn() + .mockReturnValue(of({ solution: 'classic' } as Pick<Space, 'solution'>)); + plugin.start(coreStart, { unifiedSearch, cloud, spaces }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); + }); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments }); - await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); - }); + it('should NOT set the Chrome style when on serverless', async () => { + const { plugin, coreStart, unifiedSearch, cloud } = setup({ buildFlavor: 'serverless' }); - it('should set the Chrome style to "classic" when active space solution is "classic"', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ - featureOn: true, - }); + plugin.start(coreStart, { unifiedSearch, cloud }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).not.toHaveBeenCalled(); + }); - // Spaces plugin is available but activeSpace is undefined - spaces.getActiveSpace$ = jest.fn().mockReturnValue(of(undefined)); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); - await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); + it('should set the Chrome style to "project" when space solution is a known solution', async () => { + const { plugin, coreStart, unifiedSearch, cloud, spaces } = setup(); - // Spaces plugin is available and activeSpace has solution "classic" - coreStart.chrome.setChromeStyle.mockReset(); + for (const solution of ['es', 'oblt', 'security']) { spaces.getActiveSpace$ = jest .fn() - .mockReturnValue(of({ solution: 'classic' } as Pick<Space, 'solution'>)); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); + .mockReturnValue(of({ solution } as Pick<Space, 'solution'>)); + plugin.start(coreStart, { unifiedSearch, cloud, spaces }); await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); - }); - - it('should NOT set the Chrome style when the feature is enabled BUT on serverless', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup( - { featureOn: true }, // feature enabled - { buildFlavor: 'serverless' } - ); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('project'); + coreStart.chrome.setChromeStyle.mockReset(); + } - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments }); - await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).not.toHaveBeenCalled(); - }); + spaces.getActiveSpace$ = jest.fn().mockReturnValue(of({ solution: 'unknown' })); + plugin.start(coreStart, { unifiedSearch, cloud, spaces }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); + }); + }); - it('should set the Chrome style to "project" when space solution is a known solution', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ - featureOn: true, - }); + describe('isSolutionNavEnabled$', () => { + // This test will need to be changed when we remove the feature flag + it('should be off by default', async () => { + const { plugin, coreStart, unifiedSearch, cloud } = setup(); - for (const solution of ['es', 'oblt', 'security']) { - spaces.getActiveSpace$ = jest - .fn() - .mockReturnValue(of({ solution } as Pick<Space, 'solution'>)); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); - await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('project'); - coreStart.chrome.setChromeStyle.mockReset(); - } - - spaces.getActiveSpace$ = jest.fn().mockReturnValue(of({ solution: 'unknown' })); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); - await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); + const { isSolutionNavEnabled$ } = plugin.start(coreStart, { + unifiedSearch, + cloud, }); + await new Promise((resolve) => setTimeout(resolve)); + + const isEnabled = await firstValueFrom(isSolutionNavEnabled$); + expect(isEnabled).toBe(false); }); - describe('isSolutionNavEnabled$', () => { - // This test will need to be changed when we remove the feature flag - it('should be off by default', async () => { - const { plugin, coreStart, unifiedSearch, cloud } = setup({ featureOn }); + it('should be off if space solution is "classic" or "undefined"', async () => { + const { plugin, coreStart, unifiedSearch, cloud, spaces } = setup(); + + { + spaces.getActiveSpace$ = jest + .fn() + .mockReturnValue(of({ solution: undefined } as Pick<Space, 'solution'>)); const { isSolutionNavEnabled$ } = plugin.start(coreStart, { unifiedSearch, cloud, + spaces, }); await new Promise((resolve) => setTimeout(resolve)); const isEnabled = await firstValueFrom(isSolutionNavEnabled$); expect(isEnabled).toBe(false); - }); - - it('should be off if feature flag if "ON" but space solution is "classic" or "undefined"', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ - featureOn, - }); - - cloudExperiments.getVariation.mockResolvedValue(true); // Feature flag ON - - { - spaces.getActiveSpace$ = jest - .fn() - .mockReturnValue(of({ solution: undefined } as Pick<Space, 'solution'>)); - - const { isSolutionNavEnabled$ } = plugin.start(coreStart, { - unifiedSearch, - cloud, - cloudExperiments, - spaces, - }); - await new Promise((resolve) => setTimeout(resolve)); - - const isEnabled = await firstValueFrom(isSolutionNavEnabled$); - expect(isEnabled).toBe(false); - } - - { - spaces.getActiveSpace$ = jest - .fn() - .mockReturnValue(of({ solution: 'classic' } as Pick<Space, 'solution'>)); - - const { isSolutionNavEnabled$ } = plugin.start(coreStart, { - unifiedSearch, - cloud, - cloudExperiments, - spaces, - }); - await new Promise((resolve) => setTimeout(resolve)); - - const isEnabled = await firstValueFrom(isSolutionNavEnabled$); - expect(isEnabled).toBe(false); - } - }); - - it('should be on if feature flag if "ON" and space solution is set', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ - featureOn, - }); - - cloudExperiments.getVariation.mockResolvedValue(true); // Feature flag ON + } + { spaces.getActiveSpace$ = jest .fn() - .mockReturnValue(of({ solution: 'es' } as Pick<Space, 'solution'>)); + .mockReturnValue(of({ solution: 'classic' } as Pick<Space, 'solution'>)); const { isSolutionNavEnabled$ } = plugin.start(coreStart, { unifiedSearch, cloud, - cloudExperiments, spaces, }); await new Promise((resolve) => setTimeout(resolve)); const isEnabled = await firstValueFrom(isSolutionNavEnabled$); - expect(isEnabled).toBe(true); + expect(isEnabled).toBe(false); + } + }); + + it('should be on if space solution is set', async () => { + const { plugin, coreStart, unifiedSearch, cloud, spaces } = setup(); + + spaces.getActiveSpace$ = jest + .fn() + .mockReturnValue(of({ solution: 'es' } as Pick<Space, 'solution'>)); + + const { isSolutionNavEnabled$ } = plugin.start(coreStart, { + unifiedSearch, + cloud, + spaces, }); + await new Promise((resolve) => setTimeout(resolve)); - it('on serverless should flag must be disabled', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup( - { featureOn }, - { buildFlavor: 'serverless' } - ); + const isEnabled = await firstValueFrom(isSolutionNavEnabled$); + expect(isEnabled).toBe(true); + }); - const { isSolutionNavEnabled$ } = plugin.start(coreStart, { - unifiedSearch, - cloud, - cloudExperiments, - }); - await new Promise((resolve) => setTimeout(resolve)); + it('on serverless flag must be disabled', async () => { + const { plugin, coreStart, unifiedSearch, cloud } = setup({ buildFlavor: 'serverless' }); - const isEnabled = await firstValueFrom(isSolutionNavEnabled$); - expect(isEnabled).toBe(false); + const { isSolutionNavEnabled$ } = plugin.start(coreStart, { + unifiedSearch, + cloud, }); + await new Promise((resolve) => setTimeout(resolve)); + + const isEnabled = await firstValueFrom(isSolutionNavEnabled$); + expect(isEnabled).toBe(false); }); }); }); diff --git a/src/plugins/navigation/public/plugin.tsx b/src/plugins/navigation/public/plugin.tsx index bef9b7c3a933c..66ef8cb808720 100644 --- a/src/plugins/navigation/public/plugin.tsx +++ b/src/plugins/navigation/public/plugin.tsx @@ -6,23 +6,13 @@ * Side Public License, v 1. */ import React from 'react'; -import { - firstValueFrom, - from, - of, - ReplaySubject, - shareReplay, - take, - combineLatest, - map, -} from 'rxjs'; +import { of, ReplaySubject, take, map, Observable } from 'rxjs'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { Space } from '@kbn/spaces-plugin/public'; import type { SolutionNavigationDefinition } from '@kbn/core-chrome-browser'; import { InternalChromeStart } from '@kbn/core-chrome-browser-internal'; import type { PanelContentProvider } from '@kbn/shared-ux-chrome-navigation'; -import { SOLUTION_NAV_FEATURE_FLAG_NAME } from '../common'; import type { NavigationPublicSetup, NavigationPublicStart, @@ -33,6 +23,7 @@ import type { import { TopNavMenuExtensionsRegistry, createTopNav } from './top_nav_menu'; import { RegisteredTopNavMenuData } from './top_nav_menu/top_nav_menu_data'; import { SideNavComponent } from './side_navigation'; +import { registerNavigationEventTypes } from './analytics'; export class NavigationPublicPlugin implements @@ -48,11 +39,13 @@ export class NavigationPublicPlugin private readonly stop$ = new ReplaySubject<void>(1); private coreStart?: CoreStart; private depsStart?: NavigationPublicStartDependencies; - private isSolutionNavExperiementEnabled$ = of(false); + private isSolutionNavEnabled = false; constructor(private initializerContext: PluginInitializerContext) {} - public setup(_core: CoreSetup): NavigationPublicSetup { + public setup(core: CoreSetup): NavigationPublicSetup { + registerNavigationEventTypes(core); + return { registerMenuItem: this.topNavMenuExtensionsRegistry.register.bind( this.topNavMenuExtensionsRegistry @@ -67,10 +60,14 @@ export class NavigationPublicPlugin this.coreStart = core; this.depsStart = depsStart; - const { unifiedSearch, cloud, cloudExperiments, spaces } = depsStart; + const { unifiedSearch, cloud, spaces } = depsStart; const extensions = this.topNavMenuExtensionsRegistry.getAll(); const chrome = core.chrome as InternalChromeStart; - const activeSpace$ = spaces?.getActiveSpace$() ?? of(undefined); + const activeSpace$: Observable<Space | undefined> = spaces?.getActiveSpace$() ?? of(undefined); + const onCloud = cloud !== undefined; // The new side nav will initially only be available to cloud users + const isServerless = this.initializerContext.env.packageInfo.buildFlavor === 'serverless'; + + this.isSolutionNavEnabled = onCloud && !isServerless; /* * @@ -92,30 +89,18 @@ export class NavigationPublicPlugin return createTopNav(customUnifiedSearch ?? unifiedSearch, customExtensions ?? extensions); }; - const onCloud = cloud !== undefined; // The new side nav will initially only be available to cloud users - const isServerless = this.initializerContext.env.packageInfo.buildFlavor === 'serverless'; - - if (cloudExperiments && onCloud && !isServerless) { - this.isSolutionNavExperiementEnabled$ = from( - cloudExperiments.getVariation(SOLUTION_NAV_FEATURE_FLAG_NAME, false).catch(() => false) - ).pipe(shareReplay(1)); - } - // Initialize the solution navigation if it is enabled - combineLatest([this.isSolutionNavExperiementEnabled$, activeSpace$]) - .pipe(take(1)) - .subscribe(([isEnabled, activeSpace]) => { - this.initiateChromeStyleAndSideNav(chrome, { - isFeatureEnabled: isEnabled, - isServerless, - activeSpace, - }); - - if (!isEnabled) return; - - chrome.project.setCloudUrls(cloud!); + activeSpace$.pipe(take(1)).subscribe((activeSpace) => { + this.initiateChromeStyleAndSideNav(chrome, { + isServerless, + activeSpace, }); + if (!this.isSolutionNavEnabled) return; + + chrome.project.setCloudUrls(cloud!); + }); + return { ui: { TopNavMenu: createTopNav(unifiedSearch, extensions), @@ -123,17 +108,12 @@ export class NavigationPublicPlugin createTopNavWithCustomContext: createCustomTopNav, }, addSolutionNavigation: (solutionNavigation) => { - firstValueFrom(this.isSolutionNavExperiementEnabled$).then((isEnabled) => { - if (!isEnabled) return; - this.addSolutionNavigation(solutionNavigation); - }); + if (!this.isSolutionNavEnabled) return; + this.addSolutionNavigation(solutionNavigation); }, - isSolutionNavEnabled$: combineLatest([ - this.isSolutionNavExperiementEnabled$, - activeSpace$, - ]).pipe( - map(([isFeatureEnabled, activeSpace]) => { - return getIsProjectNav(isFeatureEnabled, activeSpace?.solution) && !isServerless; + isSolutionNavEnabled$: activeSpace$.pipe( + map((activeSpace) => { + return this.isSolutionNavEnabled && getIsProjectNav(activeSpace?.solution); }) ), }; @@ -178,14 +158,10 @@ export class NavigationPublicPlugin private initiateChromeStyleAndSideNav( chrome: InternalChromeStart, - { - isFeatureEnabled, - isServerless, - activeSpace, - }: { isFeatureEnabled: boolean; isServerless: boolean; activeSpace?: Space } + { isServerless, activeSpace }: { isServerless: boolean; activeSpace?: Space } ) { const solutionView = activeSpace?.solution; - const isProjectNav = getIsProjectNav(isFeatureEnabled, solutionView) && !isServerless; + const isProjectNav = this.isSolutionNavEnabled && getIsProjectNav(solutionView); // On serverless the chrome style is already set by the serverless plugin if (!isServerless) { @@ -198,8 +174,8 @@ export class NavigationPublicPlugin } } -function getIsProjectNav(isFeatureEnabled: boolean, solutionView?: string) { - return isFeatureEnabled && Boolean(solutionView) && isKnownSolutionView(solutionView); +function getIsProjectNav(solutionView?: string) { + return Boolean(solutionView) && isKnownSolutionView(solutionView); } function isKnownSolutionView(solution?: string) { diff --git a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap index 570699aa0c0e2..ab174c6d00102 100644 --- a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap +++ b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap @@ -2,6 +2,7 @@ exports[`TopNavMenu Should render emphasized item which should be clickable 1`] = ` <EuiButton + color="primary" fill={true} iconSide="right" iconType="beaker" diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx index 2c8fc1e58d934..050fb788a7870 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx @@ -22,6 +22,8 @@ export interface TopNavMenuData { tooltip?: string | (() => string | undefined); badge?: EuiBetaBadgeProps; emphasize?: boolean; + fill?: boolean; + color?: string; isLoading?: boolean; iconType?: string; iconSide?: EuiButtonProps['iconSide']; diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx index 495e5c4ac9593..d52878aad8491 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx @@ -8,7 +8,7 @@ import { upperFirst, isFunction } from 'lodash'; import React, { MouseEvent } from 'react'; -import { EuiToolTip, EuiButton, EuiHeaderLink, EuiBetaBadge } from '@elastic/eui'; +import { EuiToolTip, EuiButton, EuiHeaderLink, EuiBetaBadge, EuiButtonColor } from '@elastic/eui'; import { TopNavMenuData } from './top_nav_menu_data'; export function TopNavMenuItem(props: TopNavMenuData) { @@ -48,6 +48,8 @@ export function TopNavMenuItem(props: TopNavMenuData) { iconSide: props.iconSide, 'data-test-subj': props.testId, className: props.className, + color: (props.color ?? 'primary') as EuiButtonColor, + fill: props.fill ?? true, }; // If the item specified a href, then override the suppress the onClick @@ -58,11 +60,11 @@ export function TopNavMenuItem(props: TopNavMenuData) { : {}; const btn = props.emphasize ? ( - <EuiButton size="s" {...commonButtonProps} fill> + <EuiButton size="s" {...commonButtonProps}> {getButtonContainer()} </EuiButton> ) : ( - <EuiHeaderLink size="s" color="primary" {...commonButtonProps} {...overrideProps}> + <EuiHeaderLink size="s" {...commonButtonProps} {...overrideProps}> {getButtonContainer()} </EuiHeaderLink> ); diff --git a/src/plugins/navigation/public/types.ts b/src/plugins/navigation/public/types.ts index 4ce2efdfef84f..f5a4522d18233 100644 --- a/src/plugins/navigation/public/types.ts +++ b/src/plugins/navigation/public/types.ts @@ -12,7 +12,6 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import type { SolutionNavigationDefinition } from '@kbn/core-chrome-browser'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; import { PanelContentProvider } from '@kbn/shared-ux-chrome-navigation'; import { TopNavMenuProps, TopNavMenuExtensionsRegistrySetup, createTopNav } from './top_nav_menu'; @@ -53,7 +52,6 @@ export interface NavigationPublicSetupDependencies { export interface NavigationPublicStartDependencies { unifiedSearch: UnifiedSearchPublicPluginStart; cloud?: CloudStart; - cloudExperiments?: CloudExperimentsPluginStart; spaces?: SpacesPluginStart; } diff --git a/src/plugins/navigation/server/types.ts b/src/plugins/navigation/server/types.ts index 80980b245d815..f17b10bbc2c1c 100644 --- a/src/plugins/navigation/server/types.ts +++ b/src/plugins/navigation/server/types.ts @@ -5,7 +5,6 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server'; import type { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server'; @@ -21,7 +20,6 @@ export interface NavigationServerSetupDependencies { } export interface NavigationServerStartDependencies { - cloudExperiments?: CloudExperimentsPluginStart; cloud?: CloudStart; spaces?: SpacesPluginStart; } diff --git a/src/plugins/navigation/tsconfig.json b/src/plugins/navigation/tsconfig.json index 03a88e87ad806..1ee0462330954 100644 --- a/src/plugins/navigation/tsconfig.json +++ b/src/plugins/navigation/tsconfig.json @@ -22,7 +22,6 @@ "@kbn/shared-ux-chrome-navigation", "@kbn/cloud-plugin", "@kbn/config", - "@kbn/cloud-experiments-plugin", "@kbn/spaces-plugin", "@kbn/core-ui-settings-common", "@kbn/config-schema", diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.test.ts b/src/plugins/telemetry/common/ebt_v3_endpoint/build_headers.test.ts similarity index 76% rename from packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.test.ts rename to src/plugins/telemetry/common/ebt_v3_endpoint/build_headers.test.ts index ecc350006eef9..96a2dcbed9f9e 100644 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.test.ts +++ b/src/plugins/telemetry/common/ebt_v3_endpoint/build_headers.test.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { buildHeaders } from './build_headers'; +import { buildShipperHeaders } from './build_headers'; -describe('buildHeaders', () => { +describe('buildShipperHeaders', () => { test('builds the headers as expected in the V3 endpoints', () => { - expect(buildHeaders('test-cluster', '1.2.3', 'test-license')).toMatchInlineSnapshot(` + expect(buildShipperHeaders('test-cluster', '1.2.3', 'test-license')).toMatchInlineSnapshot(` Object { "content-type": "application/x-ndjson", "x-elastic-cluster-id": "test-cluster", @@ -21,7 +21,7 @@ describe('buildHeaders', () => { }); test('if license is not provided, it skips the license header', () => { - expect(buildHeaders('test-cluster', '1.2.3')).toMatchInlineSnapshot(` + expect(buildShipperHeaders('test-cluster', '1.2.3')).toMatchInlineSnapshot(` Object { "content-type": "application/x-ndjson", "x-elastic-cluster-id": "test-cluster", diff --git a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.ts b/src/plugins/telemetry/common/ebt_v3_endpoint/build_headers.ts similarity index 78% rename from packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.ts rename to src/plugins/telemetry/common/ebt_v3_endpoint/build_headers.ts index b303e20b21d82..4fcf79e6fff23 100644 --- a/packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.ts +++ b/src/plugins/telemetry/common/ebt_v3_endpoint/build_headers.ts @@ -6,17 +6,23 @@ * Side Public License, v 1. */ +import type { BuildShipperHeaders } from '@elastic/ebt/shippers/elastic_v3/common'; + /** * Returns the headers to send to the Remote Telemetry Service. * @param clusterUuid The UUID of the ES cluster. * @param version The version of the ES cluster. * @param licenseId The ID of the license (if available). */ -export function buildHeaders(clusterUuid: string, version: string, licenseId?: string) { +export const buildShipperHeaders: BuildShipperHeaders = ( + clusterUuid: string, + version: string, + licenseId?: string +) => { return { 'content-type': 'application/x-ndjson', 'x-elastic-cluster-id': clusterUuid, 'x-elastic-stack-version': version, ...(licenseId && { 'x-elastic-license-id': licenseId }), }; -} +}; diff --git a/src/plugins/telemetry/common/ebt_v3_endpoint/build_url.test.ts b/src/plugins/telemetry/common/ebt_v3_endpoint/build_url.test.ts new file mode 100644 index 0000000000000..dc6bb76898aec --- /dev/null +++ b/src/plugins/telemetry/common/ebt_v3_endpoint/build_url.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 { createBuildShipperUrl } from './build_url'; + +describe('buildUrl', () => { + test('returns production URL', () => { + const buildShipperUrl = createBuildShipperUrl('production'); + expect(buildShipperUrl({ channelName: 'test-channel' })).toBe( + 'https://telemetry.elastic.co/v3/send/test-channel' + ); + }); + + test('returns staging URL', () => { + const buildShipperUrl = createBuildShipperUrl('staging'); + expect(buildShipperUrl({ channelName: 'test-channel' })).toBe( + 'https://telemetry-staging.elastic.co/v3/send/test-channel' + ); + }); +}); diff --git a/src/plugins/telemetry/common/ebt_v3_endpoint/build_url.ts b/src/plugins/telemetry/common/ebt_v3_endpoint/build_url.ts new file mode 100644 index 0000000000000..684e1a0d15aed --- /dev/null +++ b/src/plugins/telemetry/common/ebt_v3_endpoint/build_url.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 { + BuildShipperUrlOptions, + BuildShipperUrl, +} from '@elastic/ebt/shippers/elastic_v3/common'; + +/** + * Builds the URL for the V3 API. + */ +export const createBuildShipperUrl = + (sendTo: 'production' | 'staging'): BuildShipperUrl => + (urlOptions: BuildShipperUrlOptions): string => { + const { channelName } = urlOptions; + const baseUrl = + sendTo === 'production' + ? 'https://telemetry.elastic.co' + : 'https://telemetry-staging.elastic.co'; + return `${baseUrl}/v3/send/${channelName}`; + }; diff --git a/src/plugins/telemetry/common/ebt_v3_endpoint/index.ts b/src/plugins/telemetry/common/ebt_v3_endpoint/index.ts new file mode 100644 index 0000000000000..851cfb55a4fb8 --- /dev/null +++ b/src/plugins/telemetry/common/ebt_v3_endpoint/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 { buildShipperHeaders } from './build_headers'; +export { createBuildShipperUrl } from './build_url'; diff --git a/src/plugins/telemetry/public/plugin.test.ts b/src/plugins/telemetry/public/plugin.test.ts index f943f2cae86d1..6b4bf20bab467 100644 --- a/src/plugins/telemetry/public/plugin.test.ts +++ b/src/plugins/telemetry/public/plugin.test.ts @@ -5,14 +5,15 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +/* eslint-disable dot-notation */ import { of } from 'rxjs'; -import { ElasticV3BrowserShipper } from '@kbn/ebt/shippers/elastic_v3/browser'; +import { ElasticV3BrowserShipper } from '@elastic/ebt/shippers/elastic_v3/browser'; import { coreMock } from '@kbn/core/public/mocks'; import { homePluginMock } from '@kbn/home-plugin/public/mocks'; import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/public'; +import { buildShipperHeaders } from '../common/ebt_v3_endpoint'; import { isSyntheticsMonitorMock } from './plugin.test.mock'; import { TelemetryPlugin } from './plugin'; @@ -64,24 +65,44 @@ describe('TelemetryPlugin', () => { it('registers the UI telemetry shipper', () => { const initializerContext = coreMock.createPluginInitializerContext(); const coreSetupMock = coreMock.createSetup(); + const telemetryPlugin = new TelemetryPlugin(initializerContext); + + telemetryPlugin['getSendToEnv'] = jest.fn(); + telemetryPlugin.setup(coreSetupMock, { screenshotMode, home }); - new TelemetryPlugin(initializerContext).setup(coreSetupMock, { screenshotMode, home }); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledTimes(1); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledWith(undefined); expect(coreSetupMock.analytics.registerShipper).toHaveBeenCalledWith( ElasticV3BrowserShipper, - { channelName: 'kibana-browser', version: 'version', sendTo: 'staging' } + { + channelName: 'kibana-browser', + version: 'version', + buildShipperUrl: expect.any(Function), + buildShipperHeaders, + } ); }); it('registers the UI telemetry shipper (pointing to prod)', () => { const initializerContext = coreMock.createPluginInitializerContext({ sendUsageTo: 'prod' }); const coreSetupMock = coreMock.createSetup(); + const telemetryPlugin = new TelemetryPlugin(initializerContext); + + telemetryPlugin['getSendToEnv'] = jest.fn(); + telemetryPlugin.setup(coreSetupMock, { screenshotMode, home }); - new TelemetryPlugin(initializerContext).setup(coreSetupMock, { screenshotMode, home }); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledTimes(1); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledWith('prod'); expect(coreSetupMock.analytics.registerShipper).toHaveBeenCalledWith( ElasticV3BrowserShipper, - { channelName: 'kibana-browser', version: 'version', sendTo: 'production' } + { + channelName: 'kibana-browser', + version: 'version', + buildShipperUrl: expect.any(Function), + buildShipperHeaders, + } ); }); }); diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index 9b5a2c3af596a..657751212ada8 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -22,10 +22,11 @@ import type { ScreenshotModePluginStart, } from '@kbn/screenshot-mode-plugin/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { ElasticV3BrowserShipper } from '@kbn/ebt/shippers/elastic_v3/browser'; +import { ElasticV3BrowserShipper } from '@elastic/ebt/shippers/elastic_v3/browser'; import { isSyntheticsMonitor } from '@kbn/analytics-collection-utils'; - import { BehaviorSubject, map, switchMap, tap } from 'rxjs'; +import { buildShipperHeaders, createBuildShipperUrl } from '../common/ebt_v3_endpoint'; + import type { TelemetryConfigLabels } from '../server/config'; import { FetchTelemetryConfigRoute, INTERNAL_VERSION } from '../common/routes'; import type { v2 } from '../common/types'; @@ -192,10 +193,12 @@ export class TelemetryPlugin }, }); + const sendTo = this.getSendToEnv(config.sendUsageTo); analytics.registerShipper(ElasticV3BrowserShipper, { channelName: 'kibana-browser', version: currentKibanaVersion, - sendTo: config.sendUsageTo === 'prod' ? 'production' : 'staging', + buildShipperHeaders, + buildShipperUrl: createBuildShipperUrl(sendTo), }); this.telemetrySender = new TelemetrySender(this.telemetryService, async () => { @@ -296,6 +299,10 @@ export class TelemetryPlugin this.telemetrySender?.stop(); } + private getSendToEnv(sendUsageTo: string): 'production' | 'staging' { + return sendUsageTo === 'prod' ? 'production' : 'staging'; + } + /** * Kibana should skip telemetry collection if reporting is taking a screenshot * or Synthetics monitoring is navigating Kibana. diff --git a/src/plugins/telemetry/server/plugin.test.ts b/src/plugins/telemetry/server/plugin.test.ts index 6410deb2daa59..b63a86af50191 100644 --- a/src/plugins/telemetry/server/plugin.test.ts +++ b/src/plugins/telemetry/server/plugin.test.ts @@ -5,11 +5,12 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -import { ElasticV3ServerShipper } from '@kbn/ebt/shippers/elastic_v3/server'; +/* eslint-disable dot-notation */ +import { ElasticV3ServerShipper } from '@elastic/ebt/shippers/elastic_v3/server'; import { coreMock } from '@kbn/core/server/mocks'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; import { telemetryCollectionManagerPluginMock } from '@kbn/telemetry-collection-manager-plugin/server/mocks'; +import { buildShipperHeaders } from '../common/ebt_v3_endpoint'; import { TelemetryPlugin } from './plugin'; import type { NodeRoles } from '@kbn/core-node-server'; @@ -40,14 +41,25 @@ describe('TelemetryPlugin', () => { const initializerContext = coreMock.createPluginInitializerContext(); const coreSetupMock = coreMock.createSetup(); - new TelemetryPlugin(initializerContext).setup(coreSetupMock, { + const telemetryPlugin = new TelemetryPlugin(initializerContext); + telemetryPlugin['getSendToEnv'] = jest.fn(); + + telemetryPlugin.setup(coreSetupMock, { usageCollection: usageCollectionPluginMock.createSetupContract(), telemetryCollectionManager: telemetryCollectionManagerPluginMock.createSetupContract(), }); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledTimes(1); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledWith(undefined); + expect(coreSetupMock.analytics.registerShipper).toHaveBeenCalledWith( ElasticV3ServerShipper, - { channelName: 'kibana-server', version: 'version', sendTo: 'staging' } + { + channelName: 'kibana-server', + version: 'version', + buildShipperUrl: expect.any(Function), + buildShipperHeaders, + } ); }); @@ -55,14 +67,25 @@ describe('TelemetryPlugin', () => { const initializerContext = coreMock.createPluginInitializerContext({ sendUsageTo: 'prod' }); const coreSetupMock = coreMock.createSetup(); - new TelemetryPlugin(initializerContext).setup(coreSetupMock, { + const telemetryPlugin = new TelemetryPlugin(initializerContext); + telemetryPlugin['getSendToEnv'] = jest.fn(); + + telemetryPlugin.setup(coreSetupMock, { usageCollection: usageCollectionPluginMock.createSetupContract(), telemetryCollectionManager: telemetryCollectionManagerPluginMock.createSetupContract(), }); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledTimes(1); + expect(telemetryPlugin['getSendToEnv']).toHaveBeenCalledWith('prod'); + expect(coreSetupMock.analytics.registerShipper).toHaveBeenCalledWith( ElasticV3ServerShipper, - { channelName: 'kibana-server', version: 'version', sendTo: 'production' } + { + channelName: 'kibana-server', + version: 'version', + buildShipperUrl: expect.any(Function), + buildShipperHeaders, + } ); }); }); @@ -76,7 +99,6 @@ describe('TelemetryPlugin', () => { const plugin = new TelemetryPlugin(initializerContext); - // eslint-disable-next-line dot-notation const startFetcherMock = (plugin['startFetcher'] = jest.fn()); plugin.setup(coreMock.createSetup(), { diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index be8f298218e0f..02dcdef44f6e8 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -22,7 +22,7 @@ import { map, } from 'rxjs'; -import { ElasticV3ServerShipper } from '@kbn/ebt/shippers/elastic_v3/server'; +import { ElasticV3ServerShipper } from '@elastic/ebt/shippers/elastic_v3/server'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import type { @@ -41,6 +41,7 @@ import type { SecurityPluginStart } from '@kbn/security-plugin/server'; import { SavedObjectsClient } from '@kbn/core/server'; import apm from 'elastic-apm-node'; +import { buildShipperHeaders, createBuildShipperUrl } from '../common/ebt_v3_endpoint'; import { type TelemetrySavedObject, getTelemetrySavedObject, @@ -171,10 +172,12 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl const currentKibanaVersion = this.currentKibanaVersion; + const sendTo = this.getSendToEnv(this.initialConfig.sendUsageTo); analytics.registerShipper(ElasticV3ServerShipper, { channelName: 'kibana-server', version: currentKibanaVersion, - sendTo: this.initialConfig.sendUsageTo === 'prod' ? 'production' : 'staging', + buildShipperHeaders, + buildShipperUrl: createBuildShipperUrl(sendTo), }); analytics.registerContextProvider<{ labels: TelemetryConfigLabels }>({ @@ -262,6 +265,10 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl this.fetcherTask.stop(); } + private getSendToEnv(sendUsageTo: string): 'production' | 'staging' { + return sendUsageTo === 'prod' ? 'production' : 'staging'; + } + private async getOptInStatus(): Promise<boolean | undefined> { const internalRepositoryClient = await firstValueFrom(this.savedObjectsInternalClient$, { defaultValue: undefined, diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index c6198c377832e..2286d13f25e9c 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -35,7 +35,6 @@ "@kbn/analytics-collection-utils", "@kbn/react-kibana-mount", "@kbn/core-node-server", - "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/src/plugins/unified_doc_viewer/public/__mocks__/services.ts b/src/plugins/unified_doc_viewer/public/__mocks__/services.ts index 5e7222f261532..81e2f084282f7 100644 --- a/src/plugins/unified_doc_viewer/public/__mocks__/services.ts +++ b/src/plugins/unified_doc_viewer/public/__mocks__/services.ts @@ -8,6 +8,7 @@ import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { coreMock } from '@kbn/core/public/mocks'; import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; import { fieldsMetadataPluginPublicMock } from '@kbn/fields-metadata-plugin/public/mocks'; import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; @@ -29,4 +30,5 @@ export const mockUnifiedDocViewerServices: jest.Mocked<UnifiedDocViewerServices> uiSettings: uiSettingsServiceMock.createStartContract(), unifiedDocViewer: mockUnifiedDocViewer, share: sharePluginMock.createStartContract(), + core: coreMock.createStart(), }; diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx index 62f5ed497067b..97330ac8ce1a0 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table.tsx @@ -9,10 +9,10 @@ import './table.scss'; import React, { useCallback, useMemo, useState } from 'react'; import useWindowSize from 'react-use/lib/useWindowSize'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { EuiFlexGroup, EuiFlexItem, - EuiFieldSearch, EuiSpacer, EuiSelectableMessage, EuiDataGrid, @@ -22,10 +22,11 @@ import { EuiText, EuiCallOut, useResizeObserver, + EuiSwitch, + useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; -import { debounce } from 'lodash'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { getFieldIconType } from '@kbn/field-utils/src/utils/get_field_icon_type'; import { @@ -38,7 +39,6 @@ import { } from '@kbn/discover-utils'; import { FieldDescription, - fieldNameWildcardMatcher, getFieldSearchMatchingHighlight, getTextBasedColumnIconType, } from '@kbn/field-utils'; @@ -57,12 +57,14 @@ import { DEFAULT_MARGIN_BOTTOM, getTabContentAvailableHeight, } from '../doc_viewer_source/get_height'; +import { TableFilters, TableFiltersProps, useTableFilters } from './table_filters'; export type FieldRecord = TableRow; interface ItemsEntry { pinnedItems: FieldRecord[]; restItems: FieldRecord[]; + allFields: TableFiltersProps['allFields']; } const MIN_NAME_COLUMN_WIDTH = 150; @@ -71,7 +73,7 @@ const PAGE_SIZE_OPTIONS = [25, 50, 100, 250, 500]; const DEFAULT_PAGE_SIZE = 25; const PINNED_FIELDS_KEY = 'discover:pinnedFields'; const PAGE_SIZE = 'discover:pageSize'; -const SEARCH_TEXT = 'discover:searchText'; +const HIDE_NULL_VALUES = 'unifiedDocViewer:hideNullValues'; const GRID_COLUMN_FIELD_NAME = 'name'; const GRID_COLUMN_FIELD_VALUE = 'value'; @@ -122,33 +124,29 @@ const updatePageSize = (newPageSize: number, storage: Storage) => { storage.set(PAGE_SIZE, newPageSize); }; -const getSearchText = (storage: Storage) => { - return storage.get(SEARCH_TEXT) || ''; -}; -const updateSearchText = debounce( - (newSearchText: string, storage: Storage) => storage.set(SEARCH_TEXT, newSearchText), - 500 -); - export const DocViewerTable = ({ columns, columnsMeta, hit, dataView, + textBasedHits, filter, decreaseAvailableHeightBy, onAddColumn, onRemoveColumn, }: DocViewRenderProps) => { + const isEsqlMode = Array.isArray(textBasedHits); const [containerRef, setContainerRef] = useState<HTMLDivElement | null>(null); const { fieldFormats, storage, uiSettings, fieldsMetadata } = getUnifiedDocViewerServices(); const showMultiFields = uiSettings.get(SHOW_MULTIFIELDS); const currentDataViewId = dataView.id!; - const [searchText, setSearchText] = useState(getSearchText(storage)); const [pinnedFields, setPinnedFields] = useState<string[]>( getPinnedFields(currentDataViewId, storage) ); + const [areNullValuesHidden, setAreNullValuesHidden] = useLocalStorage(HIDE_NULL_VALUES, false); + + const { euiTheme } = useEuiTheme(); const flattened = hit.flattened; const shouldShowFieldHandler = useMemo( @@ -156,10 +154,6 @@ export const DocViewerTable = ({ [flattened, dataView, showMultiFields] ); - const searchPlaceholder = i18n.translate('unifiedDocViewer.docView.table.searchPlaceHolder', { - defaultMessage: 'Search field names', - }); - const mapping = useCallback((name: string) => dataView.fields.getByName(name), [dataView.fields]); const onToggleColumn = useMemo(() => { @@ -187,14 +181,7 @@ export const DocViewerTable = ({ [currentDataViewId, pinnedFields, storage] ); - const onSearch = useCallback( - (event: React.ChangeEvent<HTMLInputElement>) => { - const newSearchText = event.currentTarget.value; - updateSearchText(newSearchText, storage); - setSearchText(newSearchText); - }, - [storage] - ); + const { onFilterField, ...tableFiltersProps } = useTableFilters(storage); const fieldToItem = useCallback( (field: string, isPinned: boolean) => { @@ -252,43 +239,64 @@ export const DocViewerTable = ({ ] ); - const { pinnedItems, restItems } = Object.keys(flattened) - .sort((fieldA, fieldB) => { - const mappingA = mapping(fieldA); - const mappingB = mapping(fieldB); - const nameA = !mappingA || !mappingA.displayName ? fieldA : mappingA.displayName; - const nameB = !mappingB || !mappingB.displayName ? fieldB : mappingB.displayName; - return nameA.localeCompare(nameB); - }) - .reduce<ItemsEntry>( - (acc, curFieldName) => { - if (!shouldShowFieldHandler(curFieldName)) { - return acc; - } - - if (pinnedFields.includes(curFieldName)) { - acc.pinnedItems.push(fieldToItem(curFieldName, true)); - } else { - const fieldMapping = mapping(curFieldName); - if ( - !searchText?.trim() || - fieldNameWildcardMatcher( - { name: curFieldName, displayName: fieldMapping?.displayName }, - searchText - ) - ) { - // filter only unpinned fields - acc.restItems.push(fieldToItem(curFieldName, false)); + const { pinnedItems, restItems, allFields } = useMemo( + () => + Object.keys(flattened) + .sort((fieldA, fieldB) => { + const mappingA = mapping(fieldA); + const mappingB = mapping(fieldB); + const nameA = !mappingA || !mappingA.displayName ? fieldA : mappingA.displayName; + const nameB = !mappingB || !mappingB.displayName ? fieldB : mappingB.displayName; + return nameA.localeCompare(nameB); + }) + .reduce<ItemsEntry>( + (acc, curFieldName) => { + if (!shouldShowFieldHandler(curFieldName)) { + return acc; + } + const shouldHideNullValue = + areNullValuesHidden && flattened[curFieldName] == null && isEsqlMode; + if (shouldHideNullValue) { + return acc; + } + + const isPinned = pinnedFields.includes(curFieldName); + const row = fieldToItem(curFieldName, isPinned); + + if (isPinned) { + acc.pinnedItems.push(row); + } else { + if (onFilterField(curFieldName, row.field.displayName, row.field.fieldType)) { + // filter only unpinned fields + acc.restItems.push(row); + } + } + + acc.allFields.push({ + name: curFieldName, + displayName: row.field.displayName, + type: row.field.fieldType, + }); + + return acc; + }, + { + pinnedItems: [], + restItems: [], + allFields: [], } - } - - return acc; - }, - { - pinnedItems: [], - restItems: [], - } - ); + ), + [ + areNullValuesHidden, + fieldToItem, + flattened, + isEsqlMode, + mapping, + onFilterField, + pinnedFields, + shouldShowFieldHandler, + ] + ); const rows = useMemo(() => [...pinnedItems, ...restItems], [pinnedItems, restItems]); @@ -358,6 +366,13 @@ export const DocViewerTable = ({ [fieldCellActions, fieldValueCellActions, containerWidth] ); + const onHideNullValuesChange = useCallback( + (e) => { + setAreNullValuesHidden(e.target.checked); + }, + [setAreNullValuesHidden] + ); + const renderCellValue: EuiDataGridProps['renderCellValue'] = useCallback( ({ rowIndex, columnId, isDetails }) => { const row = rows[rowIndex]; @@ -382,7 +397,7 @@ export const DocViewerTable = ({ scripted={scripted} highlight={getFieldSearchMatchingHighlight( fieldMapping?.displayName ?? field, - searchText + tableFiltersProps.searchTerm )} isPinned={pinned} /> @@ -413,7 +428,7 @@ export const DocViewerTable = ({ return null; }, - [rows, searchText, fieldsMetadata] + [rows, tableFiltersProps.searchTerm, fieldsMetadata] ); const renderCellPopover = useCallback( @@ -469,14 +484,7 @@ export const DocViewerTable = ({ </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiFieldSearch - aria-label={searchPlaceholder} - fullWidth - onChange={onSearch} - placeholder={searchPlaceholder} - value={searchText} - data-test-subj="unifiedDocViewerFieldsSearchInput" - /> + <TableFilters {...tableFiltersProps} allFields={allFields} /> </EuiFlexItem> {rows.length === 0 ? ( @@ -493,6 +501,25 @@ export const DocViewerTable = ({ <EuiFlexItem grow={false}> <EuiSpacer size="s" /> </EuiFlexItem> + {isEsqlMode && ( + <EuiFlexItem + grow={false} + css={css` + align-self: end; + padding-bottom: ${euiTheme.size.s}; + `} + > + <EuiSwitch + label={i18n.translate('unifiedDocViewer.hideNullValues.switchLabel', { + defaultMessage: 'Hide fields with null values', + })} + checked={areNullValuesHidden ?? false} + onChange={onHideNullValuesChange} + compressed + data-test-subj="unifiedDocViewerHideNullValuesSwitch" + /> + </EuiFlexItem> + )} <EuiFlexItem grow={Boolean(containerHeight)} css={css` diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_filters.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_filters.tsx new file mode 100644 index 0000000000000..0a030336571e8 --- /dev/null +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_filters.tsx @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback, useState, useMemo } from 'react'; +import { EuiFieldSearch } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { debounce } from 'lodash'; +import { fieldNameWildcardMatcher, type FieldTypeKnown } from '@kbn/field-utils'; +import type { FieldListItem } from '@kbn/unified-field-list'; +import { + FieldTypeFilter, + type FieldTypeFilterProps, +} from '@kbn/unified-field-list/src/components/field_list_filters/field_type_filter'; +import { getUnifiedDocViewerServices } from '../../plugin'; + +export const LOCAL_STORAGE_KEY_SEARCH_TERM = 'discover:searchText'; +export const LOCAL_STORAGE_KEY_SELECTED_FIELD_TYPES = 'unifiedDocViewer:selectedFieldTypes'; + +const searchPlaceholder = i18n.translate('unifiedDocViewer.docView.table.searchPlaceHolder', { + defaultMessage: 'Search field names', +}); + +interface TableFiltersCommonProps { + // search + searchTerm: string; + onChangeSearchTerm: (searchTerm: string) => void; + // field types + selectedFieldTypes: FieldTypeFilterProps<FieldListItem>['selectedFieldTypes']; + onChangeFieldTypes: FieldTypeFilterProps<FieldListItem>['onChange']; +} + +export interface TableFiltersProps extends TableFiltersCommonProps { + allFields: FieldListItem[]; +} + +export const TableFilters: React.FC<TableFiltersProps> = ({ + searchTerm, + onChangeSearchTerm, + selectedFieldTypes, + onChangeFieldTypes, + allFields, +}) => { + const { core } = getUnifiedDocViewerServices(); + + const onSearchTermChange = useCallback( + (event: React.ChangeEvent<HTMLInputElement>) => { + const newSearchTerm = event.currentTarget.value; + onChangeSearchTerm(newSearchTerm); + }, + [onChangeSearchTerm] + ); + + return ( + <EuiFieldSearch + data-test-subj="unifiedDocViewerFieldsSearchInput" + aria-label={searchPlaceholder} + placeholder={searchPlaceholder} + fullWidth + compressed + value={searchTerm} + onChange={onSearchTermChange} + append={ + allFields && selectedFieldTypes && onChangeFieldTypes ? ( + <FieldTypeFilter + data-test-subj="unifiedDocViewerFieldsTable" + docLinks={core.docLinks} + selectedFieldTypes={selectedFieldTypes} + allFields={allFields} + onChange={onChangeFieldTypes} + /> + ) : undefined + } + /> + ); +}; + +const persistSearchTerm = debounce( + (newSearchText: string, storage: Storage) => + storage.set(LOCAL_STORAGE_KEY_SEARCH_TERM, newSearchText), + 500, + { leading: true, trailing: true } +); + +const persistSelectedFieldTypes = debounce( + (selectedFieldTypes: FieldTypeKnown[], storage: Storage) => + storage.set(LOCAL_STORAGE_KEY_SELECTED_FIELD_TYPES, JSON.stringify(selectedFieldTypes)), + 500, + { leading: true, trailing: true } +); + +const getStoredFieldTypes = (storage: Storage) => { + const storedFieldTypes = storage.get(LOCAL_STORAGE_KEY_SELECTED_FIELD_TYPES); + let parsedFieldTypes: FieldTypeKnown[] = []; + + try { + parsedFieldTypes = storedFieldTypes ? JSON.parse(storedFieldTypes) : []; + } catch { + // ignore invalid JSON + } + + return Array.isArray(parsedFieldTypes) ? parsedFieldTypes : []; +}; + +interface UseTableFiltersReturn extends TableFiltersCommonProps { + onFilterField: ( + fieldName: string, + fieldDisplayName: string | undefined, + fieldType: string | undefined + ) => boolean; +} + +export const useTableFilters = (storage: Storage): UseTableFiltersReturn => { + const [searchTerm, setSearchTerm] = useState(storage.get(LOCAL_STORAGE_KEY_SEARCH_TERM) || ''); + const [selectedFieldTypes, setSelectedFieldTypes] = useState<FieldTypeKnown[]>( + getStoredFieldTypes(storage) + ); + + const onChangeSearchTerm = useCallback( + (newSearchTerm: string) => { + setSearchTerm(newSearchTerm); + persistSearchTerm(newSearchTerm, storage); + }, + [storage, setSearchTerm] + ); + + const onChangeFieldTypes = useCallback( + (newFieldTypes: FieldTypeKnown[]) => { + setSelectedFieldTypes(newFieldTypes); + persistSelectedFieldTypes(newFieldTypes, storage); + }, + [storage, setSelectedFieldTypes] + ); + + const onFilterField: UseTableFiltersReturn['onFilterField'] = useCallback( + (fieldName, fieldDisplayName, fieldType) => { + const term = searchTerm?.trim(); + if ( + term && + !fieldNameWildcardMatcher({ name: fieldName, displayName: fieldDisplayName }, term) + ) { + return false; + } + + if (selectedFieldTypes.length > 0 && fieldType) { + return selectedFieldTypes.includes(fieldType); + } + + return true; + }, + [searchTerm, selectedFieldTypes] + ); + + return useMemo( + () => ({ + // props for TableFilters component + searchTerm, + onChangeSearchTerm, + selectedFieldTypes, + onChangeFieldTypes, + // the actual filtering function + onFilterField, + }), + [searchTerm, onChangeSearchTerm, selectedFieldTypes, onChangeFieldTypes, onFilterField] + ); +}; diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/test_filters.test.ts b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/test_filters.test.ts new file mode 100644 index 0000000000000..77895ab4f9179 --- /dev/null +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/test_filters.test.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { Storage } from '@kbn/kibana-utils-plugin/public'; +import { + useTableFilters, + LOCAL_STORAGE_KEY_SEARCH_TERM, + LOCAL_STORAGE_KEY_SELECTED_FIELD_TYPES, +} from './table_filters'; + +const storage = new Storage(window.localStorage); + +describe('useTableFilters', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + afterAll(() => { + jest.useRealTimers(); + }); + + afterEach(() => { + storage.clear(); + }); + + it('should return initial search term and field types', () => { + const { result } = renderHook(() => useTableFilters(storage)); + + expect(result.current.searchTerm).toBe(''); + expect(result.current.selectedFieldTypes).toEqual([]); + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(true); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(true); + + expect(storage.get(LOCAL_STORAGE_KEY_SEARCH_TERM)).toBeNull(); + }); + + it('should filter by search term', () => { + const { result } = renderHook(() => useTableFilters(storage)); + + act(() => { + result.current.onChangeSearchTerm('ext'); + }); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(true); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(false); + + expect(storage.get(LOCAL_STORAGE_KEY_SEARCH_TERM)).toBe('ext'); + }); + + it('should filter by field type', () => { + const { result } = renderHook(() => useTableFilters(storage)); + + act(() => { + result.current.onChangeFieldTypes(['number']); + }); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(false); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(true); + + act(() => { + result.current.onChangeFieldTypes(['keyword']); + }); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(true); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(false); + + act(() => { + result.current.onChangeFieldTypes(['number', 'keyword']); + }); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(true); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(true); + + jest.advanceTimersByTime(600); + expect(storage.get(LOCAL_STORAGE_KEY_SELECTED_FIELD_TYPES)).toBe('["number","keyword"]'); + }); + + it('should filter by search term and field type', () => { + const { result } = renderHook(() => useTableFilters(storage)); + + act(() => { + result.current.onChangeSearchTerm('ext'); + result.current.onChangeFieldTypes(['keyword']); + }); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(true); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(false); + + act(() => { + result.current.onChangeSearchTerm('ext'); + result.current.onChangeFieldTypes(['number']); + }); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(false); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(false); + + act(() => { + result.current.onChangeSearchTerm('bytes'); + result.current.onChangeFieldTypes(['number']); + }); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(false); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(true); + + jest.advanceTimersByTime(600); + expect(storage.get(LOCAL_STORAGE_KEY_SEARCH_TERM)).toBe('bytes'); + expect(storage.get(LOCAL_STORAGE_KEY_SELECTED_FIELD_TYPES)).toBe('["number"]'); + }); + + it('should restore previous filters', () => { + storage.set(LOCAL_STORAGE_KEY_SEARCH_TERM, 'bytes'); + storage.set(LOCAL_STORAGE_KEY_SELECTED_FIELD_TYPES, '["number"]'); + + const { result } = renderHook(() => useTableFilters(storage)); + + expect(result.current.searchTerm).toBe('bytes'); + expect(result.current.selectedFieldTypes).toEqual(['number']); + + expect(result.current.onFilterField('extension', undefined, 'keyword')).toBe(false); + expect(result.current.onFilterField('bytes', undefined, 'number')).toBe(true); + expect(result.current.onFilterField('bytes_counter', undefined, 'counter')).toBe(false); + }); +}); diff --git a/src/plugins/unified_doc_viewer/public/plugin.tsx b/src/plugins/unified_doc_viewer/public/plugin.tsx index e8a23342e7976..d1e9d1cb86b18 100644 --- a/src/plugins/unified_doc_viewer/public/plugin.tsx +++ b/src/plugins/unified_doc_viewer/public/plugin.tsx @@ -120,6 +120,7 @@ export class UnifiedDocViewerPublicPlugin uiSettings, unifiedDocViewer, share, + core, }; setUnifiedDocViewerServices(services); return unifiedDocViewer; diff --git a/src/plugins/unified_doc_viewer/public/types.ts b/src/plugins/unified_doc_viewer/public/types.ts index 9266328306aaf..42a562944c1e1 100644 --- a/src/plugins/unified_doc_viewer/public/types.ts +++ b/src/plugins/unified_doc_viewer/public/types.ts @@ -5,10 +5,12 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + export type { JsonCodeEditorProps } from './components'; export type { EsDocSearchProps } from './hooks'; export type { UnifiedDocViewerSetup, UnifiedDocViewerStart } from './plugin'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -27,4 +29,5 @@ export interface UnifiedDocViewerServices { uiSettings: IUiSettingsClient; unifiedDocViewer: UnifiedDocViewerStart; share: SharePluginStart; + core: CoreStart; } diff --git a/src/plugins/unified_doc_viewer/tsconfig.json b/src/plugins/unified_doc_viewer/tsconfig.json index 3b271744ed4af..ef3a7a91153ac 100644 --- a/src/plugins/unified_doc_viewer/tsconfig.json +++ b/src/plugins/unified_doc_viewer/tsconfig.json @@ -35,7 +35,9 @@ "@kbn/core-notifications-browser", "@kbn/deeplinks-observability", "@kbn/share-plugin", - "@kbn/router-utils" + "@kbn/router-utils", + "@kbn/unified-field-list", + "@kbn/core-lifecycle-browser" ], "exclude": [ "target/**/*", diff --git a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx index 9a5f8296e6b98..20bc99ab95d0a 100644 --- a/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx +++ b/src/plugins/unified_search/public/__stories__/search_bar.stories.tsx @@ -566,7 +566,7 @@ storiesOf('SearchBar', module) ], } as unknown as SearchBarProps) ) - .add('with dataviewPicker with ESQL', () => + .add('with dataviewPicker with ES|QL', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', @@ -582,13 +582,13 @@ storiesOf('SearchBar', module) }, } as SearchBarProps) ) - .add('with dataviewPicker with ESQL and ESQL query', () => + .add('with dataviewPicker with ES|QL and ES|QL query', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', trigger: { 'data-test-subj': 'dataView-switch-link', - label: 'ESQL', + label: 'ES|QL', title: 'ESQL', }, onChangeDataView: action('onChangeDataView'), @@ -599,13 +599,13 @@ storiesOf('SearchBar', module) query: { esql: 'from dataview | project field1, field2' }, } as unknown as SearchBarProps<Query>) ) - .add('with dataviewPicker with ESQL and large ESQL query', () => + .add('with dataviewPicker with ES|QL and large ES|QL query', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', trigger: { 'data-test-subj': 'dataView-switch-link', - label: 'ESQL', + label: 'ES|QL', title: 'ESQL', }, onChangeDataView: action('onChangeDataView'), @@ -618,13 +618,13 @@ storiesOf('SearchBar', module) }, } as unknown as SearchBarProps<Query>) ) - .add('with dataviewPicker with ESQL and errors in ESQL query', () => + .add('with dataviewPicker with ES|QL and errors in ES|QL query', () => wrapSearchBarInContext({ dataViewPickerComponentProps: { currentDataViewId: '1234', trigger: { 'data-test-subj': 'dataView-switch-link', - label: 'ESQL', + label: 'ES|QL', title: 'ESQL', }, onChangeDataView: action('onChangeDataView'), diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts b/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts index a2332648da3ea..85db6cb8ab08b 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.styles.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import type { EuiThemeComputed } from '@elastic/eui'; import { calculateWidthFromEntries } from '@kbn/calculate-width-from-char-count'; import { DataViewListItemEnhanced } from './dataview_list'; @@ -14,13 +14,18 @@ const MIN_WIDTH = 300; export const changeDataViewStyles = ({ fullWidth, dataViewsList, + theme, }: { fullWidth?: boolean; dataViewsList: DataViewListItemEnhanced[]; + theme: EuiThemeComputed; }) => { return { trigger: { maxWidth: fullWidth ? undefined : MIN_WIDTH, + border: theme.border.thin, + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, }, popoverContent: { width: calculateWidthFromEntries(dataViewsList, ['name', 'id'], { minWidth: MIN_WIDTH }), 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 d3625db555cda..842d57a1c5a0a 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 @@ -14,13 +14,10 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; -import { TextBasedLanguages } from '@kbn/esql-utils'; import { ChangeDataView } from './change_dataview'; import { DataViewSelector } from './data_view_selector'; import { dataViewMock, dataViewMockEsql } from './mocks/dataview'; import { DataViewPickerPropsExtended } from './data_view_picker'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; describe('DataView component', () => { const createMockWebStorage = () => ({ @@ -89,7 +86,6 @@ describe('DataView component', () => { 'data-test-subj': 'dataview-trigger', }, onChangeDataView: jest.fn(), - onTextLangQuerySubmit: jest.fn(), }; }); @@ -135,39 +131,6 @@ describe('DataView component', () => { expect(addDataViewSpy).toHaveBeenCalled(); }); - it('should render the text based languages panels if languages are given', async () => { - const component = mount( - wrapDataViewComponentInContext( - { - ...props, - textBasedLanguages: [TextBasedLanguages.ESQL], - textBasedLanguage: TextBasedLanguages.ESQL, - }, - false - ) - ); - findTestSubject(component, 'dataview-trigger').simulate('click'); - const text = component.find('[data-test-subj="select-text-based-language-panel"]'); - expect(text.length).not.toBe(0); - }); - - it('should cleanup the query is on text based mode and add new dataview', async () => { - const component = mount( - wrapDataViewComponentInContext( - { - ...props, - onDataViewCreated: jest.fn(), - textBasedLanguages: [TextBasedLanguages.ESQL], - textBasedLanguage: TextBasedLanguages.ESQL, - }, - false - ) - ); - findTestSubject(component, 'dataview-trigger').simulate('click'); - component.find('[data-test-subj="dataview-create-new"]').first().simulate('click'); - expect(props.onTextLangQuerySubmit).toHaveBeenCalled(); - }); - it('should properly handle ad hoc data views', async () => { const component = mount( wrapDataViewComponentInContext( @@ -233,44 +196,4 @@ describe('DataView component', () => { }, ]); }); - - describe('test based language switch warning icon', () => { - beforeAll(() => { - // Enzyme doesn't clean the DOM between tests, so we need to do it manually - document.body.innerHTML = ''; - }); - - it('should show text based language switch warning icon', () => { - render( - wrapDataViewComponentInContext( - { - ...props, - onDataViewCreated: jest.fn(), - textBasedLanguages: [TextBasedLanguages.ESQL], - textBasedLanguage: TextBasedLanguages.ESQL, - }, - false - ) - ); - userEvent.click(screen.getByTestId('dataview-trigger')); - expect(screen.queryByTestId('textBasedLang-warning')).toBeInTheDocument(); - }); - - it('should not show text based language switch warning icon when shouldShowTextBasedLanguageTransitionModal is false', () => { - render( - wrapDataViewComponentInContext( - { - ...props, - onDataViewCreated: jest.fn(), - textBasedLanguages: [TextBasedLanguages.ESQL], - textBasedLanguage: TextBasedLanguages.ESQL, - shouldShowTextBasedLanguageTransitionModal: false, - }, - false - ) - ); - userEvent.click(screen.getByTestId('dataview-trigger')); - expect(screen.queryByTestId('textBasedLang-warning')).not.toBeInTheDocument(); - }); - }); }); 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 4afa5f84997ee..cc5242d212872 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -7,13 +7,11 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiPopover, - EuiPanel, EuiHorizontalRule, - EuiButton, EuiContextMenuPanel, EuiContextMenuItem, useEuiTheme, @@ -24,37 +22,17 @@ import { EuiFlexGroup, EuiFlexItem, 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 { getInitialESQLQuery } from '@kbn/esql-utils'; +import { 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 { DataViewListItemEnhanced } from './dataview_list'; -import type { TextBasedLanguagesTransitionModalProps } from './text_languages_transition_modal'; import adhoc from './assets/adhoc.svg'; import { changeDataViewStyles } from './change_dataview.styles'; import { DataViewSelector } from './data_view_selector'; -// local storage key for the text based languages transition modal -const TEXT_LANG_TRANSITION_MODAL_KEY = 'data.textLangTransitionModal'; - -const Fallback = () => <div />; - -const LazyTextBasedLanguagesTransitionModal = React.lazy( - () => import('./text_languages_transition_modal') -); -export const TextBasedLanguagesTransitionModal = ( - props: TextBasedLanguagesTransitionModalProps -) => ( - <React.Suspense fallback={<Fallback />}> - <LazyTextBasedLanguagesTransitionModal {...props} /> - </React.Suspense> -); - const mapAdHocDataView = (adHocDataView: DataView): DataViewListItemEnhanced => { return { title: adHocDataView.title, @@ -75,11 +53,7 @@ export function ChangeDataView({ onDataViewCreated, trigger, selectableProps, - textBasedLanguages, - onSaveTextLanguageQuery, - onTextLangQuerySubmit, textBasedLanguage, - shouldShowTextBasedLanguageTransitionModal = true, isDisabled, onEditDataView, onCreateDefaultAdHocDataView, @@ -91,19 +65,15 @@ export function ChangeDataView({ const [isTextBasedLangSelected, setIsTextBasedLangSelected] = useState( Boolean(textBasedLanguage) ); - const [isTextLangTransitionModalVisible, setIsTextLangTransitionModalVisible] = useState(false); - const [selectedDataViewId, setSelectedDataViewId] = useState(currentDataViewId); const kibana = useKibana<IUnifiedSearchPluginServices>(); - const { application, data, storage, dataViews, dataViewEditor, appName, usageCollection } = - kibana.services; - const reportUiCounter = usageCollection?.reportUiCounter.bind(usageCollection, appName); + const { application, data, dataViews, dataViewEditor } = kibana.services; - const styles = changeDataViewStyles({ fullWidth: trigger.fullWidth, dataViewsList }); - - const [isTextLangTransitionModalDismissed, setIsTextLangTransitionModalDismissed] = useState(() => - Boolean(storage.get(TEXT_LANG_TRANSITION_MODAL_KEY)) - ); + const styles = changeDataViewStyles({ + fullWidth: trigger.fullWidth, + dataViewsList, + theme: euiTheme, + }); // Create a reusable id to ensure search input is the first focused item in the popover even though it's not the first item const searchListInputId = useGeneratedHtmlId({ prefix: 'dataviewPickerListSearchInput' }); @@ -112,14 +82,14 @@ export function ChangeDataView({ const fetchDataViews = async () => { const savedDataViewRefs: DataViewListItemEnhanced[] = savedDataViews ? savedDataViews - : await data.dataViews.getIdsWithTitle(); + : (await data.dataViews.getIdsWithTitle()) ?? []; const adHocDataViewRefs: DataViewListItemEnhanced[] = adHocDataViews?.map(mapAdHocDataView) ?? []; setDataViewsList(savedDataViewRefs.concat(adHocDataViewRefs)); }; fetchDataViews(); - }, [data, currentDataViewId, adHocDataViews, savedDataViews, isTextBasedLangSelected]); + }, [data, currentDataViewId, adHocDataViews, savedDataViews]); useEffect(() => { if (textBasedLanguage) { @@ -142,17 +112,16 @@ export function ChangeDataView({ const createTrigger = function () { const { label, title, 'data-test-subj': dataTestSubj, fullWidth, ...rest } = trigger; return ( - <EuiButton + <EuiButtonEmpty css={styles.trigger} data-test-subj={dataTestSubj} onClick={() => { setPopoverIsOpen(!isPopoverOpen); }} - color={isMissingCurrent ? 'danger' : 'primary'} + color={isMissingCurrent ? 'danger' : 'text'} iconSide="right" iconType="arrowDown" title={triggerLabel} - fullWidth={fullWidth} disabled={isDisabled} textProps={{ className: 'eui-textTruncate' }} {...rest} @@ -170,13 +139,13 @@ export function ChangeDataView({ )} {triggerLabel} </> - </EuiButton> + </EuiButtonEmpty> ); }; const getPanelItems = () => { const panelItems: EuiContextMenuPanelProps['items'] = []; - if (onAddField && !isTextBasedLangSelected) { + if (onAddField) { panelItems.push( <EuiContextMenuItem key="add" @@ -238,29 +207,6 @@ export function ChangeDataView({ > <EuiFlexItem grow={false}> <EuiFlexGroup alignItems="center" gutterSize="xs" responsive={false}> - <EuiFlexItem grow={false}> - {isTextBasedLangSelected && shouldShowTextBasedLanguageTransitionModal ? ( - <EuiToolTip - position="top" - content={i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning', - { - defaultMessage: - "Switching data views removes the current {textBasedLanguage} query. Save this search to ensure you don't lose work.", - values: { - textBasedLanguage: getLanguageDisplayName(textBasedLanguage), - }, - } - )} - > - <EuiIcon - type="warning" - color="warning" - data-test-subj="textBasedLang-warning" - /> - </EuiToolTip> - ) : null} - </EuiFlexItem> <EuiFlexItem grow={false}> <EuiText size="s"> <h5> @@ -277,16 +223,6 @@ export function ChangeDataView({ onClick={() => { setPopoverIsOpen(false); onDataViewCreated(); - // go to dataview mode - if (isTextBasedLangSelected) { - setIsTextBasedLangSelected(false); - // clean up the Text based language query - onTextLangQuerySubmit?.({ - language: 'kuery', - query: '', - }); - setTriggerLabel(trigger.label); - } }} size="xs" iconType="plusInCircleFilled" @@ -305,160 +241,60 @@ export function ChangeDataView({ searchListInputId={searchListInputId} dataViewsList={dataViewsList} selectableProps={selectableProps} - isTextBasedLangSelected={isTextBasedLangSelected} setPopoverIsOpen={setPopoverIsOpen} onChangeDataView={async (newId) => { - setSelectedDataViewId(newId); setPopoverIsOpen(false); - - if (isTextBasedLangSelected) { - const showTransitionModal = - !isTextLangTransitionModalDismissed && shouldShowTextBasedLanguageTransitionModal; - - if (showTransitionModal) { - setIsTextLangTransitionModalVisible(true); - } else { - setIsTextBasedLangSelected(false); - // clean up the Text based language query - onTextLangQuerySubmit?.({ - language: 'kuery', - query: '', - }); - onChangeDataView(newId); - setTriggerLabel(trigger.label); - } - } else { - onChangeDataView(newId); - } + onChangeDataView(newId); }} onCreateDefaultAdHocDataView={onCreateDefaultAdHocDataView} /> </React.Fragment> ); - if (textBasedLanguages?.length) { - panelItems.push( - <EuiHorizontalRule margin="none" key="textbasedLanguages-divider" />, - <EuiPanel color="transparent" paddingSize="none" key="try-esql"> - <EuiButton - css={css` - border-top-right-radius: unset; - border-top-left-radius: unset; - `} - color="success" - size="s" - fullWidth - onClick={() => onTextBasedSubmit({ esql: getInitialESQLQuery(trigger.title!) })} - data-test-subj="select-text-based-language-panel" - contentProps={{ - css: { - justifyContent: 'flex-start', - paddingLeft: '26px', - }, - }} - > - {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTryLabel', { - defaultMessage: 'Language: ES|QL', - })} - </EuiButton> - </EuiPanel> - ); - } - return panelItems; }; - let modal; - - const onTransitionModalDismiss = useCallback(() => { - storage.set(TEXT_LANG_TRANSITION_MODAL_KEY, true); - 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); - setIsTextBasedLangSelected(false); - // clean up the Text based language query - onTextLangQuerySubmit?.({ - language: 'kuery', - query: '', - }); - if (selectedDataViewId) { - onChangeDataView(selectedDataViewId); - } - setTriggerLabel(trigger.label); - if (shouldDismissModal) { - onTransitionModalDismiss(); - } - }, - [ - onChangeDataView, - onTextLangQuerySubmit, - onTransitionModalDismiss, - selectedDataViewId, - trigger.label, - ] - ); - - const onModalClose = useCallback( - (shouldDismissModal: boolean, needsSave?: boolean) => { - if (Boolean(needsSave)) { - setIsTextLangTransitionModalVisible(false); - onSaveTextLanguageQuery?.({ - onSave: () => { - cleanup(shouldDismissModal); - }, - onCancel: () => { - setIsTextLangTransitionModalVisible(false); - }, - }); - } else { - cleanup(shouldDismissModal); - } - }, - [cleanup, onSaveTextLanguageQuery] - ); - - if (isTextLangTransitionModalVisible && !isTextLangTransitionModalDismissed) { - modal = ( - <TextBasedLanguagesTransitionModal - closeModal={onModalClose} - setIsTextLangTransitionModalVisible={setIsTextLangTransitionModalVisible} - textBasedLanguage={textBasedLanguage} - /> - ); - } - return ( - <> - <EuiPopover - panelClassName="changeDataViewPopover" - button={createTrigger()} - panelProps={{ - ['data-test-subj']: 'changeDataViewPopover', - }} - isOpen={isPopoverOpen} - closePopover={() => setPopoverIsOpen(false)} - panelPaddingSize="none" - initialFocus={!isTextBasedLangSelected ? `#${searchListInputId}` : undefined} - display="block" - buffer={8} - > - <div css={styles.popoverContent}> - <EuiContextMenuPanel size="s" items={getPanelItems()} /> - </div> - </EuiPopover> - {modal} - </> + <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> + {!isTextBasedLangSelected && ( + <> + <EuiFlexItem grow={false}> + <EuiFlexGroup alignItems="center" gutterSize="none" responsive={false}> + <EuiFlexItem + grow={false} + css={css` + padding: 11px; + border-radius: ${euiTheme.border.radius.small} 0 0 ${euiTheme.border.radius.small}; + background-color: ${euiTheme.colors.lightestShade}; + border: ${euiTheme.border.thin}; + border-right: 0; + `} + > + {i18n.translate('unifiedSearch.query.queryBar.esqlMenu.switcherLabelTitle', { + defaultMessage: 'Data view', + })} + </EuiFlexItem> + <EuiPopover + panelClassName="changeDataViewPopover" + button={createTrigger()} + panelProps={{ + ['data-test-subj']: 'changeDataViewPopover', + }} + isOpen={isPopoverOpen} + closePopover={() => setPopoverIsOpen(false)} + panelPaddingSize="none" + initialFocus={`#${searchListInputId}`} + display="block" + buffer={8} + > + <div css={styles.popoverContent}> + <EuiContextMenuPanel size="s" items={getPanelItems()} /> + </div> + </EuiPopover> + </EuiFlexGroup> + </EuiFlexItem> + </> + )} + </EuiFlexGroup> ); } diff --git a/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx b/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx index 79b541f69f1ea..ab13f5c1a257b 100644 --- a/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx +++ b/src/plugins/unified_search/public/dataview_picker/data_view_picker.tsx @@ -9,7 +9,6 @@ import React from 'react'; import type { EuiButtonProps, EuiSelectableProps } from '@elastic/eui'; import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public'; -import type { AggregateQuery, Query } from '@kbn/es-query'; import { TextBasedLanguages } from '@kbn/esql-utils'; import { ChangeDataView } from './change_dataview'; @@ -18,11 +17,6 @@ export type ChangeDataViewTriggerProps = EuiButtonProps & { title?: string; }; -export interface OnSaveTextLanguageQueryProps { - onSave: () => void; - onCancel: () => void; -} - /** @public */ export interface DataViewPickerProps { /** @@ -76,16 +70,6 @@ export interface DataViewPickerProps { * will be available. */ textBasedLanguages?: TextBasedLanguages[]; - /** - * Callback that is called when the user clicks the Save and switch transition modal button - */ - onSaveTextLanguageQuery?: ({ onSave, onCancel }: OnSaveTextLanguageQueryProps) => void; - /** - * Determines if the text based language transition - * modal should be shown when switching data views - */ - shouldShowTextBasedLanguageTransitionModal?: boolean; - /** * Makes the picker disabled by disabling the popover trigger */ @@ -93,10 +77,6 @@ export interface DataViewPickerProps { } export interface DataViewPickerPropsExtended extends DataViewPickerProps { - /** - * Callback that is called when the user clicks the submit button - */ - onTextLangQuerySubmit?: (query?: Query | AggregateQuery) => void; /** * Text based language that is currently selected; depends on the query */ @@ -115,10 +95,7 @@ export const DataViewPicker = ({ trigger, selectableProps, textBasedLanguages, - onSaveTextLanguageQuery, - onTextLangQuerySubmit, textBasedLanguage, - shouldShowTextBasedLanguageTransitionModal, onCreateDefaultAdHocDataView, isDisabled, }: DataViewPickerPropsExtended) => { @@ -136,10 +113,7 @@ export const DataViewPicker = ({ savedDataViews={savedDataViews} selectableProps={selectableProps} textBasedLanguages={textBasedLanguages} - onSaveTextLanguageQuery={onSaveTextLanguageQuery} - onTextLangQuerySubmit={onTextLangQuerySubmit} textBasedLanguage={textBasedLanguage} - shouldShowTextBasedLanguageTransitionModal={shouldShowTextBasedLanguageTransitionModal} isDisabled={isDisabled} /> ); diff --git a/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx b/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx index eb70f8b0bc971..a4e9d9aa07784 100644 --- a/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx +++ b/src/plugins/unified_search/public/dataview_picker/data_view_selector.tsx @@ -19,7 +19,6 @@ export interface DataViewSelectorProps { searchListInputId?: string; dataViewsList: DataViewListItem[]; selectableProps?: EuiSelectableProps; - isTextBasedLangSelected: boolean; setPopoverIsOpen: (isOpen: boolean) => void; onChangeDataView: (dataViewId: string) => void; onCreateDefaultAdHocDataView?: (dataViewSpec: DataViewSpec) => void; @@ -30,7 +29,6 @@ export const DataViewSelector = ({ searchListInputId, dataViewsList, selectableProps, - isTextBasedLangSelected, setPopoverIsOpen, onChangeDataView, onCreateDefaultAdHocDataView, @@ -83,7 +81,6 @@ export const DataViewSelector = ({ }, }} searchListInputId={searchListInputId} - isTextBasedLangSelected={isTextBasedLangSelected} /> <ExploreMatchingButton noDataViewMatches={noDataViewMatches} diff --git a/src/plugins/unified_search/public/dataview_picker/dataview_list.test.tsx b/src/plugins/unified_search/public/dataview_picker/dataview_list.test.tsx index 11022e381d084..b8b127fcdd530 100644 --- a/src/plugins/unified_search/public/dataview_picker/dataview_list.test.tsx +++ b/src/plugins/unified_search/public/dataview_picker/dataview_list.test.tsx @@ -54,7 +54,6 @@ describe('DataView list component', () => { currentDataViewId: 'dataview-1', onChangeDataView: changeDataViewSpy, dataViewsList: list, - isTextBasedLangSelected: false, }; }); @@ -75,17 +74,8 @@ describe('DataView list component', () => { ]); }); - it('should render a warning icon if a text based language is selected', () => { - const component = shallow(<DataViewsList {...props} isTextBasedLangSelected />); - - expect(getDataViewPickerOptions(component)!.map((option: any) => option.append)).not.toBeNull(); - }); - describe('ad hoc data views', () => { - const runAdHocDataViewTest = ( - esqlMode: boolean, - esqlDataViews: DataViewListItemEnhanced[] = [] - ) => { + const runAdHocDataViewTest = (esqlDataViews: DataViewListItemEnhanced[] = []) => { const dataViewList = [ ...list, { @@ -95,9 +85,7 @@ describe('DataView list component', () => { }, ...esqlDataViews, ]; - const component = shallow( - <DataViewsList {...props} dataViewsList={dataViewList} isTextBasedLangSelected={esqlMode} /> - ); + const component = shallow(<DataViewsList {...props} dataViewsList={dataViewList} />); expect(getDataViewPickerOptions(component)!.map((option: any) => option.label)).toEqual([ 'dataview-1', 'dataview-2', @@ -114,20 +102,12 @@ describe('DataView list component', () => { }, ]; - it('should show ad hoc data views for text based mode', () => { - runAdHocDataViewTest(true); - }); - it('should show ad hoc data views for data view mode', () => { - runAdHocDataViewTest(false); - }); - - it('should not show ES|QL ad hoc data views for text based mode', () => { - runAdHocDataViewTest(true, esqlDataViews); + runAdHocDataViewTest(); }); it('should not show ES|QL ad hoc data views for data view mode', () => { - runAdHocDataViewTest(false, esqlDataViews); + runAdHocDataViewTest(esqlDataViews); }); }); }); diff --git a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx index 8a08c95f979d7..003f8181bfefb 100644 --- a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx +++ b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx @@ -66,7 +66,6 @@ export interface DataViewListItemEnhanced extends DataViewListItem { export interface DataViewsListProps { dataViewsList: DataViewListItemEnhanced[]; onChangeDataView: (newId: string) => void; - isTextBasedLangSelected?: boolean; currentDataViewId?: string; selectableProps?: EuiSelectableProps; searchListInputId?: string; @@ -75,7 +74,6 @@ export interface DataViewsListProps { export function DataViewsList({ dataViewsList, onChangeDataView, - isTextBasedLangSelected, currentDataViewId, selectableProps, searchListInputId, @@ -135,7 +133,7 @@ export function DataViewsList({ key: id, label: name ? name : title, value: id, - checked: id === currentDataViewId && !Boolean(isTextBasedLangSelected) ? 'on' : undefined, + checked: id === currentDataViewId ? 'on' : undefined, append: isAdhoc ? ( <EuiBadge color="hollow" data-test-subj={`dataViewItemTempBadge-${name}`}> {strings.editorAndPopover.adhoc.getTemporaryDataviewLabel()} diff --git a/src/plugins/unified_search/public/dataview_picker/index.tsx b/src/plugins/unified_search/public/dataview_picker/index.tsx index 895f48384ab18..8a0870d872653 100644 --- a/src/plugins/unified_search/public/dataview_picker/index.tsx +++ b/src/plugins/unified_search/public/dataview_picker/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { withSuspense } from '@kbn/shared-ux-utility'; -export type { DataViewPickerProps, OnSaveTextLanguageQueryProps } from './data_view_picker'; +export type { DataViewPickerProps } from './data_view_picker'; /** * The Lazily-loaded `DataViewsList` component. Consumers should use `React.Suspense` or 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 deleted file mode 100644 index 34d40e7ad3841..0000000000000 --- a/src/plugins/unified_search/public/dataview_picker/text_languages_transition_modal.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 and the Server Side Public License, v 1; you may not 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, useCallback } from 'react'; -import { i18n } from '@kbn/i18n'; -import { getLanguageDisplayName } from '@kbn/es-query'; -import { - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiButton, - EuiText, - EuiCheckbox, - EuiFlexItem, - EuiFlexGroup, -} from '@elastic/eui'; - -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 = getLanguageDisplayName(textBasedLanguage); - return ( - <EuiModal - onClose={() => setIsTextLangTransitionModalVisible(false)} - style={{ width: 700 }} - data-test-subj="unifiedSearch_switch_modal" - > - <EuiModalHeader> - <EuiModalHeaderTitle> - {i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle', - { - defaultMessage: 'Your query will be removed', - } - )} - </EuiModalHeaderTitle> - </EuiModalHeader> - - <EuiModalBody> - <EuiText size="m"> - {i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody', - { - defaultMessage: - "Switching data views removes the current {language} query. Save this search to ensure you don't lose work.", - values: { language }, - } - )} - </EuiText> - </EuiModalBody> - - <EuiModalFooter> - <EuiFlexGroup alignItems="center" justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiCheckbox - id="dismiss-text-based-languages-transition-modal" - label={i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton', - { - defaultMessage: "Don't show this warning again", - } - )} - checked={dismissModalChecked} - onChange={onTransitionModalDismiss} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiFlexGroup gutterSize="m"> - <EuiFlexItem grow={false}> - <EuiButton - onClick={() => closeModal(dismissModalChecked)} - color="warning" - iconType="merge" - data-test-subj="unifiedSearch_switch_noSave" - > - {i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton', - { - defaultMessage: 'Switch without saving', - } - )} - </EuiButton> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButton - onClick={() => closeModal(dismissModalChecked, true)} - fill - color="success" - iconType="save" - data-test-subj="unifiedSearch_switch_andSave" - > - {i18n.translate( - 'unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton', - { - defaultMessage: 'Save and switch', - } - )} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> - </EuiModalFooter> - </EuiModal> - ); -} 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 d8517eedba4ed..3ca57bc70fd32 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 @@ -13,7 +13,10 @@ import { Required } from '@kbn/utility-types'; import { EuiComboBox, EuiComboBoxProps } from '@elastic/eui'; import { calculateWidthFromEntries } from '@kbn/calculate-width-from-char-count'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import { MIDDLE_TRUNCATION_PROPS } from '../filter_bar/filter_editor/lib/helpers'; +import { + MIDDLE_TRUNCATION_PROPS, + SINGLE_SELECTION_AS_TEXT_PROPS, +} from '../filter_bar/filter_editor/lib/helpers'; export type IndexPatternSelectProps = Required< Omit<EuiComboBoxProps<any>, 'onSearchChange' | 'options' | 'selectedOptions' | 'onChange'>, @@ -117,7 +120,7 @@ export default class IndexPatternSelect extends Component<IndexPatternSelectInte this.setState({ isLoading: false, - options, + options: options.sort((a, b) => a.label.localeCompare(b.label)), }); if (this.props.onNoIndexPatterns && searchValue === '' && options.length === 0) { @@ -155,7 +158,7 @@ export default class IndexPatternSelect extends Component<IndexPatternSelectInte <EuiComboBox {...rest} placeholder={placeholder} - singleSelection={true} + singleSelection={SINGLE_SELECTION_AS_TEXT_PROPS} isLoading={this.state.isLoading || this.props.isLoading} onSearchChange={this.fetchOptions} options={this.state.options} diff --git a/src/plugins/unified_search/public/mocks/text_based_languages_editor.tsx b/src/plugins/unified_search/public/mocks/text_based_languages_editor.tsx index 71f2547c39734..95bcfb5770e37 100644 --- a/src/plugins/unified_search/public/mocks/text_based_languages_editor.tsx +++ b/src/plugins/unified_search/public/mocks/text_based_languages_editor.tsx @@ -22,7 +22,7 @@ function createEditor() { uiSettings: { get: () => {} }, }} > - <TextBasedLanguagesEditor {...props} isDarkMode={false} /> + <TextBasedLanguagesEditor {...props} /> </KibanaContextProvider> ); }; diff --git a/src/plugins/unified_search/public/query_string_input/query_bar.scss b/src/plugins/unified_search/public/query_string_input/query_bar.scss index 2ecf27416a907..99e93d876ee50 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar.scss +++ b/src/plugins/unified_search/public/query_string_input/query_bar.scss @@ -3,7 +3,3 @@ background-image: euiFormControlGradient($euiColorDanger); } } -// increase the section height to avoid vertical scrolling -.euiQuickSelectPopover__section { - max-height: 142px; -} diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index 2a779f8decd4b..229296e92f192 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -8,6 +8,7 @@ import dateMath from '@kbn/datemath'; import classNames from 'classnames'; +import { css } from '@emotion/react'; import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import deepEqual from 'fast-deep-equal'; import useObservable from 'react-use/lib/useObservable'; @@ -35,6 +36,7 @@ import { EuiToolTip, EuiButton, EuiButtonIcon, + useEuiTheme, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TimeHistoryContract, getQueryLog } from '@kbn/data-plugin/public'; @@ -47,11 +49,7 @@ import QueryStringInputUI from './query_string_input'; import { NoDataPopover } from './no_data_popover'; import { shallowEqual } from '../utils/shallow_equal'; import { AddFilterPopover } from './add_filter_popover'; -import { - DataViewPicker, - DataViewPickerProps, - OnSaveTextLanguageQueryProps, -} from '../dataview_picker'; +import { DataViewPicker, DataViewPickerProps } from '../dataview_picker'; import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group'; import type { @@ -167,7 +165,6 @@ export interface QueryBarTopRowProps<QT extends Query | AggregateQuery = Query> dataViewPickerComponentProps?: DataViewPickerProps; textBasedLanguageModeErrors?: Error[]; textBasedLanguageModeWarning?: string; - onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void; filterBar?: React.ReactNode; showDatePickerAsBadge?: boolean; showSubmitButton?: boolean; @@ -185,6 +182,7 @@ export interface QueryBarTopRowProps<QT extends Query | AggregateQuery = Query> onTextLangQueryChange: (query: AggregateQuery) => void; submitOnBlur?: boolean; renderQueryInputAppend?: () => React.ReactNode; + disableExternalPadding?: boolean; } export const SharingMetaFields = React.memo(function SharingMetaFields({ @@ -232,7 +230,6 @@ export const QueryBarTopRow = React.memo( ) { const isMobile = useIsWithinBreakpoints(['xs', 's']); const [isXXLarge, setIsXXLarge] = useState<boolean>(false); - const [codeEditorIsExpanded, setCodeEditorIsExpanded] = useState<boolean>(false); const submitButtonStyle: QueryBarTopRowProps['submitButtonStyle'] = props.submitButtonStyle ?? 'auto'; const submitButtonIconOnly = @@ -466,7 +463,10 @@ export const QueryBarTopRow = React.memo( } function shouldShowDatePickerAsBadge(): boolean { - return Boolean(props.showDatePickerAsBadge) && !shouldRenderQueryInput(); + return ( + (Boolean(props.showDatePickerAsBadge) && !shouldRenderQueryInput()) || + Boolean(isQueryLangSelected && props.query && isOfAggregateQueryType(props.query)) + ); } function renderDatePicker() { @@ -632,9 +632,7 @@ export const QueryBarTopRow = React.memo( <DataViewPicker {...props.dataViewPickerComponentProps} trigger={{ fullWidth: isMobile, ...props.dataViewPickerComponentProps.trigger }} - onTextLangQuerySubmit={props.onTextLangQuerySubmit} textBasedLanguage={textBasedLanguage} - onSaveTextLanguageQuery={props.onTextBasedSavedAndExit} isDisabled={props.isDisabled} /> </EuiFlexItem> @@ -734,8 +732,6 @@ export const QueryBarTopRow = React.memo( <TextBasedLangEditor query={props.query} onTextLangQueryChange={props.onTextLangQueryChange} - expandCodeEditor={(status: boolean) => setCodeEditorIsExpanded(status)} - isCodeEditorExpanded={codeEditorIsExpanded} errors={props.textBasedLanguageModeErrors} warning={props.textBasedLanguageModeWarning} detectedTimestamp={detectedTimestamp} @@ -753,7 +749,7 @@ export const QueryBarTopRow = React.memo( ) ); } - + const { euiTheme } = useEuiTheme(); const isScreenshotMode = props.isScreenshotMode === true; return ( @@ -770,6 +766,11 @@ export const QueryBarTopRow = React.memo( direction={isMobile && !shouldShowDatePickerAsBadge() ? 'column' : 'row'} responsive={false} gutterSize="s" + css={css` + padding: ${isQueryLangSelected && !props.disableExternalPadding + ? euiTheme.size.s + : 0}; + `} justifyContent={shouldShowDatePickerAsBadge() ? 'flexStart' : 'flexEnd'} wrap > @@ -778,18 +779,14 @@ export const QueryBarTopRow = React.memo( grow={!shouldShowDatePickerAsBadge()} style={{ minWidth: shouldShowDatePickerAsBadge() ? 'auto' : 320, maxWidth: '100%' }} > - {!isQueryLangSelected - ? renderQueryInput() - : !codeEditorIsExpanded - ? renderTextLangEditor() - : null} + {!isQueryLangSelected ? renderQueryInput() : null} </EuiFlexItem> {props.renderQueryInputAppend?.()} {shouldShowDatePickerAsBadge() && props.filterBar} {renderUpdateButton()} </EuiFlexGroup> {!shouldShowDatePickerAsBadge() && props.filterBar} - {codeEditorIsExpanded && renderTextLangEditor()} + {renderTextLangEditor()} </> )} </> 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 9415f09515fbc..38e3b11ae15e7 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 @@ -261,7 +261,6 @@ export function createSearchBar({ dataViewPickerComponentProps={props.dataViewPickerComponentProps} textBasedLanguageModeErrors={props.textBasedLanguageModeErrors} textBasedLanguageModeWarning={props.textBasedLanguageModeWarning} - onTextBasedSavedAndExit={props.onTextBasedSavedAndExit} displayStyle={props.displayStyle} isScreenshotMode={isScreenshotMode} dataTestSubj={props.dataTestSubj} diff --git a/src/plugins/unified_search/public/search_bar/search_bar.styles.ts b/src/plugins/unified_search/public/search_bar/search_bar.styles.ts index a0c6a4d95aaf1..29d48a388cadd 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.styles.ts +++ b/src/plugins/unified_search/public/search_bar/search_bar.styles.ts @@ -9,10 +9,10 @@ import { UseEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; -export const searchBarStyles = ({ euiTheme }: UseEuiTheme) => { +export const searchBarStyles = ({ euiTheme }: UseEuiTheme, isESQLQuery: boolean) => { return { uniSearchBar: css` - padding: ${euiTheme.size.s}; + padding: ${isESQLQuery ? 0 : euiTheme.size.s}; position: relative; `, detached: css` @@ -21,6 +21,10 @@ export const searchBarStyles = ({ euiTheme }: UseEuiTheme) => { inPage: css` padding: 0; `, + withBorders: css` + border: ${euiTheme.border.thin}; + border-bottom: none; + `, hidden: css` display: none; `, 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 a2b8ade82b3f7..2d7ce30423fc3 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -16,7 +16,14 @@ import { get, isEqual } from 'lodash'; import memoizeOne from 'memoize-one'; import { METRIC_TYPE } from '@kbn/analytics'; -import { Query, Filter, TimeRange, AggregateQuery, isOfQueryType } from '@kbn/es-query'; +import { + type Query, + type Filter, + type TimeRange, + type AggregateQuery, + isOfQueryType, + isOfAggregateQueryType, +} from '@kbn/es-query'; import { withKibana, KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; import type { TimeHistoryContract, @@ -32,7 +39,7 @@ import type { IUnifiedSearchPluginServices } from '../types'; import { SavedQueryMeta, SaveQueryForm } from '../saved_query_form'; import { SavedQueryManagementList } from '../saved_query_management'; import { QueryBarMenu, QueryBarMenuProps } from '../query_string_input/query_bar_menu'; -import type { DataViewPickerProps, OnSaveTextLanguageQueryProps } from '../dataview_picker'; +import type { DataViewPickerProps } from '../dataview_picker'; import QueryBarTopRow, { QueryBarTopRowProps } from '../query_string_input/query_bar_top_row'; import { FilterBar, FilterItems } from '../filter_bar'; import type { @@ -108,13 +115,12 @@ export interface SearchBarOwnProps<QT extends AggregateQuery | Query = Query> { disableQueryLanguageSwitcher?: boolean; // defines padding and border; use 'inPage' to avoid any padding or border; // use 'detached' if the searchBar appears at the very top of the view, without any wrapper - displayStyle?: 'inPage' | 'detached'; + displayStyle?: 'inPage' | 'detached' | 'withBorders'; // super update button background fill control fillSubmitButton?: boolean; dataViewPickerComponentProps?: DataViewPickerProps; textBasedLanguageModeErrors?: Error[]; textBasedLanguageModeWarning?: string; - onTextBasedSavedAndExit?: ({ onSave }: OnSaveTextLanguageQueryProps) => void; showSubmitButton?: boolean; submitButtonStyle?: QueryBarTopRowProps['submitButtonStyle']; // defines size of suggestions query popover @@ -480,9 +486,10 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C } public render() { - const { theme } = this.props; + const { theme, query } = this.props; + const isESQLQuery = isOfAggregateQueryType(query); const isScreenshotMode = this.props.isScreenshotMode === true; - const styles = searchBarStyles(theme); + const styles = searchBarStyles(theme, isESQLQuery); const cssStyles = [ styles.uniSearchBar, this.props.displayStyle && styles[this.props.displayStyle], @@ -640,7 +647,6 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C dataViewPickerComponentProps={this.props.dataViewPickerComponentProps} textBasedLanguageModeErrors={this.props.textBasedLanguageModeErrors} textBasedLanguageModeWarning={this.props.textBasedLanguageModeWarning} - onTextBasedSavedAndExit={this.props.onTextBasedSavedAndExit} showDatePickerAsBadge={this.shouldShowDatePickerAsBadge()} filterBar={filterBar} suggestionsSize={this.props.suggestionsSize} @@ -650,6 +656,7 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C submitOnBlur={this.props.submitOnBlur} suggestionsAbstraction={this.props.suggestionsAbstraction} renderQueryInputAppend={this.props.renderQueryInputAppend} + disableExternalPadding={this.props.displayStyle === 'withBorders'} /> </div> ); diff --git a/src/plugins/usage_collection/common/types/usage_counters/v1.ts b/src/plugins/usage_collection/common/types/usage_counters/v1.ts index c18af503d06e0..66fe379148e23 100644 --- a/src/plugins/usage_collection/common/types/usage_counters/v1.ts +++ b/src/plugins/usage_collection/common/types/usage_counters/v1.ts @@ -8,12 +8,21 @@ export type CounterEventSource = 'server' | 'ui'; -export interface CounterMetric { +export interface AbstractCounter { + /** The domainId used to create the Counter API */ domainId: string; - namespace?: string; + /** The name of the counter */ counterName: string; + /** The type of counter (defaults to 'count') */ counterType: string; + /** The source of this counter: 'server' | 'ui' */ source: CounterEventSource; + /** Namespace associated to this counter */ + namespace?: string; +} + +export interface CounterMetric extends AbstractCounter { + /** Amount of units to increment this counter */ incrementBy: number; } diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts index d1b06e63b3b95..360c549d6036e 100644 --- a/src/plugins/usage_collection/server/collector/collector_set.ts +++ b/src/plugins/usage_collection/server/collector/collector_set.ts @@ -16,8 +16,8 @@ import type { ExecutionContextSetup, } from '@kbn/core/server'; import { Collector } from './collector'; -import type { ICollector, CollectorOptions, CollectorFetchContext } from './types'; -import { UsageCollector, UsageCollectorOptions } from './usage_collector'; +import type { ICollector, CollectorOptions, CollectorFetchContext, ICollectorSet } from './types'; +import { UsageCollector, type UsageCollectorOptions } from './usage_collector'; import { DEFAULT_MAXIMUM_WAIT_TIME_FOR_ALL_COLLECTORS_IN_S } from '../../common/constants'; import { createPerformanceObsHook, perfTimerify } from './measure_duration'; import { usageCollectorsStatsCollector } from './collector_stats'; @@ -44,7 +44,7 @@ export interface CollectorSetConfig { collectors?: AnyCollector[]; } -export class CollectorSet { +export class CollectorSet implements ICollectorSet { private readonly logger: Logger; private readonly executionContext: ExecutionContextSetup; private readonly maximumWaitTimeForAllCollectorsInS: number; diff --git a/src/plugins/usage_collection/server/collector/collector_stats/usage_collector_stats_collector.ts b/src/plugins/usage_collection/server/collector/collector_stats/usage_collector_stats_collector.ts index 4bf7754e07018..25dabbeb0aaa4 100644 --- a/src/plugins/usage_collection/server/collector/collector_stats/usage_collector_stats_collector.ts +++ b/src/plugins/usage_collection/server/collector/collector_stats/usage_collector_stats_collector.ts @@ -8,7 +8,7 @@ import { sumBy } from 'lodash'; import { collectorsStatsSchema } from './schema'; -import type { CollectorSet } from '../collector_set'; +import type { ICollectorSet } from '../types'; export interface CollectorsStats { not_ready: { count: number; names: string[] }; @@ -35,7 +35,7 @@ export interface CollectorsStatsCollectorParams { } export const usageCollectorsStatsCollector = ( - usageCollection: Pick<CollectorSet, 'makeUsageCollector'>, + usageCollection: Pick<ICollectorSet, 'makeUsageCollector'>, { nonReadyCollectorTypes, timedOutCollectorsTypes, diff --git a/src/plugins/usage_collection/server/collector/index.ts b/src/plugins/usage_collection/server/collector/index.ts index e284844b34c34..a381da018c0f6 100644 --- a/src/plugins/usage_collection/server/collector/index.ts +++ b/src/plugins/usage_collection/server/collector/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +export type { ICollectorSet } from './types'; export { CollectorSet } from './collector_set'; export type { AllowedSchemaTypes, diff --git a/src/plugins/usage_collection/server/collector/types.ts b/src/plugins/usage_collection/server/collector/types.ts index 9c1802348a113..7d6257ba8a7ac 100644 --- a/src/plugins/usage_collection/server/collector/types.ts +++ b/src/plugins/usage_collection/server/collector/types.ts @@ -8,7 +8,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract, Logger } from '@kbn/core/server'; -import type { PossibleSchemaTypes, SchemaMetaOptional } from '@kbn/ebt/client'; +import type { PossibleSchemaTypes, SchemaMetaOptional } from '@elastic/ebt/client'; export type { AllowedSchemaTypes, @@ -16,7 +16,66 @@ export type { AllowedSchemaBooleanTypes, AllowedSchemaNumberTypes, PossibleSchemaTypes, -} from '@kbn/ebt/client'; +} from '@elastic/ebt/client'; + +import type { Collector, UsageCollectorOptions } from '.'; + +/** + * Interface to register and manage Usage Collectors through a CollectorSet + */ +export interface ICollectorSet { + /** + * Creates a usage collector to collect plugin telemetry data. + * registerCollector must be called to connect the created collector with the service. + */ + makeUsageCollector: <TFetchReturn, ExtraOptions extends object = {}>( + options: UsageCollectorOptions<TFetchReturn, ExtraOptions> + ) => Collector<TFetchReturn, ExtraOptions>; + /** + * Register a usage collector or a stats collector. + * Used to connect the created collector to telemetry. + */ + registerCollector: <TFetchReturn, ExtraOptions extends object>( + collector: Collector<TFetchReturn, ExtraOptions> + ) => void; + /** + * Returns a usage collector by type + */ + getCollectorByType: <TFetchReturn, ExtraOptions extends object>( + type: string + ) => Collector<TFetchReturn, ExtraOptions> | undefined; + /** + * Fetches the collection from all the registered collectors + * @internal: telemetry use + */ + bulkFetch: <TFetchReturn, ExtraOptions extends object>( + esClient: ElasticsearchClient, + soClient: SavedObjectsClientContract, + collectors?: Map<string, Collector<TFetchReturn, ExtraOptions>> + ) => Promise<Array<{ type: string; result: unknown }>>; + /** + * Converts an array of fetched stats results into key/object + * @internal: telemetry use + */ + toObject: <Result extends Record<string, unknown>, T = unknown>( + statsData?: Array<{ type: string; result: T }> + ) => Result; + /** + * Rename fields to use API conventions + * @internal: monitoring use + */ + toApiFieldNames: ( + apiData: Record<string, unknown> | unknown[] + ) => Record<string, unknown> | unknown[]; + /** + * Creates a stats collector to collect plugin telemetry data. + * registerCollector must be called to connect the created collector with the service. + * @internal: telemetry and monitoring use + */ + makeStatsCollector: <TFetchReturn, ExtraOptions extends object = {}>( + options: CollectorOptions<TFetchReturn, ExtraOptions> + ) => Collector<TFetchReturn, ExtraOptions>; +} /** * Helper to find out whether to keep recursively looking or if we are on an end value diff --git a/src/plugins/usage_collection/server/index.ts b/src/plugins/usage_collection/server/index.ts index fb2894d7f3903..bdac41e9c04da 100644 --- a/src/plugins/usage_collection/server/index.ts +++ b/src/plugins/usage_collection/server/index.ts @@ -10,6 +10,7 @@ import { PluginInitializerContext } from '@kbn/core/server'; export type { Collector, + ICollectorSet, AllowedSchemaTypes, MakeSchemaFrom, CollectorOptions, diff --git a/src/plugins/usage_collection/server/mocks.ts b/src/plugins/usage_collection/server/mocks.ts index 36f30357f6207..4e917cd72e637 100644 --- a/src/plugins/usage_collection/server/mocks.ts +++ b/src/plugins/usage_collection/server/mocks.ts @@ -13,9 +13,9 @@ import { savedObjectsClientMock, } from '@kbn/core/server/mocks'; -import { CollectorOptions, CollectorSet } from './collector'; +import { type CollectorOptions, CollectorSet } from './collector'; import { Collector } from './collector/collector'; -import { UsageCollectionSetup, CollectorFetchContext } from '.'; +import type { UsageCollectionSetup, CollectorFetchContext } from '.'; import { usageCountersServiceMock } from './usage_counters/usage_counters_service.mock'; export type { CollectorOptions }; export { Collector }; diff --git a/src/plugins/usage_collection/server/plugin.ts b/src/plugins/usage_collection/server/plugin.ts index ca551c129d84e..1b191ec687139 100644 --- a/src/plugins/usage_collection/server/plugin.ts +++ b/src/plugins/usage_collection/server/plugin.ts @@ -13,81 +13,21 @@ import type { CoreStart, ISavedObjectsRepository, Plugin, - ElasticsearchClient, - SavedObjectsClientContract, } from '@kbn/core/server'; +import { setupRoutes } from './routes'; import type { ConfigType } from './config'; +import type { ICollectorSet } from './collector/types'; +import type { UsageCountersServiceSetup, UsageCountersServiceStart } from './usage_counters/types'; import { CollectorSet } from './collector'; -import type { Collector, CollectorOptions, UsageCollectorOptions } from './collector'; -import { setupRoutes } from './routes'; - import { UsageCountersService } from './usage_counters'; -import type { UsageCounter } from './usage_counters'; -/** Server's setup APIs exposed by the UsageCollection Service **/ -export interface UsageCollectionSetup { - /** - * Creates and registers a usage counter to collect daily aggregated plugin counter events - */ - createUsageCounter: (type: string) => UsageCounter; - /** - * Returns a usage counter by type - */ - getUsageCounterByDomainId: (type: string) => UsageCounter | undefined; - /** - * Creates a usage collector to collect plugin telemetry data. - * registerCollector must be called to connect the created collector with the service. - */ - makeUsageCollector: <TFetchReturn, ExtraOptions extends object = {}>( - options: UsageCollectorOptions<TFetchReturn, ExtraOptions> - ) => Collector<TFetchReturn, ExtraOptions>; - /** - * Register a usage collector or a stats collector. - * Used to connect the created collector to telemetry. - */ - registerCollector: <TFetchReturn, ExtraOptions extends object>( - collector: Collector<TFetchReturn, ExtraOptions> - ) => void; - /** - * Returns a usage collector by type - */ - getCollectorByType: <TFetchReturn, ExtraOptions extends object>( - type: string - ) => Collector<TFetchReturn, ExtraOptions> | undefined; - /** - * Fetches the collection from all the registered collectors - * @internal: telemetry use - */ - bulkFetch: <TFetchReturn, ExtraOptions extends object>( - esClient: ElasticsearchClient, - soClient: SavedObjectsClientContract, - collectors?: Map<string, Collector<TFetchReturn, ExtraOptions>> - ) => Promise<Array<{ type: string; result: unknown }>>; - /** - * Converts an array of fetched stats results into key/object - * @internal: telemetry use - */ - toObject: <Result extends Record<string, unknown>, T = unknown>( - statsData?: Array<{ type: string; result: T }> - ) => Result; - /** - * Rename fields to use API conventions - * @internal: monitoring use - */ - toApiFieldNames: ( - apiData: Record<string, unknown> | unknown[] - ) => Record<string, unknown> | unknown[]; - /** - * Creates a stats collector to collect plugin telemetry data. - * registerCollector must be called to connect the created collector with the service. - * @internal: telemetry and monitoring use - */ - makeStatsCollector: <TFetchReturn, ExtraOptions extends object = {}>( - options: CollectorOptions<TFetchReturn, ExtraOptions> - ) => Collector<TFetchReturn, ExtraOptions>; -} +/** Plugin's setup API **/ +export type UsageCollectionSetup = ICollectorSet & UsageCountersServiceSetup; + +/** Plugin's start API **/ +export type UsageCollectionStart = UsageCountersServiceStart; -export class UsageCollectionPlugin implements Plugin<UsageCollectionSetup> { +export class UsageCollectionPlugin implements Plugin<UsageCollectionSetup, UsageCollectionStart> { private readonly logger: Logger; private savedObjects?: ISavedObjectsRepository; private usageCountersService?: UsageCountersService; @@ -112,13 +52,12 @@ export class UsageCollectionPlugin implements Plugin<UsageCollectionSetup> { bufferDurationMs: config.usageCounters.bufferDuration.asMilliseconds(), }); - const usageCountersServiceSetup = this.usageCountersService.setup(core); - const { createUsageCounter, getUsageCounterByDomainId } = usageCountersServiceSetup; + const usageCountersSetup = this.usageCountersService.setup(core); const router = core.http.createRouter(); setupRoutes({ router, - usageCountersServiceSetup, + usageCounters: usageCountersSetup, getSavedObjects: () => this.savedObjects, collectorSet, config: { @@ -133,6 +72,10 @@ export class UsageCollectionPlugin implements Plugin<UsageCollectionSetup> { }); return { + // usage counters methods + createUsageCounter: usageCountersSetup.createUsageCounter, + getUsageCounterByDomainId: usageCountersSetup.getUsageCounterByDomainId, + // collector set methods bulkFetch: collectorSet.bulkFetch, getCollectorByType: collectorSet.getCollectorByType, makeStatsCollector: collectorSet.makeStatsCollector, @@ -140,12 +83,10 @@ export class UsageCollectionPlugin implements Plugin<UsageCollectionSetup> { registerCollector: collectorSet.registerCollector, toApiFieldNames: collectorSet.toApiFieldNames, toObject: collectorSet.toObject, - createUsageCounter, - getUsageCounterByDomainId, }; } - public start({ savedObjects }: CoreStart) { + public start({ savedObjects }: CoreStart): UsageCollectionStart { this.logger.debug('Starting plugin'); const config = this.initializerContext.config.get<ConfigType>(); if (!this.usageCountersService) { @@ -153,12 +94,13 @@ export class UsageCollectionPlugin implements Plugin<UsageCollectionSetup> { } this.savedObjects = savedObjects.createInternalRepository(); - if (config.usageCounters.enabled) { - this.usageCountersService.start({ savedObjects }); - } else { - // call stop() to complete observers. - this.usageCountersService.stop(); - } + const usageCountersStart = config.usageCounters.enabled + ? this.usageCountersService.start({ savedObjects }) + : this.usageCountersService.stop(); + + return { + search: usageCountersStart.search, + }; } public stop() { diff --git a/src/plugins/usage_collection/server/report/store_ui_report.ts b/src/plugins/usage_collection/server/report/store_ui_report.ts index 1cf966e3466e8..d45f05413127b 100644 --- a/src/plugins/usage_collection/server/report/store_ui_report.ts +++ b/src/plugins/usage_collection/server/report/store_ui_report.ts @@ -15,7 +15,7 @@ import { type UsageCountersServiceSetup } from '../usage_counters'; export async function storeUiReport( internalRepository: ISavedObjectsRepository, - counters: UsageCountersServiceSetup, + usageCounters: UsageCountersServiceSetup, report: ReportSchemaType ) { const uiCounters = report.uiCounter ? Object.entries(report.uiCounter) : []; @@ -61,7 +61,8 @@ export async function storeUiReport( const { appName, eventName, total, type } = metric; const counter = - counters.getUsageCounterByDomainId(appName) ?? counters.createUsageCounter(appName); + usageCounters.getUsageCounterByDomainId(appName) ?? + usageCounters.createUsageCounter(appName); counter.incrementCounter({ counterName: eventName, diff --git a/src/plugins/usage_collection/server/routes/index.ts b/src/plugins/usage_collection/server/routes/index.ts index 3bb42cf4865ce..42c7a7b7469b1 100644 --- a/src/plugins/usage_collection/server/routes/index.ts +++ b/src/plugins/usage_collection/server/routes/index.ts @@ -12,20 +12,20 @@ import { type MetricsServiceSetup, ServiceStatus, } from '@kbn/core/server'; -import { Observable } from 'rxjs'; -import { CollectorSet } from '../collector'; +import type { Observable } from 'rxjs'; +import type { ICollectorSet } from '../collector'; import { registerUiCountersRoute } from './ui_counters'; import { registerStatsRoute } from './stats'; import type { UsageCountersServiceSetup } from '../usage_counters'; export function setupRoutes({ router, - usageCountersServiceSetup, + usageCounters, getSavedObjects, ...rest }: { router: IRouter; getSavedObjects: () => ISavedObjectsRepository | undefined; - usageCountersServiceSetup: UsageCountersServiceSetup; + usageCounters: UsageCountersServiceSetup; config: { allowAnonymous: boolean; kibanaIndex: string; @@ -37,10 +37,10 @@ export function setupRoutes({ port: number; }; }; - collectorSet: CollectorSet; + collectorSet: ICollectorSet; metrics: MetricsServiceSetup; overallStatus$: Observable<ServiceStatus>; }) { - registerUiCountersRoute(router, getSavedObjects, usageCountersServiceSetup); + registerUiCountersRoute(router, getSavedObjects, usageCounters); registerStatsRoute({ router, ...rest }); } diff --git a/src/plugins/usage_collection/server/routes/stats/stats.ts b/src/plugins/usage_collection/server/routes/stats/stats.ts index 6e4a606216035..9fad603d0b88a 100644 --- a/src/plugins/usage_collection/server/routes/stats/stats.ts +++ b/src/plugins/usage_collection/server/routes/stats/stats.ts @@ -17,7 +17,7 @@ import { ServiceStatus, ServiceStatusLevels, } from '@kbn/core/server'; -import { CollectorSet } from '../../collector'; +import { ICollectorSet } from '../../collector'; import { Stats } from '../../../common/types'; const SNAPSHOT_REGEX = /-snapshot/i; @@ -40,7 +40,7 @@ export function registerStatsRoute({ port: number; }; }; - collectorSet: CollectorSet; + collectorSet: ICollectorSet; metrics: MetricsServiceSetup; overallStatus$: Observable<ServiceStatus>; }) { diff --git a/src/plugins/usage_collection/server/routes/ui_counters.ts b/src/plugins/usage_collection/server/routes/ui_counters.ts index e19dd7c57ca6d..49e4a3251d739 100644 --- a/src/plugins/usage_collection/server/routes/ui_counters.ts +++ b/src/plugins/usage_collection/server/routes/ui_counters.ts @@ -15,7 +15,7 @@ import type { UiCounters } from '../../common/types'; export function registerUiCountersRoute( router: IRouter, getSavedObjects: () => ISavedObjectsRepository | undefined, - usageCountersServiceSetup: UsageCountersServiceSetup + usageCounters: UsageCountersServiceSetup ) { router.post( { @@ -33,8 +33,8 @@ export function registerUiCountersRoute( if (!internalRepository) { throw Error(`The saved objects client hasn't been initialised yet`); } - // we pass the whole usageCountersServiceSetup, so that we can create UI counters dynamically - await storeUiReport(internalRepository, usageCountersServiceSetup, requestBody.report); + // we need to create UI counters dynamically if not explicitly created on server-side + await storeUiReport(internalRepository, usageCounters, requestBody.report); const bodyOk: UiCounters.v1.UiCountersResponseOk = { status: 'ok', }; diff --git a/src/plugins/usage_collection/server/usage_counters/common/kuery_utils.test.ts b/src/plugins/usage_collection/server/usage_counters/common/kuery_utils.test.ts new file mode 100644 index 0000000000000..e82746ab0b23f --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/common/kuery_utils.test.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fromKueryExpression } from '@kbn/es-query'; +import { usageCountersSearchParamsToKueryFilter } from './kuery_utils'; +import type { UsageCountersSearchFilters } from '../types'; + +describe('usageCountersSearchParamsToKueryFilter', () => { + it('creates a Kuery function with the provided search arguments', () => { + const params: Omit<UsageCountersSearchFilters, 'namespace'> = { + domainId: 'foo', + counterName: 'bar', + counterType: 'count', + source: 'server', + from: '2024-07-03T10:00:00.000Z', + to: '2024-07-10T10:00:00.000Z', + }; + const fromParams = usageCountersSearchParamsToKueryFilter(params); + + const fromExpression = fromKueryExpression( + // We need to pass the SO type (+ attributes) + // This is handled (removed) by the SOR internally + [ + `usage-counter.attributes.domainId: ${params.domainId}`, + `usage-counter.attributes.counterName: ${params.counterName}`, + `usage-counter.attributes.counterType: ${params.counterType}`, + `usage-counter.attributes.source: ${params.source}`, + `usage-counter.updated_at >= "${params.from}"`, + `usage-counter.updated_at <= "${params.to}"`, + ].join(' AND ') + ); + + // hack Kuery expression, as we cannot unquote date params above + fromExpression.arguments[4].arguments[2].isQuoted = false; + fromExpression.arguments[5].arguments[2].isQuoted = false; + + expect(fromParams).toEqual(fromExpression); + + expect(fromParams).toMatchInlineSnapshot(` + Object { + "arguments": Array [ + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.domainId", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "foo", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.counterName", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "bar", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.counterType", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "count", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.source", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "server", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.updated_at", + }, + "gte", + Object { + "isQuoted": false, + "type": "literal", + "value": "2024-07-03T10:00:00.000Z", + }, + ], + "function": "range", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.updated_at", + }, + "lte", + Object { + "isQuoted": false, + "type": "literal", + "value": "2024-07-10T10:00:00.000Z", + }, + ], + "function": "range", + "type": "function", + }, + ], + "function": "and", + "type": "function", + } + `); + }); +}); diff --git a/src/plugins/usage_collection/server/usage_counters/common/kuery_utils.ts b/src/plugins/usage_collection/server/usage_counters/common/kuery_utils.ts new file mode 100644 index 0000000000000..1178aab6bafb8 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/common/kuery_utils.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 { type KueryNode, nodeTypes } from '@kbn/es-query'; +import { USAGE_COUNTERS_SAVED_OBJECT_TYPE } from '../saved_objects'; +import type { UsageCountersSearchFilters } from '../types'; + +export function usageCountersSearchParamsToKueryFilter( + params: Omit<UsageCountersSearchFilters, 'namespace'> +): KueryNode { + const { domainId, counterName, counterType, source, from, to } = params; + + const isFilters = filtersToKueryNodes({ domainId, counterName, counterType, source }); + // add a date range filters + if (from) { + isFilters.push( + nodeTypes.function.buildNode( + 'range', + `${USAGE_COUNTERS_SAVED_OBJECT_TYPE}.updated_at`, + 'gte', + from + ) + ); + } + if (to) { + isFilters.push( + nodeTypes.function.buildNode( + 'range', + `${USAGE_COUNTERS_SAVED_OBJECT_TYPE}.updated_at`, + 'lte', + to + ) + ); + } + return nodeTypes.function.buildNode('and', isFilters); +} + +function filtersToKueryNodes(filters: Partial<Record<string, string>>): KueryNode[] { + return Object.entries(filters) + .filter(([, attributeValue]) => typeof attributeValue === 'string' && attributeValue) + .map(([attributeName, attributeValue]) => + nodeTypes.function.buildNode( + 'is', + `${USAGE_COUNTERS_SAVED_OBJECT_TYPE}.attributes.${attributeName}`, + attributeValue + ) + ); +} diff --git a/src/plugins/usage_collection/server/usage_counters/index.ts b/src/plugins/usage_collection/server/usage_counters/index.ts index fcbcf9f02e681..eecd5315e4553 100644 --- a/src/plugins/usage_collection/server/usage_counters/index.ts +++ b/src/plugins/usage_collection/server/usage_counters/index.ts @@ -8,7 +8,7 @@ import { UsageCounters } from '../../common'; export type IncrementCounterParams = UsageCounters.v1.IncrementCounterParams; -export type { UsageCountersServiceSetup } from './usage_counters_service'; +export type { UsageCountersServiceSetup, UsageCountersServiceStart } from './types'; export type { UsageCountersSavedObjectAttributes, UsageCountersSavedObject } from './saved_objects'; export type { IUsageCounter as UsageCounter } from './usage_counter'; diff --git a/src/plugins/usage_collection/server/usage_counters/integration_tests/counter_utils.ts b/src/plugins/usage_collection/server/usage_counters/integration_tests/counter_utils.ts new file mode 100644 index 0000000000000..e6676a205b3fd --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/integration_tests/counter_utils.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 { USAGE_COUNTERS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectsRepository, SavedObject, ElasticsearchClient } from '@kbn/core/server'; +import type { UsageCounters } from '../../../common/types'; + +import { + serializeCounterKey, + type UsageCountersSavedObjectAttributes, + USAGE_COUNTERS_SAVED_OBJECT_TYPE, +} from '../..'; + +// domainId, counterName, counterType, source, count, namespace? +export type CounterAttributes = [ + string, + string, + string, + UsageCounters.v1.CounterEventSource, + number, + string? +]; + +export function toCounterMetric(counter: CounterAttributes): UsageCounters.v1.CounterMetric { + const [domainId, counterName, counterType, source, incrementBy, namespace] = counter; + return { domainId, counterName, counterType, source, incrementBy, namespace }; +} + +export async function createCounters( + internalRepository: ISavedObjectsRepository, + esClient: ElasticsearchClient, + isoDate: string, + counters: UsageCounters.v1.CounterMetric[] +) { + await Promise.all( + counters + .map((counter) => createCounterObject(isoDate, counter)) + .map((counter) => incrementCounter(internalRepository, counter)) + ); + + // we manually update the 'updated_at' property of the SOs, to simulate older counters + await modifyUpdatedAt( + esClient, + counters.map((counter) => + serializeCounterKey({ + ...counter, + // SOR injects '[namespace:]so_type:' prefix when generating the ID + domainId: `${USAGE_COUNTERS_SAVED_OBJECT_TYPE}:${counter.domainId}`, + date: isoDate, + }) + ), + isoDate + ); +} + +function createCounterObject( + date: string, + counter: UsageCounters.v1.CounterMetric +): SavedObject<UsageCountersSavedObjectAttributes> { + const { domainId, counterName, counterType, namespace, source, incrementBy } = counter; + const id = serializeCounterKey({ + domainId, + counterName, + counterType, + source, + date, + }); + return { + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + id, + ...(namespace && { namespaces: [namespace] }), + // updated_at: date // illustrative purpose only, overriden by SOR + attributes: { + domainId, + counterName, + counterType, + source, + count: incrementBy, + }, + references: [], + }; +} + +async function incrementCounter( + internalRepository: ISavedObjectsRepository, + counter: SavedObject<UsageCountersSavedObjectAttributes> +) { + const namespace = counter.namespaces?.[0]; + return await internalRepository.incrementCounter( + USAGE_COUNTERS_SAVED_OBJECT_TYPE, + counter.id, + [{ fieldName: 'count', incrementBy: counter.attributes.count }], + { + ...(namespace && { namespace }), + upsertAttributes: { + domainId: counter.attributes.domainId, + counterName: counter.attributes.counterName, + counterType: counter.attributes.counterType, + source: counter.attributes.source, + }, + } + ); +} + +async function modifyUpdatedAt(esClient: ElasticsearchClient, ids: string[], updatedAt: string) { + await esClient.updateByQuery({ + index: USAGE_COUNTERS_SAVED_OBJECT_INDEX, + query: { + ids: { + values: ids, + }, + }, + refresh: true, + script: { + lang: 'painless', + params: { updatedAt }, + source: `ctx._source.updated_at = params.updatedAt;`, + }, + }); +} diff --git a/src/plugins/usage_collection/server/usage_counters/integration_tests/rollups.test.ts b/src/plugins/usage_collection/server/usage_counters/integration_tests/rollups.test.ts new file mode 100644 index 0000000000000..dd8dc3b5a5578 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/integration_tests/rollups.test.ts @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import moment from 'moment'; +import type { Logger, ISavedObjectsRepository, ElasticsearchClient } from '@kbn/core/server'; +import { + type TestElasticsearchUtils, + type TestKibanaUtils, + createTestServers, + createRootWithCorePlugins, +} from '@kbn/core-test-helpers-kbn-server'; + +import { + serializeCounterKey, + UsageCountersSavedObjectAttributes, + USAGE_COUNTERS_SAVED_OBJECT_TYPE, +} from '../saved_objects'; +import { USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS } from '../rollups/constants'; +import { rollUsageCountersIndices } from '../rollups/rollups'; +import { type CounterAttributes, createCounters, toCounterMetric } from './counter_utils'; +import type { IUsageCounter } from '../usage_counter'; + +const CUSTOM_RETENTION = 90; + +const NOW = '2024-06-30T10:00:00.000Z'; +const OLD = moment(NOW).subtract(USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS + 1, 'days'); +const RECENT = moment(NOW).subtract(USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS - 1, 'days'); +const OLD_YMD = OLD.format('YYYYMMDD'); +const RECENT_YMD = RECENT.format('YYYYMMDD'); +const OLD_ISO = OLD.toISOString(); +const RECENT_ISO = RECENT.toISOString(); + +const CUSTOM_OLD = moment(NOW).subtract(CUSTOM_RETENTION + 2, 'days'); +const CUSTOM_RECENT = moment(NOW).subtract(USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS - 1, 'days'); +const CUSTOM_OLD_YMD = CUSTOM_OLD.format('YYYYMMDD'); +const CUSTOM_RECENT_YMD = CUSTOM_RECENT.format('YYYYMMDD'); +const CUSTOM_OLD_ISO = CUSTOM_OLD.toISOString(); +const CUSTOM_RECENT_ISO = CUSTOM_RECENT.toISOString(); + +const ALL_COUNTERS = [ + `testCounter|domain1:a:count:server:${OLD_YMD}`, + `testCounter|domain2:a:count:server:${OLD_YMD}`, + `one:testCounter|domain1:b:count:server:${OLD_YMD}`, + `two:testCounter|domain1:b:count:server:${OLD_YMD}`, + + `testCounter|domain1:a:count:server:${RECENT_YMD}`, + `testCounter|domain2:a:count:server:${RECENT_YMD}`, + `testCounter|domain2:c:count:server:${RECENT_YMD}`, + `one:testCounter|domain1:b:count:server:${RECENT_YMD}`, + `two:testCounter|domain1:b:count:server:${RECENT_YMD}`, + + `testCounter|retention_${CUSTOM_RETENTION}:a:count:server:${CUSTOM_OLD_YMD}`, + `testCounter|retention_${CUSTOM_RETENTION}:a:count:server:${CUSTOM_RECENT_YMD}`, +].sort(); + +const RECENT_COUNTERS = ALL_COUNTERS.filter( + (key) => key.includes(RECENT_YMD) || key.includes(CUSTOM_RECENT_YMD) +); + +describe('usage-counters', () => { + let esServer: TestElasticsearchUtils; + let root: TestKibanaUtils['root']; + let getRegisteredUsageCounters: () => IUsageCounter[]; + let internalRepository: ISavedObjectsRepository; + let logger: Logger; + + beforeAll(async () => { + const { startES } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + }); + + esServer = await startES(); + root = createRootWithCorePlugins(); + + getRegisteredUsageCounters = () => [ + { + domainId: 'testCounter|domain1', + incrementCounter: jest.fn(), + }, + { + domainId: 'testCounter|domain2', + incrementCounter: jest.fn(), + }, + { + domainId: `testCounter|retention_${CUSTOM_RETENTION}`, + retentionPeriodDays: 90, + incrementCounter: jest.fn(), + }, + ]; + + await root.preboot(); + await root.setup(); + const start = await root.start(); + + logger = root.logger.get('test daily rollups'); + internalRepository = start.savedObjects.createInternalRepository([ + USAGE_COUNTERS_SAVED_OBJECT_TYPE, + ]); + + // insert a bunch of usage counters in multiple namespaces + await createTestCounters(internalRepository, start.elasticsearch.client.asInternalUser); + }); + + it('deletes documents older that the retention period, from all namespaces', async () => { + // check that all documents are there + const beforeRollup = await internalRepository.find<UsageCountersSavedObjectAttributes>({ + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + namespaces: ['*'], + }); + expect( + beforeRollup.saved_objects + .map(({ attributes, updated_at: updatedAt, namespaces }) => + serializeCounterKey({ ...attributes, date: updatedAt, namespace: namespaces?.[0] }) + ) + .filter((counterKey) => counterKey.includes('testCounter|')) + .sort() + ).toEqual(ALL_COUNTERS); + + await rollUsageCountersIndices({ + logger, + getRegisteredUsageCounters, + internalRepository, + now: moment(NOW), + }); + + // check only recent counters are present + const afterRollup = await internalRepository.find<UsageCountersSavedObjectAttributes>({ + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + namespaces: ['*'], + }); + expect( + afterRollup.saved_objects + .map(({ attributes, updated_at: updatedAt, namespaces }) => + serializeCounterKey({ ...attributes, date: updatedAt, namespace: namespaces?.[0] }) + ) + .filter((counterKey) => counterKey.includes('testCounter|')) + .sort() + ).toEqual(RECENT_COUNTERS); + }); + + afterAll(async () => { + await esServer.stop(); + await root.shutdown(); + }); +}); + +const customOld: CounterAttributes[] = [ + // domainId, counterName, counterType, source, count, namespace? + [`testCounter|retention_${CUSTOM_RETENTION}`, 'a', 'count', 'server', 198], +]; +const customRecent: CounterAttributes[] = [ + [`testCounter|retention_${CUSTOM_RETENTION}`, 'a', 'count', 'server', 199], +]; + +const old: CounterAttributes[] = [ + ['testCounter|domain1', 'a', 'count', 'server', 28], + ['testCounter|domain1', 'b', 'count', 'server', 29, 'one'], + ['testCounter|domain1', 'b', 'count', 'server', 30, 'two'], + ['testCounter|domain2', 'a', 'count', 'server', 31], +]; + +const recent: CounterAttributes[] = [ + // domainId, counterName, counterType, source, count, namespace? + ['testCounter|domain1', 'a', 'count', 'server', 32], + ['testCounter|domain1', 'b', 'count', 'server', 33, 'one'], + ['testCounter|domain1', 'b', 'count', 'server', 34, 'two'], + ['testCounter|domain2', 'a', 'count', 'server', 35], + ['testCounter|domain2', 'c', 'count', 'server', 36], +]; + +async function createTestCounters(repo: ISavedObjectsRepository, client: ElasticsearchClient) { + await createCounters(repo, client, CUSTOM_OLD_ISO, customOld.map(toCounterMetric)); + await createCounters(repo, client, CUSTOM_RECENT_ISO, customRecent.map(toCounterMetric)); + await createCounters(repo, client, OLD_ISO, old.map(toCounterMetric)); + await createCounters(repo, client, RECENT_ISO, recent.map(toCounterMetric)); +} diff --git a/src/plugins/usage_collection/server/usage_counters/integration_tests/search.test.ts b/src/plugins/usage_collection/server/usage_counters/integration_tests/search.test.ts new file mode 100644 index 0000000000000..e1e7151a14e75 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/integration_tests/search.test.ts @@ -0,0 +1,273 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import moment from 'moment'; +import type { ISavedObjectsRepository, ElasticsearchClient } from '@kbn/core/server'; +import { + type TestElasticsearchUtils, + type TestKibanaUtils, + createTestServers, + createRootWithCorePlugins, +} from '@kbn/core-test-helpers-kbn-server'; + +import { serializeCounterKey, USAGE_COUNTERS_SAVED_OBJECT_TYPE } from '../..'; +import { type CounterAttributes, createCounters, toCounterMetric } from './counter_utils'; +import type { UsageCounterSnapshot } from '../types'; +import { searchUsageCounters } from '../search'; +import { orderBy } from 'lodash'; + +// domainId, counterName, counterType, source, count, namespace? +const FIRST_DAY_COUNTERS: CounterAttributes[] = [ + ['dashboards', 'aDashboardId', 'viewed', 'server', 10, 'first'], + ['dashboards', 'aDashboardId', 'edited', 'server', 5, 'first'], + ['dashboards', 'aDashboardId', 'viewed', 'server', 100, 'second'], + ['dashboards', 'aDashboardId', 'edited', 'server', 50, 'second'], + ['dashboards', 'aDashboardId', 'consoleErrors', 'ui', 3, 'first'], + ['dashboards', 'aDashboardId', 'consoleErrors', 'ui', 9, 'second'], + ['dashboards', 'list', 'viewed', 'ui', 256, 'default'], + ['someDomain', 'someCounterName', 'someCounter', 'server', 13, 'first'], +]; + +const SECOND_DAY_COUNTERS: CounterAttributes[] = [ + ['dashboards', 'aDashboardId', 'viewed', 'server', 11, 'first'], + ['dashboards', 'aDashboardId', 'edited', 'server', 6, 'first'], + ['dashboards', 'aDashboardId', 'viewed', 'server', 101, 'second'], + ['dashboards', 'aDashboardId', 'edited', 'server', 51, 'second'], + ['dashboards', 'aDashboardId', 'consoleErrors', 'ui', 4, 'first'], + ['dashboards', 'aDashboardId', 'consoleErrors', 'ui', 10, 'second'], + ['dashboards', 'someGlobalServerCounter', 'count', 'server', 28], + ['dashboards', 'someGlobalUiCounter', 'count', 'ui', 14], + ['dashboards', 'list', 'viewed', 'ui', 257, 'default'], + ['someDomain', 'someCounterName', 'someCounter', 'server', 14, 'first'], +]; + +const THIRD_DAY_COUNTERS: CounterAttributes[] = [ + ['dashboards', 'someGlobalServerCounter', 'count', 'server', 29], + ['dashboards', 'someGlobalUiCounter', 'count', 'ui', 15], + ['someDomain', 'someCounterName', 'someCounter', 'server', 15, 'first'], +]; + +describe('usage-counters#search', () => { + let esServer: TestElasticsearchUtils; + let root: TestKibanaUtils['root']; + let internalRepository: ISavedObjectsRepository; + + beforeAll(async () => { + const { startES } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + }); + + esServer = await startES(); + root = createRootWithCorePlugins(); + + await root.preboot(); + await root.setup(); + const start = await root.start(); + + internalRepository = start.savedObjects.createInternalRepository([ + USAGE_COUNTERS_SAVED_OBJECT_TYPE, + ]); + + await createTestCounters(internalRepository, start.elasticsearch.client.asInternalUser); + }); + + describe('namespace agnostic search', () => { + it('returns counters in the default namespace', async () => { + const dashboardsNoNamespace = await searchUsageCounters(internalRepository, { + filters: { + domainId: 'dashboards', + }, + }); + + expect( + dashboardsNoNamespace.counters.every( + ({ domainId, namespace }) => domainId === 'dashboards' && namespace === 'default' + ) + ).toEqual(true); + + expectToMatchKeys(dashboardsNoNamespace.counters, [ + 'dashboards:list:viewed:ui - 513 hits', + 'dashboards:someGlobalServerCounter:count:server - 57 hits', + 'dashboards:someGlobalUiCounter:count:ui - 29 hits', + ]); + + // check that the daily records are sorted descendingly + expect( + dashboardsNoNamespace.counters.find( + ({ counterName }) => counterName === 'someGlobalUiCounter' + )!.records + ).toMatchInlineSnapshot(` + Array [ + Object { + "count": 15, + "updatedAt": "2024-07-03T10:00:00.000Z", + }, + Object { + "count": 14, + "updatedAt": "2024-07-02T10:00:00.000Z", + }, + ] + `); + }); + }); + + describe('namespace search', () => { + it('returns all counters that match namespace', async () => { + const dashboardsFirstNamespace = await searchUsageCounters(internalRepository, { + filters: { + domainId: 'dashboards', + namespace: 'first', + }, + }); + expect( + dashboardsFirstNamespace.counters.every( + ({ domainId, namespace }) => domainId === 'dashboards' && namespace === 'first' + ) + ).toEqual(true); + expectToMatchKeys(dashboardsFirstNamespace.counters, [ + 'first:dashboards:aDashboardId:viewed:server - 21 hits', + 'first:dashboards:aDashboardId:edited:server - 11 hits', + 'first:dashboards:aDashboardId:consoleErrors:ui - 7 hits', + ]); + }); + + it('does not return counters that belong to other namespaces', async () => { + const someDomainSecondNamespace = await searchUsageCounters(internalRepository, { + filters: { + domainId: 'someDomain', + namespace: 'second', + }, + }); + expect(someDomainSecondNamespace.counters).toEqual([]); + }); + }); + + describe('specific counter search', () => { + it('allows searching for specific counters (name + type) on specific namespaces', async () => { + const dashboardsByName = await searchUsageCounters(internalRepository, { + filters: { + domainId: 'dashboards', + counterName: 'aDashboardId', + counterType: 'viewed', + source: 'server', + namespace: 'second', + }, + }); + + expect( + dashboardsByName.counters.every( + ({ domainId, counterName, counterType, source, namespace }) => + domainId === 'dashboards' && + counterName === 'aDashboardId' && + counterType === 'viewed' && + source === 'server' && + namespace === 'second' + ) + ).toEqual(true); + expectToMatchKeys(dashboardsByName.counters, [ + 'second:dashboards:aDashboardId:viewed:server - 201 hits', + ]); + }); + }); + + describe('date filters', () => { + it('allow searching for counters that are more recent than the given date', async () => { + const from = moment('2024-07-03T00:00:00.000Z'); + const dashboardsFrom = await searchUsageCounters(internalRepository, { + filters: { + domainId: 'dashboards', + from: '2024-07-03T00:00:00.000Z', + }, + }); + + expect( + dashboardsFrom.counters.every( + ({ domainId, records }) => + domainId === 'dashboards' && + records.every(({ updatedAt }) => moment(updatedAt).diff(from) > 0) + ) + ).toEqual(true); + + expectToMatchKeys(dashboardsFrom.counters, [ + 'dashboards:someGlobalServerCounter:count:server - 29 hits', + 'dashboards:someGlobalUiCounter:count:ui - 15 hits', + ]); + }); + }); + + describe('PIT search', () => { + it('allows retrieving all counters in batches', async () => { + const allDashboards = await searchUsageCounters(internalRepository, { + filters: { + domainId: 'dashboards', + namespace: '*', + }, + options: { + // we are forcing the logic to perform lots of requests to ES + // each of them retrieving just a single result, just for the sake of testing + perPage: 1, + }, + }); + + expectToMatchKeys(allDashboards.counters, [ + 'dashboards:list:viewed:ui - 513 hits', + 'second:dashboards:aDashboardId:viewed:server - 201 hits', + 'second:dashboards:aDashboardId:edited:server - 101 hits', + 'dashboards:someGlobalServerCounter:count:server - 57 hits', + 'dashboards:someGlobalUiCounter:count:ui - 29 hits', + 'first:dashboards:aDashboardId:viewed:server - 21 hits', + 'second:dashboards:aDashboardId:consoleErrors:ui - 19 hits', + 'first:dashboards:aDashboardId:edited:server - 11 hits', + 'first:dashboards:aDashboardId:consoleErrors:ui - 7 hits', + ]); + }); + }); + + afterAll(async () => { + await esServer.stop(); + await root.shutdown(); + }); +}); + +async function createTestCounters( + internalRepository: ISavedObjectsRepository, + esClient: ElasticsearchClient +) { + // insert a bunch of usage counters in multiple namespaces + await createCounters( + internalRepository, + esClient, + '2024-07-01T10:00:00.000Z', + FIRST_DAY_COUNTERS.map(toCounterMetric) + ); + await createCounters( + internalRepository, + esClient, + '2024-07-02T10:00:00.000Z', + SECOND_DAY_COUNTERS.map(toCounterMetric) + ); + await createCounters( + internalRepository, + esClient, + '2024-07-03T10:00:00.000Z', + THIRD_DAY_COUNTERS.map(toCounterMetric) + ); +} + +function expectToMatchKeys(counters: UsageCounterSnapshot[], keys: string[]) { + expect(counters.length).toEqual(keys.length); + + // the counter snapshots do not include a single date. We match a date agnostic key + expect( + orderBy( + counters.map((counter) => ({ ...counter, key: serializeCounterKey(counter) })), + ['count', 'key'], + ['desc', 'asc'] + ).map(({ key, count }) => `${key.substring(0, key.length - 9)} - ${count} hits`) + ).toEqual(keys); +} diff --git a/src/plugins/usage_collection/server/usage_counters/rollups/constants.ts b/src/plugins/usage_collection/server/usage_counters/rollups/constants.ts new file mode 100644 index 0000000000000..984c9479453b9 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/rollups/constants.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. + */ + +/** + * Roll indices every hour + */ +export const ROLL_INDICES_INTERVAL = 3600_000; + +/** + * Start rolling indices after 5 minutes up + */ +export const ROLL_INDICES_START = 5 * 60 * 1000; + +/** + * Number of days to keep the Usage counters saved object documents + */ +export const USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS = 5; diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/index.ts b/src/plugins/usage_collection/server/usage_counters/rollups/index.ts similarity index 100% rename from src/plugins/kibana_usage_collection/server/collectors/usage_counters/rollups/index.ts rename to src/plugins/usage_collection/server/usage_counters/rollups/index.ts diff --git a/src/plugins/usage_collection/server/usage_counters/rollups/register_rollups.ts b/src/plugins/usage_collection/server/usage_counters/rollups/register_rollups.ts new file mode 100644 index 0000000000000..f8617055951b4 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/rollups/register_rollups.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 { type Observable, timer, takeUntil } from 'rxjs'; +import type { Logger, ISavedObjectsRepository } from '@kbn/core/server'; +import { ROLL_INDICES_INTERVAL, ROLL_INDICES_START } from './constants'; +import { rollUsageCountersIndices } from './rollups'; +import { IUsageCounter } from '../usage_counter'; + +export function registerUsageCountersRollups({ + logger, + getRegisteredUsageCounters, + internalRepository, + pluginStop$, +}: { + logger: Logger; + getRegisteredUsageCounters: () => IUsageCounter[]; + internalRepository: ISavedObjectsRepository; + pluginStop$: Observable<void>; +}) { + timer(ROLL_INDICES_START, ROLL_INDICES_INTERVAL) + .pipe(takeUntil(pluginStop$)) + .subscribe(() => + rollUsageCountersIndices({ + logger, + getRegisteredUsageCounters, + internalRepository, + }) + ); +} diff --git a/src/plugins/usage_collection/server/usage_counters/rollups/rollups.test.ts b/src/plugins/usage_collection/server/usage_counters/rollups/rollups.test.ts new file mode 100644 index 0000000000000..0bde634a62840 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/rollups/rollups.test.ts @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import moment from 'moment'; +import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { rollUsageCountersIndices } from './rollups'; +import { USAGE_COUNTERS_SAVED_OBJECT_TYPE } from '..'; +import { createMockSavedObjectDoc } from '../saved_objects.test'; +import type { IUsageCounter } from '../usage_counter'; + +describe('rollUsageCountersIndices', () => { + let logger: ReturnType<typeof loggingSystemMock.createLogger>; + let internalRepository: ReturnType<typeof savedObjectsRepositoryMock.create>; + let getRegisteredUsageCounters: () => IUsageCounter[]; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + internalRepository = savedObjectsRepositoryMock.create(); + getRegisteredUsageCounters = () => [ + { + domainId: 'testDomain', + incrementCounter: jest.fn(), + }, + { + domainId: 'retention_3', + retentionPeriodDays: 3, + incrementCounter: jest.fn(), + }, + ]; + }); + + it('returns undefined if no savedObjectsClient initialised yet', async () => { + await expect( + rollUsageCountersIndices({ + logger, + getRegisteredUsageCounters, + internalRepository: undefined, + }) + ).resolves.toBe(undefined); + expect(logger.warn).toHaveBeenCalledTimes(0); + }); + + it('does not delete any documents on empty saved objects', async () => { + internalRepository.find.mockImplementation(async ({ type, page = 1, perPage = 10 }) => { + switch (type) { + case USAGE_COUNTERS_SAVED_OBJECT_TYPE: + return { saved_objects: [], total: 0, page, per_page: perPage }; + default: + throw new Error(`Unexpected type [${type}]`); + } + }); + await expect( + rollUsageCountersIndices({ logger, getRegisteredUsageCounters, internalRepository }) + ).resolves.toEqual(0); + expect(internalRepository.find).toHaveBeenCalledTimes(getRegisteredUsageCounters().length); + expect(internalRepository.bulkDelete).not.toBeCalled(); + expect(logger.warn).toHaveBeenCalledTimes(0); + expect(logger.debug).toHaveBeenCalledTimes(0); + }); + + it(`deletes documents older than the retention period`, async () => { + const mockSavedObjects = [ + createMockSavedObjectDoc(moment().subtract(5, 'days'), 'doc-id-0', 'testDomain'), + createMockSavedObjectDoc(moment().subtract(9, 'days'), 'doc-id-1', 'testDomain'), // old + createMockSavedObjectDoc(moment().subtract(2, 'days'), 'doc-id-2', 'retention_3'), + createMockSavedObjectDoc(moment().subtract(4, 'days'), 'doc-id-3', 'retention_3'), // old + createMockSavedObjectDoc(moment().subtract(6, 'days'), 'doc-id-4', 'testDomain', 'secondary'), // old + ]; + + internalRepository.find.mockImplementationOnce(async ({ type, page = 1, perPage = 10 }) => { + switch (type) { + case USAGE_COUNTERS_SAVED_OBJECT_TYPE: + return { + saved_objects: [mockSavedObjects[1], mockSavedObjects[4]], + total: mockSavedObjects.length, + page, + per_page: perPage, + }; + default: + throw new Error(`Unexpected type [${type}]`); + } + }); + internalRepository.find.mockImplementationOnce(async ({ type, page = 1, perPage = 10 }) => { + switch (type) { + case USAGE_COUNTERS_SAVED_OBJECT_TYPE: + return { + saved_objects: [mockSavedObjects[3]], + total: mockSavedObjects.length, + page, + per_page: perPage, + }; + default: + throw new Error(`Unexpected type [${type}]`); + } + }); + await expect( + rollUsageCountersIndices({ logger, getRegisteredUsageCounters, internalRepository }) + ).resolves.toEqual(3); + expect(internalRepository.find).toHaveBeenCalledTimes(getRegisteredUsageCounters().length); + expect(internalRepository.find).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ type: USAGE_COUNTERS_SAVED_OBJECT_TYPE }) + ); + expect(internalRepository.find).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ type: USAGE_COUNTERS_SAVED_OBJECT_TYPE }) + ); + expect(internalRepository.bulkDelete).toHaveBeenCalledTimes(3); + expect(internalRepository.bulkDelete.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Array [ + Object { + "id": "doc-id-1", + "type": "usage-counter", + }, + ], + Object { + "namespace": "default", + }, + ], + Array [ + Array [ + Object { + "id": "doc-id-4", + "type": "usage-counter", + }, + ], + Object { + "namespace": "secondary", + }, + ], + Array [ + Array [ + Object { + "id": "doc-id-3", + "type": "usage-counter", + }, + ], + Object { + "namespace": "default", + }, + ], + ] + `); + expect(logger.warn).toHaveBeenCalledTimes(0); + }); + + it(`logs warnings on savedObject.find failure`, async () => { + internalRepository.find.mockImplementation(async () => { + throw new Error(`Expected error!`); + }); + await expect( + rollUsageCountersIndices({ logger, getRegisteredUsageCounters, internalRepository }) + ).resolves.toEqual(0); + // we abort operation if the find for a given domain fails + expect(internalRepository.find).toHaveBeenCalledTimes(1); + expect(internalRepository.bulkDelete).not.toBeCalled(); + expect(logger.warn).toHaveBeenCalledTimes(2); + }); + + it(`logs warnings on savedObject.delete failure`, async () => { + const mockSavedObjects = [ + createMockSavedObjectDoc(moment().subtract(7, 'days'), 'doc-id-6', 'testDomain'), + ]; + + internalRepository.find.mockImplementationOnce(async ({ type, page = 1, perPage = 10 }) => { + switch (type) { + case USAGE_COUNTERS_SAVED_OBJECT_TYPE: + return { saved_objects: mockSavedObjects, total: 0, page, per_page: perPage }; + default: + throw new Error(`Unexpected type [${type}]`); + } + }); + internalRepository.delete.mockImplementationOnce(async () => { + throw new Error(`Expected error!`); + }); + await expect( + rollUsageCountersIndices({ logger, getRegisteredUsageCounters, internalRepository }) + ).resolves.toEqual(1); + expect(internalRepository.find).toHaveBeenCalledTimes(2); + expect(internalRepository.bulkDelete).toHaveBeenCalledTimes(1); + expect(internalRepository.bulkDelete.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Array [ + Object { + "id": "doc-id-6", + "type": "usage-counter", + }, + ], + Object { + "namespace": "default", + }, + ], + ] + `); + expect(logger.warn).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/plugins/usage_collection/server/usage_counters/rollups/rollups.ts b/src/plugins/usage_collection/server/usage_counters/rollups/rollups.ts new file mode 100644 index 0000000000000..b52f52297df1c --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/rollups/rollups.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import moment from 'moment'; +import type { ISavedObjectsRepository, Logger, SavedObjectsFindOptions } from '@kbn/core/server'; +import { groupBy } from 'lodash'; +import { USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS } from './constants'; +import { type UsageCountersSavedObjectAttributes, USAGE_COUNTERS_SAVED_OBJECT_TYPE } from '..'; +import type { IUsageCounter } from '../usage_counter'; +import { usageCountersSearchParamsToKueryFilter } from '../common/kuery_utils'; + +// Process 1000 at a time as a compromise of speed and overload +const ROLLUP_BATCH_SIZE = 1000; + +export async function rollUsageCountersIndices({ + logger, + getRegisteredUsageCounters, + internalRepository, + now = moment(), +}: { + logger: Logger; + getRegisteredUsageCounters: () => IUsageCounter[]; + internalRepository?: ISavedObjectsRepository; + now?: moment.Moment; +}) { + if (!internalRepository) { + return; + } + + let cleanupCounter = 0; + + try { + const counterQueue = getRegisteredUsageCounters(); + + while (counterQueue.length > 0) { + const counter = counterQueue.shift()!; + + const findParams: SavedObjectsFindOptions = { + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + filter: usageCountersSearchParamsToKueryFilter({ + domainId: counter.domainId, + to: moment(now) + // get documents that are OLDER than the retention period + .subtract( + 1 + (counter.retentionPeriodDays ?? USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS), + 'days' + ) + .toISOString(), + }), + sortField: 'updated_at', + sortOrder: 'asc', + namespaces: ['*'], + perPage: ROLLUP_BATCH_SIZE, + }; + + const { saved_objects: rawUiCounterDocs } = + await internalRepository.find<UsageCountersSavedObjectAttributes>(findParams); + + if (rawUiCounterDocs.length) { + const toDelete = rawUiCounterDocs.map(({ id, type, namespaces }) => ({ + id, + type, + namespace: namespaces?.[0] ?? 'default', + })); + cleanupCounter += toDelete.length; + + logger.debug( + `[Rollups] Cleaning ${toDelete.length} old Usage Counters saved objects under domain '${counter.domainId}'` + ); + + const toDeleteByNamespace = groupBy(toDelete, 'namespace'); + + // perform a Bulk delete for each namespace + await Promise.all( + Object.entries(toDeleteByNamespace).map(([namespace, counters]) => + internalRepository.bulkDelete( + counters.map(({ namespace: _, ...props }) => ({ ...props })), + { namespace } + ) + ) + ); + + if (toDelete.length === ROLLUP_BATCH_SIZE) { + // we found a lot of old Usage Counters, put the counter back in the queue, as there might be more + counterQueue.push(counter); + } + } + } + } catch (err) { + logger.warn(`Failed to rollup Usage Counters saved objects.`); + logger.warn(err); + } + + if (cleanupCounter) { + logger.debug(`[Rollups] Cleaned ${cleanupCounter} Usage Counters saved objects`); + } + return cleanupCounter; +} diff --git a/src/plugins/usage_collection/server/usage_counters/saved_objects.test.ts b/src/plugins/usage_collection/server/usage_counters/saved_objects.test.ts index 7a8934e4559fa..e4e6b4f5fc9b7 100644 --- a/src/plugins/usage_collection/server/usage_counters/saved_objects.test.ts +++ b/src/plugins/usage_collection/server/usage_counters/saved_objects.test.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import { serializeCounterKey, storeCounter } from './saved_objects'; -import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; - -import { UsageCounters } from '../../common'; - import moment from 'moment'; +import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; +import { serializeCounterKey, storeCounter } from './saved_objects'; +import type { UsageCounters } from '../../common'; +import type { SavedObjectsFindResult } from '@kbn/core/server'; +import { type UsageCountersSavedObjectAttributes, USAGE_COUNTERS_SAVED_OBJECT_TYPE } from '..'; describe('counterKey', () => { - test('#serializeCounterKey returns a serialized string', () => { + test('#serializeCounterKey returns a serialized string that omits default namespace', () => { const result = serializeCounterKey({ domainId: 'a', counterName: 'b', @@ -24,7 +24,20 @@ describe('counterKey', () => { date: moment('09042021', 'DDMMYYYY'), }); - expect(result).toEqual('a:b:c:ui:20210409:default'); + expect(result).toEqual('a:b:c:ui:20210409'); + }); + + test('#serializeCounterKey returns a serialized string for non-default namespaces', () => { + const result = serializeCounterKey({ + domainId: 'a', + counterName: 'b', + counterType: 'c', + namespace: 'second', + source: 'ui', + date: moment('09042021', 'DDMMYYYY'), + }); + + expect(result).toEqual('second:a:b:c:ui:20210409'); }); }); @@ -77,3 +90,26 @@ describe('storeCounter', () => { `); }); }); + +export const createMockSavedObjectDoc = ( + updatedAt: moment.Moment, + id: string, + domainId: string, + namespace?: string +) => + ({ + id, + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + ...(namespace && { namespaces: [namespace] }), + attributes: { + count: 3, + domainId, + counterName: 'testName', + counterType: 'count', + source: 'server', + }, + references: [], + updated_at: updatedAt.format(), + version: 'WzI5LDFd', + score: 0, + } as SavedObjectsFindResult<UsageCountersSavedObjectAttributes>); diff --git a/src/plugins/usage_collection/server/usage_counters/saved_objects.ts b/src/plugins/usage_collection/server/usage_counters/saved_objects.ts index ab8bc6620ac87..f67bf02c45faa 100644 --- a/src/plugins/usage_collection/server/usage_counters/saved_objects.ts +++ b/src/plugins/usage_collection/server/usage_counters/saved_objects.ts @@ -8,25 +8,18 @@ import moment from 'moment'; import { USAGE_COUNTERS_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; import type { SavedObject, SavedObjectsRepository, SavedObjectsServiceSetup, } from '@kbn/core/server'; -import { UsageCounters } from '../../common'; +import type { UsageCounters } from '../../common'; /** * The attributes stored in the UsageCounters' SavedObjects */ -export interface UsageCountersSavedObjectAttributes { - /** The domain ID registered in the Usage Counter **/ - domainId: string; - /** The counter name **/ - counterName: string; - /** The counter type **/ - counterType: string; - /** The source of the event that is being counted: 'server' | 'ui' **/ - source: string; +export interface UsageCountersSavedObjectAttributes extends UsageCounters.v1.AbstractCounter { /** Number of times the event has occurred **/ count: number; } @@ -77,17 +70,7 @@ export const registerUsageCountersSavedObjectTypes = ( * Parameters to the `serializeCounterKey` method * @internal used in kibana_usage_collectors */ -export interface SerializeCounterKeyParams { - /** The domain ID registered in the UsageCounter **/ - domainId: string; - /** The counter name **/ - counterName: string; - /** The counter type **/ - counterType: string; - /** The namespace of this counter */ - namespace?: string; - /** The source of the event we are counting */ - source: string; +export interface SerializeCounterKeyParams extends UsageCounters.v1.AbstractCounter { /** The date to which serialize the key (defaults to 'now') **/ date?: moment.MomentInput; } @@ -97,19 +80,17 @@ export interface SerializeCounterKeyParams { * @internal used in kibana_usage_collectors * @param opts {@link SerializeCounterKeyParams} */ -export const serializeCounterKey = ({ - domainId, - counterName, - counterType, - namespace, - source, - date, -}: SerializeCounterKeyParams) => { +export const serializeCounterKey = (params: SerializeCounterKeyParams) => { + const { domainId, counterName, counterType, namespace, source, date } = params; const dayDate = moment(date).format('YYYYMMDD'); - // e.g. 'dashboards:viewed:total:ui:20240628' // namespace-agnostic counters - // e.g. 'dashboards:viewed:total:ui:20240628:default' // namespaced counters - const namespaceSuffix = namespace ? `:${namespace}` : ''; - return `${domainId}:${counterName}:${counterType}:${source}:${dayDate}${namespaceSuffix}`; + + if (namespace && namespace !== DEFAULT_NAMESPACE_STRING) { + // e.g. 'someNamespace:dashboards:viewed:total:ui:20240628' + return `${namespace}:${domainId}:${counterName}:${counterType}:${source}:${dayDate}`; + } else { + // e.g. 'dashboards:viewed:total:ui:20240628' + return `${domainId}:${counterName}:${counterType}:${source}:${dayDate}`; + } }; export interface StoreCounterParams { diff --git a/src/plugins/usage_collection/server/usage_counters/search/index.ts b/src/plugins/usage_collection/server/usage_counters/search/index.ts new file mode 100644 index 0000000000000..69d873b429902 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/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 { searchUsageCounters } from './search'; diff --git a/src/plugins/usage_collection/server/usage_counters/search/search.fixtures.ts b/src/plugins/usage_collection/server/usage_counters/search/search.fixtures.ts new file mode 100644 index 0000000000000..39f82f4af5284 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/search/search.fixtures.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 type { SavedObjectsFindResult } from '@kbn/core-saved-objects-api-server'; +import type { UsageCounters } from '../../../common'; +import type { UsageCountersSavedObjectAttributes } from '../saved_objects'; + +// domainId, counterName, counterType, source, count, namespace? +export type CounterAttributes = [ + string, + string, + string, + UsageCounters.v1.CounterEventSource, + number, + string? +]; + +export const mockedUsageCounters: Array< + SavedObjectsFindResult<UsageCountersSavedObjectAttributes> +> = [ + toSOFR('2024-07-08T10:00:00.000Z', 'foo', 'bar', 'count', 'server', 28, 'default'), + toSOFR('2024-07-07T10:00:00.000Z', 'foo', 'bar', 'count', 'server', 27, 'default'), + toSOFR('2024-07-06T10:00:00.000Z', 'foo', 'bar', 'count', 'server', 26, 'default'), + toSOFR('2024-07-05T10:00:00.000Z', 'foo', 'bar', 'count', 'server', 25, 'default'), + toSOFR('2024-07-04T10:00:00.000Z', 'foo', 'bar', 'count', 'server', 24, 'default'), + toSOFR('2024-07-03T10:00:00.000Z', 'foo', 'bar', 'count', 'server', 23, 'default'), +]; + +function toSOFR( + isoDate: string, + ...attrs: CounterAttributes +): SavedObjectsFindResult<UsageCountersSavedObjectAttributes> { + const [domainId, counterName, counterType, source, count, namespace] = attrs; + return { + id: 'someId', + type: 'usage-counter', + ...(namespace && namespace !== 'default' && { namespaces: [namespace[0]] }), + attributes: { + domainId, + counterName, + counterType, + source, + count, + }, + updated_at: isoDate, + references: [], + score: 0, + }; +} diff --git a/src/plugins/usage_collection/server/usage_counters/search/search.test.ts b/src/plugins/usage_collection/server/usage_counters/search/search.test.ts new file mode 100644 index 0000000000000..6c149038784f8 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/search/search.test.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; +import { searchUsageCounters } from './search'; +import { mockedUsageCounters } from './search.fixtures'; + +describe('searchUsageCounters', () => { + let internalRepository: ReturnType<typeof savedObjectsRepositoryMock.create>; + + beforeEach(() => { + internalRepository = savedObjectsRepositoryMock.create(); + }); + + it('calls repository.find() with the right params', async () => { + internalRepository.find.mockResolvedValueOnce({ + page: 1, + per_page: 100, + total: 8, + saved_objects: [], + }); + + await searchUsageCounters(internalRepository, { + filters: { + domainId: 'foo', + counterName: 'bar', + counterType: 'count', + from: '2024-07-03T10:00:00.000Z', + source: 'server', + }, + }); + + expect(internalRepository.find).toHaveBeenCalledTimes(1); + expect(internalRepository.find.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "filter": Object { + "arguments": Array [ + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.domainId", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "foo", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.counterName", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "bar", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.counterType", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "count", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.attributes.source", + }, + Object { + "isQuoted": false, + "type": "literal", + "value": "server", + }, + ], + "function": "is", + "type": "function", + }, + Object { + "arguments": Array [ + Object { + "isQuoted": false, + "type": "literal", + "value": "usage-counter.updated_at", + }, + "gte", + Object { + "isQuoted": false, + "type": "literal", + "value": "2024-07-03T10:00:00.000Z", + }, + ], + "function": "range", + "type": "function", + }, + ], + "function": "and", + "type": "function", + }, + "perPage": 100, + "pit": Object { + "id": "some_pit_id", + }, + "type": "usage-counter", + } + `); + }); + + it('aggregates the usage counters with the same ID/namespace', async () => { + internalRepository.find.mockResolvedValueOnce({ + page: 1, + per_page: 1000, + total: 8, + saved_objects: mockedUsageCounters, + }); + + const res = await searchUsageCounters(internalRepository, { filters: { domainId: 'foo' } }); + + expect(res.counters).toMatchInlineSnapshot(` + Array [ + Object { + "count": 153, + "counterName": "bar", + "counterType": "count", + "domainId": "foo", + "records": Array [ + Object { + "count": 28, + "updatedAt": "2024-07-08T10:00:00.000Z", + }, + Object { + "count": 27, + "updatedAt": "2024-07-07T10:00:00.000Z", + }, + Object { + "count": 26, + "updatedAt": "2024-07-06T10:00:00.000Z", + }, + Object { + "count": 25, + "updatedAt": "2024-07-05T10:00:00.000Z", + }, + Object { + "count": 24, + "updatedAt": "2024-07-04T10:00:00.000Z", + }, + Object { + "count": 23, + "updatedAt": "2024-07-03T10:00:00.000Z", + }, + ], + "source": "server", + }, + ] + `); + }); +}); diff --git a/src/plugins/usage_collection/server/usage_counters/search/search.ts b/src/plugins/usage_collection/server/usage_counters/search/search.ts new file mode 100644 index 0000000000000..b759b166dbb4c --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/search/search.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { orderBy } from 'lodash'; +import { SortResults } from '@elastic/elasticsearch/lib/api/types'; +import type { + ISavedObjectsRepository, + SavedObjectsFindOptions, + SavedObjectsFindResult, +} from '@kbn/core-saved-objects-api-server'; +import { + serializeCounterKey, + type UsageCountersSavedObjectAttributes, + USAGE_COUNTERS_SAVED_OBJECT_TYPE, +} from '../saved_objects'; +import type { + UsageCounterSnapshot, + UsageCountersSearchParams, + UsageCountersSearchResult, +} from '../types'; +import { usageCountersSearchParamsToKueryFilter } from '../common/kuery_utils'; + +export async function searchUsageCounters( + repository: ISavedObjectsRepository, + params: UsageCountersSearchParams +): Promise<UsageCountersSearchResult> { + const { filters, options = {} } = params; + const { namespace: filterNamespace } = filters; + + const baseFindParams: SavedObjectsFindOptions = { + ...(filterNamespace && { namespaces: [filterNamespace] }), + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + filter: usageCountersSearchParamsToKueryFilter(filters), + perPage: options.perPage || 100, + }; + + // create a PIT to perform consecutive searches + const pit = await repository.openPointInTimeForType(USAGE_COUNTERS_SAVED_OBJECT_TYPE); + // create a data structure to store/aggregate all counters + const countersMap = new Map<string, UsageCounterSnapshot>(); + // the current offset for the iterative search + let searchAfter: SortResults | undefined; + + do { + const findParams: SavedObjectsFindOptions = { + ...baseFindParams, + pit, + ...(searchAfter && { searchAfter }), + }; + + // this is where the actual search call is performed + const res = await repository.find<UsageCountersSavedObjectAttributes>(findParams); + res.saved_objects.forEach((result) => processResult(countersMap, result)); + searchAfter = res.saved_objects.pop()?.sort; + } while (searchAfter); + + await repository.closePointInTime(pit.id); + + const counters = Array.from(countersMap.values()); + + // sort daily counters descending + counters.forEach( + (snapshot) => (snapshot.records = orderBy(snapshot.records, 'updatedAt', 'desc')) + ); + + return { + counters, + }; +} + +function processResult( + countersMap: Map<string, UsageCounterSnapshot>, + result: SavedObjectsFindResult<UsageCountersSavedObjectAttributes> +) { + const { attributes, updated_at: updatedAt, namespaces } = result; + const namespace = namespaces?.[0]; + const key = serializeCounterKey({ ...attributes, namespace }); + + let counterSnapshot = countersMap.get(key); + + if (!counterSnapshot) { + counterSnapshot = { + domainId: attributes.domainId, + counterName: attributes.counterName, + counterType: attributes.counterType, + source: attributes.source, + ...(namespace && namespaces?.[0] && { namespace: namespaces[0] }), + records: [ + { + updatedAt: updatedAt!, + count: attributes.count, + }, + ], + count: attributes.count, + }; + + countersMap.set(key, counterSnapshot!); + } else { + counterSnapshot.records.push({ + updatedAt: updatedAt!, + count: attributes.count, + }); + counterSnapshot.count += attributes.count; + } +} diff --git a/src/plugins/usage_collection/server/usage_counters/types.ts b/src/plugins/usage_collection/server/usage_counters/types.ts new file mode 100644 index 0000000000000..2f529381cd0b0 --- /dev/null +++ b/src/plugins/usage_collection/server/usage_counters/types.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 type { UsageCounters } from '../../common/types'; +import type { UsageCounter } from '.'; + +export interface CreateUsageCounterParams { + /** + * Number of days a usage counter must be kept in the persistence layer. + * See USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS for default value. + */ + retentionPeriodDays?: number; +} + +/** + * Provides the necessary tools to create and incremement Usage Counters + */ +export interface UsageCountersServiceSetup { + /** + * Returns a usage counter by domainId + */ + getUsageCounterByDomainId: (domainId: string) => UsageCounter | undefined; + /** + * Registers a usage counter to collect daily aggregated plugin counter events + */ + createUsageCounter: (domainId: string, params?: CreateUsageCounterParams) => UsageCounter; +} + +export interface UsageCountersSearchParams { + /** A set of filters to limit the results of the search operation */ + filters: UsageCountersSearchFilters; + /** A set of options to modify the behavior of the search operation */ + options?: UsageCountersSearchOptions; +} + +export interface UsageCountersSearchFilters { + /** The domainId used to create the Counter API */ + domainId: string; + /** The name of the counter. Optional, will return all counters in the same domainId that match the rest of filters if omitted */ + counterName?: string; + /** The type of counter. Optional, will return all counters in the same domainId that match the rest of filters if omitted */ + counterType?: string; + /** Namespace of the counter. Optional, counters of the 'default' namespace will be returned if omitted */ + namespace?: string; + /** ISO date string to limit search results: get counters that are more recent than the provided date (if specified) */ + from?: string; + /** ISO date string to limit search results: get counters that are older than the provided date (if specified) */ + to?: string; + /** Return counters from a given source only. Optional, both 'ui' and 'server' counters will be returned if omitted */ + source?: 'server' | 'ui'; +} + +export interface UsageCountersSearchOptions { + /** Number of counters to retrieve per page, when querying ES (defaults to 100) */ + perPage?: number; +} + +/** + * The result of a Usage Counters search operation + */ +export interface UsageCountersSearchResult { + /** + * The counters that matched the search criteria + */ + counters: UsageCounterSnapshot[]; +} + +/** + * Represents the current state of a Usage Counter at a given point in time + */ +export interface UsageCounterSnapshot extends UsageCounters.v1.AbstractCounter { + /** List of daily records captured for this counter */ + records: UsageCounterRecord[]; + /** Number of events captured (adds up all records) */ + count: number; +} + +/** + * Number of events counted on a given day + */ +export interface UsageCounterRecord { + /** Date where the counter was last updated */ + updatedAt: string; + /** Number of events captured on that day */ + count: number; +} + +/** + * Interface to allow searching for persisted usage-counters + */ +export interface UsageCountersServiceStart { + search: (params: UsageCountersSearchParams) => Promise<UsageCountersSearchResult>; +} diff --git a/src/plugins/usage_collection/server/usage_counters/usage_counter.ts b/src/plugins/usage_collection/server/usage_counters/usage_counter.ts index 6f8c892f2627a..bd2e31190010b 100644 --- a/src/plugins/usage_collection/server/usage_counters/usage_counter.ts +++ b/src/plugins/usage_collection/server/usage_counters/usage_counter.ts @@ -12,6 +12,7 @@ import type { UsageCounters } from '../../common'; export interface UsageCounterParams { domainId: string; counter$: Rx.Subject<UsageCounters.v1.CounterMetric>; + retentionPeriodDays?: number; } /** @@ -20,6 +21,16 @@ export interface UsageCounterParams { * API whenever the event happens. */ export interface IUsageCounter { + /** + * Defines a domainId (aka a namespace) under which multiple counters can be stored + */ + domainId: string; + /** + * Defines custom retention period for the counters under this domain. + * This is the number of days worth of counters that must be kept in the system indices. + * See USAGE_COUNTERS_KEEP_DOCS_FOR_DAYS for default value + */ + retentionPeriodDays?: number; /** * Notifies the counter about a new event happening so it can increase the count internally. * @param params {@link IncrementCounterParams} @@ -28,12 +39,14 @@ export interface IUsageCounter { } export class UsageCounter implements IUsageCounter { - private domainId: string; + public readonly domainId: string; private counter$: Rx.Subject<UsageCounters.v1.CounterMetric>; + public readonly retentionPeriodDays?: number | undefined; - constructor({ domainId, counter$ }: UsageCounterParams) { + constructor({ domainId, counter$, retentionPeriodDays }: UsageCounterParams) { this.domainId = domainId; this.counter$ = counter$; + this.retentionPeriodDays = retentionPeriodDays; } public incrementCounter = (params: UsageCounters.v1.IncrementCounterParams) => { diff --git a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock.ts b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock.ts index 8e8627c5aea2d..c0872bda432d0 100644 --- a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock.ts +++ b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock.ts @@ -7,8 +7,9 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { UsageCountersService, UsageCountersServiceSetup } from './usage_counters_service'; +import type { UsageCountersService } from './usage_counters_service'; import type { UsageCounter } from './usage_counter'; +import type { UsageCountersServiceSetup } from './types'; const createSetupContractMock = () => { const setupContract: jest.Mocked<UsageCountersServiceSetup> = { @@ -16,9 +17,14 @@ const createSetupContractMock = () => { getUsageCounterByDomainId: jest.fn(), }; - setupContract.createUsageCounter.mockReturnValue({ - incrementCounter: jest.fn(), - } as unknown as jest.Mocked<UsageCounter>); + setupContract.createUsageCounter.mockImplementation( + (domainId: string, params?: { retentionPeriodDays?: number }) => + ({ + domainId, + ...(params?.retentionPeriodDays && { retentionPeriodDays: params.retentionPeriodDays }), + incrementCounter: jest.fn(), + } as unknown as jest.Mocked<UsageCounter>) + ); return setupContract; }; diff --git a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts index 1350c8b706b87..5748cd12d46d3 100644 --- a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts +++ b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts @@ -7,10 +7,22 @@ */ /* eslint-disable dot-notation */ -import { UsageCountersService } from './usage_counters_service'; -import { loggingSystemMock, coreMock } from '@kbn/core/server/mocks'; import * as rxOp from 'rxjs'; import moment from 'moment'; +import { loggingSystemMock, coreMock } from '@kbn/core/server/mocks'; +import { UsageCountersService } from './usage_counters_service'; + +jest.mock('./rollups', () => ({ + ...jest.requireActual('./rollups'), + // used by `rollUsageCountersIndices` to determine if a counter is beyond the retention period + registerUsageCountersRollups: jest.fn(), +})); + +import { registerUsageCountersRollups } from './rollups'; + +const registerUsageCountersRollupsMock = registerUsageCountersRollups as jest.MockedFunction< + typeof registerUsageCountersRollups +>; const tick = () => { jest.useRealTimers(); @@ -42,7 +54,9 @@ describe('UsageCountersService', () => { usageCounter.incrementCounter({ counterName: 'counterA' }); usageCounter.incrementCounter({ counterName: 'counterA', namespace: 'second', source: 'ui' }); - const dataInSourcePromise = usageCountersService['source$'].pipe(rxOp.toArray()).toPromise(); + const dataInSourcePromise = rxOp.firstValueFrom( + usageCountersService['source$'].pipe(rxOp.toArray()) + ); usageCountersService['flushCache$'].next(); usageCountersService['source$'].complete(); await expect(dataInSourcePromise).resolves.toHaveLength(2); @@ -54,6 +68,20 @@ describe('UsageCountersService', () => { expect(coreSetup.savedObjects.registerType).toBeCalledTimes(2); }); + it('triggers regular cleanup of old counters on start', () => { + const usageCountersService = new UsageCountersService({ logger, retryCount, bufferDurationMs }); + usageCountersService.start(coreStart); + + expect(registerUsageCountersRollupsMock).toHaveBeenCalledTimes(1); + expect(registerUsageCountersRollupsMock).toHaveBeenCalledWith( + expect.objectContaining({ + logger: expect.any(Object), + getRegisteredUsageCounters: expect.any(Function), + internalRepository: expect.any(Object), + }) + ); + }); + it('flushes cached data on start', async () => { const usageCountersService = new UsageCountersService({ logger, retryCount, bufferDurationMs }); @@ -69,7 +97,9 @@ describe('UsageCountersService', () => { usageCounter.incrementCounter({ counterName: 'counterA' }); usageCounter.incrementCounter({ counterName: 'counterA', namespace: 'second', source: 'ui' }); - const dataInSourcePromise = usageCountersService['source$'].pipe(rxOp.toArray()).toPromise(); + const dataInSourcePromise = rxOp.firstValueFrom( + usageCountersService['source$'].pipe(rxOp.toArray()) + ); usageCountersService.start(coreStart); usageCountersService['source$'].complete(); diff --git a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts index 28c3aaf2be148..c80f453e9a0db 100644 --- a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts +++ b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts @@ -7,9 +7,9 @@ */ import * as Rx from 'rxjs'; -import * as rxOp from 'rxjs'; import moment from 'moment'; import type { + ISavedObjectsRepository, SavedObjectsRepository, SavedObjectsServiceSetup, SavedObjectsServiceStart, @@ -18,11 +18,20 @@ import type { Logger, LogMeta } from '@kbn/core/server'; import { type IUsageCounter, UsageCounter } from './usage_counter'; import type { UsageCounters } from '../../common'; +import type { + UsageCountersServiceSetup, + UsageCountersServiceStart, + UsageCountersSearchParams, + UsageCountersSearchResult, + CreateUsageCounterParams, +} from './types'; import { - registerUsageCountersSavedObjectTypes, storeCounter, serializeCounterKey, + registerUsageCountersSavedObjectTypes, } from './saved_objects'; +import { registerUsageCountersRollups } from './rollups'; +import { searchUsageCounters } from './search'; interface UsageCountersLogMeta extends LogMeta { kibana: { usageCounters: { results: unknown[] } }; @@ -34,11 +43,6 @@ export interface UsageCountersServiceDeps { bufferDurationMs: number; } -export interface UsageCountersServiceSetup { - createUsageCounter: (domainId: string) => IUsageCounter; - getUsageCounterByDomainId: (domainId: string) => IUsageCounter | undefined; -} - /* internal */ export interface UsageCountersServiceSetupDeps { savedObjects: SavedObjectsServiceSetup; @@ -56,11 +60,12 @@ export class UsageCountersService { private readonly counterSets = new Map<string, UsageCounter>(); private readonly source$ = new Rx.Subject<UsageCounters.v1.CounterMetric>(); - private readonly counter$ = this.source$.pipe(rxOp.multicast(new Rx.Subject()), rxOp.refCount()); + private readonly counter$ = this.source$.pipe(Rx.multicast(new Rx.Subject()), Rx.refCount()); private readonly flushCache$ = new Rx.Subject<void>(); - private readonly stopCaching$ = new Rx.Subject<void>(); + private repository?: ISavedObjectsRepository; + private readonly logger: Logger; constructor({ logger, retryCount, bufferDurationMs }: UsageCountersServiceDeps) { @@ -69,14 +74,14 @@ export class UsageCountersService { this.bufferDurationMs = bufferDurationMs; } - public setup = (core: UsageCountersServiceSetupDeps): UsageCountersServiceSetup => { + public setup = ({ savedObjects }: UsageCountersServiceSetupDeps): UsageCountersServiceSetup => { const cache$ = new Rx.ReplaySubject<UsageCounters.v1.CounterMetric>(); const storingCache$ = new Rx.BehaviorSubject<boolean>(false); // flush cache data from cache -> source this.flushCache$ .pipe( - rxOp.exhaustMap(() => cache$), - rxOp.takeUntil(this.stop$) + Rx.exhaustMap(() => cache$), + Rx.takeUntil(this.stop$) ) .subscribe((data) => { storingCache$.next(true); @@ -86,16 +91,17 @@ export class UsageCountersService { // store data into cache when not paused storingCache$ .pipe( - rxOp.distinctUntilChanged(), - rxOp.switchMap((isStoring) => (isStoring ? Rx.EMPTY : this.source$)), - rxOp.takeUntil(Rx.merge(this.stopCaching$, this.stop$)) + Rx.distinctUntilChanged(), + Rx.switchMap((isStoring) => (isStoring ? Rx.EMPTY : this.source$)), + Rx.takeUntil(Rx.merge(this.stopCaching$, this.stop$)) ) .subscribe((data) => { cache$.next(data); storingCache$.next(false); }); - registerUsageCountersSavedObjectTypes(core.savedObjects); + // register the usage-counter and usage-counters (deprecated) types + registerUsageCountersSavedObjectTypes(savedObjects); return { createUsageCounter: this.createUsageCounter, @@ -103,22 +109,22 @@ export class UsageCountersService { }; }; - public start = ({ savedObjects }: UsageCountersServiceStartDeps): void => { + public start = ({ savedObjects }: UsageCountersServiceStartDeps): UsageCountersServiceStart => { this.stopCaching$.next(); - const internalRepository = savedObjects.createInternalRepository(); + this.repository = savedObjects.createInternalRepository(); this.counter$ .pipe( /* buffer source events every ${bufferDurationMs} */ - rxOp.bufferTime(this.bufferDurationMs), + Rx.bufferTime(this.bufferDurationMs), /** * bufferTime will trigger every ${bufferDurationMs} * regardless if source emitted anything or not. * using filter will stop cut the pipe short */ - rxOp.filter((counters) => Array.isArray(counters) && counters.length > 0), - rxOp.map((counters) => Object.values(this.mergeCounters(counters))), - rxOp.takeUntil(this.stop$), - rxOp.concatMap((counters) => this.storeDate$(counters, internalRepository)) + Rx.filter((counters) => Array.isArray(counters) && counters.length > 0), + Rx.map((counters) => Object.values(this.mergeCounters(counters))), + Rx.takeUntil(this.stop$), + Rx.concatMap((counters) => this.storeDate$(counters, this.repository!)) ) .subscribe((results) => { this.logger.debug<UsageCountersLogMeta>('Store counters into savedObjects', { @@ -129,10 +135,27 @@ export class UsageCountersService { }); this.flushCache$.next(); + + // we start a regular, timer-based cleanup + registerUsageCountersRollups({ + logger: this.logger, + getRegisteredUsageCounters: () => Array.from(this.counterSets.values()), + internalRepository: this.repository, + pluginStop$: this.stop$, + }); + + return { + search: this.search, + }; }; - public stop = () => { + public stop = (): UsageCountersServiceStart => { this.stop$.next(); + this.stop$.complete(); + + return { + search: this.search, + }; }; private storeDate$( @@ -142,8 +165,8 @@ export class UsageCountersService { return Rx.forkJoin( counters.map((metric) => Rx.defer(() => storeCounter({ metric, soRepository })).pipe( - rxOp.retry(this.retryCount), - rxOp.catchError((error) => { + Rx.retry(this.retryCount), + Rx.catchError((error) => { this.logger.warn(error); return Rx.of(error); }) @@ -152,18 +175,25 @@ export class UsageCountersService { ); } - private createUsageCounter = (domainId: string): IUsageCounter => { + private createUsageCounter = ( + domainId: string, + params: CreateUsageCounterParams = {} + ): IUsageCounter => { if (this.counterSets.get(domainId)) { throw new Error(`Usage counter set "${domainId}" already exists.`); } - const counterSet = new UsageCounter({ domainId, counter$: this.source$ }); + const counterSet = new UsageCounter({ + domainId, + counter$: this.source$, + retentionPeriodDays: params.retentionPeriodDays, + }); this.counterSets.set(domainId, counterSet); return counterSet; }; - private getUsageCounterByDomainId = (type: string): IUsageCounter | undefined => { - return this.counterSets.get(type); + private getUsageCounterByDomainId = (domainId: string): IUsageCounter | undefined => { + return this.counterSets.get(domainId); }; private mergeCounters = ( @@ -193,4 +223,14 @@ export class UsageCountersService { return acc; }, {} as Record<string, UsageCounters.v1.CounterMetric>); }; + + private search = async ( + params: UsageCountersSearchParams + ): Promise<UsageCountersSearchResult> => { + if (!this.repository) { + throw new Error('Cannot search before this service is started. Please call start() first.'); + } + + return await searchUsageCounters(this.repository, params); + }; } diff --git a/src/plugins/usage_collection/tsconfig.json b/src/plugins/usage_collection/tsconfig.json index 53fba66071c5e..444f4908bf401 100644 --- a/src/plugins/usage_collection/tsconfig.json +++ b/src/plugins/usage_collection/tsconfig.json @@ -22,8 +22,11 @@ "@kbn/core-http-server-mocks", "@kbn/analytics-collection-utils", "@kbn/logging", - "@kbn/ebt", "@kbn/core-saved-objects-server", + "@kbn/core-test-helpers-kbn-server", + "@kbn/es-query", + "@kbn/core-saved-objects-utils-server", + "@kbn/core-saved-objects-api-server", ], "exclude": [ "target/**/*", diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index ad1678e4168ce..c6d90d879e8c7 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -527,20 +527,27 @@ export class VisualizeEmbeddable } private renderError(error: ErrorLike | string) { + const { core } = this.deps.start(); if (isFallbackDataView(this.vis.data.indexPattern)) { return ( - <VisualizationMissedSavedObjectError - renderMode={this.input.renderMode ?? 'view'} - savedObjectMeta={{ - savedObjectType: this.vis.data.savedSearchId ? 'search' : DATA_VIEW_SAVED_OBJECT_TYPE, - }} - application={getApplication()} - message={typeof error === 'string' ? error : error.message} - /> + <KibanaRenderContextProvider {...core}> + <VisualizationMissedSavedObjectError + renderMode={this.input.renderMode ?? 'view'} + savedObjectMeta={{ + savedObjectType: this.vis.data.savedSearchId ? 'search' : DATA_VIEW_SAVED_OBJECT_TYPE, + }} + application={getApplication()} + message={typeof error === 'string' ? error : error.message} + /> + </KibanaRenderContextProvider> ); } - return <VisualizationError error={error} />; + return ( + <KibanaRenderContextProvider {...core}> + <VisualizationError error={error} /> + </KibanaRenderContextProvider> + ); } public destroy() { diff --git a/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts b/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts index 9a2e235a0aa6a..ab16ca2cea6d7 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts +++ b/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts @@ -18,7 +18,7 @@ import { toArray, } from 'rxjs'; import { get } from 'lodash'; -import type { Event } from '@kbn/ebt/client'; +import type { Event } from '@elastic/ebt/client'; import type { GetEventsOptions } from './types'; export async function fetchEvents( diff --git a/test/analytics/plugins/analytics_ftr_helpers/common/types.ts b/test/analytics/plugins/analytics_ftr_helpers/common/types.ts index 25c3a8b738fe2..ed9ebeda49d96 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/common/types.ts +++ b/test/analytics/plugins/analytics_ftr_helpers/common/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { Event, EventType } from '@kbn/ebt/client'; +import type { Event, EventType } from '@elastic/ebt/client'; export type FiltersOptions = { [key in 'eq' | 'gte' | 'gt' | 'lte' | 'lt']?: unknown; diff --git a/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json b/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json index 352e95c717114..b590649cf570b 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json +++ b/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json @@ -17,6 +17,5 @@ "@kbn/core", "@kbn/std", "@kbn/config-schema", - "@kbn/ebt", ] } diff --git a/test/api_integration/apis/console/index.ts b/test/api_integration/apis/console/index.ts index f39cf6cabb129..4e4d97b8204c8 100644 --- a/test/api_integration/apis/console/index.ts +++ b/test/api_integration/apis/console/index.ts @@ -13,6 +13,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./proxy_route')); loadTestFile(require.resolve('./autocomplete_entities')); loadTestFile(require.resolve('./es_config')); - loadTestFile(require.resolve('./spec_definitions')); }); } diff --git a/test/api_integration/apis/console/spec_definitions.ts b/test/api_integration/apis/console/spec_definitions.ts deleted file mode 100644 index f8e56354f6319..0000000000000 --- a/test/api_integration/apis/console/spec_definitions.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not 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 }: FtrProviderContext) { - const supertest = getService('supertest'); - - describe('GET /api/console/api_server', () => { - it('returns autocomplete definitions', async () => { - const { body } = await supertest - .get('/api/console/api_server') - .set('kbn-xsrf', 'true') - .expect(200); - expect(body.es).to.be.ok(); - const { - es: { name, globals, endpoints }, - } = body; - expect(name).to.be.ok(); - expect(Object.keys(globals).length).to.be.above(0); - expect(Object.keys(endpoints).length).to.be.above(0); - }); - }); -} diff --git a/test/api_integration/apis/core/compression.ts b/test/api_integration/apis/core/compression.ts deleted file mode 100644 index c4b119692f4bb..0000000000000 --- a/test/api_integration/apis/core/compression.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const compressionSuite = (url: string) => { - it(`uses compression when there isn't a referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); - }); - - it(`uses compression when there is a whitelisted referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .set('referer', 'https://some-host.com') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); - }); - - it(`doesn't use compression when there is a non-whitelisted referer`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'gzip') - .set('referer', 'https://other.some-host.com') - .then((response) => { - expect(response.header).not.to.have.property('content-encoding'); - }); - }); - - it(`supports brotli compression`, async () => { - await supertest - .get(url) - .set('accept-encoding', 'br') - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'br'); - }); - }); - }; - - describe('compression', () => { - describe('against an application page', () => { - compressionSuite('/app/kibana'); - }); - }); -} diff --git a/test/api_integration/apis/core/index.ts b/test/api_integration/apis/core/index.ts index 65c8a5d5cb5c5..98010e01c8215 100644 --- a/test/api_integration/apis/core/index.ts +++ b/test/api_integration/apis/core/index.ts @@ -10,7 +10,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('core', () => { - loadTestFile(require.resolve('./compression')); loadTestFile(require.resolve('./translations')); loadTestFile(require.resolve('./capabilities')); }); diff --git a/test/api_integration/apis/esql/errors.ts b/test/api_integration/apis/esql/errors.ts index e74f86efcb44c..bf2d7e2139a91 100644 --- a/test/api_integration/apis/esql/errors.ts +++ b/test/api_integration/apis/esql/errors.ts @@ -137,6 +137,7 @@ export default function ({ getService }: FtrProviderContext) { describe('error messages', () => { const config = readSetupFromESQLPackage(); const { queryToErrors, indexes, policies } = parseConfig(config); + const missmatches: Array<{ query: string; error: string }> = []; // Swap these for DEBUG/further investigation on ES bugs const stringVariants = ['text', 'keyword'] as const; @@ -182,11 +183,23 @@ export default function ({ getService }: FtrProviderContext) { for (const index of indexes) { // setup all indexes, mappings and policies here - log.info(`creating a index "${index}" with mapping...`); + log.info( + `creating a index "${index}" with mapping...\n${JSON.stringify(config.fields)}` + ); + const fieldsExcludingCounterType = config.fields.filter( + // ES|QL supports counter_integer, counter_long, counter_double, date_period, etc. + // but they are not types suitable for Elasticsearch indices + (c: { type: string }) => + !c.type.startsWith('counter_') && + c.type !== 'date_period' && + c.type !== 'time_duration' && + c.type !== 'null' && + c.type !== 'time_literal' + ); await es.indices.create( createIndexRequest( index, - /unsupported/.test(index) ? config.unsupported_field : config.fields, + /unsupported/.test(index) ? config.unsupported_field : fieldsExcludingCounterType, stringFieldType, numberFieldType ), diff --git a/test/common/services/bsearch.ts b/test/common/services/bsearch.ts index fd9ef7f703a0b..81063813cec5f 100644 --- a/test/common/services/bsearch.ts +++ b/test/common/services/bsearch.ts @@ -39,7 +39,7 @@ const getSpaceUrlPrefix = (spaceId?: string): string => { /** * Options for the send method */ -interface SendOptions { +export interface SendOptions { supertest: SuperTest.Agent; options: object; strategy: string; diff --git a/test/common/services/index.ts b/test/common/services/index.ts index ad08829afd047..ccc786d4ccc6e 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -16,8 +16,15 @@ import { IndexPatternsService } from './index_patterns'; import { BsearchService } from './bsearch'; import { ConsoleProvider } from './console'; +// pick only services that work for any FTR config, e.g. 'samlAuth' requires SAML setup in config file +const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices; + export const services = { - ...commonFunctionalServices, + es, + esArchiver, + kibanaServer, + retry, + supertestWithoutAuth, deployment: DeploymentService, randomness: RandomnessService, security: SecurityServiceProvider, diff --git a/test/functional/apps/console/monaco/_autocomplete.ts b/test/functional/apps/console/monaco/_autocomplete.ts index 755ffa0364a01..b949a979a49db 100644 --- a/test/functional/apps/console/monaco/_autocomplete.ts +++ b/test/functional/apps/console/monaco/_autocomplete.ts @@ -261,21 +261,6 @@ GET _search ); expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); }); - - // not fixed for monaco yet https://github.com/elastic/kibana/issues/184442 - it.skip('should not activate auto-complete after comma following endpoint in URL', async () => { - await PageObjects.console.monaco.enterText('GET _search'); - - await PageObjects.console.sleepForDebouncePeriod(); - log.debug('Key type ","'); - await PageObjects.console.monaco.enterText(','); // i.e. 'GET _search,' - - await PageObjects.console.sleepForDebouncePeriod(); - log.debug('Key type Ctrl+SPACE'); - await PageObjects.console.monaco.pressCtrlSpace(); - - expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(false); - }); }); // not implemented for monaco yet https://github.com/elastic/kibana/issues/184856 diff --git a/test/functional/apps/console/monaco/_console.ts b/test/functional/apps/console/monaco/_console.ts index b48ba75529579..1c6afc39c5046 100644 --- a/test/functional/apps/console/monaco/_console.ts +++ b/test/functional/apps/console/monaco/_console.ts @@ -14,6 +14,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); + const toasts = getService('toasts'); const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'console', 'header']); const security = getService('security'); @@ -57,17 +58,12 @@ 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'; + it('should not send request with unsupported HTTP verbs', async () => { await PageObjects.console.monaco.clearEditorText(); await PageObjects.console.monaco.enterText('OPTIONS /'); await PageObjects.console.clickPlay(); await retry.try(async () => { - const actualResponse = await PageObjects.console.monaco.getOutputText(); - log.debug(actualResponse); - expect(actualResponse).to.contain(expectedResponseContains); - - expect(await PageObjects.console.hasSuccessBadge()).to.be(false); + expect(await toasts.getCount()).to.equal(1); }); }); diff --git a/test/functional/apps/console/monaco/_misc_console_behavior.ts b/test/functional/apps/console/monaco/_misc_console_behavior.ts index 1d72fa796e421..f7ad2957c8411 100644 --- a/test/functional/apps/console/monaco/_misc_console_behavior.ts +++ b/test/functional/apps/console/monaco/_misc_console_behavior.ts @@ -27,11 +27,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('keyboard shortcuts', () => { - let tabCount = 1; - - after(async () => { - if (tabCount > 1) { + let tabOpened = false; + afterEach(async () => { + if (tabOpened) { await browser.closeCurrentWindow(); + tabOpened = false; await browser.switchTab(0); } }); @@ -91,20 +91,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.console.monaco.getCurrentLineNumber()).to.be(4); }); - // flaky - it.skip('should open documentation when Ctrl+/ is pressed', async () => { - await PageObjects.console.monaco.enterText('GET _search'); - await PageObjects.console.monaco.pressEscape(); - await PageObjects.console.monaco.pressCtrlSlash(); - await retry.tryForTime(10000, async () => { - await browser.switchTab(1); - tabCount++; - }); - - // Retry until the documentation is loaded - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - expect(url).to.contain('search-search.html'); + describe('open documentation', () => { + const requests = ['GET _search', 'GET test_index/_search', 'GET /_search']; + requests.forEach((request) => { + it('should open documentation when Ctrl+/ is pressed', async () => { + await PageObjects.console.monaco.enterText(request); + await PageObjects.console.monaco.pressEscape(); + await PageObjects.console.monaco.pressCtrlSlash(); + await retry.tryForTime(10000, async () => { + await browser.switchTab(1); + tabOpened = true; + }); + + // Retry until the documentation is loaded + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain('search-search.html'); + }); + }); }); }); }); diff --git a/test/functional/apps/dashboard/group5/embed_mode.ts b/test/functional/apps/dashboard/group5/embed_mode.ts index 3c2cfbae77a9f..c819a05c8ceb2 100644 --- a/test/functional/apps/dashboard/group5/embed_mode.ts +++ b/test/functional/apps/dashboard/group5/embed_mode.ts @@ -126,7 +126,7 @@ export default function ({ 'dashboard_embed_mode_scrolling', updateBaselines ); - expect(percentDifference).to.be.lessThan(0.02); + expect(percentDifference).to.be.lessThan(0.021); }); }); diff --git a/test/functional/apps/discover/context_awareness/_data_source_profile.ts b/test/functional/apps/discover/context_awareness/_data_source_profile.ts index 594e6dee5dd78..d42a4e8b9c4c6 100644 --- a/test/functional/apps/discover/context_awareness/_data_source_profile.ts +++ b/test/functional/apps/discover/context_awareness/_data_source_profile.ts @@ -24,8 +24,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -43,8 +43,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-logs | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -66,8 +66,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); @@ -82,8 +82,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-logs | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); @@ -98,7 +98,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { describe('cell renderers', () => { it('should render custom @timestamp but not custom log.level', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -112,7 +114,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render custom @timestamp and custom log.level', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs'); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -130,7 +134,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('doc viewer extension', () => { it('should not render custom doc viewer view', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); @@ -141,7 +147,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render custom doc viewer view', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); diff --git a/test/functional/apps/discover/context_awareness/_root_profile.ts b/test/functional/apps/discover/context_awareness/_root_profile.ts index c0bb4885699f8..bf4fee50704f7 100644 --- a/test/functional/apps/discover/context_awareness/_root_profile.ts +++ b/test/functional/apps/discover/context_awareness/_root_profile.ts @@ -23,8 +23,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); @@ -38,7 +38,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { describe('cell renderers', () => { it('should render custom @timestamp', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await PageObjects.discover.waitUntilSearchingHasFinished(); const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts b/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts index eadb9db7fd708..00531b80f4a73 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_cell_renderers.ts @@ -35,8 +35,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null', }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); @@ -55,8 +55,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { esql: 'from my-example* | sort @timestamp desc | where `log.level` is not null', }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); @@ -68,7 +68,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { it('should render log.level badge cell', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs,logstash*'); await queryBar.setQuery('log.level:*'); await queryBar.submitQuery(); @@ -83,7 +85,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it("should not render log.level badge cell if it's not a logs data source", async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await queryBar.setQuery('log.level:*'); await queryBar.submitQuery(); diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_default_app_state.ts b/test/functional/apps/discover/context_awareness/extensions/_get_default_app_state.ts new file mode 100644 index 0000000000000..4991aa5f36ee1 --- /dev/null +++ b/test/functional/apps/discover/context_awareness/extensions/_get_default_app_state.ts @@ -0,0 +1,206 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList']); + const dataViews = getService('dataViews'); + const dataGrid = getService('dataGrid'); + const queryBar = getService('queryBar'); + const monacoEditor = getService('monacoEditor'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + + describe('extension getDefaultAppState', () => { + afterEach(async () => { + await kibanaServer.uiSettings.unset('defaultColumns'); + }); + + describe('ES|QL mode', () => { + it('should render default columns and row height', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-logs', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + const rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should render default columns and row height when switching index patterns', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-*', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + let rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(3); + await monacoEditor.setCodeEditorValue('from my-example-logs'); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should reset default columns and row height when clicking "New"', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-logs', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level'); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('message'); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + await dataGrid.changeRowHeightValue('Single'); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Single'); + await testSubjects.click('discoverNewButton'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should merge and dedup configured default columns with default profile columns', async () => { + await kibanaServer.uiSettings.update({ + defaultColumns: ['bad_column', 'data_stream.type', 'message'], + }); + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-logs', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']); + }); + }); + + describe('data view mode', () => { + it('should render default columns and row height', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + const rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should render default columns and row height when switching data views', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + let rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(3); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should reset default columns and row height when clicking "New"', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level'); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('message'); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + await dataGrid.changeRowHeightValue('Single'); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Single'); + await testSubjects.click('discoverNewButton'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should merge and dedup configured default columns with default profile columns', async () => { + await kibanaServer.uiSettings.update({ + defaultColumns: ['bad_column', 'data_stream.type', 'message'], + }); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']); + }); + }); + }); +} diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts b/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts index 7f60f92cf6191..e2c91143d53f7 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_doc_viewer.ts @@ -22,8 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-logs | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); @@ -38,8 +38,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-metrics | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); @@ -50,7 +50,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { it('should render logs overview tab for logs data source', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); @@ -61,7 +63,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not render logs overview tab for non-logs data source', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-metrics'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts new file mode 100644 index 0000000000000..be536fd6cdbe9 --- /dev/null +++ b/test/functional/apps/discover/context_awareness/extensions/_get_row_additional_leading_controls.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 kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('extension getRowAdditionalLeadingControls', () => { + describe('ES|QL mode', () => { + it('should render logs controls for logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-metrics | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + + describe('data view mode', () => { + it('should render logs controls for logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-metrics'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + }); +} diff --git a/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts b/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts index 72dafc0f46e7f..8efa852cbfb2a 100644 --- a/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts +++ b/test/functional/apps/discover/context_awareness/extensions/_get_row_indicator_provider.ts @@ -31,8 +31,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from logstash* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -51,8 +51,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); // my-example* has a log.level field, but it's not matching the logs profile, so the color indicator should not be rendered @@ -67,8 +67,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null', }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); // in this case it's matching the logs data source profile and has a log.level field, so the color indicator should be rendered diff --git a/test/functional/apps/discover/context_awareness/index.ts b/test/functional/apps/discover/context_awareness/index.ts index 3fdd8ab799266..0bba18a339263 100644 --- a/test/functional/apps/discover/context_awareness/index.ts +++ b/test/functional/apps/discover/context_awareness/index.ts @@ -36,7 +36,9 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid loadTestFile(require.resolve('./_root_profile')); loadTestFile(require.resolve('./_data_source_profile')); loadTestFile(require.resolve('./extensions/_get_row_indicator_provider')); + loadTestFile(require.resolve('./extensions/_get_row_additional_leading_controls')); loadTestFile(require.resolve('./extensions/_get_doc_viewer')); loadTestFile(require.resolve('./extensions/_get_cell_renderers')); + loadTestFile(require.resolve('./extensions/_get_default_app_state')); }); } diff --git a/test/functional/apps/discover/esql/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts index f9e179112533d..0ab0e30a45eab 100644 --- a/test/functional/apps/discover/esql/_esql_view.ts +++ b/test/functional/apps/discover/esql/_esql_view.ts @@ -194,8 +194,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const cell = await dataGrid.getCellElementExcludingControlColumns(0, 1); expect(await cell.getVisibleText()).to.be(' - '); expect(await dataGrid.getHeaders()).to.eql([ - 'Control column', 'Select column', + 'Control column', 'Numberbytes', 'machine.ram_range', ]); @@ -253,9 +253,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectTextBaseLang(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.selectIndexPattern('logstash-*', false); + await testSubjects.click('switch-to-dataviews'); await retry.try(async () => { - await testSubjects.existOrFail('unifiedSearch_switch_modal'); + await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); }); @@ -266,19 +266,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.selectIndexPattern('logstash-*', false); + await testSubjects.click('switch-to-dataviews'); await retry.try(async () => { - await testSubjects.existOrFail('unifiedSearch_switch_modal'); + await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); await find.clickByCssSelector( - '[data-test-subj="unifiedSearch_switch_modal"] .euiModal__closeIcon' + '[data-test-subj="discover-esql-to-dataview-modal"] .euiModal__closeIcon' ); await retry.try(async () => { - await testSubjects.missingOrFail('unifiedSearch_switch_modal'); + await testSubjects.missingOrFail('discover-esql-to-dataview-modal'); }); await PageObjects.discover.saveSearch('esql_test'); - await PageObjects.discover.selectIndexPattern('logstash-*'); - await testSubjects.missingOrFail('unifiedSearch_switch_modal'); + await testSubjects.click('switch-to-dataviews'); + await testSubjects.missingOrFail('discover-esql-to-dataview-modal'); }); it('should show switch modal when switching to a data view while a saved search with unsaved changes is open', async () => { @@ -291,9 +291,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.selectIndexPattern('logstash-*', false); + await testSubjects.click('switch-to-dataviews'); await retry.try(async () => { - await testSubjects.existOrFail('unifiedSearch_switch_modal'); + await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); }); }); @@ -339,7 +339,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); const historyItems = await esql.getHistoryItems(); log.debug(historyItems); @@ -362,7 +361,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); const historyItems = await esql.getHistoryItems(); log.debug(historyItems); @@ -379,7 +377,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); // click a history item await esql.clickHistoryItem(1); @@ -405,7 +402,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); const historyItem = await esql.getHistoryItem(0); await historyItem.findByTestSubject('TextBasedLangEditor-queryHistory-error'); @@ -597,7 +593,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await testSubjects.click('TextBasedLangEditor-expand'); await dataGrid.clickCellFilterForButtonExcludingControlColumns(0, 1); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); @@ -630,7 +625,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await testSubjects.click('TextBasedLangEditor-expand'); await dataGrid.clickCellFilterForButtonExcludingControlColumns(0, 1); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index c931187250cc3..e6cf2edca620f 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -216,8 +216,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'America/Phoenix' }); await PageObjects.common.navigateToApp('discover'); await PageObjects.header.awaitKibanaChrome(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); await queryBar.clearQuery(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); log.debug( 'check that the newest doc timestamp is now -7 hours from the UTC time in the first test' diff --git a/test/functional/apps/discover/group1/_discover_histogram.ts b/test/functional/apps/discover/group1/_discover_histogram.ts index e695a7d97ebaa..235b016c7f67e 100644 --- a/test/functional/apps/discover/group1/_discover_histogram.ts +++ b/test/functional/apps/discover/group1/_discover_histogram.ts @@ -66,13 +66,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should modify the time range when the histogram is brushed', async function () { await PageObjects.common.navigateToApp('discover'); await PageObjects.discover.waitUntilSearchingHasFinished(); - // this is the number of renderings of the histogram needed when new data is fetched - let renderingCountInc = 1; const prevRenderingCount = await elasticChart.getVisualizationRenderingCount(); - await queryBar.submitQuery(); await retry.waitFor('chart rendering complete', async () => { const actualCount = await elasticChart.getVisualizationRenderingCount(); - const expectedCount = prevRenderingCount + renderingCountInc; + const expectedCount = prevRenderingCount; log.debug(`renderings before brushing - actual: ${actualCount} expected: ${expectedCount}`); return actualCount === expectedCount; }); @@ -85,7 +82,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.brushHistogram(); await PageObjects.discover.waitUntilSearchingHasFinished(); - renderingCountInc = 2; + const renderingCountInc = 2; await retry.waitFor('chart rendering complete after being brushed', async () => { const actualCount = await elasticChart.getVisualizationRenderingCount(); const expectedCount = prevRenderingCount + renderingCountInc * 2; diff --git a/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts b/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts index 4c0a3aa53759e..55747eec93119 100644 --- a/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts +++ b/test/functional/apps/discover/group2_data_grid3/_data_grid_pagination.ts @@ -14,7 +14,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const dataGrid = getService('dataGrid'); - const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']); + const PageObjects = getPageObjects([ + 'settings', + 'common', + 'discover', + 'header', + 'timePicker', + 'dashboard', + ]); const defaultSettings = { defaultIndex: 'logstash-*', 'discover:rowHeightOption': 0, // single line @@ -22,6 +29,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); const security = getService('security'); + const dashboardAddPanel = getService('dashboardAddPanel'); + const dashboardPanelActions = getService('dashboardPanelActions'); describe('discover data grid pagination', function describeIndexTests() { before(async () => { @@ -118,6 +127,63 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); expect((await dataGrid.getDocTableRows()).length).to.be(10); // as in the saved search await dataGrid.checkCurrentRowsPerPageToBe(10); + + // should use "rowsPerPage" form the saved search on dashboard + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.addSavedSearch(savedSearchTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect((await dataGrid.getDocTableRows()).length).to.be(10); // as in the saved search + await dataGrid.checkCurrentRowsPerPageToBe(10); + + // should use "rowsPerPage" form settings by default on dashboard + await dashboardPanelActions.removePanelByTitle(savedSearchTitle); + await dashboardAddPanel.addSavedSearch('A Saved Search'); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect((await dataGrid.getDocTableRows()).length).to.be(6); // as in settings + await dataGrid.checkCurrentRowsPerPageToBe(6); + }); + + it('should not split ES|QL results into pages', async () => { + const rowsPerPage = 5; + const savedSearchESQL = 'testESQLPagination'; + await kibanaServer.uiSettings.update({ + ...defaultSettings, + 'discover:sampleRowsPerPage': rowsPerPage, + hideAnnouncements: true, + }); + + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + // expect pagination to be present for data view mode + expect((await dataGrid.getDocTableRows()).length).to.be(rowsPerPage); + await dataGrid.checkCurrentRowsPerPageToBe(rowsPerPage); + await testSubjects.existOrFail('pagination-button-0'); + + await PageObjects.discover.selectTextBaseLang(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + // expect no pagination for ES|QL mode + expect((await dataGrid.getDocTableRows()).length).to.above(rowsPerPage); + await testSubjects.missingOrFail('pagination-button-0'); + + await PageObjects.discover.saveSearch(savedSearchESQL); + + await PageObjects.common.navigateToApp('dashboard'); + + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.addSavedSearch(savedSearchESQL); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // expect no pagination for ES|QL mode on Dashboard + expect((await dataGrid.getDocTableRows()).length).to.above(rowsPerPage); + await testSubjects.missingOrFail('pagination-button-0'); }); }); } diff --git a/test/functional/apps/discover/group2_data_grid3/_data_grid_row_selection.ts b/test/functional/apps/discover/group2_data_grid3/_data_grid_row_selection.ts new file mode 100644 index 0000000000000..82532641c58ae --- /dev/null +++ b/test/functional/apps/discover/group2_data_grid3/_data_grid_row_selection.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const dataGrid = getService('dataGrid'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const security = getService('security'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker', 'dashboard']); + + const PAGE_SIZE = 5; + const defaultSettings = { + defaultIndex: 'logstash-*', + 'discover:sampleRowsPerPage': PAGE_SIZE, + hideAnnouncements: true, + }; + + describe('discover data grid row selection', function describeIndexTests() { + before(async function () { + await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await kibanaServer.uiSettings.replace(defaultSettings); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + }); + + beforeEach(async () => { + await PageObjects.common.navigateToApp('discover'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + after(async function () { + await kibanaServer.uiSettings.replace({}); + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('should be able to select rows manually', async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(false); + + await dataGrid.selectRow(1); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(1); + expect(await dataGrid.getNumberOfSelectedRows()).to.be(1); + }); + + await dataGrid.selectRow(2); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(2); + expect(await dataGrid.getNumberOfSelectedRows()).to.be(2); + }); + + // deselect + await dataGrid.selectRow(2); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(1); + expect(await dataGrid.getNumberOfSelectedRows()).to.be(1); + }); + }); + + it('should be able to bulk select rows', async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(false); + expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be( + 'Select all visible rows' + ); + + await dataGrid.selectRow(1); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(1); + expect(await dataGrid.getNumberOfSelectedRows()).to.be(1); + expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be( + 'Deselect all visible rows' + ); + }); + + await dataGrid.toggleSelectAllRowsOnCurrentPage(); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(false); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(0); + expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be( + 'Select all visible rows' + ); + }); + + await dataGrid.toggleSelectAllRowsOnCurrentPage(); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(PAGE_SIZE); + expect(await dataGrid.getNumberOfSelectedRows()).to.be(PAGE_SIZE); + expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be( + 'Deselect all visible rows' + ); + expect(await testSubjects.getVisibleText('dscGridSelectAllDocs')).to.be('Select all 500'); + }); + + await dataGrid.selectAllRows(); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(PAGE_SIZE); + expect(await dataGrid.getNumberOfSelectedRows()).to.be(500); + expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be( + 'Deselect all visible rows' + ); + await testSubjects.missingOrFail('dscGridSelectAllDocs'); + }); + + await dataGrid.toggleSelectAllRowsOnCurrentPage(); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(true); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(0); + expect(await dataGrid.getNumberOfSelectedRows()).to.be(500 - PAGE_SIZE); + expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be( + 'Select all visible rows' + ); + await testSubjects.existOrFail('dscGridSelectAllDocs'); + }); + + await dataGrid.openSelectedRowsMenu(); + await testSubjects.click('dscGridClearSelectedDocuments'); + + await retry.try(async () => { + expect(await dataGrid.isSelectedRowsMenuVisible()).to.be(false); + expect(await dataGrid.getNumberOfSelectedRowsOnCurrentPage()).to.be(0); + expect(await testSubjects.getAttribute('selectAllDocsOnPageToggle', 'title')).to.be( + 'Select all visible rows' + ); + await testSubjects.missingOrFail('dscGridSelectAllDocs'); + }); + }); + }); +} diff --git a/test/functional/apps/discover/group2_data_grid3/index.ts b/test/functional/apps/discover/group2_data_grid3/index.ts index 7200eb1e9bf10..b7a5e25a491b5 100644 --- a/test/functional/apps/discover/group2_data_grid3/index.ts +++ b/test/functional/apps/discover/group2_data_grid3/index.ts @@ -22,6 +22,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_data_grid_row_navigation')); loadTestFile(require.resolve('./_data_grid_row_height')); + loadTestFile(require.resolve('./_data_grid_row_selection')); loadTestFile(require.resolve('./_data_grid_sample_size')); loadTestFile(require.resolve('./_data_grid_pagination')); }); diff --git a/test/functional/apps/discover/group3/_doc_viewer.ts b/test/functional/apps/discover/group3/_doc_viewer.ts index ddcdf5c4e40b7..66f1f74a4ddbe 100644 --- a/test/functional/apps/discover/group3/_doc_viewer.ts +++ b/test/functional/apps/discover/group3/_doc_viewer.ts @@ -6,16 +6,24 @@ * 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 PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'header']); + const PageObjects = getPageObjects([ + 'common', + 'discover', + 'timePicker', + 'header', + 'unifiedFieldList', + ]); const testSubjects = getService('testSubjects'); const find = getService('find'); const retry = getService('retry'); const dataGrid = getService('dataGrid'); + const monacoEditor = getService('monacoEditor'); describe('discover doc viewer', function describeIndexTests() { before(async function () { @@ -103,5 +111,123 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); + + describe('filter by field type', function () { + beforeEach(async () => { + await dataGrid.clickRowToggle(); + await PageObjects.discover.isShowingDocViewer(); + await retry.waitFor('rendered items', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length > 0; + }); + }); + + it('should reveal and hide the filter form when the toggle is clicked', async function () { + await PageObjects.discover.openFilterByFieldTypeInDocViewer(); + expect(await find.allByCssSelector('[data-test-subj*="typeFilter"]')).to.have.length(6); + await PageObjects.discover.closeFilterByFieldTypeInDocViewer(); + }); + + it('should filter by field type', async function () { + const initialFieldsCount = (await find.allByCssSelector('.kbnDocViewer__fieldName')).length; + + await PageObjects.discover.openFilterByFieldTypeInDocViewer(); + + await testSubjects.click('typeFilter-date'); + + await retry.waitFor('first updates', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === 4; + }); + + await testSubjects.click('typeFilter-number'); + + await retry.waitFor('second updates', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === 7; + }); + + await testSubjects.click('unifiedDocViewerFieldsTableFieldTypeFilterClearAll'); + + await retry.waitFor('reset', async () => { + return ( + (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === initialFieldsCount + ); + }); + }); + + it('should show filters by type in ES|QL view', async function () { + await PageObjects.discover.selectTextBaseLang(); + + const testQuery = `from logstash-* | limit 10000`; + await monacoEditor.setCodeEditorValue(testQuery); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + + await dataGrid.clickRowToggle(); + await PageObjects.discover.isShowingDocViewer(); + await retry.waitFor('rendered items', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length > 0; + }); + + const initialFieldsCount = (await find.allByCssSelector('.kbnDocViewer__fieldName')).length; + const numberFieldsCount = 6; + + expect(initialFieldsCount).to.above(numberFieldsCount); + + const pinnedFieldsCount = 1; + await dataGrid.clickFieldActionInFlyout('agent', 'togglePinFilterButton'); + + await PageObjects.discover.openFilterByFieldTypeInDocViewer(); + expect(await find.allByCssSelector('[data-test-subj*="typeFilter"]')).to.have.length(6); + + await testSubjects.click('typeFilter-number'); + + await retry.waitFor('updates', async () => { + return ( + (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === + numberFieldsCount + pinnedFieldsCount + ); + }); + }); + }); + + describe('hide null values switch - ES|QL mode', function () { + beforeEach(async () => { + await PageObjects.discover.selectTextBaseLang(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); + + const testQuery = 'from logstash-* | sort @timestamp | limit 10000'; + await monacoEditor.setCodeEditorValue(testQuery); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); + await dataGrid.clickRowToggle(); + await PageObjects.discover.isShowingDocViewer(); + }); + + afterEach(async () => { + const fieldSearch = await testSubjects.find('clearSearchButton'); + await fieldSearch.click(); // clear search + const hideNullValuesSwitch = await testSubjects.find( + 'unifiedDocViewerHideNullValuesSwitch' + ); + await hideNullValuesSwitch.click(); // make sure the switch is off + }); + + it('should hide fields with null values ', async function () { + await PageObjects.discover.findFieldByNameInDocViewer('machine'); + const results = (await find.allByCssSelector('.kbnDocViewer__fieldName')).length; + const hideNullValuesSwitch = await testSubjects.find( + 'unifiedDocViewerHideNullValuesSwitch' + ); + await hideNullValuesSwitch.click(); + + await retry.waitFor('updates', async () => { + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length < results; + }); + }); + }); }); } diff --git a/test/functional/apps/discover/group6/_sidebar.ts b/test/functional/apps/discover/group6/_sidebar.ts index d3a7da7b2e92f..b268fbdf60502 100644 --- a/test/functional/apps/discover/group6/_sidebar.ts +++ b/test/functional/apps/discover/group6/_sidebar.ts @@ -489,10 +489,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { (await PageObjects.unifiedFieldList.getSidebarSectionFieldNames('selected')).join(', ') ).to.be('countB, geo.dest'); + await PageObjects.unifiedSearch.switchToDataViewMode(); + await PageObjects.unifiedSearch.switchDataView( 'discover-dataView-switch-link', - 'logstash-*', - true + 'logstash-*' ); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/discover/group6/_sidebar_field_stats.ts b/test/functional/apps/discover/group6/_sidebar_field_stats.ts index dd148e43d2d6e..ad015673fe3bb 100644 --- a/test/functional/apps/discover/group6/_sidebar_field_stats.ts +++ b/test/functional/apps/discover/group6/_sidebar_field_stats.ts @@ -169,7 +169,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.unifiedFieldList.clickFieldListPlusFilter('bytes', '0'); - await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`bytes\`==0` @@ -190,7 +189,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.unifiedFieldList.clickFieldListPlusFilter('extension.raw', 'css'); - await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension.raw\`=="css"` @@ -212,7 +210,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.unifiedFieldList.clickFieldListPlusFilter('clientip', '216.126.255.31'); - await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`clientip\`::string=="216.126.255.31"` @@ -238,7 +235,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should not have stats for a date field yet but create an is not null filter', async () => { await PageObjects.unifiedFieldList.clickFieldListItem('@timestamp'); await PageObjects.unifiedFieldList.clickFieldListExistsFilter('@timestamp'); - await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`@timestamp\` is not null` @@ -274,7 +270,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.unifiedFieldList.clickFieldListPlusFilter('extension', 'css'); - await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( `from logstash-* [METADATA _index, _id] | sort @timestamp desc | limit 500\n| WHERE \`extension\`=="css"` @@ -314,7 +309,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.unifiedFieldList.clickFieldListPlusFilter('avg(bytes)', '5453'); - await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql( `from logstash-* | sort @timestamp desc | limit 50 | stats avg(bytes) by geo.dest | limit 3\n| WHERE \`avg(bytes)\`==5453` @@ -343,7 +337,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.unifiedFieldList.clickFieldListMinusFilter('enabled', 'true'); - await testSubjects.click('TextBasedLangEditor-expand'); const editorValue = await monacoEditor.getCodeEditorValue(); expect(editorValue).to.eql(`row enabled = true\n| WHERE \`enabled\`!=true`); await PageObjects.unifiedFieldList.closeFieldPopover(); diff --git a/test/functional/apps/discover/group8/_default_route.ts b/test/functional/apps/discover/group8/_default_route.ts new file mode 100644 index 0000000000000..2740d6e42f6cb --- /dev/null +++ b/test/functional/apps/discover/group8/_default_route.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 and the Server Side Public License, v 1; you may not 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 PageObjects = getPageObjects([ + 'common', + 'home', + 'settings', + 'discover', + 'timePicker', + 'header', + ]); + const kibanaServer = getService('kibanaServer'); + const security = getService('security'); + const retry = getService('retry'); + const deployment = getService('deployment'); + const browser = getService('browser'); + const filterBar = getService('filterBar'); + const queryBar = getService('queryBar'); + + describe('discover as defaultRoute', function () { + before(async function () { + await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + }); + + after(async () => { + await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.uiSettings.replace({}); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('can use a saved search as defaultRoute', async function () { + await kibanaServer.uiSettings.update({ + defaultRoute: '/app/discover#/view/ab12e3c0-f231-11e6-9486-733b1ac9221a', + }); + await browser.navigateTo(deployment.getHostPort()); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await retry.try(async () => { + expect(await PageObjects.discover.getCurrentQueryName()).to.be('A Saved Search'); + expect(await PageObjects.discover.getHitCount()).to.be('14,004'); + }); + }); + + it('can use a URL with filters as defaultRoute', async function () { + await kibanaServer.uiSettings.update({ + defaultRoute: + "/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:60000),time:(from:'2015-09-19T06:31:44.000Z',to:'2015-09-23T18:31:44.000Z'))&_a=(columns:!(extension,host),dataSource:(dataViewId:'logstash-*',type:dataView),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,field:extension.raw,index:'logstash-*',key:extension.raw,negate:!f,params:(query:jpg),type:phrase),query:(match_phrase:(extension.raw:jpg)))),hideChart:!f,interval:auto,query:(language:lucene,query:media),sort:!(!('@timestamp',desc)))", + }); + await browser.navigateTo(deployment.getHostPort()); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await retry.try(async () => { + expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true); + expect(await queryBar.getQueryString()).to.be('media'); + expect(await PageObjects.discover.getHitCount()).to.be('9,109'); + }); + }); + }); +} diff --git a/test/functional/apps/discover/group8/index.ts b/test/functional/apps/discover/group8/index.ts index 09aaca23e8b95..962fe266776a1 100644 --- a/test/functional/apps/discover/group8/index.ts +++ b/test/functional/apps/discover/group8/index.ts @@ -20,6 +20,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); }); + loadTestFile(require.resolve('./_default_route')); loadTestFile(require.resolve('./_hide_announcements')); }); } diff --git a/test/functional/apps/home/_welcome.ts b/test/functional/apps/home/_welcome.ts index 6c8bda90de699..d61afd879090e 100644 --- a/test/functional/apps/home/_welcome.ts +++ b/test/functional/apps/home/_welcome.ts @@ -15,8 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'home']); const deployment = getService('deployment'); - // FLAKY: https://github.com/elastic/kibana/issues/186168 - describe.skip('Welcome interstitial', () => { + describe('Welcome interstitial', () => { beforeEach(async () => { // Need to navigate to page first to clear storage before test can be run await PageObjects.common.navigateToUrl('home', undefined); diff --git a/test/functional/apps/management/data_views/_data_view_create_delete.ts b/test/functional/apps/management/data_views/_data_view_create_delete.ts index 81f455149e2ba..26fe6a9f50df4 100644 --- a/test/functional/apps/management/data_views/_data_view_create_delete.ts +++ b/test/functional/apps/management/data_views/_data_view_create_delete.ts @@ -147,10 +147,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return Promise.all(comparedHeaders); }); }); + + it('should support unmatched index pattern segments', async function () { + await PageObjects.settings.createIndexPattern('l*,z*', '@timestamp'); + const patternName = await PageObjects.settings.getIndexPageHeading(); + expect(patternName).to.be('l*,z*'); + await PageObjects.settings.removeIndexPattern(); + }); }); describe('edit index pattern', () => { it('on edit click', async () => { + await testSubjects.click('detail-link-logstash-*'); await PageObjects.settings.editIndexPattern('logstash-*', '@timestamp', 'Logstash Star'); await retry.try(async () => { diff --git a/test/functional/apps/management/data_views/_field_formatter.ts b/test/functional/apps/management/data_views/_field_formatter.ts index 9754bacee599f..7cfa792c41332 100644 --- a/test/functional/apps/management/data_views/_field_formatter.ts +++ b/test/functional/apps/management/data_views/_field_formatter.ts @@ -549,7 +549,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.setFieldFormat(spec.applyFormatterType); if (spec.beforeSave) { - await spec.beforeSave(await testSubjects.find('formatRow')); + await spec.beforeSave(); } }); }); @@ -604,7 +604,7 @@ interface FieldFormatEditorSpecDescriptor { * Use it set specific configuration params for applied field formatter * @param formatRowContainer - field format editor container */ - beforeSave?: (formatRowContainer: WebElementWrapper) => Promise<void>; + beforeSave?: () => Promise<void>; /** * An expected formatted value rendered by Discover app, diff --git a/test/functional/apps/visualize/group3/_annotation_listing.ts b/test/functional/apps/visualize/group3/_annotation_listing.ts index 541fc6fb5538e..f58f2fd386028 100644 --- a/test/functional/apps/visualize/group3/_annotation_listing.ts +++ b/test/functional/apps/visualize/group3/_annotation_listing.ts @@ -18,8 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); - // Failing: See https://github.com/elastic/kibana/issues/168281 - describe.skip('annotation listing page', function () { + describe('annotation listing page', function () { before(async function () { await kibanaServer.importExport.load( 'test/functional/fixtures/kbn_archiver/annotation_listing_page_search' @@ -49,8 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await listingTable.clearSearchFilter(); }); - // FLAKY: https://github.com/elastic/kibana/issues/168281 - describe.skip('by text', () => { + describe('by text', () => { it('matches on the first word', async function () { await retry.try(async () => { await listingTable.searchForItemWithName('search'); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 81f4bad5b5b1e..3d364d9ff2c3e 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -448,6 +448,19 @@ export class DiscoverPageObject extends FtrService { await fieldSearch.type(name); } + public async openFilterByFieldTypeInDocViewer() { + await this.testSubjects.click('unifiedDocViewerFieldsTableFieldTypeFilterToggle'); + await this.testSubjects.existOrFail('unifiedDocViewerFieldsTableFieldTypeFilterOptions'); + } + + public async closeFilterByFieldTypeInDocViewer() { + await this.testSubjects.click('unifiedDocViewerFieldsTableFieldTypeFilterToggle'); + + await this.retry.waitFor('doc viewer filter closed', async () => { + return !(await this.testSubjects.exists('unifiedDocViewerFieldsTableFieldTypeFilterOptions')); + }); + } + public async getMarks() { const table = await this.docTable.getTable(); const marks = await table.findAllByTagName('mark'); @@ -571,9 +584,10 @@ export class DiscoverPageObject extends FtrService { } public async selectTextBaseLang() { - await this.testSubjects.click('discover-dataView-switch-link'); - await this.testSubjects.click('select-text-based-language-panel'); - await this.header.waitUntilLoadingHasFinished(); + if (await this.testSubjects.exists('select-text-based-language-btn')) { + await this.testSubjects.click('select-text-based-language-btn'); + await this.header.waitUntilLoadingHasFinished(); + } } public async removeHeaderColumn(name: string) { diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index 0c85c51381b94..7dd5ee171afe6 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -57,9 +57,14 @@ export class HomePageObject extends FtrService { } async isWelcomeInterstitialDisplayed() { - // give the interstitial enough time to fade in - await new Promise((resolve) => setTimeout(resolve, 500)); - return await this.testSubjects.isDisplayed('homeWelcomeInterstitial', 2000); + // This element inherits style defined {@link https://github.com/elastic/kibana/blob/v8.14.3/src/core/public/styles/core_app/_mixins.scss#L72|here} + // with an animation duration set to $euiAnimSpeedExtraSlow {@see https://eui.elastic.co/#/theming/more-tokens#animation}, + // hence we setup a delay so the interstitial has enough time to fade in + const animSpeedExtraSlow = 500; + await new Promise((resolve) => setTimeout(resolve, animSpeedExtraSlow)); + return this.retry.try(async () => { + return await this.testSubjects.isDisplayed('homeWelcomeInterstitial', animSpeedExtraSlow * 4); + }); } async isGuidedOnboardingLandingDisplayed() { diff --git a/test/functional/page_objects/unified_search_page.ts b/test/functional/page_objects/unified_search_page.ts index eee70098d2ab1..8b7ee691a9f25 100644 --- a/test/functional/page_objects/unified_search_page.ts +++ b/test/functional/page_objects/unified_search_page.ts @@ -13,21 +13,13 @@ export class UnifiedSearchPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly find = this.ctx.getService('find'); - public async switchDataView( - switchButtonSelector: string, - dataViewTitle: string, - transitionFromTextBasedLanguages?: boolean - ) { + public async switchDataView(switchButtonSelector: string, dataViewTitle: string) { await this.testSubjects.click(switchButtonSelector); const indexPatternSwitcher = await this.testSubjects.find('indexPattern-switcher', 500); await this.testSubjects.setValue('indexPattern-switcher--input', dataViewTitle); await (await indexPatternSwitcher.findByCssSelector(`[title="${dataViewTitle}"]`)).click(); - if (Boolean(transitionFromTextBasedLanguages)) { - await this.testSubjects.click('unifiedSearch_switch_noSave'); - } - await this.retry.waitFor( 'wait for updating switcher', async () => (await this.getSelectedDataView(switchButtonSelector)) === dataViewTitle @@ -50,4 +42,9 @@ export class UnifiedSearchPageObject extends FtrService { `[data-test-subj="text-based-languages-switcher"] [title="${language}"]` ); } + + public async switchToDataViewMode() { + await this.testSubjects.click('switch-to-dataviews'); + await this.testSubjects.click('discover-esql-to-dataview-no-save-btn'); + } } diff --git a/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png b/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png index 3fd3025ebb9a1..94de1a1c3cc4f 100644 Binary files a/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png and b/test/functional/screenshots/baseline/dashboard_embed_mode_scrolling.png differ diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index f95de6a65a48c..70a67d33ffd00 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -139,6 +139,22 @@ export class DataGridService extends FtrService { 'euiDataGridCellExpandButton' ); await actionButton.click(); + await this.retry.waitFor('popover to be opened', async () => { + return await this.testSubjects.exists('euiDataGridExpansionPopover'); + }); + } + + /** + * Clicks grid cell 'expand' action button + * @param rowIndex data row index starting from 0 (0 means 1st row) + * @param columnIndex column index starting from 0 (0 means 1st column) + */ + public async clickCellExpandButtonExcludingControlColumns( + rowIndex: number = 0, + columnIndex: number = 0 + ) { + const controlsCount = await this.getControlColumnsCount(); + await this.clickCellExpandButton(rowIndex, controlsCount + columnIndex); } /** @@ -469,6 +485,13 @@ export class DataGridService extends FtrService { return value; } + public async getCustomRowHeightNumber(scope: 'row' | 'header' = 'row') { + const input = await this.testSubjects.find( + `unifiedDataTable${scope === 'header' ? 'Header' : ''}RowHeightSettings_lineCountNumber` + ); + return Number(await input.getAttribute('value')); + } + public async changeRowHeightValue(newValue: string) { const buttonGroup = await this.testSubjects.find( 'unifiedDataTableRowHeightSettings_rowHeightButtonGroup' @@ -584,6 +607,36 @@ export class DataGridService extends FtrService { await checkbox.click(); } + public async getNumberOfSelectedRows() { + const label = await this.find.byCssSelector( + '[data-test-subj=unifiedDataTableSelectionBtn] .euiNotificationBadge' + ); + return Number(await label.getVisibleText()); + } + + public async getNumberOfSelectedRowsOnCurrentPage() { + const selectedRows = await this.find.allByCssSelector( + '.euiDataGridRow [data-gridcell-column-id="select"] .euiCheckbox__input:checked' + ); + return selectedRows.length; + } + + public async toggleSelectAllRowsOnCurrentPage() { + const checkbox = await this.testSubjects.find('selectAllDocsOnPageToggle'); + + await checkbox.click(); + } + + public async selectAllRows() { + const button = await this.testSubjects.find('dscGridSelectAllDocs'); + + await button.click(); + } + + public async isSelectedRowsMenuVisible() { + return await this.testSubjects.exists('unifiedDataTableSelectionBtn'); + } + public async openSelectedRowsMenu() { await this.testSubjects.click('unifiedDataTableSelectionBtn'); await this.retry.try(async () => { @@ -591,11 +644,22 @@ export class DataGridService extends FtrService { }); } + public async closeSelectedRowsMenu() { + await this.testSubjects.click('unifiedDataTableSelectionBtn'); + await this.retry.try(async () => { + return !(await this.testSubjects.exists('unifiedDataTableSelectionMenu')); + }); + } + public async compareSelectedButtonExists() { - return await this.testSubjects.exists('unifiedDataTableCompareSelectedDocuments'); + await this.openSelectedRowsMenu(); + const exists = await this.testSubjects.exists('unifiedDataTableCompareSelectedDocuments'); + await this.closeSelectedRowsMenu(); + return exists; } public async clickCompareSelectedButton() { + await this.openSelectedRowsMenu(); await this.testSubjects.click('unifiedDataTableCompareSelectedDocuments'); } diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index 9a3e6580db0e9..a3a056358fa24 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -85,7 +85,7 @@ export class ListingTableService extends FtrService { } public async waitUntilTableIsLoaded() { - return this.retry.try(async () => { + await this.retry.try(async () => { const isLoaded = await this.find.existsByDisplayedByCssSelector( '[data-test-subj="itemsInMemTable"]:not(.euiBasicTable-loading)' ); @@ -264,6 +264,10 @@ export class ListingTableService extends FtrService { await searchFilter.type(name); await this.common.pressEnterKey(); + const filterValue = await this.getSearchFilterValue(); + if (filterValue !== name) { + throw new Error(`the input value has not updated properly`); + } }); await this.waitUntilTableIsLoaded(); diff --git a/test/kibana.jsonc b/test/kibana.jsonc index fc078e7405190..f133563c47ca9 100644 --- a/test/kibana.jsonc +++ b/test/kibana.jsonc @@ -1,6 +1,6 @@ { "type": "test-helper", "id": "@kbn/test-suites-src", - "owner": "@elastic/appex-qa", + "owner": [], "devOnly": true } diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index a551030b943ba..ba6ff39066939 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -328,6 +328,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.security.roleManagementEnabled (any)', 'xpack.spaces.maxSpaces (number)', 'xpack.spaces.allowFeatureVisibility (any)', + 'xpack.spaces.allowSolutionVisibility (any)', 'xpack.securitySolution.enableExperimental (array)', 'xpack.securitySolution.prebuiltRulesPackageVersion (string)', 'xpack.securitySolution.offeringSettings (record)', diff --git a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts index c37bd671a764f..3bb078d4d6864 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/hidden_from_http_apis.ts @@ -12,6 +12,11 @@ import type { Response } from 'supertest'; import { SavedObject } from '@kbn/core/types'; import type { PluginFunctionalProviderContext } from '../../services'; +interface MinimalSO { + id: string; + type: string; +} + function parseNdJson(input: string): Array<SavedObject<any>> { return input.split('\n').map((str) => JSON.parse(str)); } @@ -112,10 +117,12 @@ export default function ({ getService }: PluginFunctionalProviderContext) { .expect(200) .then((resp) => { expect( - resp.body.saved_objects.map((so: { id: string; type: string }) => ({ - id: so.id, - type: so.type, - })) + resp.body.saved_objects + .map((so: MinimalSO) => ({ + id: so.id, + type: so.type, + })) + .sort((a: MinimalSO, b: MinimalSO) => (a.id > b.id ? 1 : -1)) ).to.eql([ { id: 'hidden-from-http-apis-1', diff --git a/tsconfig.base.json b/tsconfig.base.json index 60f6ff081014a..e9fb8a5b9c426 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -86,14 +86,14 @@ "@kbn/apm-synthtrace/*": ["packages/kbn-apm-synthtrace/*"], "@kbn/apm-synthtrace-client": ["packages/kbn-apm-synthtrace-client"], "@kbn/apm-synthtrace-client/*": ["packages/kbn-apm-synthtrace-client/*"], + "@kbn/apm-types": ["packages/kbn-apm-types"], + "@kbn/apm-types/*": ["packages/kbn-apm-types/*"], "@kbn/apm-utils": ["packages/kbn-apm-utils"], "@kbn/apm-utils/*": ["packages/kbn-apm-utils/*"], "@kbn/app-link-test-plugin": ["test/plugin_functional/plugins/app_link_test"], "@kbn/app-link-test-plugin/*": ["test/plugin_functional/plugins/app_link_test/*"], "@kbn/application-usage-test-plugin": ["x-pack/test/usage_collection/plugins/application_usage_test"], "@kbn/application-usage-test-plugin/*": ["x-pack/test/usage_collection/plugins/application_usage_test/*"], - "@kbn/assets-data-access-plugin": ["x-pack/plugins/observability_solution/assets_data_access"], - "@kbn/assets-data-access-plugin/*": ["x-pack/plugins/observability_solution/assets_data_access/*"], "@kbn/audit-log-plugin": ["x-pack/test/security_api_integration/plugins/audit_log"], "@kbn/audit-log-plugin/*": ["x-pack/test/security_api_integration/plugins/audit_log/*"], "@kbn/avc-banner": ["packages/kbn-avc-banner"], @@ -746,8 +746,6 @@ "@kbn/docs-utils/*": ["packages/kbn-docs-utils/*"], "@kbn/dom-drag-drop": ["packages/kbn-dom-drag-drop"], "@kbn/dom-drag-drop/*": ["packages/kbn-dom-drag-drop/*"], - "@kbn/ebt": ["packages/analytics/ebt"], - "@kbn/ebt/*": ["packages/analytics/ebt/*"], "@kbn/ebt-tools": ["packages/kbn-ebt-tools"], "@kbn/ebt-tools/*": ["packages/kbn-ebt-tools/*"], "@kbn/ecs-data-quality-dashboard": ["x-pack/packages/security-solution/ecs_data_quality_dashboard"], @@ -778,6 +776,8 @@ "@kbn/encrypted-saved-objects-plugin/*": ["x-pack/plugins/encrypted_saved_objects/*"], "@kbn/enterprise-search-plugin": ["x-pack/plugins/enterprise_search"], "@kbn/enterprise-search-plugin/*": ["x-pack/plugins/enterprise_search/*"], + "@kbn/entities-data-access-plugin": ["x-pack/plugins/observability_solution/entities_data_access"], + "@kbn/entities-data-access-plugin/*": ["x-pack/plugins/observability_solution/entities_data_access/*"], "@kbn/entities-schema": ["x-pack/packages/kbn-entities-schema"], "@kbn/entities-schema/*": ["x-pack/packages/kbn-entities-schema/*"], "@kbn/entityManager-plugin": ["x-pack/plugins/observability_solution/entity_manager"], @@ -992,6 +992,8 @@ "@kbn/index-patterns-test-plugin/*": ["test/plugin_functional/plugins/index_patterns/*"], "@kbn/inference_integration_flyout": ["x-pack/packages/ml/inference_integration_flyout"], "@kbn/inference_integration_flyout/*": ["x-pack/packages/ml/inference_integration_flyout/*"], + "@kbn/inference-plugin": ["x-pack/plugins/inference"], + "@kbn/inference-plugin/*": ["x-pack/plugins/inference/*"], "@kbn/infra-forge": ["x-pack/packages/kbn-infra-forge"], "@kbn/infra-forge/*": ["x-pack/packages/kbn-infra-forge/*"], "@kbn/infra-plugin": ["x-pack/plugins/observability_solution/infra"], @@ -1544,6 +1546,8 @@ "@kbn/server-http-tools/*": ["packages/kbn-server-http-tools/*"], "@kbn/server-route-repository": ["packages/kbn-server-route-repository"], "@kbn/server-route-repository/*": ["packages/kbn-server-route-repository/*"], + "@kbn/server-route-repository-client": ["packages/kbn-server-route-repository-client"], + "@kbn/server-route-repository-client/*": ["packages/kbn-server-route-repository-client/*"], "@kbn/server-route-repository-utils": ["packages/kbn-server-route-repository-utils"], "@kbn/server-route-repository-utils/*": ["packages/kbn-server-route-repository-utils/*"], "@kbn/serverless": ["x-pack/plugins/serverless"], @@ -1716,6 +1720,8 @@ "@kbn/synthetics-e2e/*": ["x-pack/plugins/observability_solution/synthetics/e2e/*"], "@kbn/synthetics-plugin": ["x-pack/plugins/observability_solution/synthetics"], "@kbn/synthetics-plugin/*": ["x-pack/plugins/observability_solution/synthetics/*"], + "@kbn/synthetics-private-location": ["x-pack/packages/kbn-synthetics-private-location"], + "@kbn/synthetics-private-location/*": ["x-pack/packages/kbn-synthetics-private-location/*"], "@kbn/task-manager-fixture-plugin": ["x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture"], "@kbn/task-manager-fixture-plugin/*": ["x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture/*"], "@kbn/task-manager-performance-plugin": ["x-pack/test/plugin_api_perf/plugins/task_manager_performance"], diff --git a/versions.json b/versions.json index 3816626bb83ca..4d3ab259480e9 100644 --- a/versions.json +++ b/versions.json @@ -8,19 +8,13 @@ "currentMinor": true }, { - "version": "8.15.0", + "version": "8.15.1", "branch": "8.15", "currentMajor": true, "previousMinor": true }, { - "version": "8.14.4", - "branch": "8.14", - "currentMajor": true, - "previousMinor": true - }, - { - "version": "7.17.23", + "version": "7.17.24", "branch": "7.17", "previousMajor": true } diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index d0a336369ed29..7ff0f3e3ef766 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -54,6 +54,7 @@ "xpack.fleet": "plugins/fleet", "xpack.ingestPipelines": "plugins/ingest_pipelines", "xpack.integrationAssistant": "plugins/integration_assistant", + "xpack.inference": "plugins/inference", "xpack.investigate": "plugins/observability_solution/investigate", "xpack.investigateApp": "plugins/observability_solution/investigate_app", "xpack.kubernetesSecurity": "plugins/kubernetes_security", diff --git a/x-pack/packages/kbn-data-forge/src/data_sources/service_logs/index.ts b/x-pack/packages/kbn-data-forge/src/data_sources/service_logs/index.ts index a74afb5bd7328..b22b60574c3c2 100644 --- a/x-pack/packages/kbn-data-forge/src/data_sources/service_logs/index.ts +++ b/x-pack/packages/kbn-data-forge/src/data_sources/service_logs/index.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { faker } from '@faker-js/faker'; import { omit, sample } from 'lodash'; import { SERVICE_LOGS } from '../../constants'; import { GeneratorFunction } from '../../types'; import { generateService } from './lib/generate_service'; +import { generateLogMessage } from './lib/generate_log_message'; export const generateEvent: GeneratorFunction = (_config, _schedule, index, timestamp) => { const service = generateService(index + 1); @@ -19,10 +19,10 @@ export const generateEvent: GeneratorFunction = (_config, _schedule, index, time { namespace: SERVICE_LOGS, '@timestamp': timestamp.toISOString(), - message: faker.git.commitMessage(), data_stream: { type: 'logs', dataset: SERVICE_LOGS, namespace: 'default' }, service: omit(service, 'hostsWithCloud'), ...hostWithCloud, + ...generateLogMessage(timestamp), }, ]; }; diff --git a/x-pack/packages/kbn-data-forge/src/data_sources/service_logs/lib/generate_log_message.ts b/x-pack/packages/kbn-data-forge/src/data_sources/service_logs/lib/generate_log_message.ts new file mode 100644 index 0000000000000..168b749855a35 --- /dev/null +++ b/x-pack/packages/kbn-data-forge/src/data_sources/service_logs/lib/generate_log_message.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { faker } from '@faker-js/faker'; +import { Moment } from 'moment'; +import { URL } from 'node:url'; + +// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" + +export function generateLogMessage(timestamp: Moment) { + const statusCode = faker.internet.httpStatusCode(); + const method = faker.internet.httpMethod(); + const logLevel = statusCode >= 500 ? 'error' : 'info'; + const userAgent = faker.internet.userAgent(); + const remoteAddress = faker.internet.ipv4(); + const path = `/api/${faker.word.noun()}/${faker.word.verb()}`; + const rawUrl = `${faker.internet.url()}/${path}`; + const parsedUrl = new URL(rawUrl); + const bytesSent = parseInt( + faker.string.numeric({ length: { min: 3, max: 10 }, allowLeadingZeros: false }), + 10 + ); + const message = `${remoteAddress} - - [${timestamp.toISOString()}] "${method} ${path} HTTP/1.1" ${statusCode} ${bytesSent} "${ + parsedUrl.origin + }" "${userAgent}"`; + return { + message, + log: { level: logLevel }, + url: { + domain: parsedUrl.hostname, + port: parsedUrl.port || 80, + full: rawUrl, + original: rawUrl, + path, + scheme: parsedUrl.protocol, + }, + http: { + request: { + method, + referrer: parsedUrl.origin, + mime_type: 'application/json', + body: { + bytes: parseInt(faker.string.numeric(3), 10), + }, + }, + response: { + bytes: bytesSent, + mime_type: 'application/json', + status_code: statusCode, + }, + version: '1.1', + }, + user_agent: { + original: userAgent, + }, + }; +} diff --git a/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/ess/elastic_assistant_api_2023_10_31.bundled.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/ess/elastic_assistant_api_2023_10_31.bundled.schema.yaml index cceb3c90a27ab..34d7625691d25 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/ess/elastic_assistant_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/ess/elastic_assistant_api_2023_10_31.bundled.schema.yaml @@ -65,6 +65,7 @@ paths: description: Generic Error summary: Applies a bulk action to multiple anonymization fields tags: + - Security AI Assistant API - Bulk API /api/security_ai_assistant/anonymization_fields/_find: get: @@ -150,6 +151,7 @@ paths: description: Generic Error summary: Finds anonymization fields that match the given query. tags: + - Security AI Assistant API - AnonymizationFields API /api/security_ai_assistant/chat/complete: post: @@ -184,6 +186,7 @@ paths: description: Generic Error summary: Creates a model response for the given chat conversation. tags: + - Security AI Assistant API - Chat Complete API /api/security_ai_assistant/current_user/conversations: post: @@ -217,6 +220,7 @@ paths: description: Generic Error summary: Create a conversation tags: + - Security AI Assistant API - Conversation API /api/security_ai_assistant/current_user/conversations/_find: get: @@ -302,6 +306,7 @@ paths: description: Generic Error summary: Finds conversations that match the given query. tags: + - Security AI Assistant API - Conversations API '/api/security_ai_assistant/current_user/conversations/{id}': delete: @@ -336,6 +341,7 @@ paths: description: Generic Error summary: Deletes a single conversation using the `id` field. tags: + - Security AI Assistant API - Conversation API get: description: Read a single conversation @@ -369,6 +375,7 @@ paths: description: Generic Error summary: Read a single conversation tags: + - Security AI Assistant API - Conversations API put: description: Update a single conversation @@ -408,6 +415,7 @@ paths: description: Generic Error summary: Update a conversation tags: + - Security AI Assistant API - Conversation API /api/security_ai_assistant/prompts/_bulk_action: post: @@ -463,6 +471,7 @@ paths: description: Generic Error summary: Applies a bulk action to multiple prompts tags: + - Security AI Assistant API - Bulk API /api/security_ai_assistant/prompts/_find: get: @@ -548,6 +557,7 @@ paths: description: Generic Error summary: Finds prompts that match the given query. tags: + - Security AI Assistant API - Prompts API components: schemas: @@ -1222,3 +1232,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: Manage and interact with Security Assistant resources. + name: Security AI Assistant API diff --git a/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/serverless/elastic_assistant_api_2023_10_31.bundled.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/serverless/elastic_assistant_api_2023_10_31.bundled.schema.yaml index 9fa31b0920aba..4e5d49eb12051 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/serverless/elastic_assistant_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/docs/openapi/serverless/elastic_assistant_api_2023_10_31.bundled.schema.yaml @@ -65,6 +65,7 @@ paths: description: Generic Error summary: Applies a bulk action to multiple anonymization fields tags: + - Security AI Assistant API - Bulk API /api/security_ai_assistant/anonymization_fields/_find: get: @@ -150,6 +151,7 @@ paths: description: Generic Error summary: Finds anonymization fields that match the given query. tags: + - Security AI Assistant API - AnonymizationFields API /api/security_ai_assistant/chat/complete: post: @@ -184,6 +186,7 @@ paths: description: Generic Error summary: Creates a model response for the given chat conversation. tags: + - Security AI Assistant API - Chat Complete API /api/security_ai_assistant/current_user/conversations: post: @@ -217,6 +220,7 @@ paths: description: Generic Error summary: Create a conversation tags: + - Security AI Assistant API - Conversation API /api/security_ai_assistant/current_user/conversations/_find: get: @@ -302,6 +306,7 @@ paths: description: Generic Error summary: Finds conversations that match the given query. tags: + - Security AI Assistant API - Conversations API '/api/security_ai_assistant/current_user/conversations/{id}': delete: @@ -336,6 +341,7 @@ paths: description: Generic Error summary: Deletes a single conversation using the `id` field. tags: + - Security AI Assistant API - Conversation API get: description: Read a single conversation @@ -369,6 +375,7 @@ paths: description: Generic Error summary: Read a single conversation tags: + - Security AI Assistant API - Conversations API put: description: Update a single conversation @@ -408,6 +415,7 @@ paths: description: Generic Error summary: Update a conversation tags: + - Security AI Assistant API - Conversation API /api/security_ai_assistant/prompts/_bulk_action: post: @@ -463,6 +471,7 @@ paths: description: Generic Error summary: Applies a bulk action to multiple prompts tags: + - Security AI Assistant API - Bulk API /api/security_ai_assistant/prompts/_find: get: @@ -548,6 +557,7 @@ paths: description: Generic Error summary: Finds prompts that match the given query. tags: + - Security AI Assistant API - Prompts API components: schemas: @@ -1222,3 +1232,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: Manage and interact with Security Assistant resources. + name: Security AI Assistant API diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts index 1e759df2819ed..d97f2aef17b4b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts @@ -21,5 +21,5 @@ export type AssistantFeatureKey = keyof AssistantFeatures; export const defaultAssistantFeatures = Object.freeze({ assistantKnowledgeBaseByDefault: false, assistantModelEvaluation: false, - assistantBedrockChat: false, + assistantBedrockChat: true, }); diff --git a/x-pack/packages/kbn-elastic-assistant-common/scripts/openapi/bundle.js b/x-pack/packages/kbn-elastic-assistant-common/scripts/openapi/bundle.js index eb45fe104ad48..63d21a2d93ae3 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/scripts/openapi/bundle.js +++ b/x-pack/packages/kbn-elastic-assistant-common/scripts/openapi/bundle.js @@ -21,9 +21,17 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..'); ), options: { includeLabels: ['serverless'], - specInfo: { - title: 'Security AI Assistant API (Elastic Cloud Serverless)', - description: 'Manage and interact with Security Assistant resources.', + prototypeDocument: { + info: { + title: 'Security AI Assistant API (Elastic Cloud Serverless)', + description: 'Manage and interact with Security Assistant resources.', + }, + tags: [ + { + name: 'Security AI Assistant API', + description: 'Manage and interact with Security Assistant resources.', + }, + ], }, }, }); @@ -36,9 +44,17 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..'); ), options: { includeLabels: ['ess'], - specInfo: { - title: 'Security AI Assistant API (Elastic Cloud & self-hosted)', - description: 'Manage and interact with Security Assistant resources.', + prototypeDocument: { + info: { + title: 'Security AI Assistant API (Elastic Cloud & self-hosted)', + description: 'Manage and interact with Security Assistant resources.', + }, + tags: [ + { + name: 'Security AI Assistant API', + description: 'Manage and interact with Security Assistant resources.', + }, + ], }, }, }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.test.tsx index 0eb9d0a916c44..4d6b80b3e60a4 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.test.tsx @@ -7,9 +7,10 @@ import React from 'react'; import { ConnectorSelector } from '.'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { TestProviders } from '../../mock/test_providers/test_providers'; import { mockActionTypes, mockConnectors } from '../../mock/connectors'; +import * as i18n from '../translations'; const onConnectorSelectionChange = jest.fn(); const setIsOpen = jest.fn(); @@ -107,4 +108,28 @@ describe('Connector selector', () => { expect(mockRefetchConnectors).toHaveBeenCalled(); expect(setIsOpen).toHaveBeenCalledWith(false); }); + + it('renders the expected placeholder when selectedConnectorId is undefined', () => { + render( + <TestProviders> + <ConnectorSelector {...defaultProps} selectedConnectorId={undefined} /> + </TestProviders> + ); + + expect(screen.getByTestId('connector-selector')).toHaveTextContent( + i18n.INLINE_CONNECTOR_PLACEHOLDER + ); + }); + + it('does NOT render the placeholder when selectedConnectorId is defined', () => { + render( + <TestProviders> + <ConnectorSelector {...defaultProps} /> + </TestProviders> + ); + + expect(screen.getByTestId('connector-selector')).not.toHaveTextContent( + i18n.INLINE_CONNECTOR_PLACEHOLDER + ); + }); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx index ad0fffc44e6b5..12cb0a30c0c9c 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_selector/index.tsx @@ -6,11 +6,13 @@ */ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSuperSelect, EuiText } from '@elastic/eui'; +import { css } from '@emotion/css'; import React, { Suspense, useCallback, useMemo, useState } from 'react'; import { ActionConnector, ActionType } from '@kbn/triggers-actions-ui-plugin/public'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/openai/constants'; +import { euiThemeVars } from '@kbn/ui-theme'; import { some } from 'lodash'; import type { AttackDiscoveryStats } from '@kbn/elastic-assistant-common'; import { AttackDiscoveryStatusIndicator } from './attack_discovery_status_indicator'; @@ -23,6 +25,13 @@ import { AddConnectorModal } from '../add_connector_modal'; export const ADD_NEW_CONNECTOR = 'ADD_NEW_CONNECTOR'; +const placeholderCss = css` + .euiSuperSelectControl__placeholder { + color: ${euiThemeVars.euiColorPrimary}; + margin-right: ${euiThemeVars.euiSizeXS}; + } +`; + interface Props { isDisabled?: boolean; isOpen?: boolean; @@ -187,6 +196,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo( ) : ( <EuiSuperSelect aria-label={i18n.CONNECTOR_SELECTOR_TITLE} + className={placeholderCss} compressed={true} data-test-subj="connector-selector" disabled={localIsDisabled} @@ -195,6 +205,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo( onChange={onChange} options={allConnectorOptions} valueOfSelected={selectedConnectorId} + placeholder={i18n.INLINE_CONNECTOR_PLACEHOLDER} popoverProps={{ panelMinWidth: 400, anchorPosition: 'downRight' }} /> )} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts index 11d214afc4faf..c5642e54188d3 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/translations.ts @@ -52,7 +52,7 @@ export const ADD_CONNECTOR = i18n.translate( export const INLINE_CONNECTOR_PLACEHOLDER = i18n.translate( 'xpack.elasticAssistant.assistant.connectors.connectorSelectorInline.connectorPlaceholder', { - defaultMessage: 'Select a Connector', + defaultMessage: 'Select a connector', } ); diff --git a/x-pack/packages/kbn-entities-schema/index.ts b/x-pack/packages/kbn-entities-schema/index.ts index 8251e1c14755f..33a989cb8d804 100644 --- a/x-pack/packages/kbn-entities-schema/index.ts +++ b/x-pack/packages/kbn-entities-schema/index.ts @@ -8,6 +8,7 @@ export * from './src/schema/entity_definition'; export * from './src/schema/entity'; export * from './src/schema/common'; +export * from './src/schema/patterns'; export * from './src/rest_spec/delete'; export * from './src/rest_spec/reset'; export * from './src/rest_spec/get'; diff --git a/x-pack/packages/kbn-entities-schema/kibana.jsonc b/x-pack/packages/kbn-entities-schema/kibana.jsonc index 9895c2074a584..732a640df908b 100644 --- a/x-pack/packages/kbn-entities-schema/kibana.jsonc +++ b/x-pack/packages/kbn-entities-schema/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/entities-schema", - "owner": "@elastic/obs-knowledge-team" + "owner": "@elastic/obs-entities" } diff --git a/x-pack/packages/kbn-entities-schema/src/schema/common.test.ts b/x-pack/packages/kbn-entities-schema/src/schema/common.test.ts index c03bff2db74c0..f59363866c37a 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/common.test.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/common.test.ts @@ -5,9 +5,7 @@ * 2.0. */ -import { SafeParseSuccess } from 'zod'; -import { durationSchema, metadataSchema, semVerSchema } from './common'; -import moment from 'moment'; +import { durationSchema, metadataSchema, semVerSchema, historySettingsSchema } from './common'; describe('schemas', () => { describe('metadataSchema', () => { @@ -60,38 +58,46 @@ describe('schemas', () => { expect(result).toMatchSnapshot(); }); }); + describe('durationSchema', () => { it('should work with 1m', () => { const result = durationSchema.safeParse('1m'); expect(result.success).toBeTruthy(); - expect((result as SafeParseSuccess<moment.Duration>).data.toJSON()).toBe('1m'); - expect((result as SafeParseSuccess<moment.Duration>).data.asSeconds()).toEqual(60); + expect(result.data).toBe('1m'); }); it('should work with 10s', () => { const result = durationSchema.safeParse('10s'); expect(result.success).toBeTruthy(); - expect((result as SafeParseSuccess<moment.Duration>).data.toJSON()).toBe('10s'); - expect((result as SafeParseSuccess<moment.Duration>).data.asSeconds()).toEqual(10); + expect(result.data).toBe('10s'); }); it('should work with 999h', () => { const result = durationSchema.safeParse('999h'); expect(result.success).toBeTruthy(); - expect((result as SafeParseSuccess<moment.Duration>).data.toJSON()).toBe('999h'); - expect((result as SafeParseSuccess<moment.Duration>).data.asSeconds()).toEqual(999 * 60 * 60); + expect(result.data).toBe('999h'); }); it('should work with 90d', () => { const result = durationSchema.safeParse('90d'); expect(result.success).toBeTruthy(); - expect((result as SafeParseSuccess<moment.Duration>).data.toJSON()).toBe('90d'); - expect((result as SafeParseSuccess<moment.Duration>).data.asSeconds()).toEqual( - 90 * 24 * 60 * 60 - ); + expect(result.data).toBe('90d'); }); it('should not work with 1ms', () => { const result = durationSchema.safeParse('1ms'); expect(result.success).toBeFalsy(); }); + it('should not work with invalid values', () => { + let result = durationSchema.safeParse('PT1H'); + expect(result.success).toBeFalsy(); + result = durationSchema.safeParse('1H'); + expect(result.success).toBeFalsy(); + result = durationSchema.safeParse('1f'); + expect(result.success).toBeFalsy(); + result = durationSchema.safeParse('foo'); + expect(result.success).toBeFalsy(); + result = durationSchema.safeParse(' 1h '); + expect(result.success).toBeFalsy(); + }); }); + describe('semVerSchema', () => { it('should validate with 999.999.999', () => { const result = semVerSchema.safeParse('999.999.999'); @@ -103,4 +109,30 @@ describe('schemas', () => { expect(result).toMatchSnapshot(); }); }); + + describe('historySettingsSchema', () => { + it('should return default values when not defined', () => { + let result = historySettingsSchema.safeParse(undefined); + expect(result.success).toBeTruthy(); + expect(result.data).toEqual({ lookbackPeriod: '1h' }); + + result = historySettingsSchema.safeParse({ syncDelay: '1m' }); + expect(result.success).toBeTruthy(); + expect(result.data).toEqual({ syncDelay: '1m', lookbackPeriod: '1h' }); + }); + + it('should return user defined values when defined', () => { + const result = historySettingsSchema.safeParse({ + lookbackPeriod: '30m', + syncField: 'event.ingested', + syncDelay: '5m', + }); + expect(result.success).toBeTruthy(); + expect(result.data).toEqual({ + lookbackPeriod: '30m', + syncField: 'event.ingested', + syncDelay: '5m', + }); + }); + }); }); diff --git a/x-pack/packages/kbn-entities-schema/src/schema/common.ts b/x-pack/packages/kbn-entities-schema/src/schema/common.ts index 5df208ab9c27d..a7f0f97d755f0 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/common.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/common.ts @@ -44,20 +44,21 @@ export const docCountMetricSchema = z.object({ filter: filterSchema, }); -export const durationSchema = z - .string() - .regex(/^\d+[m|d|s|h]$/) - .transform((val: string) => { - const parts = val.match(/(\d+)([m|s|h|d])/); - if (parts === null) { - throw new Error('Unable to parse duration'); - } - const value = parseInt(parts[1], 10); - const unit = parts[2] as 'm' | 's' | 'h' | 'd'; - const duration = moment.duration(value, unit); - duration.toJSON = () => val; - return duration; - }); +export const durationSchema = z.string().regex(/^\d+[m|d|s|h]$/); + +export const durationSchemaWithMinimum = (minimumMinutes: number) => + durationSchema.refine( + (val: string) => { + const parts = val.match(/(\d+)([m|s|h|d])/); + if (parts === null) { + throw new Error('Unable to parse duration'); + } + const value = parseInt(parts[1], 10); + const unit = parts[2] as 'm' | 's' | 'h' | 'd'; + return moment.duration(value, unit).asMinutes() >= minimumMinutes; + }, + { message: `can not be less than ${minimumMinutes}m` } + ); export const percentileMetricSchema = z.object({ name: metricNameSchema, @@ -131,3 +132,22 @@ export const semVerSchema = z.string().refine((maybeSemVer) => semVerRegex.test( message: 'The string does use the Semantic Versioning (Semver) format of {major}.{minor}.{patch} (e.g., 1.0.0), ensure each part contains only digits.', }); + +export const historySettingsSchema = z + .optional( + z.object({ + syncField: z.optional(z.string()), + syncDelay: z.optional(durationSchema), + lookbackPeriod: z.optional(durationSchema).default('1h'), + frequency: z.optional(durationSchema), + backfillSyncDelay: z.optional(durationSchema), + backfillLookbackPeriod: z.optional(durationSchema), + backfillFrequency: z.optional(durationSchema), + }) + ) + .transform((settings) => { + return { + ...settings, + lookbackPeriod: settings?.lookbackPeriod || durationSchema.parse('1h'), + }; + }); diff --git a/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts b/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts index c297a2d5542ae..bcb26cf8818db 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts @@ -14,6 +14,8 @@ import { durationSchema, identityFieldsSchema, semVerSchema, + historySettingsSchema, + durationSchemaWithMinimum, } from './common'; export const entityDefinitionSchema = z.object({ @@ -32,27 +34,16 @@ export const entityDefinitionSchema = z.object({ managed: z.optional(z.boolean()).default(false), history: z.object({ timestampField: z.string(), - interval: durationSchema.refine((val) => val.asMinutes() >= 1, { - message: 'The history.interval can not be less than 1m', - }), - settings: z.optional( - z.object({ - syncField: z.optional(z.string()), - syncDelay: z.optional(z.string()), - frequency: z.optional(z.string()), - backfillSyncDelay: z.optional(z.string()), - backfillLookbackPeriod: z.optional(durationSchema), - backfillFrequency: z.optional(z.string()), - }) - ), + interval: durationSchemaWithMinimum(1), + settings: historySettingsSchema, }), latest: z.optional( z.object({ settings: z.optional( z.object({ syncField: z.optional(z.string()), - syncDelay: z.optional(z.string()), - frequency: z.optional(z.string()), + syncDelay: z.optional(durationSchema), + frequency: z.optional(durationSchema), }) ), }) diff --git a/x-pack/packages/kbn-entities-schema/src/schema/patterns.test.ts b/x-pack/packages/kbn-entities-schema/src/schema/patterns.test.ts new file mode 100644 index 0000000000000..0d049152991f1 --- /dev/null +++ b/x-pack/packages/kbn-entities-schema/src/schema/patterns.test.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 { entitiesIndexPattern, entitiesAliasPattern } from './patterns'; + +describe('index/alias pattern helpers', () => { + describe('entitiesIndexPattern', () => { + it('generates a index pattern', () => { + expect( + entitiesIndexPattern({ + definitionId: 'my-definition', + schemaVersion: 'v1', + dataset: 'latest', + }) + ).toEqual('.entities.v1.latest.my-definition'); + }); + }); + + describe('entitiesAliasPattern', () => { + it('generates a alias pattern', () => { + expect( + entitiesAliasPattern({ + type: 'service', + dataset: 'latest', + }) + ).toEqual('entities-service-latest'); + }); + }); +}); diff --git a/x-pack/packages/kbn-entities-schema/src/schema/patterns.ts b/x-pack/packages/kbn-entities-schema/src/schema/patterns.ts new file mode 100644 index 0000000000000..2481769f2b473 --- /dev/null +++ b/x-pack/packages/kbn-entities-schema/src/schema/patterns.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. + */ + +export const ENTITY_BASE_PREFIX = 'entities'; +export const ENTITY_HISTORY = 'history' as const; +export const ENTITY_LATEST = 'latest' as const; + +export const ENTITY_SCHEMA_VERSION_V1 = 'v1'; + +type SchemaVersion = `v${number}`; +type Dataset = typeof ENTITY_LATEST | typeof ENTITY_HISTORY; + +interface IndexPatternOptions<TDataset extends Dataset> { + dataset: TDataset; + schemaVersion: SchemaVersion; + definitionId: string; +} + +interface AliasPatternOptions<TDataset extends Dataset> { + dataset: TDataset; + type: string; +} + +export function entitiesIndexPattern<TDataset extends Dataset>({ + schemaVersion, + dataset, + definitionId, +}: IndexPatternOptions<TDataset>) { + return `.${ENTITY_BASE_PREFIX}.${schemaVersion}.${dataset}.${definitionId}` as const; +} + +export function entitiesAliasPattern<TDataset extends Dataset>({ + type, + dataset, +}: AliasPatternOptions<TDataset>) { + return `${ENTITY_BASE_PREFIX}-${type}-${dataset}` as const; +} diff --git a/x-pack/packages/kbn-langchain/server/language_models/bedrock_chat.ts b/x-pack/packages/kbn-langchain/server/language_models/bedrock_chat.ts index 3381da798a249..ac229b97c8757 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/bedrock_chat.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/bedrock_chat.ts @@ -69,7 +69,7 @@ export class ActionsClientBedrockChatModel extends _BedrockChat { params: { subAction: 'invokeAIRaw', subActionParams: { - messages: inputBody.messages, + messages: prepareMessages(inputBody.messages), temperature: params.temperature ?? inputBody.temperature, stopSequences: inputBody.stop_sequences, system: inputBody.system, @@ -99,3 +99,20 @@ export class ActionsClientBedrockChatModel extends _BedrockChat { }); } } + +const prepareMessages = (messages: Array<{ role: string; content: string[] }>) => + messages.reduce((acc, { role, content }) => { + const lastMessage = acc[acc.length - 1]; + + if (!lastMessage || lastMessage.role !== role) { + acc.push({ role, content }); + return acc; + } + + if (lastMessage.role === role) { + acc[acc.length - 1].content = lastMessage.content.concat(content); + return acc; + } + + return acc; + }, [] as Array<{ role: string; content: string[] }>); diff --git a/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts b/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts index 1a691737cac1c..a34f34ecce482 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/gemini_chat.ts @@ -69,7 +69,7 @@ export class ActionsClientGeminiChatModel extends ChatGoogleGenerativeAI { } async completionWithRetry( - request: string | GenerateContentRequest | Array<string | Part>, + request: GenerateContentRequest, options?: this['ParsedCallOptions'] ): Promise<GenerateContentResult> { return this.caller.callWithOptions({ signal: options?.signal }, async () => { @@ -80,7 +80,8 @@ export class ActionsClientGeminiChatModel extends ChatGoogleGenerativeAI { subAction: 'invokeAIRaw', subActionParams: { model: this.#model, - messages: request, + messages: request.contents, + tools: request.tools, temperature: this.#temperature, }, }, diff --git a/x-pack/packages/kbn-synthetics-private-location/README.md b/x-pack/packages/kbn-synthetics-private-location/README.md new file mode 100644 index 0000000000000..57fea6207dd10 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/README.md @@ -0,0 +1,56 @@ +# @kbn/synthetics-private-location + +Quickily start Fleet, enroll Elastic Agent, and create a private location. + +## Usage + +``` +node x-pack/scripts/synthetics_private_location.js +``` + +For available options, run `--help`. + +## Prerequistes + +This script requires `docker` and the following `kibama.yml` configuration. + +``` +# Create an agent policy for Fleet Server. +xpack.fleet.agentPolicies: + - name: Fleet Server policy + id: fleet-server-policy + is_default_fleet_server: true + # is_managed: true # Useful to mimic cloud environment + description: Fleet server policy + namespace: default + package_policies: + - name: Fleet Server + package: + name: fleet_server + inputs: + - type: fleet-server + keep_enabled: true + vars: + - name: host + value: 0.0.0.0 + frozen: true + - name: port + value: 8220 + frozen: true + +# Set a default Fleet Server host. +xpack.fleet.fleetServerHosts: + - id: default-fleet-server + name: Default Fleet server + is_default: true + host_urls: ['https://host.docker.internal:8220'] # For running a Fleet Server Docker container + +# Set a default Elasticsearch output. +xpack.fleet.outputs: + - id: es-default-output + name: Default output + type: elasticsearch + is_default: true + is_default_monitoring: true + hosts: ['http://host.docker.internal:9200'] # For enrolling dockerized agents +``` diff --git a/x-pack/packages/kbn-synthetics-private-location/index.ts b/x-pack/packages/kbn-synthetics-private-location/index.ts new file mode 100644 index 0000000000000..9411c1c3b0eea --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/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 type { CliOptions } from './src/types'; +export { run } from './src/run'; +export { cli } from './src/cli'; +// export { cleanup } from './src/cleanup'; +export { DEFAULTS } from './src/constants'; diff --git a/x-pack/packages/kbn-synthetics-private-location/jest.config.js b/x-pack/packages/kbn-synthetics-private-location/jest.config.js new file mode 100644 index 0000000000000..658017a2f17c9 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['<rootDir>/x-pack/packages/kbn-synthetics-private-location'], +}; diff --git a/x-pack/packages/kbn-synthetics-private-location/kibana.jsonc b/x-pack/packages/kbn-synthetics-private-location/kibana.jsonc new file mode 100644 index 0000000000000..6ff6d3f1645c2 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/synthetics-private-location", + "owner": "@elastic/obs-ux-management-team" +} diff --git a/x-pack/packages/kbn-synthetics-private-location/package.json b/x-pack/packages/kbn-synthetics-private-location/package.json new file mode 100644 index 0000000000000..3b3e435e6feb3 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/synthetics-private-location", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/packages/kbn-synthetics-private-location/src/cli.ts b/x-pack/packages/kbn-synthetics-private-location/src/cli.ts new file mode 100644 index 0000000000000..4a25b5323b7ab --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/cli.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 { ToolingLog } from '@kbn/tooling-log'; +import { parseCliOptions } from './lib/parse_cli_options'; +import { CliOptions } from './types'; +import { run } from './run'; + +export async function cli(cliOptions?: CliOptions) { + const options = cliOptions ?? parseCliOptions(); + const logger = new ToolingLog({ level: 'info', writeTo: process.stdout }); + return run(options, logger); +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/constants.ts b/x-pack/packages/kbn-synthetics-private-location/src/constants.ts new file mode 100644 index 0000000000000..41fcfcb112c6b --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/constants.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 { v4 as uuidv4 } from 'uuid'; + +export const DEFAULTS = { + LOCATION_NAME: `Default location ${uuidv4()}`, + AGENT_POLICY_NAME: `Synthetics agent policy ${uuidv4()}`, + ELASTICSEARCH_HOST: 'http://localhost:9200', + KIBANA_URL: 'http://localhost:5601', + KIBANA_USERNAME: 'elastic', + KIBANA_PASSWORD: 'changeme', +}; diff --git a/x-pack/packages/kbn-synthetics-private-location/src/lib/create_agent_policy.ts b/x-pack/packages/kbn-synthetics-private-location/src/lib/create_agent_policy.ts new file mode 100644 index 0000000000000..5171abcca6906 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/lib/create_agent_policy.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 { isError } from 'lodash'; +import { ToolingLog } from '@kbn/tooling-log'; +import { CliOptions } from '../types'; +import type { KibanaAPIClient } from './kibana_api_client'; + +export async function createElasticAgentPolicy( + { agentPolicyName }: CliOptions, + logger: ToolingLog, + kibanaApiClient: KibanaAPIClient +) { + try { + const response = await kibanaApiClient.sendRequest({ + method: 'post', + url: 'api/fleet/agent_policies', + data: { + name: agentPolicyName, + description: '', + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + inactivity_timeout: 1209600, + is_protected: false, + }, + }); + + logger.info(`Generated elastic agent policy`); + return response.data; + } catch (error) { + if (isError(error)) { + logger.error(`Error generating elastic agent policy: ${error.message} ${error.stack}`); + } + throw error; + } +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/lib/create_private_location.ts b/x-pack/packages/kbn-synthetics-private-location/src/lib/create_private_location.ts new file mode 100644 index 0000000000000..a0d389eafa4a5 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/lib/create_private_location.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 { isError } from 'lodash'; +import { ToolingLog } from '@kbn/tooling-log'; +import { CliOptions } from '../types'; +import { KibanaAPIClient } from './kibana_api_client'; + +export async function createPrivateLocation( + { kibanaUrl, kibanaPassword, kibanaUsername, locationName }: CliOptions, + logger: ToolingLog, + kibanaApiClient: KibanaAPIClient, + agentPolicyId: string +) { + try { + const response = await kibanaApiClient.sendRequest({ + method: 'post', + url: 'api/synthetics/private_locations', + data: { + label: locationName, + agentPolicyId, + }, + }); + + logger.info(`Synthetics private location created successfully`); + return response.data; + } catch (error) { + if (isError(error)) { + logger.error(`Error creating synthetics private location: ${error.message} ${error.stack}`); + } + throw error; + } +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/lib/enroll_agent.ts b/x-pack/packages/kbn-synthetics-private-location/src/lib/enroll_agent.ts new file mode 100644 index 0000000000000..143ff08642fff --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/lib/enroll_agent.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 { spawn, spawnSync } from 'child_process'; +import * as path from 'path'; +import { CliOptions } from '../types'; +import { KibanaAPIClient } from './kibana_api_client'; + +export async function enrollAgent( + { kibanaUrl, elasticsearchHost }: CliOptions, + enrollmentToken: string, + kibanaApiClient: KibanaAPIClient +) { + const formattedKibanaURL = new URL(kibanaUrl); + const formattedElasticsearchHost = new URL(elasticsearchHost); + if (formattedKibanaURL.hostname === 'localhost') { + formattedKibanaURL.hostname = 'host.docker.internal'; + } + if (formattedElasticsearchHost.hostname === 'localhost') { + formattedElasticsearchHost.hostname = 'host.docker.internal'; + } + const version = `${await kibanaApiClient.getKibanaVersion()}-SNAPSHOT`; + await new Promise((res, rej) => { + try { + const fleetProcess = spawn( + 'docker', + [ + 'run', + '-e', + 'FLEET_SERVER_ENABLE=1', + '-e', + `FLEET_SERVER_ELASTICSEARCH_HOST=${formattedElasticsearchHost.origin}`, + '-e', + 'FLEET_SERVER_POLICY_ID=fleet-server-policy', + '-e', + 'FLEET_INSECURE=1', + '-e', + `KIBANA_HOST=${formattedKibanaURL.origin}`, + '-e', + 'KIBANA_USERNAME=elastic', + '-e', + 'KIBANA_PASSWORD=changeme', + '-e', + 'KIBANA_FLEET_SETUP=1', + '-p', + '8220:8220', + '--rm', + `docker.elastic.co/beats/elastic-agent:${version}`, + ], + { + shell: true, + cwd: path.join(__dirname, '../'), + timeout: 120000, + } + ); + setTimeout(res, 10_000); + fleetProcess.on('error', rej); + } catch (error) { + rej(error); + } + }); + + spawnSync( + 'docker', + [ + 'run', + '-e', + 'FLEET_URL=https://host.docker.internal:8220', + '-e', + 'FLEET_ENROLL=1', + '-e', + `FLEET_ENROLLMENT_TOKEN=${enrollmentToken}`, + '-e', + 'FLEET_INSECURE=1', + '--rm', + `docker.elastic.co/beats/elastic-agent-complete:${version}`, + ], + { + stdio: 'inherit', + } + ); +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/lib/fetch_agent_policy_enrollment_token.ts b/x-pack/packages/kbn-synthetics-private-location/src/lib/fetch_agent_policy_enrollment_token.ts new file mode 100644 index 0000000000000..0eeecc2529289 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/lib/fetch_agent_policy_enrollment_token.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 { isError } from 'lodash'; +import { ToolingLog } from '@kbn/tooling-log'; +import { KibanaAPIClient } from './kibana_api_client'; +import { CliOptions } from '../types'; + +export async function fetchAgentPolicyEnrollmentToken( + { kibanaUrl, kibanaPassword, kibanaUsername }: CliOptions, + logger: ToolingLog, + kibanaApiClient: KibanaAPIClient, + agentPolicyId: string +) { + try { + const response = await kibanaApiClient.sendRequest({ + method: 'get', + url: `api/fleet/enrollment_api_keys?kuery=policy_id:${agentPolicyId}`, + }); + + logger.info(`Fetching agent policy enrollment token`); + return response.data; + } catch (error) { + if (isError(error)) { + logger.error(`Error fetching agent enrollment token: ${error.message} ${error.stack}`); + } + throw error; + } +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/lib/generate_fleet_service_token.ts b/x-pack/packages/kbn-synthetics-private-location/src/lib/generate_fleet_service_token.ts new file mode 100644 index 0000000000000..c754840ed8a11 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/lib/generate_fleet_service_token.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 { isError } from 'lodash'; +import { ToolingLog } from '@kbn/tooling-log'; +import { CliOptions } from '../types'; +import { KibanaAPIClient } from './kibana_api_client'; + +export async function generateFleetServiceToken( + { kibanaUrl, kibanaPassword, kibanaUsername }: CliOptions, + logger: ToolingLog, + kibanaApiClient: KibanaAPIClient +) { + try { + const response = await kibanaApiClient.sendRequest({ + method: 'post', + url: 'api/fleet/service_tokens', + }); + + logger.info(`Generated fleet server service token saved`); + return response.data; + } catch (error) { + if (isError(error)) { + logger.error(`Error generating fleet server service token: ${error.message} ${error.stack}`); + } + throw error; + } +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/lib/kibana_api_client.ts b/x-pack/packages/kbn-synthetics-private-location/src/lib/kibana_api_client.ts new file mode 100644 index 0000000000000..f6f0709218499 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/lib/kibana_api_client.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isError } from 'lodash'; +import { ToolingLog } from '@kbn/tooling-log'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import fs from 'fs'; +import https, { Agent } from 'https'; +import axios from 'axios'; + +export class KibanaAPIClient { + private isHTTPS: boolean; + private httpsAgent: Agent | undefined; + + constructor( + private kibanaUrl: string, + private kibanaUsername: string, + private kibanaPassword: string, + private logger: ToolingLog + ) { + this.isHTTPS = new URL(kibanaUrl).protocol === 'https:'; + this.httpsAgent = this.isHTTPS + ? new https.Agent({ + ca: fs.readFileSync(KBN_CERT_PATH), + key: fs.readFileSync(KBN_KEY_PATH), + // hard-coded set to false like in packages/kbn-cli-dev-mode/src/base_path_proxy_server.ts + rejectUnauthorized: false, + }) + : undefined; + } + + public async sendRequest({ + method, + url, + data, + headers, + }: { + method: string; + url: string; + data?: Record<string, unknown>; + headers?: Record<string, unknown>; + }) { + try { + const response = await axios({ + method, + url: `${this.kibanaUrl}/${url}`, + data, + headers: { + 'kbn-xsrf': 'true', + 'elastic-api-version': '2023-10-31', + ...headers, + }, + auth: { + username: this.kibanaUsername, + password: this.kibanaPassword, + }, + httpsAgent: this.httpsAgent, + }); + return response; + } catch (e) { + if (isError(e)) { + this.logger.error(`Error sending request to Kibana: ${e.message} ${e.stack}`); + } + throw e; + } + } + + public async getKibanaVersion() { + const res = await this.sendRequest({ method: 'GET', url: 'api/status' }); + return res.data.version.number; + } +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/lib/parse_cli_options.ts b/x-pack/packages/kbn-synthetics-private-location/src/lib/parse_cli_options.ts new file mode 100644 index 0000000000000..f4fc29411db1d --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/lib/parse_cli_options.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 { Command } from 'commander'; +import { CliOptions } from '../types'; +import { DEFAULTS } from '../constants'; + +export function parseCliOptions(): CliOptions { + const program = new Command(); + program + .name('synthetics_private_location.js') + .description( + 'A script to start Fleet Server, enroll Elastic Agent, and create a Synthetics private location' + ) + .option( + '--elasticsearch-host <address>', + 'The address to the Elasticsearch cluster', + DEFAULTS.ELASTICSEARCH_HOST + ) + .option('--kibana-url <address>', 'The address to the Kibana server', DEFAULTS.KIBANA_URL) + .option( + '--kibana-username <username>', + 'The username for the Kibana server', + DEFAULTS.KIBANA_USERNAME + ) + .option( + '--kibana-password <password>', + 'The password for the Kibana server', + DEFAULTS.KIBANA_PASSWORD + ) + .option( + '--location-name <name>', + 'The name of the Synthetics private location', + DEFAULTS.LOCATION_NAME + ) + .option( + '--agent-policy-name <name>', + 'The name of the agent policy', + DEFAULTS.AGENT_POLICY_NAME + ); + + program.parse(process.argv); + return program.opts() as CliOptions; +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/run.ts b/x-pack/packages/kbn-synthetics-private-location/src/run.ts new file mode 100644 index 0000000000000..c9a0931b3733d --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/run.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 { ToolingLog } from '@kbn/tooling-log'; +import type { CliOptions } from './types'; +import { createElasticAgentPolicy } from './lib/create_agent_policy'; +import { fetchAgentPolicyEnrollmentToken } from './lib/fetch_agent_policy_enrollment_token'; +import { enrollAgent } from './lib/enroll_agent'; +import { createPrivateLocation } from './lib/create_private_location'; +import { KibanaAPIClient } from './lib/kibana_api_client'; + +export async function run(options: CliOptions, logger: ToolingLog) { + const kibanaClient = new KibanaAPIClient( + options.kibanaUrl, + options.kibanaUsername, + options.kibanaPassword, + logger + ); + const { + item: { id: agentPolicyId }, + } = await createElasticAgentPolicy(options, logger, kibanaClient); + await createPrivateLocation(options, logger, kibanaClient, agentPolicyId); + const { list } = await fetchAgentPolicyEnrollmentToken( + options, + logger, + kibanaClient, + agentPolicyId + ); + const [enrollmentTokenConfig] = list; + const { api_key: enrollmentToken } = enrollmentTokenConfig; + enrollAgent(options, enrollmentToken, kibanaClient); +} diff --git a/x-pack/packages/kbn-synthetics-private-location/src/types.ts b/x-pack/packages/kbn-synthetics-private-location/src/types.ts new file mode 100644 index 0000000000000..1ec7f6ad9d595 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/src/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. + */ + +export interface CliOptions { + locationName: string; + agentPolicyName: string; + kibanaUrl: string; + kibanaUsername: string; + kibanaPassword: string; + elasticsearchHost: string; +} diff --git a/x-pack/packages/kbn-synthetics-private-location/tsconfig.json b/x-pack/packages/kbn-synthetics-private-location/tsconfig.json new file mode 100644 index 0000000000000..c6a5932133cb3 --- /dev/null +++ b/x-pack/packages/kbn-synthetics-private-location/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/tooling-log", + "@kbn/dev-utils", + ] +} diff --git a/x-pack/packages/ml/agg_utils/index.ts b/x-pack/packages/ml/agg_utils/index.ts index 049623ffddb38..4b4d41811ae0d 100644 --- a/x-pack/packages/ml/agg_utils/index.ts +++ b/x-pack/packages/ml/agg_utils/index.ts @@ -14,11 +14,12 @@ export { numberValidator } from './src/validate_number'; export type { FieldsForHistograms, + NumericDataItem, NumericChartData, NumericHistogramField, } from './src/fetch_histograms_for_fields'; export { isMultiBucketAggregate } from './src/is_multi_bucket_aggregate'; -export { isSignificantItem } from './src/type_guards'; +export { isSignificantItem, isSignificantItemGroup } from './src/type_guards'; export { SIGNIFICANT_ITEM_TYPE } from './src/types'; export type { AggCardinality, diff --git a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts index f74ebbaac3994..4646b390f6d2b 100644 --- a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts +++ b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts @@ -31,6 +31,11 @@ interface AggHistogram { histogram: { field: string; interval: number; + min_doc_count?: number; + extended_bounds?: { + min: number; + max: number; + }; }; } @@ -45,7 +50,7 @@ interface AggTerms { * Represents an item in numeric data. * @interface */ -interface NumericDataItem { +export interface NumericDataItem { /** * The numeric key. */ diff --git a/x-pack/packages/ml/agg_utils/src/type_guards.ts b/x-pack/packages/ml/agg_utils/src/type_guards.ts index 7d8dbc69b265e..c057699caf531 100644 --- a/x-pack/packages/ml/agg_utils/src/type_guards.ts +++ b/x-pack/packages/ml/agg_utils/src/type_guards.ts @@ -7,7 +7,7 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import type { SignificantItem } from './types'; +import type { SignificantItem, SignificantItemGroup } from './types'; /** * Type guard for a significant item. @@ -15,7 +15,7 @@ import type { SignificantItem } from './types'; * for a p-value based variant, not a generic significant terms * aggregation type. * @param arg The unknown type to be evaluated - * @returns whether arg is of type SignificantItem + * @returns Return whether arg is of type SignificantItem */ export function isSignificantItem(arg: unknown): arg is SignificantItem { return isPopulatedObject(arg, [ @@ -32,3 +32,11 @@ export function isSignificantItem(arg: unknown): arg is SignificantItem { 'normalizedScore', ]); } +/** + * Type guard to check if the given argument is a SignificantItemGroup. + * @param arg The unknown type to be evaluated + * @returns Return whether arg is of type SignificantItemGroup + */ +export function isSignificantItemGroup(arg: unknown): arg is SignificantItemGroup { + return isPopulatedObject(arg, ['id', 'group', 'docCount', 'pValue']); +} diff --git a/x-pack/packages/ml/agg_utils/src/types.ts b/x-pack/packages/ml/agg_utils/src/types.ts index 4c02846a24038..843172e323799 100644 --- a/x-pack/packages/ml/agg_utils/src/types.ts +++ b/x-pack/packages/ml/agg_utils/src/types.ts @@ -180,25 +180,13 @@ interface SignificantItemHistogramItemBase { } /** - * @deprecated since version 2 of internal log rate analysis REST API endpoint + * Represents a data item in a significant term histogram. */ -interface SignificantItemHistogramItemV1 extends SignificantItemHistogramItemBase { - /** The document count for this item in the significant term context. */ - doc_count_significant_term: number; -} - -interface SignificantItemHistogramItemV2 extends SignificantItemHistogramItemBase { +export interface SignificantItemHistogramItem extends SignificantItemHistogramItemBase { /** The document count for this histogram item in the significant item context. */ doc_count_significant_item: number; } -/** - * Represents a data item in a significant term histogram. - */ -export type SignificantItemHistogramItem = - | SignificantItemHistogramItemV1 - | SignificantItemHistogramItemV2; - /** * Represents histogram data for a field/value pair. * @interface diff --git a/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts b/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts index efb6eb2c3e23f..c3556803745a7 100644 --- a/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts +++ b/x-pack/packages/ml/aiops_log_pattern_analysis/create_category_request.ts @@ -112,11 +112,11 @@ export function createCategoryRequest( return { params: { index, - size: 0, body: { query, aggs: wrap(aggs), ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), + size: 0, }, }, }; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_result.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_result.ts new file mode 100644 index 0000000000000..4b233c4d67bce --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_result.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. + */ + +export const topCategoriesResultMock = [ + { + bg_count: 0, + doc_count: 1642, + fieldName: 'message', + fieldValue: + '71.231.222.196 - - [2018-08-13T05:04:08.731Z] "GET /kibana/kibana-6.3.2-windows-x86_64.zip HTTP/1.1" 200 15139 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1"', + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux x86_64 rv Gecko/20110421 Firefox/6.0a1', + normalizedScore: 0, + pValue: 1, + score: 0, + total_bg_count: 0, + total_doc_count: 0, + type: 'log_pattern', + }, + { + bg_count: 0, + doc_count: 1488, + fieldName: 'message', + fieldValue: + '7.210.210.41 - - [2018-08-13T04:20:49.558Z] "GET /elasticsearch/elasticsearch-6.3.2.deb HTTP/1.1" 404 6699 "-" "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24"', + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux i686 AppleWebKit/534.24 KHTML like Gecko Chrome/11.0.696.50 Safari/534.24', + normalizedScore: 0, + pValue: 1, + score: 0, + total_bg_count: 0, + total_doc_count: 0, + type: 'log_pattern', + }, +]; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_search_response.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_search_response.ts new file mode 100644 index 0000000000000..4114620a1b5c6 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_categories_search_response.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. + */ + +export const topCategoriesSearchResponseMock = { + took: 98, + responses: [ + { + took: 98, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 4413, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + categories: { + buckets: [ + { + doc_count: 1642, + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux x86_64 rv Gecko/20110421 Firefox/6.0a1', + regex: + '.*?GET.+?HTTP/1\\.1.+?Mozilla/5\\.0.+?X11.+?Linux.+?x86_64.+?rv.+?Gecko/20110421.+?Firefox/6\\.0a1.*?', + max_matching_length: 233, + examples: { + hits: { + total: { value: 1642, relation: 'eq' }, + max_score: null, + hits: [ + { + _index: '.ds-kibana_sample_data_logs-2024.07.08-000001', + _id: 'zpkLk5AB4oRN3GwDmOW1', + _score: null, + _source: { + message: + '71.231.222.196 - - [2018-08-13T05:04:08.731Z] "GET /kibana/kibana-6.3.2-windows-x86_64.zip HTTP/1.1" 200 15139 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1"', + }, + sort: [1721624648731], + }, + ], + }, + }, + }, + { + doc_count: 1488, + key: 'GET HTTP/1.1 Mozilla/5.0 X11 Linux i686 AppleWebKit/534.24 KHTML like Gecko Chrome/11.0.696.50 Safari/534.24', + regex: + '.*?GET.+?HTTP/1\\.1.+?Mozilla/5\\.0.+?X11.+?Linux.+?i686.+?AppleWebKit/534\\.24.+?KHTML.+?like.+?Gecko.+?Chrome/11\\.0\\.696\\.50.+?Safari/534\\.24.*?', + max_matching_length: 266, + examples: { + hits: { + total: { value: 1488, relation: 'eq' }, + max_score: null, + hits: [ + { + _index: '.ds-kibana_sample_data_logs-2024.07.08-000001', + _id: 'VpkLk5AB4oRN3GwDmOW1', + _score: null, + _source: { + message: + '7.210.210.41 - - [2018-08-13T04:20:49.558Z] "GET /elasticsearch/elasticsearch-6.3.2.deb HTTP/1.1" 404 6699 "-" "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24"', + }, + sort: [1721622049558], + }, + ], + }, + }, + }, + ], + }, + }, + status: 200, + }, + ], +}; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_result.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_result.ts new file mode 100644 index 0000000000000..e3290941646b0 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_result.ts @@ -0,0 +1,1311 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 topTermsResult = [ + { + key: 'agent.keyword:Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', + type: 'keyword', + fieldName: 'agent.keyword', + fieldValue: + 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', + doc_count: 179, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'agent.keyword:Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1', + type: 'keyword', + fieldName: 'agent.keyword', + fieldValue: 'Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1', + doc_count: 87, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'agent.keyword:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)', + type: 'keyword', + fieldName: 'agent.keyword', + fieldValue: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)', + doc_count: 63, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:30.156.16.164', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '30.156.16.164', + doc_count: 100, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:107.152.89.90', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '107.152.89.90', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:112.106.69.227', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '112.106.69.227', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:160.20.100.193', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '160.20.100.193', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:186.153.168.71', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '186.153.168.71', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:16.241.165.21', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '16.241.165.21', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:20.129.3.8', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '20.129.3.8', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:24.42.142.201', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '24.42.142.201', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:43.86.71.5', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '43.86.71.5', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'clientip:50.184.59.162', + type: 'keyword', + fieldName: 'clientip', + fieldValue: '50.184.59.162', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'event.dataset:sample_web_logs', + type: 'keyword', + fieldName: 'event.dataset', + fieldValue: 'sample_web_logs', + doc_count: 329, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: '', + doc_count: 196, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:gz', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'gz', + doc_count: 42, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:zip', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'zip', + doc_count: 28, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:css', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'css', + doc_count: 27, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:deb', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'deb', + doc_count: 26, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'extension.keyword:rpm', + type: 'keyword', + fieldName: 'extension.keyword', + fieldValue: 'rpm', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:IN', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'IN', + doc_count: 135, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:CN', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'CN', + doc_count: 38, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:US', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'US', + doc_count: 18, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:ID', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'ID', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:BD', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'BD', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:BR', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'BR', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:NG', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'NG', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:AR', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'AR', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:DE', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'DE', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.dest:ET', + type: 'keyword', + fieldName: 'geo.dest', + fieldValue: 'ET', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.src:US', + type: 'keyword', + fieldName: 'geo.src', + fieldValue: 'US', + doc_count: 329, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:IN', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:IN', + doc_count: 135, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:CN', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:CN', + doc_count: 38, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:US', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:US', + doc_count: 18, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:ID', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:ID', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:BD', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:BD', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:BR', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:BR', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:NG', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:NG', + doc_count: 7, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:AR', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:AR', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:DE', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:DE', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'geo.srcdest:US:ET', + type: 'keyword', + fieldName: 'geo.srcdest', + fieldValue: 'US:ET', + doc_count: 4, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:elastic-elastic-elastic.org', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'elastic-elastic-elastic.org', + doc_count: 112, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:artifacts.elastic.co', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'artifacts.elastic.co', + doc_count: 106, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:www.elastic.co', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'www.elastic.co', + doc_count: 84, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'host.keyword:cdn.elastic-elastic-elastic.org', + type: 'keyword', + fieldName: 'host.keyword', + fieldValue: 'cdn.elastic-elastic-elastic.org', + doc_count: 27, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'index.keyword:kibana_sample_data_logs', + type: 'keyword', + fieldName: 'index.keyword', + fieldValue: 'kibana_sample_data_logs', + doc_count: 329, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:30.156.16.163', + type: 'keyword', + fieldName: 'ip', + fieldValue: '30.156.16.163', + doc_count: 101, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:107.152.89.90', + type: 'keyword', + fieldName: 'ip', + fieldValue: '107.152.89.90', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:112.106.69.227', + type: 'keyword', + fieldName: 'ip', + fieldValue: '112.106.69.227', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:160.20.100.193', + type: 'keyword', + fieldName: 'ip', + fieldValue: '160.20.100.193', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:186.153.168.71', + type: 'keyword', + fieldName: 'ip', + fieldValue: '186.153.168.71', + doc_count: 3, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:16.241.165.21', + type: 'keyword', + fieldName: 'ip', + fieldValue: '16.241.165.21', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:20.129.3.8', + type: 'keyword', + fieldName: 'ip', + fieldValue: '20.129.3.8', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:24.42.142.201', + type: 'keyword', + fieldName: 'ip', + fieldValue: '24.42.142.201', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:43.86.71.5', + type: 'keyword', + fieldName: 'ip', + fieldValue: '43.86.71.5', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'ip:50.184.59.162', + type: 'keyword', + fieldName: 'ip', + fieldValue: '50.184.59.162', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:win xp', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'win xp', + doc_count: 148, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:osx', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'osx', + doc_count: 50, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:ios', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'ios', + doc_count: 44, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:win 7', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'win 7', + doc_count: 44, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'machine.os.keyword:win 8', + type: 'keyword', + fieldName: 'machine.os.keyword', + fieldValue: 'win 8', + doc_count: 43, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://www.elastic-elastic-elastic.com/success/timothy-l-kopra', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://www.elastic-elastic-elastic.com/success/timothy-l-kopra', + doc_count: 101, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/daniel-barry', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/daniel-barry', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/mark-kelly', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/mark-kelly', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/pavel-popovich', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/pavel-popovich', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://facebook.com/success/scott-altman', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://facebook.com/success/scott-altman', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/dafydd-williams', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/dafydd-williams', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/valentin-lebedev', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/valentin-lebedev', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/viktor-m-afanasyev', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/viktor-m-afanasyev', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://twitter.com/success/y-ng-l-w-i', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://twitter.com/success/y-ng-l-w-i', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'referer:http://www.elastic-elastic-elastic.com/success/georgi-dobrovolski', + type: 'keyword', + fieldName: 'referer', + fieldValue: 'http://www.elastic-elastic-elastic.com/success/georgi-dobrovolski', + doc_count: 2, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/apm', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/apm', + doc_count: 19, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats', + doc_count: 13, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats/filebeat', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats/filebeat', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/elasticsearch/elasticsearch-6.3.2.tar.gz', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/elasticsearch/elasticsearch-6.3.2.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/apm-server/apm-server-6.3.2-windows-x86.zip', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/apm-server/apm-server-6.3.2-windows-x86.zip', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats/metricbeat', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats/metricbeat', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'request.keyword:/elasticsearch/elasticsearch-6.3.2.deb', + type: 'keyword', + fieldName: 'request.keyword', + fieldValue: '/elasticsearch/elasticsearch-6.3.2.deb', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'response.keyword:200', + type: 'keyword', + fieldName: 'response.keyword', + fieldValue: '200', + doc_count: 210, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'response.keyword:404', + type: 'keyword', + fieldName: 'response.keyword', + fieldValue: '404', + doc_count: 110, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'response.keyword:503', + type: 'keyword', + fieldName: 'response.keyword', + fieldValue: '503', + doc_count: 9, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:success', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'success', + doc_count: 295, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:info', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'info', + doc_count: 279, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:security', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'security', + doc_count: 37, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:warning', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'warning', + doc_count: 23, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:login', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'login', + doc_count: 13, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'tags.keyword:error', + type: 'keyword', + fieldName: 'tags.keyword', + fieldValue: 'error', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/apm', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/apm', + doc_count: 16, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/beats', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/beats', + doc_count: 13, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/beats/filebeat', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/beats/filebeat', + doc_count: 11, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/apm-server/apm-server-6.3.2-windows-x86.zip', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: + 'https://artifacts.elastic.co/downloads/apm-server/apm-server-6.3.2-windows-x86.zip', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/beats/metricbeat', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/beats/metricbeat', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, + { + key: 'url.keyword:https://www.elastic.co/downloads/enterprise', + type: 'keyword', + fieldName: 'url.keyword', + fieldValue: 'https://www.elastic.co/downloads/enterprise', + doc_count: 10, + bg_count: 0, + total_doc_count: 0, + total_bg_count: 0, + score: 0, + pValue: 1, + normalizedScore: 0, + }, +]; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_search_response.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_search_response.ts new file mode 100644 index 0000000000000..ef2f0f9eb6a26 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/top_terms_search_response.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 topTermsSearchResponseMock = { + took: 8, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 329, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + top_terms_0: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.50 Safari/534.24', + doc_count: 179, + }, + { + key: 'Mozilla/5.0 (X11; Linux x86_64; rv:6.0a1) Gecko/20110421 Firefox/6.0a1', + doc_count: 87, + }, + { + key: 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)', + doc_count: 63, + }, + ], + }, + top_terms_1: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 207, + buckets: [ + { key: '30.156.16.164', doc_count: 100 }, + { key: '107.152.89.90', doc_count: 3 }, + { key: '112.106.69.227', doc_count: 3 }, + { key: '160.20.100.193', doc_count: 3 }, + { key: '186.153.168.71', doc_count: 3 }, + { key: '16.241.165.21', doc_count: 2 }, + { key: '20.129.3.8', doc_count: 2 }, + { key: '24.42.142.201', doc_count: 2 }, + { key: '43.86.71.5', doc_count: 2 }, + { key: '50.184.59.162', doc_count: 2 }, + ], + }, + top_terms_2: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'sample_web_logs', doc_count: 329 }], + }, + top_terms_3: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: '', doc_count: 196 }, + { key: 'gz', doc_count: 42 }, + { key: 'zip', doc_count: 28 }, + { key: 'css', doc_count: 27 }, + { key: 'deb', doc_count: 26 }, + { key: 'rpm', doc_count: 10 }, + ], + }, + top_terms_4: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 95, + buckets: [ + { key: 'IN', doc_count: 135 }, + { key: 'CN', doc_count: 38 }, + { key: 'US', doc_count: 18 }, + { key: 'ID', doc_count: 10 }, + { key: 'BD', doc_count: 7 }, + { key: 'BR', doc_count: 7 }, + { key: 'NG', doc_count: 7 }, + { key: 'AR', doc_count: 4 }, + { key: 'DE', doc_count: 4 }, + { key: 'ET', doc_count: 4 }, + ], + }, + top_terms_5: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'US', doc_count: 329 }], + }, + top_terms_6: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 95, + buckets: [ + { key: 'US:IN', doc_count: 135 }, + { key: 'US:CN', doc_count: 38 }, + { key: 'US:US', doc_count: 18 }, + { key: 'US:ID', doc_count: 10 }, + { key: 'US:BD', doc_count: 7 }, + { key: 'US:BR', doc_count: 7 }, + { key: 'US:NG', doc_count: 7 }, + { key: 'US:AR', doc_count: 4 }, + { key: 'US:DE', doc_count: 4 }, + { key: 'US:ET', doc_count: 4 }, + ], + }, + top_terms_7: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'elastic-elastic-elastic.org', doc_count: 112 }, + { key: 'artifacts.elastic.co', doc_count: 106 }, + { key: 'www.elastic.co', doc_count: 84 }, + { key: 'cdn.elastic-elastic-elastic.org', doc_count: 27 }, + ], + }, + top_terms_8: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'kibana_sample_data_logs', doc_count: 329 }], + }, + top_terms_9: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 206, + buckets: [ + { key: '30.156.16.163', doc_count: 101 }, + { key: '107.152.89.90', doc_count: 3 }, + { key: '112.106.69.227', doc_count: 3 }, + { key: '160.20.100.193', doc_count: 3 }, + { key: '186.153.168.71', doc_count: 3 }, + { key: '16.241.165.21', doc_count: 2 }, + { key: '20.129.3.8', doc_count: 2 }, + { key: '24.42.142.201', doc_count: 2 }, + { key: '43.86.71.5', doc_count: 2 }, + { key: '50.184.59.162', doc_count: 2 }, + ], + }, + top_terms_10: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'win xp', doc_count: 148 }, + { key: 'osx', doc_count: 50 }, + { key: 'ios', doc_count: 44 }, + { key: 'win 7', doc_count: 44 }, + { key: 'win 8', doc_count: 43 }, + ], + }, + top_terms_11: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 210, + buckets: [ + { key: 'http://www.elastic-elastic-elastic.com/success/timothy-l-kopra', doc_count: 101 }, + { key: 'http://facebook.com/success/daniel-barry', doc_count: 2 }, + { key: 'http://facebook.com/success/mark-kelly', doc_count: 2 }, + { key: 'http://facebook.com/success/pavel-popovich', doc_count: 2 }, + { key: 'http://facebook.com/success/scott-altman', doc_count: 2 }, + { key: 'http://twitter.com/success/dafydd-williams', doc_count: 2 }, + { key: 'http://twitter.com/success/valentin-lebedev', doc_count: 2 }, + { key: 'http://twitter.com/success/viktor-m-afanasyev', doc_count: 2 }, + { key: 'http://twitter.com/success/y-ng-l-w-i', doc_count: 2 }, + { + key: 'http://www.elastic-elastic-elastic.com/success/georgi-dobrovolski', + doc_count: 2, + }, + ], + }, + top_terms_12: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 214, + buckets: [ + { key: '/apm', doc_count: 19 }, + { key: '/beats', doc_count: 13 }, + { key: '/beats/filebeat', doc_count: 11 }, + { key: '/elasticsearch/elasticsearch-6.3.2.tar.gz', doc_count: 11 }, + { key: '/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', doc_count: 11 }, + { key: '/', doc_count: 10 }, + { key: '/apm-server/apm-server-6.3.2-windows-x86.zip', doc_count: 10 }, + { key: '/beats/metricbeat', doc_count: 10 }, + { key: '/beats/metricbeat/metricbeat-6.3.2-i686.rpm', doc_count: 10 }, + { key: '/elasticsearch/elasticsearch-6.3.2.deb', doc_count: 10 }, + ], + }, + top_terms_13: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: '200', doc_count: 210 }, + { key: '404', doc_count: 110 }, + { key: '503', doc_count: 9 }, + ], + }, + top_terms_14: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'success', doc_count: 295 }, + { key: 'info', doc_count: 279 }, + { key: 'security', doc_count: 37 }, + { key: 'warning', doc_count: 23 }, + { key: 'login', doc_count: 13 }, + { key: 'error', doc_count: 11 }, + ], + }, + top_terms_15: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 217, + buckets: [ + { key: 'https://www.elastic.co/downloads/apm', doc_count: 16 }, + { key: 'https://www.elastic.co/downloads/beats', doc_count: 13 }, + { + key: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.tar.gz', + doc_count: 11, + }, + { + key: 'https://artifacts.elastic.co/downloads/kibana/kibana-6.3.2-darwin-x86_64.tar.gz', + doc_count: 11, + }, + { key: 'https://www.elastic.co/downloads/beats/filebeat', doc_count: 11 }, + { + key: 'https://artifacts.elastic.co/downloads/apm-server/apm-server-6.3.2-windows-x86.zip', + doc_count: 10, + }, + { + key: 'https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-6.3.2-i686.rpm', + doc_count: 10, + }, + { + key: 'https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.deb', + doc_count: 10, + }, + { key: 'https://www.elastic.co/downloads/beats/metricbeat', doc_count: 10 }, + { key: 'https://www.elastic.co/downloads/enterprise', doc_count: 10 }, + ], + }, + }, +}; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts index 280ffd9ed907f..64477ab64aff9 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.test.ts @@ -8,7 +8,27 @@ import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import { paramsMock } from './__mocks__/params_match_all'; -import { getBaselineOrDeviationFilter, getCategoryRequest } from './fetch_categories'; +import { + getBaselineOrDeviationFilter, + getCategoryRequest, + isMsearchResponseItemWithAggs, +} from './fetch_categories'; + +describe('isMsearchResponseItemWithAggs', () => { + it('returns true if the argument is an MsearchMultiSearchItem with aggregations', () => { + const arg = { + aggregations: {}, + }; + + expect(isMsearchResponseItemWithAggs(arg)).toBe(true); + }); + + it('returns false if the argument is not an MsearchMultiSearchItem with aggregations', () => { + const arg = {}; + + expect(isMsearchResponseItemWithAggs(arg)).toBe(false); + }); +}); describe('getBaselineOrDeviationFilter', () => { it('returns a filter that matches both baseline and deviation time range', () => { @@ -47,7 +67,6 @@ describe('getCategoryRequest', () => { // time range filter whatsoever, for example for start/end (0,50). expect(query).toEqual({ index: 'the-index', - size: 0, body: { query: { bool: { @@ -97,6 +116,7 @@ describe('getCategoryRequest', () => { }, }, }, + size: 0, }, }); }); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts index 8f5c801931298..2a06b36a9fac4 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_categories.ts @@ -10,6 +10,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { createRandomSamplerWrapper, type RandomSamplerWrapper, @@ -23,6 +24,10 @@ import type { AiopsLogRateAnalysisSchema } from '../api/schema'; import { getQueryWithParams } from './get_query_with_params'; +export const isMsearchResponseItemWithAggs = ( + arg: unknown +): arg is estypes.MsearchMultiSearchItem => isPopulatedObject(arg, ['aggregations']); + // Filter that includes docs from both the baseline and deviation time range. export const getBaselineOrDeviationFilter = ( params: AiopsLogRateAnalysisSchema @@ -79,6 +84,7 @@ export const getCategoryRequest = ( wrap, undefined, undefined, + false, false ); @@ -111,21 +117,21 @@ export const fetchCategories = async ( const result: FetchCategoriesResponse[] = []; - const settledPromises = await Promise.allSettled( - fieldNames.map((fieldName) => { - const request = getCategoryRequest(params, fieldName, randomSamplerWrapper); - return esClient.search(request, { - signal: abortSignal, - maxRetries: 0, - }); - }) - ); + const searches: estypes.MsearchRequestItem[] = fieldNames.flatMap((fieldName) => [ + { index: params.index }, + getCategoryRequest(params, fieldName, randomSamplerWrapper) + .body as estypes.MsearchMultisearchBody, + ]); - function reportError(fieldName: string, error: unknown) { + let mSearchResponse; + + try { + mSearchResponse = await esClient.msearch({ searches }, { signal: abortSignal, maxRetries: 0 }); + } catch (error) { if (!isRequestAbortedError(error)) { if (logger) { logger.error( - `Failed to fetch category aggregation for fieldName "${fieldName}", got: \n${JSON.stringify( + `Failed to fetch category aggregation for field names ${fieldNames.join()}, got: \n${JSON.stringify( error, null, 2 @@ -134,55 +140,59 @@ export const fetchCategories = async ( } if (emitError) { - emitError(`Failed to fetch category aggregation for fieldName "${fieldName}".`); + emitError(`Failed to fetch category aggregation for field names "${fieldNames.join()}".`); } } + return result; } - for (const [index, settledPromise] of settledPromises.entries()) { + for (const [index, resp] of mSearchResponse.responses.entries()) { const fieldName = fieldNames[index]; - if (settledPromise.status === 'rejected') { - reportError(fieldName, settledPromise.reason); - // Still continue the analysis even if individual category queries fail. - continue; - } - - const resp = settledPromise.value; - const { aggregations } = resp; + if (isMsearchResponseItemWithAggs(resp)) { + const { aggregations } = resp; + + const { + categories: { buckets }, + } = randomSamplerWrapper.unwrap( + aggregations as unknown as Record<string, estypes.AggregationsAggregate> + ) as CategoriesAgg; + + const categories: Category[] = buckets.map((b) => { + const sparkline = + b.sparkline === undefined + ? {} + : b.sparkline.buckets.reduce<Record<number, number>>((acc2, cur2) => { + acc2[cur2.key] = cur2.doc_count; + return acc2; + }, {}); + + return { + key: b.key, + count: b.doc_count, + examples: b.examples.hits.hits.map((h) => get(h._source, fieldName)), + sparkline, + regex: b.regex, + }; + }); + result.push({ + categories, + }); + } else { + if (logger) { + logger.error( + `Failed to fetch category aggregation for field "${fieldName}", got: \n${JSON.stringify( + resp, + null, + 2 + )}` + ); + } - if (aggregations === undefined) { - reportError(fieldName, resp); - // Still continue the analysis even if individual category queries fail. - continue; + if (emitError) { + emitError(`Failed to fetch category aggregation for field "${fieldName}".`); + } } - - const { - categories: { buckets }, - } = randomSamplerWrapper.unwrap( - aggregations as unknown as Record<string, estypes.AggregationsAggregate> - ) as CategoriesAgg; - - const categories: Category[] = buckets.map((b) => { - const sparkline = - b.sparkline === undefined - ? {} - : b.sparkline.buckets.reduce<Record<number, number>>((acc2, cur2) => { - acc2[cur2.key] = cur2.doc_count; - return acc2; - }, {}); - - return { - key: b.key, - count: b.doc_count, - examples: b.examples.hits.hits.map((h) => get(h._source, fieldName)), - sparkline, - regex: b.regex, - }; - }); - result.push({ - categories, - }); } return result; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_category_counts.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_category_counts.ts index 0879e5de7aeff..0fe63259f6280 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_category_counts.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_category_counts.ts @@ -92,13 +92,7 @@ export const fetchCategoryCounts = async ( let mSearchresponse; try { - mSearchresponse = await esClient.msearch( - { searches }, - { - signal: abortSignal, - maxRetries: 0, - } - ); + mSearchresponse = await esClient.msearch({ searches }, { signal: abortSignal, maxRetries: 0 }); } catch (error) { if (!isRequestAbortedError(error)) { if (logger) { diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.test.ts index 43a12bc845bb8..61aabb90f1618 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.test.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.test.ts @@ -133,7 +133,6 @@ describe('fetchFieldCandidates', () => { 'event.type', 'fileset.name', 'host.architecture', - 'host.containerized', 'host.hostname', 'host.ip', 'host.mac', diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.ts index 1ee4b2184071c..796cbd6c63a81 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_field_candidates.ts @@ -20,7 +20,8 @@ export const TEXT_FIELD_SAFE_LIST = ['message', 'error.message']; export const SUPPORTED_ES_FIELD_TYPES = [ ES_FIELD_TYPES.KEYWORD, ES_FIELD_TYPES.IP, - ES_FIELD_TYPES.BOOLEAN, + // Disabled boolean support because it causes problems with the `frequent_item_sets` aggregation + // ES_FIELD_TYPES.BOOLEAN, ]; export const SUPPORTED_ES_FIELD_TYPES_TEXT = [ES_FIELD_TYPES.TEXT, ES_FIELD_TYPES.MATCH_ONLY_TEXT]; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts index 4a2960c1775ef..c600f1cf6506c 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts @@ -97,7 +97,7 @@ export const fetchIndexInfo = async ({ return { keywordFieldCandidates: fieldCandidates?.selectedKeywordFieldCandidates.sort() ?? [], - textFieldCandidates: fieldCandidates?.textFieldCandidates.sort() ?? [], + textFieldCandidates: fieldCandidates?.selectedTextFieldCandidates.sort() ?? [], baselineTotalDocCount, deviationTotalDocCount, zeroDocsFallback: baselineTotalDocCount === 0 || deviationTotalDocCount === 0, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_mini_histograms_for_significant_groups.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_mini_histograms_for_significant_groups.ts new file mode 100644 index 0000000000000..cde3887c2c5d6 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_mini_histograms_for_significant_groups.ts @@ -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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; +import type { + NumericChartData, + SignificantItemGroup, + SignificantItemGroupHistogram, +} from '@kbn/ml-agg-utils'; +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; +import { isRequestAbortedError } from '@kbn/aiops-common/is_request_aborted_error'; + +import { RANDOM_SAMPLER_SEED } from '../constants'; +import type { AiopsLogRateAnalysisSchema } from '../api/schema'; + +import { getGroupFilter } from './get_group_filter'; +import { getHistogramQuery } from './get_histogram_query'; +import { + getMiniHistogramDataFromAggResponse, + getMiniHistogramAgg, + MINI_HISTOGRAM_AGG_PREFIX, + type MiniHistogramAgg, +} from './mini_histogram_utils'; + +export const fetchMiniHistogramsForSignificantGroups = async ( + esClient: ElasticsearchClient, + params: AiopsLogRateAnalysisSchema, + significantGroups: SignificantItemGroup[], + overallTimeSeries: NumericChartData['data'], + logger: Logger, + // The default value of 1 means no sampling will be used + randomSamplerProbability: number = 1, + emitError: (m: string) => void, + abortSignal?: AbortSignal +): Promise<SignificantItemGroupHistogram[]> => { + const histogramQuery = getHistogramQuery(params); + + const histogramAggs = significantGroups.reduce< + Record<string, estypes.AggregationsAggregationContainer> + >((aggs, significantGroup, index) => { + aggs[`${MINI_HISTOGRAM_AGG_PREFIX}${index}`] = { + filter: { + bool: { filter: getGroupFilter(significantGroup) }, + }, + aggs: getMiniHistogramAgg(params), + }; + + return aggs; + }, {}); + + const { wrap, unwrap } = createRandomSamplerWrapper({ + probability: randomSamplerProbability, + seed: RANDOM_SAMPLER_SEED, + }); + + const resp = await esClient.search( + { + index: params.index, + size: 0, + body: { + query: histogramQuery, + aggs: wrap(histogramAggs), + size: 0, + }, + }, + { signal: abortSignal, maxRetries: 0 } + ); + + if (resp.aggregations === undefined) { + if (!isRequestAbortedError(resp)) { + if (logger) { + logger.error( + `Failed to fetch the histogram data chunk, got: \n${JSON.stringify(resp, null, 2)}` + ); + } + + if (emitError) { + emitError(`Failed to fetch the histogram data chunk.`); + } + } + return []; + } + + const unwrappedResp = unwrap(resp.aggregations) as Record<string, MiniHistogramAgg>; + + return significantGroups.map((significantGroup, index) => ({ + id: significantGroup.id, + histogram: getMiniHistogramDataFromAggResponse(overallTimeSeries, unwrappedResp, index), + })); +}; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_mini_histograms_for_significant_items.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_mini_histograms_for_significant_items.ts new file mode 100644 index 0000000000000..1e28d39996ad5 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_mini_histograms_for_significant_items.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; +import type { + NumericChartData, + SignificantItem, + SignificantItemHistogram, +} from '@kbn/ml-agg-utils'; +import { isSignificantItem } from '@kbn/ml-agg-utils'; +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; +import { isRequestAbortedError } from '@kbn/aiops-common/is_request_aborted_error'; +import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query'; + +import { RANDOM_SAMPLER_SEED } from '../constants'; +import type { AiopsLogRateAnalysisSchema } from '../api/schema'; + +import { getHistogramQuery } from './get_histogram_query'; +import { + getMiniHistogramDataFromAggResponse, + getMiniHistogramAgg, + MINI_HISTOGRAM_AGG_PREFIX, + type MiniHistogramAgg, +} from './mini_histogram_utils'; + +export const fetchMiniHistogramsForSignificantItems = async ( + esClient: ElasticsearchClient, + params: AiopsLogRateAnalysisSchema, + significantItems: SignificantItem[], + overallTimeSeries: NumericChartData['data'], + logger: Logger, + // The default value of 1 means no sampling will be used + randomSamplerProbability: number = 1, + emitError: (m: string) => void, + abortSignal?: AbortSignal +): Promise<SignificantItemHistogram[]> => { + const histogramQuery = getHistogramQuery(params); + + const histogramAggs = significantItems.reduce< + Record<string, estypes.AggregationsAggregationContainer> + >((aggs, significantItem, index) => { + let filter; + + if (isSignificantItem(significantItem) && significantItem.type === 'keyword') { + filter = { + term: { [significantItem.fieldName]: significantItem.fieldValue }, + }; + } else if (isSignificantItem(significantItem) && significantItem.type === 'log_pattern') { + filter = getCategoryQuery(significantItem.fieldName, [ + { + key: `${significantItem.key}`, + count: significantItem.doc_count, + examples: [], + regex: '', + }, + ]); + } else { + throw new Error('Invalid significant item type.'); + } + + aggs[`${MINI_HISTOGRAM_AGG_PREFIX}${index}`] = { + filter, + aggs: getMiniHistogramAgg(params), + }; + + return aggs; + }, {}); + + const { wrap, unwrap } = createRandomSamplerWrapper({ + probability: randomSamplerProbability, + seed: RANDOM_SAMPLER_SEED, + }); + + const resp = await esClient.search( + { + index: params.index, + size: 0, + body: { + query: histogramQuery, + aggs: wrap(histogramAggs), + size: 0, + }, + }, + { signal: abortSignal, maxRetries: 0 } + ); + + if (resp.aggregations === undefined) { + if (!isRequestAbortedError(resp)) { + if (logger) { + logger.error( + `Failed to fetch the histogram data chunk, got: \n${JSON.stringify(resp, null, 2)}` + ); + } + + if (emitError) { + emitError(`Failed to fetch the histogram data chunk.`); + } + } + return []; + } + + const unwrappedResp = unwrap(resp.aggregations) as Record<string, MiniHistogramAgg>; + + return significantItems.map((significantItem, index) => ({ + fieldName: significantItem.fieldName, + fieldValue: significantItem.fieldValue, + histogram: getMiniHistogramDataFromAggResponse(overallTimeSeries, unwrappedResp, index), + })); +}; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_significant_term_p_values.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_significant_term_p_values.ts index 735dadc280b6b..a2105bfd4ba84 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_significant_term_p_values.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_significant_term_p_values.ts @@ -5,9 +5,10 @@ * 2.0. */ import { uniqBy } from 'lodash'; + import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ElasticsearchClient } from '@kbn/core/server'; +import type { ElasticsearchClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.test.ts new file mode 100644 index 0000000000000..e7bdd6c7944d7 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { paramsMock } from './__mocks__/params_match_all'; + +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; + +import { topCategoriesSearchResponseMock } from './__mocks__/top_categories_search_response'; +import { topCategoriesResultMock } from './__mocks__/top_categories_result'; +import { fetchTopCategories } from './fetch_top_categories'; + +const esClientMock = { + msearch: jest.fn().mockImplementation(() => topCategoriesSearchResponseMock), +} as unknown as ElasticsearchClient; + +const loggerMock = {} as unknown as Logger; + +describe('fetchTopCategories', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should fetch top categories successfully', async () => { + const abortSignal = new AbortController().signal; + + const result = await fetchTopCategories({ + esClient: esClientMock, + logger: loggerMock, + emitError: jest.fn(), + abortSignal, + arguments: { ...paramsMock, fieldNames: ['message'] }, + }); + expect(result).toEqual(topCategoriesResultMock); + expect(esClientMock.msearch).toHaveBeenCalledWith( + { + searches: [ + { index: 'the-index' }, + { + aggs: { + categories: { + aggs: { + examples: { + top_hits: { _source: 'message', size: 4, sort: ['the-time-field-name'] }, + }, + }, + categorize_text: { field: 'message', size: 1000 }, + }, + }, + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + range: { + 'the-time-field-name': { + format: 'epoch_millis', + gte: 10, + lte: 20, + }, + }, + }, + { + range: { + 'the-time-field-name': { + format: 'epoch_millis', + gte: 30, + lte: 40, + }, + }, + }, + ], + }, + }, + ], + }, + }, + size: 0, + }, + ], + }, + { maxRetries: 0, signal: abortSignal } + ); + }); +}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts index a9fb020c10c51..2806c21834405 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_categories.ts @@ -7,24 +7,21 @@ import { uniq } from 'lodash'; -import type { ElasticsearchClient } from '@kbn/core/server'; -import type { Logger } from '@kbn/logging'; import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; -import type { AiopsLogRateAnalysisSchema } from '../api/schema'; - import { fetchCategories } from './fetch_categories'; +import type { FetchTopOptions } from './fetch_top_types'; -export const fetchTopCategories = async ( - esClient: ElasticsearchClient, - params: AiopsLogRateAnalysisSchema, - fieldNames: string[], - logger: Logger, +export const fetchTopCategories = async ({ + esClient, + abortSignal, + emitError, + logger, + arguments: args, +}: FetchTopOptions) => { // The default value of 1 means no sampling will be used - sampleProbability: number = 1, - emitError: (m: string) => void, - abortSignal?: AbortSignal -) => { + const { fieldNames, sampleProbability = 1, ...params } = args; + const categoriesOverall = await fetchCategories( esClient, params, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.test.ts new file mode 100644 index 0000000000000..761c39c00e74a --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.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 { paramsMock } from './__mocks__/params_match_all'; + +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; + +import { topTermsSearchResponseMock } from './__mocks__/top_terms_search_response'; +import { topTermsResult } from './__mocks__/top_terms_result'; +import { fetchTopTerms } from './fetch_top_terms'; + +const esClientMock = { + search: jest.fn().mockImplementation(() => topTermsSearchResponseMock), +} as unknown as ElasticsearchClient; + +const loggerMock = {} as unknown as Logger; + +describe('fetchTopTerms', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should fetch top terms successfully', async () => { + const abortSignal = new AbortController().signal; + + const result = await fetchTopTerms({ + esClient: esClientMock, + logger: loggerMock, + emitError: jest.fn(), + abortSignal, + arguments: { + ...paramsMock, + fieldNames: [ + 'agent.keyword', + 'clientip', + 'event.dataset', + 'extension.keyword', + 'geo.dest', + 'geo.src', + 'geo.srcdest', + 'host.keyword', + 'index.keyword', + 'ip', + 'machine.os.keyword', + 'referer', + 'request.keyword', + 'response.keyword', + 'tags.keyword', + 'url.keyword', + ], + }, + }); + + expect(esClientMock.search).toHaveBeenCalledTimes(1); + expect(result).toEqual(topTermsResult); + }); +}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts index 0eebb67908dd3..ed39c37cbbb97 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_terms.ts @@ -5,10 +5,9 @@ * 2.0. */ import { uniqBy } from 'lodash'; + import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ElasticsearchClient } from '@kbn/core/server'; -import type { Logger } from '@kbn/logging'; import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { createRandomSamplerWrapper, @@ -21,13 +20,16 @@ import { LOG_RATE_ANALYSIS_SETTINGS, RANDOM_SAMPLER_SEED } from '../constants'; import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; +import type { FetchTopOptions } from './fetch_top_types'; // TODO Consolidate with duplicate `fetchDurationFieldCandidates` in // `x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts` +const TOP_TERM_AGG_PREFIX = 'top_terms_'; + export const getTopTermRequest = ( params: AiopsLogRateAnalysisSchema, - fieldName: string, + fieldNames: string[], { wrap }: RandomSamplerWrapper ): estypes.SearchRequest => { const query = getQueryWithParams({ @@ -55,19 +57,24 @@ export const getTopTermRequest = ( ]; } - const termAgg: Record<'log_rate_top_terms', estypes.AggregationsAggregationContainer> = { - log_rate_top_terms: { - terms: { - field: fieldName, - size: LOG_RATE_ANALYSIS_SETTINGS.TOP_TERMS_FALLBACK_SIZE, - }, + const termAggs = fieldNames.reduce<Record<string, estypes.AggregationsAggregationContainer>>( + (aggs, fieldName, index) => { + aggs[`${TOP_TERM_AGG_PREFIX}${index}`] = { + terms: { + field: fieldName, + size: LOG_RATE_ANALYSIS_SETTINGS.TOP_TERMS_FALLBACK_SIZE, + }, + }; + + return aggs; }, - }; + {} + ); const body = { query, size: 0, - aggs: wrap(termAgg), + aggs: wrap(termAggs), }; return { @@ -80,16 +87,16 @@ interface Aggs extends estypes.AggregationsLongTermsAggregate { buckets: estypes.AggregationsLongTermsBucket[]; } -export const fetchTopTerms = async ( - esClient: ElasticsearchClient, - params: AiopsLogRateAnalysisSchema, - fieldNames: string[], - logger: Logger, +export const fetchTopTerms = async ({ + esClient, + abortSignal, + emitError, + logger, + arguments: args, +}: FetchTopOptions): Promise<SignificantItem[]> => { // The default value of 1 means no sampling will be used - sampleProbability: number = 1, - emitError: (m: string) => void, - abortSignal?: AbortSignal -): Promise<SignificantItem[]> => { + const { fieldNames, sampleProbability = 1, ...params } = args; + const randomSamplerWrapper = createRandomSamplerWrapper({ probability: sampleProbability, seed: RANDOM_SAMPLER_SEED, @@ -97,50 +104,36 @@ export const fetchTopTerms = async ( const result: SignificantItem[] = []; - const settledPromises = await Promise.allSettled( - fieldNames.map((fieldName) => - esClient.search(getTopTermRequest(params, fieldName, randomSamplerWrapper), { - signal: abortSignal, - maxRetries: 0, - }) - ) - ); + const resp = await esClient.search(getTopTermRequest(params, fieldNames, randomSamplerWrapper), { + signal: abortSignal, + maxRetries: 0, + }); - function reportError(fieldName: string, error: unknown) { - if (!isRequestAbortedError(error)) { - logger.error( - `Failed to fetch term aggregation for fieldName "${fieldName}", got: \n${JSON.stringify( - error, - null, - 2 - )}` - ); - emitError(`Failed to fetch term aggregation for fieldName "${fieldName}".`); + if (resp.aggregations === undefined) { + if (!isRequestAbortedError(resp)) { + if (logger) { + logger.error( + `Failed to fetch terms aggregation for field names ${fieldNames.join()}, got: \n${JSON.stringify( + resp, + null, + 2 + )}` + ); + } + + if (emitError) { + emitError(`Failed to fetch terms aggregation for field names ${fieldNames.join()}.`); + } } + return result; } - for (const [index, settledPromise] of settledPromises.entries()) { - const fieldName = fieldNames[index]; - - if (settledPromise.status === 'rejected') { - reportError(fieldName, settledPromise.reason); - // Still continue the analysis even if individual p-value queries fail. - continue; - } - - const resp = settledPromise.value; - - if (resp.aggregations === undefined) { - reportError(fieldName, resp); - // Still continue the analysis even if individual p-value queries fail. - continue; - } + const unwrappedResp = randomSamplerWrapper.unwrap(resp.aggregations) as Record<string, Aggs>; - const overallResult = ( - randomSamplerWrapper.unwrap(resp.aggregations) as Record<'log_rate_top_terms', Aggs> - ).log_rate_top_terms; + for (const [index, fieldName] of fieldNames.entries()) { + const termBuckets = unwrappedResp[`${TOP_TERM_AGG_PREFIX}${index}`]; - for (const bucket of overallResult.buckets) { + for (const bucket of termBuckets.buckets) { result.push({ key: `${fieldName}:${String(bucket.key)}`, type: SIGNIFICANT_ITEM_TYPE.KEYWORD, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_types.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_types.ts new file mode 100644 index 0000000000000..26d743caa9347 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_top_types.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 type { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; + +import type { AiopsLogRateAnalysisSchema } from '../api/schema'; + +export interface FetchTopOptions { + esClient: ElasticsearchClient; + logger: Logger; + emitError: (m: string) => void; + abortSignal?: AbortSignal; + arguments: AiopsLogRateAnalysisSchema & { + fieldNames: string[]; + sampleProbability?: number; + }; +} diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/mini_histogram_utils.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/mini_histogram_utils.test.ts new file mode 100644 index 0000000000000..051024906fb4d --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/mini_histogram_utils.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { NumericChartData } from '@kbn/ml-agg-utils'; + +import { getDateHistogramBuckets } from '../__mocks__/date_histogram'; +import { paramsMock } from './__mocks__/params_match_all'; +import { + getMiniHistogramAgg, + getMiniHistogramDataFromAggResponse, + type MiniHistogramAgg, +} from './mini_histogram_utils'; + +describe('getMiniHistogramAgg', () => { + it('returns DSL for a mini histogram aggregation', () => { + expect(getMiniHistogramAgg(paramsMock)).toStrictEqual({ + mini_histogram: { + histogram: { + extended_bounds: { + max: 50, + min: 0, + }, + field: 'the-time-field-name', + interval: 2.6315789473684212, + min_doc_count: 0, + }, + }, + }); + }); +}); + +describe('getMiniHistogramDataFromAggResponse', () => { + it('returns data for a mini histogram chart', () => { + // overall time series mock + const numericChartDataMock: NumericChartData['data'] = Object.entries( + getDateHistogramBuckets() + ).map(([key, value]) => ({ + doc_count: value, + key: parseInt(key, 10), + key_as_string: new Date(parseInt(key, 10)).toISOString(), + })); + + // aggregation response mock + const aggResponseMock: Record<string, MiniHistogramAgg> = { + mini_histogram_0: { + doc_count: 0, + mini_histogram: { + buckets: Object.entries(getDateHistogramBuckets()).map(([key, value]) => ({ + doc_count: Math.round(value / 10), + key: parseInt(key, 10), + key_as_string: new Date(parseInt(key, 10)).toISOString(), + })), + }, + }, + }; + + // we'll only check the first element to the returned array to avoid a large snapshot + expect( + getMiniHistogramDataFromAggResponse(numericChartDataMock, aggResponseMock, 0)[0] + ).toStrictEqual({ + // response should correctly calculate values based on the overall time series and the aggregation response + doc_count_overall: 4436, + doc_count_significant_item: 493, + key: 1654566600000, + key_as_string: '2022-06-07T01:50:00.000Z', + }); + }); +}); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/mini_histogram_utils.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/mini_histogram_utils.ts new file mode 100644 index 0000000000000..b90e1bd9de416 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/mini_histogram_utils.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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import type { NumericChartData, SignificantItemHistogramItem } from '@kbn/ml-agg-utils'; + +import type { AiopsLogRateAnalysisSchema } from '../api/schema'; + +export interface MiniHistogramAgg extends estypes.AggregationsSingleBucketAggregateBase { + doc_count: number; + mini_histogram: { + buckets: Array< + estypes.AggregationsSingleBucketAggregateBase & estypes.AggregationsHistogramBucketKeys + >; + }; +} + +export const MINI_HISTOGRAM_AGG_PREFIX = 'mini_histogram_'; +export const MINI_HISTOGRAM_BAR_TARGET = 20; + +export const getMiniHistogramAgg = (params: AiopsLogRateAnalysisSchema) => { + return { + mini_histogram: { + histogram: { + field: params.timeFieldName, + interval: (params.end - params.start) / (MINI_HISTOGRAM_BAR_TARGET - 1), + min_doc_count: 0, + extended_bounds: { + min: params.start, + max: params.end, + }, + }, + }, + }; +}; + +export const getMiniHistogramDataFromAggResponse = ( + overallTimeSeries: NumericChartData['data'], + aggReponse: Record<string, MiniHistogramAgg>, + index: number +): SignificantItemHistogramItem[] => + overallTimeSeries.map((overallTimeSeriesItem) => { + const current = aggReponse[`${MINI_HISTOGRAM_AGG_PREFIX}${index}`].mini_histogram.buckets.find( + (bucket) => bucket.key_as_string === overallTimeSeriesItem.key_as_string + ) ?? { + doc_count: 0, + }; + + return { + key: overallTimeSeriesItem.key, + key_as_string: overallTimeSeriesItem.key_as_string ?? '', + doc_count_significant_item: current.doc_count, + doc_count_overall: Math.max(0, overallTimeSeriesItem.doc_count - current.doc_count), + }; + }) ?? []; diff --git a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts index dd8f6aef06141..4d065fb062cb5 100644 --- a/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts +++ b/x-pack/packages/ml/trained_models_utils/src/constants/trained_models.ts @@ -58,7 +58,10 @@ export const BUILT_IN_MODEL_TAG = 'prepackaged'; export const ELASTIC_MODEL_TAG = 'elastic'; -export const ELASTIC_MODEL_DEFINITIONS: Record<string, ModelDefinition> = Object.freeze({ +export const ELASTIC_MODEL_DEFINITIONS: Record< + string, + Omit<ModelDefinition, 'supported'> +> = Object.freeze({ [ELSER_ID_V1]: { modelName: 'elser', hidden: true, @@ -156,6 +159,8 @@ export interface ModelDefinition { default?: boolean; /** Indicates if model version is recommended for deployment based on the cluster configuration */ recommended?: boolean; + /** Indicates if model version is supported by the cluster */ + supported: boolean; hidden?: boolean; /** Software license of a model, e.g. MIT */ license?: string; diff --git a/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx index e55e3378c92d9..a07699d282325 100644 --- a/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx +++ b/x-pack/packages/security-solution/data_table/components/data_table/column_headers/helpers.tsx @@ -9,8 +9,9 @@ import { i18n } from '@kbn/i18n'; import type { EuiDataGridColumnActions } from '@elastic/eui'; import { keyBy } from 'lodash/fp'; import React from 'react'; +import type { FieldSpec } from '@kbn/data-plugin/common'; +import { BrowserFields } from '@kbn/timelines-plugin/common'; -import { BrowserField, BrowserFields } from '@kbn/timelines-plugin/common'; import { DEFAULT_TABLE_COLUMN_MIN_WIDTH, DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH } from '../constants'; import { defaultColumnHeaderType } from '../../../store/data_table/defaults'; import { ColumnHeaderOptions } from '../../../common/types'; @@ -25,7 +26,7 @@ export const allowSorting = ({ browserField, fieldName, }: { - browserField: Partial<BrowserField> | undefined; + browserField: Partial<FieldSpec> | undefined; fieldName: string; }): boolean => { const isAggregatable = browserField?.aggregatable ?? false; @@ -94,8 +95,8 @@ export const allowSorting = ({ return isAllowlistedNonBrowserField || isAggregatable; }; -const getAllBrowserFields = (browserFields: BrowserFields): Array<Partial<BrowserField>> => - Object.values(browserFields).reduce<Array<Partial<BrowserField>>>( +const getAllBrowserFields = (browserFields: BrowserFields): Array<Partial<FieldSpec>> => + Object.values(browserFields).reduce<Array<Partial<FieldSpec>>>( (acc, namespace) => [ ...acc, ...Object.values(namespace.fields != null ? namespace.fields : {}), @@ -105,8 +106,7 @@ const getAllBrowserFields = (browserFields: BrowserFields): Array<Partial<Browse const getAllFieldsByName = ( browserFields: BrowserFields -): { [fieldName: string]: Partial<BrowserField> } => - keyBy('name', getAllBrowserFields(browserFields)); +): { [fieldName: string]: Partial<FieldSpec> } => keyBy('name', getAllBrowserFields(browserFields)); /** * Valid built-in schema types for the `schema` property of `EuiDataGridColumn` @@ -204,7 +204,7 @@ export const getColumnHeaders = ( const headersToMap = isEventRenderedView ? eventRenderedViewColumns : headers; return headersToMap ? headersToMap.map((header) => { - const browserField: Partial<BrowserField> | undefined = browserFieldByName[header.id]; + const browserField: Partial<FieldSpec> | undefined = browserFieldByName[header.id]; // augment the header with metadata from browserFields: const augmentedHeader = { diff --git a/x-pack/packages/security-solution/data_table/tsconfig.json b/x-pack/packages/security-solution/data_table/tsconfig.json index 8fc4b2264f848..9fab7f29b9aab 100644 --- a/x-pack/packages/security-solution/data_table/tsconfig.json +++ b/x-pack/packages/security-solution/data_table/tsconfig.json @@ -20,6 +20,7 @@ "@kbn/i18n-react", "@kbn/ui-actions-plugin", "@kbn/data-views-plugin", - "@kbn/field-formats-plugin" + "@kbn/field-formats-plugin", + "@kbn/data-plugin" ] } diff --git a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index 286a41c376c23..53d79f80d697b 100644 --- a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -4490,6 +4490,33 @@ Object { ], "type": "number", }, + "tools": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-any-type": true, + }, + ], + "type": "any", + }, + ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "array", + }, }, "type": "object", } diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/discover_tabs.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/discover_tabs.tsx index 96fae405eec2c..eede82d50076d 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/discover_tabs.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_for_embeddable/discover_tabs.tsx @@ -62,7 +62,7 @@ export const DiscoverTabs: FC<Props> = ({ query, }) => { return ( - <EuiFlexItem grow={false}> + <EuiFlexItem grow={false} className="unifiedDataTableToolbar"> <EuiFlexGroup gutterSize="none"> <EuiFlexItem grow={false}>{renderViewModeToggle(data?.categories.length)}</EuiFlexItem> <EuiFlexItem /> diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx index 6dff02f95286f..91a067d56d4d5 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_results.tsx @@ -193,6 +193,8 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({ 'Baseline rate', 'Deviation rate', ]); + // null is used as the uninitialized state to identify the first load. + const [skippedFields, setSkippedFields] = useState<string[] | null>(null); const onGroupResultsToggle = (optionId: string) => { setToggleIdSelected(optionId); @@ -207,20 +209,27 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({ fieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates, - selectedKeywordFieldCandidates, - selectedTextFieldCandidates, } = fieldCandidates; const fieldFilterButtonDisabled = isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0; - const onFieldsFilterChange = (skippedFields: string[]) => { + // Set skipped fields only on first load, otherwise we'd overwrite the user's selection. + useEffect(() => { + if (skippedFields === null && fieldFilterSkippedItems.length > 0) + setSkippedFields(fieldFilterSkippedItems); + }, [fieldFilterSkippedItems, skippedFields]); + + const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => { dispatch(resetResults()); + setSkippedFields(skippedFieldsUpdate); setOverrides({ loaded: 0, remainingKeywordFieldCandidates: keywordFieldCandidates.filter( - (d) => !skippedFields.includes(d) + (d) => !skippedFieldsUpdate.includes(d) + ), + remainingTextFieldCandidates: textFieldCandidates.filter( + (d) => !skippedFieldsUpdate.includes(d) ), - remainingTextFieldCandidates: textFieldCandidates.filter((d) => !skippedFields.includes(d)), regroupOnly: false, }); startHandler(true, false); @@ -287,8 +296,12 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({ if (!continueAnalysis) { dispatch(resetResults()); setOverrides({ - remainingKeywordFieldCandidates: selectedKeywordFieldCandidates, - remainingTextFieldCandidates: selectedTextFieldCandidates, + remainingKeywordFieldCandidates: keywordFieldCandidates.filter( + (d) => skippedFields === null || !skippedFields.includes(d) + ), + remainingTextFieldCandidates: textFieldCandidates.filter( + (d) => skippedFields === null || !skippedFields.includes(d) + ), }); } diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts index 258b94bb12cc6..a21b7a3e4a0d0 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts @@ -5,21 +5,19 @@ * 2.0. */ +import { chunk } from 'lodash'; import { queue } from 'async'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { KBN_FIELD_TYPES } from '@kbn/field-types'; import { i18n } from '@kbn/i18n'; -import { - fetchHistogramsForFields, - type SignificantItem, - type SignificantItemGroup, - type SignificantItemHistogramItem, - type NumericChartData, +import type { + SignificantItem, + SignificantItemGroup, + SignificantItemGroupHistogram, + NumericChartData, } from '@kbn/ml-agg-utils'; -import { RANDOM_SAMPLER_SEED } from '@kbn/aiops-log-rate-analysis/constants'; - +import { QUEUE_CHUNKING_SIZE } from '@kbn/aiops-log-rate-analysis/queue_field_candidates'; import { addSignificantItemsGroup, addSignificantItemsGroupHistogram, @@ -27,14 +25,19 @@ import { } from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-log-rate-analysis/api/schema'; import { isRequestAbortedError } from '@kbn/aiops-common/is_request_aborted_error'; - import { fetchFrequentItemSets } from '@kbn/aiops-log-rate-analysis/queries/fetch_frequent_item_sets'; import { fetchTerms2CategoriesCounts } from '@kbn/aiops-log-rate-analysis/queries/fetch_terms_2_categories_counts'; -import { getGroupFilter } from '@kbn/aiops-log-rate-analysis/queries/get_group_filter'; -import { getHistogramQuery } from '@kbn/aiops-log-rate-analysis/queries/get_histogram_query'; import { getSignificantItemGroups } from '@kbn/aiops-log-rate-analysis/queries/get_significant_item_groups'; +import { fetchMiniHistogramsForSignificantGroups } from '@kbn/aiops-log-rate-analysis/queries/fetch_mini_histograms_for_significant_groups'; -import { MAX_CONCURRENT_QUERIES, PROGRESS_STEP_GROUPING } from '../response_stream_utils/constants'; +import { + MAX_CONCURRENT_QUERIES, + LOADED_FIELD_CANDIDATES, + PROGRESS_STEP_P_VALUES, + PROGRESS_STEP_GROUPING, + PROGRESS_STEP_HISTOGRAMS, + PROGRESS_STEP_HISTOGRAMS_GROUPS, +} from '../response_stream_utils/constants'; import type { ResponseStreamFetchOptions } from '../response_stream_factory'; export const groupingHandlerFactory = @@ -50,7 +53,7 @@ export const groupingHandlerFactory = async ( significantCategories: SignificantItem[], significantTerms: SignificantItem[], - overallTimeSeries?: NumericChartData + overallTimeSeries?: NumericChartData['data'] ) => { logDebugMessage('Group results.'); @@ -138,7 +141,12 @@ export const groupingHandlerFactory = responseStream.push(addSignificantItemsGroup(significantItemGroups)); } - stateHandler.loaded(PROGRESS_STEP_GROUPING, false); + stateHandler.loaded( + LOADED_FIELD_CANDIDATES + + PROGRESS_STEP_P_VALUES + + PROGRESS_STEP_HISTOGRAMS + + PROGRESS_STEP_GROUPING + ); pushHistogramDataLoadingState(); if (stateHandler.shouldStop()) { @@ -149,7 +157,11 @@ export const groupingHandlerFactory = logDebugMessage(`Fetch ${significantItemGroups.length} group histograms.`); - const groupHistogramQueue = queue(async function (cpg: SignificantItemGroup) { + const groupHistogramQueueChunks = chunk(significantItemGroups, QUEUE_CHUNKING_SIZE); + const loadingStepSize = + (1 / groupHistogramQueueChunks.length) * PROGRESS_STEP_HISTOGRAMS_GROUPS; + + const groupHistogramQueue = queue(async function (payload: SignificantItemGroup[]) { if (stateHandler.shouldStop()) { logDebugMessage('shouldStop abort fetching group histograms.'); groupHistogramQueue.kill(); @@ -158,71 +170,34 @@ export const groupingHandlerFactory = } if (overallTimeSeries !== undefined) { - const histogramQuery = getHistogramQuery(requestBody, getGroupFilter(cpg)); + let histograms: SignificantItemGroupHistogram[]; - let cpgTimeSeries: NumericChartData; try { - cpgTimeSeries = ( - (await fetchHistogramsForFields({ - esClient, - abortSignal, - arguments: { - indexPattern: requestBody.index, - query: histogramQuery, - fields: [ - { - fieldName: requestBody.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], - }, - ], - samplerShardSize: -1, - randomSamplerProbability: stateHandler.sampleProbability(), - randomSamplerSeed: RANDOM_SAMPLER_SEED, - }, - })) as [NumericChartData] - )[0]; + histograms = await fetchMiniHistogramsForSignificantGroups( + esClient, + requestBody, + payload, + overallTimeSeries, + logger, + stateHandler.sampleProbability(), + () => {}, + abortSignal + ); } catch (e) { - if (!isRequestAbortedError(e)) { - logger.error( - `Failed to fetch the histogram data for group #${cpg.id}, got: \n${e.toString()}` - ); - responseStream.pushError( - `Failed to fetch the histogram data for group #${cpg.id}.` - ); - } + logger.error( + `Failed to fetch the histogram data chunk for groups, got: \n${e.toString()}` + ); + responseStream.pushError(`Failed to fetch the histogram data chunk for groups.`); return; } - const histogram: SignificantItemHistogramItem[] = - overallTimeSeries.data.map((o) => { - const current = cpgTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_significant_item: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), - }; - }) ?? []; - - responseStream.push( - addSignificantItemsGroupHistogram([ - { - id: cpg.id, - histogram, - }, - ]) - ); + + stateHandler.loaded(loadingStepSize, false); + pushHistogramDataLoadingState(); + responseStream.push(addSignificantItemsGroupHistogram(histograms)); } }, MAX_CONCURRENT_QUERIES); - await groupHistogramQueue.push(significantItemGroups); + await groupHistogramQueue.push(groupHistogramQueueChunks); await groupHistogramQueue.drain(); } } catch (e) { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts index 2863d5c6dd427..5b6f18a5ca721 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts @@ -5,26 +5,22 @@ * 2.0. */ +import { chunk } from 'lodash'; import { queue } from 'async'; import { i18n } from '@kbn/i18n'; -import { KBN_FIELD_TYPES } from '@kbn/field-types'; import type { SignificantItem, - SignificantItemHistogramItem, + SignificantItemHistogram, NumericChartData, } from '@kbn/ml-agg-utils'; -import { fetchHistogramsForFields } from '@kbn/ml-agg-utils'; -import { RANDOM_SAMPLER_SEED } from '@kbn/aiops-log-rate-analysis/constants'; - +import { QUEUE_CHUNKING_SIZE } from '@kbn/aiops-log-rate-analysis/queue_field_candidates'; import { addSignificantItemsHistogram, updateLoadingState, } from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; +import { fetchMiniHistogramsForSignificantItems } from '@kbn/aiops-log-rate-analysis/queries/fetch_mini_histograms_for_significant_items'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-log-rate-analysis/api/schema'; -import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query'; - -import { getHistogramQuery } from '@kbn/aiops-log-rate-analysis/queries/get_histogram_query'; import { MAX_CONCURRENT_QUERIES, @@ -46,7 +42,7 @@ export const histogramHandlerFactory = fieldValuePairsCount: number, significantCategories: SignificantItem[], significantTerms: SignificantItem[], - overallTimeSeries?: NumericChartData + overallTimeSeries?: NumericChartData['data'] ) => { function pushHistogramDataLoadingState() { responseStream.push( @@ -67,11 +63,18 @@ export const histogramHandlerFactory = // time series filtered by fields if ( - significantTerms.length > 0 && + (significantTerms.length > 0 || significantCategories.length > 0) && overallTimeSeries !== undefined && !requestBody.overrides?.regroupOnly ) { - const fieldValueHistogramQueue = queue(async function (cp: SignificantItem) { + const fieldValueHistogramQueueChunks = [ + ...chunk(significantTerms, QUEUE_CHUNKING_SIZE), + ...chunk(significantCategories, QUEUE_CHUNKING_SIZE), + ]; + const loadingStepSize = + (1 / fieldValueHistogramQueueChunks.length) * PROGRESS_STEP_HISTOGRAMS; + + const fieldValueHistogramQueue = queue(async function (payload: SignificantItem[]) { if (stateHandler.shouldStop()) { logDebugMessage('shouldStop abort fetching field/value histograms.'); fieldValueHistogramQueue.kill(); @@ -80,170 +83,32 @@ export const histogramHandlerFactory = } if (overallTimeSeries !== undefined) { - const histogramQuery = getHistogramQuery(requestBody, [ - { - term: { [cp.fieldName]: cp.fieldValue }, - }, - ]); - - let cpTimeSeries: NumericChartData; + let histograms: SignificantItemHistogram[]; try { - cpTimeSeries = ( - (await fetchHistogramsForFields({ - esClient, - abortSignal, - arguments: { - indexPattern: requestBody.index, - query: histogramQuery, - fields: [ - { - fieldName: requestBody.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], - }, - ], - samplerShardSize: -1, - randomSamplerProbability: stateHandler.sampleProbability(), - randomSamplerSeed: RANDOM_SAMPLER_SEED, - }, - })) as [NumericChartData] - )[0]; - } catch (e) { - logger.error( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${ - cp.fieldValue - }", got: \n${e.toString()}` - ); - responseStream.pushError( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".` + histograms = await fetchMiniHistogramsForSignificantItems( + esClient, + requestBody, + payload, + overallTimeSeries, + logger, + stateHandler.sampleProbability(), + () => {}, + abortSignal ); + } catch (e) { + logger.error(`Failed to fetch the histogram data chunk, got: \n${e.toString()}`); + responseStream.pushError(`Failed to fetch the histogram data chunk.`); return; } - const histogram: SignificantItemHistogramItem[] = - overallTimeSeries.data.map((o) => { - const current = cpTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_significant_item: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), - }; - }) ?? []; - - const { fieldName, fieldValue } = cp; - - stateHandler.loaded((1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS, false); + stateHandler.loaded(loadingStepSize, false); pushHistogramDataLoadingState(); - responseStream.push( - addSignificantItemsHistogram([ - { - fieldName, - fieldValue, - histogram, - }, - ]) - ); + responseStream.push(addSignificantItemsHistogram(histograms)); } }, MAX_CONCURRENT_QUERIES); - await fieldValueHistogramQueue.push(significantTerms); + await fieldValueHistogramQueue.push(fieldValueHistogramQueueChunks); await fieldValueHistogramQueue.drain(); } - - // histograms for text field patterns - if ( - overallTimeSeries !== undefined && - significantCategories.length > 0 && - !requestBody.overrides?.regroupOnly - ) { - const significantCategoriesHistogramQueries = significantCategories.map((d) => { - const histogramQuery = getHistogramQuery(requestBody); - const categoryQuery = getCategoryQuery(d.fieldName, [ - { key: `${d.key}`, count: d.doc_count, examples: [], regex: '' }, - ]); - if (Array.isArray(histogramQuery.bool?.filter)) { - histogramQuery.bool?.filter?.push(categoryQuery); - } - return histogramQuery; - }); - - for (const [i, histogramQuery] of significantCategoriesHistogramQueries.entries()) { - const cp = significantCategories[i]; - let catTimeSeries: NumericChartData; - - try { - catTimeSeries = ( - (await fetchHistogramsForFields({ - esClient, - abortSignal, - arguments: { - indexPattern: requestBody.index, - query: histogramQuery, - fields: [ - { - fieldName: requestBody.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], - }, - ], - samplerShardSize: -1, - randomSamplerProbability: stateHandler.sampleProbability(), - randomSamplerSeed: RANDOM_SAMPLER_SEED, - }, - })) as [NumericChartData] - )[0]; - } catch (e) { - logger.error( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${ - cp.fieldValue - }", got: \n${e.toString()}` - ); - responseStream.pushError( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".` - ); - return; - } - - const histogram: SignificantItemHistogramItem[] = - overallTimeSeries.data.map((o) => { - const current = catTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_significant_item: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), - }; - }) ?? []; - - const { fieldName, fieldValue } = cp; - - stateHandler.loaded((1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS, false); - pushHistogramDataLoadingState(); - responseStream.push( - addSignificantItemsHistogram([ - { - fieldName, - fieldValue, - histogram, - }, - ]) - ); - } - } }; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts index 8a5cb4e042e24..e9c9fc73c5adf 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts @@ -6,7 +6,6 @@ */ import { i18n } from '@kbn/i18n'; - import { updateLoadingState, setZeroDocsFallback, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overall_histogram_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overall_histogram_handler.ts index 97e56795a9dfc..01f1972831b65 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overall_histogram_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overall_histogram_handler.ts @@ -5,17 +5,16 @@ * 2.0. */ -import { KBN_FIELD_TYPES } from '@kbn/field-types'; -import { - fetchHistogramsForFields, - type NumericChartData, - type NumericHistogramField, -} from '@kbn/ml-agg-utils'; +import { type NumericChartData } from '@kbn/ml-agg-utils'; import { RANDOM_SAMPLER_SEED } from '@kbn/aiops-log-rate-analysis/constants'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-log-rate-analysis/api/schema'; import { isRequestAbortedError } from '@kbn/aiops-common/is_request_aborted_error'; - import { getHistogramQuery } from '@kbn/aiops-log-rate-analysis/queries/get_histogram_query'; +import { + getMiniHistogramAgg, + type MiniHistogramAgg, +} from '@kbn/aiops-log-rate-analysis/queries/mini_histogram_utils'; +import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import type { ResponseStreamFetchOptions } from '../response_stream_factory'; @@ -29,32 +28,32 @@ export const overallHistogramHandlerFactory = responseStream, stateHandler, }: ResponseStreamFetchOptions<T>) => - async () => { - const histogramFields: [NumericHistogramField] = [ - { fieldName: requestBody.timeFieldName, type: KBN_FIELD_TYPES.DATE }, - ]; - + async (): Promise<NumericChartData['data']> => { logDebugMessage('Fetch overall histogram.'); - let overallTimeSeries: NumericChartData | undefined; - const overallHistogramQuery = getHistogramQuery(requestBody); + const miniHistogramAgg = getMiniHistogramAgg(requestBody); + + const { wrap, unwrap } = createRandomSamplerWrapper({ + probability: stateHandler.sampleProbability() ?? 1, + seed: RANDOM_SAMPLER_SEED, + }); + + let resp; try { - overallTimeSeries = ( - (await fetchHistogramsForFields({ - esClient, - abortSignal, - arguments: { - indexPattern: requestBody.index, + resp = await esClient.search( + { + index: requestBody.index, + size: 0, + body: { query: overallHistogramQuery, - fields: histogramFields, - samplerShardSize: -1, - randomSamplerProbability: stateHandler.sampleProbability(), - randomSamplerSeed: RANDOM_SAMPLER_SEED, + aggs: wrap(miniHistogramAgg), + size: 0, }, - })) as [NumericChartData] - )[0]; + }, + { signal: abortSignal, maxRetries: 0 } + ); } catch (e) { if (!isRequestAbortedError(e)) { logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`); @@ -66,8 +65,22 @@ export const overallHistogramHandlerFactory = if (stateHandler.shouldStop()) { logDebugMessage('shouldStop after fetching overall histogram.'); responseStream.end(); - return; + return []; + } + + if (resp?.aggregations === undefined) { + if (!isRequestAbortedError(resp)) { + if (logger) { + logger.error( + `Failed to fetch the histogram data chunk, got: \n${JSON.stringify(resp, null, 2)}` + ); + } + + responseStream.pushError(`Failed to fetch the histogram data chunk.`); + } + return []; } - return overallTimeSeries; + const unwrappedResp = unwrap(resp.aggregations) as MiniHistogramAgg; + return unwrappedResp.mini_histogram.buckets; }; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts index 8765ff969bf48..8966b588728d0 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts @@ -53,9 +53,6 @@ export const significantItemsHandlerFactory = keywordFieldCandidates: string[]; textFieldCandidates: string[]; }) => { - let keywordFieldCandidatesCount = keywordFieldCandidates.length; - const textFieldCandidatesCount = textFieldCandidates.length; - // This will store the combined count of detected significant log patterns and keywords let fieldValuePairsCount = 0; @@ -83,7 +80,7 @@ export const significantItemsHandlerFactory = loadingStepSizePValues = LOADED_FIELD_CANDIDATES + PROGRESS_STEP_P_VALUES - requestBody.overrides?.loaded; } else { - loadingStepSizePValues = LOADED_FIELD_CANDIDATES; + loadingStepSizePValues = PROGRESS_STEP_P_VALUES; } if (version === '2') { @@ -93,7 +90,6 @@ export const significantItemsHandlerFactory = if (Array.isArray(overridesRemainingFieldCandidates)) { keywordFieldCandidates.push(...overridesRemainingFieldCandidates); remainingKeywordFieldCandidates = overridesRemainingFieldCandidates; - keywordFieldCandidatesCount = keywordFieldCandidates.length; } else { remainingKeywordFieldCandidates = keywordFieldCandidates; } @@ -107,7 +103,6 @@ export const significantItemsHandlerFactory = if (Array.isArray(overridesRemainingKeywordFieldCandidates)) { keywordFieldCandidates.push(...overridesRemainingKeywordFieldCandidates); remainingKeywordFieldCandidates = overridesRemainingKeywordFieldCandidates; - keywordFieldCandidatesCount = keywordFieldCandidates.length; } else { remainingKeywordFieldCandidates = keywordFieldCandidates; } @@ -125,15 +120,17 @@ export const significantItemsHandlerFactory = logDebugMessage('Fetch p-values.'); - const loadingStep = - (1 / (keywordFieldCandidatesCount + textFieldCandidatesCount)) * loadingStepSizePValues; + const pValuesQueueChunks = [ + ...chunk(textFieldCandidates, QUEUE_CHUNKING_SIZE).map((d) => ({ textFieldCandidates: d })), + ...chunk(keywordFieldCandidates, QUEUE_CHUNKING_SIZE).map((d) => ({ + keywordFieldCandidates: d, + })), + ]; + const loadingStepSize = (1 / pValuesQueueChunks.length) * loadingStepSizePValues; const pValuesQueue = queue(async function (payload: QueueFieldCandidate) { - let queueItemLoadingStep = 0; - if (isKeywordFieldCandidates(payload)) { const { keywordFieldCandidates: fieldNames } = payload; - queueItemLoadingStep = loadingStep * fieldNames.length; let pValues: Awaited<ReturnType<typeof fetchSignificantTermPValues>>; try { @@ -169,7 +166,6 @@ export const significantItemsHandlerFactory = } } else if (isTextFieldCandidates(payload)) { const { textFieldCandidates: fieldNames } = payload; - queueItemLoadingStep = loadingStep * fieldNames.length; let significantCategoriesForField: Awaited<ReturnType<typeof fetchSignificantCategories>>; @@ -206,7 +202,7 @@ export const significantItemsHandlerFactory = } } - stateHandler.loaded(queueItemLoadingStep, false); + stateHandler.loaded(loadingStepSize, false); responseStream.push( updateLoadingState({ @@ -232,26 +228,18 @@ export const significantItemsHandlerFactory = // to the async queue for processing. Each chunk will be part of a single // query using multiple aggs for each candidate. For many candidates, // on top of that the async queue will process multiple queries concurrently. - pValuesQueue.push( - [ - ...chunk(textFieldCandidates, QUEUE_CHUNKING_SIZE).map((d) => ({ textFieldCandidates: d })), - ...chunk(keywordFieldCandidates, QUEUE_CHUNKING_SIZE).map((d) => ({ - keywordFieldCandidates: d, - })), - ], - (err) => { - if (err) { - logger.error(`Failed to fetch p-values.', got: \n${err.toString()}`); - responseStream.pushError(`Failed to fetch p-values.`); - pValuesQueue.kill(); - responseStream.end(); - } else if (stateHandler.shouldStop()) { - logDebugMessage('shouldStop fetching p-values.'); - pValuesQueue.kill(); - responseStream.end(); - } + pValuesQueue.push(pValuesQueueChunks, (err) => { + if (err) { + logger.error(`Failed to fetch p-values.', got: \n${err.toString()}`); + responseStream.pushError(`Failed to fetch p-values.`); + pValuesQueue.kill(); + responseStream.end(); + } else if (stateHandler.shouldStop()) { + logDebugMessage('shouldStop fetching p-values.'); + pValuesQueue.kill(); + responseStream.end(); } - ); + }); await pValuesQueue.drain(); fieldValuePairsCount = significantCategories.length + significantTerms.length; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts index 67432ffc6360e..a59b46887b5ff 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts @@ -6,6 +6,7 @@ */ import { queue } from 'async'; +import { chunk } from 'lodash'; import { SIGNIFICANT_ITEM_TYPE, type SignificantItem } from '@kbn/ml-agg-utils'; import { i18n } from '@kbn/i18n'; @@ -22,6 +23,12 @@ import type { import { isRequestAbortedError } from '@kbn/aiops-common/is_request_aborted_error'; import { fetchTopCategories } from '@kbn/aiops-log-rate-analysis/queries/fetch_top_categories'; import { fetchTopTerms } from '@kbn/aiops-log-rate-analysis/queries/fetch_top_terms'; +import type { QueueFieldCandidate } from '@kbn/aiops-log-rate-analysis/queue_field_candidates'; +import { + isKeywordFieldCandidates, + isTextFieldCandidates, + QUEUE_CHUNKING_SIZE, +} from '@kbn/aiops-log-rate-analysis/queue_field_candidates'; import { LOADED_FIELD_CANDIDATES, @@ -48,8 +55,6 @@ export const topItemsHandlerFactory = keywordFieldCandidates: string[]; textFieldCandidates: string[]; }) => { - let keywordFieldCandidatesCount = keywordFieldCandidates.length; - // This will store the combined count of detected log patterns and keywords let fieldValuePairsCount = 0; @@ -70,25 +75,6 @@ export const topItemsHandlerFactory = ) ?? []) ); - // Get categories of text fields - if (textFieldCandidates.length > 0) { - topCategories.push( - ...(await fetchTopCategories( - esClient, - requestBody, - textFieldCandidates, - logger, - stateHandler.sampleProbability(), - responseStream.pushError, - abortSignal - )) - ); - - if (topCategories.length > 0) { - responseStream.push(addSignificantItems(topCategories)); - } - } - const topTerms: SignificantItem[] = []; topTerms.push( @@ -107,7 +93,6 @@ export const topItemsHandlerFactory = if (Array.isArray(overridesRemainingFieldCandidates)) { keywordFieldCandidates.push(...overridesRemainingFieldCandidates); remainingKeywordFieldCandidates = overridesRemainingFieldCandidates; - keywordFieldCandidatesCount = keywordFieldCandidates.length; loadingStepSizeTopTerms = LOADED_FIELD_CANDIDATES + PROGRESS_STEP_P_VALUES - @@ -123,7 +108,6 @@ export const topItemsHandlerFactory = if (Array.isArray(overridesRemainingKeywordFieldCandidates)) { keywordFieldCandidates.push(...overridesRemainingKeywordFieldCandidates); remainingKeywordFieldCandidates = overridesRemainingKeywordFieldCandidates; - keywordFieldCandidatesCount = keywordFieldCandidates.length; loadingStepSizeTopTerms = LOADED_FIELD_CANDIDATES + PROGRESS_STEP_P_VALUES - @@ -135,58 +119,95 @@ export const topItemsHandlerFactory = logDebugMessage('Fetch top items.'); - const topTermsQueue = queue(async function (fieldCandidate: string) { - stateHandler.loaded((1 / keywordFieldCandidatesCount) * loadingStepSizeTopTerms, false); + const topTermsQueueChunks = [ + ...chunk(keywordFieldCandidates, QUEUE_CHUNKING_SIZE).map((d) => ({ + keywordFieldCandidates: d, + })), + ...chunk(textFieldCandidates, QUEUE_CHUNKING_SIZE).map((d) => ({ textFieldCandidates: d })), + ]; + const loadingStepSize = (1 / topTermsQueueChunks.length) * loadingStepSizeTopTerms; + + const topTermsQueue = queue(async function (payload: QueueFieldCandidate) { + if (isKeywordFieldCandidates(payload)) { + const { keywordFieldCandidates: fieldNames } = payload; + let fetchedTopTerms: Awaited<ReturnType<typeof fetchTopTerms>>; + + try { + fetchedTopTerms = await fetchTopTerms({ + esClient, + logger, + emitError: responseStream.pushError, + abortSignal, + arguments: { + ...requestBody, + fieldNames, + sampleProbability: stateHandler.sampleProbability(), + }, + }); + } catch (e) { + if (!isRequestAbortedError(e)) { + logger.error( + `Failed to fetch top items for ${fieldNames.join()}, got: \n${e.toString()}` + ); + responseStream.pushError(`Failed to fetch top items for ${fieldNames.join()}.`); + } + return; + } - let fetchedTopTerms: Awaited<ReturnType<typeof fetchTopTerms>>; + remainingKeywordFieldCandidates = remainingKeywordFieldCandidates.filter( + (d) => !fieldNames.includes(d) + ); - try { - fetchedTopTerms = await fetchTopTerms( + if (fetchedTopTerms.length > 0) { + topTerms.push(...fetchedTopTerms); + responseStream.push(addSignificantItems(fetchedTopTerms)); + } + + stateHandler.loaded(loadingStepSize, false); + + responseStream.push( + updateLoadingState({ + ccsWarning: false, + loaded: stateHandler.loaded(), + loadingState: i18n.translate( + 'xpack.aiops.logRateAnalysis.loadingState.identifiedFieldValuePairs', + { + defaultMessage: + 'Identified {fieldValuePairsCount, plural, one {# significant field/value pair} other {# significant field/value pairs}}.', + values: { + fieldValuePairsCount, + }, + } + ), + remainingKeywordFieldCandidates, + }) + ); + } else if (isTextFieldCandidates(payload)) { + const { textFieldCandidates: fieldNames } = payload; + + const topCategoriesForField = await fetchTopCategories({ esClient, - requestBody, - [fieldCandidate], logger, - stateHandler.sampleProbability(), - responseStream.pushError, - abortSignal - ); - } catch (e) { - if (!isRequestAbortedError(e)) { - logger.error(`Failed to fetch p-values for '${fieldCandidate}', got: \n${e.toString()}`); - responseStream.pushError(`Failed to fetch p-values for '${fieldCandidate}'.`); + emitError: responseStream.pushError, + abortSignal, + arguments: { + ...requestBody, + fieldNames, + sampleProbability: stateHandler.sampleProbability(), + }, + }); + + if (topCategoriesForField.length > 0) { + topCategories.push(...topCategoriesForField); + responseStream.push(addSignificantItems(topCategoriesForField)); + fieldValuePairsCount += topCategoriesForField.length; } - return; - } - remainingKeywordFieldCandidates = remainingKeywordFieldCandidates.filter( - (d) => d !== fieldCandidate - ); - - if (fetchedTopTerms.length > 0) { - topTerms.push(...fetchedTopTerms); - responseStream.push(addSignificantItems(fetchedTopTerms)); + stateHandler.loaded(loadingStepSize, false); } - - responseStream.push( - updateLoadingState({ - ccsWarning: false, - loaded: stateHandler.loaded(), - loadingState: i18n.translate( - 'xpack.aiops.logRateAnalysis.loadingState.identifiedFieldValuePairs', - { - defaultMessage: - 'Identified {fieldValuePairsCount, plural, one {# significant field/value pair} other {# significant field/value pairs}}.', - values: { - fieldValuePairsCount, - }, - } - ), - remainingKeywordFieldCandidates, - }) - ); }, MAX_CONCURRENT_QUERIES); - topTermsQueue.push(keywordFieldCandidates, (err) => { + topTermsQueue.push(topTermsQueueChunks, (err) => { if (err) { logger.error(`Failed to fetch p-values.', got: \n${err.toString()}`); responseStream.pushError(`Failed to fetch p-values.`); diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/state_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/state_handler.ts index 98763170083f6..1d6239a45f4a2 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/state_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/state_handler.ts @@ -43,9 +43,9 @@ export const stateHandlerFactory = (overrides: Partial<StreamState>) => { function loaded(d?: number, replace = true) { if (typeof d === 'number') { if (replace) { - state.loaded = d; + state.loaded = Math.round(d * 100) / 100; } else { - state.loaded += d; + state.loaded += Math.round(d * 100) / 100; } } else { return state.loaded; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts index 4c8145043891f..e6e680c46a055 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/route_handler_factory.ts @@ -12,6 +12,7 @@ import type { RequestHandler, KibanaResponseFactory, } from '@kbn/core/server'; +import { withSpan } from '@kbn/apm-utils'; import type { Logger } from '@kbn/logging'; import { createExecutionContext } from '@kbn/ml-route-utils'; import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; @@ -27,7 +28,6 @@ import { trackAIOpsRouteUsage } from '../../lib/track_route_usage'; import type { AiopsLicense } from '../../types'; import { responseStreamFactory } from './response_stream_factory'; -import { PROGRESS_STEP_HISTOGRAMS_GROUPS } from './response_stream_utils/constants'; /** * The log rate analysis route handler sets up `responseStreamFactory` @@ -82,7 +82,10 @@ export function routeHandlerFactory<T extends ApiVersion>( responseStream.pushPingWithTimeout(); // Step 1: Index Info: Field candidates and zero docs fallback flag - const indexInfo = await analysis.indexInfoHandler(); + const indexInfo = await withSpan( + { name: 'fetch_index_info', type: 'aiops-log-rate-analysis' }, + () => analysis.indexInfoHandler() + ); if (!indexInfo) { return; @@ -90,8 +93,13 @@ export function routeHandlerFactory<T extends ApiVersion>( // Step 2: Significant categories and terms const significantItemsObj = indexInfo.zeroDocsFallback - ? await analysis.topItemsHandler(indexInfo) - : await analysis.significantItemsHandler(indexInfo); + ? await withSpan({ name: 'fetch_top_items', type: 'aiops-log-rate-analysis' }, () => + analysis.topItemsHandler(indexInfo) + ) + : await withSpan( + { name: 'fetch_significant_items', type: 'aiops-log-rate-analysis' }, + () => analysis.significantItemsHandler(indexInfo) + ); if (!significantItemsObj) { return; @@ -101,27 +109,32 @@ export function routeHandlerFactory<T extends ApiVersion>( significantItemsObj; // Step 3: Fetch overall histogram - const overallTimeSeries = await analysis.overallHistogramHandler(); + const overallTimeSeries = await withSpan( + { name: 'fetch_overall_timeseries', type: 'aiops-log-rate-analysis' }, + () => analysis.overallHistogramHandler() + ); + + // Step 4: Histograms + await withSpan( + { name: 'significant-item-histograms', type: 'aiops-log-rate-analysis' }, + () => + analysis.histogramHandler( + fieldValuePairsCount, + significantCategories, + significantTerms, + overallTimeSeries + ) + ); - // Step 4: Smart gropuing + // Step 5: Smart grouping if (stateHandler.groupingEnabled()) { - await analysis.groupingHandler( - significantCategories, - significantTerms, - overallTimeSeries + await withSpan( + { name: 'grouping-with-histograms', type: 'aiops-log-rate-analysis' }, + () => + analysis.groupingHandler(significantCategories, significantTerms, overallTimeSeries) ); } - stateHandler.loaded(PROGRESS_STEP_HISTOGRAMS_GROUPS, false); - - // Step 5: Histograms - await analysis.histogramHandler( - fieldValuePairsCount, - significantCategories, - significantTerms, - overallTimeSeries - ); - responseStream.endWithUpdatedLoadingState(); } catch (e) { if (!isRequestAbortedError(e)) { diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index dde05c2b6ef93..1c89006ff5eff 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -78,6 +78,7 @@ "@kbn/usage-collection-plugin", "@kbn/utility-types", "@kbn/observability-ai-assistant-plugin", + "@kbn/apm-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/alerting/server/config.test.ts b/x-pack/plugins/alerting/server/config.test.ts index 164b317cdf2ac..9e2d2d089ac62 100644 --- a/x-pack/plugins/alerting/server/config.test.ts +++ b/x-pack/plugins/alerting/server/config.test.ts @@ -23,7 +23,7 @@ describe('config validation', () => { }, "maxEphemeralActionsPerAlert": 10, "rules": Object { - "maxScheduledPerMinute": 10000, + "maxScheduledPerMinute": 32000, "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 6dd96667f9553..ccacc6440b03a 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -38,7 +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 }), + maxScheduledPerMinute: schema.number({ defaultValue: 32000, max: 32000, min: 0 }), overwriteProducer: schema.maybe( schema.oneOf([ schema.literal('observability'), diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index 3ee3551a301d5..e678228660e51 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -564,6 +564,39 @@ describe('Create Lifecycle', () => { }); }); + test('injects custom cost for certain rule types', () => { + const ruleType: RuleType<never, never, never, never, never, 'default', 'recovered', {}> = { + id: 'siem.indicatorRule', + name: 'Test', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + executor: jest.fn(), + category: 'test', + producer: 'alerts', + ruleTaskTimeout: '20m', + validate: { + params: { validate: (params) => params }, + }, + }; + const registry = new RuleTypeRegistry(ruleTypeRegistryParams); + registry.register(ruleType); + expect(taskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1); + expect(taskManager.registerTaskDefinitions.mock.calls[0][0]).toMatchObject({ + 'alerting:siem.indicatorRule': { + timeout: '20m', + title: 'Test', + cost: 10, + }, + }); + }); + test('shallow clones the given rule type', () => { const ruleType: RuleType<never, never, never, never, never, 'default', 'recovered', {}> = { id: 'test', diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index d1ffe59df3b6f..bc7a10d767ff0 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -14,6 +14,7 @@ import { Logger } from '@kbn/core/server'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import { RunContext, TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { stateSchemaByVersion } from '@kbn/alerting-state-types'; +import { TaskCost } from '@kbn/task-manager-plugin/server/task'; import { TaskRunnerFactory } from './task_runner'; import { RuleType, @@ -40,6 +41,9 @@ import { AlertsService } from './alerts_service/alerts_service'; import { getRuleTypeIdValidLegacyConsumers } from './rule_type_registry_deprecated_consumers'; import { AlertingConfig } from './config'; +const RULE_TYPES_WITH_CUSTOM_COST: Record<string, TaskCost> = { + 'siem.indicatorRule': TaskCost.ExtraLarge, +}; export interface ConstructorOptions { config: AlertingConfig; logger: Logger; @@ -289,6 +293,8 @@ export class RuleTypeRegistry { normalizedRuleType as unknown as UntypedNormalizedRuleType ); + const taskCost: TaskCost | undefined = RULE_TYPES_WITH_CUSTOM_COST[ruleType.id]; + this.taskManager.registerTaskDefinitions({ [`alerting:${ruleType.id}`]: { title: ruleType.name, @@ -310,6 +316,7 @@ export class RuleTypeRegistry { spaceId: schema.string(), consumer: schema.maybe(schema.string()), }), + ...(taskCost ? { cost: taskCost } : {}), }, }); diff --git a/x-pack/plugins/cases/public/common/mock/test_providers.tsx b/x-pack/plugins/cases/public/common/mock/test_providers.tsx index 20efe528429c2..820ae554c3aa7 100644 --- a/x-pack/plugins/cases/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/cases/public/common/mock/test_providers.tsx @@ -7,28 +7,23 @@ /* eslint-disable no-console */ -import React from 'react'; +import React, { useMemo } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { ThemeProvider } from '@emotion/react'; - import { render as reactRender } from '@testing-library/react'; import type { RenderOptions, RenderResult } from '@testing-library/react'; import type { ILicense } from '@kbn/licensing-plugin/public'; import type { ScopedFilesClient } from '@kbn/files-plugin/public'; - -import { euiDarkVars } from '@kbn/ui-theme'; -import { I18nProvider } from '@kbn/i18n-react'; import { createMockFilesClient } from '@kbn/shared-ux-file-mocks'; import { QueryClient } from '@tanstack/react-query'; - import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { FilesContext } from '@kbn/shared-ux-file-context'; - import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { coreMock } from '@kbn/core/public/mocks'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; + import type { CasesFeatures, CasesPermissions } from '../../../common/ui/types'; import type { StartServices } from '../../types'; import type { ReleasePhase } from '../../components/types'; - import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { CasesProvider } from '../../components/cases_context'; import { createStartServicesMock } from '../lib/kibana/kibana_react.mock'; @@ -77,7 +72,8 @@ const TestProvidersComponent: React.FC<TestProviderProps> = ({ persistableStateAttachmentTypeRegistry = new PersistableStateAttachmentTypeRegistry(), license, }) => { - const services = createStartServicesMock({ license }); + const coreStart = useMemo(() => coreMock.createStart(), []); + const services = useMemo(() => createStartServicesMock({ license }), [license]); const queryClient = new QueryClient({ defaultOptions: { @@ -95,27 +91,25 @@ const TestProvidersComponent: React.FC<TestProviderProps> = ({ const getFilesClient = mockGetFilesClient(); return ( - <I18nProvider> + <KibanaRenderContextProvider i18n={coreStart.i18n} theme={coreStart.theme}> <KibanaContextProvider services={services}> - <ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}> - <MemoryRouter> - <CasesProvider - value={{ - externalReferenceAttachmentTypeRegistry, - persistableStateAttachmentTypeRegistry, - features, - owner, - permissions, - getFilesClient, - }} - queryClient={queryClient} - > - <FilesContext client={createMockFilesClient()}>{children}</FilesContext> - </CasesProvider> - </MemoryRouter> - </ThemeProvider> + <MemoryRouter> + <CasesProvider + value={{ + externalReferenceAttachmentTypeRegistry, + persistableStateAttachmentTypeRegistry, + features, + owner, + permissions, + getFilesClient, + }} + queryClient={queryClient} + > + <FilesContext client={createMockFilesClient()}>{children}</FilesContext> + </CasesProvider> + </MemoryRouter> </KibanaContextProvider> - </I18nProvider> + </KibanaRenderContextProvider> ); }; TestProvidersComponent.displayName = 'TestProviders'; @@ -158,6 +152,7 @@ export const createAppMockRenderer = ({ persistableStateAttachmentTypeRegistry = new PersistableStateAttachmentTypeRegistry(), license, }: Omit<TestProviderProps, 'children'> = {}): AppMockRenderer => { + const coreStart = coreMock.createStart(); const services = createStartServicesMock({ license }); const queryClient = new QueryClient({ @@ -176,28 +171,26 @@ export const createAppMockRenderer = ({ const getFilesClient = mockGetFilesClient(); const AppWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( - <I18nProvider> + <KibanaRenderContextProvider i18n={coreStart.i18n} theme={coreStart.theme}> <KibanaContextProvider services={services}> - <ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}> - <MemoryRouter> - <CasesProvider - value={{ - externalReferenceAttachmentTypeRegistry, - persistableStateAttachmentTypeRegistry, - features, - owner, - permissions, - releasePhase, - getFilesClient, - }} - queryClient={queryClient} - > - {children} - </CasesProvider> - </MemoryRouter> - </ThemeProvider> + <MemoryRouter> + <CasesProvider + value={{ + externalReferenceAttachmentTypeRegistry, + persistableStateAttachmentTypeRegistry, + features, + owner, + permissions, + releasePhase, + getFilesClient, + }} + queryClient={queryClient} + > + {children} + </CasesProvider> + </MemoryRouter> </KibanaContextProvider> - </I18nProvider> + </KibanaRenderContextProvider> ); AppWrapper.displayName = 'AppWrapper'; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index 242fc3260b7e2..5324a5a793067 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -184,31 +184,28 @@ describe('AllCasesListGeneric', () => { useLicenseMock.mockReturnValue({ isAtLeastPlatinum: () => true }); appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getAllByTestId('case-details-link')[0]).toHaveAttribute( - 'href', - '/app/security/cases/test' - ); - expect(screen.getAllByTestId('case-details-link')[0]).toHaveTextContent( - useGetCasesMockState.data.cases[0].title - ); - expect( - screen.getAllByTestId('case-user-profile-avatar-damaged_raccoon')[0] - ).toHaveTextContent('DR'); - expect(screen.getAllByTestId('case-table-column-tags-coke')[0]).toHaveAttribute( - 'title', - useGetCasesMockState.data.cases[0].tags[0] - ); - expect( - screen.getAllByTestId('case-table-column-createdAt')[0].querySelector('.euiToolTipAnchor') - ).toHaveTextContent(removeMsFromDate(useGetCasesMockState.data.cases[0].createdAt)); - expect(screen.getByTestId('case-table-case-count')).toHaveTextContent( - `Showing 10 of ${useGetCasesMockState.data.total} cases` - ); + const caseDetailsLinks = await screen.findAllByTestId('case-details-link'); + + expect(caseDetailsLinks[0]).toHaveAttribute('href', '/app/security/cases/test'); + expect(caseDetailsLinks[0]).toHaveTextContent(useGetCasesMockState.data.cases[0].title); + expect( + (await screen.findAllByTestId('case-user-profile-avatar-damaged_raccoon'))[0] + ).toHaveTextContent('DR'); + expect((await screen.findAllByTestId('case-table-column-tags-coke'))[0]).toHaveAttribute( + 'title', + useGetCasesMockState.data.cases[0].tags[0] + ); + expect( + (await screen.findAllByTestId('case-table-column-createdAt'))[0].querySelector( + '.euiToolTipAnchor' + ) + ).toHaveTextContent(removeMsFromDate(useGetCasesMockState.data.cases[0].createdAt)); + expect(await screen.findByTestId('case-table-case-count')).toHaveTextContent( + `Showing 10 of ${useGetCasesMockState.data.total} cases` + ); - expect(screen.queryByTestId('all-cases-maximum-limit-warning')).not.toBeInTheDocument(); - expect(screen.queryByTestId('all-cases-clear-filters-link-icon')).not.toBeInTheDocument(); - }); + expect(screen.queryByTestId('all-cases-maximum-limit-warning')).not.toBeInTheDocument(); + expect(screen.queryByTestId('all-cases-clear-filters-link-icon')).not.toBeInTheDocument(); }); it("should show a tooltip with the assignee's email when hover over the assignee avatar", async () => { @@ -216,21 +213,17 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); - userEvent.hover(screen.queryAllByTestId('case-user-profile-avatar-damaged_raccoon')[0]); + userEvent.hover((await screen.findAllByTestId('case-user-profile-avatar-damaged_raccoon'))[0]); - await waitFor(() => { - expect(screen.getByText('damaged_raccoon@elastic.co')).toBeInTheDocument(); - }); + expect(await screen.findByText('damaged_raccoon@elastic.co')).toBeInTheDocument(); }); it('should show a tooltip with all tags when hovered', async () => { appMockRenderer.render(<AllCasesList />); - userEvent.hover(screen.queryAllByTestId('case-table-column-tags')[0]); + userEvent.hover((await screen.findAllByTestId('case-table-column-tags'))[0]); - await waitFor(() => { - expect(screen.getByTestId('case-table-column-tags-tooltip')).toBeTruthy(); - }); + expect(await screen.findByTestId('case-table-column-tags-tooltip')).toBeTruthy(); }); it('should render empty fields', async () => { @@ -259,8 +252,10 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); - const checkIt = (columnName: string, key: number) => { - const column = screen.getByTestId('cases-table').querySelectorAll('tbody .euiTableRowCell'); + const checkIt = async (columnName: string, key: number) => { + const column = (await screen.findByTestId('cases-table')).querySelectorAll( + 'tbody .euiTableRowCell' + ); expect(column[key].querySelector('.euiTableRowCell--hideForDesktop')).toHaveTextContent( columnName ); @@ -290,7 +285,7 @@ describe('AllCasesListGeneric', () => { }, }); appMockRenderer.render(<AllCasesList isSelectorView={false} />); - userEvent.click(screen.getByTestId('cases-table-add-case')); + userEvent.click(await screen.findByTestId('cases-table-add-case')); await waitFor(() => { expect(onRowClick).not.toHaveBeenCalled(); }); @@ -299,7 +294,7 @@ describe('AllCasesListGeneric', () => { it('should tableHeaderSortButton AllCasesList', async () => { appMockRenderer.render(<AllCasesList />); - userEvent.click(screen.getAllByTestId('tableHeaderSortButton')[0]); + userEvent.click((await screen.findAllByTestId('tableHeaderSortButton'))[0]); await waitFor(() => { expect(useGetCasesMock).toBeCalledWith( @@ -315,28 +310,26 @@ describe('AllCasesListGeneric', () => { it('renders the columns correctly', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - const casesTable = within(screen.getByTestId('cases-table')); - - expect(casesTable.getByTitle('Name')).toBeInTheDocument(); - expect(casesTable.getByTitle('Category')).toBeInTheDocument(); - expect(casesTable.getByTitle('Created on')).toBeInTheDocument(); - expect(casesTable.getByTitle('Updated on')).toBeInTheDocument(); - expect(casesTable.getByTitle('Status')).toBeInTheDocument(); - expect(casesTable.getByTitle('Severity')).toBeInTheDocument(); - expect(casesTable.getByTitle('Tags')).toBeInTheDocument(); - expect(casesTable.getByTitle('Alerts')).toBeInTheDocument(); - expect(casesTable.getByTitle('Comments')).toBeInTheDocument(); - expect(casesTable.getByTitle('External incident')).toBeInTheDocument(); - expect(casesTable.getByTitle('Actions')).toBeInTheDocument(); + const casesTable = within(await screen.findByTestId('cases-table')); + + expect(await casesTable.findByTitle('Name')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Category')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Created on')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Updated on')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Status')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Severity')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Tags')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Alerts')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Comments')).toBeInTheDocument(); + expect(await casesTable.findByTitle('External incident')).toBeInTheDocument(); + expect(await casesTable.findByTitle('Actions')).toBeInTheDocument(); }); it('should not render table utility bar when isSelectorView=true', async () => { appMockRenderer.render(<AllCasesList isSelectorView={true} />); - await waitFor(() => { - expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); - expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); - }); + expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); + expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); }); it('should not render table utility bar when the user does not have permissions to delete', async () => { @@ -346,31 +339,28 @@ describe('AllCasesListGeneric', () => { </TestProviders> ); - await waitFor(() => { - expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); - expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); - }); + expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); + expect(screen.queryByTestId('case-table-bulk-actions')).not.toBeInTheDocument(); }); it('should render metrics when isSelectorView=false', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - await waitFor(() => { - expect(screen.getByTestId('cases-metrics-stats')).toBeInTheDocument(); - }); + + expect(await screen.findByTestId('cases-metrics-stats')).toBeInTheDocument(); }); it('should not render metrics when isSelectorView=true', async () => { appMockRenderer.render(<AllCasesList isSelectorView={true} />); - await waitFor(() => { - expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); - expect(screen.queryByTestId('cases-metrics-stats')).not.toBeInTheDocument(); - }); + + expect(screen.queryByTestId('case-table-selected-case-count')).not.toBeInTheDocument(); + expect(screen.queryByTestId('cases-metrics-stats')).not.toBeInTheDocument(); }); it('should call onRowClick with no cases and isSelectorView=true when create case is clicked', async () => { appMockRenderer.render(<AllCasesList isSelectorView={true} onRowClick={onRowClick} />); - userEvent.click(screen.getByTestId('cases-table-add-case-filter-bar')); + userEvent.click(await screen.findByTestId('cases-table-add-case-filter-bar')); const isCreateCase = true; + await waitFor(() => { expect(onRowClick).toHaveBeenCalled(); expect(onRowClick).toBeCalledWith(undefined, isCreateCase); @@ -382,7 +372,7 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList isSelectorView={true} onRowClick={onRowClick} />); - userEvent.click(screen.getByTestId(`cases-table-row-select-${theCase.id}`)); + userEvent.click(await screen.findByTestId(`cases-table-row-select-${theCase.id}`)); await waitFor(() => { expect(onRowClick).toHaveBeenCalledWith(theCase); @@ -392,7 +382,7 @@ describe('AllCasesListGeneric', () => { it('should NOT call onRowClick when clicking a case with modal=true', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - userEvent.click(screen.getByTestId('cases-table-row-1')); + userEvent.click(await screen.findByTestId('cases-table-row-1')); await waitFor(() => { expect(onRowClick).not.toHaveBeenCalled(); @@ -403,7 +393,7 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); // 0 is the status filter button label - userEvent.click(screen.getAllByTitle('Status')[1]); + userEvent.click((await screen.findAllByTitle('Status'))[1]); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -420,20 +410,19 @@ describe('AllCasesListGeneric', () => { it('should render Name, Category, CreatedOn and Severity columns when isSelectorView=true', async () => { appMockRenderer.render(<AllCasesList isSelectorView={true} />); - await waitFor(() => { - expect(screen.getByTitle('Name')).toBeInTheDocument(); - expect(screen.getByTitle('Category')).toBeInTheDocument(); - expect(screen.getByTitle('Created on')).toBeInTheDocument(); - // 0 is the severity filter button label - expect(screen.getAllByTitle('Severity')[1]).toBeInTheDocument(); - }); + + expect(await screen.findByTitle('Name')).toBeInTheDocument(); + expect(await screen.findByTitle('Category')).toBeInTheDocument(); + expect(await screen.findByTitle('Created on')).toBeInTheDocument(); + // 0 is the severity filter button label + expect((await screen.findAllByTitle('Severity'))[1]).toBeInTheDocument(); }); it('should sort by severity', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); // 0 is the severity filter button label - userEvent.click(screen.getAllByTitle('Severity')[1]); + userEvent.click((await screen.findAllByTitle('Severity'))[1]); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -451,7 +440,7 @@ describe('AllCasesListGeneric', () => { it('should sort by title', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - userEvent.click(screen.getByTitle('Name')); + userEvent.click(await screen.findByTitle('Name')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -469,7 +458,7 @@ describe('AllCasesListGeneric', () => { it('should sort by updatedOn', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - userEvent.click(screen.getByTitle('Updated on')); + userEvent.click(await screen.findByTitle('Updated on')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -487,7 +476,7 @@ describe('AllCasesListGeneric', () => { it('should sort by category', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - userEvent.click(screen.getByTitle('Category')); + userEvent.click(await screen.findByTitle('Category')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith( @@ -505,9 +494,9 @@ describe('AllCasesListGeneric', () => { it('should filter by category', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - userEvent.click(screen.getByTestId('options-filter-popover-button-category')); + userEvent.click(await screen.findByTestId('options-filter-popover-button-category')); await waitForEuiPopoverOpen(); - userEvent.click(screen.getByTestId('options-filter-popover-item-twix')); + userEvent.click(await screen.findByTestId('options-filter-popover-item-twix')); await waitFor(() => { expect(useGetCasesMock).toHaveBeenLastCalledWith({ @@ -524,17 +513,17 @@ describe('AllCasesListGeneric', () => { it('should show the correct count on stats', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - userEvent.click(screen.getByTestId('options-filter-popover-button-status')); + userEvent.click(await screen.findByTestId('options-filter-popover-button-status')); - await waitFor(() => { - expect(screen.getByTestId('options-filter-popover-item-open')).toHaveTextContent('Open (20)'); - expect(screen.getByTestId('options-filter-popover-item-in-progress')).toHaveTextContent( - 'In progress (40)' - ); - expect(screen.getByTestId('options-filter-popover-item-closed')).toHaveTextContent( - 'Closed (130)' - ); - }); + expect(await screen.findByTestId('options-filter-popover-item-open')).toHaveTextContent( + 'Open (20)' + ); + expect(await screen.findByTestId('options-filter-popover-item-in-progress')).toHaveTextContent( + 'In progress (40)' + ); + expect(await screen.findByTestId('options-filter-popover-item-closed')).toHaveTextContent( + 'Closed (130)' + ); }); it('shows Solution column if there are no set owners', async () => { @@ -544,17 +533,13 @@ describe('AllCasesListGeneric', () => { </TestProviders> ); - await waitFor(() => { - expect(screen.getAllByText('Solution')[0]).toBeInTheDocument(); - }); + expect((await screen.findAllByText('Solution'))[0]).toBeInTheDocument(); }); it('hides Solution column if there is a set owner', async () => { appMockRenderer.render(<AllCasesList isSelectorView={false} />); - await waitFor(() => { - expect(screen.queryByText('Solution')).not.toBeInTheDocument(); - }); + expect(screen.queryByText('Solution')).not.toBeInTheDocument(); }); it('should deselect cases when refreshing', async () => { @@ -568,7 +553,7 @@ describe('AllCasesListGeneric', () => { expect(checkbox).toBeChecked(); } - userEvent.click(screen.getByText('Refresh')); + userEvent.click(await screen.findByText('Refresh')); for (const checkbox of checkboxes) { expect(checkbox).not.toBeChecked(); } @@ -593,9 +578,9 @@ describe('AllCasesListGeneric', () => { expect(checkbox).toBeChecked(); } - userEvent.click(screen.getByTestId('options-filter-popover-button-status')); + userEvent.click(await screen.findByTestId('options-filter-popover-button-status')); await waitForEuiPopoverOpen(); - userEvent.click(screen.getByTestId('options-filter-popover-item-open')); + userEvent.click(await screen.findByTestId('options-filter-popover-item-open')); for (const checkbox of checkboxes) { expect(checkbox).not.toBeChecked(); @@ -611,10 +596,8 @@ describe('AllCasesListGeneric', () => { </TestProviders> ); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('case-table-column-alertsCount').length).toBe(0); - }); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); + expect(screen.queryAllByTestId('case-table-column-alertsCount').length).toBe(0); }); it('should show the alerts column if the alert feature is enabled', async () => { @@ -659,13 +642,13 @@ describe('AllCasesListGeneric', () => { describe('Solutions', () => { it('should hide the solutions filter if the owner is provided', async () => { - const { queryByTestId } = render( + render( <TestProviders owner={[SECURITY_SOLUTION_OWNER]}> <AllCasesList /> </TestProviders> ); - expect(queryByTestId('options-filter-popover-button-owner')).toBeFalsy(); + expect(screen.queryByTestId('options-filter-popover-button-owner')).not.toBeInTheDocument(); }); }); @@ -677,16 +660,13 @@ describe('AllCasesListGeneric', () => { it('Renders bulk action', async () => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('checkboxSelectAll')); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); - userEvent.click(screen.getByText('Bulk actions')); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); + userEvent.click(await screen.findByText('Bulk actions')); - expect(screen.getByTestId('case-bulk-action-status')).toBeInTheDocument(); - expect(screen.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); + expect(await screen.findByTestId('case-bulk-action-status')).toBeInTheDocument(); + expect(await screen.findByTestId('cases-bulk-action-delete')).toBeInTheDocument(); }); it.each([[CaseStatuses.open], [CaseStatuses['in-progress']], [CaseStatuses.closed]])( @@ -694,25 +674,21 @@ describe('AllCasesListGeneric', () => { async (status) => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('checkboxSelectAll')); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); - userEvent.click(screen.getByText('Bulk actions')); + userEvent.click(await screen.findByText('Bulk actions')); - userEvent.click(screen.getByTestId('case-bulk-action-status'), undefined, { + userEvent.click(await screen.findByTestId('case-bulk-action-status'), undefined, { skipPointerEventsCheck: true, }); - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-status-${status}`)).toBeInTheDocument(); - }); + expect( + await screen.findByTestId(`cases-bulk-action-status-${status}`) + ).toBeInTheDocument(); - userEvent.click(screen.getByTestId(`cases-bulk-action-status-${status}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-status-${status}`)); await waitFor(() => { expect(updateCasesSpy).toBeCalledWith({ @@ -734,25 +710,21 @@ describe('AllCasesListGeneric', () => { ])('Bulk update severity: %s', async (severity) => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('checkboxSelectAll')); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); + userEvent.click(await screen.findByText('Bulk actions')); - userEvent.click(screen.getByText('Bulk actions')); - - userEvent.click(screen.getByTestId('case-bulk-action-severity'), undefined, { + userEvent.click(await screen.findByTestId('case-bulk-action-severity'), undefined, { skipPointerEventsCheck: true, }); - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-severity-${severity}`)).toBeInTheDocument(); - }); + expect( + await screen.findByTestId(`cases-bulk-action-severity-${severity}`) + ).toBeInTheDocument(); - userEvent.click(screen.getByTestId(`cases-bulk-action-severity-${severity}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-severity-${severity}`)); await waitFor(() => { expect(updateCasesSpy).toBeCalledWith({ @@ -768,23 +740,19 @@ describe('AllCasesListGeneric', () => { it('Bulk delete', async () => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeInTheDocument(); - }); - - userEvent.click(screen.getByTestId('checkboxSelectAll')); + expect(await screen.findByTestId('cases-table')).toBeInTheDocument(); - expect(screen.getByText('Bulk actions')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); - userEvent.click(screen.getByText('Bulk actions')); + userEvent.click(await screen.findByText('Bulk actions')); - userEvent.click(screen.getByTestId('cases-bulk-action-delete'), undefined, { + userEvent.click(await screen.findByTestId('cases-bulk-action-delete'), undefined, { skipPointerEventsCheck: true, }); - expect(screen.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-case-modal')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('confirmModalConfirmButton')); + userEvent.click(await screen.findByTestId('confirmModalConfirmButton')); await waitFor(() => { expect(deleteCasesSpy).toHaveBeenCalledWith({ @@ -806,18 +774,15 @@ describe('AllCasesListGeneric', () => { appMockRenderer = createAppMockRenderer({ permissions: readCasesPermissions() }); appMockRenderer.render(<AllCasesList />); - expect(screen.getByTestId('checkboxSelectAll')).toBeDisabled(); + expect(await screen.findByTestId('checkboxSelectAll')).toBeDisabled(); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect(screen.getByTestId(`checkboxSelectRow-${theCase.id}`)).toBeDisabled(); - }); + expect(await screen.findByTestId(`checkboxSelectRow-${theCase.id}`)).toBeDisabled(); } }); }); - // FLAKY: https://github.com/elastic/kibana/issues/148095 - describe.skip('Row actions', () => { + describe('Row actions', () => { const statusTests = [ [CaseStatuses.open], [CaseStatuses['in-progress']], @@ -835,11 +800,9 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect( - screen.getByTestId(`case-action-popover-button-${theCase.id}`) - ).toBeInTheDocument(); - }); + expect( + await screen.findByTestId(`case-action-popover-button-${theCase.id}`) + ).toBeInTheDocument(); } }); @@ -849,21 +812,17 @@ describe('AllCasesListGeneric', () => { const inProgressCase = useGetCasesMockState.data.cases[1]; const theCase = status === CaseStatuses.open ? inProgressCase : openCase; - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-popover-button-${theCase.id}`)); + userEvent.click(await screen.findByTestId(`case-action-popover-button-${theCase.id}`)); - expect(screen.getByTestId(`case-action-status-panel-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-status-panel-${theCase.id}`), undefined, { - skipPointerEventsCheck: true, - }); - - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-status-${status}`)).toBeInTheDocument(); - }); + userEvent.click( + await screen.findByTestId(`case-action-status-panel-${theCase.id}`), + undefined, + { + skipPointerEventsCheck: true, + } + ); - userEvent.click(screen.getByTestId(`cases-bulk-action-status-${status}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-status-${status}`)); await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalledWith({ @@ -878,21 +837,17 @@ describe('AllCasesListGeneric', () => { const mediumCase = useGetCasesMockState.data.cases[1]; const theCase = severity === CaseSeverity.LOW ? mediumCase : lowCase; - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); + userEvent.click(await screen.findByTestId(`case-action-popover-button-${theCase.id}`)); - userEvent.click(screen.getByTestId(`case-action-popover-button-${theCase.id}`)); - - expect(screen.getByTestId(`case-action-severity-panel-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-severity-panel-${theCase.id}`), undefined, { - skipPointerEventsCheck: true, - }); - - await waitFor(() => { - expect(screen.getByTestId(`cases-bulk-action-severity-${severity}`)).toBeInTheDocument(); - }); + userEvent.click( + await screen.findByTestId(`case-action-severity-panel-${theCase.id}`), + undefined, + { + skipPointerEventsCheck: true, + } + ); - userEvent.click(screen.getByTestId(`cases-bulk-action-severity-${severity}`)); + userEvent.click(await screen.findByTestId(`cases-bulk-action-severity-${severity}`)); await waitFor(() => { expect(updateCasesSpy).toHaveBeenCalledWith({ @@ -905,19 +860,15 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); const theCase = defaultGetCases.data.cases[0]; - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeInTheDocument(); - - userEvent.click(screen.getByTestId(`case-action-popover-button-${theCase.id}`)); + userEvent.click(await screen.findByTestId(`case-action-popover-button-${theCase.id}`)); - expect(screen.getByTestId('cases-bulk-action-delete')).toBeInTheDocument(); - - userEvent.click(screen.getByTestId('cases-bulk-action-delete'), undefined, { + userEvent.click(await screen.findByTestId('cases-bulk-action-delete'), undefined, { skipPointerEventsCheck: true, }); - expect(screen.getByTestId('confirm-delete-case-modal')).toBeInTheDocument(); + expect(await screen.findByTestId('confirm-delete-case-modal')).toBeInTheDocument(); - userEvent.click(screen.getByTestId('confirmModalConfirmButton')); + userEvent.click(await screen.findByTestId('confirmModalConfirmButton')); await waitFor(() => { expect(deleteCasesSpy).toHaveBeenCalledWith({ caseIds: ['basic-case-id'] }); @@ -927,12 +878,12 @@ describe('AllCasesListGeneric', () => { it('should disable row actions when bulk selecting all cases', async () => { appMockRenderer.render(<AllCasesList />); - userEvent.click(screen.getByTestId('checkboxSelectAll')); + userEvent.click(await screen.findByTestId('checkboxSelectAll')); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeDisabled(); - }); + expect( + await screen.findByTestId(`case-action-popover-button-${theCase.id}`) + ).toBeDisabled(); } }); @@ -940,12 +891,12 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); const caseToSelect = defaultGetCases.data.cases[0]; - userEvent.click(screen.getByTestId(`checkboxSelectRow-${caseToSelect.id}`)); + userEvent.click(await screen.findByTestId(`checkboxSelectRow-${caseToSelect.id}`)); for (const theCase of defaultGetCases.data.cases) { - await waitFor(() => { - expect(screen.getByTestId(`case-action-popover-button-${theCase.id}`)).toBeDisabled(); - }); + expect( + await screen.findByTestId(`case-action-popover-button-${theCase.id}`) + ).toBeDisabled(); } }); }); @@ -956,10 +907,8 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('case-table-column-assignee').length).toBe(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect(screen.queryAllByTestId('case-table-column-assignee').length).toBe(0); }); it('should show the assignees column on platinum license', async () => { @@ -967,10 +916,8 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('case-table-column-assignee').length).toBeGreaterThan(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect(screen.queryAllByTestId('case-table-column-assignee').length).toBeGreaterThan(0); }); it('should hide the assignees filters on basic license', async () => { @@ -978,10 +925,8 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect(screen.queryAllByTestId('options-filter-popover-button-assignees').length).toBe(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect(screen.queryAllByTestId('options-filter-popover-button-assignees').length).toBe(0); }); it('should show the assignees filters on platinum license', async () => { @@ -989,12 +934,10 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); - await waitFor(() => { - expect(screen.getByTestId('cases-table')).toBeTruthy(); - expect( - screen.queryAllByTestId('options-filter-popover-button-assignees').length - ).toBeGreaterThan(0); - }); + expect(await screen.findByTestId('cases-table')).toBeTruthy(); + expect( + screen.queryAllByTestId('options-filter-popover-button-assignees').length + ).toBeGreaterThan(0); }); it('should reset the assignees when deactivating the filter', async () => { @@ -1003,15 +946,19 @@ describe('AllCasesListGeneric', () => { appMockRenderer.render(<AllCasesList />); // Opens assignees filter and checks an option - const assigneesButton = screen.getByTestId('options-filter-popover-button-assignees'); + const assigneesButton = await screen.findByTestId( + 'options-filter-popover-button-assignees' + ); userEvent.click(assigneesButton); - userEvent.click(screen.getByText('Damaged Raccoon')); - expect(within(assigneesButton).getByLabelText('1 active filters')).toBeInTheDocument(); + userEvent.click(await screen.findByText('Damaged Raccoon')); + expect( + await within(assigneesButton).findByLabelText('1 active filters') + ).toBeInTheDocument(); // Deactivates assignees filter - userEvent.click(screen.getByRole('button', { name: 'More' })); + userEvent.click(await screen.findByRole('button', { name: 'More' })); await waitForEuiPopoverOpen(); - userEvent.click(screen.getByRole('option', { name: 'Assignees' })); + userEvent.click(await screen.findByRole('option', { name: 'Assignees' })); expect(useGetCasesMock).toHaveBeenLastCalledWith({ filterOptions: { @@ -1022,14 +969,14 @@ describe('AllCasesListGeneric', () => { }); // Reopens assignees filter - userEvent.click(screen.getByRole('option', { name: 'Assignees' })); + userEvent.click(await screen.findByRole('option', { name: 'Assignees' })); // Opens the assignees popup userEvent.click(assigneesButton); - expect(screen.getByLabelText('click to filter assignees')).toBeInTheDocument(); + expect(await screen.findByLabelText('click to filter assignees')).toBeInTheDocument(); expect( - within(screen.getByTestId('options-filter-popover-button-assignees')).queryByLabelText( - '1 active filters' - ) + within( + await screen.findByTestId('options-filter-popover-button-assignees') + ).queryByLabelText('1 active filters') ).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx index 10bdd185ef9f1..c2575b146b9f8 100644 --- a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.test.tsx @@ -6,12 +6,11 @@ */ import React from 'react'; import { MultiSelectFilter } from './multi_select_filter'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; -// FLAKY: https://github.com/elastic/kibana/issues/183663 -describe.skip('multi select filter', () => { +describe('multi select filter', () => { it('should render the amount of options available', async () => { const onChange = jest.fn(); const props = { @@ -29,10 +28,10 @@ describe.skip('multi select filter', () => { render(<MultiSelectFilter {...props} />); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getByText('4 options')).toBeInTheDocument(); + expect(await screen.findByText('4 options')).toBeInTheDocument(); }); it('hides the limit reached warning when a selected tag is removed', async () => { @@ -53,14 +52,17 @@ describe.skip('multi select filter', () => { const { rerender } = render(<MultiSelectFilter {...props} />); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getByText('Limit reached')).toBeInTheDocument(); + expect(await screen.findByText('Limit reached')).toBeInTheDocument(); - userEvent.click(screen.getByRole('option', { name: 'tag a' })); + userEvent.click(await screen.findByRole('option', { name: 'tag a' })); + + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); + }); - expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); rerender(<MultiSelectFilter {...props} selectedOptionKeys={[]} />); expect(screen.queryByText('Limit reached')).not.toBeInTheDocument(); @@ -84,20 +86,23 @@ describe.skip('multi select filter', () => { const { rerender } = render(<MultiSelectFilter {...props} />); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); expect(screen.queryByText('Limit reached')).not.toBeInTheDocument(); - userEvent.click(screen.getByRole('option', { name: 'tag b' })); + userEvent.click(await screen.findByRole('option', { name: 'tag b' })); - expect(onChange).toHaveBeenCalledWith({ - filterId: 'tags', - selectedOptionKeys: ['tag a', 'tag b'], + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ + filterId: 'tags', + selectedOptionKeys: ['tag a', 'tag b'], + }); }); + rerender(<MultiSelectFilter {...props} selectedOptionKeys={['tag a', 'tag b']} />); - expect(screen.getByText('Limit reached')).toBeInTheDocument(); + expect(await screen.findByText('Limit reached')).toBeInTheDocument(); }); it('should not call onChange when the limit has been reached', async () => { @@ -118,14 +123,16 @@ describe.skip('multi select filter', () => { render(<MultiSelectFilter {...props} />); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getByText('Limit reached')).toBeInTheDocument(); + expect(await screen.findByText('Limit reached')).toBeInTheDocument(); - userEvent.click(screen.getByRole('option', { name: 'tag b' })); + userEvent.click(await screen.findByRole('option', { name: 'tag b' })); - expect(onChange).not.toHaveBeenCalled(); + await waitFor(() => { + expect(onChange).not.toHaveBeenCalled(); + }); }); it('should remove selected option if it suddenly disappeared from the list', async () => { @@ -144,7 +151,10 @@ describe.skip('multi select filter', () => { const { rerender } = render(<MultiSelectFilter {...props} />); rerender(<MultiSelectFilter {...props} options={[{ key: 'tag a', label: 'tag a' }]} />); - expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); + + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith({ filterId: 'tags', selectedOptionKeys: [] }); + }); }); it('activates custom renderOption when set', async () => { @@ -164,12 +174,12 @@ describe.skip('multi select filter', () => { }; render(<MultiSelectFilter {...props} />); - userEvent.click(screen.getByRole('button', { name: 'Tags' })); + userEvent.click(await screen.findByRole('button', { name: 'Tags' })); await waitForEuiPopoverOpen(); - expect(screen.getAllByTestId(TEST_ID).length).toBe(2); + expect((await screen.findAllByTestId(TEST_ID)).length).toBe(2); }); - it('should not show the amount of options if hideActiveOptionsNumber is active', () => { + it('should not show the amount of options if hideActiveOptionsNumber is active', async () => { const onChange = jest.fn(); const props = { id: 'tags', @@ -184,7 +194,7 @@ describe.skip('multi select filter', () => { }; const { rerender } = render(<MultiSelectFilter {...props} />); - expect(screen.queryByLabelText('1 active filters')).toBeInTheDocument(); + expect(await screen.findByLabelText('1 active filters')).toBeInTheDocument(); rerender(<MultiSelectFilter {...props} hideActiveOptionsNumber />); expect(screen.queryByLabelText('1 active filters')).not.toBeInTheDocument(); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx index d1bb46c7b8717..22ac90472a6dc 100644 --- a/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.test.tsx @@ -19,8 +19,7 @@ const LABELS = { inProgress: i18n.STATUS_IN_PROGRESS, }; -// FLAKY: https://github.com/elastic/kibana/issues/177334 -describe.skip('StatusFilter', () => { +describe('StatusFilter', () => { const onChange = jest.fn(); const defaultProps = { selectedOptionKeys: [], @@ -37,16 +36,18 @@ describe.skip('StatusFilter', () => { it('should render', async () => { render(<StatusFilter {...defaultProps} />); - expect(await screen.findByTestId('options-filter-popover-button-status')).toBeInTheDocument(); expect(await screen.findByTestId('options-filter-popover-button-status')).not.toBeDisabled(); userEvent.click(await screen.findByRole('button', { name: 'Status' })); + await waitForEuiPopoverOpen(); - expect(await screen.findByRole('option', { name: LABELS.open })).toBeInTheDocument(); - expect(await screen.findByRole('option', { name: LABELS.inProgress })).toBeInTheDocument(); - expect(await screen.findByRole('option', { name: LABELS.closed })).toBeInTheDocument(); - expect((await screen.findAllByRole('option')).length).toBe(3); + const options = await screen.findAllByRole('option'); + + expect(options.length).toBe(3); + expect(options[0]).toHaveTextContent(LABELS.open); + expect(options[1]).toHaveTextContent(LABELS.inProgress); + expect(options[2]).toHaveTextContent(LABELS.closed); }); it('should call onStatusChanged when changing status to open', async () => { @@ -68,10 +69,13 @@ describe.skip('StatusFilter', () => { render(<StatusFilter {...defaultProps} hiddenStatuses={[CaseStatuses.closed]} />); userEvent.click(await screen.findByRole('button', { name: 'Status' })); + await waitForEuiPopoverOpen(); - expect(await screen.findAllByRole('option')).toHaveLength(2); - expect(await screen.findByRole('option', { name: LABELS.open })).toBeInTheDocument(); - expect(await screen.findByRole('option', { name: LABELS.inProgress })).toBeInTheDocument(); + const options = await screen.findAllByRole('option'); + + expect(options.length).toBe(2); + expect(options[0]).toHaveTextContent(LABELS.open); + expect(options[1]).toHaveTextContent(LABELS.inProgress); }); }); diff --git a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx index e88b5e52c0848..4335d24910f39 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_actions.test.tsx @@ -28,7 +28,8 @@ import { jest.mock('../../containers/api'); jest.mock('../../containers/user_profiles/api'); -describe('useActions', () => { +// FLAKY: https://github.com/elastic/kibana/issues/188361 +describe.skip('useActions', () => { let appMockRender: AppMockRenderer; beforeEach(() => { diff --git a/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.test.tsx index 9a869ad0c89b9..614420d5ebc5a 100644 --- a/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/custom_fields.test.tsx @@ -14,10 +14,8 @@ import { createAppMockRenderer } from '../../common/mock'; import { FormTestComponent } from '../../common/test_utils'; import { customFieldsConfigurationMock } from '../../containers/mock'; import { CustomFields } from './custom_fields'; -import * as i18n from './translations'; -// FLAKY: https://github.com/elastic/kibana/issues/188133 -describe.skip('CustomFields', () => { +describe('CustomFields', () => { let appMockRender: AppMockRenderer; const onSubmit = jest.fn(); @@ -40,14 +38,17 @@ describe.skip('CustomFields', () => { </FormTestComponent> ); - expect(await screen.findByText(i18n.ADDITIONAL_FIELDS)).toBeInTheDocument(); expect(await screen.findByTestId('caseCustomFields')).toBeInTheDocument(); - for (const item of customFieldsConfigurationMock) { - expect( - await screen.findByTestId(`${item.key}-${item.type}-create-custom-field`) - ).toBeInTheDocument(); - } + const cf0 = customFieldsConfigurationMock[0]; + const cf1 = customFieldsConfigurationMock[1]; + + expect( + await screen.findByTestId(`${cf0.key}-${cf0.type}-create-custom-field`) + ).toBeInTheDocument(); + expect( + await screen.findByTestId(`${cf1.key}-${cf1.type}-create-custom-field`) + ).toBeInTheDocument(); }); it('should not show the custom fields if the configuration is empty', async () => { @@ -61,7 +62,7 @@ describe.skip('CustomFields', () => { </FormTestComponent> ); - expect(screen.queryByText(i18n.ADDITIONAL_FIELDS)).not.toBeInTheDocument(); + expect(screen.queryByTestId('caseCustomFields')).not.toBeInTheDocument(); expect(screen.queryAllByTestId('create-custom-field', { exact: false }).length).toEqual(0); }); @@ -76,7 +77,7 @@ describe.skip('CustomFields', () => { </FormTestComponent> ); - expect(screen.getAllByTestId('form-optional-field-label')).toHaveLength(2); + expect(await screen.findAllByTestId('form-optional-field-label')).toHaveLength(2); }); it('should not set default value when in edit mode', async () => { diff --git a/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx b/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx index 3caf90433f8fb..aa1ad06067616 100644 --- a/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx +++ b/x-pack/plugins/cases/public/components/case_form_fields/title.test.tsx @@ -7,18 +7,21 @@ import type { FC, PropsWithChildren } from 'react'; import React from 'react'; -import { mount } from 'enzyme'; -import { act } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import type { FormHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import type { CaseFormFieldsSchemaProps } from './schema'; + import { useForm, Form } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import userEvent from '@testing-library/user-event'; + import { Title } from './title'; import { schema } from '../create/schema'; -import type { CaseFormFieldsSchemaProps } from './schema'; +import { createAppMockRenderer, type AppMockRenderer } from '../../common/mock'; -// FLAKY: https://github.com/elastic/kibana/issues/187364 -describe.skip('Title', () => { +describe('Title', () => { let globalForm: FormHook; + let appMockRender: AppMockRenderer; const MockHookWrapperComponent: FC<PropsWithChildren<unknown>> = ({ children }) => { const { form } = useForm<CaseFormFieldsSchemaProps>({ @@ -35,42 +38,37 @@ describe.skip('Title', () => { beforeEach(() => { jest.resetAllMocks(); + appMockRender = createAppMockRenderer(); }); it('it renders', async () => { - const wrapper = mount( + appMockRender.render( <MockHookWrapperComponent> <Title isLoading={false} /> </MockHookWrapperComponent> ); - expect(wrapper.find(`[data-test-subj="caseTitle"]`).exists()).toBeTruthy(); + expect(await screen.findByTestId('caseTitle')).toBeInTheDocument(); }); it('it disables the input when loading', async () => { - const wrapper = mount( + appMockRender.render( <MockHookWrapperComponent> <Title isLoading={true} /> </MockHookWrapperComponent> ); - - expect(wrapper.find(`[data-test-subj="caseTitle"] input`).prop('disabled')).toBeTruthy(); + expect(await screen.findByTestId('input')).toBeDisabled(); }); it('it changes the title', async () => { - const wrapper = mount( + appMockRender.render( <MockHookWrapperComponent> <Title isLoading={false} /> </MockHookWrapperComponent> ); - await act(async () => { - wrapper - .find(`[data-test-subj="caseTitle"] input`) - .first() - .simulate('change', { target: { value: 'My new title' } }); - }); + userEvent.paste(await screen.findByTestId('input'), ' is updated'); - expect(globalForm.getFormData()).toEqual({ title: 'My new title' }); + expect(globalForm.getFormData()).toEqual({ title: 'My title is updated' }); }); }); diff --git a/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx b/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx index 80646c47109ca..3bbb78beaeaba 100644 --- a/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx +++ b/x-pack/plugins/cases/public/components/category/category_form_field.test.tsx @@ -16,8 +16,7 @@ import { categories } from '../../containers/mock'; import { MAX_CATEGORY_LENGTH } from '../../../common/constants'; import { FormTestComponent } from '../../common/test_utils'; -// FLAKY: https://github.com/elastic/kibana/issues/177792 -describe.skip('Category', () => { +describe('Category', () => { let appMockRender: AppMockRenderer; const onSubmit = jest.fn(); @@ -26,14 +25,14 @@ describe.skip('Category', () => { appMockRender = createAppMockRenderer(); }); - it('renders the category field correctly', () => { + it('renders the category field correctly', async () => { appMockRender.render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={false} availableCategories={categories} /> </FormTestComponent> ); - expect(screen.getByTestId('categories-list')).toBeInTheDocument(); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); }); it('can submit without setting a category', async () => { @@ -43,8 +42,8 @@ describe.skip('Category', () => { </FormTestComponent> ); - expect(screen.getByTestId('categories-list')).toBeInTheDocument(); - userEvent.click(screen.getByText('Submit')); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -59,8 +58,8 @@ describe.skip('Category', () => { </FormTestComponent> ); - expect(screen.getByTestId('categories-list')).toBeInTheDocument(); - userEvent.click(screen.getByText('Submit')); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -75,8 +74,8 @@ describe.skip('Category', () => { </FormTestComponent> ); - expect(screen.getByTestId('categories-list')).toBeInTheDocument(); - userEvent.click(screen.getByText('Submit')); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -91,9 +90,9 @@ describe.skip('Category', () => { </FormTestComponent> ); - expect(screen.getByTestId('categories-list')).toBeInTheDocument(); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -112,9 +111,9 @@ describe.skip('Category', () => { </FormTestComponent> ); - expect(screen.getByTestId('categories-list')).toBeInTheDocument(); + expect(await screen.findByTestId('categories-list')).toBeInTheDocument(); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -136,7 +135,7 @@ describe.skip('Category', () => { ); userEvent.type(screen.getByRole('combobox'), `${categories[1]}{enter}`); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -152,7 +151,7 @@ describe.skip('Category', () => { ); userEvent.type(screen.getByRole('combobox'), 'my new category{enter}'); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -168,7 +167,7 @@ describe.skip('Category', () => { ); userEvent.type(screen.getByRole('combobox'), ' {enter}'); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -185,15 +184,15 @@ describe.skip('Category', () => { ); userEvent.type(screen.getByRole('combobox'), ' {enter}'); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid expect(onSubmit).toBeCalledWith({}, false); }); - userEvent.click(screen.getByTestId('comboBoxClearButton')); - userEvent.click(screen.getByText('Submit')); + userEvent.click(await screen.findByTestId('comboBoxClearButton')); + userEvent.click(await screen.findByTestId('form-test-component-submit-button')); await waitFor(() => { // data, isValid @@ -201,13 +200,13 @@ describe.skip('Category', () => { }); }); - it('disables the component correctly when it is loading', () => { + it('disables the component correctly when it is loading', async () => { appMockRender.render( <FormTestComponent onSubmit={onSubmit}> <CategoryFormField isLoading={true} availableCategories={categories} /> </FormTestComponent> ); - expect(screen.getByRole('combobox')).toBeDisabled(); + expect(await screen.findByRole('combobox')).toBeDisabled(); }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx index b5ae9dc9ec57b..f1cb277f1a24b 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_fields_by_issue_type.test.tsx @@ -30,7 +30,7 @@ describe('useGetFieldsByIssueType', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getFieldsByIssueType'); - const { waitForNextUpdate } = renderHook( + const { result, waitFor } = renderHook( () => useGetFieldsByIssueType({ http, @@ -40,7 +40,7 @@ describe('useGetFieldsByIssueType', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); + await waitFor(() => result.current.isSuccess); expect(spy).toHaveBeenCalledWith({ http, @@ -88,7 +88,7 @@ describe('useGetFieldsByIssueType', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetFieldsByIssueType({ http, @@ -98,8 +98,9 @@ describe('useGetFieldsByIssueType', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); it('calls addError when the getFieldsByIssueType api returns successfully but contains an error', async () => { @@ -113,7 +114,7 @@ describe('useGetFieldsByIssueType', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetFieldsByIssueType({ http, @@ -123,7 +124,8 @@ describe('useGetFieldsByIssueType', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx index 5b9aaf18fedae..0d7e3127dd9fe 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/use_get_issue_types.test.tsx @@ -30,7 +30,7 @@ describe('useGetIssueTypes', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getIssueTypes'); - const { waitForNextUpdate } = renderHook( + const { result, waitFor } = renderHook( () => useGetIssueTypes({ http, @@ -39,7 +39,7 @@ describe('useGetIssueTypes', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); + await waitFor(() => result.current.isSuccess); expect(spy).toHaveBeenCalledWith({ http, @@ -70,7 +70,7 @@ describe('useGetIssueTypes', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetIssueTypes({ http, @@ -79,8 +79,9 @@ describe('useGetIssueTypes', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); it('calls addError when the getIssueTypes api returns successfully but contains an error', async () => { @@ -94,7 +95,7 @@ describe('useGetIssueTypes', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetIssueTypes({ http, @@ -103,7 +104,8 @@ describe('useGetIssueTypes', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx index 569483e43e566..7bd0c16a6a4d5 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_incident_types.test.tsx @@ -30,7 +30,7 @@ describe('useGetIncidentTypes', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getIncidentTypes'); - const { waitFor } = renderHook( + const { result, waitFor } = renderHook( () => useGetIncidentTypes({ http, @@ -39,9 +39,7 @@ describe('useGetIncidentTypes', () => { { wrapper: appMockRender.AppWrapper } ); - await waitFor(() => { - expect(spy).toHaveBeenCalled(); - }); + await waitFor(() => result.current.isSuccess); expect(spy).toHaveBeenCalledWith({ http, diff --git a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx index bf2ba2a4ece3c..6f59b4d50c31c 100644 --- a/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/resilient/use_get_severity.test.tsx @@ -19,8 +19,7 @@ jest.mock('./api'); const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; -// FLAKY: https://github.com/elastic/kibana/issues/187456 -describe.skip('useGetSeverity', () => { +describe('useGetSeverity', () => { const { http } = useKibanaMock().services; let appMockRender: AppMockRenderer; @@ -31,7 +30,7 @@ describe.skip('useGetSeverity', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getSeverity'); - const { waitForNextUpdate } = renderHook( + const { result, waitFor } = renderHook( () => useGetSeverity({ http, @@ -40,7 +39,7 @@ describe.skip('useGetSeverity', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); + await waitFor(() => result.current.isSuccess); expect(spy).toHaveBeenCalledWith({ http, @@ -71,7 +70,7 @@ describe.skip('useGetSeverity', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetSeverity({ http, @@ -80,8 +79,9 @@ describe.skip('useGetSeverity', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); it('calls addError when the getSeverity api returns successfully but contains an error', async () => { @@ -95,7 +95,7 @@ describe.skip('useGetSeverity', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetSeverity({ http, @@ -104,7 +104,8 @@ describe.skip('useGetSeverity', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx index 0368783f602c5..1508817619501 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/use_get_choices.test.tsx @@ -47,7 +47,7 @@ describe('useGetChoices', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'getChoices'); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetChoices({ http, @@ -57,7 +57,9 @@ describe('useGetChoices', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); + await waitFor(() => { + expect(spy).toHaveBeenCalled(); + }); expect(spy).toHaveBeenCalledWith({ http, @@ -90,7 +92,7 @@ describe('useGetChoices', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetChoices({ http, @@ -100,8 +102,9 @@ describe('useGetChoices', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); it('calls addError when the getChoices api returns successfully but contains an error', async () => { @@ -115,7 +118,7 @@ describe('useGetChoices', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addSuccess: jest.fn(), addError }); - const { waitForNextUpdate } = renderHook( + const { waitFor } = renderHook( () => useGetChoices({ http, @@ -125,7 +128,8 @@ describe('useGetChoices', () => { { wrapper: appMockRender.AppWrapper } ); - await waitForNextUpdate(); - expect(addError).toHaveBeenCalled(); + await waitFor(() => { + expect(addError).toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/cases/public/components/create/template.test.tsx b/x-pack/plugins/cases/public/components/create/template.test.tsx index 837b87827e591..d3b1c59b71254 100644 --- a/x-pack/plugins/cases/public/components/create/template.test.tsx +++ b/x-pack/plugins/cases/public/components/create/template.test.tsx @@ -13,8 +13,7 @@ import { createAppMockRenderer } from '../../common/mock'; import { templatesConfigurationMock } from '../../containers/mock'; import { TemplateSelector } from './templates'; -// FLAKY: https://github.com/elastic/kibana/issues/178457 -describe.skip('CustomFields', () => { +describe('CustomFields', () => { let appMockRender: AppMockRenderer; const onTemplateChange = jest.fn(); diff --git a/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx index 125a5ada01e5f..89fdca73fefbf 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/form.test.tsx @@ -18,8 +18,7 @@ import userEvent from '@testing-library/user-event'; import { customFieldsConfigurationMock } from '../../containers/mock'; import type { FormState } from '../configure_cases/flyout'; -// FLAKY: https://github.com/elastic/kibana/issues/187554 -describe.skip('CustomFieldsForm ', () => { +describe('CustomFieldsForm ', () => { let appMockRender: AppMockRenderer; const onChange = jest.fn(); diff --git a/x-pack/plugins/cases/public/components/custom_fields/index.test.tsx b/x-pack/plugins/cases/public/components/custom_fields/index.test.tsx index 4da76d846dd9d..2a09c0b207be1 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/index.test.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/index.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; @@ -17,8 +17,7 @@ import { MAX_CUSTOM_FIELDS_PER_CASE } from '../../../common/constants'; import { CustomFields } from '.'; import * as i18n from './translations'; -// FLAKY: https://github.com/elastic/kibana/issues/176805 -describe.skip('CustomFields', () => { +describe('CustomFields', () => { let appMockRender: AppMockRenderer; const props = { @@ -68,7 +67,9 @@ describe.skip('CustomFields', () => { userEvent.click(await screen.findByTestId('add-custom-field')); - expect(props.handleAddCustomField).toBeCalled(); + await waitFor(() => { + expect(props.handleAddCustomField).toBeCalled(); + }); }); it('calls handleEditCustomField on edit option click', async () => { @@ -80,13 +81,9 @@ describe.skip('CustomFields', () => { await screen.findByTestId(`${customFieldsConfigurationMock[0].key}-custom-field-edit`) ); - expect(props.handleEditCustomField).toBeCalledWith(customFieldsConfigurationMock[0].key); - }); - - it('shows the experimental badge', async () => { - appMockRender.render(<CustomFields {...props} />); - - expect(await screen.findByTestId('case-experimental-badge')).toBeInTheDocument(); + await waitFor(() => { + expect(props.handleEditCustomField).toBeCalledWith(customFieldsConfigurationMock[0].key); + }); }); it('shows error when custom fields reaches the limit', async () => { diff --git a/x-pack/plugins/cases/public/components/description/index.test.tsx b/x-pack/plugins/cases/public/components/description/index.test.tsx index 108a33876312e..05f2477eda715 100644 --- a/x-pack/plugins/cases/public/components/description/index.test.tsx +++ b/x-pack/plugins/cases/public/components/description/index.test.tsx @@ -27,7 +27,8 @@ const defaultProps = { isLoadingDescription: false, }; -describe('Description', () => { +// Failing: See https://github.com/elastic/kibana/issues/185879 +describe.skip('Description', () => { const onUpdateField = jest.fn(); let appMockRender: AppMockRenderer; diff --git a/x-pack/plugins/cases/public/components/edit_connector/push_button.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/push_button.test.tsx index fee6fdc8d1557..54fae78d22813 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/push_button.test.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/push_button.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { AppMockRenderer } from '../../common/mock'; @@ -36,39 +36,42 @@ describe('PushButton ', () => { it('renders the button without tooltip', async () => { appMockRender.render(<PushButton {...defaultProps} />); - expect(screen.getByTestId('push-to-external-service')).toBeInTheDocument(); + expect(await screen.findByTestId('push-to-external-service')).toBeInTheDocument(); expect(screen.queryByTestId('push-button-tooltip')).not.toBeInTheDocument(); }); it('renders the correct label when the connector has not been pushed', async () => { appMockRender.render(<PushButton {...defaultProps} />); - expect(screen.getByText('Push as My SN connector incident')).toBeInTheDocument(); + expect(await screen.findByText('Push as My SN connector incident')).toBeInTheDocument(); }); it('renders the correct label when the connector has been pushed', async () => { appMockRender.render(<PushButton {...defaultProps} hasBeenPushed={true} />); - expect(screen.getByText('Update My SN connector incident')).toBeInTheDocument(); + expect(await screen.findByText('Update My SN connector incident')).toBeInTheDocument(); }); it('pushed correctly', async () => { appMockRender.render(<PushButton {...defaultProps} />); - userEvent.click(screen.getByTestId('push-to-external-service')); - expect(pushToService).toHaveBeenCalled(); + userEvent.click(await screen.findByTestId('push-to-external-service')); + + await waitFor(() => { + expect(pushToService).toHaveBeenCalled(); + }); }); it('disables the button', async () => { appMockRender.render(<PushButton {...defaultProps} disabled={true} />); - expect(screen.getByTestId('push-to-external-service')).toBeDisabled(); + expect(await screen.findByTestId('push-to-external-service')).toBeDisabled(); }); it('shows the tooltip context correctly', async () => { appMockRender.render(<PushButton {...defaultProps} showTooltip={true} />); - userEvent.hover(screen.getByTestId('push-to-external-service')); + userEvent.hover(await screen.findByTestId('push-to-external-service')); expect(await screen.findByText('My SN connector incident is up to date')).toBeInTheDocument(); expect(await screen.findByText('No update is required')).toBeInTheDocument(); @@ -83,7 +86,7 @@ describe('PushButton ', () => { /> ); - userEvent.hover(screen.getByTestId('push-to-external-service')); + userEvent.hover(await screen.findByTestId('push-to-external-service')); expect(await screen.findByText('My title')).toBeInTheDocument(); expect(await screen.findByText('My desc')).toBeInTheDocument(); diff --git a/x-pack/plugins/cases/public/components/files/add_file.tsx b/x-pack/plugins/cases/public/components/files/add_file.tsx index ad20db1d51f22..449e9bf269923 100644 --- a/x-pack/plugins/cases/public/components/files/add_file.tsx +++ b/x-pack/plugins/cases/public/components/files/add_file.tsx @@ -135,6 +135,7 @@ const AddFileComponent: React.FC<AddFileProps> = ({ caseId }) => { </EuiFlexItem> ) : null; }; + AddFileComponent.displayName = 'AddFile'; export const AddFile = React.memo(AddFileComponent); diff --git a/x-pack/plugins/cases/public/components/files/file_actions_popover_button.test.tsx b/x-pack/plugins/cases/public/components/files/file_actions_popover_button.test.tsx index dc2e9681fc19e..d30b70b077269 100644 --- a/x-pack/plugins/cases/public/components/files/file_actions_popover_button.test.tsx +++ b/x-pack/plugins/cases/public/components/files/file_actions_popover_button.test.tsx @@ -76,6 +76,36 @@ describe('FileActionsPopoverButton', () => { expect(await screen.queryByTestId('cases-files-copy-hash-button')).not.toBeInTheDocument(); }); + it('only renders menu items for the enabled hashes', async () => { + appMockRender.render( + <FileActionsPopoverButton + caseId={basicCaseId} + theFile={{ ...basicFileMock, hash: { sha1: 'sha1' } }} + /> + ); + + const popoverButton = await screen.findByTestId( + `cases-files-actions-popover-button-${basicFileMock.id}` + ); + + expect(popoverButton).toBeInTheDocument(); + userEvent.click(popoverButton); + + expect( + await screen.findByTestId(`cases-files-popover-${basicFileMock.id}`) + ).toBeInTheDocument(); + + const copyFileHashButton = await screen.findByTestId('cases-files-copy-hash-button'); + + expect(copyFileHashButton).toBeInTheDocument(); + + userEvent.click(copyFileHashButton); + + expect(await screen.findByTestId(`cases-files-copy-sha1-hash-button`)).toBeInTheDocument(); + expect(screen.queryByTestId('cases-files-copy-md5-hash-button')).not.toBeInTheDocument(); + expect(screen.queryByTestId('cases-files-copy-sha256-hash-button')).not.toBeInTheDocument(); + }); + it('clicking the copy file hash button rerenders the popover correctly', async () => { appMockRender.render(<FileActionsPopoverButton caseId={basicCaseId} theFile={basicFileMock} />); diff --git a/x-pack/plugins/cases/public/components/files/file_actions_popover_button.tsx b/x-pack/plugins/cases/public/components/files/file_actions_popover_button.tsx index 08729fef5c834..691174717af00 100644 --- a/x-pack/plugins/cases/public/components/files/file_actions_popover_button.tsx +++ b/x-pack/plugins/cases/public/components/files/file_actions_popover_button.tsx @@ -65,49 +65,52 @@ export const FileActionsPopoverButton: React.FC<{ caseId: string; theFile: FileJ id: 1, title: i18n.COPY_FILE_HASH, items: [ - { - name: 'MD5', - icon: 'copyClipboard', - disabled: !theFile.hash?.md5, - onClick: () => { - if (theFile.hash?.md5) { - navigator.clipboard.writeText(theFile.hash.md5).then(() => { - closePopover(); - showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('md5')); - }); + theFile.hash?.md5 + ? { + name: 'MD5', + icon: 'copyClipboard', + onClick: () => { + if (theFile.hash?.md5) { + navigator.clipboard.writeText(theFile.hash.md5).then(() => { + closePopover(); + showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('md5')); + }); + } + }, + 'data-test-subj': 'cases-files-copy-md5-hash-button', } - }, - 'data-test-subj': 'cases-files-copy-md5-hash-button', - }, - { - name: 'SHA1', - icon: 'copyClipboard', - disabled: !theFile.hash?.sha1, - onClick: () => { - if (theFile.hash?.sha1) { - navigator.clipboard.writeText(theFile.hash.sha1).then(() => { - closePopover(); - showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('sha1')); - }); + : null, + theFile.hash?.sha1 + ? { + name: 'SHA1', + icon: 'copyClipboard', + onClick: () => { + if (theFile.hash?.sha1) { + navigator.clipboard.writeText(theFile.hash.sha1).then(() => { + closePopover(); + showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('sha1')); + }); + } + }, + 'data-test-subj': 'cases-files-copy-sha1-hash-button', } - }, - 'data-test-subj': 'cases-files-copy-sha1-hash-button', - }, - { - name: 'SHA256', - icon: 'copyClipboard', - disabled: !theFile.hash?.sha256, - onClick: () => { - if (theFile.hash?.sha256) { - navigator.clipboard.writeText(theFile.hash.sha256).then(() => { - closePopover(); - showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('sha256')); - }); + : null, + theFile.hash?.sha256 + ? { + name: 'SHA256', + icon: 'copyClipboard', + onClick: () => { + if (theFile.hash?.sha256) { + navigator.clipboard.writeText(theFile.hash.sha256).then(() => { + closePopover(); + showSuccessToast(i18n.COPY_FILE_HASH_SUCCESS('sha256')); + }); + } + }, + 'data-test-subj': 'cases-files-copy-sha256-hash-button', } - }, - 'data-test-subj': 'cases-files-copy-sha256-hash-button', - }, - ], + : null, + ].filter(Boolean) as EuiContextMenuPanelDescriptor['items'], }, ]; diff --git a/x-pack/plugins/cases/public/components/files/files_utility_bar.test.tsx b/x-pack/plugins/cases/public/components/files/files_utility_bar.test.tsx index 42e4c4c69fff4..3dd712d739055 100644 --- a/x-pack/plugins/cases/public/components/files/files_utility_bar.test.tsx +++ b/x-pack/plugins/cases/public/components/files/files_utility_bar.test.tsx @@ -10,16 +10,27 @@ import { screen } from '@testing-library/react'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; +import { useCreateAttachments } from '../../containers/use_create_attachments'; import userEvent from '@testing-library/user-event'; import { FilesUtilityBar } from './files_utility_bar'; +jest.mock('../../containers/api'); +jest.mock('../../containers/use_create_attachments'); +jest.mock('../../common/lib/kibana'); + +const useCreateAttachmentsMock = useCreateAttachments as jest.Mock; + +useCreateAttachmentsMock.mockReturnValue({ + isLoading: false, + mutateAsync: jest.fn(), +}); + const defaultProps = { caseId: 'foobar', onSearch: jest.fn(), }; -// FLAKY: https://github.com/elastic/kibana/issues/174571 -describe.skip('FilesUtilityBar', () => { +describe('FilesUtilityBar', () => { let appMockRender: AppMockRenderer; beforeEach(() => { @@ -30,14 +41,14 @@ describe.skip('FilesUtilityBar', () => { it('renders correctly', async () => { appMockRender.render(<FilesUtilityBar {...defaultProps} />); - expect(await screen.findByTestId('cases-files-add')).toBeInTheDocument(); expect(await screen.findByTestId('cases-files-search')).toBeInTheDocument(); + expect(await screen.findByTestId('cases-files-add')).toBeInTheDocument(); }); it('search text passed correctly to callback', async () => { appMockRender.render(<FilesUtilityBar {...defaultProps} />); - await userEvent.type(screen.getByTestId('cases-files-search'), 'My search{enter}'); + userEvent.type(await screen.findByTestId('cases-files-search'), 'My search{enter}'); expect(defaultProps.onSearch).toBeCalledWith('My search'); }); }); diff --git a/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.test.tsx b/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.test.tsx index e43eb7a253026..02cfa92f9e2a0 100644 --- a/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.test.tsx +++ b/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.test.tsx @@ -9,18 +9,25 @@ import React from 'react'; import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { showEuiComboBoxOptions } from '@elastic/eui/lib/test/rtl'; +import { useAlertsDataView } from '@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view'; import { useApplication } from '../../../common/lib/kibana/use_application'; -import { useAlertDataViews } from '../hooks/use_alert_data_view'; import { CasesParamsFields } from './cases_params'; -import { showEuiComboBoxOptions } from '@elastic/eui/lib/test/rtl'; +import { useKibana } from '../../../common/lib/kibana/kibana_react'; +import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; -jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); +jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view'); jest.mock('../../../common/lib/kibana/use_application'); -jest.mock('../hooks/use_alert_data_view'); +jest.mock('../../../common/lib/kibana/kibana_react'); -const useAlertDataViewsMock = useAlertDataViews as jest.Mock; +const useKibanaMock = jest.mocked(useKibana); +const useAlertsDataViewMock = jest.mocked(useAlertsDataView); const useApplicationMock = useApplication as jest.Mock; +useKibanaMock.mockReturnValue({ + services: { ...createStartServicesMock(), data: { dataViews: {} } }, +} as unknown as ReturnType<typeof useKibana>); + const actionParams = { subAction: 'run', subActionParams: { @@ -52,24 +59,25 @@ describe('CasesParamsFields renders', () => { beforeEach(() => { jest.clearAllMocks(); useApplicationMock.mockReturnValueOnce({ appId: 'management' }); - useAlertDataViewsMock.mockReturnValue({ - loading: false, - dataViews: [ - { - title: '.alerts-test', - fields: [ - { - name: 'host.ip', - type: 'ip', - aggregatable: true, - }, - { - name: 'host.geo.location', - type: 'geo_point', - }, - ], - }, - ], + useAlertsDataViewMock.mockReturnValue({ + isLoading: false, + dataView: { + title: '.alerts-test', + fields: [ + { + name: 'host.ip', + type: 'ip', + aggregatable: true, + searchable: true, + }, + { + name: 'host.geo.location', + type: 'geo_point', + aggregatable: false, + searchable: true, + }, + ], + }, }); }); @@ -83,14 +91,14 @@ describe('CasesParamsFields renders', () => { }); it('renders loading state of grouping by fields correctly', async () => { - useAlertDataViewsMock.mockReturnValue({ loading: true }); + useAlertsDataViewMock.mockReturnValue({ isLoading: true }); render(<CasesParamsFields {...defaultProps} />); expect(await screen.findByRole('progressbar')).toBeInTheDocument(); }); it('disables dropdown when loading grouping by fields', async () => { - useAlertDataViewsMock.mockReturnValue({ loading: true }); + useAlertsDataViewMock.mockReturnValue({ isLoading: true }); render(<CasesParamsFields {...defaultProps} />); expect(await screen.findByRole('progressbar')).toBeInTheDocument(); @@ -161,29 +169,31 @@ describe('CasesParamsFields renders', () => { }); it('updates grouping by field by search', async () => { - useAlertDataViewsMock.mockReturnValue({ - loading: false, - dataViews: [ - { - title: '.alerts-test', - fields: [ - { - name: 'host.ip', - type: 'ip', - aggregatable: true, - }, - { - name: 'host.geo.location', - type: 'geo_point', - }, - { - name: 'alert.name', - type: 'string', - aggregatable: true, - }, - ], - }, - ], + useAlertsDataViewMock.mockReturnValue({ + isLoading: false, + dataView: { + title: '.alerts-test', + fields: [ + { + name: 'host.ip', + type: 'ip', + aggregatable: true, + searchable: true, + }, + { + name: 'host.geo.location', + type: 'geo_point', + aggregatable: false, + searchable: true, + }, + { + name: 'alert.name', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, }); render(<CasesParamsFields {...defaultProps} />); diff --git a/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.tsx b/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.tsx index 71e9380246278..4b667d7880758 100644 --- a/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.tsx +++ b/x-pack/plugins/cases/public/components/system_actions/cases/cases_params.tsx @@ -8,6 +8,7 @@ import React, { memo, useCallback, useEffect, useMemo } from 'react'; import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public/types'; +import type { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiCheckbox, @@ -19,20 +20,30 @@ import { EuiSpacer, EuiComboBox, } from '@elastic/eui'; -import type { ValidFeatureId } from '@kbn/rule-data-utils'; -import { CASES_CONNECTOR_SUB_ACTION } from '../../../../common/constants'; +import { useAlertsDataView } from '@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view'; import * as i18n from './translations'; import type { CasesActionParams } from './types'; +import { CASES_CONNECTOR_SUB_ACTION } from '../../../../common/constants'; import { DEFAULT_TIME_WINDOW, TIME_UNITS } from './constants'; import { getTimeUnitOptions } from './utils'; -import { useAlertDataViews } from '../hooks/use_alert_data_view'; +import { useKibana } from '../../../common/lib/kibana'; export const CasesParamsFieldsComponent: React.FunctionComponent< ActionParamsProps<CasesActionParams> > = ({ actionParams, editAction, errors, index, producerId }) => { - const { dataViews, loading: loadingAlertDataViews } = useAlertDataViews( - producerId ? [producerId as ValidFeatureId] : [] - ); + const { + http, + notifications: { toasts }, + data: { dataViews: dataViewsService }, + } = useKibana().services; + const { dataView, isLoading: loadingAlertDataViews } = useAlertsDataView({ + http, + toasts, + dataViewsService, + featureIds: producerId + ? [producerId as Exclude<ValidFeatureId, typeof AlertConsumers.SIEM>] + : [], + }); const { timeWindow, reopenClosedCases, groupingBy } = useMemo( () => @@ -108,21 +119,17 @@ export const CasesParamsFieldsComponent: React.FunctionComponent< ); const options: Array<EuiComboBoxOptionOption<string>> = useMemo(() => { - if (!dataViews?.length) { + if (!dataView) { return []; } - return dataViews - .map((dataView) => { - return dataView.fields - .filter((field) => Boolean(field.aggregatable)) - .map((field) => ({ - value: field.name, - label: field.name, - })); - }) - .flat(); - }, [dataViews]); + return dataView.fields + .filter((field) => Boolean(field.aggregatable)) + .map((field) => ({ + value: field.name, + label: field.name, + })); + }, [dataView]); const selectedOptions = groupingBy.map((field) => ({ value: field, label: field })); diff --git a/x-pack/plugins/cases/public/components/system_actions/hooks/alert_fields.ts b/x-pack/plugins/cases/public/components/system_actions/hooks/alert_fields.ts deleted file mode 100644 index 8a73674a5e264..0000000000000 --- a/x-pack/plugins/cases/public/components/system_actions/hooks/alert_fields.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ValidFeatureId } from '@kbn/rule-data-utils'; -import type { HttpSetup } from '@kbn/core/public'; -import type { FieldSpec } from '@kbn/data-views-plugin/common'; -import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; - -export async function fetchAlertFields({ - http, - featureIds, -}: { - http: HttpSetup; - featureIds: ValidFeatureId[]; -}): Promise<FieldSpec[]> { - const { fields: alertFields = [] } = await http.get<{ fields: FieldSpec[] }>( - `${BASE_RAC_ALERTS_API_PATH}/browser_fields`, - { - query: { featureIds }, - } - ); - return alertFields; -} diff --git a/x-pack/plugins/cases/public/components/system_actions/hooks/alert_index.ts b/x-pack/plugins/cases/public/components/system_actions/hooks/alert_index.ts deleted file mode 100644 index 4b5a496f226bb..0000000000000 --- a/x-pack/plugins/cases/public/components/system_actions/hooks/alert_index.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 { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common'; -import type { HttpSetup } from '@kbn/core/public'; - -export async function fetchAlertIndexNames({ - http, - features, -}: { - http: HttpSetup; - features: string; -}): Promise<string[]> { - const { index_name: indexNamesStr = [] } = await http.get<{ index_name: string[] }>( - `${BASE_RAC_ALERTS_API_PATH}/index`, - { - query: { features }, - } - ); - return indexNamesStr; -} diff --git a/x-pack/plugins/cases/public/components/system_actions/hooks/use_alert_data_view.test.tsx b/x-pack/plugins/cases/public/components/system_actions/hooks/use_alert_data_view.test.tsx deleted file mode 100644 index 8dbb501e4cf15..0000000000000 --- a/x-pack/plugins/cases/public/components/system_actions/hooks/use_alert_data_view.test.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { waitFor } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { AlertConsumers } from '@kbn/rule-data-utils'; -import type { ValidFeatureId } from '@kbn/rule-data-utils'; -import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; -import { useAlertDataViews } from './use_alert_data_view'; - -const mockUseKibanaReturnValue = createStartServicesMock(); - -jest.mock('@kbn/kibana-react-plugin/public', () => ({ - __esModule: true, - useKibana: jest.fn(() => ({ - services: mockUseKibanaReturnValue, - })), -})); - -jest.mock('./alert_index', () => ({ - fetchAlertIndexNames: jest.fn(), -})); - -const { fetchAlertIndexNames } = jest.requireMock('./alert_index'); - -jest.mock('./alert_fields', () => ({ - fetchAlertFields: jest.fn(), -})); -const { fetchAlertFields } = jest.requireMock('./alert_fields'); - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - cacheTime: 0, - }, - }, -}); -const wrapper = ({ children }: { children: Node }) => ( - <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> -); - -describe('useAlertDataView', () => { - const observabilityAlertFeatureIds: ValidFeatureId[] = [ - AlertConsumers.APM, - AlertConsumers.INFRASTRUCTURE, - AlertConsumers.LOGS, - AlertConsumers.UPTIME, - ]; - - beforeEach(() => { - fetchAlertIndexNames.mockResolvedValue([ - '.alerts-observability.uptime.alerts-*', - '.alerts-observability.metrics.alerts-*', - '.alerts-observability.logs.alerts-*', - '.alerts-observability.apm.alerts-*', - ]); - fetchAlertFields.mockResolvedValue([{ data: ' fields' }]); - }); - - afterEach(() => { - queryClient.clear(); - jest.clearAllMocks(); - }); - - it('initially is loading and does not have data', async () => { - const mockedAsyncDataView = { - loading: true, - dataview: undefined, - }; - - const { result } = renderHook(() => useAlertDataViews(observabilityAlertFeatureIds), { - wrapper, - }); - - await waitFor(() => expect(result.current).toEqual(mockedAsyncDataView)); - }); - - it('fetch index names + fields for the provided o11y featureIds', async () => { - renderHook(() => useAlertDataViews(observabilityAlertFeatureIds), { - wrapper, - }); - - await waitFor(() => expect(fetchAlertIndexNames).toHaveBeenCalledTimes(1)); - expect(fetchAlertFields).toHaveBeenCalledTimes(1); - }); - - it('only fetch index names for security featureId', async () => { - renderHook(() => useAlertDataViews([AlertConsumers.SIEM]), { - wrapper, - }); - - await waitFor(() => expect(fetchAlertIndexNames).toHaveBeenCalledTimes(1)); - expect(fetchAlertFields).toHaveBeenCalledTimes(0); - }); - - it('Do not fetch anything if security and o11y featureIds are mixed together', async () => { - const { result } = renderHook( - () => useAlertDataViews([AlertConsumers.SIEM, AlertConsumers.LOGS]), - { - wrapper, - } - ); - - await waitFor(() => - expect(result.current).toEqual({ - loading: false, - dataview: undefined, - }) - ); - expect(fetchAlertIndexNames).toHaveBeenCalledTimes(0); - expect(fetchAlertFields).toHaveBeenCalledTimes(0); - }); - - it('if fetch throws error return no data', async () => { - fetchAlertIndexNames.mockRejectedValue('error'); - - const { result } = renderHook(() => useAlertDataViews(observabilityAlertFeatureIds), { - wrapper, - }); - - await waitFor(() => - expect(result.current).toEqual({ - loading: false, - dataview: undefined, - }) - ); - }); -}); diff --git a/x-pack/plugins/cases/public/components/system_actions/hooks/use_alert_data_view.ts b/x-pack/plugins/cases/public/components/system_actions/hooks/use_alert_data_view.ts deleted file mode 100644 index 863b07949af9d..0000000000000 --- a/x-pack/plugins/cases/public/components/system_actions/hooks/use_alert_data_view.ts +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import type { DataView } from '@kbn/data-views-plugin/common'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { ValidFeatureId } from '@kbn/rule-data-utils'; -import { AlertConsumers } from '@kbn/rule-data-utils'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import type { TriggersAndActionsUiServices } from '@kbn/triggers-actions-ui-plugin/public'; -import { fetchAlertIndexNames } from './alert_index'; -import { fetchAlertFields } from './alert_fields'; - -export interface UserAlertDataViews { - dataViews?: DataView[]; - loading: boolean; -} - -export function useAlertDataViews(featureIds: ValidFeatureId[]): UserAlertDataViews { - const { - http, - data: dataService, - notifications: { toasts }, - } = useKibana<TriggersAndActionsUiServices>().services; - const [dataViews, setDataViews] = useState<DataView[] | undefined>(undefined); - const features = featureIds.sort().join(','); - const isOnlySecurity = featureIds.length === 1 && featureIds.includes(AlertConsumers.SIEM); - - const hasSecurityAndO11yFeatureIds = - featureIds.length > 1 && featureIds.includes(AlertConsumers.SIEM); - - const hasNoSecuritySolution = - featureIds.length > 0 && !isOnlySecurity && !hasSecurityAndO11yFeatureIds; - - const queryIndexNameFn = () => { - return fetchAlertIndexNames({ http, features }); - }; - - const queryAlertFieldsFn = () => { - return fetchAlertFields({ http, featureIds }); - }; - - const onErrorFn = () => { - toasts.addDanger( - i18n.translate('xpack.cases.systemActions.useAlertDataView.useAlertDataMessage', { - defaultMessage: 'Unable to load alert data view', - }) - ); - }; - - const { - data: indexNames, - isSuccess: isIndexNameSuccess, - isInitialLoading: isIndexNameInitialLoading, - isLoading: isIndexNameLoading, - } = useQuery({ - queryKey: ['loadAlertIndexNames', features], - queryFn: queryIndexNameFn, - onError: onErrorFn, - refetchOnWindowFocus: false, - enabled: featureIds.length > 0 && !hasSecurityAndO11yFeatureIds, - }); - - const { - data: alertFields, - isSuccess: isAlertFieldsSuccess, - isInitialLoading: isAlertFieldsInitialLoading, - isLoading: isAlertFieldsLoading, - } = useQuery({ - queryKey: ['loadAlertFields', features], - queryFn: queryAlertFieldsFn, - onError: onErrorFn, - refetchOnWindowFocus: false, - enabled: hasNoSecuritySolution, - }); - - useEffect(() => { - return () => { - dataViews?.map((dv) => dataService.dataViews.clearInstanceCache(dv.id)); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataViews]); - - // FUTURE ENGINEER this useEffect is for security solution user since - // we are using the user privilege to access the security alert index - useEffect(() => { - async function createDataView() { - const localDataview = await dataService?.dataViews.create({ - title: (indexNames ?? []).join(','), - allowNoIndex: true, - }); - setDataViews([localDataview]); - } - - if (isOnlySecurity && isIndexNameSuccess) { - createDataView(); - } - }, [dataService?.dataViews, indexNames, isIndexNameSuccess, isOnlySecurity]); - - // FUTURE ENGINEER this useEffect is for o11y and stack solution user since - // we are using the kibana user privilege to access the alert index - useEffect(() => { - if ( - indexNames && - alertFields && - !isOnlySecurity && - isAlertFieldsSuccess && - isIndexNameSuccess - ) { - setDataViews([ - { - title: (indexNames ?? []).join(','), - fieldFormatMap: {}, - fields: (alertFields ?? [])?.map((field) => { - return { - ...field, - ...(field.esTypes && field.esTypes.includes('flattened') ? { type: 'string' } : {}), - }; - }), - }, - ] as unknown as DataView[]); - } - }, [ - alertFields, - dataService?.dataViews, - indexNames, - isIndexNameSuccess, - isOnlySecurity, - isAlertFieldsSuccess, - ]); - - return useMemo( - () => ({ - dataViews, - loading: - featureIds.length === 0 || hasSecurityAndO11yFeatureIds - ? false - : isOnlySecurity - ? isIndexNameInitialLoading || isIndexNameLoading - : isIndexNameInitialLoading || - isIndexNameLoading || - isAlertFieldsInitialLoading || - isAlertFieldsLoading, - }), - [ - dataViews, - featureIds.length, - hasSecurityAndO11yFeatureIds, - isOnlySecurity, - isIndexNameInitialLoading, - isIndexNameLoading, - isAlertFieldsInitialLoading, - isAlertFieldsLoading, - ] - ); -} diff --git a/x-pack/plugins/cases/public/containers/use_get_case.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx index 6f7540eafa80e..1187c7de07d28 100644 --- a/x-pack/plugins/cases/public/containers/use_get_case.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_get_case.test.tsx @@ -28,7 +28,8 @@ const wrapper: FC<PropsWithChildren<unknown>> = ({ children }) => { return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>; }; -describe('Use get case hook', () => { +// Failing: See https://github.com/elastic/kibana/issues/189634 +describe.skip('Use get case hook', () => { it('calls the api when invoked with the correct parameters', async () => { const spy = jest.spyOn(api, 'resolveCase'); const { waitForNextUpdate } = renderHook(() => useGetCase('case-1'), { wrapper }); diff --git a/x-pack/plugins/cases/server/files/index.test.ts b/x-pack/plugins/cases/server/files/index.test.ts index 177392660225c..8f9fb0b35e96c 100644 --- a/x-pack/plugins/cases/server/files/index.test.ts +++ b/x-pack/plugins/cases/server/files/index.test.ts @@ -109,6 +109,15 @@ describe('server files', () => { }); }); + describe('hashing algorithms', () => { + it('excludes md5 when fips is enabled', () => { + const schema = ConfigSchema.validate({}); + registerCaseFileKinds(schema.files, mockFilesSetup, true); + + expect(mockFilesSetup.registerFileKind.mock.calls[0][0].hashes).not.toContain('md5'); + }); + }); + describe('allowed mime types', () => { describe('image png', () => { const schema = ConfigSchema.validate({ files: { allowedMimeTypes: ['image/png'] } }); diff --git a/x-pack/plugins/cases/server/files/index.ts b/x-pack/plugins/cases/server/files/index.ts index b1e7f6cf9c0f0..fb2e5e6372c3b 100644 --- a/x-pack/plugins/cases/server/files/index.ts +++ b/x-pack/plugins/cases/server/files/index.ts @@ -20,13 +20,17 @@ import { IMAGE_MIME_TYPES } from '../../common/constants/mime_types'; import type { FilesConfig } from './types'; import { constructFileKindIdByOwner, constructFilesHttpOperationTag } from '../../common/files'; -const buildFileKind = (config: FilesConfig, owner: Owner): FileKind => { +const buildFileKind = (config: FilesConfig, owner: Owner, isFipsMode = false): FileKind => { + const hashes: FileKind['hashes'] = ['sha1', 'sha256']; + if (!isFipsMode) { + hashes.unshift('md5'); + } return { id: constructFileKindIdByOwner(owner), http: fileKindHttpTags(owner), maxSizeBytes: createMaxCallback(config), allowedMimeTypes: config.allowedMimeTypes, - hashes: ['md5', 'sha1', 'sha256'], + hashes, }; }; @@ -73,16 +77,20 @@ export const createMaxCallback = /** * The file kind definition for interacting with the file service for the backend */ -const createFileKinds = (config: FilesConfig): Record<Owner, FileKind> => { +const createFileKinds = (config: FilesConfig, isFipsMode = false): Record<Owner, FileKind> => { return { - [APP_ID]: buildFileKind(config, APP_ID), - [OBSERVABILITY_OWNER]: buildFileKind(config, OBSERVABILITY_OWNER), - [SECURITY_SOLUTION_OWNER]: buildFileKind(config, SECURITY_SOLUTION_OWNER), + [APP_ID]: buildFileKind(config, APP_ID, isFipsMode), + [OBSERVABILITY_OWNER]: buildFileKind(config, OBSERVABILITY_OWNER, isFipsMode), + [SECURITY_SOLUTION_OWNER]: buildFileKind(config, SECURITY_SOLUTION_OWNER, isFipsMode), }; }; -export const registerCaseFileKinds = (config: FilesConfig, filesSetupPlugin: FilesSetup) => { - const fileKinds = createFileKinds(config); +export const registerCaseFileKinds = ( + config: FilesConfig, + filesSetupPlugin: FilesSetup, + isFipsMode = false +) => { + const fileKinds = createFileKinds(config, isFipsMode); for (const fileKind of Object.values(fileKinds)) { filesSetupPlugin.registerFileKind(fileKind); diff --git a/x-pack/plugins/cases/server/plugin.ts b/x-pack/plugins/cases/server/plugin.ts index 48ed1722149ed..262289bc6a24c 100644 --- a/x-pack/plugins/cases/server/plugin.ts +++ b/x-pack/plugins/cases/server/plugin.ts @@ -86,7 +86,7 @@ export class CasePlugin this.persistableStateAttachmentTypeRegistry ); - registerCaseFileKinds(this.caseConfig.files, plugins.files); + registerCaseFileKinds(this.caseConfig.files, plugins.files, core.security.fips.isEnabled()); this.securityPluginSetup = plugins.security; this.lensEmbeddableFactory = plugins.lens.lensEmbeddableFactory; diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index 6bbc5cfd4e421..fbe05134b5bfd 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -73,9 +73,9 @@ "@kbn/datemath", "@kbn/core-logging-server-mocks", "@kbn/core-logging-browser-mocks", - "@kbn/data-views-plugin", "@kbn/core-http-router-server-internal", "@kbn/presentation-publishing", + "@kbn/alerts-ui-shared", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts index e4c5a88a847c4..4e2b7cbe4825d 100644 --- a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts +++ b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AnalyticsClient } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; import { of } from 'rxjs'; import { parseDeploymentIdFromDeploymentUrl } from './parse_deployment_id_from_deployment_url'; diff --git a/x-pack/plugins/cloud/server/collectors/cloud_usage_collector.ts b/x-pack/plugins/cloud/server/collectors/cloud_usage_collector.ts index 2d1924817e56e..f4b1aab787a89 100644 --- a/x-pack/plugins/cloud/server/collectors/cloud_usage_collector.ts +++ b/x-pack/plugins/cloud/server/collectors/cloud_usage_collector.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; export interface CloudUsageCollectorConfig { isCloudEnabled: boolean; diff --git a/x-pack/plugins/cloud/tsconfig.json b/x-pack/plugins/cloud/tsconfig.json index ec6d5881a0531..fdf2ad6db58cd 100644 --- a/x-pack/plugins/cloud/tsconfig.json +++ b/x-pack/plugins/cloud/tsconfig.json @@ -16,7 +16,6 @@ "@kbn/config-schema", "@kbn/logging-mocks", "@kbn/logging", - "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.ts index 3615d1055a0b1..4efbca83ce2cc 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/constants.ts @@ -36,10 +36,6 @@ export enum FEATURE_FLAG_NAMES { * Options are: `true` and `false`. */ 'observability_onboarding.experimental_onboarding_flow_enabled' = 'observability_onboarding.experimental_onboarding_flow_enabled', - /** - * Used to enable the new stack navigation around solutions during the rollout period. - */ - 'solutionNavEnabled' = 'solutionNavEnabled', } /** diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts index a3b7207eb3122..420346c19b3ce 100755 --- a/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts @@ -69,7 +69,7 @@ export class CloudFullStoryPlugin implements Plugin { } // Keep this import async so that we do not load any FullStory code into the browser when it is disabled. - const { FullStoryShipper } = await import('@kbn/ebt/shippers/fullstory'); + const { FullStoryShipper } = await import('@elastic/ebt/shippers/fullstory'); analytics.registerShipper(FullStoryShipper, { eventTypesAllowlist, fullStoryOrgId, diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json index 852ce8bb8af86..faaf88812ac42 100644 --- a/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json @@ -14,7 +14,6 @@ "@kbn/core", "@kbn/cloud-plugin", "@kbn/config-schema", - "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 30cbcbce75f0a..053dcc4867bdd 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -12,6 +12,7 @@ import { VulnSeverity, } from './types_old'; +export const CLOUD_SECURITY_INTERTAL_PREFIX_ROUTE_PATH = '/internal/cloud_security_posture/'; export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status'; export const STATUS_API_CURRENT_VERSION = '1'; @@ -40,6 +41,19 @@ export const DETECTION_RULE_ALERTS_STATUS_API_CURRENT_VERSION = '1'; export const DETECTION_RULE_RULES_API_CURRENT_VERSION = '2023-10-31'; export const CLOUD_SECURITY_POSTURE_PACKAGE_NAME = 'cloud_security_posture'; + +export const CDR_MISCONFIGURATIONS_DATA_VIEW_NAME = 'Latest Cloud Security Misconfigurations'; +export const CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX = + 'security_solution_cdr_latest_misconfigurations'; +export const CDR_MISCONFIGURATIONS_INDEX_PATTERN = + 'logs-*_latest_misconfigurations_cdr,logs-cloud_security_posture.findings_latest-default'; + +export const CDR_VULNERABILITIES_DATA_VIEW_NAME = 'Latest Cloud Security Vulnerabilities'; +export const CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX = + 'security_solution_cdr_latest_vulnerabilities'; +export const CDR_VULNERABILITIES_INDEX_PATTERN = + 'logs-*_latest_vulnerabilities_cdr,logs-cloud_security_posture.vulnerabilities_latest-default'; + // TODO: REMOVE CSP_LATEST_FINDINGS_DATA_VIEW and replace it with LATEST_FINDINGS_INDEX_PATTERN export const CSP_LATEST_FINDINGS_DATA_VIEW = 'logs-cloud_security_posture.findings_latest-*'; @@ -71,8 +85,6 @@ export const LATEST_VULNERABILITIES_INDEX_DEFAULT_NS = 'logs-cloud_security_posture.vulnerabilities_latest-default'; export const LATEST_VULNERABILITIES_RETENTION_POLICY = '3d'; -export const DATA_VIEW_INDEX_PATTERN = 'logs-*'; - export const SECURITY_DEFAULT_DATA_VIEW_ID = 'security-solution-default'; export const ALERTS_INDEX_PATTERN = '.alerts-security.alerts-*'; @@ -143,9 +155,6 @@ export const POSTURE_TYPES: { [x: string]: PostureTypes } = { [POSTURE_TYPE_ALL]: POSTURE_TYPE_ALL, }; -export const VULNERABILITIES = 'vulnerabilities'; -export const CONFIGURATIONS = 'configurations'; - export const VULNERABILITIES_SEVERITY: Record<VulnSeverity, VulnSeverity> = { LOW: 'LOW', MEDIUM: 'MEDIUM', @@ -154,8 +163,6 @@ export const VULNERABILITIES_SEVERITY: Record<VulnSeverity, VulnSeverity> = { UNKNOWN: 'UNKNOWN', }; -export const VULNERABILITIES_ENUMERATION = 'CVE'; - export const AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP: AwsCredentialsTypeFieldMap = { assume_role: ['role_arn'], direct_access_keys: ['access_key_id', 'secret_access_key'], @@ -195,7 +202,6 @@ export const AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP = { manual: [], }; -export const CLOUD_FORMATION_STACK_NAME = 'Elastic-Cloud-Security-Posture-Management'; export const TEMPLATE_URL_ACCOUNT_TYPE_ENV_VAR = 'ACCOUNT_TYPE'; export const ORGANIZATION_ACCOUNT = 'organization-account'; diff --git a/x-pack/plugins/cloud_security_posture/common/dev_docs/__auto_generated_csp_requirements_test_coverage.md b/x-pack/plugins/cloud_security_posture/common/dev_docs/__auto_generated_csp_requirements_test_coverage.md index a6fdc6aa618ab..d1765e96f6f37 100644 --- a/x-pack/plugins/cloud_security_posture/common/dev_docs/__auto_generated_csp_requirements_test_coverage.md +++ b/x-pack/plugins/cloud_security_posture/common/dev_docs/__auto_generated_csp_requirements_test_coverage.md @@ -7,7 +7,7 @@ You can also check out the dedicated app view, which enables easier search and f ## Directory: x-pack/plugins/cloud_security_posture -**Total Tests:** 458 | **Skipped:** 5 (1.09%) | **Todo:** 0 (0.00%) +**Total Tests:** 473 | **Skipped:** 5 (1.06%) | **Todo:** 0 (0.00%) ![](https://img.shields.io/badge/UT-brightgreen) ![](https://img.shields.io/badge/HAS-SKIP-yellow) @@ -121,6 +121,8 @@ You can also check out the dedicated app view, which enables easier search and f | [renders loading state](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.test.tsx) | it | | | | [renders empty state when no rows are present](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.test.tsx) | it | | | | [renders data table with rows](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.test.tsx) | it | | | +| [renders data table with actions button](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.test.tsx) | it | | | +| [renders data table without actions button](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.test.tsx) | it | | | | [FieldsSelectorTable](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/fields_selector/fields_selector_table.test.tsx) | describe | | | | [renders the table with data correctly](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/fields_selector/fields_selector_table.test.tsx) | it | | | | [calls onAddColumn when a checkbox is checked](x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/fields_selector/fields_selector_table.test.tsx) | it | | | @@ -209,6 +211,7 @@ You can also check out the dedicated app view, which enables easier search and f | [renders ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx) | it | | | | [updates ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx) | it | | | | [Agentless](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx) | describe | | | +| [should not render setup technology selector if agentless is not available and CSPM integration supports agentless](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx) | it | | | | [should render setup technology selector for AWS and allow to select agent-based](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx) | it | | | | [should render setup technology selector for GCP for organisation account type](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx) | it | | | | [should render setup technology selector for GCP for single-account](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx) | it | | | @@ -228,11 +231,11 @@ You can also check out the dedicated app view, which enables easier search and f | [sets to AGENTLESS when agentless is available and GCP cloud](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | | [sets to AGENTLESS when agentless is available and Azure cloud](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | | [sets to AGENT_BASED when agentless is available but input is not supported for agentless](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | -| [sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | +| [sets to AGENT_BASED when isAgentlessEnabled is false](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | | [calls handleSetupTechnologyChange when setupTechnology changes](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | | [edit page flow](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | describe | | | | [initializes with AGENT_BASED technology](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | -| [initializes with AGENTLESS technology if the agent policy id is "agentless"](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | +| [initializes with AGENTLESS technology if isAgentlessEnable is true](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | | [should not call handleSetupTechnologyChange when setupTechnology changes](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | | [should not update setupTechnology when agentlessPolicyId becomes available](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts) | it | | | | [getPosturePolicy](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts) | describe | | | @@ -265,14 +268,14 @@ You can also check out the dedicated app view, which enables easier search and f | [Should return undefined when datastream is undefined](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts) | it | | | | [Should return undefined when stream is undefined](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts) | it | | | | [Should return undefined when stream.var is invalid](x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts) | it | | | -| [NoFindingsStates](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | describe | | | -| [should show the indexing notification when CSPM is not installed and KSPM is indexing](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | it | | | -| [should show the indexing notification when KSPM is not installed and CSPM is indexing](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | it | | | -| [should show the indexing timout notification when CSPM is status is index-timeout](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | it | | | -| [should show the indexing timout notification when KSPM is status is index-timeout](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | it | | | -| [should show the unprivileged notification when CSPM is status is index-timeout](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | it | | | -| [should show the unprivileged notification when KSPM is status is index-timeout](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | it | | | -| [should show the not-installed notification when CSPM and KSPM status is not-installed](x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx) | it | | | +| [NoFindingsStates](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | describe | | | +| [shows integrations installation prompt with installation links when integration is not-installed](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | it | | | +| [shows install agent prompt with install agent link when status is not-deployed](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | it | | | +| [shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | it | | | +| [shows indexing message when status is indexing](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | it | | | +| [shows timeout message when status is index-timeout](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | it | | | +| [shows unprivileged message when status is unprivileged](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | it | | | +| [renders empty container when the status does not match a no finding status](x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx) | it | | | | [<BenchmarksTable />](x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.test.tsx) | describe | | | | [renders cis integration name](x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.test.tsx) | it | | | | [renders benchmark version](x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.test.tsx) | it | | | @@ -320,19 +323,31 @@ You can also check out the dedicated app view, which enables easier search and f | [renders counters content according to mock](x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx) | it | | | | [renders counters value in compact abbreviation if its above one million](x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.test.tsx) | it | | | | [<Findings />](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | describe | | | -| [no findings state: not-deployed - shows NotDeployed instead of findings](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | -| [no findings state: indexing - shows Indexing instead of findings](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | -| [no findings state: index-timeout - shows IndexTimeout instead of findings](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | -| [no findings state: unprivileged - shows Unprivileged instead of findings](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | | [renders integrations installation prompt if integration is not installed](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | +| [SearchBar](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | describe | | | +| [set search query](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | +| [renders no results message and reset button when search query does not match](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | +| [add filter](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | +| [remove filter](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | +| [DistributionBar](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | describe | | | +| [renders the distribution bar](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | +| [filters by passed findings when clicking on the passed findings button](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | +| [filters by failed findings when clicking on the failed findings button](x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx) | it | | | | [<FindingsFlyout/>](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | describe | | | | [Overview Tab](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | describe | | | | [details and remediation accordions are open](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | | [displays text details summary info](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | +| [displays missing info callout when data source is not CSP](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | +| [does not display missing info callout when data source is CSP](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | | [Rule Tab](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | describe | | | | [displays rule text details](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | +| [displays missing info callout when data source is not CSP](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | +| [does not display missing info callout when data source is CSP](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | | [Table Tab](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | describe | | | | [displays resource name and id](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | +| [does not display missing info callout for 3Ps](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | +| [JSON Tab](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | describe | | | +| [does not display missing info callout for 3Ps](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | | [should allow pagination with next](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | | [should allow pagination with previous](x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx) | it | | | | [Get Filters](x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/get_filters.test.ts) | describe | | | @@ -528,7 +543,7 @@ You can also check out the dedicated app view, which enables easier search and f ## Directory: x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture -**Total Tests:** 4 | **Skipped:** 0 (0.00%) | **Todo:** 0 (0.00%) +**Total Tests:** 23 | **Skipped:** 0 (0.00%) | **Todo:** 0 (0.00%) ![](https://img.shields.io/badge/FTR-blue) ![](https://img.shields.io/badge/SERVERLESS-pink) @@ -537,9 +552,28 @@ You can also check out the dedicated app view, which enables easier search and f | Test Label | Type | Skipped | Todo | |------------|------|---------|------| +| [Agentless CIS Integration Page](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts) | describe | | | +| [Agentless CIS_AWS Single Account Launch Cloud formation](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts) | describe | | | +| [should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts) | it | | | +| [should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts) | it | | | +| [Agentless CIS_AWS ORG Account Launch Cloud formation](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts) | describe | | | +| [should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts) | it | | | +| [should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts) | it | | | +| [Agentless CIS Integration Page](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts) | describe | | | +| [Agentless CIS_GCP Single Account Launch Cloud shell](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts) | describe | | | +| [should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts) | it | | | +| [should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts) | it | | | +| [Agentless CIS_GCP ORG Account Launch Cloud Shell](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts) | describe | | | +| [should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts) | it | | | +| [should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts) | it | | | +| [cloud_security_posture](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/index.ts) | describe | | | | [Cloud Posture Dashboard Page](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts) | describe | | | | [Kubernetes Dashboard](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts) | describe | | | | [displays accurate summary compliance score](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts) | it | | | +| [[Essentials PLI] Test Cloud Security Posture Integrations on Serverless](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.essentials.ts) | describe | | | +| [[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.essentials.ts) | it | | | +| [Test Cloud Security Posture Integrations on Serverless](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.ts) | describe | | | +| [Integration installation form should not be available without required PLI](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.ts) | it | | | | [cloud_security_posture](x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/index.ts) | describe | | | </details> @@ -620,19 +654,19 @@ You can also check out the dedicated app view, which enables easier search and f ## Directory: x-pack/test/cloud_security_posture_api -**Total Tests:** 35 | **Skipped:** 4 (11.43%) | **Todo:** 0 (0.00%) +**Total Tests:** 35 | **Skipped:** 0 (0.00%) | **Todo:** 0 (0.00%) -![](https://img.shields.io/badge/FTR-blue) ![](https://img.shields.io/badge/API-INTEGRATION-purple) ![](https://img.shields.io/badge/HAS-SKIP-yellow) +![](https://img.shields.io/badge/FTR-blue) ![](https://img.shields.io/badge/API-INTEGRATION-purple) <details> <summary>Test Details</summary> | Test Label | Type | Skipped | Todo | |------------|------|---------|------| -| [GET /internal/cloud_security_posture/benchmarks](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | -| [Get Benchmark API](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | -| [Verify cspm benchmark score is updated when muting rules](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | -| [Verify kspm benchmark score is updated when muting rules](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [GET /internal/cloud_security_posture/benchmarks](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | describe | | | +| [Get Benchmark API](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | describe | | | +| [Verify cspm benchmark score is updated when muting rules](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | it | | | +| [Verify kspm benchmark score is updated when muting rules](x-pack/test/cloud_security_posture_api/routes/benchmarks.ts) | it | | | | [Verify update csp rules states API](x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts) | describe | | | | [mute benchmark rules successfully](x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts) | it | | | | [unmute rules successfully](x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts) | it | | | @@ -668,7 +702,7 @@ You can also check out the dedicated app view, which enables easier search and f ## Directory: x-pack/test/cloud_security_posture_functional -**Total Tests:** 202 | **Skipped:** 41 (20.30%) | **Todo:** 3 (1.49%) +**Total Tests:** 201 | **Skipped:** 71 (35.32%) | **Todo:** 3 (1.49%) ![](https://img.shields.io/badge/FTR-blue) ![](https://img.shields.io/badge/HAS-SKIP-yellow) ![](https://img.shields.io/badge/HAS-TODO-green) @@ -677,6 +711,15 @@ You can also check out the dedicated app view, which enables easier search and f | Test Label | Type | Skipped | Todo | |------------|------|---------|------| +| [Cloud Posture Dashboard Page](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | describe | | | +| [Cloud Dashboard](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | describe | | | +| [displays compliance score greater than 40](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | it | | | +| [displays all compliance scores](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | it | | | +| [displays a number of resources evaluated greater than 3000](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | it | | | +| [Kubernetes Dashboard](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | describe | | | +| [displays compliance score greater than 80](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | it | | | +| [displays a number of resources evaluated greater than 150](x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts) | it | | | +| [Cloud Security Posture](x-pack/test/cloud_security_posture_functional/cloud_tests/index.ts) | describe | | | | [Access with custom roles](x-pack/test/cloud_security_posture_functional/pages/benchmark.ts) | describe | | | | [Access with valid user role](x-pack/test/cloud_security_posture_functional/pages/benchmark.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | | [Access with invalid user role](x-pack/test/cloud_security_posture_functional/pages/benchmark.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | @@ -684,16 +727,16 @@ You can also check out the dedicated app view, which enables easier search and f | [Access with valid user role](x-pack/test/cloud_security_posture_functional/pages/benchmark.ts) | it | | | | [Access with invalid user role](x-pack/test/cloud_security_posture_functional/pages/benchmark.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | | [Test adding Cloud Security Posture Integrations CNVM](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | describe | | | -| [CNVM AWS](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | describe | | | -| [Hyperlink on PostInstallation Modal should have the correct URL](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | it | | | -| [On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | it | | | -| [Clicking on Launch CloudFormation on post intall modal should lead user to Cloud Formation page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | it | | | +| [CNVM AWS](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Hyperlink on PostInstallation Modal should have the correct URL](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Clicking on Launch CloudFormation on post intall modal should lead user to Cloud Formation page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | | [Test adding Cloud Security Posture Integrations CSPM AWS](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | describe | | | -| [CIS_AWS Organization Cloud Formation](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | describe | | | -| [Initial form state, AWS Org account, and CloudFormation should be selected by default](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | | | -| [Hyperlink on PostInstallation Modal should have the correct URL](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | | | -| [On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | | | -| [Clicking on Launch CloudFormation on post intall modal should lead user to Cloud Formation page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | | | +| [CIS_AWS Organization Cloud Formation](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Initial form state, AWS Org account, and CloudFormation should be selected by default](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Hyperlink on PostInstallation Modal should have the correct URL](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Clicking on Launch CloudFormation on post intall modal should lead user to Cloud Formation page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | | [CIS_AWS Organization Manual Assume Role](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | describe | | | | [CIS_AWS Organization Manual Assume Role Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | it | | | | [CIS_AWS Organization Manual Direct Access](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts) | describe | | | @@ -729,37 +772,37 @@ You can also check out the dedicated app view, which enables easier search and f | [Azure Single Manual Service Principle with Client Secret Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts) | it | | | | [Azure Single Manual Service Principle with Client Certificate](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts) | describe | | | | [Azure Single Manual Service Principle with Client Certificate Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_azure.ts) | it | | | -| [Test adding Cloud Security Posture Integrations CSPM GCP](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | | | -| [CIS_GCP Organization](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | | | -| [Switch between Manual and Google cloud shell](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are no Project ID or Organization ID provided, it should use default value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID or Organization ID provided, it should use that value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Add Agent FLyout - Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID or Organization ID provided, it should use that value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Organization ID field on cloud shell command should only be shown if user chose Google Cloud Shell, if user chose Single Account it shouldn not show up](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Hyperlink on PostInstallation Modal should have the correct URL](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Clicking on Launch CloudShell on post intall modal should lead user to CloudShell page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [CIS_GCP Organization Credentials File](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | | | -| [CIS_GCP Organization Credentials File workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [CIS_GCP Organization Credentials JSON](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | | | -| [CIS_GCP Organization Credentials JSON workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [CIS_GCP Single](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | | | -| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are no Project ID, it should use default value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID, it should use that value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Add Agent FLyout - Organization ID field on cloud shell command should only be shown if user chose Google Cloud Shell, if user chose Single Account it shouldn not show up](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [On add agent modal, if user chose Google Cloud Shell as their setup access; a google cloud shell modal should show up and clicking on the launch button will redirect user to Google cloud shell page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Users are able to add CIS_GCP Integration with Manual settings using Credentials File](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Users are able to switch credentials_type from/to Credential JSON fields ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Users are able to add CIS_GCP Integration with Manual settings using Credentials JSON](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | -| [Users are able to switch credentials_type from/to Credential File fields ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | | | +| [Test adding Cloud Security Posture Integrations CSPM GCP](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [CIS_GCP Organization](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Switch between Manual and Google cloud shell](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are no Project ID or Organization ID provided, it should use default value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID or Organization ID provided, it should use that value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Add Agent FLyout - Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID or Organization ID provided, it should use that value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Organization ID field on cloud shell command should only be shown if user chose Google Cloud Shell, if user chose Single Account it shouldn not show up](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Hyperlink on PostInstallation Modal should have the correct URL](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Clicking on Launch CloudShell on post intall modal should lead user to CloudShell page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [CIS_GCP Organization Credentials File](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [CIS_GCP Organization Credentials File workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [CIS_GCP Organization Credentials JSON](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [CIS_GCP Organization Credentials JSON workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [CIS_GCP Single](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are no Project ID, it should use default value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID, it should use that value](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Add Agent FLyout - Organization ID field on cloud shell command should only be shown if user chose Google Cloud Shell, if user chose Single Account it shouldn not show up](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [On add agent modal, if user chose Google Cloud Shell as their setup access; a google cloud shell modal should show up and clicking on the launch button will redirect user to Google cloud shell page](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Users are able to add CIS_GCP Integration with Manual settings using Credentials File](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Users are able to switch credentials_type from/to Credential JSON fields ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Users are able to add CIS_GCP Integration with Manual settings using Credentials JSON](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Users are able to switch credentials_type from/to Credential File fields ](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | | [Test adding Cloud Security Posture Integrations KSPM EKS](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | | | -| [KSPM EKS Assume Role](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | | | -| [KSPM EKS Assume Role workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | | | -| [KSPM EKS Direct Access](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | | | -| [KSPM EKS Direct Access Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | | | -| [KSPM EKS Temporary Keys](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | | | -| [KSPM EKS Temporary Keys Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | | | -| [KSPM EKS Shared Credentials](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | | | -| [KSPM EKS Shared Credentials Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | | | +| [KSPM EKS Assume Role](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [KSPM EKS Assume Role workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [KSPM EKS Direct Access](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [KSPM EKS Direct Access Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [KSPM EKS Temporary Keys](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [KSPM EKS Temporary Keys Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [KSPM EKS Shared Credentials](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | +| [KSPM EKS Shared Credentials Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | | [Test adding Cloud Security Posture Integrations KSPM K8S](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_k8s.ts) | describe | | | | [KSPM K8S](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_k8s.ts) | describe | | | | [KSPM K8S Workflow](x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_k8s.ts) | it | | | @@ -772,9 +815,9 @@ You can also check out the dedicated app view, which enables easier search and f | [Access with valid user role](x-pack/test/cloud_security_posture_functional/pages/compliance_dashboard.ts) | it | | | | [todo - Access with invalid user role](x-pack/test/cloud_security_posture_functional/pages/compliance_dashboard.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | ![](https://img.shields.io/badge/todo-green) | | [Findings Page - Alerts](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | describe | | | -| [Create detection rule](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | -| [Creates a detection rule from the Take Action button and navigates to rule page](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | -| [Creates a detection rule from the Alerts section and navigates to rule page](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | +| [Create detection rule](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | describe | | | +| [Creates a detection rule from the Take Action button and navigates to rule page](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | it | | | +| [Creates a detection rule from the Alerts section and navigates to rule page](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | it | | | | [Rule details](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | describe | | | | [The rule page contains the expected matching data](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | it | | | | [Navigation](x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts) | describe | | | @@ -803,16 +846,8 @@ You can also check out the dedicated app view, which enables easier search and f | [clicking on the ](x-pack/test/cloud_security_posture_functional/pages/findings_onboarding.ts) | it | | | | [clicking on the ](x-pack/test/cloud_security_posture_functional/pages/findings_onboarding.ts) | it | | | | [Findings Page - DataTable](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | describe | | | -| [SearchBar](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | -| [add filter](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | -| [remove filter](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | -| [set search query](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | -| [Table Sort](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | describe | ![](https://img.shields.io/badge/skipped-yellow) | | -| [sorts by a column, should be case sensitive/insensitive depending on the column](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | ![](https://img.shields.io/badge/skipped-yellow) | | -| [DistributionBar](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | describe | | | -| [filters by ${type} findings](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | | | -| [DataTable features](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | describe | | | -| [Edit data view field option is Enabled](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | | | +| [Table Sort](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | describe | | | +| [sorts by a column, should be case sensitive/insensitive depending on the column](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | | | | [Findings - Fields selector](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | describe | | | | [Add fields to the Findings DataTable](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | | | | [Remove fields from the Findings DataTable](x-pack/test/cloud_security_posture_functional/pages/findings.ts) | it | | | @@ -867,8 +902,6 @@ You can also check out the dedicated app view, which enables easier search and f | [add filter](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | it | | | | [remove filter](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | it | | | | [set search query](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | it | | | -| [DataTable features](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | describe | | | -| [Edit data view field option is Enabled](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | it | | | | [Vulnerabilities - Fields selector](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | describe | | | | [Add fields to the Vulnerabilities DataTable](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | it | | | | [Remove fields from the Vulnerabilities DataTable](x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts) | it | | | diff --git a/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts b/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts deleted file mode 100644 index eed3eba2f7220..0000000000000 --- a/x-pack/plugins/cloud_security_posture/common/runtime_mappings/get_safe_kspm_cluster_id_runtime_mapping.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; - -/** - * Creates the `safe_posture_type` runtime field with the value of either - * `kspm` or `cspm` based on the value of `rule.benchmark.posture_type` - */ -export const getSafeKspmClusterIdRuntimeMapping = (): MappingRuntimeFields => ({ - safe_kspm_cluster_id: { - type: 'keyword', - script: { - source: ` - def orchestratorIdAvailable = doc.containsKey("orchestrator.cluster.id") && - !doc["orchestrator.cluster.id"].empty; - def clusterIdAvailable = doc.containsKey("cluster_id") && - !doc["cluster_id"].empty; - - if (orchestratorIdAvailable) { - emit(doc["orchestrator.cluster.id"].value); - } else if (clusterIdAvailable) { - emit(doc["cluster_id"].value); - } - `, - }, - }, -}); diff --git a/x-pack/plugins/cloud_security_posture/common/schemas/csp_finding.ts b/x-pack/plugins/cloud_security_posture/common/schemas/csp_finding.ts index 3033da7b043b9..7ac4e79393f01 100644 --- a/x-pack/plugins/cloud_security_posture/common/schemas/csp_finding.ts +++ b/x-pack/plugins/cloud_security_posture/common/schemas/csp_finding.ts @@ -6,7 +6,7 @@ */ // TODO: this needs to be defined in a versioned schema -import type { EcsEvent } from '@elastic/ecs'; +import type { EcsDataStream, EcsEvent } from '@elastic/ecs'; import { CspBenchmarkRuleMetadata } from '../types/latest'; export interface CspFinding { @@ -19,6 +19,7 @@ export interface CspFinding { rule: CspBenchmarkRuleMetadata; host: CspFindingHost; event: EcsEvent; + data_stream: EcsDataStream; agent: CspFindingAgent; ecs: { version: string; diff --git a/x-pack/plugins/cloud_security_posture/common/scripts/__auto_generated_csp_test_log.json b/x-pack/plugins/cloud_security_posture/common/scripts/__auto_generated_csp_test_log.json index 8c177cd46e713..3db661753de50 100644 --- a/x-pack/plugins/cloud_security_posture/common/scripts/__auto_generated_csp_test_log.json +++ b/x-pack/plugins/cloud_security_posture/common/scripts/__auto_generated_csp_test_log.json @@ -2365,7 +2365,9 @@ "describe('CloudSecurityDataTable')", " it('renders loading state')", " it('renders empty state when no rows are present')", - " it('renders data table with rows')" + " it('renders data table with rows')", + " it('renders data table with actions button')", + " it('renders data table without actions button')" ], "testSuits": [ { @@ -2407,6 +2409,26 @@ "type": "it", "isSkipped": false, "isTodo": false + }, + { + "id": "renders-data-table-with-actions-button", + "rawLine": " it('renders data table with actions button', async () => {", + "line": " it('renders data table with actions button')", + "label": "renders data table with actions button", + "indent": 2, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "renders-data-table-without-actions-button", + "rawLine": " it('renders data table without actions button', async () => {", + "line": " it('renders data table without actions button')", + "label": "renders data table without actions button", + "indent": 2, + "type": "it", + "isSkipped": false, + "isTodo": false } ], "tree": [ @@ -2449,6 +2471,26 @@ "type": "it", "isSkipped": false, "isTodo": false + }, + { + "id": "renders-data-table-with-actions-button", + "rawLine": " it('renders data table with actions button', async () => {", + "line": " it('renders data table with actions button')", + "label": "renders data table with actions button", + "indent": 2, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "renders-data-table-without-actions-button", + "rawLine": " it('renders data table without actions button', async () => {", + "line": " it('renders data table without actions button')", + "label": "renders data table without actions button", + "indent": 2, + "type": "it", + "isSkipped": false, + "isTodo": false } ] } @@ -3123,6 +3165,7 @@ " it(`renders ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`)", " it(`updates ${CLOUDBEAT_AZURE} Service Principal with Client Secret fields`)", " describe('Agentless')", + " it('should not render setup technology selector if agentless is not available and CSPM integration supports agentless')", " it('should render setup technology selector for AWS and allow to select agent-based')", " it('should render setup technology selector for GCP for organisation account type')", " it('should render setup technology selector for GCP for single-account')", @@ -3757,6 +3800,16 @@ "isSkipped": false, "isTodo": false }, + { + "id": "should-not-render-setup-technology-selector-if-agentless-is-not-available-and-cspm-integration-supports-agentless", + "rawLine": " it('should not render setup technology selector if agentless is not available and CSPM integration supports agentless', async () => {", + "line": " it('should not render setup technology selector if agentless is not available and CSPM integration supports agentless')", + "label": "should not render setup technology selector if agentless is not available and CSPM integration supports agentless", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, { "id": "should-render-setup-technology-selector-for-aws-and-allow-to-select-agent-based", "rawLine": " it('should render setup technology selector for AWS and allow to select agent-based', async () => {", @@ -4511,6 +4564,16 @@ "isSkipped": false, "isTodo": false, "children": [ + { + "id": "should-not-render-setup-technology-selector-if-agentless-is-not-available-and-cspm-integration-supports-agentless", + "rawLine": " it('should not render setup technology selector if agentless is not available and CSPM integration supports agentless', async () => {", + "line": " it('should not render setup technology selector if agentless is not available and CSPM integration supports agentless')", + "label": "should not render setup technology selector if agentless is not available and CSPM integration supports agentless", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, { "id": "should-render-setup-technology-selector-for-aws-and-allow-to-select-agent-based", "rawLine": " it('should render setup technology selector for AWS and allow to select agent-based', async () => {", @@ -4652,11 +4715,11 @@ " it('sets to AGENTLESS when agentless is available and GCP cloud')", " it('sets to AGENTLESS when agentless is available and Azure cloud')", " it('sets to AGENT_BASED when agentless is available but input is not supported for agentless')", - " it('sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId')", + " it('sets to AGENT_BASED when isAgentlessEnabled is false')", " it('calls handleSetupTechnologyChange when setupTechnology changes')", " describe('edit page flow')", " it('initializes with AGENT_BASED technology')", - " it('initializes with AGENTLESS technology if the agent policy id is \"agentless\"')", + " it('initializes with AGENTLESS technology if isAgentlessEnable is true')", " it('should not call handleSetupTechnologyChange when setupTechnology changes')", " it('should not update setupTechnology when agentlessPolicyId becomes available')" ], @@ -4732,10 +4795,10 @@ "isTodo": false }, { - "id": "sets-to-agent_based-when-agentpolicyid-differs-from-agentlesspolicyid", - "rawLine": " it('sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId', () => {", - "line": " it('sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId')", - "label": "sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId", + "id": "sets-to-agent_based-when-isagentlessenabled-is-false", + "rawLine": " it('sets to AGENT_BASED when isAgentlessEnabled is false', () => {", + "line": " it('sets to AGENT_BASED when isAgentlessEnabled is false')", + "label": "sets to AGENT_BASED when isAgentlessEnabled is false", "indent": 4, "type": "it", "isSkipped": false, @@ -4772,10 +4835,10 @@ "isTodo": false }, { - "id": "initializes-with-agentless-technology-if-the-agent-policy-id-is-\"agentless\"", - "rawLine": " it('initializes with AGENTLESS technology if the agent policy id is \"agentless\"', () => {", - "line": " it('initializes with AGENTLESS technology if the agent policy id is \"agentless\"')", - "label": "initializes with AGENTLESS technology if the agent policy id is \"agentless\"", + "id": "initializes-with-agentless-technology-if-isagentlessenable-is-true", + "rawLine": " it('initializes with AGENTLESS technology if isAgentlessEnable is true', () => {", + "line": " it('initializes with AGENTLESS technology if isAgentlessEnable is true')", + "label": "initializes with AGENTLESS technology if isAgentlessEnable is true", "indent": 4, "type": "it", "isSkipped": false, @@ -4874,10 +4937,10 @@ "isTodo": false }, { - "id": "sets-to-agent_based-when-agentpolicyid-differs-from-agentlesspolicyid", - "rawLine": " it('sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId', () => {", - "line": " it('sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId')", - "label": "sets to AGENT_BASED when agentPolicyId differs from agentlessPolicyId", + "id": "sets-to-agent_based-when-isagentlessenabled-is-false", + "rawLine": " it('sets to AGENT_BASED when isAgentlessEnabled is false', () => {", + "line": " it('sets to AGENT_BASED when isAgentlessEnabled is false')", + "label": "sets to AGENT_BASED when isAgentlessEnabled is false", "indent": 4, "type": "it", "isSkipped": false, @@ -4916,10 +4979,10 @@ "isTodo": false }, { - "id": "initializes-with-agentless-technology-if-the-agent-policy-id-is-\"agentless\"", - "rawLine": " it('initializes with AGENTLESS technology if the agent policy id is \"agentless\"', () => {", - "line": " it('initializes with AGENTLESS technology if the agent policy id is \"agentless\"')", - "label": "initializes with AGENTLESS technology if the agent policy id is \"agentless\"", + "id": "initializes-with-agentless-technology-if-isagentlessenable-is-true", + "rawLine": " it('initializes with AGENTLESS technology if isAgentlessEnable is true', () => {", + "line": " it('initializes with AGENTLESS technology if isAgentlessEnable is true')", + "label": "initializes with AGENTLESS technology if isAgentlessEnable is true", "indent": 4, "type": "it", "isSkipped": false, @@ -5675,7 +5738,7 @@ ] }, { - "filePath": "x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx", + "filePath": "x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx", "fileName": "no_findings_states.test.tsx", "directory": "x-pack/plugins/cloud_security_posture", "tags": [ @@ -5683,13 +5746,13 @@ ], "lines": [ "describe('NoFindingsStates')", - " it('should show the indexing notification when CSPM is not installed and KSPM is indexing')", - " it('should show the indexing notification when KSPM is not installed and CSPM is indexing')", - " it('should show the indexing timout notification when CSPM is status is index-timeout')", - " it('should show the indexing timout notification when KSPM is status is index-timeout')", - " it('should show the unprivileged notification when CSPM is status is index-timeout')", - " it('should show the unprivileged notification when KSPM is status is index-timeout')", - " it('should show the not-installed notification when CSPM and KSPM status is not-installed')" + " it('shows integrations installation prompt with installation links when integration is not-installed')", + " it('shows install agent prompt with install agent link when status is not-deployed')", + " it('shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM')", + " it('shows indexing message when status is indexing')", + " it('shows timeout message when status is index-timeout')", + " it('shows unprivileged message when status is unprivileged')", + " it('renders empty container when the status does not match a no finding status')" ], "testSuits": [ { @@ -5703,70 +5766,70 @@ "isTodo": false }, { - "id": "should-show-the-indexing-notification-when-cspm-is-not-installed-and-kspm-is-indexing", - "rawLine": " it('should show the indexing notification when CSPM is not installed and KSPM is indexing', async () => {", - "line": " it('should show the indexing notification when CSPM is not installed and KSPM is indexing')", - "label": "should show the indexing notification when CSPM is not installed and KSPM is indexing", + "id": "shows-integrations-installation-prompt-with-installation-links-when-integration-is-not-installed", + "rawLine": " it('shows integrations installation prompt with installation links when integration is not-installed', async () => {", + "line": " it('shows integrations installation prompt with installation links when integration is not-installed')", + "label": "shows integrations installation prompt with installation links when integration is not-installed", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-indexing-notification-when-kspm-is-not-installed-and-cspm-is-indexing", - "rawLine": " it('should show the indexing notification when KSPM is not installed and CSPM is indexing', async () => {", - "line": " it('should show the indexing notification when KSPM is not installed and CSPM is indexing')", - "label": "should show the indexing notification when KSPM is not installed and CSPM is indexing", + "id": "shows-install-agent-prompt-with-install-agent-link-when-status-is-not-deployed", + "rawLine": " it('shows install agent prompt with install agent link when status is not-deployed', async () => {", + "line": " it('shows install agent prompt with install agent link when status is not-deployed')", + "label": "shows install agent prompt with install agent link when status is not-deployed", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-indexing-timout-notification-when-cspm-is-status-is-index-timeout", - "rawLine": " it('should show the indexing timout notification when CSPM is status is index-timeout', async () => {", - "line": " it('should show the indexing timout notification when CSPM is status is index-timeout')", - "label": "should show the indexing timout notification when CSPM is status is index-timeout", + "id": "shows-install-agent-prompt-with-install-agent-link-when-status-is-not-deployed-and-posturetype-is-kspm", + "rawLine": " it('shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM', async () => {", + "line": " it('shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM')", + "label": "shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-indexing-timout-notification-when-kspm-is-status-is-index-timeout", - "rawLine": " it('should show the indexing timout notification when KSPM is status is index-timeout', async () => {", - "line": " it('should show the indexing timout notification when KSPM is status is index-timeout')", - "label": "should show the indexing timout notification when KSPM is status is index-timeout", + "id": "shows-indexing-message-when-status-is-indexing", + "rawLine": " it('shows indexing message when status is indexing', async () => {", + "line": " it('shows indexing message when status is indexing')", + "label": "shows indexing message when status is indexing", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-unprivileged-notification-when-cspm-is-status-is-index-timeout", - "rawLine": " it('should show the unprivileged notification when CSPM is status is index-timeout', async () => {", - "line": " it('should show the unprivileged notification when CSPM is status is index-timeout')", - "label": "should show the unprivileged notification when CSPM is status is index-timeout", + "id": "shows-timeout-message-when-status-is-index-timeout", + "rawLine": " it('shows timeout message when status is index-timeout', async () => {", + "line": " it('shows timeout message when status is index-timeout')", + "label": "shows timeout message when status is index-timeout", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-unprivileged-notification-when-kspm-is-status-is-index-timeout", - "rawLine": " it('should show the unprivileged notification when KSPM is status is index-timeout', async () => {", - "line": " it('should show the unprivileged notification when KSPM is status is index-timeout')", - "label": "should show the unprivileged notification when KSPM is status is index-timeout", + "id": "shows-unprivileged-message-when-status-is-unprivileged", + "rawLine": " it('shows unprivileged message when status is unprivileged', async () => {", + "line": " it('shows unprivileged message when status is unprivileged')", + "label": "shows unprivileged message when status is unprivileged", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-not-installed-notification-when-cspm-and-kspm-status-is-not-installed", - "rawLine": " it('should show the not-installed notification when CSPM and KSPM status is not-installed', async () => {", - "line": " it('should show the not-installed notification when CSPM and KSPM status is not-installed')", - "label": "should show the not-installed notification when CSPM and KSPM status is not-installed", + "id": "renders-empty-container-when-the-status-does-not-match-a-no-finding-status", + "rawLine": " it('renders empty container when the status does not match a no finding status', async () => {", + "line": " it('renders empty container when the status does not match a no finding status')", + "label": "renders empty container when the status does not match a no finding status", "indent": 2, "type": "it", "isSkipped": false, @@ -5785,70 +5848,70 @@ "isTodo": false, "children": [ { - "id": "should-show-the-indexing-notification-when-cspm-is-not-installed-and-kspm-is-indexing", - "rawLine": " it('should show the indexing notification when CSPM is not installed and KSPM is indexing', async () => {", - "line": " it('should show the indexing notification when CSPM is not installed and KSPM is indexing')", - "label": "should show the indexing notification when CSPM is not installed and KSPM is indexing", + "id": "shows-integrations-installation-prompt-with-installation-links-when-integration-is-not-installed", + "rawLine": " it('shows integrations installation prompt with installation links when integration is not-installed', async () => {", + "line": " it('shows integrations installation prompt with installation links when integration is not-installed')", + "label": "shows integrations installation prompt with installation links when integration is not-installed", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-indexing-notification-when-kspm-is-not-installed-and-cspm-is-indexing", - "rawLine": " it('should show the indexing notification when KSPM is not installed and CSPM is indexing', async () => {", - "line": " it('should show the indexing notification when KSPM is not installed and CSPM is indexing')", - "label": "should show the indexing notification when KSPM is not installed and CSPM is indexing", + "id": "shows-install-agent-prompt-with-install-agent-link-when-status-is-not-deployed", + "rawLine": " it('shows install agent prompt with install agent link when status is not-deployed', async () => {", + "line": " it('shows install agent prompt with install agent link when status is not-deployed')", + "label": "shows install agent prompt with install agent link when status is not-deployed", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-indexing-timout-notification-when-cspm-is-status-is-index-timeout", - "rawLine": " it('should show the indexing timout notification when CSPM is status is index-timeout', async () => {", - "line": " it('should show the indexing timout notification when CSPM is status is index-timeout')", - "label": "should show the indexing timout notification when CSPM is status is index-timeout", + "id": "shows-install-agent-prompt-with-install-agent-link-when-status-is-not-deployed-and-posturetype-is-kspm", + "rawLine": " it('shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM', async () => {", + "line": " it('shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM')", + "label": "shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-indexing-timout-notification-when-kspm-is-status-is-index-timeout", - "rawLine": " it('should show the indexing timout notification when KSPM is status is index-timeout', async () => {", - "line": " it('should show the indexing timout notification when KSPM is status is index-timeout')", - "label": "should show the indexing timout notification when KSPM is status is index-timeout", + "id": "shows-indexing-message-when-status-is-indexing", + "rawLine": " it('shows indexing message when status is indexing', async () => {", + "line": " it('shows indexing message when status is indexing')", + "label": "shows indexing message when status is indexing", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-unprivileged-notification-when-cspm-is-status-is-index-timeout", - "rawLine": " it('should show the unprivileged notification when CSPM is status is index-timeout', async () => {", - "line": " it('should show the unprivileged notification when CSPM is status is index-timeout')", - "label": "should show the unprivileged notification when CSPM is status is index-timeout", + "id": "shows-timeout-message-when-status-is-index-timeout", + "rawLine": " it('shows timeout message when status is index-timeout', async () => {", + "line": " it('shows timeout message when status is index-timeout')", + "label": "shows timeout message when status is index-timeout", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-unprivileged-notification-when-kspm-is-status-is-index-timeout", - "rawLine": " it('should show the unprivileged notification when KSPM is status is index-timeout', async () => {", - "line": " it('should show the unprivileged notification when KSPM is status is index-timeout')", - "label": "should show the unprivileged notification when KSPM is status is index-timeout", + "id": "shows-unprivileged-message-when-status-is-unprivileged", + "rawLine": " it('shows unprivileged message when status is unprivileged', async () => {", + "line": " it('shows unprivileged message when status is unprivileged')", + "label": "shows unprivileged message when status is unprivileged", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-show-the-not-installed-notification-when-cspm-and-kspm-status-is-not-installed", - "rawLine": " it('should show the not-installed notification when CSPM and KSPM status is not-installed', async () => {", - "line": " it('should show the not-installed notification when CSPM and KSPM status is not-installed')", - "label": "should show the not-installed notification when CSPM and KSPM status is not-installed", + "id": "renders-empty-container-when-the-status-does-not-match-a-no-finding-status", + "rawLine": " it('renders empty container when the status does not match a no finding status', async () => {", + "line": " it('renders empty container when the status does not match a no finding status')", + "label": "renders empty container when the status does not match a no finding status", "indent": 2, "type": "it", "isSkipped": false, @@ -6933,11 +6996,16 @@ ], "lines": [ "describe('<Findings />')", - " it('no findings state: not-deployed - shows NotDeployed instead of findings')", - " it('no findings state: indexing - shows Indexing instead of findings')", - " it('no findings state: index-timeout - shows IndexTimeout instead of findings')", - " it('no findings state: unprivileged - shows Unprivileged instead of findings')", - " it('renders integrations installation prompt if integration is not installed')" + " it('renders integrations installation prompt if integration is not installed')", + " describe('SearchBar')", + " it('set search query')", + " it('renders no results message and reset button when search query does not match')", + " it('add filter')", + " it('remove filter')", + " describe('DistributionBar')", + " it('renders the distribution bar')", + " it('filters by passed findings when clicking on the passed findings button')", + " it('filters by failed findings when clicking on the failed findings button')" ], "testSuits": [ { @@ -6951,51 +7019,101 @@ "isTodo": false }, { - "id": "no-findings-state:-not-deployed---shows-notdeployed-instead-of-findings", - "rawLine": " it('no findings state: not-deployed - shows NotDeployed instead of findings', () => {", - "line": " it('no findings state: not-deployed - shows NotDeployed instead of findings')", - "label": "no findings state: not-deployed - shows NotDeployed instead of findings", + "id": "renders-integrations-installation-prompt-if-integration-is-not-installed", + "rawLine": " it('renders integrations installation prompt if integration is not installed', async () => {", + "line": " it('renders integrations installation prompt if integration is not installed')", + "label": "renders integrations installation prompt if integration is not installed", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "no-findings-state:-indexing---shows-indexing-instead-of-findings", - "rawLine": " it('no findings state: indexing - shows Indexing instead of findings', () => {", - "line": " it('no findings state: indexing - shows Indexing instead of findings')", - "label": "no findings state: indexing - shows Indexing instead of findings", + "id": "searchbar", + "rawLine": " describe('SearchBar', () => {", + "line": " describe('SearchBar')", + "label": "SearchBar", "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "set-search-query", + "rawLine": " it('set search query', async () => {", + "line": " it('set search query')", + "label": "set search query", + "indent": 4, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "no-findings-state:-index-timeout---shows-indextimeout-instead-of-findings", - "rawLine": " it('no findings state: index-timeout - shows IndexTimeout instead of findings', () => {", - "line": " it('no findings state: index-timeout - shows IndexTimeout instead of findings')", - "label": "no findings state: index-timeout - shows IndexTimeout instead of findings", - "indent": 2, + "id": "renders-no-results-message-and-reset-button-when-search-query-does-not-match", + "rawLine": " it('renders no results message and reset button when search query does not match', async () => {", + "line": " it('renders no results message and reset button when search query does not match')", + "label": "renders no results message and reset button when search query does not match", + "indent": 4, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "no-findings-state:-unprivileged---shows-unprivileged-instead-of-findings", - "rawLine": " it('no findings state: unprivileged - shows Unprivileged instead of findings', () => {", - "line": " it('no findings state: unprivileged - shows Unprivileged instead of findings')", - "label": "no findings state: unprivileged - shows Unprivileged instead of findings", - "indent": 2, + "id": "add-filter", + "rawLine": " it('add filter', async () => {", + "line": " it('add filter')", + "label": "add filter", + "indent": 4, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "renders-integrations-installation-prompt-if-integration-is-not-installed", - "rawLine": " it('renders integrations installation prompt if integration is not installed', async () => {", - "line": " it('renders integrations installation prompt if integration is not installed')", - "label": "renders integrations installation prompt if integration is not installed", + "id": "remove-filter", + "rawLine": " it('remove filter', async () => {", + "line": " it('remove filter')", + "label": "remove filter", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "distributionbar", + "rawLine": " describe('DistributionBar', () => {", + "line": " describe('DistributionBar')", + "label": "DistributionBar", "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "renders-the-distribution-bar", + "rawLine": " it('renders the distribution bar', async () => {", + "line": " it('renders the distribution bar')", + "label": "renders the distribution bar", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "filters-by-passed-findings-when-clicking-on-the-passed-findings-button", + "rawLine": " it('filters by passed findings when clicking on the passed findings button', async () => {", + "line": " it('filters by passed findings when clicking on the passed findings button')", + "label": "filters by passed findings when clicking on the passed findings button", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "filters-by-failed-findings-when-clicking-on-the-failed-findings-button", + "rawLine": " it('filters by failed findings when clicking on the failed findings button', async () => {", + "line": " it('filters by failed findings when clicking on the failed findings button')", + "label": "filters by failed findings when clicking on the failed findings button", + "indent": 4, "type": "it", "isSkipped": false, "isTodo": false @@ -7013,54 +7131,108 @@ "isTodo": false, "children": [ { - "id": "no-findings-state:-not-deployed---shows-notdeployed-instead-of-findings", - "rawLine": " it('no findings state: not-deployed - shows NotDeployed instead of findings', () => {", - "line": " it('no findings state: not-deployed - shows NotDeployed instead of findings')", - "label": "no findings state: not-deployed - shows NotDeployed instead of findings", - "indent": 2, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "no-findings-state:-indexing---shows-indexing-instead-of-findings", - "rawLine": " it('no findings state: indexing - shows Indexing instead of findings', () => {", - "line": " it('no findings state: indexing - shows Indexing instead of findings')", - "label": "no findings state: indexing - shows Indexing instead of findings", - "indent": 2, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "no-findings-state:-index-timeout---shows-indextimeout-instead-of-findings", - "rawLine": " it('no findings state: index-timeout - shows IndexTimeout instead of findings', () => {", - "line": " it('no findings state: index-timeout - shows IndexTimeout instead of findings')", - "label": "no findings state: index-timeout - shows IndexTimeout instead of findings", + "id": "renders-integrations-installation-prompt-if-integration-is-not-installed", + "rawLine": " it('renders integrations installation prompt if integration is not installed', async () => {", + "line": " it('renders integrations installation prompt if integration is not installed')", + "label": "renders integrations installation prompt if integration is not installed", "indent": 2, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "no-findings-state:-unprivileged---shows-unprivileged-instead-of-findings", - "rawLine": " it('no findings state: unprivileged - shows Unprivileged instead of findings', () => {", - "line": " it('no findings state: unprivileged - shows Unprivileged instead of findings')", - "label": "no findings state: unprivileged - shows Unprivileged instead of findings", + "id": "searchbar", + "rawLine": " describe('SearchBar', () => {", + "line": " describe('SearchBar')", + "label": "SearchBar", "indent": 2, - "type": "it", + "type": "describe", "isSkipped": false, - "isTodo": false + "isTodo": false, + "children": [ + { + "id": "set-search-query", + "rawLine": " it('set search query', async () => {", + "line": " it('set search query')", + "label": "set search query", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "renders-no-results-message-and-reset-button-when-search-query-does-not-match", + "rawLine": " it('renders no results message and reset button when search query does not match', async () => {", + "line": " it('renders no results message and reset button when search query does not match')", + "label": "renders no results message and reset button when search query does not match", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "add-filter", + "rawLine": " it('add filter', async () => {", + "line": " it('add filter')", + "label": "add filter", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "remove-filter", + "rawLine": " it('remove filter', async () => {", + "line": " it('remove filter')", + "label": "remove filter", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] }, { - "id": "renders-integrations-installation-prompt-if-integration-is-not-installed", - "rawLine": " it('renders integrations installation prompt if integration is not installed', async () => {", - "line": " it('renders integrations installation prompt if integration is not installed')", - "label": "renders integrations installation prompt if integration is not installed", + "id": "distributionbar", + "rawLine": " describe('DistributionBar', () => {", + "line": " describe('DistributionBar')", + "label": "DistributionBar", "indent": 2, - "type": "it", + "type": "describe", "isSkipped": false, - "isTodo": false + "isTodo": false, + "children": [ + { + "id": "renders-the-distribution-bar", + "rawLine": " it('renders the distribution bar', async () => {", + "line": " it('renders the distribution bar')", + "label": "renders the distribution bar", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "filters-by-passed-findings-when-clicking-on-the-passed-findings-button", + "rawLine": " it('filters by passed findings when clicking on the passed findings button', async () => {", + "line": " it('filters by passed findings when clicking on the passed findings button')", + "label": "filters by passed findings when clicking on the passed findings button", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "filters-by-failed-findings-when-clicking-on-the-failed-findings-button", + "rawLine": " it('filters by failed findings when clicking on the failed findings button', async () => {", + "line": " it('filters by failed findings when clicking on the failed findings button')", + "label": "filters by failed findings when clicking on the failed findings button", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] } ] } @@ -7078,10 +7250,17 @@ " describe('Overview Tab')", " it('details and remediation accordions are open')", " it('displays text details summary info')", + " it('displays missing info callout when data source is not CSP')", + " it('does not display missing info callout when data source is CSP')", " describe('Rule Tab')", " it('displays rule text details')", + " it('displays missing info callout when data source is not CSP')", + " it('does not display missing info callout when data source is CSP')", " describe('Table Tab')", " it('displays resource name and id')", + " it('does not display missing info callout for 3Ps')", + " describe('JSON Tab')", + " it('does not display missing info callout for 3Ps')", " it('should allow pagination with next')", " it('should allow pagination with previous')" ], @@ -7126,6 +7305,26 @@ "isSkipped": false, "isTodo": false }, + { + "id": "displays-missing-info-callout-when-data-source-is-not-csp", + "rawLine": " it('displays missing info callout when data source is not CSP', () => {", + "line": " it('displays missing info callout when data source is not CSP')", + "label": "displays missing info callout when data source is not CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "does-not-display-missing-info-callout-when-data-source-is-csp", + "rawLine": " it('does not display missing info callout when data source is CSP', () => {", + "line": " it('does not display missing info callout when data source is CSP')", + "label": "does not display missing info callout when data source is CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, { "id": "rule-tab", "rawLine": " describe('Rule Tab', () => {", @@ -7146,6 +7345,26 @@ "isSkipped": false, "isTodo": false }, + { + "id": "displays-missing-info-callout-when-data-source-is-not-csp", + "rawLine": " it('displays missing info callout when data source is not CSP', () => {", + "line": " it('displays missing info callout when data source is not CSP')", + "label": "displays missing info callout when data source is not CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "does-not-display-missing-info-callout-when-data-source-is-csp", + "rawLine": " it('does not display missing info callout when data source is CSP', () => {", + "line": " it('does not display missing info callout when data source is CSP')", + "label": "does not display missing info callout when data source is CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, { "id": "table-tab", "rawLine": " describe('Table Tab', () => {", @@ -7166,6 +7385,36 @@ "isSkipped": false, "isTodo": false }, + { + "id": "does-not-display-missing-info-callout-for-3ps", + "rawLine": " it('does not display missing info callout for 3Ps', () => {", + "line": " it('does not display missing info callout for 3Ps')", + "label": "does not display missing info callout for 3Ps", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "json-tab", + "rawLine": " describe('JSON Tab', () => {", + "line": " describe('JSON Tab')", + "label": "JSON Tab", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "does-not-display-missing-info-callout-for-3ps", + "rawLine": " it('does not display missing info callout for 3Ps', () => {", + "line": " it('does not display missing info callout for 3Ps')", + "label": "does not display missing info callout for 3Ps", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, { "id": "should-allow-pagination-with-next", "rawLine": " it('should allow pagination with next', async () => {", @@ -7227,6 +7476,26 @@ "type": "it", "isSkipped": false, "isTodo": false + }, + { + "id": "displays-missing-info-callout-when-data-source-is-not-csp", + "rawLine": " it('displays missing info callout when data source is not CSP', () => {", + "line": " it('displays missing info callout when data source is not CSP')", + "label": "displays missing info callout when data source is not CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "does-not-display-missing-info-callout-when-data-source-is-csp", + "rawLine": " it('does not display missing info callout when data source is CSP', () => {", + "line": " it('does not display missing info callout when data source is CSP')", + "label": "does not display missing info callout when data source is CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false } ] }, @@ -7249,6 +7518,26 @@ "type": "it", "isSkipped": false, "isTodo": false + }, + { + "id": "displays-missing-info-callout-when-data-source-is-not-csp", + "rawLine": " it('displays missing info callout when data source is not CSP', () => {", + "line": " it('displays missing info callout when data source is not CSP')", + "label": "displays missing info callout when data source is not CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "does-not-display-missing-info-callout-when-data-source-is-csp", + "rawLine": " it('does not display missing info callout when data source is CSP', () => {", + "line": " it('does not display missing info callout when data source is CSP')", + "label": "does not display missing info callout when data source is CSP", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false } ] }, @@ -7271,17 +7560,49 @@ "type": "it", "isSkipped": false, "isTodo": false + }, + { + "id": "does-not-display-missing-info-callout-for-3ps", + "rawLine": " it('does not display missing info callout for 3Ps', () => {", + "line": " it('does not display missing info callout for 3Ps')", + "label": "does not display missing info callout for 3Ps", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false } ] }, { - "id": "should-allow-pagination-with-next", - "rawLine": " it('should allow pagination with next', async () => {", - "line": " it('should allow pagination with next')", - "label": "should allow pagination with next", - "indent": 2, - "type": "it", - "isSkipped": false, + "id": "json-tab", + "rawLine": " describe('JSON Tab', () => {", + "line": " describe('JSON Tab')", + "label": "JSON Tab", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "does-not-display-missing-info-callout-for-3ps", + "rawLine": " it('does not display missing info callout for 3Ps', () => {", + "line": " it('does not display missing info callout for 3Ps')", + "label": "does not display missing info callout for 3Ps", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] + }, + { + "id": "should-allow-pagination-with-next", + "rawLine": " it('should allow pagination with next', async () => {", + "line": " it('should allow pagination with next')", + "label": "should allow pagination with next", + "indent": 2, + "type": "it", + "isSkipped": false, "isTodo": false }, { @@ -11522,44 +11843,88 @@ ] }, { - "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts", - "fileName": "compliance_dashboard.ts", + "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts", + "fileName": "cis_integration_aws.ts", "directory": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture", "tags": [ "FTR", "SERVERLESS" ], "lines": [ - " describe('Cloud Posture Dashboard Page')", - " describe('Kubernetes Dashboard')", - " it('displays accurate summary compliance score')" + " describe('Agentless CIS Integration Page')", + " describe('Agentless CIS_AWS Single Account Launch Cloud formation')", + " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`)", + " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`)", + " describe('Agentless CIS_AWS ORG Account Launch Cloud formation')", + " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`)", + " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`)" ], "testSuits": [ { - "id": "cloud-posture-dashboard-page", - "rawLine": " describe('Cloud Posture Dashboard Page', function () {", - "line": " describe('Cloud Posture Dashboard Page')", - "label": "Cloud Posture Dashboard Page", + "id": "agentless-cis-integration-page", + "rawLine": " describe('Agentless CIS Integration Page', function () {", + "line": " describe('Agentless CIS Integration Page')", + "label": "Agentless CIS Integration Page", "indent": 2, "type": "describe", "isSkipped": false, "isTodo": false }, { - "id": "kubernetes-dashboard", - "rawLine": " describe('Kubernetes Dashboard', () => {", - "line": " describe('Kubernetes Dashboard')", - "label": "Kubernetes Dashboard", + "id": "agentless-cis_aws-single-account-launch-cloud-formation", + "rawLine": " describe('Agentless CIS_AWS Single Account Launch Cloud formation', () => {", + "line": " describe('Agentless CIS_AWS Single Account Launch Cloud formation')", + "label": "Agentless CIS_AWS Single Account Launch Cloud formation", "indent": 4, "type": "describe", "isSkipped": false, "isTodo": false }, { - "id": "displays-accurate-summary-compliance-score", - "rawLine": " it('displays accurate summary compliance score', async () => {", - "line": " it('displays accurate summary compliance score')", - "label": "displays accurate summary compliance score", + "id": "should-show-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-direct-access-keys-and-package-version-is-${cloud_credentials_package_version}", + "rawLine": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`, async () => {", + "line": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`)", + "label": "should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-hide-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-temporary-keys-and-package-version-is-less-than-${previouspackageversion}", + "rawLine": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`, async () => {", + "line": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`)", + "label": "should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "agentless-cis_aws-org-account-launch-cloud-formation", + "rawLine": " describe('Agentless CIS_AWS ORG Account Launch Cloud formation', () => {", + "line": " describe('Agentless CIS_AWS ORG Account Launch Cloud formation')", + "label": "Agentless CIS_AWS ORG Account Launch Cloud formation", + "indent": 4, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-show-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-direct-access-keys-and-package-version-is-${cloud_credentials_package_version}", + "rawLine": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`, async () => {", + "line": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`)", + "label": "should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-hide-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-temporary-keys-and-package-version-is-less-than-${previouspackageversion}", + "rawLine": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`, async () => {", + "line": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`)", + "label": "should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}", "indent": 6, "type": "it", "isSkipped": false, @@ -11568,30 +11933,72 @@ ], "tree": [ { - "id": "cloud-posture-dashboard-page", - "rawLine": " describe('Cloud Posture Dashboard Page', function () {", - "line": " describe('Cloud Posture Dashboard Page')", - "label": "Cloud Posture Dashboard Page", + "id": "agentless-cis-integration-page", + "rawLine": " describe('Agentless CIS Integration Page', function () {", + "line": " describe('Agentless CIS Integration Page')", + "label": "Agentless CIS Integration Page", "indent": 2, "type": "describe", "isSkipped": false, "isTodo": false, "children": [ { - "id": "kubernetes-dashboard", - "rawLine": " describe('Kubernetes Dashboard', () => {", - "line": " describe('Kubernetes Dashboard')", - "label": "Kubernetes Dashboard", + "id": "agentless-cis_aws-single-account-launch-cloud-formation", + "rawLine": " describe('Agentless CIS_AWS Single Account Launch Cloud formation', () => {", + "line": " describe('Agentless CIS_AWS Single Account Launch Cloud formation')", + "label": "Agentless CIS_AWS Single Account Launch Cloud formation", "indent": 4, "type": "describe", "isSkipped": false, "isTodo": false, "children": [ { - "id": "displays-accurate-summary-compliance-score", - "rawLine": " it('displays accurate summary compliance score', async () => {", - "line": " it('displays accurate summary compliance score')", - "label": "displays accurate summary compliance score", + "id": "should-show-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-direct-access-keys-and-package-version-is-${cloud_credentials_package_version}", + "rawLine": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`, async () => {", + "line": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`)", + "label": "should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-hide-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-temporary-keys-and-package-version-is-less-than-${previouspackageversion}", + "rawLine": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`, async () => {", + "line": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`)", + "label": "should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] + }, + { + "id": "agentless-cis_aws-org-account-launch-cloud-formation", + "rawLine": " describe('Agentless CIS_AWS ORG Account Launch Cloud formation', () => {", + "line": " describe('Agentless CIS_AWS ORG Account Launch Cloud formation')", + "label": "Agentless CIS_AWS ORG Account Launch Cloud formation", + "indent": 4, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "should-show-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-direct-access-keys-and-package-version-is-${cloud_credentials_package_version}", + "rawLine": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`, async () => {", + "line": " it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`)", + "label": "should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-hide-cis_aws-launch-cloud-formation-button-when-credentials-selector-is-temporary-keys-and-package-version-is-less-than-${previouspackageversion}", + "rawLine": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`, async () => {", + "line": " it(`should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}`)", + "label": "should hide CIS_AWS Launch Cloud formation button when credentials selector is temporary keys and package version is less than ${previousPackageVersion}", "indent": 6, "type": "it", "isSkipped": false, @@ -11604,103 +12011,89 @@ ] }, { - "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/index.ts", - "fileName": "index.ts", + "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts", + "fileName": "cis_integration_gcp.ts", "directory": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture", "tags": [ "FTR", "SERVERLESS" ], "lines": [ - " describe('cloud_security_posture')" + " describe('Agentless CIS Integration Page')", + " describe('Agentless CIS_GCP Single Account Launch Cloud shell')", + " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`)", + " it(`should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}`)", + " describe('Agentless CIS_GCP ORG Account Launch Cloud Shell')", + " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`)", + " it(`should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}`)" ], "testSuits": [ { - "id": "cloud_security_posture", - "rawLine": " describe('cloud_security_posture', function () {", - "line": " describe('cloud_security_posture')", - "label": "cloud_security_posture", + "id": "agentless-cis-integration-page", + "rawLine": " describe('Agentless CIS Integration Page', function () {", + "line": " describe('Agentless CIS Integration Page')", + "label": "Agentless CIS Integration Page", "indent": 2, "type": "describe", "isSkipped": false, "isTodo": false - } - ], - "tree": [ + }, { - "id": "cloud_security_posture", - "rawLine": " describe('cloud_security_posture', function () {", - "line": " describe('cloud_security_posture')", - "label": "cloud_security_posture", - "indent": 2, + "id": "agentless-cis_gcp-single-account-launch-cloud-shell", + "rawLine": " describe('Agentless CIS_GCP Single Account Launch Cloud shell', () => {", + "line": " describe('Agentless CIS_GCP Single Account Launch Cloud shell')", + "label": "Agentless CIS_GCP Single Account Launch Cloud shell", + "indent": 4, "type": "describe", "isSkipped": false, "isTodo": false - } - ] - }, - { - "filePath": "x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v1.ts", - "fileName": "v1.ts", - "directory": "x-pack/test/api_integration/apis/cloud_security_posture", - "tags": [ - "FTR", - "API INTEGRATION" - ], - "lines": [ - " describe('GET /internal/cloud_security_posture/benchmark')", - " it(`Should return non-empty array filled with Rules if user has CSP integrations`)", - " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", - " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", - " it(`Should return empty array when we set page to be above the last page number`)" - ], - "testSuits": [ + }, { - "id": "get-/internal/cloud_security_posture/benchmark", - "rawLine": " describe('GET /internal/cloud_security_posture/benchmark', () => {", - "line": " describe('GET /internal/cloud_security_posture/benchmark')", - "label": "GET /internal/cloud_security_posture/benchmark", - "indent": 2, - "type": "describe", + "id": "should-show-cis_gcp-launch-cloud-shell-button-when-package-version-is-${agentlessprereleaseversion}", + "rawLine": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`, async () => {", + "line": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`)", + "label": "should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}", + "indent": 6, + "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-return-non-empty-array-filled-with-rules-if-user-has-csp-integrations", - "rawLine": " it(`Should return non-empty array filled with Rules if user has CSP integrations`, async () => {", - "line": " it(`Should return non-empty array filled with Rules if user has CSP integrations`)", - "label": "Should return non-empty array filled with Rules if user has CSP integrations", - "indent": 4, + "id": "should-hide-cis_gcp-launch-cloud-shell-button-when-package-version-is-less-than-${agentlessprereleaseversion}", + "rawLine": " it(`should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}`, async () => {", + "line": " it(`should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}`)", + "label": "should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}", + "indent": 6, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", - "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", - "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", - "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", + "id": "agentless-cis_gcp-org-account-launch-cloud-shell", + "rawLine": " describe('Agentless CIS_GCP ORG Account Launch Cloud Shell', () => {", + "line": " describe('Agentless CIS_GCP ORG Account Launch Cloud Shell')", + "label": "Agentless CIS_GCP ORG Account Launch Cloud Shell", "indent": 4, - "type": "it", + "type": "describe", "isSkipped": false, "isTodo": false }, { - "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", - "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", - "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", - "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", - "indent": 4, + "id": "should-show-cis_gcp-launch-cloud-shell-button-when-package-version-is-${agentlessprereleaseversion}", + "rawLine": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`, async () => {", + "line": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`)", + "label": "should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}", + "indent": 6, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "should-return-empty-array-when-we-set-page-to-be-above-the-last-page-number", - "rawLine": " it(`Should return empty array when we set page to be above the last page number`, async () => {", - "line": " it(`Should return empty array when we set page to be above the last page number`)", - "label": "Should return empty array when we set page to be above the last page number", - "indent": 4, + "id": "should-hide-cis_gcp-launch-cloud-shell-button-when-package-version-is-${previouspackageversion}", + "rawLine": " it(`should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}`, async () => {", + "line": " it(`should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}`)", + "label": "should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}", + "indent": 6, "type": "it", "isSkipped": false, "isTodo": false @@ -11708,76 +12101,494 @@ ], "tree": [ { - "id": "get-/internal/cloud_security_posture/benchmark", - "rawLine": " describe('GET /internal/cloud_security_posture/benchmark', () => {", - "line": " describe('GET /internal/cloud_security_posture/benchmark')", - "label": "GET /internal/cloud_security_posture/benchmark", + "id": "agentless-cis-integration-page", + "rawLine": " describe('Agentless CIS Integration Page', function () {", + "line": " describe('Agentless CIS Integration Page')", + "label": "Agentless CIS Integration Page", "indent": 2, "type": "describe", "isSkipped": false, "isTodo": false, "children": [ { - "id": "should-return-non-empty-array-filled-with-rules-if-user-has-csp-integrations", - "rawLine": " it(`Should return non-empty array filled with Rules if user has CSP integrations`, async () => {", - "line": " it(`Should return non-empty array filled with Rules if user has CSP integrations`)", - "label": "Should return non-empty array filled with Rules if user has CSP integrations", - "indent": 4, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", - "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", - "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", - "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", - "indent": 4, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", - "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", - "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", - "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", + "id": "agentless-cis_gcp-single-account-launch-cloud-shell", + "rawLine": " describe('Agentless CIS_GCP Single Account Launch Cloud shell', () => {", + "line": " describe('Agentless CIS_GCP Single Account Launch Cloud shell')", + "label": "Agentless CIS_GCP Single Account Launch Cloud shell", "indent": 4, - "type": "it", + "type": "describe", "isSkipped": false, - "isTodo": false + "isTodo": false, + "children": [ + { + "id": "should-show-cis_gcp-launch-cloud-shell-button-when-package-version-is-${agentlessprereleaseversion}", + "rawLine": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`, async () => {", + "line": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`)", + "label": "should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-hide-cis_gcp-launch-cloud-shell-button-when-package-version-is-less-than-${agentlessprereleaseversion}", + "rawLine": " it(`should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}`, async () => {", + "line": " it(`should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}`)", + "label": "should hide CIS_GCP Launch Cloud Shell button when package version is less than ${agentlessPreReleaseVersion}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] }, { - "id": "should-return-empty-array-when-we-set-page-to-be-above-the-last-page-number", - "rawLine": " it(`Should return empty array when we set page to be above the last page number`, async () => {", - "line": " it(`Should return empty array when we set page to be above the last page number`)", - "label": "Should return empty array when we set page to be above the last page number", + "id": "agentless-cis_gcp-org-account-launch-cloud-shell", + "rawLine": " describe('Agentless CIS_GCP ORG Account Launch Cloud Shell', () => {", + "line": " describe('Agentless CIS_GCP ORG Account Launch Cloud Shell')", + "label": "Agentless CIS_GCP ORG Account Launch Cloud Shell", "indent": 4, - "type": "it", + "type": "describe", "isSkipped": false, - "isTodo": false + "isTodo": false, + "children": [ + { + "id": "should-show-cis_gcp-launch-cloud-shell-button-when-package-version-is-${agentlessprereleaseversion}", + "rawLine": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`, async () => {", + "line": " it(`should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}`)", + "label": "should show CIS_GCP Launch Cloud Shell button when package version is ${agentlessPreReleaseVersion}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-hide-cis_gcp-launch-cloud-shell-button-when-package-version-is-${previouspackageversion}", + "rawLine": " it(`should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}`, async () => {", + "line": " it(`should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}`)", + "label": "should hide CIS_GCP Launch Cloud shell button when package version is ${previousPackageVersion}", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] } ] } ] }, { - "filePath": "x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v2.ts", - "fileName": "v2.ts", - "directory": "x-pack/test/api_integration/apis/cloud_security_posture", + "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/index.ts", + "fileName": "index.ts", + "directory": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture", "tags": [ "FTR", - "API INTEGRATION" + "SERVERLESS" ], "lines": [ - " describe('GET /internal/cloud_security_posture/benchmark')", - " it(`Should return all benchmarks if user has CSP integrations`)" + " describe('cloud_security_posture')" ], "testSuits": [ { - "id": "get-/internal/cloud_security_posture/benchmark", - "rawLine": " describe('GET /internal/cloud_security_posture/benchmark', () => {", - "line": " describe('GET /internal/cloud_security_posture/benchmark')", + "id": "cloud_security_posture", + "rawLine": " describe('cloud_security_posture', function () {", + "line": " describe('cloud_security_posture')", + "label": "cloud_security_posture", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "cloud_security_posture", + "rawLine": " describe('cloud_security_posture', function () {", + "line": " describe('cloud_security_posture')", + "label": "cloud_security_posture", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + } + ] + }, + { + "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/compliance_dashboard.ts", + "fileName": "compliance_dashboard.ts", + "directory": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture", + "tags": [ + "FTR", + "SERVERLESS" + ], + "lines": [ + " describe('Cloud Posture Dashboard Page')", + " describe('Kubernetes Dashboard')", + " it('displays accurate summary compliance score')" + ], + "testSuits": [ + { + "id": "cloud-posture-dashboard-page", + "rawLine": " describe('Cloud Posture Dashboard Page', function () {", + "line": " describe('Cloud Posture Dashboard Page')", + "label": "Cloud Posture Dashboard Page", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "kubernetes-dashboard", + "rawLine": " describe('Kubernetes Dashboard', () => {", + "line": " describe('Kubernetes Dashboard')", + "label": "Kubernetes Dashboard", + "indent": 4, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "displays-accurate-summary-compliance-score", + "rawLine": " it('displays accurate summary compliance score', async () => {", + "line": " it('displays accurate summary compliance score')", + "label": "displays accurate summary compliance score", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "cloud-posture-dashboard-page", + "rawLine": " describe('Cloud Posture Dashboard Page', function () {", + "line": " describe('Cloud Posture Dashboard Page')", + "label": "Cloud Posture Dashboard Page", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "kubernetes-dashboard", + "rawLine": " describe('Kubernetes Dashboard', () => {", + "line": " describe('Kubernetes Dashboard')", + "label": "Kubernetes Dashboard", + "indent": 4, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "displays-accurate-summary-compliance-score", + "rawLine": " it('displays accurate summary compliance score', async () => {", + "line": " it('displays accurate summary compliance score')", + "label": "displays accurate summary compliance score", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] + } + ] + } + ] + }, + { + "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.essentials.ts", + "fileName": "csp_integrations_form.essentials.ts", + "directory": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture", + "tags": [ + "FTR", + "SERVERLESS" + ], + "lines": [ + " describe('[Essentials PLI] Test Cloud Security Posture Integrations on Serverless')", + " it('[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI')" + ], + "testSuits": [ + { + "id": "[essentials-pli]-test-cloud-security-posture-integrations-on-serverless", + "rawLine": " describe('[Essentials PLI] Test Cloud Security Posture Integrations on Serverless', function () {", + "line": " describe('[Essentials PLI] Test Cloud Security Posture Integrations on Serverless')", + "label": "[Essentials PLI] Test Cloud Security Posture Integrations on Serverless", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "[essentials-pli]-integration-installation-form-should-be-available-with-essentials-or-complete-pli", + "rawLine": " it('[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI', async () => {", + "line": " it('[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI')", + "label": "[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "[essentials-pli]-test-cloud-security-posture-integrations-on-serverless", + "rawLine": " describe('[Essentials PLI] Test Cloud Security Posture Integrations on Serverless', function () {", + "line": " describe('[Essentials PLI] Test Cloud Security Posture Integrations on Serverless')", + "label": "[Essentials PLI] Test Cloud Security Posture Integrations on Serverless", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "[essentials-pli]-integration-installation-form-should-be-available-with-essentials-or-complete-pli", + "rawLine": " it('[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI', async () => {", + "line": " it('[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI')", + "label": "[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] + } + ] + }, + { + "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.ts", + "fileName": "csp_integrations_form.ts", + "directory": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture", + "tags": [ + "FTR", + "SERVERLESS" + ], + "lines": [ + " describe('Test Cloud Security Posture Integrations on Serverless')", + " it('Integration installation form should not be available without required PLI')" + ], + "testSuits": [ + { + "id": "test-cloud-security-posture-integrations-on-serverless", + "rawLine": " describe('Test Cloud Security Posture Integrations on Serverless', function () {", + "line": " describe('Test Cloud Security Posture Integrations on Serverless')", + "label": "Test Cloud Security Posture Integrations on Serverless", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "integration-installation-form-should-not-be-available-without-required-pli", + "rawLine": " it('Integration installation form should not be available without required PLI', async () => {", + "line": " it('Integration installation form should not be available without required PLI')", + "label": "Integration installation form should not be available without required PLI", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "test-cloud-security-posture-integrations-on-serverless", + "rawLine": " describe('Test Cloud Security Posture Integrations on Serverless', function () {", + "line": " describe('Test Cloud Security Posture Integrations on Serverless')", + "label": "Test Cloud Security Posture Integrations on Serverless", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "integration-installation-form-should-not-be-available-without-required-pli", + "rawLine": " it('Integration installation form should not be available without required PLI', async () => {", + "line": " it('Integration installation form should not be available without required PLI')", + "label": "Integration installation form should not be available without required PLI", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] + } + ] + }, + { + "filePath": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/index.ts", + "fileName": "index.ts", + "directory": "x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture", + "tags": [ + "FTR", + "SERVERLESS" + ], + "lines": [ + " describe('cloud_security_posture')" + ], + "testSuits": [ + { + "id": "cloud_security_posture", + "rawLine": " describe('cloud_security_posture', function () {", + "line": " describe('cloud_security_posture')", + "label": "cloud_security_posture", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "cloud_security_posture", + "rawLine": " describe('cloud_security_posture', function () {", + "line": " describe('cloud_security_posture')", + "label": "cloud_security_posture", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + } + ] + }, + { + "filePath": "x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v1.ts", + "fileName": "v1.ts", + "directory": "x-pack/test/api_integration/apis/cloud_security_posture", + "tags": [ + "FTR", + "API INTEGRATION" + ], + "lines": [ + " describe('GET /internal/cloud_security_posture/benchmark')", + " it(`Should return non-empty array filled with Rules if user has CSP integrations`)", + " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", + " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", + " it(`Should return empty array when we set page to be above the last page number`)" + ], + "testSuits": [ + { + "id": "get-/internal/cloud_security_posture/benchmark", + "rawLine": " describe('GET /internal/cloud_security_posture/benchmark', () => {", + "line": " describe('GET /internal/cloud_security_posture/benchmark')", + "label": "GET /internal/cloud_security_posture/benchmark", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-return-non-empty-array-filled-with-rules-if-user-has-csp-integrations", + "rawLine": " it(`Should return non-empty array filled with Rules if user has CSP integrations`, async () => {", + "line": " it(`Should return non-empty array filled with Rules if user has CSP integrations`)", + "label": "Should return non-empty array filled with Rules if user has CSP integrations", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", + "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", + "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", + "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", + "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", + "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", + "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-return-empty-array-when-we-set-page-to-be-above-the-last-page-number", + "rawLine": " it(`Should return empty array when we set page to be above the last page number`, async () => {", + "line": " it(`Should return empty array when we set page to be above the last page number`)", + "label": "Should return empty array when we set page to be above the last page number", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "get-/internal/cloud_security_posture/benchmark", + "rawLine": " describe('GET /internal/cloud_security_posture/benchmark', () => {", + "line": " describe('GET /internal/cloud_security_posture/benchmark')", + "label": "GET /internal/cloud_security_posture/benchmark", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "should-return-non-empty-array-filled-with-rules-if-user-has-csp-integrations", + "rawLine": " it(`Should return non-empty array filled with Rules if user has CSP integrations`, async () => {", + "line": " it(`Should return non-empty array filled with Rules if user has CSP integrations`)", + "label": "Should return non-empty array filled with Rules if user has CSP integrations", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", + "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", + "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", + "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-return-array-size-2-when-we-set-per-page-to-be-only-2-(total-element-is-still-3)", + "rawLine": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`, async () => {", + "line": " it(`Should return array size 2 when we set per page to be only 2 (total element is still 3)`)", + "label": "Should return array size 2 when we set per page to be only 2 (total element is still 3)", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "should-return-empty-array-when-we-set-page-to-be-above-the-last-page-number", + "rawLine": " it(`Should return empty array when we set page to be above the last page number`, async () => {", + "line": " it(`Should return empty array when we set page to be above the last page number`)", + "label": "Should return empty array when we set page to be above the last page number", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] + } + ] + }, + { + "filePath": "x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v2.ts", + "fileName": "v2.ts", + "directory": "x-pack/test/api_integration/apis/cloud_security_posture", + "tags": [ + "FTR", + "API INTEGRATION" + ], + "lines": [ + " describe('GET /internal/cloud_security_posture/benchmark')", + " it(`Should return all benchmarks if user has CSP integrations`)" + ], + "testSuits": [ + { + "id": "get-/internal/cloud_security_posture/benchmark", + "rawLine": " describe('GET /internal/cloud_security_posture/benchmark', () => {", + "line": " describe('GET /internal/cloud_security_posture/benchmark')", "label": "GET /internal/cloud_security_posture/benchmark", "indent": 2, "type": "describe", @@ -13163,11 +13974,10 @@ "directory": "x-pack/test/cloud_security_posture_api", "tags": [ "FTR", - "API INTEGRATION", - "HAS SKIP" + "API INTEGRATION" ], "lines": [ - " describe.skip('GET /internal/cloud_security_posture/benchmarks')", + " describe('GET /internal/cloud_security_posture/benchmarks')", " describe('Get Benchmark API')", " it('Verify cspm benchmark score is updated when muting rules')", " it('Verify kspm benchmark score is updated when muting rules')" @@ -13175,12 +13985,12 @@ "testSuits": [ { "id": "get-/internal/cloud_security_posture/benchmarks", - "rawLine": " describe.skip('GET /internal/cloud_security_posture/benchmarks', () => {", - "line": " describe.skip('GET /internal/cloud_security_posture/benchmarks')", + "rawLine": " describe('GET /internal/cloud_security_posture/benchmarks', () => {", + "line": " describe('GET /internal/cloud_security_posture/benchmarks')", "label": "GET /internal/cloud_security_posture/benchmarks", "indent": 2, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false }, { @@ -13217,12 +14027,12 @@ "tree": [ { "id": "get-/internal/cloud_security_posture/benchmarks", - "rawLine": " describe.skip('GET /internal/cloud_security_posture/benchmarks', () => {", - "line": " describe.skip('GET /internal/cloud_security_posture/benchmarks')", + "rawLine": " describe('GET /internal/cloud_security_posture/benchmarks', () => {", + "line": " describe('GET /internal/cloud_security_posture/benchmarks')", "label": "GET /internal/cloud_security_posture/benchmarks", "indent": 2, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false, "children": [ { @@ -13232,7 +14042,7 @@ "label": "Get Benchmark API", "indent": 4, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false, "children": [ { @@ -13242,7 +14052,7 @@ "label": "Verify cspm benchmark score is updated when muting rules", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, "isTodo": false }, { @@ -13252,7 +14062,7 @@ "label": "Verify kspm benchmark score is updated when muting rules", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, "isTodo": false } ] @@ -13913,26 +14723,190 @@ "line": " it('includes CSPM and KSPM findings')", "label": "includes CSPM and KSPM findings", "indent": 4, - "type": "it", + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "", + "rawLine": " it(`'includes only KSPM findings without posture_type'`, async () => {", + "line": " it(`'includes only KSPM findings without posture_type'`)", + "label": "", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "includes-kspm-findings-without-posture_type-and-cspm-findings-as-well", + "rawLine": " it('includes KSPM findings without posture_type and CSPM findings as well', async () => {", + "line": " it('includes KSPM findings without posture_type and CSPM findings as well')", + "label": "includes KSPM findings without posture_type and CSPM findings as well", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "verify-cloud_security_posture-telemetry-payloads", + "rawLine": " describe('Verify cloud_security_posture telemetry payloads', async () => {", + "line": " describe('Verify cloud_security_posture telemetry payloads')", + "label": "Verify cloud_security_posture telemetry payloads", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "includes-only-kspm-findings", + "rawLine": " it('includes only KSPM findings', async () => {", + "line": " it('includes only KSPM findings')", + "label": "includes only KSPM findings", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "includes-only-cspm-findings", + "rawLine": " it('includes only CSPM findings', async () => {", + "line": " it('includes only CSPM findings')", + "label": "includes only CSPM findings", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "includes-cspm-and-kspm-findings", + "rawLine": " it('includes CSPM and KSPM findings', async () => {", + "line": " it('includes CSPM and KSPM findings')", + "label": "includes CSPM and KSPM findings", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "", + "rawLine": " it(`'includes only KSPM findings without posture_type'`, async () => {", + "line": " it(`'includes only KSPM findings without posture_type'`)", + "label": "", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "includes-kspm-findings-without-posture_type-and-cspm-findings-as-well", + "rawLine": " it('includes KSPM findings without posture_type and CSPM findings as well', async () => {", + "line": " it('includes KSPM findings without posture_type and CSPM findings as well')", + "label": "includes KSPM findings without posture_type and CSPM findings as well", + "indent": 4, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] + } + ] + }, + { + "filePath": "x-pack/test/cloud_security_posture_functional/cloud_tests/basic_ui_sanity.ts", + "fileName": "basic_ui_sanity.ts", + "directory": "x-pack/test/cloud_security_posture_functional", + "tags": [ + "FTR" + ], + "lines": [ + " describe('Cloud Posture Dashboard Page')", + " describe('Cloud Dashboard')", + " it('displays compliance score greater than 40')", + " it('displays all compliance scores')", + " it('displays a number of resources evaluated greater than 3000')", + " describe('Kubernetes Dashboard')", + " it('displays compliance score greater than 80')", + " it('displays a number of resources evaluated greater than 150')" + ], + "testSuits": [ + { + "id": "cloud-posture-dashboard-page", + "rawLine": " describe('Cloud Posture Dashboard Page', function () {", + "line": " describe('Cloud Posture Dashboard Page')", + "label": "Cloud Posture Dashboard Page", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "cloud-dashboard", + "rawLine": " describe('Cloud Dashboard', () => {", + "line": " describe('Cloud Dashboard')", + "label": "Cloud Dashboard", + "indent": 4, + "type": "describe", + "isSkipped": false, + "isTodo": false + }, + { + "id": "displays-compliance-score-greater-than-40", + "rawLine": " it('displays compliance score greater than 40', async () => {", + "line": " it('displays compliance score greater than 40')", + "label": "displays compliance score greater than 40", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "displays-all-compliance-scores", + "rawLine": " it('displays all compliance scores', async () => {", + "line": " it('displays all compliance scores')", + "label": "displays all compliance scores", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "displays-a-number-of-resources-evaluated-greater-than-3000", + "rawLine": " it('displays a number of resources evaluated greater than 3000', async () => {", + "line": " it('displays a number of resources evaluated greater than 3000')", + "label": "displays a number of resources evaluated greater than 3000", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "kubernetes-dashboard", + "rawLine": " describe('Kubernetes Dashboard', () => {", + "line": " describe('Kubernetes Dashboard')", + "label": "Kubernetes Dashboard", + "indent": 4, + "type": "describe", "isSkipped": false, "isTodo": false }, { - "id": "", - "rawLine": " it(`'includes only KSPM findings without posture_type'`, async () => {", - "line": " it(`'includes only KSPM findings without posture_type'`)", - "label": "", - "indent": 4, + "id": "displays-compliance-score-greater-than-80", + "rawLine": " it('displays compliance score greater than 80', async () => {", + "line": " it('displays compliance score greater than 80')", + "label": "displays compliance score greater than 80", + "indent": 6, "type": "it", "isSkipped": false, "isTodo": false }, { - "id": "includes-kspm-findings-without-posture_type-and-cspm-findings-as-well", - "rawLine": " it('includes KSPM findings without posture_type and CSPM findings as well', async () => {", - "line": " it('includes KSPM findings without posture_type and CSPM findings as well')", - "label": "includes KSPM findings without posture_type and CSPM findings as well", - "indent": 4, + "id": "displays-a-number-of-resources-evaluated-greater-than-150", + "rawLine": " it('displays a number of resources evaluated greater than 150', async () => {", + "line": " it('displays a number of resources evaluated greater than 150')", + "label": "displays a number of resources evaluated greater than 150", + "indent": 6, "type": "it", "isSkipped": false, "isTodo": false @@ -13940,69 +14914,128 @@ ], "tree": [ { - "id": "verify-cloud_security_posture-telemetry-payloads", - "rawLine": " describe('Verify cloud_security_posture telemetry payloads', async () => {", - "line": " describe('Verify cloud_security_posture telemetry payloads')", - "label": "Verify cloud_security_posture telemetry payloads", + "id": "cloud-posture-dashboard-page", + "rawLine": " describe('Cloud Posture Dashboard Page', function () {", + "line": " describe('Cloud Posture Dashboard Page')", + "label": "Cloud Posture Dashboard Page", "indent": 2, "type": "describe", "isSkipped": false, "isTodo": false, "children": [ { - "id": "includes-only-kspm-findings", - "rawLine": " it('includes only KSPM findings', async () => {", - "line": " it('includes only KSPM findings')", - "label": "includes only KSPM findings", - "indent": 4, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "includes-only-cspm-findings", - "rawLine": " it('includes only CSPM findings', async () => {", - "line": " it('includes only CSPM findings')", - "label": "includes only CSPM findings", - "indent": 4, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "includes-cspm-and-kspm-findings", - "rawLine": " it('includes CSPM and KSPM findings', async () => {", - "line": " it('includes CSPM and KSPM findings')", - "label": "includes CSPM and KSPM findings", - "indent": 4, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "", - "rawLine": " it(`'includes only KSPM findings without posture_type'`, async () => {", - "line": " it(`'includes only KSPM findings without posture_type'`)", - "label": "", + "id": "cloud-dashboard", + "rawLine": " describe('Cloud Dashboard', () => {", + "line": " describe('Cloud Dashboard')", + "label": "Cloud Dashboard", "indent": 4, - "type": "it", + "type": "describe", "isSkipped": false, - "isTodo": false + "isTodo": false, + "children": [ + { + "id": "displays-compliance-score-greater-than-40", + "rawLine": " it('displays compliance score greater than 40', async () => {", + "line": " it('displays compliance score greater than 40')", + "label": "displays compliance score greater than 40", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "displays-all-compliance-scores", + "rawLine": " it('displays all compliance scores', async () => {", + "line": " it('displays all compliance scores')", + "label": "displays all compliance scores", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "displays-a-number-of-resources-evaluated-greater-than-3000", + "rawLine": " it('displays a number of resources evaluated greater than 3000', async () => {", + "line": " it('displays a number of resources evaluated greater than 3000')", + "label": "displays a number of resources evaluated greater than 3000", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] }, { - "id": "includes-kspm-findings-without-posture_type-and-cspm-findings-as-well", - "rawLine": " it('includes KSPM findings without posture_type and CSPM findings as well', async () => {", - "line": " it('includes KSPM findings without posture_type and CSPM findings as well')", - "label": "includes KSPM findings without posture_type and CSPM findings as well", + "id": "kubernetes-dashboard", + "rawLine": " describe('Kubernetes Dashboard', () => {", + "line": " describe('Kubernetes Dashboard')", + "label": "Kubernetes Dashboard", "indent": 4, - "type": "it", + "type": "describe", "isSkipped": false, - "isTodo": false + "isTodo": false, + "children": [ + { + "id": "displays-compliance-score-greater-than-80", + "rawLine": " it('displays compliance score greater than 80', async () => {", + "line": " it('displays compliance score greater than 80')", + "label": "displays compliance score greater than 80", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "displays-a-number-of-resources-evaluated-greater-than-150", + "rawLine": " it('displays a number of resources evaluated greater than 150', async () => {", + "line": " it('displays a number of resources evaluated greater than 150')", + "label": "displays a number of resources evaluated greater than 150", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + } + ] } ] } ] }, + { + "filePath": "x-pack/test/cloud_security_posture_functional/cloud_tests/index.ts", + "fileName": "index.ts", + "directory": "x-pack/test/cloud_security_posture_functional", + "tags": [ + "FTR" + ], + "lines": [ + " describe('Cloud Security Posture')" + ], + "testSuits": [ + { + "id": "cloud-security-posture", + "rawLine": " describe('Cloud Security Posture', function () {", + "line": " describe('Cloud Security Posture')", + "label": "Cloud Security Posture", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + } + ], + "tree": [ + { + "id": "cloud-security-posture", + "rawLine": " describe('Cloud Security Posture', function () {", + "line": " describe('Cloud Security Posture')", + "label": "Cloud Security Posture", + "indent": 2, + "type": "describe", + "isSkipped": false, + "isTodo": false + } + ] + }, { "filePath": "x-pack/test/cloud_security_posture_functional/pages/benchmark.ts", "fileName": "benchmark.ts", @@ -14153,11 +15186,12 @@ "fileName": "cis_integration_cnvm.ts", "directory": "x-pack/test/cloud_security_posture_functional", "tags": [ - "FTR" + "FTR", + "HAS SKIP" ], "lines": [ " describe('Test adding Cloud Security Posture Integrations CNVM')", - " describe('CNVM AWS')", + " describe.skip('CNVM AWS')", " it('Hyperlink on PostInstallation Modal should have the correct URL')", " it('On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ')", " it('Clicking on Launch CloudFormation on post intall modal should lead user to Cloud Formation page')" @@ -14175,12 +15209,12 @@ }, { "id": "cnvm-aws", - "rawLine": " describe('CNVM AWS', () => {", - "line": " describe('CNVM AWS')", + "rawLine": " describe.skip('CNVM AWS', () => {", + "line": " describe.skip('CNVM AWS')", "label": "CNVM AWS", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -14227,12 +15261,12 @@ "children": [ { "id": "cnvm-aws", - "rawLine": " describe('CNVM AWS', () => {", - "line": " describe('CNVM AWS')", + "rawLine": " describe.skip('CNVM AWS', () => {", + "line": " describe.skip('CNVM AWS')", "label": "CNVM AWS", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -14242,7 +15276,7 @@ "label": "Hyperlink on PostInstallation Modal should have the correct URL", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -14252,7 +15286,7 @@ "label": "On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -14262,7 +15296,7 @@ "label": "Clicking on Launch CloudFormation on post intall modal should lead user to Cloud Formation page", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] @@ -14276,11 +15310,12 @@ "fileName": "cis_integration_aws.ts", "directory": "x-pack/test/cloud_security_posture_functional", "tags": [ - "FTR" + "FTR", + "HAS SKIP" ], "lines": [ " describe('Test adding Cloud Security Posture Integrations CSPM AWS')", - " describe('CIS_AWS Organization Cloud Formation')", + " describe.skip('CIS_AWS Organization Cloud Formation')", " it('Initial form state, AWS Org account, and CloudFormation should be selected by default')", " it('Hyperlink on PostInstallation Modal should have the correct URL')", " it('On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ')", @@ -14317,12 +15352,12 @@ }, { "id": "cis_aws-organization-cloud-formation", - "rawLine": " describe('CIS_AWS Organization Cloud Formation', () => {", - "line": " describe('CIS_AWS Organization Cloud Formation')", + "rawLine": " describe.skip('CIS_AWS Organization Cloud Formation', () => {", + "line": " describe.skip('CIS_AWS Organization Cloud Formation')", "label": "CIS_AWS Organization Cloud Formation", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -14559,12 +15594,12 @@ "children": [ { "id": "cis_aws-organization-cloud-formation", - "rawLine": " describe('CIS_AWS Organization Cloud Formation', () => {", - "line": " describe('CIS_AWS Organization Cloud Formation')", + "rawLine": " describe.skip('CIS_AWS Organization Cloud Formation', () => {", + "line": " describe.skip('CIS_AWS Organization Cloud Formation')", "label": "CIS_AWS Organization Cloud Formation", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -14574,7 +15609,7 @@ "label": "Initial form state, AWS Org account, and CloudFormation should be selected by default", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -14584,7 +15619,7 @@ "label": "Hyperlink on PostInstallation Modal should have the correct URL", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -14594,7 +15629,7 @@ "label": "On Add Agent modal there should be modal that has Cloud Formation details as well as button that redirects user to Cloud formation page on AWS upon clicking them ", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -14604,7 +15639,7 @@ "label": "Clicking on Launch CloudFormation on post intall modal should lead user to Cloud Formation page", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] @@ -15205,10 +16240,11 @@ "fileName": "cis_integration_gcp.ts", "directory": "x-pack/test/cloud_security_posture_functional", "tags": [ - "FTR" + "FTR", + "HAS SKIP" ], "lines": [ - " describe('Test adding Cloud Security Posture Integrations CSPM GCP')", + " describe.skip('Test adding Cloud Security Posture Integrations CSPM GCP')", " describe('CIS_GCP Organization')", " it('Switch between Manual and Google cloud shell')", " it('Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are no Project ID or Organization ID provided, it should use default value')", @@ -15234,12 +16270,12 @@ "testSuits": [ { "id": "test-adding-cloud-security-posture-integrations-cspm-gcp", - "rawLine": " describe('Test adding Cloud Security Posture Integrations CSPM GCP', function () {", - "line": " describe('Test adding Cloud Security Posture Integrations CSPM GCP')", + "rawLine": " describe.skip('Test adding Cloud Security Posture Integrations CSPM GCP', function () {", + "line": " describe.skip('Test adding Cloud Security Posture Integrations CSPM GCP')", "label": "Test adding Cloud Security Posture Integrations CSPM GCP", "indent": 2, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15456,12 +16492,12 @@ "tree": [ { "id": "test-adding-cloud-security-posture-integrations-cspm-gcp", - "rawLine": " describe('Test adding Cloud Security Posture Integrations CSPM GCP', function () {", - "line": " describe('Test adding Cloud Security Posture Integrations CSPM GCP')", + "rawLine": " describe.skip('Test adding Cloud Security Posture Integrations CSPM GCP', function () {", + "line": " describe.skip('Test adding Cloud Security Posture Integrations CSPM GCP')", "label": "Test adding Cloud Security Posture Integrations CSPM GCP", "indent": 2, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15471,7 +16507,7 @@ "label": "CIS_GCP Organization", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15481,7 +16517,7 @@ "label": "Switch between Manual and Google cloud shell", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15491,7 +16527,7 @@ "label": "Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are no Project ID or Organization ID provided, it should use default value", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15501,7 +16537,7 @@ "label": "Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID or Organization ID provided, it should use that value", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15511,7 +16547,7 @@ "label": "Add Agent FLyout - Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID or Organization ID provided, it should use that value", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15521,7 +16557,7 @@ "label": "Organization ID field on cloud shell command should only be shown if user chose Google Cloud Shell, if user chose Single Account it shouldn not show up", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15531,7 +16567,7 @@ "label": "Hyperlink on PostInstallation Modal should have the correct URL", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15541,7 +16577,7 @@ "label": "Clicking on Launch CloudShell on post intall modal should lead user to CloudShell page", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] @@ -15553,7 +16589,7 @@ "label": "CIS_GCP Organization Credentials File", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15563,7 +16599,7 @@ "label": "CIS_GCP Organization Credentials File workflow", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] @@ -15575,7 +16611,7 @@ "label": "CIS_GCP Organization Credentials JSON", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15585,7 +16621,7 @@ "label": "CIS_GCP Organization Credentials JSON workflow", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] @@ -15597,7 +16633,7 @@ "label": "CIS_GCP Single", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15607,7 +16643,7 @@ "label": "Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are no Project ID, it should use default value", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15617,7 +16653,7 @@ "label": "Post Installation Google Cloud Shell modal pops up after user clicks on Save button when adding integration, when there are Project ID, it should use that value", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15627,7 +16663,7 @@ "label": "Add Agent FLyout - Organization ID field on cloud shell command should only be shown if user chose Google Cloud Shell, if user chose Single Account it shouldn not show up", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15637,7 +16673,7 @@ "label": "On add agent modal, if user chose Google Cloud Shell as their setup access; a google cloud shell modal should show up and clicking on the launch button will redirect user to Google cloud shell page", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15647,7 +16683,7 @@ "label": "Users are able to add CIS_GCP Integration with Manual settings using Credentials File", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15657,7 +16693,7 @@ "label": "Users are able to switch credentials_type from/to Credential JSON fields ", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15667,7 +16703,7 @@ "label": "Users are able to add CIS_GCP Integration with Manual settings using Credentials JSON", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15677,7 +16713,7 @@ "label": "Users are able to switch credentials_type from/to Credential File fields ", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] @@ -15691,17 +16727,18 @@ "fileName": "cis_integration_eks.ts", "directory": "x-pack/test/cloud_security_posture_functional", "tags": [ - "FTR" + "FTR", + "HAS SKIP" ], "lines": [ " describe('Test adding Cloud Security Posture Integrations KSPM EKS')", - " describe('KSPM EKS Assume Role')", + " describe.skip('KSPM EKS Assume Role')", " it('KSPM EKS Assume Role workflow')", - " describe('KSPM EKS Direct Access')", + " describe.skip('KSPM EKS Direct Access')", " it('KSPM EKS Direct Access Workflow')", - " describe('KSPM EKS Temporary Keys')", + " describe.skip('KSPM EKS Temporary Keys')", " it('KSPM EKS Temporary Keys Workflow')", - " describe('KSPM EKS Shared Credentials')", + " describe.skip('KSPM EKS Shared Credentials')", " it('KSPM EKS Shared Credentials Workflow')" ], "testSuits": [ @@ -15717,12 +16754,12 @@ }, { "id": "kspm-eks-assume-role", - "rawLine": " describe('KSPM EKS Assume Role', async () => {", - "line": " describe('KSPM EKS Assume Role')", + "rawLine": " describe.skip('KSPM EKS Assume Role', async () => {", + "line": " describe.skip('KSPM EKS Assume Role')", "label": "KSPM EKS Assume Role", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15737,12 +16774,12 @@ }, { "id": "kspm-eks-direct-access", - "rawLine": " describe('KSPM EKS Direct Access', async () => {", - "line": " describe('KSPM EKS Direct Access')", + "rawLine": " describe.skip('KSPM EKS Direct Access', async () => {", + "line": " describe.skip('KSPM EKS Direct Access')", "label": "KSPM EKS Direct Access", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15757,12 +16794,12 @@ }, { "id": "kspm-eks-temporary-keys", - "rawLine": " describe('KSPM EKS Temporary Keys', () => {", - "line": " describe('KSPM EKS Temporary Keys')", + "rawLine": " describe.skip('KSPM EKS Temporary Keys', () => {", + "line": " describe.skip('KSPM EKS Temporary Keys')", "label": "KSPM EKS Temporary Keys", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15777,12 +16814,12 @@ }, { "id": "kspm-eks-shared-credentials", - "rawLine": " describe('KSPM EKS Shared Credentials', () => {", - "line": " describe('KSPM EKS Shared Credentials')", + "rawLine": " describe.skip('KSPM EKS Shared Credentials', () => {", + "line": " describe.skip('KSPM EKS Shared Credentials')", "label": "KSPM EKS Shared Credentials", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false }, { @@ -15809,12 +16846,12 @@ "children": [ { "id": "kspm-eks-assume-role", - "rawLine": " describe('KSPM EKS Assume Role', async () => {", - "line": " describe('KSPM EKS Assume Role')", + "rawLine": " describe.skip('KSPM EKS Assume Role', async () => {", + "line": " describe.skip('KSPM EKS Assume Role')", "label": "KSPM EKS Assume Role", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15824,19 +16861,19 @@ "label": "KSPM EKS Assume Role workflow", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] }, { "id": "kspm-eks-direct-access", - "rawLine": " describe('KSPM EKS Direct Access', async () => {", - "line": " describe('KSPM EKS Direct Access')", + "rawLine": " describe.skip('KSPM EKS Direct Access', async () => {", + "line": " describe.skip('KSPM EKS Direct Access')", "label": "KSPM EKS Direct Access", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15846,19 +16883,19 @@ "label": "KSPM EKS Direct Access Workflow", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] }, { "id": "kspm-eks-temporary-keys", - "rawLine": " describe('KSPM EKS Temporary Keys', () => {", - "line": " describe('KSPM EKS Temporary Keys')", + "rawLine": " describe.skip('KSPM EKS Temporary Keys', () => {", + "line": " describe.skip('KSPM EKS Temporary Keys')", "label": "KSPM EKS Temporary Keys", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15868,19 +16905,19 @@ "label": "KSPM EKS Temporary Keys Workflow", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] }, { "id": "kspm-eks-shared-credentials", - "rawLine": " describe('KSPM EKS Shared Credentials', () => {", - "line": " describe('KSPM EKS Shared Credentials')", + "rawLine": " describe.skip('KSPM EKS Shared Credentials', () => {", + "line": " describe.skip('KSPM EKS Shared Credentials')", "label": "KSPM EKS Shared Credentials", "indent": 4, "type": "describe", - "isSkipped": false, + "isSkipped": true, "isTodo": false, "children": [ { @@ -15890,7 +16927,7 @@ "label": "KSPM EKS Shared Credentials Workflow", "indent": 6, "type": "it", - "isSkipped": false, + "isSkipped": true, "isTodo": false } ] @@ -16177,12 +17214,11 @@ "fileName": "findings_alerts.ts", "directory": "x-pack/test/cloud_security_posture_functional", "tags": [ - "FTR", - "HAS SKIP" + "FTR" ], "lines": [ " describe('Findings Page - Alerts')", - " describe.skip('Create detection rule')", + " describe('Create detection rule')", " it('Creates a detection rule from the Take Action button and navigates to rule page')", " it('Creates a detection rule from the Alerts section and navigates to rule page')", " describe('Rule details')", @@ -16204,12 +17240,12 @@ }, { "id": "create-detection-rule", - "rawLine": " describe.skip('Create detection rule', () => {", - "line": " describe.skip('Create detection rule')", + "rawLine": " describe('Create detection rule', () => {", + "line": " describe('Create detection rule')", "label": "Create detection rule", "indent": 4, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false }, { @@ -16296,12 +17332,12 @@ "children": [ { "id": "create-detection-rule", - "rawLine": " describe.skip('Create detection rule', () => {", - "line": " describe.skip('Create detection rule')", + "rawLine": " describe('Create detection rule', () => {", + "line": " describe('Create detection rule')", "label": "Create detection rule", "indent": 4, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false, "children": [ { @@ -16311,7 +17347,7 @@ "label": "Creates a detection rule from the Take Action button and navigates to rule page", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, "isTodo": false }, { @@ -16321,7 +17357,7 @@ "label": "Creates a detection rule from the Alerts section and navigates to rule page", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, "isTodo": false } ] @@ -16909,21 +17945,12 @@ "fileName": "findings.ts", "directory": "x-pack/test/cloud_security_posture_functional", "tags": [ - "FTR", - "HAS SKIP" + "FTR" ], "lines": [ " describe('Findings Page - DataTable')", - " describe.skip('SearchBar')", - " it('add filter')", - " it('remove filter')", - " it('set search query')", - " describe.skip('Table Sort')", + " describe('Table Sort')", " it('sorts by a column, should be case sensitive/insensitive depending on the column')", - " describe('DistributionBar')", - " it(`filters by ${type} findings`)", - " describe('DataTable features')", - " it('Edit data view field option is Enabled')", " describe('Findings - Fields selector')", " it('Add fields to the Findings DataTable')", " it('Remove fields from the Findings DataTable')", @@ -16945,54 +17972,14 @@ "isSkipped": false, "isTodo": false }, - { - "id": "searchbar", - "rawLine": " describe.skip('SearchBar', () => {", - "line": " describe.skip('SearchBar')", - "label": "SearchBar", - "indent": 4, - "type": "describe", - "isSkipped": true, - "isTodo": false - }, - { - "id": "add-filter", - "rawLine": " it('add filter', async () => {", - "line": " it('add filter')", - "label": "add filter", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "remove-filter", - "rawLine": " it('remove filter', async () => {", - "line": " it('remove filter')", - "label": "remove filter", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "set-search-query", - "rawLine": " it('set search query', async () => {", - "line": " it('set search query')", - "label": "set search query", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, { "id": "table-sort", - "rawLine": " describe.skip('Table Sort', () => {", - "line": " describe.skip('Table Sort')", + "rawLine": " describe('Table Sort', () => {", + "line": " describe('Table Sort')", "label": "Table Sort", "indent": 4, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false }, { @@ -17005,46 +17992,6 @@ "isSkipped": false, "isTodo": false }, - { - "id": "distributionbar", - "rawLine": " describe('DistributionBar', () => {", - "line": " describe('DistributionBar')", - "label": "DistributionBar", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false - }, - { - "id": "filters-by-${type}-findings", - "rawLine": " it(`filters by ${type} findings`, async () => {", - "line": " it(`filters by ${type} findings`)", - "label": "filters by ${type} findings", - "indent": 8, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "datatable-features", - "rawLine": " describe('DataTable features', () => {", - "line": " describe('DataTable features')", - "label": "DataTable features", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false - }, - { - "id": "edit-data-view-field-option-is-enabled", - "rawLine": " it('Edit data view field option is Enabled', async () => {", - "line": " it('Edit data view field option is Enabled')", - "label": "Edit data view field option is Enabled", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, { "id": "findings---fields-selector", "rawLine": " describe('Findings - Fields selector', () => {", @@ -17148,85 +18095,117 @@ "isTodo": false, "children": [ { - "id": "searchbar", - "rawLine": " describe.skip('SearchBar', () => {", - "line": " describe.skip('SearchBar')", - "label": "SearchBar", + "id": "table-sort", + "rawLine": " describe('Table Sort', () => {", + "line": " describe('Table Sort')", + "label": "Table Sort", "indent": 4, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false, "children": [ { - "id": "add-filter", - "rawLine": " it('add filter', async () => {", - "line": " it('add filter')", - "label": "add filter", + "id": "sorts-by-a-column,-should-be-case-sensitive/insensitive-depending-on-the-column", + "rawLine": " it('sorts by a column, should be case sensitive/insensitive depending on the column', async () => {", + "line": " it('sorts by a column, should be case sensitive/insensitive depending on the column')", + "label": "sorts by a column, should be case sensitive/insensitive depending on the column", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, + "isTodo": false + } + ] + }, + { + "id": "findings---fields-selector", + "rawLine": " describe('Findings - Fields selector', () => {", + "line": " describe('Findings - Fields selector')", + "label": "Findings - Fields selector", + "indent": 4, + "type": "describe", + "isSkipped": false, + "isTodo": false, + "children": [ + { + "id": "add-fields-to-the-findings-datatable", + "rawLine": " it('Add fields to the Findings DataTable', async () => {", + "line": " it('Add fields to the Findings DataTable')", + "label": "Add fields to the Findings DataTable", + "indent": 6, + "type": "it", + "isSkipped": false, "isTodo": false }, { - "id": "remove-filter", - "rawLine": " it('remove filter', async () => {", - "line": " it('remove filter')", - "label": "remove filter", + "id": "remove-fields-from-the-findings-datatable", + "rawLine": " it('Remove fields from the Findings DataTable', async () => {", + "line": " it('Remove fields from the Findings DataTable')", + "label": "Remove fields from the Findings DataTable", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, "isTodo": false }, { - "id": "set-search-query", - "rawLine": " it('set search query', async () => {", - "line": " it('set search query')", - "label": "set search query", + "id": "reset-fields-to-default", + "rawLine": " it('Reset fields to default', async () => {", + "line": " it('Reset fields to default')", + "label": "Reset fields to default", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, "isTodo": false } ] }, { - "id": "table-sort", - "rawLine": " describe.skip('Table Sort', () => {", - "line": " describe.skip('Table Sort')", - "label": "Table Sort", + "id": "findings-page---support-muting-rules", + "rawLine": " describe('Findings Page - support muting rules', () => {", + "line": " describe('Findings Page - support muting rules')", + "label": "Findings Page - support muting rules", "indent": 4, "type": "describe", - "isSkipped": true, + "isSkipped": false, "isTodo": false, "children": [ { - "id": "sorts-by-a-column,-should-be-case-sensitive/insensitive-depending-on-the-column", - "rawLine": " it('sorts by a column, should be case sensitive/insensitive depending on the column', async () => {", - "line": " it('sorts by a column, should be case sensitive/insensitive depending on the column')", - "label": "sorts by a column, should be case sensitive/insensitive depending on the column", + "id": "verify-only-enabled-rules-appears", + "rawLine": " it(`verify only enabled rules appears`, async () => {", + "line": " it(`verify only enabled rules appears`)", + "label": "verify only enabled rules appears", "indent": 6, "type": "it", - "isSkipped": true, + "isSkipped": false, "isTodo": false } ] }, { - "id": "distributionbar", - "rawLine": " describe('DistributionBar', () => {", - "line": " describe('DistributionBar')", - "label": "DistributionBar", + "id": "access-with-custom-roles", + "rawLine": " describe('Access with custom roles', async () => {", + "line": " describe('Access with custom roles')", + "label": "Access with custom roles", "indent": 4, "type": "describe", "isSkipped": false, "isTodo": false, "children": [ { - "id": "filters-by-${type}-findings", - "rawLine": " it(`filters by ${type} findings`, async () => {", - "line": " it(`filters by ${type} findings`)", - "label": "filters by ${type} findings", - "indent": 8, + "id": "access-with-valid-user-role", + "rawLine": " it('Access with valid user role', async () => {", + "line": " it('Access with valid user role')", + "label": "Access with valid user role", + "indent": 6, + "type": "it", + "isSkipped": false, + "isTodo": false + }, + { + "id": "access-with-invalid-user-role", + "rawLine": " it('Access with invalid user role', async () => {", + "line": " it('Access with invalid user role')", + "label": "Access with invalid user role", + "indent": 6, "type": "it", "isSkipped": false, "isTodo": false @@ -17234,124 +18213,6 @@ ] } ] - }, - { - "id": "datatable-features", - "rawLine": " describe('DataTable features', () => {", - "line": " describe('DataTable features')", - "label": "DataTable features", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false, - "children": [ - { - "id": "edit-data-view-field-option-is-enabled", - "rawLine": " it('Edit data view field option is Enabled', async () => {", - "line": " it('Edit data view field option is Enabled')", - "label": "Edit data view field option is Enabled", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - } - ] - }, - { - "id": "findings---fields-selector", - "rawLine": " describe('Findings - Fields selector', () => {", - "line": " describe('Findings - Fields selector')", - "label": "Findings - Fields selector", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false, - "children": [ - { - "id": "add-fields-to-the-findings-datatable", - "rawLine": " it('Add fields to the Findings DataTable', async () => {", - "line": " it('Add fields to the Findings DataTable')", - "label": "Add fields to the Findings DataTable", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "remove-fields-from-the-findings-datatable", - "rawLine": " it('Remove fields from the Findings DataTable', async () => {", - "line": " it('Remove fields from the Findings DataTable')", - "label": "Remove fields from the Findings DataTable", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "reset-fields-to-default", - "rawLine": " it('Reset fields to default', async () => {", - "line": " it('Reset fields to default')", - "label": "Reset fields to default", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - } - ] - }, - { - "id": "findings-page---support-muting-rules", - "rawLine": " describe('Findings Page - support muting rules', () => {", - "line": " describe('Findings Page - support muting rules')", - "label": "Findings Page - support muting rules", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false, - "children": [ - { - "id": "verify-only-enabled-rules-appears", - "rawLine": " it(`verify only enabled rules appears`, async () => {", - "line": " it(`verify only enabled rules appears`)", - "label": "verify only enabled rules appears", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - } - ] - }, - { - "id": "access-with-custom-roles", - "rawLine": " describe('Access with custom roles', async () => {", - "line": " describe('Access with custom roles')", - "label": "Access with custom roles", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false, - "children": [ - { - "id": "access-with-valid-user-role", - "rawLine": " it('Access with valid user role', async () => {", - "line": " it('Access with valid user role')", - "label": "Access with valid user role", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, - { - "id": "access-with-invalid-user-role", - "rawLine": " it('Access with invalid user role', async () => {", - "line": " it('Access with invalid user role')", - "label": "Access with invalid user role", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - } - ] } ] }, @@ -18271,8 +19132,6 @@ " it('add filter')", " it('remove filter')", " it('set search query')", - " describe('DataTable features')", - " it('Edit data view field option is Enabled')", " describe('Vulnerabilities - Fields selector')", " it('Add fields to the Vulnerabilities DataTable')", " it('Remove fields from the Vulnerabilities DataTable')", @@ -18329,26 +19188,6 @@ "isSkipped": false, "isTodo": false }, - { - "id": "datatable-features", - "rawLine": " describe('DataTable features', () => {", - "line": " describe('DataTable features')", - "label": "DataTable features", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false - }, - { - "id": "edit-data-view-field-option-is-enabled", - "rawLine": " it('Edit data view field option is Enabled', async () => {", - "line": " it('Edit data view field option is Enabled')", - "label": "Edit data view field option is Enabled", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - }, { "id": "vulnerabilities---fields-selector", "rawLine": " describe('Vulnerabilities - Fields selector', () => {", @@ -18443,28 +19282,6 @@ } ] }, - { - "id": "datatable-features", - "rawLine": " describe('DataTable features', () => {", - "line": " describe('DataTable features')", - "label": "DataTable features", - "indent": 4, - "type": "describe", - "isSkipped": false, - "isTodo": false, - "children": [ - { - "id": "edit-data-view-field-option-is-enabled", - "rawLine": " it('Edit data view field option is Enabled', async () => {", - "line": " it('Edit data view field option is Enabled')", - "label": "Edit data view field option is Enabled", - "indent": 6, - "type": "it", - "isSkipped": false, - "isTodo": false - } - ] - }, { "id": "vulnerabilities---fields-selector", "rawLine": " describe('Vulnerabilities - Fields selector', () => {", diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts index 85c38c50022b8..a00bf1a8077e6 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts @@ -18,8 +18,6 @@ export type CspBenchmarkRuleMetadata = TypeOf<typeof cspBenchmarkRuleMetadataSch export type CspBenchmarkRule = TypeOf<typeof cspBenchmarkRuleSchema>; -export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>; - export const cspBenchmarkRuleMetadataSchema = schema.object({ audit: schema.string(), benchmark: schema.object({ diff --git a/x-pack/plugins/cloud_security_posture/common/types_old.ts b/x-pack/plugins/cloud_security_posture/common/types_old.ts index 6b290979a97f5..f77ac4678a526 100644 --- a/x-pack/plugins/cloud_security_posture/common/types_old.ts +++ b/x-pack/plugins/cloud_security_posture/common/types_old.ts @@ -36,10 +36,6 @@ export type AzureCredentialsType = | 'service_principal_with_client_username_and_password' | 'managed_identity'; -export type AzureCredentialsTypeFieldMap = { - [key in AzureCredentialsType]: string[]; -}; - export type Evaluation = 'passed' | 'failed' | 'NA'; export type PostureTypes = 'cspm' | 'kspm' | 'vuln_mgmt' | 'all'; diff --git a/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts b/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts index f178d412675e6..61567211c8d22 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/detection_rules.ts @@ -29,7 +29,7 @@ export const convertRuleTagsToMatchAnyKQL = (tags: string[]): string => { export const getFindingsDetectionRuleSearchTags = ( cspBenchmarkRule: CspBenchmarkRuleMetadata ): string[] => { - if (!cspBenchmarkRule.benchmark || !cspBenchmarkRule.benchmark.id) { + if (!cspBenchmarkRule?.benchmark || !cspBenchmarkRule?.benchmark?.id) { // Return an empty array if benchmark ID is undefined return []; } diff --git a/x-pack/plugins/cloud_security_posture/kibana.jsonc b/x-pack/plugins/cloud_security_posture/kibana.jsonc index 56ea8549629ac..d1aacf2f340fc 100644 --- a/x-pack/plugins/cloud_security_posture/kibana.jsonc +++ b/x-pack/plugins/cloud_security_posture/kibana.jsonc @@ -22,7 +22,8 @@ "licensing", "share", "kibanaUtils", - "alerting" + "alerting", + "spaces" ], "optionalPlugins": ["usageCollection"], "requiredBundles": ["kibanaReact", "usageCollection"] diff --git a/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_alerts_status.ts b/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_alerts_status.ts index efea6629b7743..ed4b2933dddd0 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_alerts_status.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/api/use_fetch_detection_rules_alerts_status.ts @@ -27,10 +27,12 @@ export const useFetchDetectionRulesAlertsStatus = (tags: string[]) => { throw new Error('Kibana http service is not available'); } - return useQuery<AlertStatus, Error>([DETECTION_ENGINE_ALERTS_KEY, tags], () => - http.get<AlertStatus>(GET_DETECTION_RULE_ALERTS_STATUS_PATH, { - version: DETECTION_RULE_ALERTS_STATUS_API_CURRENT_VERSION, - query: { tags }, - }) - ); + return useQuery<AlertStatus, Error>({ + queryKey: [DETECTION_ENGINE_ALERTS_KEY, tags], + queryFn: () => + http.get<AlertStatus>(GET_DETECTION_RULE_ALERTS_STATUS_PATH, { + version: DETECTION_RULE_ALERTS_STATUS_API_CURRENT_VERSION, + query: { tags }, + }), + }); }; diff --git a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx index 6eece8a31c5e8..ecc5edcbf2e73 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx +++ b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx @@ -27,17 +27,6 @@ type FilterOption<T extends string, K extends string = string> = EuiSelectableOp label: T; }>; -export type { FilterOption as MultiSelectFilterOption }; - -export const mapToMultiSelectOption = <T extends string>(options: T[]) => { - return options.map((option) => { - return { - key: option, - label: option, - }; - }); -}; - const fromRawOptionsToEuiSelectableOptions = <T extends string, K extends string>( options: Array<FilterOption<T, K>>, selectedOptionKeys: string[] diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts index 73934752e819a..8054917ba7462 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts @@ -40,11 +40,8 @@ export const DEFAULT_VISIBLE_ROWS_PER_PAGE = 25; export const LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY = 'cloudPosture:dataTable:pageSize'; export const LOCAL_STORAGE_DATA_TABLE_COLUMNS_KEY = 'cloudPosture:dataTable:columns'; -export const LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY = 'cloudPosture:findings:pageSize'; export const LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY = 'cloudPosture:benchmark:pageSize'; export const LOCAL_STORAGE_PAGE_SIZE_RULES_KEY = 'cloudPosture:rules:pageSize'; -export const LOCAL_STORAGE_DASHBOARD_CLUSTER_SORT_KEY = - 'cloudPosture:complianceDashboard:clusterSort'; export const LOCAL_STORAGE_DASHBOARD_BENCHMARK_SORT_KEY = 'cloudPosture:complianceDashboard:benchmarkSort'; export const LOCAL_STORAGE_FINDINGS_LAST_SELECTED_TAB_KEY = 'cloudPosture:findings:lastSelectedTab'; diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts index 6628cc7711a82..98270ffee59bf 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_cloud_posture_data_table/utils.ts @@ -5,28 +5,6 @@ * 2.0. */ -import type { EuiBasicTableProps, Pagination } from '@elastic/eui'; - -type TablePagination = NonNullable<EuiBasicTableProps<object>['pagination']>; - -export const getPaginationTableParams = ( - params: TablePagination & Pick<Required<TablePagination>, 'pageIndex' | 'pageSize'>, - pageSizeOptions = [10, 25, 100], - showPerPageOptions = true -): Required<TablePagination> => ({ - ...params, - pageSizeOptions, - showPerPageOptions, -}); - -export const getPaginationQuery = ({ - pageIndex, - pageSize, -}: Required<Pick<Pagination, 'pageIndex' | 'pageSize'>>) => ({ - from: pageIndex * pageSize, - size: pageSize, -}); - export const getDefaultQuery = ({ query, filters }: any): any => ({ query, filters, diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts deleted file mode 100644 index e089724b25909..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_page_slice.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { useMemo } from 'react'; - -/** - * @description given an array index and page size, returns a slice of said array. - */ -export const usePageSlice = (data: any[] | undefined, pageIndex: number, pageSize: number) => { - return useMemo(() => { - if (!data) { - return []; - } - - const cursor = pageIndex * pageSize; - return data.slice(cursor, cursor + pageSize); - }, [data, pageIndex, pageSize]); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/types.ts b/x-pack/plugins/cloud_security_posture/public/common/types.ts index f2881c1798883..d0d491c256e0e 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/types.ts @@ -6,9 +6,6 @@ */ import type { Criteria } from '@elastic/eui'; import type { BoolQuery, Filter, Query, EsQueryConfig } from '@kbn/es-query'; -import { CspFinding } from '../../common/schemas/csp_finding'; - -export type FindingsGroupByKind = 'default' | 'resource'; export interface FindingsBaseURLQuery { query: Query; @@ -33,11 +30,6 @@ export interface FindingsBaseEsQuery { }; } -export interface CspFindingsQueryData { - page: CspFinding[]; - total: number; -} - export type Sort<T> = NonNullable<Criteria<T>['sort']>; interface RuleSeverityMapping { diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_dataset_display_name.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_dataset_display_name.ts new file mode 100644 index 0000000000000..2e0e1d8b21573 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_dataset_display_name.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. + */ + +type Dataset = 'wiz.cloud_configuration_finding' | 'cloud_security_posture.findings'; + +export const WIZ_DATASET = 'wiz.cloud_configuration_finding'; +export const CSP_DATASET = 'cloud_security_posture.findings'; + +export const getDatasetDisplayName = (dataset?: Dataset | string) => { + if (dataset === WIZ_DATASET) return 'Wiz'; + if (dataset === CSP_DATASET) return 'Elastic CSP'; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.ts deleted file mode 100644 index cd0a413648bed..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_enabled_csp_integration_details.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 { PackagePolicy } from '@kbn/fleet-plugin/common'; -import type { PostureInput } from '../../../common/types_old'; -import { SUPPORTED_CLOUDBEAT_INPUTS } from '../../../common/constants'; -import { cloudPostureIntegrations, type CloudPostureIntegrations } from '../constants'; - -const isPolicyTemplate = (name: unknown): name is keyof CloudPostureIntegrations => - typeof name === 'string' && name in cloudPostureIntegrations; - -export const getEnabledCspIntegrationDetails = (packageInfo?: PackagePolicy) => { - const enabledInput = packageInfo?.inputs.find((input) => input.enabled); - - // Check for valid and support input - if ( - !enabledInput || - !isPolicyTemplate(enabledInput.policy_template) || - !SUPPORTED_CLOUDBEAT_INPUTS.includes(enabledInput.type as PostureInput) - ) - return null; - - const integration = cloudPostureIntegrations[enabledInput.policy_template]; - const enabledIntegrationOption = integration.options.find( - (option) => option.type === enabledInput.type - ); - - return { integration, enabledIntegrationOption }; -}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts deleted file mode 100644 index f62062ba37f4d..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getLimitProperties } from './get_limit_properties'; - -describe('getLimitProperties', () => { - it('less items than limit', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(200, 500, 100, 1); - - expect(limitedTotalItemCount).toBe(200); - expect(isLastLimitedPage).toBe(false); - }); - - it('more items than limit', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(600, 500, 100, 4); - - expect(limitedTotalItemCount).toBe(500); - expect(isLastLimitedPage).toBe(true); - }); - - it('per page calculations are correct', () => { - const { limitedTotalItemCount, isLastLimitedPage } = getLimitProperties(600, 500, 25, 19); - - expect(limitedTotalItemCount).toBe(500); - expect(isLastLimitedPage).toBe(true); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.ts deleted file mode 100644 index f09990bc3f854..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_limit_properties.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 { useMemo } from 'react'; -import { MAX_FINDINGS_TO_LOAD } from '../constants'; - -export const getLimitProperties = ( - totalItems: number, - maxItems: number, - pageSize: number, - pageIndex: number -): { isLastLimitedPage: boolean; limitedTotalItemCount: number } => { - const limitItems = totalItems > maxItems; - const limitedTotalItemCount = limitItems ? maxItems : totalItems; - const lastLimitedPage = Math.ceil(limitedTotalItemCount / pageSize); - const isLastPage = lastLimitedPage === pageIndex + 1; - const isLastLimitedPage = limitItems && isLastPage; - - return { isLastLimitedPage, limitedTotalItemCount }; -}; - -export const useLimitProperties = ({ - total, - pageIndex, - pageSize, -}: { - total?: number; - pageSize: number; - pageIndex: number; -}) => - useMemo( - () => getLimitProperties(total || 0, MAX_FINDINGS_TO_LOAD, pageSize, pageIndex), - [total, pageIndex, pageSize] - ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx b/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx index b0b5703937c90..68d4f50a6e4f3 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cis_benchmark_icon.tsx @@ -19,8 +19,8 @@ interface Props { size?: IconSize; } -const getBenchmarkIdIconType = (props: Props): string => { - switch (props.type) { +const getBenchmarkIdIconType = (type: BenchmarkId): string | undefined => { + switch (type) { case 'cis_eks': return cisEksIcon; case 'cis_azure': @@ -30,13 +30,17 @@ const getBenchmarkIdIconType = (props: Props): string => { case 'cis_gcp': return googleCloudLogo; case 'cis_k8s': - default: return 'logoKubernetes'; } }; -export const CISBenchmarkIcon = (props: Props) => ( - <EuiToolTip content={props.name}> - <EuiIcon type={getBenchmarkIdIconType(props)} size={props.size || 'xl'} css={props.style} /> - </EuiToolTip> -); +export const CISBenchmarkIcon = (props: Props) => { + const iconType = getBenchmarkIdIconType(props.type); + if (!iconType) return <></>; + + return ( + <EuiToolTip content={props.name}> + <EuiIcon type={iconType} size={props.size || 'xl'} css={props.style} /> + </EuiToolTip> + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx b/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx index 009ab56ac27b9..6e68c18197dbf 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/csp_evaluation_badge.tsx @@ -12,7 +12,7 @@ import { css } from '@emotion/react'; import { statusColors } from '../common/constants'; interface Props { - type: 'passed' | 'failed'; + type?: 'passed' | 'failed'; } // 'fail' / 'pass' are same chars length, but not same width size. @@ -37,8 +37,10 @@ export const CspEvaluationBadge = ({ type }: Props) => ( > {type === 'failed' ? ( <FormattedMessage id="xpack.csp.cspEvaluationBadge.failLabel" defaultMessage="Fail" /> - ) : ( + ) : type === 'passed' ? ( <FormattedMessage id="xpack.csp.cspEvaluationBadge.passLabel" defaultMessage="Pass" /> + ) : ( + <FormattedMessage id="xpack.csp.cspEvaluationBadge.naLabel" defaultMessage="N/A" /> )} </EuiBadge> ); diff --git a/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx b/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx index 4cf8c5983cb86..5465c144b6772 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/detection_rule_counter.tsx @@ -31,7 +31,11 @@ interface DetectionRuleCounterProps { export const DetectionRuleCounter = ({ tags, createRuleFn }: DetectionRuleCounterProps) => { const { data: rulesData, isLoading: ruleIsLoading } = useFetchDetectionRulesByTags(tags); - const { data: alertsData, isLoading: alertsIsLoading } = useFetchDetectionRulesAlertsStatus(tags); + const { + data: alertsData, + isLoading: alertsIsLoading, + isError: alertsIsError, + } = useFetchDetectionRulesAlertsStatus(tags); const [isCreateRuleLoading, setIsCreateRuleLoading] = useState(false); @@ -68,6 +72,8 @@ export const DetectionRuleCounter = ({ tags, createRuleFn }: DetectionRuleCounte queryClient.invalidateQueries([DETECTION_ENGINE_ALERTS_KEY]); }, [createRuleFn, http, analytics, notifications, i18n, theme, queryClient]); + if (alertsIsError) return <>{'-'}</>; + return ( <EuiSkeletonText data-test-subj="csp:detection-rule-counter-loading" diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx index 867fc1af4d9ea..8a5587cf16622 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form/aws_credentials_form_agentless.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiButton, EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import semverCompare from 'semver/functions/compare'; import semverValid from 'semver/functions/valid'; @@ -36,106 +36,6 @@ import { AwsCredentialTypeSelector, } from './aws_credentials_form'; -const CLOUD_FORMATION_EXTERNAL_DOC_URL = - 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-whatis-howdoesitwork.html'; - -export const CloudFormationCloudCredentialsGuide = ({ - isOrganization, -}: { - isOrganization?: boolean; -}) => { - return ( - <EuiText> - <p> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.guide.description" - defaultMessage="CloudFormation will create all the necessary resources to evaluate the security posture of your AWS environment. {learnMore}." - values={{ - learnMore: ( - <EuiLink - href={CLOUD_FORMATION_EXTERNAL_DOC_URL} - target="_blank" - rel="noopener nofollow noreferrer" - data-test-subj="externalLink" - > - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.guide.learnMoreLinkText" - defaultMessage="Learn more about CloudFormation" - /> - </EuiLink> - ), - }} - /> - </p> - <EuiText size="s" color="subdued"> - <ol> - {isOrganization ? ( - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.guide.steps.organizationLogin" - defaultMessage="Log in as an admin in the management account of the AWS Organization you want to onboard" - /> - </li> - ) : ( - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.guide.steps.login" - defaultMessage="Log in as an admin in the AWS account you want to onboard" - /> - </li> - )} - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.guide.steps.launch" - defaultMessage="Click the Launch CloudFormation button below." - /> - </li> - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.steps.region" - defaultMessage="(Optional) Change the Amazon region in the upper right corner to the region you want to deploy your stack to" - /> - </li> - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.gsteps.accept" - defaultMessage="Tick the checkbox under capabilities in the opened CloudFormation stack review form: {acknowledge}" - values={{ - acknowledge: ( - <strong> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.steps.accept.acknowledge" - defaultMessage="I acknowledge that AWS CloudFormation might create IAM resources." - /> - </strong> - ), - }} - /> - </li> - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.steps.create" - defaultMessage="Click Create stack." - /> - </li> - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.steps.stackStatus" - defaultMessage="Once stack status is CREATE_COMPLETE then click the Ouputs tab" - /> - </li> - <li> - <FormattedMessage - id="xpack.csp.agentlessForm.cloudFormation.steps.credentials" - defaultMessage="Copy Access Key Id and Secret Access Key then paste the credentials below" - /> - </li> - </ol> - </EuiText> - </EuiText> - ); -}; - export const AwsCredentialsFormAgentless = ({ input, newPolicy, diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx index 84abf584b5080..116170b2ac29c 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx @@ -1460,7 +1460,7 @@ describe('<CspPolicyTemplateForm />', () => { expect(setupTechnologySelector).not.toBeInTheDocument(); }); - it('should render setup technology selector for AWS and allow to select agent-based', async () => { + it('should render setup technology selector for AWS and allow to select agentless', async () => { const newPackagePolicy = getMockPolicyAWS(); const { getByTestId, getByRole } = render( @@ -1471,31 +1471,34 @@ describe('<CspPolicyTemplateForm />', () => { SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ ); const setupTechnologySelector = getByTestId(SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ); - const awsCredentialsTypeSelector = getByTestId(AWS_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ); - const options: HTMLOptionElement[] = within(awsCredentialsTypeSelector).getAllByRole( - 'option' - ); - const optionValues = options.map((option) => option.value); // default state expect(setupTechnologySelectorAccordion).toBeInTheDocument(); expect(setupTechnologySelector).toBeInTheDocument(); - expect(setupTechnologySelector).toHaveTextContent(/agentless/i); - expect(options).toHaveLength(2); - expect(optionValues).toEqual( - expect.arrayContaining(['direct_access_keys', 'temporary_keys']) - ); + expect(setupTechnologySelector).toHaveTextContent(/agent-based/i); + + expect( + getByTestId(AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.CLOUDFORMATION) + ).toBeInTheDocument(); + expect(getByTestId(AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.MANUAL)).toBeInTheDocument(); // select agent-based and check for cloudformation option userEvent.click(setupTechnologySelector); - const agentBasedOption = getByRole('option', { name: /agent-based/i }); + const agentlessOption = getByRole('option', { name: /agentless/i }); await waitForEuiPopoverOpen(); - userEvent.click(agentBasedOption); + userEvent.click(agentlessOption); + + const awsCredentialsTypeSelector = getByTestId(AWS_CREDENTIALS_TYPE_SELECTOR_TEST_SUBJ); + const options: HTMLOptionElement[] = within(awsCredentialsTypeSelector).getAllByRole( + 'option' + ); + const optionValues = options.map((option) => option.value); + await waitFor(() => { - expect( - getByTestId(AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.CLOUDFORMATION) - ).toBeInTheDocument(); - expect(getByTestId(AWS_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ.MANUAL)).toBeInTheDocument(); + expect(options).toHaveLength(2); + expect(optionValues).toEqual( + expect.arrayContaining(['direct_access_keys', 'temporary_keys']) + ); }); }); @@ -1614,13 +1617,28 @@ describe('<CspPolicyTemplateForm />', () => { SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ ); const setupTechnologySelector = getByTestId(SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ); + + // default state for Azure with the Org selected + expect(setupTechnologySelectorAccordion).toBeInTheDocument(); + expect(setupTechnologySelector).toBeInTheDocument(); + expect(setupTechnologySelector).toHaveTextContent(/agent-based/i); + await waitFor(() => { + expect(getByTestId(CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS.ARM_TEMPLATE)).toBeInTheDocument(); + expect(getByTestId(CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS.MANUAL)).toBeInTheDocument(); + }); + + // select agent-based and check for ARM template option + userEvent.click(setupTechnologySelector); + const agentlessOption = getByRole('option', { name: /agentless/i }); + await waitForEuiPopoverOpen(); + userEvent.click(agentlessOption); + const tenantIdField = queryByTestId(CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.TENANT_ID); const clientIdField = queryByTestId(CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_ID); const clientSecretField = queryByTestId(CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_SECRET); const armTemplateSelector = queryByTestId(CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS.ARM_TEMPLATE); const manualSelector = queryByTestId(CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS.MANUAL); - // default state for Azure with the Org selected expect(setupTechnologySelectorAccordion).toBeInTheDocument(); expect(setupTechnologySelector).toBeInTheDocument(); expect(setupTechnologySelector).toHaveTextContent(/agentless/i); @@ -1629,16 +1647,6 @@ describe('<CspPolicyTemplateForm />', () => { expect(clientSecretField).toBeInTheDocument(); expect(armTemplateSelector).not.toBeInTheDocument(); expect(manualSelector).not.toBeInTheDocument(); - - // select agent-based and check for ARM template option - userEvent.click(setupTechnologySelector); - const agentBasedOption = getByRole('option', { name: /agent-based/i }); - await waitForEuiPopoverOpen(); - userEvent.click(agentBasedOption); - await waitFor(() => { - expect(getByTestId(CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS.ARM_TEMPLATE)).toBeInTheDocument(); - expect(getByTestId(CIS_AZURE_SETUP_FORMAT_TEST_SUBJECTS.MANUAL)).toBeInTheDocument(); - }); }); it('should render setup technology selector for Azure for Single Subscription type', async () => { @@ -1646,7 +1654,7 @@ describe('<CspPolicyTemplateForm />', () => { 'azure.account_type': { value: 'single-account', type: 'text' }, }); - const { getByTestId, queryByTestId } = render( + const { getByTestId, queryByTestId, getByRole } = render( <WrappedComponent newPolicy={newPackagePolicy} isAgentlessEnabled={true} @@ -1662,6 +1670,13 @@ describe('<CspPolicyTemplateForm />', () => { SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ ); const setupTechnologySelector = getByTestId(SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ); + + // select agentless and check for ARM template option + userEvent.click(setupTechnologySelector); + const agentlessOption = getByRole('option', { name: /agentless/i }); + await waitForEuiPopoverOpen(); + userEvent.click(agentlessOption); + const tenantIdField = queryByTestId(CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.TENANT_ID); const clientIdField = queryByTestId(CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_ID); const clientSecretField = queryByTestId(CIS_AZURE_INPUT_FIELDS_TEST_SUBJECTS.CLIENT_SECRET); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx index 0bd4c57129cc4..31daba522e9f2 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx @@ -552,7 +552,6 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio input, isAgentlessEnabled, handleSetupTechnologyChange, - isEditPage, }); const shouldRenderAgentlessSelector = (!isEditPage && isAgentlessAvailable) || diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/setup_technology_selector.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/setup_technology_selector.tsx index 03ca7ab21150e..8353ec8f9847f 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/setup_technology_selector.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/setup_technology_selector.tsx @@ -9,7 +9,9 @@ import React from 'react'; import { SetupTechnology } from '@kbn/fleet-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; import { + EuiBetaBadge, EuiAccordion, EuiFormRow, EuiLink, @@ -17,6 +19,9 @@ import { EuiSuperSelect, EuiText, useGeneratedHtmlId, + EuiFlexItem, + EuiFlexGroup, + useEuiTheme, } from '@elastic/eui'; import { SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ, @@ -32,28 +37,70 @@ export const SetupTechnologySelector = ({ setupTechnology: SetupTechnology; onSetupTechnologyChange: (value: SetupTechnology) => void; }) => { + const { euiTheme } = useEuiTheme(); + const agentlessOptionBadge = (isDropDownDisplay: boolean) => { + const title = isDropDownDisplay ? ( + <strong> + <FormattedMessage + id="xpack.csp.fleetIntegration.setupTechnology.agentlessDrowpownDisplay" + defaultMessage="Agentless" + /> + </strong> + ) : ( + <FormattedMessage + id="xpack.csp.fleetIntegration.setupTechnology.agentlessInputDisplay" + defaultMessage="Agentless" + /> + ); + return ( + <EuiFlexGroup alignItems="center" responsive={false}> + <EuiFlexItem grow={false}>{title}</EuiFlexItem> + <EuiFlexItem css={{ paddingTop: !isDropDownDisplay ? euiTheme.size.xs : undefined }}> + <EuiBetaBadge + label={i18n.translate( + 'xpack.csp.fleetIntegration.setupTechnology.agentlessInputDisplay.techPreviewBadge.label', + { + defaultMessage: 'Beta', + } + )} + size="m" + color="hollow" + tooltipContent={i18n.translate( + 'xpack.csp.fleetIntegration.setupTechnology.agentlessInputDisplay.techPreviewBadge.tooltip', + { + defaultMessage: + 'This functionality is in technical preview and may be changed in a future release. Please help us by reporting any bugs.', + } + )} + /> + </EuiFlexItem> + </EuiFlexGroup> + ); + }; + const options = [ { - value: SetupTechnology.AGENTLESS, + value: SetupTechnology.AGENT_BASED, + 'data-test-subj': 'setup-technology-agent-based-option', inputDisplay: ( <FormattedMessage - id="xpack.csp.fleetIntegration.setupTechnology.agentlessInputDisplay" - defaultMessage="Agentless" + id="xpack.csp.fleetIntegration.setupTechnology.agentbasedInputDisplay" + defaultMessage="Agent-based" /> ), dropdownDisplay: ( <> <strong> <FormattedMessage - id="xpack.csp.fleetIntegration.setupTechnology.agentlessDrowpownDisplay" - defaultMessage="Agentless" + id="xpack.csp.fleetIntegration.setupTechnology.agentbasedDrowpownDisplay" + defaultMessage="Agent-based" /> </strong> <EuiText size="s" color="subdued"> <p> <FormattedMessage - id="xpack.csp.fleetIntegration.setupTechnology.agentlessDrowpownDescription" - defaultMessage="Set up the integration without an agent" + id="xpack.csp.fleetIntegration.setupTechnology.agentbasedDrowpownDescription" + defaultMessage="Set up the integration with an agent" /> </p> </EuiText> @@ -61,26 +108,17 @@ export const SetupTechnologySelector = ({ ), }, { - value: SetupTechnology.AGENT_BASED, - inputDisplay: ( - <FormattedMessage - id="xpack.csp.fleetIntegration.setupTechnology.agentbasedInputDisplay" - defaultMessage="Agent-based" - /> - ), + value: SetupTechnology.AGENTLESS, + inputDisplay: agentlessOptionBadge(false), + 'data-test-subj': 'setup-technology-agentless-option', dropdownDisplay: ( <> - <strong> - <FormattedMessage - id="xpack.csp.fleetIntegration.setupTechnology.agentbasedDrowpownDisplay" - defaultMessage="Agent-based" - /> - </strong> + {agentlessOptionBadge(true)} <EuiText size="s" color="subdued"> <p> <FormattedMessage - id="xpack.csp.fleetIntegration.setupTechnology.agentbasedDrowpownDescription" - defaultMessage="Set up the integration with an agent" + id="xpack.csp.fleetIntegration.setupTechnology.agentlessDrowpownDescription" + defaultMessage="Set up the integration without an agent" /> </p> </EuiText> diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts index ae0cd7745fecd..e6aec495aca07 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.test.ts @@ -20,59 +20,46 @@ import { useSetupTechnology } from './use_setup_technology'; describe('useSetupTechnology', () => { describe('create page flow', () => { - const isEditPage = false; - it('initializes with AGENT_BASED technology', () => { const { result } = renderHook(() => useSetupTechnology({ input: { type: 'cloudbeat/no-agentless-support' } as NewPackagePolicyInput, - isEditPage, }) ); expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); - it('sets to AGENTLESS when agentless is available and AWS cloud', () => { + it('sets to AGENT-BASED when agentless is available and AWS cloud', () => { const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput; - const { result } = renderHook(() => - useSetupTechnology({ input, isAgentlessEnabled: true, isEditPage }) - ); + const { result } = renderHook(() => useSetupTechnology({ input, isAgentlessEnabled: true })); expect(result.current.isAgentlessAvailable).toBeTruthy(); - expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS); + expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); - it('sets to AGENTLESS when agentless is available and GCP cloud', () => { + it('sets to AGENT-BASED when agentless is available and GCP cloud', () => { const input = { type: CLOUDBEAT_GCP } as NewPackagePolicyInput; - const { result } = renderHook(() => - useSetupTechnology({ input, isAgentlessEnabled: true, isEditPage }) - ); + const { result } = renderHook(() => useSetupTechnology({ input, isAgentlessEnabled: true })); expect(result.current.isAgentlessAvailable).toBeTruthy(); - expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS); + expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); - it('sets to AGENTLESS when agentless is available and Azure cloud', () => { + it('sets to AGENT-BASED when agentless is available and Azure cloud', () => { const input = { type: CLOUDBEAT_AZURE } as NewPackagePolicyInput; - const { result } = renderHook(() => - useSetupTechnology({ input, isAgentlessEnabled: true, isEditPage }) - ); + const { result } = renderHook(() => useSetupTechnology({ input, isAgentlessEnabled: true })); expect(result.current.isAgentlessAvailable).toBeTruthy(); - expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS); + expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); it('sets to AGENT_BASED when agentless is available but input is not supported for agentless', () => { const input = { type: CLOUDBEAT_EKS } as NewPackagePolicyInput; - const { result } = renderHook(() => - useSetupTechnology({ input, isAgentlessEnabled: true, isEditPage }) - ); + const { result } = renderHook(() => useSetupTechnology({ input, isAgentlessEnabled: true })); expect(result.current.isAgentlessAvailable).toBeFalsy(); expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); it('sets to AGENT_BASED when isAgentlessEnabled is false', () => { const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput; - const { result } = renderHook(() => - useSetupTechnology({ input, isAgentlessEnabled: false, isEditPage }) - ); + const { result } = renderHook(() => useSetupTechnology({ input, isAgentlessEnabled: false })); expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); @@ -82,41 +69,39 @@ describe('useSetupTechnology', () => { useSetupTechnology({ input: { type: 'someType' } as NewPackagePolicyInput, handleSetupTechnologyChange: handleSetupTechnologyChangeMock, - isEditPage, }) ); + expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); + act(() => { - result.current.setSetupTechnology(SetupTechnology.AGENTLESS); + result.current.updateSetupTechnology(SetupTechnology.AGENTLESS); }); + expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS); expect(handleSetupTechnologyChangeMock).toHaveBeenCalledWith(SetupTechnology.AGENTLESS); }); }); describe('edit page flow', () => { - const isEditPage = true; - it('initializes with AGENT_BASED technology', () => { const { result } = renderHook(() => useSetupTechnology({ input: { type: 'cloudbeat/no-agentless-support' } as NewPackagePolicyInput, - isEditPage, }) ); expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); - it('initializes with AGENTLESS technology if isAgentlessEnable is true', () => { + it('initializes with AGENT-BASED technology if isAgentlessEnable is true', () => { const input = { type: CLOUDBEAT_AWS } as NewPackagePolicyInput; const { result } = renderHook(() => useSetupTechnology({ input, isAgentlessEnabled: true, - isEditPage, }) ); - expect(result.current.setupTechnology).toBe(SetupTechnology.AGENTLESS); + expect(result.current.setupTechnology).toBe(SetupTechnology.AGENT_BASED); }); it('should not call handleSetupTechnologyChange when setupTechnology changes', () => { @@ -126,12 +111,11 @@ describe('useSetupTechnology', () => { useSetupTechnology({ input, handleSetupTechnologyChange: handleSetupTechnologyChangeMock, - isEditPage, }) ); act(() => { - result.current.setSetupTechnology(SetupTechnology.AGENTLESS); + result.current.setSetupTechnology(SetupTechnology.AGENT_BASED); }); expect(handleSetupTechnologyChangeMock).not.toHaveBeenCalled(); @@ -143,7 +127,6 @@ describe('useSetupTechnology', () => { const { result, rerender } = renderHook(() => useSetupTechnology({ input, - isEditPage, }) ); @@ -153,7 +136,6 @@ describe('useSetupTechnology', () => { rerender({ input, agentlessPolicy, - isEditPage, }); }); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts index 77b99d42d6411..e5327b65e3b36 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/setup_technology_selector/use_setup_technology.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { NewPackagePolicyInput } from '@kbn/fleet-plugin/common'; import { SetupTechnology } from '@kbn/fleet-plugin/public'; @@ -14,58 +14,26 @@ export const useSetupTechnology = ({ input, isAgentlessEnabled, handleSetupTechnologyChange, - isEditPage, }: { input: NewPackagePolicyInput; isAgentlessEnabled?: boolean; handleSetupTechnologyChange?: (value: SetupTechnology) => void; - isEditPage: boolean; }) => { const isCspmAws = input.type === CLOUDBEAT_AWS; const isCspmGcp = input.type === CLOUDBEAT_GCP; const isCspmAzure = input.type === CLOUDBEAT_AZURE; const isAgentlessSupportedForCloudProvider = isCspmAws || isCspmGcp || isCspmAzure; const isAgentlessAvailable = isAgentlessSupportedForCloudProvider && isAgentlessEnabled; - const [setupTechnology, setSetupTechnology] = useState<SetupTechnology>(() => { - if (isEditPage && isAgentlessAvailable) { - return SetupTechnology.AGENTLESS; - } - - return SetupTechnology.AGENT_BASED; - }); - - const [isDirty, setIsDirty] = useState<boolean>(false); + const [setupTechnology, setSetupTechnology] = useState<SetupTechnology>( + SetupTechnology.AGENT_BASED + ); const updateSetupTechnology = (value: SetupTechnology) => { setSetupTechnology(value); - setIsDirty(true); - }; - - useEffect(() => { - if (isEditPage || isDirty) { - return; - } - - if (!isAgentlessAvailable) { - setSetupTechnology(SetupTechnology.AGENT_BASED); - } else { - /* - preselecting agentless when available - and resetting to agent-based when switching to another integration type, which doesn't support agentless - */ - setSetupTechnology(SetupTechnology.AGENTLESS); - } - }, [isAgentlessAvailable, isDirty, isEditPage]); - - useEffect(() => { - if (isEditPage) { - return; - } - if (handleSetupTechnologyChange) { - handleSetupTechnologyChange(setupTechnology); + handleSetupTechnologyChange(value); } - }, [handleSetupTechnologyChange, isEditPage, setupTechnology]); + }; return { isAgentlessAvailable, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx index 32cb0c6f11f98..2da4cc06ddd5d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks_table.tsx @@ -38,6 +38,8 @@ import { import { useKibana } from '../../common/hooks/use_kibana'; export const ERROR_STATE_TEST_SUBJECT = 'benchmark_page_error'; +export const EMPTY_EVALUATION_TEST_SUBJECT = 'benchmark-not-evaluated-account'; +export const EMPTY_SCORE_TEST_SUBJECT = 'benchmark-score-no-findings'; interface BenchmarksTableProps extends Pick<EuiBasicTableProps<Benchmark>, 'loading' | 'error' | 'noItemsMessage' | 'sorting'>, @@ -170,7 +172,12 @@ const getBenchmarkTableColumns = ( if (benchmarkEvaluation === 0) { return ( - <EuiButtonEmpty href={integrationLink} iconType="plusInCircle" flush="left"> + <EuiButtonEmpty + data-test-subj={EMPTY_EVALUATION_TEST_SUBJECT} + href={integrationLink} + iconType="plusInCircle" + flush="left" + > {i18n.translate('xpack.csp.benchmarks.benchmarksTable.addIntegrationTitle', { defaultMessage: 'Add {resourceCountLabel}', values: { resourceCountLabel }, @@ -215,10 +222,12 @@ const getBenchmarkTableColumns = ( ); return ( - <FormattedMessage - id="xpack.csp.benchmarks.benchmarksTable.noFindingsScore" - defaultMessage="No Findings" - /> + <span data-test-subj={EMPTY_SCORE_TEST_SUBJECT}> + <FormattedMessage + id="xpack.csp.benchmarks.benchmarksTable.noFindingsScore" + defaultMessage="No Findings" + /> + </span> ); }, }, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx index aff4feeed048a..d1b681e77347b 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx @@ -26,6 +26,7 @@ import { type EuiTextProps, EuiToolTip, EuiToolTipProps, + type CommonProps, } from '@elastic/eui'; import { FormattedDate, FormattedTime } from '@kbn/i18n-react'; import moment from 'moment'; @@ -50,13 +51,14 @@ const CounterButtonLink = ({ text, count, color, + 'data-test-subj': dataTestSubj, onClick, }: { count: number; text: string; color: EuiTextProps['color']; onClick: EuiLinkButtonProps['onClick']; -}) => { +} & Pick<CommonProps, 'data-test-subj'>) => { const { euiTheme } = useEuiTheme(); return ( @@ -74,6 +76,7 @@ const CounterButtonLink = ({ <EuiLink color="text" onClick={onClick} + data-test-subj={dataTestSubj} css={css` display: flex; &:hover { @@ -148,6 +151,7 @@ const PercentageLabels = ({ text="Passed Findings" count={stats.totalPassed} color={statusColors.passed} + data-test-subj="dashboard-summary-passed-findings" onClick={() => onEvalCounterClick(RULE_PASSED)} /> </EuiFlexItem> @@ -156,6 +160,7 @@ const PercentageLabels = ({ text="Failed Findings" count={stats.totalFailed} color={statusColors.failed} + data-test-subj="dashboard-summary-failed-findings" onClick={() => onEvalCounterClick(RULE_FAILED)} /> </EuiFlexItem> @@ -257,7 +262,12 @@ const CounterLink = ({ return ( <EuiToolTip content={tooltipContent}> - <EuiLink color="text" onClick={onClick} css={{ display: 'flex' }}> + <EuiLink + data-test-subj={`compliance-score-section-${text}`} + color="text" + onClick={onClick} + css={{ display: 'flex' }} + > <EuiText color={color} style={{ fontWeight: euiTheme.font.weight.medium }} size="s"> <CompactFormattedNumber number={count} abbreviateAbove={999} />   diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx index d8fce2e0bfaa0..0787099308a72 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/risks_table.tsx @@ -58,7 +58,12 @@ export const RisksTable = ({ defaultMessage: 'CIS Section', }), render: (name: GroupedFindingsEvaluation['name']) => ( - <EuiLink onClick={() => onCellClick(name)} className="eui-textTruncate" color="text"> + <EuiLink + data-test-subj="grouped-findings-evaluation-link" + onClick={() => onCellClick(name)} + className="eui-textTruncate" + color="text" + > {name} </EuiLink> ), @@ -106,7 +111,11 @@ export const RisksTable = ({ </EuiFlexItem> <EuiFlexItem grow={false}> <div> - <EuiButtonEmpty onClick={onViewAllClick} iconType="search"> + <EuiButtonEmpty + data-test-subj="view-all-failed-findings" + onClick={onViewAllClick} + iconType="search" + > {viewAllButtonTitle} </EuiButtonEmpty> </div> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx index ac9b48ddfdbfe..226face0d95a0 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/benchmark_details_box.tsx @@ -163,14 +163,22 @@ export const BenchmarkDetailsBox = ({ benchmark }: { benchmark: BenchmarkData }) </EuiText> } > - <EuiLink onClick={benchmarkInfo.handleClick} color="text"> + <EuiLink + onClick={benchmarkInfo.handleClick} + color="text" + data-test-subj="benchmark-section-bench-name" + > <EuiTitle css={{ fontSize: 20 }}> <h5>{benchmarkInfo.name}</h5> </EuiTitle> </EuiLink> </EuiToolTip> - <EuiLink onClick={benchmarkInfo.handleClick} color="text"> + <EuiLink + data-test-subj="benchmark-asset-type" + onClick={benchmarkInfo.handleClick} + color="text" + > <EuiText size="xs">{benchmarkInfo.assetType}</EuiText> </EuiLink> </EuiFlexItem> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx index 0f176171b82c0..64d0a225f20d4 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/dashboard_sections/summary_section.tsx @@ -135,6 +135,7 @@ export const SummarySection = ({ button: ( <EuiButtonEmpty iconType="search" + data-test-subj="dashboard-view-all-resources" onClick={() => { navToFindings(getPolicyTemplateQuery(dashboardType), [ FINDINGS_GROUPING_OPTIONS.RESOURCE_NAME, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts index 2a325ee225128..6c1a73ed735aa 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/test_subjects.ts @@ -5,7 +5,6 @@ * 2.0. */ -export const MISSING_FINDINGS_NO_DATA_CONFIG = 'missing-findings-no-data-config'; export const DASHBOARD_CONTAINER = 'dashboard-container'; export const DASHBOARD_SUMMARY_CONTAINER = 'dashboard-summary-section'; export const KUBERNETES_DASHBOARD_CONTAINER = 'kubernetes-dashboard-container'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/__mocks__/findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/__mocks__/findings.ts index 274d5a789b981..4b01603b4c03b 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/__mocks__/findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/__mocks__/findings.ts @@ -115,4 +115,84 @@ export const mockFindingsHit: CspFinding = { category: ['configuration'], outcome: 'success', }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, +}; + +export const mockWizFinding = { + agent: { + name: 'ip-172-31-29-186.eu-west-1.compute.internal', + id: 'd66400e6-6224-489a-aae5-0dd529e7b61a', + ephemeral_id: '3159ed3a-8517-4289-9c4c-ab15abc7f938', + type: 'filebeat', + version: '8.14.1', + }, + resource: { + name: 'annam-instance-group-61wh', + id: '45860879-12db-5fce-838d-eb4deac2a544', + }, + elastic_agent: { + id: 'd66400e6-6224-489a-aae5-0dd529e7b61a', + version: '8.14.1', + snapshot: false, + }, + wiz: { + cloud_configuration_finding: { + rule: { + id: '02fde46d-ba1c-405e-b20f-a3742a8d2f41', + }, + }, + }, + rule: { + name: 'Unattached volume for more than 7 days', + id: '02fde46d-ba1c-405e-b20f-a3742a8d2f41', + }, + message: + "This rule checks if Compute Disks have been unattached for more than 7 days. \nThis rule fails if a disk's status is `READY`, it has no users attached, and the `lastDetachTimestamp` is more than 7 days ago. \nUnattached disks can incur costs without providing any benefits and may also pose a security risk if they contain sensitive data that is not being used. It is recommended to either delete unattached disks that are no longer needed or reattach them to a relevant instance.", + tags: ['preserve_original_event', 'forwarded', 'wiz-cloud_configuration_finding'], + cloud: { + availability_zone: 'eu-west-1b', + image: { + id: 'ami-0551ce4d67096d606', + }, + instance: { + id: 'i-0d3beee17a99bf575', + }, + provider: 'GCP', + service: { + name: 'EC2', + }, + machine: { + type: 't2.micro', + }, + region: 'us-central1', + account: { + id: '704479110758', + }, + }, + input: { + type: 'cel', + }, + '@timestamp': '2024-07-15T10:00:16.283Z', + ecs: { + version: '8.11.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'wiz.cloud_configuration_finding', + }, + event: { + agent_id_status: 'auth_metadata_missing', + ingested: '2024-07-15T10:49:45Z', + original: + '{"analyzedAt":"2024-07-15T10:00:16.283504Z","firstSeenAt":"2024-07-15T10:00:22.271901Z","id":"fd5b53a4-d85c-5d3a-b0bf-2eb270582db5","ignoreRules":null,"remediation":null,"resource":{"id":"45860879-12db-5fce-838d-eb4deac2a544","name":"annam-instance-group-61wh","nativeType":"compute#disk","projects":[{"id":"0f19bcc4-c17b-57d0-a187-db3a6b1a5100","name":"Project 3","riskProfile":{"businessImpact":"MBI"}}],"providerId":"https://www.googleapis.com/compute/v1/projects/my-walla-website/zones/us-central1-c/disks/annam-instance-group-61wh","region":"us-central1","subscription":{"cloudProvider":"GCP","externalId":"my-walla-website","id":"64982819-64ed-5c02-8a73-93d25fef8d89","name":"Product Integration"},"tags":[],"type":"VOLUME"},"result":"PASS","rule":{"description":"This rule checks if Compute Disks have been unattached for more than 7 days. \\nThis rule fails if a disk\'s status is `READY`, it has no users attached, and the `lastDetachTimestamp` is more than 7 days ago. \\nUnattached disks can incur costs without providing any benefits and may also pose a security risk if they contain sensitive data that is not being used. It is recommended to either delete unattached disks that are no longer needed or reattach them to a relevant instance.","functionAsControl":false,"graphId":"60db4cc3-d5c8-5e76-8dc9-77dde142ba98","id":"02fde46d-ba1c-405e-b20f-a3742a8d2f41","name":"Unattached volume for more than 7 days","remediationInstructions":"Perform the following step in order to delete a disk via GCP CLI: \\n``` \\ngcloud compute disks delete {{DiskName}} --zone={{Zone}}\\n``` \\n\\u003e**Note** \\n\\u003eA disk can only be deleted if it is not attached to any virtual machine instances."},"securitySubCategories":[{"category":{"framework":{"id":"wf-id-120","name":"NIS2 Directive (Article 21)"},"id":"wct-id-2418","name":"Article 21 Cybersecurity risk-management measures"},"id":"wsct-id-18827","title":"21.2.1 The measures to protect network and information systems shall include policies on risk analysis and information system security"},{"category":{"framework":{"id":"wf-id-105","name":"Wiz (Legacy)"},"id":"wct-id-2136","name":"Operationalization"},"id":"wsct-id-5540","title":"Operationalization"},{"category":{"framework":{"id":"wf-id-1","name":"Wiz for Risk Assessment"},"id":"wct-id-940","name":"Operationalization"},"id":"wsct-id-6548","title":"Operationalization"},{"category":{"framework":{"id":"wf-id-78","name":"Wiz for Cost Optimization"},"id":"wct-id-1796","name":"Waste"},"id":"wsct-id-10216","title":"Storage"}],"severity":"NONE","status":"RESOLVED","targetExternalId":"1404039754344376914","targetObjectProviderUniqueId":"https://www.googleapis.com/compute/v1/projects/my-walla-website/zones/us-central1-c/disks/annam-instance-group-61wh"}', + created: '2024-07-15T10:00:22.271Z', + kind: 'event', + id: 'fd5b53a4-d85c-5d3a-b0bf-2eb270582db5', + category: ['configuration'], + type: ['info'], + dataset: 'wiz.cloud_configuration_finding', + }, }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts index c5fb197583dd9..e79f737b11a3c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.handlers.mock.ts @@ -132,6 +132,9 @@ export const generateCspFinding = ( dataset: 'cloud_security_posture.findings', outcome: 'success', }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }; }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.ts deleted file mode 100644 index d4a14320fe225..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/constants.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. - */ - -export const FINDINGS_PIT_KEEP_ALIVE = '2m'; -// Set to half of the PIT keep alive to make sure we keep the PIT window open as long as the components are mounted -export const FINDINGS_REFETCH_INTERVAL_MS = 1000 * 60; // One minute diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx index a7b4e931b4371..a67a7bd873e4d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.test.tsx @@ -9,7 +9,7 @@ import userEvent from '@testing-library/user-event'; import { FindingsRuleFlyout } from './findings_flyout'; import { render, screen } from '@testing-library/react'; import { TestProvider } from '../../../test/test_provider'; -import { mockFindingsHit } from '../__mocks__/findings'; +import { mockFindingsHit, mockWizFinding } from '../__mocks__/findings'; import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../../../common/constants'; const onPaginate = jest.fn(); @@ -21,7 +21,7 @@ const TestComponent = ({ ...overrideProps }) => ( flyoutIndex={0} findingsCount={2} onPaginate={onPaginate} - findings={mockFindingsHit} + finding={mockFindingsHit} {...overrideProps} /> </TestProvider> @@ -48,12 +48,22 @@ describe('<FindingsFlyout/>', () => { getAllByText(tag); }); }); + + it('displays missing info callout when data source is not CSP', () => { + const { getByText } = render(<TestComponent finding={mockWizFinding} />); + getByText('Some fields not provided by Wiz'); + }); + + it('does not display missing info callout when data source is CSP', () => { + const { queryByText } = render(<TestComponent finding={mockFindingsHit} />); + const missingInfoCallout = queryByText('Some fields not provided by Wiz'); + expect(missingInfoCallout).toBeNull(); + }); }); describe('Rule Tab', () => { it('displays rule text details', () => { const { getByText, getAllByText } = render(<TestComponent />); - userEvent.click(screen.getByTestId('findings_flyout_tab_rule')); getAllByText(mockFindingsHit.rule.name); @@ -63,17 +73,49 @@ describe('<FindingsFlyout/>', () => { getAllByText(tag); }); }); + + it('displays missing info callout when data source is not CSP', () => { + const { getByText } = render(<TestComponent finding={mockWizFinding} />); + userEvent.click(screen.getByTestId('findings_flyout_tab_rule')); + + getByText('Some fields not provided by Wiz'); + }); + + it('does not display missing info callout when data source is CSP', () => { + const { queryByText } = render(<TestComponent finding={mockFindingsHit} />); + userEvent.click(screen.getByTestId('findings_flyout_tab_rule')); + + const missingInfoCallout = queryByText('Some fields not provided by Wiz'); + expect(missingInfoCallout).toBeNull(); + }); }); describe('Table Tab', () => { it('displays resource name and id', () => { const { getAllByText } = render(<TestComponent />); - userEvent.click(screen.getByTestId('findings_flyout_tab_table')); getAllByText(mockFindingsHit.resource.name); getAllByText(mockFindingsHit.resource.id); }); + + it('does not display missing info callout for 3Ps', () => { + const { queryByText } = render(<TestComponent finding={mockWizFinding} />); + userEvent.click(screen.getByTestId('findings_flyout_tab_table')); + + const missingInfoCallout = queryByText('Some fields not provided by Wiz'); + expect(missingInfoCallout).toBeNull(); + }); + }); + + describe('JSON Tab', () => { + it('does not display missing info callout for 3Ps', () => { + const { queryByText } = render(<TestComponent finding={mockWizFinding} />); + userEvent.click(screen.getByTestId('findings_flyout_tab_json')); + + const missingInfoCallout = queryByText('Some fields not provided by Wiz'); + expect(missingInfoCallout).toBeNull(); + }); }); it('should allow pagination with next', async () => { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx index 1c95c3120b894..4d8c5b6569efd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/findings_flyout.tsx @@ -6,6 +6,7 @@ */ import React, { useState } from 'react'; import { + useEuiTheme, EuiFlexItem, EuiSpacer, EuiTextColor, @@ -24,13 +25,17 @@ import { EuiFlyoutFooter, EuiToolTip, EuiDescriptionListProps, + EuiCallOut, + EuiLink, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { assertNever } from '@kbn/std'; import { i18n } from '@kbn/i18n'; import type { HttpSetup } from '@kbn/core/public'; import { generatePath } from 'react-router-dom'; import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; +import { CSP_DATASET, getDatasetDisplayName } from '../../../common/utils/get_dataset_display_name'; import { truthy } from '../../../../common/utils/helpers'; import { benchmarksNavigation } from '../../../common/navigation/constants'; import cisLogoIcon from '../../../assets/icons/cis_logo.svg'; @@ -82,9 +87,11 @@ const PAGINATION_LABEL = i18n.translate('xpack.csp.findings.findingsFlyout.pagin type FindingsTab = (typeof tabs)[number]; +export const EMPTY_VALUE = '-'; + interface FindingFlyoutProps { onClose(): void; - findings: CspFinding; + finding: CspFinding; flyoutIndex?: number; findingsCount?: number; onPaginate?: (pageIndex: number) => void; @@ -98,7 +105,7 @@ export const CspFlyoutMarkdown: React.FC<PropsOf<typeof EuiMarkdownFormat>> = (p <EuiMarkdownFormat textSize="s" {...props} /> ); -export const CisKubernetesIcons = ({ +export const BenchmarkIcons = ({ benchmarkId, benchmarkName, }: { @@ -106,17 +113,41 @@ export const CisKubernetesIcons = ({ benchmarkName: BenchmarkName; }) => ( <EuiFlexGroup gutterSize="s" alignItems="center"> - <EuiFlexItem grow={false}> - <EuiToolTip content="Center for Internet Security"> - <EuiIcon type={cisLogoIcon} size="xl" /> - </EuiToolTip> - </EuiFlexItem> + {benchmarkId.startsWith('cis') && ( + <EuiFlexItem grow={false}> + <EuiToolTip content="Center for Internet Security"> + <EuiIcon type={cisLogoIcon} size="xl" /> + </EuiToolTip> + </EuiFlexItem> + )} <EuiFlexItem grow={false}> <CISBenchmarkIcon type={benchmarkId} name={benchmarkName} /> </EuiFlexItem> </EuiFlexGroup> ); +export const RuleNameLink = ({ + ruleFlyoutLink, + ruleName, +}: { + ruleFlyoutLink?: string; + ruleName: string; +}) => { + return ruleFlyoutLink && ruleName ? ( + <EuiToolTip + position="top" + content={i18n.translate( + 'xpack.csp.findings.findingsFlyout.ruleNameTabField.ruleNameTooltip', + { defaultMessage: 'Manage Rule' } + )} + > + <EuiLink href={ruleFlyoutLink}>{ruleName}</EuiLink> + </EuiToolTip> + ) : ( + <>{ruleName}</> + ); +}; + const getFlyoutDescriptionList = (finding: CspFinding): EuiDescriptionListProps['listItems'] => [ finding.resource?.id && { @@ -134,34 +165,71 @@ const getFlyoutDescriptionList = (finding: CspFinding): EuiDescriptionListProps[ }, ].filter(truthy); -const FindingsTab = ({ tab, findings }: { findings: CspFinding; tab: FindingsTab }) => { +const FindingsTab = ({ tab, finding }: { finding: CspFinding; tab: FindingsTab }) => { const { application } = useKibana().services; - const ruleFlyoutLink = application.getUrlForApp('security', { - path: generatePath(benchmarksNavigation.rules.path, { - benchmarkVersion: findings.rule.benchmark.version.split('v')[1], // removing the v from the version - benchmarkId: findings.rule.benchmark.id, - ruleId: findings.rule.id, - }), - }); + const ruleFlyoutLink = + // currently we only support rule linking for native CSP findings + finding.data_stream.dataset === CSP_DATASET && + finding.rule?.benchmark?.version && + finding.rule?.benchmark?.id && + finding.rule?.id + ? application.getUrlForApp('security', { + path: generatePath(benchmarksNavigation.rules.path, { + benchmarkVersion: finding.rule.benchmark.version.split('v')[1], // removing the v from the version + benchmarkId: finding.rule.benchmark.id, + ruleId: finding.rule.id, + }), + }) + : undefined; switch (tab.id) { case 'overview': - return <OverviewTab data={findings} ruleFlyoutLink={ruleFlyoutLink} />; + return <OverviewTab data={finding} ruleFlyoutLink={ruleFlyoutLink} />; case 'rule': - return <RuleTab data={findings} ruleFlyoutLink={ruleFlyoutLink} />; + return <RuleTab data={finding} ruleFlyoutLink={ruleFlyoutLink} />; case 'table': - return <TableTab data={findings} />; + return <TableTab data={finding} />; case 'json': - return <JsonTab data={findings} />; + return <JsonTab data={finding} />; default: assertNever(tab); } }; +const isNativeCspFinding = (finding: CspFinding) => finding.data_stream.dataset === CSP_DATASET; + +const MissingFieldsCallout = ({ finding }: { finding: CspFinding }) => { + const { euiTheme } = useEuiTheme(); + const datasetDisplayName = + getDatasetDisplayName(finding.data_stream.dataset) || finding.data_stream.dataset; + + return ( + <EuiCallOut + style={{ + borderRadius: 4, + overflow: 'hidden', + }} + size="s" + iconType="iInCircle" + title={ + <span style={{ color: euiTheme.colors.text }}> + <FormattedMessage + id="xpack.csp.findings.findingsFlyout.calloutTitle" + defaultMessage="Some fields not provided by {datasource}" + values={{ + datasource: datasetDisplayName || 'the data source', + }} + /> + </span> + } + /> + ); +}; + export const FindingsRuleFlyout = ({ onClose, - findings, + finding, flyoutIndex, findingsCount, onPaginate, @@ -169,19 +237,19 @@ export const FindingsRuleFlyout = ({ const [tab, setTab] = useState<FindingsTab>(tabs[0]); const createMisconfigurationRuleFn = async (http: HttpSetup) => - await createDetectionRuleFromBenchmarkRule(http, findings.rule); + await createDetectionRuleFromBenchmarkRule(http, finding.rule); return ( <EuiFlyout onClose={onClose} data-test-subj={FINDINGS_FLYOUT}> <EuiFlyoutHeader> <EuiFlexGroup alignItems="center"> <EuiFlexItem grow={false}> - <CspEvaluationBadge type={findings.result.evaluation} /> + <CspEvaluationBadge type={finding.result?.evaluation} /> </EuiFlexItem> <EuiFlexItem grow style={{ minWidth: 0 }}> <EuiTitle size="m" className="eui-textTruncate"> - <EuiTextColor color="primary" title={findings.rule.name}> - {findings.rule.name} + <EuiTextColor color="primary" title={finding.rule?.name}> + {finding.rule?.name} </EuiTextColor> </EuiTitle> </EuiFlexItem> @@ -194,7 +262,7 @@ export const FindingsRuleFlyout = ({ > <CspInlineDescriptionList testId={FINDINGS_MISCONFIGS_FLYOUT_DESCRIPTION_LIST} - listItems={getFlyoutDescriptionList(findings)} + listItems={getFlyoutDescriptionList(finding)} /> </div> <EuiSpacer /> @@ -212,7 +280,12 @@ export const FindingsRuleFlyout = ({ </EuiTabs> </EuiFlyoutHeader> <EuiFlyoutBody key={tab.id}> - <FindingsTab tab={tab} findings={findings} /> + {!isNativeCspFinding(finding) && ['overview', 'rule'].includes(tab.id) && ( + <div style={{ marginBottom: 16 }}> + <MissingFieldsCallout finding={finding} /> + </div> + )} + <FindingsTab tab={tab} finding={finding} /> </EuiFlyoutBody> <EuiFlyoutFooter> <EuiFlexGroup diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/json_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/json_tab.tsx index 38697d3084f4f..30f8237de91b0 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/json_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/json_tab.tsx @@ -10,14 +10,12 @@ import { CodeEditor } from '@kbn/code-editor'; import { XJsonLang } from '@kbn/monaco'; import { CspFinding } from '../../../../common/schemas/csp_finding'; -const offsetTopHeight = 120; -const offsetBottomHeight = 72; - export const JsonTab = ({ data }: { data: CspFinding }) => ( - <div style={{ position: 'absolute', inset: 0, top: offsetTopHeight, bottom: offsetBottomHeight }}> + <div style={{ position: 'absolute', inset: 0 }}> <CodeEditor isCopyable allowFullScreen + enableFindAction languageId={XJsonLang.ID} value={JSON.stringify(data, null, 2)} options={{ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/overview_tab.tsx index 9835204768861..c23a6b7d82ddd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/overview_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/overview_tab.tsx @@ -13,7 +13,6 @@ import { EuiPanel, EuiSpacer, EuiText, - EuiToolTip, } from '@elastic/eui'; import React, { useMemo } from 'react'; import moment from 'moment'; @@ -21,6 +20,7 @@ import type { EuiDescriptionListProps, EuiAccordionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { isEmpty } from 'lodash'; +import { getDatasetDisplayName } from '../../../common/utils/get_dataset_display_name'; import { truthy } from '../../../../common/utils/helpers'; import { CSP_MOMENT_FORMAT } from '../../../common/constants'; import { @@ -31,26 +31,31 @@ import { import { useDataView } from '../../../common/api/use_data_view'; import { useKibana } from '../../../common/hooks/use_kibana'; import { CspFinding } from '../../../../common/schemas/csp_finding'; -import { CisKubernetesIcons, CodeBlock, CspFlyoutMarkdown } from './findings_flyout'; +import { + BenchmarkIcons, + CodeBlock, + CspFlyoutMarkdown, + EMPTY_VALUE, + RuleNameLink, +} from './findings_flyout'; import { FindingsDetectionRuleCounter } from './findings_detection_rule_counter'; type Accordion = Pick<EuiAccordionProps, 'title' | 'id' | 'initialIsOpen'> & Pick<EuiDescriptionListProps, 'listItems'>; -const getDetailsList = (data: CspFinding, ruleFlyoutLink: string, discoverIndexLink?: string) => [ +const getDetailsList = ( + data: CspFinding, + ruleFlyoutLink?: string, + discoverDataViewLink?: string +) => [ { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle', { defaultMessage: 'Rule Name', }), - description: ( - <EuiToolTip - position="top" - content={i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTooltip', { - defaultMessage: 'Manage Rule', - })} - > - <EuiLink href={ruleFlyoutLink}>{data.rule.name}</EuiLink> - </EuiToolTip> + description: data.rule?.name ? ( + <RuleNameLink ruleFlyoutLink={ruleFlyoutLink} ruleName={data.rule.name} /> + ) : ( + EMPTY_VALUE ), }, { @@ -63,43 +68,57 @@ const getDetailsList = (data: CspFinding, ruleFlyoutLink: string, discoverIndexL title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle', { defaultMessage: 'Rule Tags', }), - description: ( + description: data.rule?.tags?.length ? ( <> {data.rule.tags.map((tag) => ( <EuiBadge key={tag}>{tag}</EuiBadge> ))} </> + ) : ( + EMPTY_VALUE ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle', { defaultMessage: 'Evaluated at', }), - description: moment(data['@timestamp']).format(CSP_MOMENT_FORMAT), + description: data['@timestamp'] + ? moment(data['@timestamp']).format(CSP_MOMENT_FORMAT) + : EMPTY_VALUE, }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle', { defaultMessage: 'Framework Sources', }), - description: ( - <CisKubernetesIcons - benchmarkId={data.rule.benchmark.id} - benchmarkName={data.rule.benchmark.name} - /> - ), + description: + data.rule?.benchmark?.id && data.rule?.benchmark?.name ? ( + <BenchmarkIcons + benchmarkId={data.rule?.benchmark?.id} + benchmarkName={data.rule?.benchmark?.name} + /> + ) : ( + EMPTY_VALUE + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle', { - defaultMessage: 'CIS Section', + defaultMessage: 'Framework Section', }), - description: data.rule.section, + description: data.rule?.section ? data.rule?.section : EMPTY_VALUE, + }, + { + title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.sourceTitle', { + defaultMessage: 'Source', + }), + description: + getDatasetDisplayName(data.data_stream?.dataset) || data.data_stream?.dataset || EMPTY_VALUE, }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.indexTitle', { defaultMessage: 'Index', }), - description: discoverIndexLink ? ( - <EuiLink href={discoverIndexLink}>{LATEST_FINDINGS_INDEX_DEFAULT_NS}</EuiLink> + description: discoverDataViewLink ? ( + <EuiLink href={discoverDataViewLink}>{LATEST_FINDINGS_INDEX_DEFAULT_NS}</EuiLink> ) : ( LATEST_FINDINGS_INDEX_DEFAULT_NS ), @@ -109,33 +128,37 @@ const getDetailsList = (data: CspFinding, ruleFlyoutLink: string, discoverIndexL export const getRemediationList = (rule: CspFinding['rule']) => [ { title: '', - description: <CspFlyoutMarkdown>{rule.remediation}</CspFlyoutMarkdown>, + description: rule?.remediation ? ( + <CspFlyoutMarkdown>{rule?.remediation}</CspFlyoutMarkdown> + ) : ( + EMPTY_VALUE + ), + }, + { + title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.impactTitle', { + defaultMessage: 'Impact', + }), + description: rule?.impact ? <CspFlyoutMarkdown>{rule.impact}</CspFlyoutMarkdown> : EMPTY_VALUE, + }, + { + title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle', { + defaultMessage: 'Default Value', + }), + description: rule?.default_value ? ( + <CspFlyoutMarkdown>{rule.default_value}</CspFlyoutMarkdown> + ) : ( + EMPTY_VALUE + ), }, - ...(rule.impact - ? [ - { - title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.impactTitle', { - defaultMessage: 'Impact', - }), - description: <CspFlyoutMarkdown>{rule.impact}</CspFlyoutMarkdown>, - }, - ] - : []), - ...(rule.default_value - ? [ - { - title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle', { - defaultMessage: 'Default Value', - }), - description: <CspFlyoutMarkdown>{rule.default_value}</CspFlyoutMarkdown>, - }, - ] - : []), { title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle', { defaultMessage: 'Rationale', }), - description: <CspFlyoutMarkdown>{rule.rationale}</CspFlyoutMarkdown>, + description: rule?.rationale ? ( + <CspFlyoutMarkdown>{rule.rationale}</CspFlyoutMarkdown> + ) : ( + EMPTY_VALUE + ), }, ]; @@ -152,7 +175,7 @@ const getEvidenceList = ({ result }: CspFinding) => /> </EuiText> <EuiSpacer size={'s'} /> - <CodeBlock language="json">{JSON.stringify(result.evidence, null, 2)}</CodeBlock> + <CodeBlock language="json">{JSON.stringify(result?.evidence, null, 2)}</CodeBlock> </> ), }, @@ -163,20 +186,36 @@ export const OverviewTab = ({ ruleFlyoutLink, }: { data: CspFinding; - ruleFlyoutLink: string; + ruleFlyoutLink?: string; }) => { const { discover } = useKibana().services; const latestFindingsDataView = useDataView(LATEST_FINDINGS_INDEX_PATTERN); - const discoverIndexLink = useMemo( + // link will navigate to our dataview in discover, filtered by the data source of the finding + const discoverDataViewLink = useMemo( () => discover.locator?.getRedirectUrl({ - indexPatternId: latestFindingsDataView.data?.id, + dataViewId: latestFindingsDataView.data?.id, + ...(data.data_stream?.dataset && { + filters: [ + { + meta: { + type: 'phrase', + key: 'data_stream.dataset', + }, + query: { + match_phrase: { + 'data_stream.dataset': data.data_stream.dataset, + }, + }, + }, + ], + }), }), - [discover.locator, latestFindingsDataView.data?.id] + [data.data_stream?.dataset, discover.locator, latestFindingsDataView.data?.id] ); - const hasEvidence = !isEmpty(data.result.evidence); + const hasEvidence = !isEmpty(data.result?.evidence); const accordions: Accordion[] = useMemo( () => @@ -187,7 +226,7 @@ export const OverviewTab = ({ defaultMessage: 'Details', }), id: 'detailsAccordion', - listItems: getDetailsList(data, ruleFlyoutLink, discoverIndexLink), + listItems: getDetailsList(data, ruleFlyoutLink, discoverDataViewLink), }, { initialIsOpen: true, @@ -208,7 +247,7 @@ export const OverviewTab = ({ listItems: getEvidenceList(data), }, ].filter(truthy), - [data, discoverIndexLink, hasEvidence, ruleFlyoutLink] + [data, discoverDataViewLink, hasEvidence, ruleFlyoutLink] ); return ( diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/rule_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/rule_tab.tsx index 9b7b400a58196..6a7eea41410e9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/rule_tab.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/findings_flyout/rule_tab.tsx @@ -5,16 +5,16 @@ * 2.0. */ -import { EuiBadge, EuiDescriptionList, EuiLink, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiDescriptionList } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { CspFinding } from '../../../../common/schemas/csp_finding'; import { RulesDetectionRuleCounter } from '../../rules/rules_detection_rule_counter'; -import { CisKubernetesIcons, CspFlyoutMarkdown } from './findings_flyout'; +import { BenchmarkIcons, CspFlyoutMarkdown, EMPTY_VALUE, RuleNameLink } from './findings_flyout'; export const getRuleList = ( - rule: CspFinding['rule'], + rule?: CspFinding['rule'], ruleState = 'unmuted', ruleFlyoutLink?: string ) => [ @@ -22,31 +22,28 @@ export const getRuleList = ( title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.nameTitle', { defaultMessage: 'Name', }), - description: ruleFlyoutLink ? ( - <EuiToolTip - position="top" - content={i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.nameTooltip', { - defaultMessage: 'Manage Rule', - })} - > - <EuiLink href={ruleFlyoutLink}>{rule.name}</EuiLink> - </EuiToolTip> + description: rule?.name ? ( + <RuleNameLink ruleFlyoutLink={ruleFlyoutLink} ruleName={rule.name} /> ) : ( - rule.name + EMPTY_VALUE ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.descriptionTitle', { defaultMessage: 'Description', }), - description: <CspFlyoutMarkdown>{rule.description}</CspFlyoutMarkdown>, + description: rule?.description ? ( + <CspFlyoutMarkdown>{rule.description}</CspFlyoutMarkdown> + ) : ( + EMPTY_VALUE + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.AlertsTitle', { defaultMessage: 'Alerts', }), description: - ruleState === 'unmuted' ? ( + ruleState === 'unmuted' && rule?.benchmark?.name ? ( <RulesDetectionRuleCounter benchmarkRule={rule} /> ) : ( <FormattedMessage @@ -59,58 +56,73 @@ export const getRuleList = ( title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle', { defaultMessage: 'Tags', }), - description: ( + description: rule?.tags?.length ? ( <> {rule.tags.map((tag) => ( <EuiBadge key={tag}>{tag}</EuiBadge> ))} </> + ) : ( + EMPTY_VALUE ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.frameworkSourcesTitle', { defaultMessage: 'Framework Sources', }), - description: ( - <CisKubernetesIcons benchmarkId={rule.benchmark.id} benchmarkName={rule.benchmark.name} /> - ), + description: + rule?.benchmark?.id && rule?.benchmark?.name ? ( + <BenchmarkIcons benchmarkId={rule.benchmark.id} benchmarkName={rule.benchmark.name} /> + ) : ( + EMPTY_VALUE + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.cisSectionTitle', { - defaultMessage: 'CIS Section', + defaultMessage: 'Framework Section', }), - description: rule.section, + description: rule?.section || EMPTY_VALUE, }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.profileApplicabilityTitle', { defaultMessage: 'Profile Applicability', }), - description: <CspFlyoutMarkdown>{rule.profile_applicability}</CspFlyoutMarkdown>, + description: rule?.profile_applicability ? ( + <CspFlyoutMarkdown>{rule.profile_applicability}</CspFlyoutMarkdown> + ) : ( + EMPTY_VALUE + ), }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.benchmarkTitle', { defaultMessage: 'Benchmark', }), - description: rule.benchmark.name, + description: rule?.benchmark?.name || EMPTY_VALUE, }, { title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.auditTitle', { defaultMessage: 'Audit', }), - description: <CspFlyoutMarkdown>{rule.audit}</CspFlyoutMarkdown>, + description: rule?.audit ? <CspFlyoutMarkdown>{rule.audit}</CspFlyoutMarkdown> : EMPTY_VALUE, + }, + { + title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle', { + defaultMessage: 'References', + }), + description: rule?.references ? ( + <CspFlyoutMarkdown>{rule.references}</CspFlyoutMarkdown> + ) : ( + EMPTY_VALUE + ), }, - ...(rule.references - ? [ - { - title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle', { - defaultMessage: 'References', - }), - description: <CspFlyoutMarkdown>{rule.references}</CspFlyoutMarkdown>, - }, - ] - : []), ]; -export const RuleTab = ({ data, ruleFlyoutLink }: { data: CspFinding; ruleFlyoutLink: string }) => { +export const RuleTab = ({ + data, + ruleFlyoutLink, +}: { + data: CspFinding; + ruleFlyoutLink?: string; +}) => { return <EuiDescriptionList listItems={getRuleList(data.rule, ruleFlyoutLink)} />; }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts index 9f7ee15604f35..9472a7064a6a4 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/constants.ts @@ -126,5 +126,6 @@ export const defaultColumns: CloudSecurityDefaultColumn[] = [ { id: 'rule.benchmark.rule_number' }, { id: 'rule.name' }, { id: 'rule.section' }, + { id: 'data_stream.dataset' }, { id: '@timestamp' }, ]; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/findings_table_field_labels.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/findings_table_field_labels.ts index 1e4d0f7244af7..75ffaef8085e9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/findings_table_field_labels.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/findings_table_field_labels.ts @@ -33,7 +33,11 @@ export const findingsTableFieldLabels: Record<string, string> = { ), 'rule.section': i18n.translate( 'xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel', - { defaultMessage: 'CIS Section' } + { defaultMessage: 'Framework Section' } + ), + 'data_stream.dataset': i18n.translate( + 'xpack.csp.findings.findingsTable.findingsTableColumn.sourceColumnLabel', + { defaultMessage: 'Source' } ), '@timestamp': i18n.translate( 'xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel', diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx index cd49925798b3a..535a465917d44 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_group_renderer.tsx @@ -64,15 +64,15 @@ export const groupPanelRenderer: GroupPanelRenderer<FindingsGroupingAggregation> css={css` word-break: break-all; `} - title={bucket.resourceName?.buckets?.[0].key} + title={bucket.resourceName?.buckets?.[0]?.key} > - <strong>{bucket.key_as_string}</strong> {bucket.resourceName?.buckets?.[0].key} + <strong>{bucket.key_as_string}</strong> {bucket.resourceName?.buckets?.[0]?.key} </EuiTextBlockTruncate> </EuiText> </EuiFlexItem> <EuiFlexItem> <EuiText size="xs" color="subdued"> - {bucket.resourceSubType?.buckets?.[0].key} + {bucket.resourceSubType?.buckets?.[0]?.key} </EuiText> </EuiFlexItem> </EuiFlexGroup> @@ -93,8 +93,8 @@ export const groupPanelRenderer: GroupPanelRenderer<FindingsGroupingAggregation> </EuiFlexItem> <EuiFlexItem> <EuiText size="xs" color="subdued"> - {firstNonNullValue(bucket.benchmarkName?.buckets?.[0].key)}{' '} - {firstNonNullValue(bucket.benchmarkVersion?.buckets?.[0].key)} + {firstNonNullValue(bucket.benchmarkName?.buckets?.[0]?.key)}{' '} + {firstNonNullValue(bucket.benchmarkVersion?.buckets?.[0]?.key)} </EuiText> </EuiFlexItem> </EuiFlexGroup> @@ -185,7 +185,7 @@ const FindingsCountComponent = ({ bucket }: { bucket: RawBucket<FindingsGrouping <EuiToolTip content={bucket.doc_count}> <EuiBadge css={css` - margin-left: ${euiTheme.size.s}}; + margin-left: ${euiTheme.size.s}; `} color="hollow" data-test-subj={FINDINGS_GROUPING_COUNTER} @@ -208,7 +208,7 @@ const ComplianceBarComponent = ({ bucket }: { bucket: RawBucket<FindingsGrouping size="l" overrideCss={css` width: 104px; - margin-left: ${euiTheme.size.s}}; + margin-left: ${euiTheme.size.s}; `} totalFailed={totalFailed} totalPassed={totalPassed} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx index a93907825bc7d..2d9e1c3f25ae1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/latest_findings_table.tsx @@ -11,6 +11,8 @@ import { DataTableRecord } from '@kbn/discover-utils/types'; import { HttpSetup } from '@kbn/core-http-browser'; import { i18n } from '@kbn/i18n'; import { EuiDataGridCellValueElementProps, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { CspFinding } from '../../../../common/schemas/csp_finding'; +import { getDatasetDisplayName } from '../../../common/utils/get_dataset_display_name'; import * as TEST_SUBJECTS from '../test_subjects'; import { FindingsDistributionBar } from '../layout/findings_distribution_bar'; import { ErrorCallout } from '../layout/error_callout'; @@ -19,7 +21,6 @@ import { getDefaultQuery, defaultColumns } from './constants'; import { useLatestFindingsTable } from './use_latest_findings_table'; import { TimestampTableCell } from '../../../components/timestamp_table_cell'; import { CspEvaluationBadge } from '../../../components/csp_evaluation_badge'; -import { CspFinding } from '../../../../common/schemas/csp_finding'; import { FindingsRuleFlyout } from '../findings_flyout/findings_flyout'; import { createDetectionRuleFromBenchmarkRule } from '../utils/create_detection_rule_from_benchmark'; import { findingsTableFieldLabels } from './findings_table_field_labels'; @@ -30,42 +31,26 @@ interface LatestFindingsTableProps { showDistributionBar?: boolean; nonPersistedFilters?: Filter[]; } + /** * Type Guard for checking if the given source is a CspFinding */ const isCspFinding = (source: Record<string, any> | undefined): source is CspFinding => { - return source?.result?.evaluation !== undefined; -}; - -const getCspFinding = (source: Record<string, any> | undefined): CspFinding | false => { - return isCspFinding(source) && (source as CspFinding); + return source?.data_stream?.dataset !== undefined; }; -/** - * This Wrapper component renders the children if the given row is a CspFinding - * it uses React's Render Props pattern - */ -const CspFindingRenderer = ({ - row, - children, -}: { - row: DataTableRecord; - children: ({ finding }: { finding: CspFinding }) => JSX.Element; -}) => { - const finding = getCspFinding(row.raw._source); - if (!finding) return <></>; - return children({ finding }); +const getCspFinding = (source: Record<string, any> | undefined): CspFinding | undefined => { + if (isCspFinding(source)) return source as CspFinding; }; /** * Flyout component for the latest findings table */ const flyoutComponent = (row: DataTableRecord, onCloseFlyout: () => void): JSX.Element => { - return ( - <CspFindingRenderer row={row}> - {({ finding }) => <FindingsRuleFlyout findings={finding} onClose={onCloseFlyout} />} - </CspFindingRenderer> - ); + const finding = row.raw._source; + if (!finding || !isCspFinding(finding)) return <></>; + + return <FindingsRuleFlyout finding={finding} onClose={onCloseFlyout} />; }; const title = i18n.translate('xpack.csp.findings.latestFindings.tableRowTypeLabel', { @@ -73,16 +58,23 @@ const title = i18n.translate('xpack.csp.findings.latestFindings.tableRowTypeLabe }); const customCellRenderer = (rows: DataTableRecord[]) => ({ - 'result.evaluation': ({ rowIndex }: EuiDataGridCellValueElementProps) => ( - <CspFindingRenderer row={rows[rowIndex]}> - {({ finding }) => <CspEvaluationBadge type={finding.result.evaluation} />} - </CspFindingRenderer> - ), - '@timestamp': ({ rowIndex }: EuiDataGridCellValueElementProps) => ( - <CspFindingRenderer row={rows[rowIndex]}> - {({ finding }) => <TimestampTableCell timestamp={finding['@timestamp']} />} - </CspFindingRenderer> - ), + 'result.evaluation': ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const finding = getCspFinding(rows[rowIndex].raw._source); + + return <CspEvaluationBadge type={finding?.result?.evaluation} />; + }, + 'data_stream.dataset': ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const finding = getCspFinding(rows[rowIndex].raw._source); + const source = getDatasetDisplayName(finding?.data_stream?.dataset); + + return <>{source || finding?.data_stream?.dataset || ''}</>; + }, + '@timestamp': ({ rowIndex }: EuiDataGridCellValueElementProps) => { + const finding = getCspFinding(rows[rowIndex].raw._source); + if (!finding?.['@timestamp']) return <></>; + + return <TimestampTableCell timestamp={finding['@timestamp']} />; + }, }); export const LatestFindingsTable = ({ diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts index a9bb7540ab12e..d609617824269 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts @@ -8,7 +8,6 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import { number } from 'io-ts'; import { lastValueFrom } from 'rxjs'; import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types'; -import type { Pagination } from '@elastic/eui'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { buildDataTableRecord } from '@kbn/discover-utils'; import { EsHitRecord } from '@kbn/discover-utils/types'; @@ -32,11 +31,6 @@ interface UseFindingsOptions extends FindingsBaseEsQuery { pageSize: number; } -export interface FindingsGroupByNoneQuery { - pageIndex: Pagination['pageIndex']; - sort: any; -} - type LatestFindingsRequest = IKibanaSearchRequest<estypes.SearchRequest>; type LatestFindingsResponse = IKibanaSearchResponse< estypes.SearchResponse<CspFinding, FindingsAggs> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx index 56ca9687551d8..811abe1bd1ccc 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_distribution_bar.tsx @@ -6,16 +6,8 @@ */ import React from 'react'; import { css } from '@emotion/react'; -import { - EuiHealth, - EuiBadge, - EuiSpacer, - EuiFlexGroup, - useEuiTheme, - EuiTextColor, -} from '@elastic/eui'; +import { EuiHealth, EuiBadge, EuiSpacer, EuiFlexGroup, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { getAbbreviatedNumber } from '../../../common/utils/get_abbreviated_number'; import { RULE_FAILED, RULE_PASSED } from '../../../../common/constants'; import { statusColors } from '../../../common/constants'; @@ -35,31 +27,6 @@ const I18N_FAILED_FINDINGS = i18n.translate('xpack.csp.findings.distributionBar. defaultMessage: 'Failed Findings', }); -export const CurrentPageOfTotal = ({ - pageEnd, - pageStart, - total, - type, -}: { - pageEnd: number; - pageStart: number; - total: number; - type: string; -}) => ( - <EuiTextColor color="subdued"> - <FormattedMessage - id="xpack.csp.findings.distributionBar.showingPageOfTotalLabel" - defaultMessage="Showing {pageStart}-{pageEnd} of {total} {type}" - values={{ - pageStart: <b>{pageStart}</b>, - pageEnd: <b>{pageEnd}</b>, - total: <b>{getAbbreviatedNumber(total)}</b>, - type, - }} - /> - </EuiTextColor> -); - export const FindingsDistributionBar = (props: Props) => ( <div> <Counters {...props} /> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.tsx deleted file mode 100644 index d8d8768df0cf1..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/layout/findings_group_by_selector.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, { useMemo } from 'react'; -import { EuiComboBox, EuiFormLabel, type EuiComboBoxOptionOption } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useHistory } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import type { FindingsGroupByKind } from '../../../common/types'; -import { findingsNavigation } from '../../../common/navigation/constants'; -import * as TEST_SUBJECTS from '../test_subjects'; - -const getGroupByOptions = (): Array<EuiComboBoxOptionOption<FindingsGroupByKind>> => [ - { - value: 'default', - label: i18n.translate('xpack.csp.findings.groupBySelector.groupByNoneLabel', { - defaultMessage: 'None', - }), - }, - { - value: 'resource', - label: i18n.translate('xpack.csp.findings.groupBySelector.groupByResourceIdLabel', { - defaultMessage: 'Resource', - }), - }, -]; - -interface Props { - type: FindingsGroupByKind; - pathnameHandler?: (opts: Array<EuiComboBoxOptionOption<FindingsGroupByKind>>) => string; -} - -const getFindingsGroupPath = (opts: Array<EuiComboBoxOptionOption<FindingsGroupByKind>>) => { - const [firstOption] = opts; - - switch (firstOption?.value) { - case 'resource': - return findingsNavigation.findings_by_resource.path; - case 'default': - default: - return findingsNavigation.findings_default.path; - } -}; - -export const FindingsGroupBySelector = ({ - type, - pathnameHandler = getFindingsGroupPath, -}: Props) => { - const groupByOptions = useMemo(getGroupByOptions, []); - const history = useHistory(); - - const onChange = (options: Array<EuiComboBoxOptionOption<FindingsGroupByKind>>) => - history.push({ pathname: pathnameHandler(options) }); - - return ( - <EuiComboBox - data-test-subj={TEST_SUBJECTS.FINDINGS_GROUP_BY_SELECTOR} - prepend={<GroupByLabel />} - singleSelection={{ asPlainText: true }} - options={groupByOptions} - selectedOptions={groupByOptions.filter((o) => o.value === type)} - onChange={onChange} - isClearable={false} - compressed - /> - ); -}; - -const GroupByLabel = () => ( - <EuiFormLabel> - <FormattedMessage - id="xpack.csp.findings.groupBySelector.groupByLabel" - defaultMessage="Group by" - /> - </EuiFormLabel> -); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts index b8a670e8ba58b..d27a7739ab9a9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/test_subjects.ts @@ -6,33 +6,11 @@ */ export const FINDINGS_FLYOUT = 'findings_flyout'; -export const FINDINGS_TABLE_EXPAND_COLUMN = 'findings_table_expand_column'; -export const FINDINGS_TABLE = 'findings_table'; -export const FINDINGS_CONTAINER = 'findings_container'; -export const FINDINGS_BY_RESOURCE_CONTAINER = 'findings_by_resource_container'; -export const FINDINGS_BY_RESOURCE_TABLE_RESOURCE_ID_COLUMN = - 'findings_by_resource_table_resource_id_column'; -export const FINDINGS_BY_RESOURCE_TABLE = 'findings_by_resource_table'; -export const getFindingsByResourceTableRowTestId = (id: string) => - `findings_resource_table_row_${id}`; export const LATEST_FINDINGS_CONTAINER = 'latest_findings_container'; export const LATEST_FINDINGS_TABLE = 'latest_findings_table'; -export const FINDINGS_GROUP_BY_SELECTOR = 'findings_group_by_selector'; export const FINDINGS_GROUPING_COUNTER = 'findings_grouping_counter'; -export const getFindingsTableRowTestId = (id: string) => `findings_table_row_${id}`; -export const getFindingsTableCellTestId = (columnId: string, rowId: string) => - `findings_table_cell_${columnId}_${rowId}`; - -export const FINDINGS_TABLE_CELL_ADD_FILTER = 'findings_table_cell_add_filter'; -export const FINDINGS_TABLE_CELL_ADD_NEGATED_FILTER = 'findings_table_cell_add_negated_filter'; - -export const RESOURCES_FINDINGS_CONTAINER = 'resources_findings_container'; -export const RESOURCES_FINDINGS_TABLE = 'resource_findings_table'; -export const getResourceFindingsTableRowTestId = (id: string) => - `resource_findings_table_row_${id}`; - export const FINDINGS_MISCONFIGS_FLYOUT_DESCRIPTION_LIST = 'misconfigs-findings-flyout-description-list'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts deleted file mode 100644 index 66da177e1cea8..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/generate_findings_tags.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { CspFinding } from '../../../../common/schemas/csp_finding'; - -const CSP_RULE_TAG = 'Cloud Security'; -const CNVM_RULE_TAG_USE_CASE = 'Use Case: Configuration Audit'; -const CNVM_RULE_TAG_DATA_SOURCE_PREFIX = 'Data Source: '; - -const STATIC_RULE_TAGS = [CSP_RULE_TAG, CNVM_RULE_TAG_USE_CASE]; - -export const generateFindingsTags = (finding: CspFinding) => { - return [STATIC_RULE_TAGS] - .concat(finding.rule.tags) - .concat( - finding.rule.benchmark.posture_type - ? [ - `${CNVM_RULE_TAG_DATA_SOURCE_PREFIX}${finding.rule.benchmark.posture_type.toUpperCase()}`, - ] - : [] - ) - .concat( - finding.rule.benchmark.posture_type === 'cspm' ? ['Domain: Cloud'] : ['Domain: Container'] - ) - .flat(); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts index 3e0277d7cd4c1..67b37ad1001fd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/utils/utils.ts @@ -6,19 +6,8 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { EuiThemeComputed } from '@elastic/eui'; -import type { CspFinding } from '../../../../common/schemas/csp_finding'; export { getFilters } from './get_filters'; -export const getFindingsPageSizeInfo = ({ - currentPageSize, - pageIndex, - pageSize, -}: Record<'pageIndex' | 'pageSize' | 'currentPageSize', number>) => ({ - pageStart: pageIndex * pageSize + 1, - pageEnd: pageIndex * pageSize + currentPageSize, -}); - export const getFindingsCountAggQuery = () => ({ count: { terms: { field: 'result.evaluation' } }, }); @@ -34,14 +23,3 @@ export const getAggregationCount = ( failed: failed?.doc_count || 0, }; }; - -const isSelectedRow = (row: CspFinding, selected?: CspFinding) => - row.resource.id === selected?.resource.id && row.rule.id === selected?.rule.id; - -export const getSelectedRowStyle = ( - theme: EuiThemeComputed, - row: CspFinding, - selected?: CspFinding -): React.CSSProperties => ({ - background: isSelectedRow(row, selected) ? theme.colors.highlight : undefined, -}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx index 7abcd0c37060b..66829ee739010 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx @@ -22,9 +22,6 @@ import { useLicenseManagementLocatorApi } from '../../common/api/use_license_man import { useCspBenchmarkIntegrationsV2 } from '../benchmarks/use_csp_benchmark_integrations'; import * as TEST_SUBJECTS from './test_subjects'; -jest.mock('./use_csp_integration', () => ({ - useCspIntegrationInfo: jest.fn(), -})); jest.mock('../../common/api/use_setup_status_api'); jest.mock('../../common/api/use_license_management_locator_api'); jest.mock('../../common/hooks/use_subscription_status'); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx index 83745e5f5d113..fc714263f38be 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx @@ -42,8 +42,6 @@ export const RULES_SELECT_ALL_RULES = 'select-all-rules-button'; export const RULES_CLEAR_ALL_RULES_SELECTION = 'clear-rules-selection-button'; export const RULES_DISABLED_FILTER = 'rules-disabled-filter'; export const RULES_ENABLED_FILTER = 'rules-enabled-filter'; -export const CIS_SECTION_FILTER = 'cis-section-filter'; -export const RULE_NUMBER_FILTER = 'rule-number-filter'; interface RulesTableToolbarProps { search: (value: string) => void; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts index 43b209c6ce7d4..eb723c4e7894a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts @@ -6,9 +6,7 @@ */ export const CSP_RULES_CONTAINER = 'csp_rules_container'; -export const CSP_RULES_SHARED_VALUES = 'csp_rules_shared_values'; -export const CSP_RULES_TABLE_ITEM_SWITCH = 'csp_rules_table_item_switch'; -export const CSP_RULES_SAVE_BUTTON = 'csp_rules_table_save_button'; + export const CSP_RULES_TABLE = 'csp_rules_table'; export const CSP_RULES_TABLE_ROW_ITEM_NAME = 'csp_rules_table_row_item_name'; export const CSP_RULES_FLYOUT_CONTAINER = 'csp_rules_flyout_container'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts index d580a7719ed0a..fd8c5bf794935 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts @@ -20,7 +20,6 @@ export type RulesQuery = Pick< FindCspBenchmarkRuleRequest, 'section' | 'search' | 'page' | 'perPage' | 'ruleNumber' | 'sortField' | 'sortOrder' >; -export type RulesQueryResult = ReturnType<typeof useFindCspBenchmarkRule>; export const useFindCspBenchmarkRule = ( { search, page, perPage, section, ruleNumber, sortField, sortOrder }: RulesQuery, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.tsx deleted file mode 100644 index 45d4743490e3f..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_integration.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { useQuery } from '@tanstack/react-query'; -import { - type CopyAgentPolicyResponse, - type GetOnePackagePolicyResponse, - packagePolicyRouteService, - agentPolicyRouteService, - API_VERSIONS, -} from '@kbn/fleet-plugin/common'; -import { useKibana } from '../../common/hooks/use_kibana'; -import { PageUrlParams } from '../../../common/types/rules/v3'; - -export const useCspIntegrationInfo = ({ packagePolicyId, policyId }: PageUrlParams) => { - const { http } = useKibana().services; - - return useQuery(['cspBenchmarkRuleInfo', { packagePolicyId, policyId }], () => - Promise.all([ - http - .get<GetOnePackagePolicyResponse>(packagePolicyRouteService.getInfoPath(packagePolicyId), { - version: API_VERSIONS.public.v1, - }) - .then((response) => response.item), - http - .get<CopyAgentPolicyResponse>(agentPolicyRouteService.getInfoPath(policyId), { - version: API_VERSIONS.public.v1, - }) - .then((response) => response.item), - ]) - ); -}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx index 51dc1e9009502..351cecd83d502 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/latest_vulnerabilities_group_renderer.tsx @@ -70,9 +70,9 @@ export const groupPanelRenderer: GroupPanelRenderer<VulnerabilitiesGroupingAggre css={css` word-break: break-all; `} - title={bucket.resourceId?.buckets?.[0].key} + title={bucket.resourceId?.buckets?.[0]?.key} > - <strong>{bucket.key_as_string}</strong> {bucket.resourceId?.buckets?.[0].key} + <strong>{bucket.key_as_string}</strong> {bucket.resourceId?.buckets?.[0]?.key} </EuiTextBlockTruncate> </EuiText> </EuiFlexItem> @@ -150,7 +150,7 @@ const VulnerabilitiesCountComponent = ({ <EuiToolTip content={bucket.doc_count}> <EuiBadge css={css` - margin-left: ${euiTheme.size.s}}; + margin-left: ${euiTheme.size.s}; `} color="hollow" data-test-subj={VULNERABILITIES_GROUPING_COUNTER} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts index 8ad512f8a41ee..d57f86171e4b4 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/test_subjects.ts @@ -5,12 +5,10 @@ * 2.0. */ -export const FINDINGS_VULNERABILITY_FLYOUT = 'vulnerability_findings_flyout'; export const FINDINGS_VULNERABILITY_FLYOUT_DESCRIPTION_LIST = 'vulnerability-flyout-description-list'; export const JSON_TAB_VULNERABILITY_FLYOUT = 'vulnerability_json_tab_flyout'; export const OVERVIEW_TAB_VULNERABILITY_FLYOUT = 'vulnerability_overview_tab_flyout'; -export const SEVERITY_STATUS_VULNERABILITY_FLYOUT = 'vulnerability_severity_status_flyout'; export const TAB_ID_VULNERABILITY_FLYOUT = (tabId: string) => `vulnerability-finding-flyout-tab-${tabId}`; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts index cc5392bd6da66..2864ff7f29005 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/translations.ts @@ -9,18 +9,6 @@ import { i18n } from '@kbn/i18n'; import { VULNERABILITY_GROUPING_OPTIONS } from '../../common/constants'; -export const FILTER_IN = i18n.translate('xpack.csp.vulnerabilities.table.filterIn', { - defaultMessage: 'Filter in', -}); -export const FILTER_OUT = i18n.translate('xpack.csp.vulnerabilities.table.filterOut', { - defaultMessage: 'Filter out', -}); -export const SEARCH_BAR_PLACEHOLDER = i18n.translate( - 'xpack.csp.vulnerabilities.searchBar.placeholder', - { - defaultMessage: 'Search vulnerabilities (eg. vulnerability.severity : "CRITICAL" )', - } -); export const VULNERABILITIES = i18n.translate('xpack.csp.vulnerabilities', { defaultMessage: 'Vulnerabilities', }); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts index 14e9fbae28d41..e0c97ce6ff76d 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { VectorScoreBase, CspVulnerabilityFinding } from '../../../common/schemas'; +import { VectorScoreBase } from '../../../common/schemas'; export type Vendor = 'NVD' | 'Red Hat' | 'GHSA'; @@ -19,29 +19,3 @@ export interface Vector { vector: string; score: number | undefined; } - -export interface VulnerabilitiesQueryData { - page: CspVulnerabilityFinding[]; - total: number; -} - -export interface VulnerabilitiesByResourceQueryData { - page: Array<{ - resource: { - id: string; - name: string; - }; - cloud: { - region: string; - }; - vulnerabilities_count: number; - severity_map: { - critical: number; - high: number; - medium: number; - low: number; - }; - }>; - total: number; - total_vulnerabilities: number; -} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts index d184b0ed568a4..780cd539305b3 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/custom_sort_script.ts @@ -5,43 +5,6 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -export const severitySchemaConfig = { - type: 'severitySchema', - detector() { - return 0; // this schema is always explicitly defined - }, - sortTextAsc: i18n.translate('xpack.csp.vulnerabilityTable.column.sortAscending', { - defaultMessage: 'Low -> Critical', - }), - sortTextDesc: i18n.translate('xpack.csp.vulnerabilityTable.column.sortDescending', { - defaultMessage: 'Critical -> Low', - }), - icon: 'dot', - color: '', -}; - -export const severitySortScript = (direction: string) => ({ - _script: { - type: 'number', - script: { - lang: 'painless', - inline: - "if(doc.containsKey('vulnerability.severity') && !doc['vulnerability.severity'].empty && doc['vulnerability.severity'].size()!=0 && doc['vulnerability.severity'].value!=null && params.scores.containsKey(doc['vulnerability.severity'].value)) { return params.scores[doc['vulnerability.severity'].value];} return 0;", - params: { - scores: { - LOW: 1, - MEDIUM: 2, - HIGH: 3, - CRITICAL: 4, - }, - }, - }, - order: direction, - }, -}); - /** * Generates Painless sorting in case-insensitive manner */ diff --git a/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts index 66c0f6f20328a..1fec1c76430eb 100644 --- a/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts +++ b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts @@ -4,63 +4,3 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { EcsEvent } from '@elastic/ecs'; -import Chance from 'chance'; -import { CspFinding } from '../../../common/schemas/csp_finding'; - -const chance = new Chance(); - -export const getFindingsFixture = (): CspFinding & { id: string } => ({ - cluster_id: chance.guid(), - id: chance.word(), - result: { - expected: { - source: {}, - }, - evaluation: chance.weighted(['passed', 'failed'], [0.5, 0.5]), - evidence: { - filemode: chance.word(), - }, - }, - rule: { - audit: chance.paragraph(), - benchmark: { - name: 'CIS Kubernetes', - version: '1.6.0', - id: 'cis_k8s', - rule_number: '1.1.1', - posture_type: 'kspm', - }, - default_value: chance.sentence(), - description: chance.paragraph(), - id: chance.guid(), - impact: chance.word(), - name: chance.string(), - profile_applicability: chance.sentence(), - rationale: chance.paragraph(), - references: chance.paragraph(), - rego_rule_id: 'cis_X_X_X', - remediation: chance.word(), - section: chance.sentence(), - tags: [], - version: '1.0', - }, - agent: { - id: chance.string(), - name: chance.string(), - type: chance.string(), - version: chance.string(), - }, - resource: { - name: chance.string(), - type: chance.string(), - raw: {} as any, - sub_type: chance.string(), - id: chance.string(), - }, - host: {} as any, - ecs: {} as any, - event: {} as EcsEvent, - '@timestamp': new Date().toISOString(), -}); diff --git a/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts b/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.ts deleted file mode 100644 index 1edffe7af2988..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/test/fixtures/navigation_item.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 Chance from 'chance'; -import type { CspPageNavigationItem } from '../../common/navigation/types'; - -type CreateNavigationItemFixtureInput = { chance?: Chance.Chance } & Partial<CspPageNavigationItem>; -export const createPageNavigationItemFixture = ({ - chance = new Chance(), - name = chance.word(), - path = `/${chance.word()}`, - disabled = undefined, - id = 'cloud_security_posture-dashboard', -}: CreateNavigationItemFixtureInput = {}): CspPageNavigationItem => ({ - name, - path, - disabled, - id, -}); diff --git a/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts index 2073f7b146275..7948fd9d90578 100644 --- a/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts +++ b/x-pack/plugins/cloud_security_posture/server/create_indices/ingest_pipelines.ts @@ -9,7 +9,6 @@ import type { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/ty import { CSP_INGEST_TIMESTAMP_PIPELINE, CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE, - CSP_LATEST_VULNERABILITIES_INGEST_TIMESTAMP_PIPELINE, } from '../../common/constants'; export const scorePipelineIngestConfig: IngestPutPipelineRequest = { @@ -53,24 +52,3 @@ export const latestFindingsPipelineIngestConfig: IngestPutPipelineRequest = { }, ], }; - -export const latestVulnerabilitiesPipelineIngestConfig: IngestPutPipelineRequest = { - id: CSP_LATEST_VULNERABILITIES_INGEST_TIMESTAMP_PIPELINE, - description: 'Pipeline for cloudbeat latest vulnerabilities index', - processors: [ - { - set: { - field: 'event.ingested', - value: '{{_ingest.timestamp}}', - }, - }, - ], - on_failure: [ - { - set: { - field: 'error.message', - value: '{{ _ingest.on_failure_message }}', - }, - }, - ], -}; diff --git a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts index 370c9676099d6..d5db37879554b 100644 --- a/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/lib/telemetry/collectors/types.ts @@ -180,10 +180,6 @@ export interface KubernetesVersion { metrics: { 'cloudbeat.kubernetes.version': string }; } -export interface PackagePolicyId { - metrics: { 'cloud_security_posture.package_policy.id': string }; -} - export interface LatestDocTimestamp { metrics: { '@timestamp': string }; } diff --git a/x-pack/plugins/cloud_security_posture/server/plugin.test.ts b/x-pack/plugins/cloud_security_posture/server/plugin.test.ts index 5b755bf097128..9b6d970640480 100644 --- a/x-pack/plugins/cloud_security_posture/server/plugin.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/plugin.test.ts @@ -41,6 +41,7 @@ import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME } from '../common/constants'; import Chance from 'chance'; import type { AwaitedProperties } from '@kbn/utility-types'; import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { createIndexPatternsStartMock } from '@kbn/data-views-plugin/server/mocks'; import { ElasticsearchClient, RequestHandlerContext, @@ -82,6 +83,7 @@ describe('Cloud Security Posture Plugin', () => { taskManager: taskManagerMock.createStart(), security: securityMock.createStart(), licensing: licensingMock.createStart(), + dataViews: createIndexPatternsStartMock(), }; const contextMock = coreMock.createCustomRequestHandlerContext(mockRouteContext); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts index 31b80b880bcc9..63a9201b1f265 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts @@ -45,7 +45,7 @@ export const defineBulkActionCspBenchmarkRulesRoute = (router: CspRouter) => access: 'internal', path: CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH, options: { - tags: ['access:cloud-security-posture-read'], + tags: ['access:cloud-security-posture-all'], }, }) .addVersion( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts index 026cf68819ab2..52ff94ad7086c 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts @@ -15,10 +15,6 @@ import { } from '../../../common/constants'; import { CspRouter } from '../../types'; -export interface VulnerabilitiesStatisticsQueryResult { - total: number; -} - const DEFAULT_ALERTS_INDEX = '.alerts-security.alerts-default' as const; export const getDetectionEngineAlertsCountByRuleTags = async ( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts b/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts index 7b46e4ef113c3..51d9e591b1987 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/setup_routes.ts @@ -5,9 +5,13 @@ * 2.0. */ -import type { CoreSetup, Logger } from '@kbn/core/server'; +import { type CoreSetup, type Logger } from '@kbn/core/server'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE } from '../../common/constants'; + +import { + INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE, + CLOUD_SECURITY_INTERTAL_PREFIX_ROUTE_PATH, +} from '../../common/constants'; import type { CspRequestHandlerContext, CspServerPluginStart, @@ -22,6 +26,7 @@ import { defineFindCspBenchmarkRuleRoute } from './benchmark_rules/find/find'; import { defineGetDetectionEngineAlertsStatus } from './detection_engine/get_detection_engine_alerts_count_by_rule_tags'; import { defineBulkActionCspBenchmarkRulesRoute } from './benchmark_rules/bulk_action/bulk_action'; import { defineGetCspBenchmarkRulesStatesRoute } from './benchmark_rules/get_states/get_states'; +import { setupCdrDataViews } from '../saved_objects/data_views'; /** * 1. Registers routes @@ -46,6 +51,22 @@ export function setupRoutes({ defineBulkActionCspBenchmarkRulesRoute(router); defineGetCspBenchmarkRulesStatesRoute(router); + core.http.registerOnPreRouting(async (request, response, toolkit) => { + if (request.url.pathname.includes(CLOUD_SECURITY_INTERTAL_PREFIX_ROUTE_PATH)) { + try { + const [coreStart, startDeps] = await core.getStartServices(); + const esClient = coreStart.elasticsearch.client.asInternalUser; + const soClient = coreStart.savedObjects.createInternalRepository(); + const spaces = startDeps.spaces?.spacesService; + const dataViews = startDeps.dataViews; + await setupCdrDataViews(esClient, soClient, spaces, dataViews, request, logger); + } catch (err) { + logger.error(`Failed to create CDR data views: ${err}`); + } + } + return toolkit.next(); + }); + core.http.registerRouteHandlerContext<CspRequestHandlerContext, typeof PLUGIN_ID>( PLUGIN_ID, async (context, request) => { diff --git a/x-pack/plugins/cloud_security_posture/server/saved_objects/data_views.ts b/x-pack/plugins/cloud_security_posture/server/saved_objects/data_views.ts new file mode 100644 index 0000000000000..b18db2bb3a25a --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/saved_objects/data_views.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + ElasticsearchClient, + ISavedObjectsRepository, + SavedObject, + type KibanaRequest, + type Logger, +} from '@kbn/core/server'; +import { DataViewAttributes } from '@kbn/data-views-plugin/common'; +import { SpacesServiceStart } from '@kbn/spaces-plugin/server'; +import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; + +import { + CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX, + CDR_MISCONFIGURATIONS_DATA_VIEW_NAME, + CDR_MISCONFIGURATIONS_INDEX_PATTERN, + CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX, + CDR_VULNERABILITIES_DATA_VIEW_NAME, + CDR_VULNERABILITIES_INDEX_PATTERN, +} from '../../common/constants'; + +const DATA_VIEW_TIME_FIELD = '@timestamp'; +const DEFAULT_SPACE_ID = 'default'; + +const getDataViewSafe = async ( + soClient: ISavedObjectsRepository, + currentSpaceId: string, + currentSpaceDataViewId: string +): Promise<SavedObject<DataViewAttributes> | undefined> => { + try { + const dataView = await soClient.get<DataViewAttributes>( + 'index-pattern', + currentSpaceDataViewId, + { + namespace: currentSpaceId, + } + ); + + return dataView; + } catch (e) { + return; + } +}; + +const getCurrentSpaceId = ( + spacesService: SpacesServiceStart | undefined, + request: KibanaRequest +): string => { + return spacesService?.getSpaceId(request) || DEFAULT_SPACE_ID; +}; + +export const installDataView = async ( + esClient: ElasticsearchClient, + soClient: ISavedObjectsRepository, + spacesService: SpacesServiceStart | undefined, + dataViewsService: DataViewsServerPluginStart, + request: KibanaRequest, + dataViewName: string, + indexPattern: string, + dataViewId: string, + logger: Logger +) => { + try { + const currentSpaceId = await getCurrentSpaceId(spacesService, request); + const currentSpaceDataViewId = `${dataViewId}-${currentSpaceId}`; + + const doesDataViewExist = await getDataViewSafe( + soClient, + currentSpaceId, + currentSpaceDataViewId + ); + + if (doesDataViewExist) return; + + logger.info(`Creating and saving data view with ID: ${currentSpaceDataViewId}`); + + const dataViewsClient = await dataViewsService.dataViewsServiceFactory( + soClient, + esClient, + request, + true + ); + await dataViewsClient.createAndSave( + { + id: currentSpaceDataViewId, + title: indexPattern, + name: `${dataViewName} - ${currentSpaceId} `, + namespaces: [currentSpaceId], + allowNoIndex: true, + timeFieldName: DATA_VIEW_TIME_FIELD, + }, + true + ); + } catch (error) { + logger.error(`Failed to setup data view`, error); + } +}; + +export const setupCdrDataViews = async ( + esClient: ElasticsearchClient, + soClient: ISavedObjectsRepository, + spacesService: SpacesServiceStart | undefined, + dataViewsService: DataViewsServerPluginStart, + request: KibanaRequest, + logger: Logger +) => { + await installDataView( + esClient, + soClient, + spacesService, + dataViewsService, + request, + CDR_MISCONFIGURATIONS_DATA_VIEW_NAME, + CDR_MISCONFIGURATIONS_INDEX_PATTERN, + CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX, + logger + ); + + await installDataView( + esClient, + soClient, + spacesService, + dataViewsService, + request, + CDR_VULNERABILITIES_DATA_VIEW_NAME, + CDR_VULNERABILITIES_INDEX_PATTERN, + CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX, + logger + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/server/types.ts b/x-pack/plugins/cloud_security_posture/server/types.ts index 15dee7c9941d0..1190c0a1c5556 100644 --- a/x-pack/plugins/cloud_security_posture/server/types.ts +++ b/x-pack/plugins/cloud_security_posture/server/types.ts @@ -10,6 +10,7 @@ import type { PluginStart as DataPluginStart, } from '@kbn/data-plugin/server'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/server'; +import { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import { TaskManagerSetupContract, TaskManagerStartContract, @@ -21,9 +22,6 @@ import type { Logger, SavedObjectsClientContract, IScopedClusterClient, - KibanaResponseFactory, - RequestHandler, - RouteMethod, } from '@kbn/core/server'; import type { AgentService, @@ -36,6 +34,7 @@ import type { FleetStartContract, FleetRequestHandlerContext } from '@kbn/fleet- import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import type { AlertingApiRequestHandlerContext } from '@kbn/alerting-plugin/server'; import type { AlertingPluginSetup } from '@kbn/alerting-plugin/public/plugin'; +import { SpacesPluginStart } from '@kbn/spaces-plugin/server'; import { CspStatusCode, IndexDetails } from '../common/types_old'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -61,6 +60,8 @@ export interface CspServerPluginStartDeps { taskManager: TaskManagerStartContract; security: SecurityPluginStart; licensing: LicensingPluginStart; + dataViews: DataViewsPluginStart; + spaces?: SpacesPluginStart; } export type CspServerPluginStartServices = Promise< @@ -86,18 +87,6 @@ export type CspRequestHandlerContext = CustomRequestHandlerContext<{ alerting: AlertingApiRequestHandlerContext; }>; -/** - * Convenience type for request handlers in CSP that includes the CspRequestHandlerContext type - * @internal - */ -export type CspRequestHandler< - P = unknown, - Q = unknown, - B = unknown, - Method extends RouteMethod = any, - ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory -> = RequestHandler<P, Q, B, CspRequestHandlerContext, Method, ResponseFactory>; - /** * Convenience type for routers in Csp that includes the CspRequestHandlerContext type * @internal diff --git a/x-pack/plugins/cloud_security_posture/tsconfig.json b/x-pack/plugins/cloud_security_posture/tsconfig.json index 1febc701d66d6..83891e3269eca 100755 --- a/x-pack/plugins/cloud_security_posture/tsconfig.json +++ b/x-pack/plugins/cloud_security_posture/tsconfig.json @@ -64,7 +64,8 @@ "@kbn/code-editor", "@kbn/code-editor-mock", "@kbn/search-types", - "@kbn/react-kibana-mount" + "@kbn/react-kibana-mount", + "@kbn/spaces-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts index 7424e49099105..87c702087e9af 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts @@ -147,8 +147,8 @@ export function getActions( available: (item: FieldVisConfig) => { return item.deletable === true; }, - onClick: (item: FieldVisConfig) => { - dataViewEditorRef.current = services.dataViewFieldEditor?.openDeleteModal({ + onClick: async (item: FieldVisConfig) => { + dataViewEditorRef.current = await services.dataViewFieldEditor?.openDeleteModal({ ctx: { dataView }, fieldName: item.fieldName!, onDelete: refreshPage, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx index 686dcffdc9f76..6671e4c73c2e8 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_esql.tsx @@ -54,7 +54,6 @@ export interface IndexDataVisualizerESQLProps { getAdditionalLinks?: GetAdditionalLinks; } const DEFAULT_ESQL_QUERY = { esql: '' }; -const expandCodeEditor = () => true; export const IndexDataVisualizerESQL: FC<IndexDataVisualizerESQLProps> = (dataVisualizerProps) => { const { services } = useDataVisualizerKibana(); const { data } = services; @@ -264,10 +263,7 @@ export const IndexDataVisualizerESQL: FC<IndexDataVisualizerESQLProps> = (dataVi query={localQuery} onTextLangQueryChange={onTextLangQueryChange} onTextLangQuerySubmit={onTextLangQuerySubmit} - expandCodeEditor={expandCodeEditor} - isCodeEditorExpanded={true} detectedTimestamp={currentDataView?.timeFieldName} - hideMinimizeButton={true} hideRunQueryText={false} isLoading={queryHistoryStatus ?? false} /> diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx index a015d975fdf18..f9e22c71aaf4f 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/embeddables/field_stats/field_stats_esql_editor.tsx @@ -9,8 +9,6 @@ import { TextBasedLangEditor } from '@kbn/esql/public'; import { EuiFlexItem } from '@elastic/eui'; import type { AggregateQuery } from '@kbn/es-query'; -const expandCodeEditor = (status: boolean) => {}; - interface FieldStatsESQLEditorProps { canEditTextBasedQuery?: boolean; query: AggregateQuery; @@ -47,9 +45,6 @@ export const FieldStatsESQLEditor = ({ setQuery(q); prevQuery.current = q; }} - expandCodeEditor={expandCodeEditor} - isCodeEditorExpanded - hideMinimizeButton editorIsInline hideRunQueryText onTextLangQuerySubmit={onTextLangQuerySubmit} diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts index 38759d4d68ea3..0af5f0453ec8b 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts @@ -57,23 +57,32 @@ export const callAgentExecutor: AgentExecutor<true | false> = async ({ const isOpenAI = llmType === 'openai'; const llmClass = getLlmClass(llmType, bedrockChatEnabled); - const llm = new llmClass({ - actionsClient, - connectorId, - llmType, - logger, - // possible client model override, - // let this be undefined otherwise so the connector handles the model - model: request.body.model, - // ensure this is defined because we default to it in the language_models - // This is where the LangSmith logs (Metadata > Invocation Params) are set - temperature: getDefaultArguments(llmType).temperature, - signal: abortSignal, - streaming: isStream, - // prevents the agent from retrying on failure - // failure could be due to bad connector, we should deliver that result to the client asap - maxRetries: 0, - }); + /** + * Creates a new instance of llmClass. + * + * This function ensures that a new llmClass instance is created every time it is called. + * This is necessary to avoid any potential side effects from shared state. By always + * creating a new instance, we prevent other uses of llm from binding and changing + * the state unintentionally. For this reason, never assign this value to a variable (ex const llm = createLlmInstance()) + */ + const createLlmInstance = () => + new llmClass({ + actionsClient, + connectorId, + llmType, + logger, + // possible client model override, + // let this be undefined otherwise so the connector handles the model + model: request.body.model, + // ensure this is defined because we default to it in the language_models + // This is where the LangSmith logs (Metadata > Invocation Params) are set + temperature: getDefaultArguments(llmType).temperature, + signal: abortSignal, + streaming: isStream, + // prevents the agent from retrying on failure + // failure could be due to bad connector, we should deliver that result to the client asap + maxRetries: 0, + }); const anonymizationFieldsRes = await dataClients?.anonymizationFieldsDataClient?.findDocuments<EsAnonymizationFieldsSchema>({ @@ -99,7 +108,7 @@ export const callAgentExecutor: AgentExecutor<true | false> = async ({ const modelExists = await esStore.isModelInstalled(); // Create a chain that uses the ELSER backed ElasticsearchStore, override k=10 for esql query generation for now - const chain = RetrievalQAChain.fromLLM(llm, esStore.asRetriever(10)); + const chain = RetrievalQAChain.fromLLM(createLlmInstance(), esStore.asRetriever(10)); // Fetch any applicable tools that the source plugin may have registered const assistantToolParams: AssistantToolParams = { @@ -108,7 +117,6 @@ export const callAgentExecutor: AgentExecutor<true | false> = async ({ chain, esClient, isEnabledKnowledgeBase: true, - llm, logger, modelExists, onNewReplacements, @@ -118,7 +126,7 @@ export const callAgentExecutor: AgentExecutor<true | false> = async ({ }; const tools: ToolInterface[] = assistantTools.flatMap( - (tool) => tool.getTool(assistantToolParams) ?? [] + (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance() }) ?? [] ); logger.debug( @@ -132,14 +140,14 @@ export const callAgentExecutor: AgentExecutor<true | false> = async ({ }; // isOpenAI check is not on agentType alone because typescript doesn't like const executor = isOpenAI - ? await initializeAgentExecutorWithOptions(tools, llm, { + ? await initializeAgentExecutorWithOptions(tools, createLlmInstance(), { agentType: 'openai-functions', ...executorArgs, }) : llmType === 'bedrock' && bedrockChatEnabled ? new lcAgentExecutor({ agent: await createToolCallingAgent({ - llm, + llm: createLlmInstance(), tools, prompt: ChatPromptTemplate.fromMessages([ ['system', 'You are a helpful assistant'], @@ -151,7 +159,7 @@ export const callAgentExecutor: AgentExecutor<true | false> = async ({ }), tools, }) - : await initializeAgentExecutorWithOptions(tools, llm, { + : await initializeAgentExecutorWithOptions(tools, createLlmInstance(), { agentType: 'structured-chat-zero-shot-react-description', ...executorArgs, returnIntermediateSteps: false, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts index 2a20dbc9be866..6eac3f1c98303 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts @@ -41,8 +41,7 @@ interface GetDefaultAssistantGraphParams { agentRunnable: AgentRunnableSequence; dataClients?: AssistantDataClients; conversationId?: string; - getLlmInstance: () => BaseChatModel; - llm: BaseChatModel; + createLlmInstance: () => BaseChatModel; logger: Logger; tools: StructuredTool[]; responseLanguage: string; @@ -61,8 +60,7 @@ export const getDefaultAssistantGraph = ({ agentRunnable, conversationId, dataClients, - getLlmInstance, - llm, + createLlmInstance, logger, responseLanguage, tools, @@ -90,7 +88,7 @@ export const getDefaultAssistantGraph = ({ default: () => undefined, }, messages: { - value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y), + value: (x: BaseMessage[], y: BaseMessage[]) => y ?? x, default: () => [], }, chatTitle: { @@ -106,7 +104,6 @@ export const getDefaultAssistantGraph = ({ // Default node parameters const nodeParams: NodeParamsBase = { - model: llm, logger, }; @@ -131,6 +128,7 @@ export const getDefaultAssistantGraph = ({ const generateChatTitleNode = (state: AgentState) => generateChatTitle({ ...nodeParams, + model: createLlmInstance(), state, responseLanguage, }); @@ -154,7 +152,7 @@ export const getDefaultAssistantGraph = ({ const respondNode = (state: AgentState) => respond({ ...nodeParams, - llm: getLlmInstance(), + model: createLlmInstance(), state, }); const shouldContinueEdge = (state: AgentState) => shouldContinue({ ...nodeParams, state }); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts index 0d39e7f0711ae..dc146d6b1b692 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts @@ -90,6 +90,7 @@ export const streamGraph = async ({ runName: DEFAULT_ASSISTANT_GRAPH_ID, tags: traceOptions?.tags ?? [], version: 'v2', + streamMode: 'values', }, llmType === 'bedrock' ? { includeNames: ['Summarizer'] } : undefined ); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts index 68dfd720f3569..758a3a757eb76 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts @@ -57,7 +57,16 @@ export const callAssistantGraph: AgentExecutor<true | false> = async ({ const logger = parentLogger.get('defaultAssistantGraph'); const isOpenAI = llmType === 'openai'; const llmClass = getLlmClass(llmType, bedrockChatEnabled); - const getLlmInstance = () => + + /** + * Creates a new instance of llmClass. + * + * This function ensures that a new llmClass instance is created every time it is called. + * This is necessary to avoid any potential side effects from shared state. By always + * creating a new instance, we prevent other uses of llm from binding and changing + * the state unintentionally. For this reason, never assign this value to a variable (ex const llm = createLlmInstance()) + */ + const createLlmInstance = () => new llmClass({ actionsClient, connectorId, @@ -76,8 +85,6 @@ export const callAssistantGraph: AgentExecutor<true | false> = async ({ maxRetries: 0, }); - const llm = getLlmInstance(); - const anonymizationFieldsRes = await dataClients?.anonymizationFieldsDataClient?.findDocuments<EsAnonymizationFieldsSchema>({ perPage: 1000, @@ -93,7 +100,7 @@ export const callAssistantGraph: AgentExecutor<true | false> = async ({ const modelExists = await esStore.isModelInstalled(); // Create a chain that uses the ELSER backed ElasticsearchStore, override k=10 for esql query generation for now - const chain = RetrievalQAChain.fromLLM(llm, esStore.asRetriever(10)); + const chain = RetrievalQAChain.fromLLM(createLlmInstance(), esStore.asRetriever(10)); // Check if KB is available const isEnabledKnowledgeBase = (await dataClients?.kbDataClient?.isModelDeployed()) ?? false; @@ -106,7 +113,6 @@ export const callAssistantGraph: AgentExecutor<true | false> = async ({ esClient, isEnabledKnowledgeBase, kbDataClient: dataClients?.kbDataClient, - llm, logger, modelExists, onNewReplacements, @@ -116,26 +122,26 @@ export const callAssistantGraph: AgentExecutor<true | false> = async ({ }; const tools: StructuredTool[] = assistantTools.flatMap( - (tool) => tool.getTool(assistantToolParams) ?? [] + (tool) => tool.getTool({ ...assistantToolParams, llm: createLlmInstance() }) ?? [] ); const agentRunnable = isOpenAI ? await createOpenAIFunctionsAgent({ - llm, + llm: createLlmInstance(), tools, prompt: openAIFunctionAgentPrompt, streamRunnable: isStream, }) : llmType && ['bedrock', 'gemini'].includes(llmType) && bedrockChatEnabled - ? createToolCallingAgent({ - llm, + ? await createToolCallingAgent({ + llm: createLlmInstance(), tools, prompt: llmType === 'bedrock' ? bedrockToolCallingAgentPrompt : geminiToolCallingAgentPrompt, streamRunnable: isStream, }) : await createStructuredChatAgent({ - llm, + llm: createLlmInstance(), tools, prompt: structuredChatAgentPrompt, streamRunnable: isStream, @@ -147,9 +153,8 @@ export const callAssistantGraph: AgentExecutor<true | false> = async ({ agentRunnable, conversationId, dataClients, - llm, // we need to pass it like this or streaming does not work for bedrock - getLlmInstance, + createLlmInstance, logger, tools, responseLanguage, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/execute_tools.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/execute_tools.ts index c20155e7c01db..6796385686cc6 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/execute_tools.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/execute_tools.ts @@ -8,7 +8,8 @@ import { RunnableConfig } from '@langchain/core/runnables'; import { StructuredTool } from '@langchain/core/tools'; import { ToolExecutor } from '@langchain/langgraph/prebuilt'; -import { isArray } from 'lodash'; +import { castArray } from 'lodash'; +import { AgentAction } from 'langchain/agents'; import { AgentState, NodeParamsBase } from '../types'; export interface ExecuteToolsParams extends NodeParamsBase { @@ -34,23 +35,30 @@ export const executeTools = async ({ config, logger, state, tools }: ExecuteTool logger.debug(() => `Node state:\n${JSON.stringify(state, null, 2)}`); const toolExecutor = new ToolExecutor({ tools }); - const agentAction = isArray(state.agentOutcome) ? state.agentOutcome[0] : state.agentOutcome; + const agentAction = state.agentOutcome; if (!agentAction || 'returnValues' in agentAction) { throw new Error('Agent has not been run yet'); } - let out; - try { - out = await toolExecutor.invoke(agentAction, config); - } catch (err) { - return { - steps: [{ action: agentAction, observation: JSON.stringify(`Error: ${err}`, null, 2) }], - }; - } + const steps = await Promise.all( + castArray(state.agentOutcome as AgentAction)?.map(async (action) => { + let out; + try { + out = await toolExecutor.invoke(action, config); + } catch (err) { + return { + action, + observation: JSON.stringify(`Error: ${err}`, null, 2), + }; + } + + return { + action, + observation: JSON.stringify(out, null, 2), + }; + }) + ); - return { - ...state, - steps: [{ action: agentAction, observation: JSON.stringify(out, null, 2) }], - }; + return { steps }; }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts index c9fa5c98ef470..9cda33fdbabbc 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts @@ -7,6 +7,7 @@ import { StringOutputParser } from '@langchain/core/output_parsers'; import { ChatPromptTemplate } from '@langchain/core/prompts'; +import { BaseChatModel } from '@langchain/core/language_models/chat_models'; import { AgentState, NodeParamsBase } from '../types'; export const GENERATE_CHAT_TITLE_PROMPT = (responseLanguage: string) => @@ -25,6 +26,7 @@ export const GENERATE_CHAT_TITLE_PROMPT = (responseLanguage: string) => export interface GenerateChatTitleParams extends NodeParamsBase { responseLanguage: string; state: AgentState; + model: BaseChatModel; } export const GENERATE_CHAT_TITLE_NODE = 'generateChatTitle'; @@ -46,7 +48,6 @@ export const generateChatTitle = async ({ logger.debug(`chatTitle: ${chatTitle}`); return { - ...state, chatTitle, }; }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts index 8f4b285410fbd..a4dd031507067 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/get_persisted_conversation.ts @@ -27,7 +27,6 @@ export const getPersistedConversation = async ({ if (!conversationId) { logger.debug('Cannot get conversation, because conversationId is undefined'); return { - ...state, conversation: undefined, messages: [], chatTitle: '', @@ -39,7 +38,6 @@ export const getPersistedConversation = async ({ if (!conversation) { logger.debug('Requested conversation, because conversation is undefined'); return { - ...state, conversation: undefined, messages: [], chatTitle: '', @@ -50,11 +48,21 @@ export const getPersistedConversation = async ({ logger.debug(`conversationId: ${conversationId}`); const messages = getLangChainMessages(conversation.messages ?? []); + + if (!state.input) { + const lastMessage = messages?.splice(-1)[0]; + return { + conversation, + messages, + chatTitle: conversation.title, + input: lastMessage?.content as string, + }; + } + return { - ...state, conversation, messages, chatTitle: conversation.title, - input: !state.input ? conversation.messages?.slice(-1)[0].content : state.input, + input: state.input, }; }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts index 40717d7d64e78..615c4b2449c4c 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/persist_conversation_changes.ts @@ -57,7 +57,6 @@ export const persistConversationChanges = async ({ const langChainMessages = getLangChainMessages(state.conversation.messages ?? []); const messages = langChainMessages.slice(0, -1); // all but the last message return { - ...state, conversation: state.conversation, messages, }; @@ -78,7 +77,7 @@ export const persistConversationChanges = async ({ }); if (!updatedConversation) { logger.debug('Not updated conversation'); - return { ...state, conversation: undefined, messages: [] }; + return { conversation: undefined, messages: [] }; } logger.debug(`conversationId: ${conversationId}`); @@ -86,7 +85,6 @@ export const persistConversationChanges = async ({ const messages = langChainMessages.slice(0, -1); // all but the last message return { - ...state, conversation: updatedConversation, messages, }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts index bb3b3a518e06d..7c11b96bbca0d 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/respond.ts @@ -11,7 +11,7 @@ import { AGENT_NODE_TAG } from './run_agent'; import { AgentState } from '../types'; export const RESPOND_NODE = 'respond'; -export const respond = async ({ llm, state }: { llm: BaseChatModel; state: AgentState }) => { +export const respond = async ({ model, state }: { model: BaseChatModel; state: AgentState }) => { if (state?.agentOutcome && 'returnValues' in state.agentOutcome) { const userMessage = [ 'user', @@ -21,7 +21,7 @@ export const respond = async ({ llm, state }: { llm: BaseChatModel; state: Agent Do not verify, confirm or anything else. Just reply with the same content as provided above.`, ] as [StringWithAutocomplete<'user'>, string]; - const responseMessage = await llm + const responseMessage = await model // use AGENT_NODE_TAG to identify as agent node for stream parsing .withConfig({ runName: 'Summarizer', tags: [AGENT_NODE_TAG] }) .invoke([userMessage]); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts index 9f59fedbbadef..fcd6686f1d17f 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts @@ -42,13 +42,12 @@ export const runAgent = async ({ const agentOutcome = await agentRunnable.withConfig({ tags: [AGENT_NODE_TAG] }).invoke( { ...state, - messages: state.messages.splice(-1), chat_history: state.messages, // TODO: Message de-dupe with ...state spread }, config ); + return { - ...state, agentOutcome, }; }; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts index 57fcb5510318b..803fedb6f42dc 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts @@ -31,7 +31,7 @@ export const shouldContinue = ({ logger, state }: ShouldContinueParams) => { export const shouldContinueGenerateTitle = ({ logger, state }: ShouldContinueParams) => { logger.debug(`Node state:\n${JSON.stringify(state, null, 2)}`); - if (state.conversation?.title !== NEW_CHAT) { + if (state.conversation?.title?.length && state.conversation?.title !== NEW_CHAT) { return 'end'; } diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts index e32b0ea03439b..9f0ff1b7a51f8 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts @@ -17,7 +17,7 @@ export const openAIFunctionAgentPrompt = ChatPromptTemplate.fromMessages([ export const bedrockToolCallingAgentPrompt = ChatPromptTemplate.fromMessages([ [ 'system', - 'You are a helpful assistant. ALWAYS use the provided tools. Use tools as often as possible, as they have access to the latest data and syntax.', + 'You are a helpful assistant. ALWAYS use the provided tools. Use tools as often as possible, as they have access to the latest data and syntax. Always return value from ESQLKnowledgeBaseTool as is. Never return <thinking> tags in the response, but make sure to include <result> tags content in the response. Do not reflect on the quality of the returned search results in your response.', ], ['placeholder', '{chat_history}'], ['human', '{input}'], @@ -28,7 +28,7 @@ export const geminiToolCallingAgentPrompt = ChatPromptTemplate.fromMessages([ [ 'system', 'You are a helpful assistant. ALWAYS use the provided tools. Use tools as often as possible, as they have access to the latest data and syntax.\n\n' + - `The final response will be the only output the user sees and should be a complete answer to the user's question, as if you were responding to the user's initial question, which is "{input}". The final response should never be empty.`, + "The final response will be the only output the user sees and should be a complete answer to the user's question, as if you were responding to the user's initial question. The final response should never be empty.", ], ['placeholder', '{chat_history}'], ['human', '{input}'], diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts index 4ee4f1ba1b148..5d86a0f6b97ed 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts @@ -7,7 +7,6 @@ import { BaseMessage } from '@langchain/core/messages'; import { AgentAction, AgentFinish, AgentStep } from '@langchain/core/agents'; -import { BaseChatModel } from '@langchain/core/language_models/chat_models'; import type { Logger } from '@kbn/logging'; import { ConversationResponse } from '@kbn/elastic-assistant-common'; @@ -25,5 +24,4 @@ export interface AgentState extends AgentStateBase { export interface NodeParamsBase { logger: Logger; - model: BaseChatModel; } diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index 0dc2562ff2fe3..91221ef0ed95e 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -15,6 +15,7 @@ "features", "licensing", "logsShared", + "logsDataAccess", "esUiShared", "navigation", ], diff --git a/x-pack/plugins/file_upload/kibana.jsonc b/x-pack/plugins/file_upload/kibana.jsonc index 82b16fe0af18b..6c6e3fddd0e7c 100644 --- a/x-pack/plugins/file_upload/kibana.jsonc +++ b/x-pack/plugins/file_upload/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/file-upload-plugin", - "owner": "@elastic/kibana-gis", + "owner": ["@elastic/kibana-gis", "@elastic/ml-ui"], "description": "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.", "plugin": { "id": "fileUpload", diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index adfbcb299238b..6509786f2c53e 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -93,6 +93,7 @@ export const agentAssetTypes = { export const dataTypes = { Logs: 'logs', Metrics: 'metrics', + Traces: 'traces', } as const; // currently identical but may be a subset or otherwise different some day diff --git a/x-pack/plugins/fleet/common/services/generate_new_agent_policy.test.ts b/x-pack/plugins/fleet/common/services/generate_new_agent_policy.test.ts index bc4a6b55f75ee..82bda9d6e28ae 100644 --- a/x-pack/plugins/fleet/common/services/generate_new_agent_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/generate_new_agent_policy.test.ts @@ -15,7 +15,7 @@ describe('generateNewAgentPolicyWithDefaults', () => { name: '', description: '', namespace: 'default', - monitoring_enabled: ['logs', 'metrics'], + monitoring_enabled: ['logs', 'metrics', 'traces'], inactivity_timeout: 1209600, is_protected: false, }); diff --git a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts index 3b4fa8fc0cc92..0ecaf170de715 100644 --- a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts +++ b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts @@ -245,6 +245,65 @@ describe('Fleet - isAgentUpgradeableToVersion', () => { isAgentUpgradeableToVersion(getAgent({ version: '7.9.0', upgradeable: true }), '7.9.0') ).toBe(false); }); + + describe('+build versions', () => { + it('returns true with target version of a +build version on the same patch', () => { + expect( + isAgentUpgradeableToVersion( + getAgent({ version: '7.9.0', upgradeable: true }), + '7.9.0+build202408011234' + ) + ).toBe(true); + }); + it('returns true with target version of a +build version on a newer patch', () => { + expect( + isAgentUpgradeableToVersion( + getAgent({ version: '7.9.0', upgradeable: true }), + '7.9.1+build202408011234' + ) + ).toBe(true); + }); + it('returns true with target version of a +build version on a newer minor', () => { + expect( + isAgentUpgradeableToVersion( + getAgent({ version: '7.9.0', upgradeable: true }), + '7.10.0+build202408011234' + ) + ).toBe(true); + }); + it('returns true with current version on a +build version', () => { + expect( + isAgentUpgradeableToVersion( + getAgent({ version: '7.9.0+build202408011234', upgradeable: true }), + '7.9.1' + ) + ).toBe(true); + }); + it('returns true when upgrade build is newer than current build', () => { + expect( + isAgentUpgradeableToVersion( + getAgent({ version: '8.12.0+build202408011234', upgradeable: true }), + '8.12.0+build202408061235' + ) + ).toBe(true); + }); + it('returns false when upgrade build is older than current build', () => { + expect( + isAgentUpgradeableToVersion( + getAgent({ version: '8.12.0+build202408061234' }), + '8.12.0+build202408011234' + ) + ).toBe(false); + }); + it('returns false with target version of a +build version on an older patch', () => { + expect( + isAgentUpgradeableToVersion( + getAgent({ version: '7.9.0', upgradeable: true }), + '7.8.9+build202408011234' + ) + ).toBe(false); + }); + }); }); describe('Fleet - getNotUpgradeableMessage', () => { diff --git a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts index e8a8536c50441..79b85249e1515 100644 --- a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts +++ b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.ts @@ -141,11 +141,19 @@ export const getNotUpgradeableMessage = ( }; const isNotDowngrade = (agentVersion: string, versionToUpgrade: string): boolean => { - const agentVersionNumber = semverCoerce(agentVersion); + const agentVersionNumber = semverCoerce(agentVersion, { includePrerelease: true }); if (!agentVersionNumber) throw new Error(`${INVALID_VERSION_ERROR}`); - const versionToUpgradeNumber = semverCoerce(versionToUpgrade); + + const versionToUpgradeNumber = semverCoerce(versionToUpgrade, { includePrerelease: true }); if (!versionToUpgradeNumber) return true; + // If the versions are equal, allow upgrading to newer build versions + if (semverEq(agentVersionNumber, versionToUpgradeNumber)) { + const isUpgradeToBuildVersion = versionToUpgradeNumber.compareBuild(agentVersionNumber) === 1; + + return isUpgradeToBuildVersion; + } + return semverGt(versionToUpgradeNumber, agentVersionNumber); }; diff --git a/x-pack/plugins/fleet/kibana.jsonc b/x-pack/plugins/fleet/kibana.jsonc index 54b4a4928594c..dded2caf4c7e2 100644 --- a/x-pack/plugins/fleet/kibana.jsonc +++ b/x-pack/plugins/fleet/kibana.jsonc @@ -24,7 +24,8 @@ "files", "uiActions", "dashboard", - "fieldsMetadata" + "fieldsMetadata", + "logsDataAccess" ], "optionalPlugins": [ "features", diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/setup_technology_selector.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/setup_technology_selector.tsx new file mode 100644 index 0000000000000..a9477c1016b96 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/setup_technology_selector.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiBetaBadge, EuiFormRow, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; + +import { SetupTechnology } from '../../../../../types'; + +export const SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ = 'setup-technology-selector'; + +export const SetupTechnologySelector = ({ + disabled, + setupTechnology, + onSetupTechnologyChange, +}: { + disabled: boolean; + setupTechnology: SetupTechnology; + onSetupTechnologyChange: (value: SetupTechnology) => void; +}) => { + const options = [ + { + value: SetupTechnology.AGENTLESS, + inputDisplay: ( + <> + <FormattedMessage + id="xpack.fleet.setupTechnology.agentlessInputDisplay" + defaultMessage="Agentless" + /> +   + <EuiBetaBadge + label="Beta" + size="s" + tooltipContent="This module is not yet GA. Please help us by reporting any bugs." + /> + </> + ), + dropdownDisplay: ( + <> + <strong> + <FormattedMessage + id="xpack.fleet.setupTechnology.agentlessDrowpownDisplay" + defaultMessage="Agentless" + /> + </strong> +   + <EuiBetaBadge + label="Beta" + size="s" + tooltipContent="This module is not GA. Please help us by reporting any bugs." + /> + <EuiText size="s" color="subdued"> + <p> + <FormattedMessage + id="xpack.fleet.setupTechnology.agentlessDrowpownDescription" + defaultMessage="Set up the integration without an agent" + /> + </p> + </EuiText> + </> + ), + }, + { + value: SetupTechnology.AGENT_BASED, + inputDisplay: ( + <FormattedMessage + id="xpack.fleet.setupTechnology.agentbasedInputDisplay" + defaultMessage="Agent-based" + /> + ), + dropdownDisplay: ( + <> + <strong> + <FormattedMessage + id="xpack.fleet.setupTechnology.agentbasedDrowpownDisplay" + defaultMessage="Agent-based" + /> + </strong> + <EuiText size="s" color="subdued"> + <p> + <FormattedMessage + id="xpack.fleet.setupTechnology.agentbasedDrowpownDescription" + defaultMessage="Set up the integration with an agent" + /> + </p> + </EuiText> + </> + ), + }, + ]; + + return ( + <> + <EuiSpacer size="l" /> + <EuiFormRow + fullWidth + label={ + <FormattedMessage + id="xpack.fleet.setupTechnology.setupTechnologyLabel" + defaultMessage="Setup technology" + /> + } + > + <EuiSuperSelect + disabled={disabled} + options={options} + valueOfSelected={setupTechnology} + placeholder={ + <FormattedMessage + id="xpack.fleet.setupTechnology.setupTechnologyPlaceholder" + defaultMessage="Select the setup technology" + /> + } + onChange={onSetupTechnologyChange} + itemLayoutAlign="top" + hasDividers + fullWidth + data-test-subj={SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ} + /> + </EuiFormRow> + </> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts index cb72bfd8da245..e9c5c04892e7f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts @@ -92,12 +92,8 @@ export function useSetupTechnology({ isEditPage?: boolean; }) { const { cloud } = useStartServices(); - const { - isAgentlessEnabled, - isAgentlessIntegration, - isAgentlessCloudEnabled, - isAgentlessServerlessEnabled, - } = useAgentless(); + const { isAgentlessEnabled, isAgentlessCloudEnabled, isAgentlessServerlessEnabled } = + useAgentless(); // this is a placeholder for the new agent-BASED policy that will be used when the user switches from agentless to agent-based and back const newAgentBasedPolicy = useRef<NewAgentPolicy>(newAgentPolicy); @@ -107,6 +103,7 @@ export function useSetupTechnology({ const [newAgentlessPolicy, setNewAgentlessPolicy] = useState<AgentPolicy | NewAgentPolicy>( generateNewAgentPolicyWithDefaults({ supports_agentless: true, + monitoring_enabled: [], }) ); @@ -135,12 +132,6 @@ export function useSetupTechnology({ setNewAgentPolicy, ]); - useEffect(() => { - if (isAgentlessEnabled && packageInfo && isAgentlessIntegration(packageInfo)) { - setSelectedSetupTechnology(SetupTechnology.AGENTLESS); - } - }, [isAgentlessEnabled, isAgentlessIntegration, packageInfo]); - // tech debt: remove this useEffect when Serverless uses the Agentless API // https://github.com/elastic/security-team/issues/9781 useEffect(() => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index 4109a66a59638..ac7d0e9db77fc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -127,6 +127,7 @@ jest.mock('react-router-dom', () => ({ import { AGENTLESS_POLICY_ID } from '../../../../../../../common/constants'; import { CreatePackagePolicySinglePage } from '.'; +import { SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ } from './components/setup_technology_selector'; // mock console.debug to prevent noisy logs from console.debugs in ./index.tsx let consoleDebugMock: any; @@ -160,6 +161,7 @@ describe('When on the package policy create page', () => { function getMockPackageInfo(options?: { requiresRoot?: boolean; dataStreamRequiresRoot?: boolean; + agentlessEnabled?: boolean; }) { return { data: { @@ -181,6 +183,7 @@ describe('When on the package policy create page', () => { }, ], multiple: true, + deployment_modes: { agentless: { enabled: options?.agentlessEnabled } }, }, ], data_streams: [ @@ -488,7 +491,7 @@ describe('When on the package policy create page', () => { expect(sendCreateAgentPolicy as jest.MockedFunction<any>).toHaveBeenCalledWith( { description: '', - monitoring_enabled: ['logs', 'metrics'], + monitoring_enabled: ['logs', 'metrics', 'traces'], name: 'Agent policy 2', namespace: 'default', inactivity_timeout: 1209600, @@ -523,7 +526,7 @@ describe('When on the package policy create page', () => { expect(sendCreateAgentPolicy as jest.MockedFunction<any>).toHaveBeenCalledWith( { description: '', - monitoring_enabled: ['logs', 'metrics'], + monitoring_enabled: ['logs', 'metrics', 'traces'], name: 'Agent policy 2', namespace: 'default', inactivity_timeout: 1209600, @@ -802,15 +805,18 @@ describe('When on the package policy create page', () => { }); jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({ agentless: true } as any); (useGetPackageInfoByKeyQuery as jest.Mock).mockReturnValue( - getMockPackageInfo({ requiresRoot: false, dataStreamRequiresRoot: false }) + getMockPackageInfo({ + requiresRoot: false, + dataStreamRequiresRoot: false, + agentlessEnabled: true, + }) ); - await act(async () => { render(); }); }); - test('should create create agent and package policy when in cloud and agentless API url is set', async () => { + test('should create agent policy and package policy when in cloud and agentless API url is set', async () => { await act(async () => { fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); }); @@ -818,7 +824,35 @@ describe('When on the package policy create page', () => { // tech debt: this should be converted to use MSW to mock the API calls // https://github.com/elastic/security-team/issues/9816 expect(sendGetOneAgentPolicy).not.toHaveBeenCalled(); - expect(sendCreateAgentPolicy).toHaveBeenCalled(); + expect(sendCreateAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + monitoring_enabled: ['logs', 'metrics', 'traces'], + name: 'Agent policy 1', + }), + { withSysMonitoring: true } + ); + expect(sendCreatePackagePolicy).toHaveBeenCalled(); + + await waitFor(() => { + expect(renderResult.getByText('Nginx integration added')).toBeInTheDocument(); + }); + }); + + test('should create agentless agent policy and package policy when in cloud and agentless API url is set', async () => { + fireEvent.click(renderResult.getByTestId(SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ)); + fireEvent.click(renderResult.getByText('Agentless')); + await act(async () => { + fireEvent.click(renderResult.getByText(/Save and continue/).closest('button')!); + }); + + expect(sendCreateAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + monitoring_enabled: [], + name: 'Agentless policy for nginx-1', + supports_agentless: true, + }), + { withSysMonitoring: false } + ); expect(sendCreatePackagePolicy).toHaveBeenCalled(); await waitFor(() => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index 7190a90d56198..99ed5592ac3ab 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -83,6 +83,7 @@ import { PostInstallGoogleCloudShellModal } from './components/cloud_security_po import { PostInstallAzureArmTemplateModal } from './components/cloud_security_posture/post_install_azure_arm_template_modal'; import { RootPrivilegesCallout } from './root_callout'; import { useAgentless } from './hooks/setup_technology'; +import { SetupTechnologySelector } from './components/setup_technology_selector'; export const StepsWithLessPadding = styled(EuiSteps)` .euiStep__content { @@ -349,7 +350,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ "'package-policy-create' and 'package-policy-replace-define-step' cannot both be registered as UI extensions" ); } - const { isAgentlessEnabled } = useAgentless(); + const { isAgentlessEnabled, isAgentlessIntegration } = useAgentless(); const { handleSetupTechnologyChange, selectedSetupTechnology } = useSetupTechnology({ newAgentPolicy, setNewAgentPolicy, @@ -397,6 +398,19 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ submitAttempted={formState === 'INVALID'} /> + {/* TODO move SetupTechnologySelector out of extensionView */} + {!extensionView && isAgentlessIntegration(packageInfo) && ( + <SetupTechnologySelector + disabled={false} + setupTechnology={selectedSetupTechnology} + onSetupTechnologyChange={(value) => { + handleSetupTechnologyChange(value); + // agentless doesn't need system integration + setWithSysMonitoring(value === SetupTechnology.AGENT_BASED); + }} + /> + )} + {/* Only show the out-of-box configuration step if a UI extension is NOT registered */} {!extensionView && ( <StepConfigurePackagePolicy @@ -435,6 +449,9 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ extensionView, handleExtensionViewOnChange, spaceSettings?.allowedNamespacePrefixes, + handleSetupTechnologyChange, + isAgentlessIntegration, + selectedSetupTechnology, ] ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index 2cecdf37f30f5..41df6b53dae1b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -119,8 +119,18 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({ <EuiFlexGroup gutterSize="s" alignItems="center"> <EuiFlexItem data-test-subj="PackagePoliciesTableName" grow={false}> <EuiLink - title={value} - {...(canReadIntegrationPolicies + title={ + agentPolicy.supports_agentless + ? i18n.translate( + 'xpack.fleet.policyDetails.packagePoliciesTable.disabledEditTitle', + { + defaultMessage: + 'Editing an agentless integration is not supported. Add a new integration if needed.', + } + ) + : value + } + {...(canReadIntegrationPolicies && !agentPolicy.supports_agentless ? { href: getHref('edit_integration', { policyId: agentPolicy.id, @@ -129,9 +139,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({ } : { disabled: true })} > - <span className="eui-textTruncate" title={value}> - {value} - </span> + <span className="eui-textTruncate">{value}</span> {packagePolicy.description ? ( <span>   diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx index 8e52ced483d72..6669f3dfa332f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx @@ -541,7 +541,7 @@ describe('edit package policy page', () => { expect(sendCreateAgentPolicy as jest.MockedFunction<any>).toHaveBeenCalledWith( { description: '', - monitoring_enabled: ['logs', 'metrics'], + monitoring_enabled: ['logs', 'metrics', 'traces'], name: 'Agent policy 2', namespace: 'default', inactivity_timeout: 1209600, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx index 1a1320e8b942a..4c6c83dd7145e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx @@ -278,14 +278,14 @@ export const AgentListTable: React.FC<Props> = (props: Props) => { { field: VERSION_FIELD, sortable: true, - width: '180px', + width: '220px', name: i18n.translate('xpack.fleet.agentList.versionTitle', { defaultMessage: 'Version', }), render: (version: string, agent: Agent) => ( <EuiFlexGroup gutterSize="none" style={{ minWidth: 0 }} direction="column"> <EuiFlexItem grow={false}> - <EuiFlexGroup gutterSize="s" alignItems="center"> + <EuiFlexGroup gutterSize="s" alignItems="center" wrap> <EuiFlexItem grow={false}> <EuiText size="s" className="eui-textNoWrap"> {safeMetadata(version)} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx index 2859b0ff7d8ae..9fa6df1e9f8ca 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx @@ -22,7 +22,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { AVCResultsBanner2024 } from '@kbn/avc-banner'; +import { AVCResultsBanner2024, useIsStillYear2024 } from '@kbn/avc-banner'; import { isIntegrationPolicyTemplate, @@ -313,7 +313,7 @@ export const OverviewPage: React.FC<Props> = memo( </SideBar> <EuiFlexItem grow={9} className="eui-textBreakWord"> {isUnverified && <UnverifiedCallout />} - {isElasticDefend && showAVCBanner && ( + {useIsStillYear2024() && isElasticDefend && showAVCBanner && ( <> <AVCResultsBanner2024 onDismiss={onBannerDismiss} /> <EuiSpacer size="s" /> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index aa86607a84ee5..b5383df7275d6 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -58,16 +58,31 @@ interface InMemoryPackagePolicyAndAgentPolicy { const IntegrationDetailsLink = memo<{ packagePolicy: InMemoryPackagePolicyAndAgentPolicy['packagePolicy']; -}>(({ packagePolicy }) => { + agentPolicies: InMemoryPackagePolicyAndAgentPolicy['agentPolicies']; +}>(({ packagePolicy, agentPolicies }) => { const { getHref } = useLink(); + const policySupportsAgentless = agentPolicies?.some((policy) => policy.supports_agentless); return ( <EuiLink className="eui-textTruncate" data-test-subj="integrationNameLink" - title={packagePolicy.name} - href={getHref('integration_policy_edit', { - packagePolicyId: packagePolicy.id, - })} + {...(policySupportsAgentless + ? { + disabled: true, + title: i18n.translate( + 'xpack.fleet.epm.packageDetails.integrationList.disabledEditTitle', + { + defaultMessage: + 'Editing an agentless integration is not supported. Add a new integration if needed.', + } + ), + } + : { + href: getHref('integration_policy_edit', { + packagePolicyId: packagePolicy.id, + }), + title: packagePolicy.name, + })} > {packagePolicy.name} </EuiLink> @@ -182,8 +197,10 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.name', { defaultMessage: 'Integration policy', }), - render(_, { packagePolicy }) { - return <IntegrationDetailsLink packagePolicy={packagePolicy} />; + render(_, { agentPolicies, packagePolicy }) { + return ( + <IntegrationDetailsLink packagePolicy={packagePolicy} agentPolicies={agentPolicies} /> + ); }, }, { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx index 2276d2df1924a..031a972e30851 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/category_facets.tsx @@ -89,6 +89,14 @@ export function CategoryFacets({ id={category.id} quantity={category.count} onClick={() => onCategoryChange(category)} + aria-label={i18n.translate('xpack.fleet.epmList.facetButton.ariaLabel', { + defaultMessage: + '{key}, {count} {count, plural, one { integration } other { integrations }}', + values: { + key: category.title, + count: category.count, + }, + })} > {category.title} </EuiFacetButton> diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx index ad241e4eb52cd..41d81986c12b8 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx @@ -7,12 +7,12 @@ import React from 'react'; -import { act } from '@testing-library/react'; +import { act, fireEvent } from '@testing-library/react'; import type { AgentPolicy, InMemoryPackagePolicy } from '../types'; import { createIntegrationsTestRendererMock } from '../mock'; -import { useMultipleAgentPolicies } from '../hooks'; +import { useMultipleAgentPolicies, useStartServices } from '../hooks'; import { PackagePolicyActionsMenu } from './package_policy_actions_menu'; @@ -20,6 +20,19 @@ jest.mock('../hooks', () => { return { ...jest.requireActual('../hooks'), useMultipleAgentPolicies: jest.fn(), + useStartServices: jest.fn().mockReturnValue({ + application: { + navigateToApp: jest.fn(), + }, + notifications: { + toasts: { addSuccess: jest.fn() }, + }, + }), + useLink: jest.fn().mockReturnValue({ + getHref: jest + .fn() + .mockReturnValue('/mock/app/fleet/policies/some-uuid1/edit-integration/some-uuid2'), + }), }; }); @@ -140,6 +153,32 @@ describe('PackagePolicyActionsMenu', () => { }); }); + it('Should be able to delete integration from a managed agentless policy', async () => { + const agentPolicies = createMockAgentPolicies({ is_managed: true, supports_agentless: true }); + const packagePolicy = createMockPackagePolicy(); + const { utils } = renderMenu({ agentPolicies, packagePolicy }); + await act(async () => { + expect(utils.queryByText('Delete integration')).not.toBeNull(); + }); + }); + + it('Should navigate on delete integration when having an agentless policy', async () => { + const agentPolicies = createMockAgentPolicies({ is_managed: true, supports_agentless: true }); + const packagePolicy = createMockPackagePolicy(); + const { utils } = renderMenu({ agentPolicies, packagePolicy }); + + await act(async () => { + fireEvent.click(utils.getByTestId('PackagePolicyActionsDeleteItem')); + }); + await act(async () => { + fireEvent.click(utils.getByTestId('confirmModalConfirmButton')); + }); + expect(useStartServices().application.navigateToApp as jest.Mock).toHaveBeenCalledWith( + 'fleet', + { path: '/policies' } + ); + }); + it('Should show add button if the policy is not managed and showAddAgent=true', async () => { const agentPolicies = createMockAgentPolicies(); const packagePolicy = createMockPackagePolicy({ hasUpgrade: true }); @@ -170,4 +209,14 @@ describe('PackagePolicyActionsMenu', () => { ); }); }); + + it('Should disable Edit integration when agentPolicy is agentless', async () => { + const agentPolicies = createMockAgentPolicies({ is_managed: true, supports_agentless: true }); + const packagePolicy = createMockPackagePolicy(); + const { utils } = renderMenu({ agentPolicies, packagePolicy }); + await act(async () => { + const editButton = utils.getByTestId('PackagePolicyActionsEditItem'); + expect(editButton).toHaveAttribute('disabled'); + }); + }); }); diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx index 7dc525e862271..82600880eaef3 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx @@ -10,9 +10,11 @@ import { EuiContextMenuItem, EuiPortal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import type { AgentPolicy, InMemoryPackagePolicy } from '../types'; -import { useAgentPolicyRefresh, useAuthz, useLink } from '../hooks'; +import { useAgentPolicyRefresh, useAuthz, useLink, useStartServices } from '../hooks'; import { policyHasFleetServer } from '../services'; +import { PLUGIN_ID, pagePathGetters } from '../constants'; + import { AgentEnrollmentFlyout } from './agent_enrollment_flyout'; import { ContextMenuActions } from './context_menu_actions'; import { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; @@ -36,6 +38,9 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); const { getHref } = useLink(); const authz = useAuthz(); + const { + application: { navigateToApp }, + } = useStartServices(); const agentPolicy = agentPolicies.length > 0 ? agentPolicies[0] : undefined; // TODO: handle multiple agent policies const canWriteIntegrationPolicies = authz.integrations.writeIntegrationPolicies; @@ -87,13 +92,23 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ : []), <EuiContextMenuItem data-test-subj="PackagePolicyActionsEditItem" - disabled={!canWriteIntegrationPolicies || !agentPolicy} + disabled={ + !canWriteIntegrationPolicies || !agentPolicy || (agentPolicy?.supports_agentless ?? false) + } icon="pencil" href={`${getHref('edit_integration', { policyId: agentPolicy?.id ?? '', packagePolicyId: packagePolicy.id, })}${from ? `?from=${from}` : ''}`} key="packagePolicyEdit" + toolTipContent={ + (agentPolicy?.supports_agentless ?? false) && ( + <FormattedMessage + id="xpack.fleet.epm.packageDetails.integrationList.editIntegrationAgentlessTooltip" + defaultMessage="Editing an agentless integration is not supported. Add a new integration if needed." + /> + ) + } > <FormattedMessage id="xpack.fleet.policyDetails.packagePoliciesTable.editActionTitle" @@ -123,7 +138,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ // </EuiContextMenuItem>, ]; - if (!agentPolicy || !agentPolicyIsManaged) { + if (!agentPolicy || !agentPolicyIsManaged || agentPolicy?.supports_agentless) { const ContextMenuItem = canWriteIntegrationPolicies ? DangerEuiContextMenuItem : EuiContextMenuItem; @@ -138,7 +153,12 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ onClick={() => { deletePackagePoliciesPrompt([packagePolicy.id], () => { setIsActionsMenuOpen(false); - refreshAgentPolicy(); + if (agentPolicy?.supports_agentless) { + // go back to all agent policies + navigateToApp(PLUGIN_ID, { path: pagePathGetters.policies_list()[1] }); + } else { + refreshAgentPolicy(); + } }); }} > diff --git a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts index 4969500eb05df..9b5bbdccb3540 100644 --- a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts @@ -17,6 +17,7 @@ import type { import type { ActionsService } from '../../services/agents'; import type { PostNewAgentActionResponse } from '../../../common/types/rest_spec'; import { defaultFleetErrorHandler } from '../../errors'; +import { getCurrentNamespace } from '../../services/spaces/get_current_namespace'; export const postNewAgentActionHandlerBuilder = function ( actionsService: ActionsService @@ -39,6 +40,7 @@ export const postNewAgentActionHandlerBuilder = function ( created_at: new Date().toISOString(), ...newAgentAction, agents: [agent.id], + namespaces: [getCurrentNamespace(soClient)], }); const body: PostNewAgentActionResponse = { diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index ff5b6a3c8523b..8ff3f82b7e6c6 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -44,7 +44,8 @@ import { defaultFleetErrorHandler, FleetNotFoundError } from '../../errors'; import * as AgentService from '../../services/agents'; import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics'; import { getAgentStatusForAgentPolicy } from '../../services/agents'; -import { isAgentInNamespace } from '../../services/agents/namespace'; +import { isAgentInNamespace } from '../../services/spaces/agent_namespaces'; +import { getCurrentNamespace } from '../../services/spaces/get_current_namespace'; function verifyNamespace(agent: Agent, namespace?: string) { if (!isAgentInNamespace(agent, namespace)) { @@ -61,7 +62,7 @@ export const getAgentHandler: FleetRequestHandler< const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser; let agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); - verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace()); + verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client)); if (request.query.withMetrics) { agent = (await fetchAndAssignAgentMetrics(esClientCurrentUser, [agent]))[0]; @@ -91,7 +92,7 @@ export const deleteAgentHandler: FleetRequestHandler< try { const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); - verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace()); + verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client)); await AgentService.deleteAgent(esClient, request.params.agentId); @@ -131,7 +132,7 @@ export const updateAgentHandler: FleetRequestHandler< try { const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); - verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace()); + verifyNamespace(agent, getCurrentNamespace(soClient)); await AgentService.updateAgent(esClient, request.params.agentId, partialAgent); const body = { @@ -387,7 +388,11 @@ export const getActionStatusHandler: RequestHandler< const esClient = coreContext.elasticsearch.client.asInternalUser; try { - const actionStatuses = await AgentService.getActionStatuses(esClient, request.query); + const actionStatuses = await AgentService.getActionStatuses( + esClient, + request.query, + getCurrentNamespace(coreContext.savedObjects.client) + ); const body: GetActionStatusResponse = { items: actionStatuses }; return response.ok({ body }); } catch (error) { diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index 016a731d1c67e..4edea93176de4 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -7,7 +7,6 @@ import { type RequestHandler, SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { TypeOf } from '@kbn/config-schema'; -import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; import type { GetEnrollmentAPIKeysRequestSchema, @@ -25,6 +24,7 @@ import * as APIKeyService from '../../services/api_keys'; import { agentPolicyService } from '../../services/agent_policy'; import { defaultFleetErrorHandler, AgentPolicyNotFoundError } from '../../errors'; import { appContextService } from '../../services'; +import { getCurrentNamespace } from '../../services/spaces/get_current_namespace'; export const getEnrollmentApiKeysHandler: RequestHandler< undefined, @@ -40,9 +40,7 @@ export const getEnrollmentApiKeysHandler: RequestHandler< page: request.query.page, perPage: request.query.perPage, kuery: request.query.kuery, - spaceId: useSpaceAwareness - ? soClient.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING - : undefined, + spaceId: useSpaceAwareness ? getCurrentNamespace(soClient) : undefined, }); const body: GetEnrollmentAPIKeysResponse = { list: items, // deprecated @@ -96,8 +94,7 @@ export const deleteEnrollmentApiKeyHandler: RequestHandler< const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asInternalUser; - const currentNamespace = - coreContext.savedObjects.client.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING; + const currentNamespace = getCurrentNamespace(coreContext.savedObjects.client); await APIKeyService.deleteEnrollmentApiKey( esClient, request.params.keyId, @@ -126,8 +123,7 @@ export const getOneEnrollmentApiKeyHandler: RequestHandler< try { const coreContext = await context.core; const esClient = coreContext.elasticsearch.client.asInternalUser; - const currentNamespace = - coreContext.savedObjects.client.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING; + const currentNamespace = getCurrentNamespace(coreContext.savedObjects.client); const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); const apiKey = await APIKeyService.getEnrollmentAPIKey( diff --git a/x-pack/plugins/fleet/server/services/agents/action_status.ts b/x-pack/plugins/fleet/server/services/agents/action_status.ts index 1435857ffe670..5c93c5eaa3c2f 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_status.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_status.ts @@ -23,6 +23,7 @@ import { AGENT_POLICY_INDEX, } from '../../../common'; import { appContextService } from '..'; +import { addNamespaceFilteringToQuery } from '../spaces/query_namespaces_filtering'; /** * Return current bulk actions. @@ -35,10 +36,11 @@ import { appContextService } from '..'; */ export async function getActionStatuses( esClient: ElasticsearchClient, - options: ActionStatusOptions + options: ActionStatusOptions, + namespace?: string ): Promise<ActionStatus[]> { - const actionResults = await getActionResults(esClient, options); - const policyChangeActions = await getPolicyChangeActions(esClient, options); + const actionResults = await getActionResults(esClient, options, namespace); + const policyChangeActions = await getPolicyChangeActions(esClient, options, namespace); const actionStatuses = [...actionResults, ...policyChangeActions] .sort((a: ActionStatus, b: ActionStatus) => (b.creationTime > a.creationTime ? 1 : -1)) .slice(getPage(options), getPerPage(options)); @@ -47,15 +49,17 @@ export async function getActionStatuses( async function getActionResults( esClient: ElasticsearchClient, - options: ActionStatusOptions + options: ActionStatusOptions, + namespace?: string ): Promise<ActionStatus[]> { - const actions = await getActions(esClient, options); + const actions = await getActions(esClient, options, namespace); const cancelledActions = await getCancelledActions(esClient); let acks: any; try { acks = await esClient.search({ index: AGENT_ACTIONS_RESULTS_INDEX, + ignore_unavailable: true, query: { bool: { // There's some perf/caching advantages to using filter over must @@ -101,6 +105,7 @@ async function getActionResults( // query to find errors in action results, cannot do aggregation on text type const errorResults = await esClient.search({ index: AGENT_ACTIONS_RESULTS_INDEX, + ignore_unavailable: true, track_total_hits: true, rest_total_hits_as_int: true, query: { @@ -200,41 +205,41 @@ export function getPerPage(options: ActionStatusOptions) { async function getActions( esClient: ElasticsearchClient, - options: ActionStatusOptions + options: ActionStatusOptions, + namespace?: string ): Promise<ActionStatus[]> { + const query = { + bool: { + must_not: [ + { + term: { + type: 'CANCEL', + }, + }, + ], + ...(options.date || options.latest + ? { + filter: [ + { + range: { + '@timestamp': { + // options.date overrides options.latest + gte: options.date ?? `now-${(options.latest ?? 0) / 1000}s/s`, + lte: options.date ? moment(options.date).add(1, 'days').toISOString() : 'now/s', + }, + }, + }, + ], + } + : {}), + }, + }; const res = await esClient.search<FleetServerAgentAction>({ index: AGENT_ACTIONS_INDEX, ignore_unavailable: true, from: 0, size: getPerPage(options), - query: { - bool: { - must_not: [ - { - term: { - type: 'CANCEL', - }, - }, - ], - ...(options.date || options.latest - ? { - filter: [ - { - range: { - '@timestamp': { - // options.date overrides options.latest - gte: options.date ?? `now-${(options.latest ?? 0) / 1000}s/s`, - lte: options.date - ? moment(options.date).add(1, 'days').toISOString() - : 'now/s', - }, - }, - }, - ], - } - : {}), - }, - }, + query: addNamespaceFilteringToQuery(query, namespace), body: { sort: [{ '@timestamp': 'desc' }], }, @@ -340,49 +345,52 @@ export const hasRolloutPeriodPassed = (source: FleetServerAgentAction) => async function getPolicyChangeActions( esClient: ElasticsearchClient, - options: ActionStatusOptions + options: ActionStatusOptions, + namespace?: string ): Promise<ActionStatus[]> { // option.latest is used to fetch recent errors, which policy change actions do not contain if (options.latest) { return []; } - const agentPoliciesRes = await esClient.search({ - index: AGENT_POLICY_INDEX, - size: getPerPage(options), - query: { - bool: { - filter: [ - { - range: { - revision_idx: { - gt: 1, - }, + const query = { + bool: { + filter: [ + { + range: { + revision_idx: { + gt: 1, }, }, - // This filter is for retrieving docs created by Kibana, as opposed to Fleet Server (coordinator_idx: 1). - // Note: the coordinator will be removed from Fleet Server (https://github.com/elastic/fleet-server/pull/3131), - // so this filter will eventually not be needed. - { - term: { - coordinator_idx: 0, - }, + }, + // This filter is for retrieving docs created by Kibana, as opposed to Fleet Server (coordinator_idx: 1). + // Note: the coordinator will be removed from Fleet Server (https://github.com/elastic/fleet-server/pull/3131), + // so this filter will eventually not be needed. + { + term: { + coordinator_idx: 0, }, - ...(options.date - ? [ - { - range: { - '@timestamp': { - gte: options.date, - lte: moment(options.date).add(1, 'days').toISOString(), - }, + }, + ...(options.date + ? [ + { + range: { + '@timestamp': { + gte: options.date, + lte: moment(options.date).add(1, 'days').toISOString(), }, }, - ] - : []), - ], - }, + }, + ] + : []), + ], }, + }; + const agentPoliciesRes = await esClient.search({ + index: AGENT_POLICY_INDEX, + ignore_unavailable: true, + size: getPerPage(options), + query: addNamespaceFilteringToQuery(query, namespace), sort: [ { '@timestamp': { diff --git a/x-pack/plugins/fleet/server/services/agents/agent_service.ts b/x-pack/plugins/fleet/server/services/agents/agent_service.ts index 9af97cc307c08..c6eb4e55ed8fe 100644 --- a/x-pack/plugins/fleet/server/services/agents/agent_service.ts +++ b/x-pack/plugins/fleet/server/services/agents/agent_service.ts @@ -19,14 +19,14 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SortResults } from '@elastic/elasticsearch/lib/api/types'; -import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; - import type { AgentStatus, ListWithKuery } from '../../types'; import type { Agent, GetAgentStatusResponse } from '../../../common/types'; import { getAuthzFromRequest } from '../security'; import { appContextService } from '../app_context'; import { FleetUnauthorizedError } from '../../errors'; +import { getCurrentNamespace } from '../spaces/get_current_namespace'; + import { getAgentsByKuery, getAgentById } from './crud'; import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status'; import { getLatestAvailableAgentVersion } from './versions'; @@ -183,7 +183,7 @@ export class AgentServiceImpl implements AgentService { this.internalEsClient, soClient, preflightCheck, - soClient.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING + getCurrentNamespace(soClient) ); } diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 368d5c0df834e..64c20cbbc4d6b 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -26,11 +26,11 @@ import { AgentNotFoundError, FleetUnauthorizedError, } from '../../errors'; - import { auditLoggingService } from '../audit_logging'; +import { isAgentInNamespace } from '../spaces/agent_namespaces'; +import { getCurrentNamespace } from '../spaces/get_current_namespace'; import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers'; - import { buildAgentStatusRuntimeField } from './build_status_runtime_field'; import { getLatestAvailableAgentVersion } from './versions'; @@ -406,6 +406,10 @@ export async function getAgentById( throw new AgentNotFoundError(`Agent ${agentId} not found`); } + if (!isAgentInNamespace(agentHit, getCurrentNamespace(soClient))) { + throw new AgentNotFoundError(`${agentHit.id} not found in namespace`); + } + return agentHit; } diff --git a/x-pack/plugins/fleet/server/services/agents/namespace.test.ts b/x-pack/plugins/fleet/server/services/agents/namespace.test.ts deleted file mode 100644 index b5cda85cc45e4..0000000000000 --- a/x-pack/plugins/fleet/server/services/agents/namespace.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { appContextService } from '../app_context'; - -import type { Agent } from '../../types'; - -import { agentsKueryNamespaceFilter, isAgentInNamespace } from './namespace'; - -jest.mock('../app_context'); - -const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>; - -describe('isAgentInNamespace', () => { - describe('with the useSpaceAwareness feature flag disabled', () => { - beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: false, - } as any); - }); - - it('returns true even if the agent is in a different space', () => { - const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; - expect(isAgentInNamespace(agent, 'space2')).toEqual(true); - }); - }); - - describe('with the useSpaceAwareness feature flag enabled', () => { - beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: true, - } as any); - }); - - describe('when the namespace is defined', () => { - it('returns true if the agent namespaces include the namespace', () => { - const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; - expect(isAgentInNamespace(agent, 'space1')).toEqual(true); - }); - - it('returns false if the agent namespaces do not include the namespace', () => { - const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; - expect(isAgentInNamespace(agent, 'space2')).toEqual(false); - }); - - it('returns false if the agent has zero length namespaces', () => { - const agent = { id: '123', namespaces: [] as string[] } as Agent; - expect(isAgentInNamespace(agent, 'space1')).toEqual(false); - }); - - it('returns false if the agent does not have namespaces', () => { - const agent = { id: '123' } as Agent; - expect(isAgentInNamespace(agent, 'space1')).toEqual(false); - }); - }); - - describe('when the namespace is undefined', () => { - it('returns true if the agent does not have namespaces', () => { - const agent = { id: '123' } as Agent; - expect(isAgentInNamespace(agent)).toEqual(true); - }); - - it('returns true if the agent has zero length namespaces', () => { - const agent = { id: '123', namespaces: [] as string[] } as Agent; - expect(isAgentInNamespace(agent)).toEqual(true); - }); - - it('returns true if the agent namespaces include the default one', () => { - const agent = { id: '123', namespaces: ['default'] } as Agent; - expect(isAgentInNamespace(agent)).toEqual(true); - }); - - it('returns false if the agent namespaces include the default one', () => { - const agent = { id: '123', namespaces: ['space1'] } as Agent; - expect(isAgentInNamespace(agent)).toEqual(false); - }); - }); - }); -}); - -describe('agentsKueryNamespaceFilter', () => { - describe('with the useSpaceAwareness feature flag disabled', () => { - beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: false, - } as any); - }); - - it('returns undefined if the useSpaceAwareness feature flag disabled', () => { - expect(agentsKueryNamespaceFilter('space1')).toBeUndefined(); - }); - }); - - describe('with the useSpaceAwareness feature flag enabled', () => { - beforeEach(() => { - mockedAppContextService.getExperimentalFeatures.mockReturnValue({ - useSpaceAwareness: true, - } as any); - }); - - it('returns undefined if the namespace is undefined', () => { - expect(agentsKueryNamespaceFilter()).toBeUndefined(); - }); - - it('returns a kuery for the default space', () => { - expect(agentsKueryNamespaceFilter('default')).toEqual( - 'namespaces:(default) or not namespaces:*' - ); - }); - - it('returns a kuery for custom spaces', () => { - expect(agentsKueryNamespaceFilter('space1')).toEqual('namespaces:(space1)'); - }); - }); -}); diff --git a/x-pack/plugins/fleet/server/services/agents/namespace.ts b/x-pack/plugins/fleet/server/services/agents/namespace.ts deleted file mode 100644 index 28cecdf22e30b..0000000000000 --- a/x-pack/plugins/fleet/server/services/agents/namespace.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; - -import { appContextService } from '../app_context'; - -import type { Agent } from '../../types'; - -export function isAgentInNamespace(agent: Agent, namespace?: string) { - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; - if (!useSpaceAwareness) { - return true; - } - - return ( - (namespace && agent.namespaces?.includes(namespace)) || - (!namespace && - (!agent.namespaces || - agent.namespaces.length === 0 || - agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING))) - ); -} - -export function agentsKueryNamespaceFilter(namespace?: string) { - const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; - if (!useSpaceAwareness || !namespace) { - return; - } - return namespace === DEFAULT_NAMESPACE_STRING - ? `namespaces:(${DEFAULT_NAMESPACE_STRING}) or not namespaces:*` - : `namespaces:(${namespace})`; -} diff --git a/x-pack/plugins/fleet/server/services/agents/status.test.ts b/x-pack/plugins/fleet/server/services/agents/status.test.ts new file mode 100644 index 0000000000000..d4b2fcbc8e9ae --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/status.test.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 { errors as EsErrors } from '@elastic/elasticsearch'; + +import { createAppContextStartContractMock } from '../../mocks'; + +import { appContextService } from '../app_context'; + +import { getAgentStatusForAgentPolicy } from './status'; + +describe('getAgentStatusForAgentPolicy', () => { + beforeEach(async () => { + appContextService.start(createAppContextStartContractMock()); + }); + + afterEach(() => { + appContextService.stop(); + }); + + it('should return agent status for agent policy', async () => { + const esClient = { + search: jest.fn().mockResolvedValue({ + aggregations: { + status: { + buckets: [ + { + key: 'online', + doc_count: 1, + }, + { + key: 'error', + doc_count: 2, + }, + ], + }, + }, + }), + }; + + const soClient = { + find: jest.fn().mockResolvedValue({ + saved_objects: [ + { + id: 'agentPolicyId', + attributes: { + name: 'Policy 1', + }, + }, + ], + }), + }; + const agentPolicyId = 'agentPolicyId'; + const filterKuery = 'filterKuery'; + const spaceId = 'spaceId'; + + const result = await getAgentStatusForAgentPolicy( + esClient as any, + soClient as any, + agentPolicyId, + filterKuery, + spaceId + ); + + expect(result).toEqual( + expect.objectContaining({ + active: 3, + all: 3, + error: 2, + events: 0, + inactive: 0, + offline: 0, + online: 1, + other: 0, + total: 3, + unenrolled: 0, + updating: 0, + }) + ); + + expect(esClient.search).toHaveBeenCalledTimes(1); + }); + + it('retries on 503', async () => { + const esClient = { + search: jest + .fn() + .mockRejectedValueOnce( + new EsErrors.ResponseError({ warnings: [], meta: {} as any, statusCode: 503 }) + ) + .mockResolvedValue({ + aggregations: { + status: { + buckets: [ + { + key: 'online', + doc_count: 1, + }, + { + key: 'error', + doc_count: 2, + }, + ], + }, + }, + }), + }; + + const soClient = { + find: jest.fn().mockResolvedValue({ + saved_objects: [ + { + id: 'agentPolicyId', + attributes: { + name: 'Policy 1', + }, + }, + ], + }), + }; + const agentPolicyId = 'agentPolicyId'; + const filterKuery = 'filterKuery'; + const spaceId = 'spaceId'; + + const result = await getAgentStatusForAgentPolicy( + esClient as any, + soClient as any, + agentPolicyId, + filterKuery, + spaceId + ); + + expect(result).toEqual( + expect.objectContaining({ + active: 3, + all: 3, + error: 2, + events: 0, + inactive: 0, + offline: 0, + online: 1, + other: 0, + total: 3, + unenrolled: 0, + updating: 0, + }) + ); + + expect(esClient.search).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index 31fb9f5fe9218..1940a816dd2d7 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -19,10 +19,12 @@ import { agentStatusesToSummary } from '../../../common/services'; import { AGENTS_INDEX } from '../../constants'; import type { AgentStatus } from '../../types'; -import { FleetUnauthorizedError } from '../../errors'; +import { FleetError, FleetUnauthorizedError } from '../../errors'; import { appContextService } from '../app_context'; +import { retryTransientEsErrors } from '../epm/elasticsearch/retry'; + import { getAgentById, removeSOAttributes } from './crud'; import { buildAgentStatusRuntimeField } from './build_status_runtime_field'; @@ -104,28 +106,34 @@ export async function getAgentStatusForAgentPolicy( let response; try { - response = await esClient.search< - null, - { status: AggregationsTermsAggregateBase<AggregationsStatusTermsBucketKeys> } - >({ - index: AGENTS_INDEX, - size: 0, - query, - fields: Object.keys(runtimeFields), - runtime_mappings: runtimeFields, - aggregations: { - status: { - terms: { - field: 'status', - size: Object.keys(statuses).length, + response = await retryTransientEsErrors( + () => + esClient.search< + null, + { status: AggregationsTermsAggregateBase<AggregationsStatusTermsBucketKeys> } + >({ + index: AGENTS_INDEX, + size: 0, + query, + fields: Object.keys(runtimeFields), + runtime_mappings: runtimeFields, + aggregations: { + status: { + terms: { + field: 'status', + size: Object.keys(statuses).length, + }, + }, }, - }, - }, - ignore_unavailable: true, - }); + ignore_unavailable: true, + }), + { logger } + ); } catch (error) { logger.debug(`Error getting agent statuses: ${error}`); - throw error; + throw new FleetError( + `Unable to retrive agent statuses for policy ${agentPolicyId} due to error: ${error.message}` + ); } const buckets = (response?.aggregations?.status?.buckets || @@ -153,11 +161,14 @@ export async function getAgentStatusForAgentPolicy( total: allActive, }; } + export async function getIncomingDataByAgentsId( esClient: ElasticsearchClient, agentsIds: string[], returnDataPreview: boolean = false ) { + const logger = appContextService.getLogger(); + try { const { has_all_requested: hasAllPrivileges } = await esClient.security.hasPrivileges({ body: { @@ -169,46 +180,51 @@ export async function getIncomingDataByAgentsId( ], }, }); + if (!hasAllPrivileges) { throw new FleetUnauthorizedError('Missing permissions to read data streams indices'); } - const searchResult = await esClient.search({ - index: DATA_STREAM_INDEX_PATTERN, - allow_partial_search_results: true, - _source: returnDataPreview, - timeout: '5s', - size: returnDataPreview ? MAX_AGENT_DATA_PREVIEW_SIZE : 0, - body: { - query: { - bool: { - filter: [ - { - terms: { - 'agent.id': agentsIds, - }, - }, - { - range: { - '@timestamp': { - gte: 'now-5m', - lte: 'now', + const searchResult = await retryTransientEsErrors( + () => + esClient.search({ + index: DATA_STREAM_INDEX_PATTERN, + allow_partial_search_results: true, + _source: returnDataPreview, + timeout: '5s', + size: returnDataPreview ? MAX_AGENT_DATA_PREVIEW_SIZE : 0, + body: { + query: { + bool: { + filter: [ + { + terms: { + 'agent.id': agentsIds, + }, + }, + { + range: { + '@timestamp': { + gte: 'now-5m', + lte: 'now', + }, + }, }, + ], + }, + }, + aggs: { + agent_ids: { + terms: { + field: 'agent.id', + size: agentsIds.length, }, }, - ], - }, - }, - aggs: { - agent_ids: { - terms: { - field: 'agent.id', - size: agentsIds.length, }, }, - }, - }, - }); + }), + { logger } + ); if (!searchResult.aggregations?.agent_ids) { return { @@ -231,7 +247,10 @@ export async function getIncomingDataByAgentsId( ); return { items, dataPreview }; - } catch (e) { - throw new Error(e); + } catch (error) { + logger.debug(`Error getting incoming data for agents: ${error}`); + throw new FleetError( + `Unable to retrive incoming data for agents due to error: ${error.message}` + ); } } diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts index e4a7ca8f0148d..f3443458249b7 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts @@ -12,10 +12,13 @@ import { AgentReassignmentError } from '../../errors'; import { SO_SEARCH_LIMIT } from '../../constants'; +import { agentsKueryNamespaceFilter, isAgentInNamespace } from '../spaces/agent_namespaces'; + +import { getCurrentNamespace } from '../spaces/get_current_namespace'; + import { getAgentsById, getAgentsByKuery, openPointInTime } from './crud'; import type { GetAgentsOptions } from '.'; import { UpdateAgentTagsActionRunner, updateTagsBatch } from './update_agent_tags_action_runner'; -import { agentsKueryNamespaceFilter, isAgentInNamespace } from './namespace'; export async function updateAgentTags( soClient: SavedObjectsClientContract, @@ -26,7 +29,7 @@ export async function updateAgentTags( ): Promise<{ actionId: string }> { const outgoingErrors: Record<Agent['id'], Error> = {}; const givenAgents: Agent[] = []; - const currentNameSpace = soClient.getCurrentNamespace(); + const currentNameSpace = getCurrentNamespace(soClient); if ('agentIds' in options) { const maybeAgents = await getAgentsById(esClient, soClient, options.agentIds); diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts index 44fef8d4a6c2b..8b68e1b6e9fd8 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts @@ -17,6 +17,8 @@ import { appContextService } from '../app_context'; import { FleetError } from '../../errors'; +import { getCurrentNamespace } from '../spaces/get_current_namespace'; + import { ActionRunner } from './action_runner'; import { BulkActionTaskType } from './bulk_action_types'; @@ -149,8 +151,8 @@ export async function updateTagsBatch( const versionConflictCount = res.version_conflicts ?? 0; const versionConflictIds = isLastRetry ? getUuidArray(versionConflictCount) : []; - const currentNameSpace = soClient.getCurrentNamespace(); - const namespaces = currentNameSpace ? [currentNameSpace] : []; + const currentNameSpace = getCurrentNamespace(soClient); + const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {}; // creating an action doc so that update tags shows up in activity // the logic only saves agent count in the action that updated, failed or in case of last retry, conflicted @@ -160,7 +162,7 @@ export async function updateTagsBatch( agents: updatedIds .concat(failures.map((failure) => failure.id)) .concat(isLastRetry ? versionConflictIds : []), - namespaces, + ...namespaces, created_at: new Date().toISOString(), type: 'UPDATE_TAGS', total: options.total ?? res.total, @@ -180,7 +182,7 @@ export async function updateTagsBatch( updatedIds.map((id) => ({ agentId: id, actionId, - namespaces, + ...namespaces, })) ); appContextService.getLogger().debug(`action updated result wrote on ${updatedCount} agents`); diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts index 6f1390f5e17cd..3ae1c4a6cffba 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts @@ -12,7 +12,7 @@ import { appContextService } from '../../..'; import { getPackageSavedObjects } from '../../packages/get'; const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; -export const indexPatternTypes = Object.values(dataTypes); +export const indexPatternTypes = [dataTypes.Logs, dataTypes.Metrics]; export function getIndexPatternSavedObjects() { return indexPatternTypes.map((indexPatternType) => ({ diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index ae2f5721aae8b..1911ed14a7c80 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -40,7 +40,7 @@ import type { InstallResult } from '../../../common'; import { appContextService } from '..'; -import type { CustomPackageDatasetConfiguration } from './packages/install'; +import type { CustomPackageDatasetConfiguration, EnsurePackageResult } from './packages/install'; import type { FetchFindLatestPackageOptions } from './registry'; import { getPackageFieldsMetadata } from './registry'; @@ -73,7 +73,7 @@ export interface PackageClient { pkgVersion?: string; spaceId?: string; force?: boolean; - }): Promise<Installation>; + }): Promise<EnsurePackageResult>; installPackage(options: { pkgName: string; @@ -201,7 +201,7 @@ class PackageClientImpl implements PackageClient { pkgVersion?: string; spaceId?: string; force?: boolean; - }): Promise<Installation> { + }): Promise<EnsurePackageResult> { await this.#runPreflight(INSTALL_PACKAGES_AUTHZ); return ensureInstalledPackage({ diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index f27cb794475c9..ce406773a0635 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -158,6 +158,11 @@ export async function isPackageVersionOrLaterInstalled(options: { }); } +export interface EnsurePackageResult { + status: InstallResultStatus; + package: Installation; +} + export async function ensureInstalledPackage(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; @@ -166,7 +171,7 @@ export async function ensureInstalledPackage(options: { spaceId?: string; force?: boolean; authorizationHeader?: HTTPAuthorizationHeader | null; -}): Promise<Installation> { +}): Promise<EnsurePackageResult> { const { savedObjectsClient, pkgName, @@ -189,7 +194,10 @@ export async function ensureInstalledPackage(options: { }); if (installedPackageResult) { - return installedPackageResult.package; + return { + status: 'already_installed', + package: installedPackageResult.package, + }; } const pkgkey = Registry.pkgToPkgKey(pkgKeyProps); const installResult = await installPackage({ @@ -226,7 +234,10 @@ export async function ensureInstalledPackage(options: { const installation = await getInstallation({ savedObjectsClient, pkgName }); if (!installation) throw new FleetError(`Could not get installation for ${pkgName}`); - return installation; + return { + status: 'installed', + package: installation, + }; } export async function handleInstallPackageFailure({ diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 8ee996c69e523..d9605880c1572 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -6,7 +6,7 @@ */ /* eslint-disable max-classes-per-file */ -import { omit, partition, isEqual, cloneDeep } from 'lodash'; +import { omit, partition, isEqual, cloneDeep, without } from 'lodash'; import { i18n } from '@kbn/i18n'; import semverLt from 'semver/functions/lt'; import { getFlattenedObject } from '@kbn/std'; @@ -67,6 +67,7 @@ import type { DeletePackagePoliciesResponse, PolicySecretReference, AssetsMap, + AgentPolicy, } from '../../common/types'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; import { @@ -1044,6 +1045,8 @@ class PackagePolicyClientImpl implements PackagePolicyClient { error: Error | SavedObjectError; }> = []; + const secretStorageEnabled = await isSecretStorageEnabled(esClient, soClient); + await pMap(packagePolicyUpdates, async (packagePolicyUpdate) => { try { const id = packagePolicyUpdate.id; @@ -1075,7 +1078,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { if (pkgInfoAndAsset) { const { pkgInfo, assetsMap } = pkgInfoAndAsset; validatePackagePolicyOrThrow(packagePolicy, pkgInfo); - if (await isSecretStorageEnabled(esClient, soClient)) { + if (secretStorageEnabled) { const secretsRes = await extractAndUpdateSecrets({ oldPackagePolicy, packagePolicyUpdate: { ...restOfPackagePolicy, inputs }, @@ -1248,15 +1251,20 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ]; const hostedAgentPolicies: string[] = []; + const agentlessAgentPolicies = []; for (const agentPolicyId of uniqueAgentPolicyIds) { try { - await validateIsNotHostedPolicy( + const agentPolicy = await validateIsNotHostedPolicy( soClient, agentPolicyId, options?.force, 'Cannot remove integrations of hosted agent policy' ); + // collect agentless agent policies to delete + if (agentPolicy.supports_agentless) { + agentlessAgentPolicies.push(agentPolicyId); + } } catch (e) { hostedAgentPolicies.push(agentPolicyId); } @@ -1331,14 +1339,21 @@ class PackagePolicyClientImpl implements PackagePolicyClient { }); } + if (agentlessAgentPolicies.length > 0) { + for (const agentPolicyId of agentlessAgentPolicies) { + await agentPolicyService.delete(soClient, esClient, agentPolicyId, { force: true }); + } + } + if (!options?.skipUnassignFromAgentPolicies) { - const uniquePolicyIdsR = [ + let uniquePolicyIdsR = [ ...new Set( result .filter((r) => r.success && r.policy_ids && r.policy_ids.length > 0) .flatMap((r) => r.policy_ids!) ), ]; + uniquePolicyIdsR = without(uniquePolicyIdsR, ...agentlessAgentPolicies); const agentPoliciesWithEndpointPackagePolicies = result.reduce((acc, cur) => { if (cur.success && cur.policy_ids && cur.package?.name === 'endpoint') { @@ -2754,7 +2769,7 @@ async function validateIsNotHostedPolicy( id: string, force = false, errorMessage?: string -) { +): Promise<AgentPolicy> { const agentPolicy = await agentPolicyService.get(soClient, id, false); if (!agentPolicy) { @@ -2769,6 +2784,8 @@ async function validateIsNotHostedPolicy( errorMessage ?? `Cannot update integrations of hosted agent policy ${id}` ); } + + return agentPolicy; } export function sendUpdatePackagePolicyTelemetryEvent( diff --git a/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.test.ts b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.test.ts new file mode 100644 index 0000000000000..3cf070ab8fea3 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.test.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 { appContextService } from '../app_context'; + +import type { Agent } from '../../types'; + +import { agentsKueryNamespaceFilter, isAgentInNamespace } from './agent_namespaces'; + +jest.mock('../app_context'); + +const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>; + +describe('isAgentInNamespace', () => { + describe('with the useSpaceAwareness feature flag disabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: false, + } as any); + }); + + it('returns true even if the agent is in a different space', () => { + const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; + expect(isAgentInNamespace(agent, 'space2')).toEqual(true); + }); + }); + + describe('with the useSpaceAwareness feature flag enabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: true, + } as any); + }); + + describe('when the namespace is defined', () => { + it('returns true in a custom space if the agent namespaces include the namespace', () => { + const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; + expect(isAgentInNamespace(agent, 'space1')).toEqual(true); + }); + + it('returns false in a custom space if the agent namespaces do not include the namespace', () => { + const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; + expect(isAgentInNamespace(agent, 'space2')).toEqual(false); + }); + + it('returns true in the default space if the agent has zero length namespaces', () => { + const agent = { id: '123', namespaces: [] as string[] } as Agent; + expect(isAgentInNamespace(agent, 'default')).toEqual(true); + }); + + it('returns false in a custom space if the agent has zero length namespaces', () => { + const agent = { id: '123', namespaces: [] as string[] } as Agent; + expect(isAgentInNamespace(agent, 'space1')).toEqual(false); + }); + + it('returns true in the default space if the agent does not have namespaces', () => { + const agent = { id: '123' } as Agent; + expect(isAgentInNamespace(agent, 'default')).toEqual(true); + }); + + it('returns false in a custom space if the agent does not have namespaces', () => { + const agent = { id: '123' } as Agent; + expect(isAgentInNamespace(agent, 'space1')).toEqual(false); + }); + }); + + describe('when the namespace is undefined', () => { + it('returns true if the agent does not have namespaces', () => { + const agent = { id: '123' } as Agent; + expect(isAgentInNamespace(agent)).toEqual(true); + }); + + it('returns true if the agent has zero length namespaces', () => { + const agent = { id: '123', namespaces: [] as string[] } as Agent; + expect(isAgentInNamespace(agent)).toEqual(true); + }); + + it('returns true if the agent namespaces include the default one', () => { + const agent = { id: '123', namespaces: ['default'] } as Agent; + expect(isAgentInNamespace(agent)).toEqual(true); + }); + + it('returns false if the agent namespaces include the default one', () => { + const agent = { id: '123', namespaces: ['space1'] } as Agent; + expect(isAgentInNamespace(agent)).toEqual(false); + }); + }); + }); +}); + +describe('agentsKueryNamespaceFilter', () => { + describe('with the useSpaceAwareness feature flag disabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: false, + } as any); + }); + + it('returns undefined if the useSpaceAwareness feature flag disabled', () => { + expect(agentsKueryNamespaceFilter('space1')).toBeUndefined(); + }); + }); + + describe('with the useSpaceAwareness feature flag enabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: true, + } as any); + }); + + it('returns undefined if the namespace is undefined', () => { + expect(agentsKueryNamespaceFilter()).toBeUndefined(); + }); + + it('returns a kuery for the default space', () => { + expect(agentsKueryNamespaceFilter('default')).toEqual( + 'namespaces:(default) or not namespaces:*' + ); + }); + + it('returns a kuery for custom spaces', () => { + expect(agentsKueryNamespaceFilter('space1')).toEqual('namespaces:(space1)'); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.ts b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.ts new file mode 100644 index 0000000000000..1a1834635662b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/agent_namespaces.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 { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; + +import { appContextService } from '../app_context'; + +import type { Agent } from '../../types'; + +export function isAgentInNamespace(agent: Agent, namespace?: string) { + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (!useSpaceAwareness) { + return true; + } + + // In a custom space, only return true if the agent is explicitly in that space. + if (namespace && namespace !== DEFAULT_NAMESPACE_STRING) { + return agent.namespaces?.includes(namespace) ?? false; + } + + // In the default space OR in if the current namespace is not defined, + // return true if the agent is explicitly in the default space OR if it has no defined namespaces. + return ( + !agent.namespaces || + agent.namespaces.length === 0 || + agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING) + ); +} + +export function agentsKueryNamespaceFilter(namespace?: string) { + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (!useSpaceAwareness || !namespace) { + return; + } + return namespace === DEFAULT_NAMESPACE_STRING + ? `namespaces:(${DEFAULT_NAMESPACE_STRING}) or not namespaces:*` + : `namespaces:(${namespace})`; +} diff --git a/x-pack/plugins/fleet/server/services/spaces/get_current_namespace.ts b/x-pack/plugins/fleet/server/services/spaces/get_current_namespace.ts new file mode 100644 index 0000000000000..534e21ad44ae8 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/get_current_namespace.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 { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; +import type { SavedObjectsClientContract } from '@kbn/core/server'; + +/* + * soClient.getCurrentNamespace() returns undefined in the default space. + * This helper returns the name of the current space and 'default' in the default space. + */ +export function getCurrentNamespace(soClient: SavedObjectsClientContract) { + return soClient.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING; +} diff --git a/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.test.ts b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.test.ts new file mode 100644 index 0000000000000..43713597e364a --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.test.ts @@ -0,0 +1,296 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { appContextService } from '..'; + +import { addNamespaceFilteringToQuery } from './query_namespaces_filtering'; + +const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>; + +jest.mock('../app_context'); + +describe('addNamespaceFilteringToQuery', () => { + const baseActionQuery = { + bool: { + must_not: [ + { + term: { + type: 'CANCEL', + }, + }, + ], + }, + }; + + const baseActionQueryWithFilter = { + bool: { + must_not: [ + { + term: { + type: 'CANCEL', + }, + }, + ], + filter: [ + { + range: { + '@timestamp': { + gte: 'now-3600s/s', + lte: 'now/s', + }, + }, + }, + ], + }, + }; + + const basePolicyQuery = { + bool: { + filter: [ + { + range: { + revision_idx: { + gt: 1, + }, + }, + }, + { + term: { + coordinator_idx: 0, + }, + }, + ], + }, + }; + + describe('with the useSpaceAwareness feature flag disabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: false, + } as any); + }); + + it('should return the same query', () => { + expect(addNamespaceFilteringToQuery(baseActionQuery, 'mySpace')).toEqual(baseActionQuery); + }); + }); + + describe('with the useSpaceAwareness feature flag enabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: true, + } as any); + }); + + it('should return the same query if the current namespace is undefined', () => { + expect(addNamespaceFilteringToQuery(baseActionQuery)).toEqual(baseActionQuery); + }); + + it('should add a filter to the base action query in a custom space', () => { + expect(addNamespaceFilteringToQuery(baseActionQuery, 'mySpace')).toEqual({ + bool: { + must_not: [ + { + term: { + type: 'CANCEL', + }, + }, + ], + filter: [ + { + terms: { + namespaces: ['mySpace'], + }, + }, + ], + }, + }); + }); + + it('should add a filter to the base action query in a custom space if there is already filtering', () => { + expect(addNamespaceFilteringToQuery(baseActionQueryWithFilter, 'mySpace')).toEqual({ + bool: { + must_not: [ + { + term: { + type: 'CANCEL', + }, + }, + ], + filter: [ + { + range: { + '@timestamp': { + gte: 'now-3600s/s', + lte: 'now/s', + }, + }, + }, + { + terms: { + namespaces: ['mySpace'], + }, + }, + ], + }, + }); + }); + + it('should add a filter to the base policy query in a custom space', () => { + expect(addNamespaceFilteringToQuery(basePolicyQuery, 'mySpace')).toEqual({ + bool: { + filter: [ + { + range: { + revision_idx: { + gt: 1, + }, + }, + }, + { + term: { + coordinator_idx: 0, + }, + }, + { + terms: { + namespaces: ['mySpace'], + }, + }, + ], + }, + }); + }); + + it('should add a filter to the base action query in the default space', () => { + expect(addNamespaceFilteringToQuery(baseActionQuery, 'default')).toEqual({ + bool: { + must_not: [ + { + term: { + type: 'CANCEL', + }, + }, + ], + filter: [ + { + bool: { + should: [ + { + terms: { + namespaces: ['default'], + }, + }, + { + bool: { + must_not: [ + { + exists: { + field: 'namespaces', + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }); + }); + + it('should add a filter to the base action query in the default space if there is already filtering', () => { + expect(addNamespaceFilteringToQuery(baseActionQueryWithFilter, 'default')).toEqual({ + bool: { + must_not: [ + { + term: { + type: 'CANCEL', + }, + }, + ], + filter: [ + { + range: { + '@timestamp': { + gte: 'now-3600s/s', + lte: 'now/s', + }, + }, + }, + { + bool: { + should: [ + { + terms: { + namespaces: ['default'], + }, + }, + { + bool: { + must_not: [ + { + exists: { + field: 'namespaces', + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }); + }); + + it('should add a filter to the base policy query in the default space', () => { + expect(addNamespaceFilteringToQuery(basePolicyQuery, 'default')).toEqual({ + bool: { + filter: [ + { + range: { + revision_idx: { + gt: 1, + }, + }, + }, + { + term: { + coordinator_idx: 0, + }, + }, + { + bool: { + should: [ + { + terms: { + namespaces: ['default'], + }, + }, + { + bool: { + must_not: [ + { + exists: { + field: 'namespaces', + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.ts b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.ts new file mode 100644 index 0000000000000..a2c233cbcc21a --- /dev/null +++ b/x-pack/plugins/fleet/server/services/spaces/query_namespaces_filtering.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 { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; + +import { appContextService } from '..'; + +export function addNamespaceFilteringToQuery(query: any, namespace?: string) { + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (!useSpaceAwareness || !namespace) { + return query; + } + + // In the default space, return documents with namespaces: ['default'] OR with no namespaces property. + // In custom spaces, return documents with namespaces: ['custom_space']. + const filter = + namespace === DEFAULT_NAMESPACE_STRING + ? { + bool: { + should: [ + { + terms: { + namespaces: ['default'], + }, + }, + { + bool: { + must_not: [ + { + exists: { + field: 'namespaces', + }, + }, + ], + }, + }, + ], + }, + } + : { + terms: { + namespaces: [namespace], + }, + }; + + return { + bool: { + ...query.bool, + filter: query.bool.filter ? [...query.bool.filter, filter] : [filter], + }, + }; +} diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts index 43928b3737e21..ca5858dccb5c5 100644 --- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts @@ -56,7 +56,11 @@ export const AgentPolicyBaseSchema = { }), monitoring_enabled: schema.maybe( schema.arrayOf( - schema.oneOf([schema.literal(dataTypes.Logs), schema.literal(dataTypes.Metrics)]) + schema.oneOf([ + schema.literal(dataTypes.Logs), + schema.literal(dataTypes.Metrics), + schema.literal(dataTypes.Traces), + ]) ) ), keep_monitoring_alive: schema.maybe(schema.boolean({ defaultValue: false })), diff --git a/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts b/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts index f6c7767b4e2d0..c808123b14a76 100644 --- a/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts +++ b/x-pack/plugins/index_management/public/application/hooks/use_index_errors.ts @@ -45,12 +45,15 @@ export const useIndexErrors = ( if (!model) { return { field, - error: i18n.translate('xpack.idxMgmt.indexOverview.indexErrors.missingModelError', { - defaultMessage: 'Model not found for inference endpoint {inferenceId}', - values: { - inferenceId: field.source.inference_id as string, - }, - }), + error: i18n.translate( + 'xpack.idxMgmt.indexOverview.indexErrors.missingInferenceEndpointError', + { + defaultMessage: 'Inference endpoint {inferenceId} not found', + values: { + inferenceId: field.source.inference_id as string, + }, + } + ), }; } if (isLocalModel(model)) { diff --git a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts index 73dc3f59a08b1..97cb0a1c1c69c 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/validate_schemas.ts @@ -13,7 +13,8 @@ export const templateSchema = schema.object({ version: schema.maybe(schema.number()), order: schema.maybe(schema.number()), priority: schema.maybe(schema.number()), - allowAutoCreate: schema.string(), + // Not present for legacy templates + allowAutoCreate: schema.maybe(schema.string()), template: schema.maybe( schema.object({ settings: schema.maybe(schema.object({}, { unknowns: 'allow' })), diff --git a/x-pack/plugins/inference/README.md b/x-pack/plugins/inference/README.md new file mode 100644 index 0000000000000..1807da7f29faa --- /dev/null +++ b/x-pack/plugins/inference/README.md @@ -0,0 +1,100 @@ +# Inference plugin + +The inference plugin is a central place to handle all interactions with the Elasticsearch Inference API and +external LLM APIs. Its goals are: + +- Provide a single place for all interactions with large language models and other generative AI adjacent tasks. +- Abstract away differences between different LLM providers like OpenAI, Bedrock and Gemini +- Host commonly used LLM-based tasks like generating ES|QL from natural language and knowledge base recall. +- Allow us to move gradually to the \_inference endpoint without disrupting engineers. + +## Architecture and examples + +![CleanShot 2024-07-14 at 14 45 27@2x](https://github.com/user-attachments/assets/e65a3e47-bce1-4dcf-bbed-4f8ac12a104f) + +## Terminology + +The following concepts are commonly used throughout the plugin: + +- **chat completion**: the process in which the LLM generates the next message in the conversation. This is sometimes referred to as inference, text completion, text generation or content generation. +- **tasks**: higher level tasks that, based on its input, use the LLM in conjunction with other services like Elasticsearch to achieve a result. The example in this POC is natural language to ES|QL. +- **tools**: a set of tools that the LLM can choose to use when generating the next message. In essence, it allows the consumer of the API to define a schema for structured output instead of plain text, and having the LLM select the most appropriate one. +- **tool call**: when the LLM has chosen a tool (schema) to use for its output, and returns a document that matches the schema, this is referred to as a tool call. + +## Usage examples + +```ts +class MyPlugin { + setup(coreSetup, pluginsSetup) { + const router = coreSetup.http.createRouter(); + + router.post( + { + path: '/internal/my_plugin/do_something', + validate: { + body: schema.object({ + connectorId: schema.string(), + }), + }, + }, + async (context, request, response) => { + const [coreStart, pluginsStart] = await coreSetup.getStartServices(); + + const inferenceClient = pluginsSetup.inference.getClient({ request }); + + const chatComplete$ = inferenceClient.chatComplete({ + connectorId: request.body.connectorId, + system: `Here is my system message`, + messages: [ + { + role: MessageRole.User, + content: 'Do something', + }, + ], + }); + + const message = await lastValueFrom( + chatComplete$.pipe(withoutTokenCountEvents(), withoutChunkEvents()) + ); + + return response.ok({ + body: { + message, + }, + }); + } + ); + } +} +``` + +## Services + +### `chatComplete`: + +`chatComplete` generates a response to a prompt or a conversation using the LLM. Here's what is supported: + +- Normalizing request and response formats from different connector types (e.g. OpenAI, Bedrock, Claude, Elastic Inference Service) +- Tool calling and validation of tool calls +- Emits token count events +- Emits message events, which is the concatenated message based on the response chunks + +### `output` + +`output` is a wrapper around `chatComplete` that is catered towards a single use case: having the LLM output a structured response, based on a schema. It also drops the token count events to simplify usage. + +### Observable event streams + +These APIs, both on the client and the server, return Observables that emit events. When converting the Observable into a stream, the following things happen: + +- Errors are caught and serialized as events sent over the stream (after an error, the stream ends). +- The response stream outputs data as [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) +- The client that reads the stream, parses the event source as an Observable, and if it encounters a serialized error, it deserializes it and throws an error in the Observable. + +### Errors + +All known errors are instances, and not extensions, from the `InferenceTaskError` base class, which has a `code`, a `message`, and `meta` information about the error. This allows us to serialize and deserialize errors over the wire without a complicated factory pattern. + +### Tools + +Tools are defined as a record, with a `description` and optionally a `schema`. The reason why it's a record is because of type-safety. This allows us to have fully typed tool calls (e.g. when the name of the tool being called is `x`, its arguments are typed as the schema of `x`). diff --git a/x-pack/plugins/inference/common/chat_complete/errors.ts b/x-pack/plugins/inference/common/chat_complete/errors.ts new file mode 100644 index 0000000000000..8497350d7b49b --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/errors.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 { i18n } from '@kbn/i18n'; +import { InferenceTaskError } from '../errors'; +import type { UnvalidatedToolCall } from './tools'; + +export enum ChatCompletionErrorCode { + TokenLimitReachedError = 'tokenLimitReachedError', + ToolNotFoundError = 'toolNotFoundError', + ToolValidationError = 'toolValidationError', +} + +export type ChatCompletionTokenLimitReachedError = InferenceTaskError< + ChatCompletionErrorCode.TokenLimitReachedError, + { + tokenLimit?: number; + tokenCount?: number; + } +>; + +export type ChatCompletionToolNotFoundError = InferenceTaskError< + ChatCompletionErrorCode.ToolNotFoundError, + { + name: string; + } +>; + +export type ChatCompletionToolValidationError = InferenceTaskError< + ChatCompletionErrorCode.ToolValidationError, + { + name?: string; + arguments?: string; + errorsText?: string; + toolCalls?: UnvalidatedToolCall[]; + } +>; + +export function createTokenLimitReachedError( + tokenLimit?: number, + tokenCount?: number +): ChatCompletionTokenLimitReachedError { + return new InferenceTaskError( + ChatCompletionErrorCode.TokenLimitReachedError, + i18n.translate('xpack.inference.chatCompletionError.tokenLimitReachedError', { + defaultMessage: `Token limit reached. Token limit is {tokenLimit}, but the current conversation has {tokenCount} tokens.`, + values: { tokenLimit, tokenCount }, + }), + { tokenLimit, tokenCount } + ); +} + +export function createToolNotFoundError(name: string): ChatCompletionToolNotFoundError { + return new InferenceTaskError( + ChatCompletionErrorCode.ToolNotFoundError, + `Tool ${name} called but was not available`, + { + name, + } + ); +} + +export function createToolValidationError( + message: string, + meta: { + name?: string; + arguments?: string; + errorsText?: string; + toolCalls?: UnvalidatedToolCall[]; + } +): ChatCompletionToolValidationError { + return new InferenceTaskError(ChatCompletionErrorCode.ToolValidationError, message, meta); +} + +export function isToolValidationError(error?: Error): error is ChatCompletionToolValidationError { + return ( + error instanceof InferenceTaskError && + error.code === ChatCompletionErrorCode.ToolValidationError + ); +} + +export function isTokenLimitReachedError( + error: Error +): error is ChatCompletionTokenLimitReachedError { + return ( + error instanceof InferenceTaskError && + error.code === ChatCompletionErrorCode.TokenLimitReachedError + ); +} + +export function isToolNotFoundError(error: Error): error is ChatCompletionToolNotFoundError { + return ( + error instanceof InferenceTaskError && error.code === ChatCompletionErrorCode.ToolNotFoundError + ); +} diff --git a/x-pack/plugins/inference/common/chat_complete/index.ts b/x-pack/plugins/inference/common/chat_complete/index.ts new file mode 100644 index 0000000000000..175f86f74b5c4 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/index.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 type { Observable } from 'rxjs'; +import type { InferenceTaskEventBase } from '../tasks'; +import type { ToolCall, ToolCallsOf, ToolOptions } from './tools'; + +export enum MessageRole { + User = 'user', + Assistant = 'assistant', + Tool = 'tool', +} + +interface MessageBase<TRole extends MessageRole> { + role: TRole; +} + +export type UserMessage = MessageBase<MessageRole.User> & { content: string }; + +export type AssistantMessage = MessageBase<MessageRole.Assistant> & { + content: string | null; + toolCalls?: Array<ToolCall<string, Record<string, any> | undefined>>; +}; + +export type ToolMessage<TToolResponse extends Record<string, any> | unknown> = + MessageBase<MessageRole.Tool> & { + toolCallId: string; + response: TToolResponse; + }; + +export type Message = UserMessage | AssistantMessage | ToolMessage<unknown>; + +export type ChatCompletionMessageEvent<TToolOptions extends ToolOptions> = + InferenceTaskEventBase<ChatCompletionEventType.ChatCompletionMessage> & { + content: string; + } & { toolCalls: ToolCallsOf<TToolOptions>['toolCalls'] }; + +export type ChatCompletionResponse<TToolOptions extends ToolOptions = ToolOptions> = Observable< + ChatCompletionEvent<TToolOptions> +>; + +export enum ChatCompletionEventType { + ChatCompletionChunk = 'chatCompletionChunk', + ChatCompletionTokenCount = 'chatCompletionTokenCount', + ChatCompletionMessage = 'chatCompletionMessage', +} + +export interface ChatCompletionChunkToolCall { + index: number; + toolCallId: string; + function: { + name: string; + arguments: string; + }; +} + +export type ChatCompletionChunkEvent = + InferenceTaskEventBase<ChatCompletionEventType.ChatCompletionChunk> & { + content: string; + tool_calls: ChatCompletionChunkToolCall[]; + }; + +export type ChatCompletionTokenCountEvent = + InferenceTaskEventBase<ChatCompletionEventType.ChatCompletionTokenCount> & { + tokens: { + prompt: number; + completion: number; + total: number; + }; + }; + +export type ChatCompletionEvent<TToolOptions extends ToolOptions = ToolOptions> = + | ChatCompletionChunkEvent + | ChatCompletionTokenCountEvent + | ChatCompletionMessageEvent<TToolOptions>; + +/** + * Request a completion from the LLM based on a prompt or conversation. + * + * @param {string} options.connectorId The ID of the connector to use + * @param {string} [options.system] A system message that defines the behavior of the LLM. + * @param {Message[]} options.message A list of messages that make up the conversation to be completed. + * @param {ToolChoice} [options.toolChoice] Force the LLM to call a (specific) tool, or no tool + * @param {Record<string, ToolDefinition>} [options.tools] A map of tools that can be called by the LLM + */ +export type ChatCompleteAPI<TToolOptions extends ToolOptions = ToolOptions> = ( + options: { + connectorId: string; + system?: string; + messages: Message[]; + } & TToolOptions +) => ChatCompletionResponse<TToolOptions>; diff --git a/x-pack/plugins/inference/common/chat_complete/request.ts b/x-pack/plugins/inference/common/chat_complete/request.ts new file mode 100644 index 0000000000000..104d1856c9c80 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/request.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 { Message } from '.'; +import { ToolOptions } from './tools'; + +export type ChatCompleteRequestBody = { + connectorId: string; + stream?: boolean; + system?: string; + messages: Message[]; +} & ToolOptions; diff --git a/x-pack/plugins/inference/common/chat_complete/tool_schema.ts b/x-pack/plugins/inference/common/chat_complete/tool_schema.ts new file mode 100644 index 0000000000000..5ca3e0ab57a49 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/tool_schema.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Required, ValuesType, UnionToIntersection } from 'utility-types'; + +interface ToolSchemaFragmentBase { + description?: string; +} + +interface ToolSchemaTypeObject extends ToolSchemaFragmentBase { + type: 'object'; + properties: Record<string, ToolSchemaFragment>; + required?: string[] | readonly string[]; +} + +interface ToolSchemaTypeString extends ToolSchemaFragmentBase { + type: 'string'; + const?: string; + enum?: string[] | readonly string[]; +} + +interface ToolSchemaTypeBoolean extends ToolSchemaFragmentBase { + type: 'boolean'; + const?: string; + enum?: string[] | readonly string[]; +} + +interface ToolSchemaTypeNumber extends ToolSchemaFragmentBase { + type: 'number'; + const?: string; + enum?: string[] | readonly string[]; +} + +interface ToolSchemaAnyOf extends ToolSchemaFragmentBase { + anyOf: ToolSchemaType[]; +} + +interface ToolSchemaAllOf extends ToolSchemaFragmentBase { + allOf: ToolSchemaType[]; +} + +interface ToolSchemaTypeArray extends ToolSchemaFragmentBase { + type: 'array'; + items: Exclude<ToolSchemaType, ToolSchemaTypeArray>; +} + +type ToolSchemaType = + | ToolSchemaTypeObject + | ToolSchemaTypeString + | ToolSchemaTypeBoolean + | ToolSchemaTypeNumber + | ToolSchemaTypeArray; + +type ToolSchemaFragment = ToolSchemaType | ToolSchemaAnyOf | ToolSchemaAllOf; + +type FromToolSchemaObject<TToolSchemaObject extends ToolSchemaTypeObject> = Required< + { + [key in keyof TToolSchemaObject['properties']]?: FromToolSchema< + TToolSchemaObject['properties'][key] + >; + }, + TToolSchemaObject['required'] extends string[] | readonly string[] + ? ValuesType<TToolSchemaObject['required']> + : never +>; + +type FromToolSchemaArray<TToolSchemaObject extends ToolSchemaTypeArray> = Array< + FromToolSchema<TToolSchemaObject['items']> +>; + +type FromToolSchemaString<TToolSchemaString extends ToolSchemaTypeString> = + TToolSchemaString extends { const: string } + ? TToolSchemaString['const'] + : TToolSchemaString extends { enum: string[] } | { enum: readonly string[] } + ? ValuesType<TToolSchemaString['enum']> + : string; + +type FromToolSchemaAnyOf<TToolSchemaAnyOf extends ToolSchemaAnyOf> = FromToolSchema< + ValuesType<TToolSchemaAnyOf['anyOf']> +>; + +type FromToolSchemaAllOf<TToolSchemaAllOf extends ToolSchemaAllOf> = UnionToIntersection< + FromToolSchema<ValuesType<TToolSchemaAllOf['allOf']>> +>; + +export type ToolSchema = ToolSchemaTypeObject; + +export type FromToolSchema<TToolSchema extends ToolSchemaFragment> = + TToolSchema extends ToolSchemaTypeObject + ? FromToolSchemaObject<TToolSchema> + : TToolSchema extends ToolSchemaTypeArray + ? FromToolSchemaArray<TToolSchema> + : TToolSchema extends ToolSchemaTypeBoolean + ? boolean + : TToolSchema extends ToolSchemaTypeNumber + ? number + : TToolSchema extends ToolSchemaTypeString + ? FromToolSchemaString<TToolSchema> + : TToolSchema extends ToolSchemaAnyOf + ? FromToolSchemaAnyOf<TToolSchema> + : TToolSchema extends ToolSchemaAllOf + ? FromToolSchemaAllOf<TToolSchema> + : never; diff --git a/x-pack/plugins/inference/common/chat_complete/tools.ts b/x-pack/plugins/inference/common/chat_complete/tools.ts new file mode 100644 index 0000000000000..85fb4cd9d7020 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/tools.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 type { ValuesType } from 'utility-types'; +import { FromToolSchema, ToolSchema } from './tool_schema'; + +type Assert<TValue, TType> = TValue extends TType ? TValue & TType : never; + +interface CustomToolChoice<TName extends string = string> { + function: TName; +} + +type ToolsOfChoice<TToolOptions extends ToolOptions> = TToolOptions['toolChoice'] extends { + function: infer TToolName; +} + ? TToolName extends keyof TToolOptions['tools'] + ? Pick<TToolOptions['tools'], TToolName> + : TToolOptions['tools'] + : TToolOptions['tools']; + +type ToolResponsesOf<TTools extends Record<string, ToolDefinition> | undefined> = + TTools extends Record<string, ToolDefinition> + ? Array< + ValuesType<{ + [TName in keyof TTools]: ToolResponseOf<Assert<TName, string>, TTools[TName]>; + }> + > + : never[]; + +type ToolResponseOf<TName extends string, TToolDefinition extends ToolDefinition> = ToolCall< + TName, + TToolDefinition extends { schema: ToolSchema } ? FromToolSchema<TToolDefinition['schema']> : {} +>; + +export type ToolChoice<TName extends string = string> = ToolChoiceType | CustomToolChoice<TName>; + +export interface ToolDefinition { + description: string; + schema?: ToolSchema; +} + +export type ToolCallsOf<TToolOptions extends ToolOptions> = TToolOptions extends { + tools?: Record<string, ToolDefinition>; +} + ? TToolOptions extends { toolChoice: ToolChoiceType.none } + ? { toolCalls: [] } + : { + toolCalls: ToolResponsesOf< + Assert<ToolsOfChoice<TToolOptions>, Record<string, ToolDefinition> | undefined> + >; + } + : { toolCalls: never[] }; + +export enum ToolChoiceType { + none = 'none', + auto = 'auto', + required = 'required', +} + +export interface UnvalidatedToolCall { + toolCallId: string; + function: { + name: string; + arguments: string; + }; +} + +export interface ToolCall< + TName extends string = string, + TArguments extends Record<string, any> | undefined = undefined +> { + toolCallId: string; + function: { + name: TName; + } & (TArguments extends Record<string, any> ? { arguments: TArguments } : {}); +} + +export interface ToolOptions<TToolNames extends string = string> { + toolChoice?: ToolChoice<TToolNames>; + tools?: Record<TToolNames, ToolDefinition>; +} diff --git a/x-pack/plugins/inference/common/chat_complete/without_chunk_events.ts b/x-pack/plugins/inference/common/chat_complete/without_chunk_events.ts new file mode 100644 index 0000000000000..58e72e2c90903 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/without_chunk_events.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 { filter, OperatorFunction } from 'rxjs'; +import { ChatCompletionChunkEvent, ChatCompletionEvent, ChatCompletionEventType } from '.'; + +export function withoutChunkEvents<T extends ChatCompletionEvent>(): OperatorFunction< + T, + Exclude<T, ChatCompletionChunkEvent> +> { + return filter( + (event): event is Exclude<T, ChatCompletionChunkEvent> => + event.type !== ChatCompletionEventType.ChatCompletionChunk + ); +} diff --git a/x-pack/plugins/inference/common/chat_complete/without_token_count_events.ts b/x-pack/plugins/inference/common/chat_complete/without_token_count_events.ts new file mode 100644 index 0000000000000..1b7dbdb9c1372 --- /dev/null +++ b/x-pack/plugins/inference/common/chat_complete/without_token_count_events.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 { filter, OperatorFunction } from 'rxjs'; +import { ChatCompletionEvent, ChatCompletionEventType, ChatCompletionTokenCountEvent } from '.'; + +export function withoutTokenCountEvents<T extends ChatCompletionEvent>(): OperatorFunction< + T, + Exclude<T, ChatCompletionTokenCountEvent> +> { + return filter( + (event): event is Exclude<T, ChatCompletionTokenCountEvent> => + event.type !== ChatCompletionEventType.ChatCompletionTokenCount + ); +} diff --git a/x-pack/plugins/inference/common/connectors.ts b/x-pack/plugins/inference/common/connectors.ts new file mode 100644 index 0000000000000..82baea2f83c39 --- /dev/null +++ b/x-pack/plugins/inference/common/connectors.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 enum InferenceConnectorType { + OpenAI = '.gen-ai', + Bedrock = '.bedrock', + Gemini = '.gemini', +} + +export interface InferenceConnector { + type: InferenceConnectorType; + name: string; + connectorId: string; +} + +export function isSupportedConnectorType(id: string): id is InferenceConnectorType { + return ( + id === InferenceConnectorType.OpenAI || + id === InferenceConnectorType.Bedrock || + id === InferenceConnectorType.Gemini + ); +} diff --git a/x-pack/plugins/inference/common/errors.ts b/x-pack/plugins/inference/common/errors.ts new file mode 100644 index 0000000000000..fa063e1669936 --- /dev/null +++ b/x-pack/plugins/inference/common/errors.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import { InferenceTaskEventBase, InferenceTaskEventType } from './tasks'; + +export enum InferenceTaskErrorCode { + internalError = 'internalError', + requestError = 'requestError', +} + +export class InferenceTaskError< + TCode extends string, + TMeta extends Record<string, any> | undefined +> extends Error { + constructor(public code: TCode, message: string, public meta: TMeta) { + super(message); + } + + toJSON(): InferenceTaskErrorEvent { + return { + type: InferenceTaskEventType.error, + error: { + code: this.code, + message: this.message, + meta: this.meta, + }, + }; + } +} + +export type InferenceTaskErrorEvent = InferenceTaskEventBase<InferenceTaskEventType.error> & { + error: { + code: string; + message: string; + meta?: Record<string, any>; + }; +}; + +export type InferenceTaskInternalError = InferenceTaskError< + InferenceTaskErrorCode.internalError, + {} +>; + +export type InferenceTaskRequestError = InferenceTaskError< + InferenceTaskErrorCode.requestError, + { status: number } +>; + +export function createInferenceInternalError( + message: string = i18n.translate('xpack.inference.internalError', { + defaultMessage: 'An internal error occurred', + }) +): InferenceTaskInternalError { + return new InferenceTaskError(InferenceTaskErrorCode.internalError, message, {}); +} + +export function createInferenceRequestError( + message: string, + status: number +): InferenceTaskRequestError { + return new InferenceTaskError(InferenceTaskErrorCode.requestError, message, { + status, + }); +} + +export function isInferenceError( + error: unknown +): error is InferenceTaskError<string, Record<string, any> | undefined> { + return error instanceof InferenceTaskError; +} + +export function isInferenceInternalError(error: unknown): error is InferenceTaskInternalError { + return isInferenceError(error) && error.code === InferenceTaskErrorCode.internalError; +} + +export function isInferenceRequestError(error: unknown): error is InferenceTaskRequestError { + return isInferenceError(error) && error.code === InferenceTaskErrorCode.requestError; +} diff --git a/x-pack/plugins/inference/common/output/create_output_api.ts b/x-pack/plugins/inference/common/output/create_output_api.ts new file mode 100644 index 0000000000000..9842f9635dea8 --- /dev/null +++ b/x-pack/plugins/inference/common/output/create_output_api.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 { map } from 'rxjs'; +import { ChatCompleteAPI, ChatCompletionEventType, MessageRole } from '../chat_complete'; +import { withoutTokenCountEvents } from '../chat_complete/without_token_count_events'; +import { OutputAPI, OutputEvent, OutputEventType } from '.'; + +export function createOutputApi(chatCompleteApi: ChatCompleteAPI): OutputAPI { + return (id, { connectorId, input, schema, system }) => { + return chatCompleteApi({ + connectorId, + system, + messages: [ + { + role: MessageRole.User, + content: input, + }, + ], + ...(schema + ? { + tools: { output: { description: `Output your response in the this format`, schema } }, + toolChoice: { function: 'output' }, + } + : {}), + }).pipe( + withoutTokenCountEvents(), + map((event): OutputEvent<any, any> => { + if (event.type === ChatCompletionEventType.ChatCompletionChunk) { + return { + type: OutputEventType.OutputUpdate, + id, + content: event.content, + }; + } + return { + id, + type: OutputEventType.OutputComplete, + output: event.toolCalls[0].function.arguments, + }; + }) + ); + }; +} diff --git a/x-pack/plugins/inference/common/output/index.ts b/x-pack/plugins/inference/common/output/index.ts new file mode 100644 index 0000000000000..69df7fb3ecd9d --- /dev/null +++ b/x-pack/plugins/inference/common/output/index.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 { Observable } from 'rxjs'; +import { FromToolSchema, ToolSchema } from '../chat_complete/tool_schema'; +import { InferenceTaskEventBase } from '../tasks'; + +export enum OutputEventType { + OutputUpdate = 'output', + OutputComplete = 'complete', +} + +type Output = Record<string, any> | undefined; + +export type OutputUpdateEvent<TId extends string = string> = + InferenceTaskEventBase<OutputEventType.OutputUpdate> & { + id: TId; + content: string; + }; + +export type OutputCompleteEvent< + TId extends string = string, + TOutput extends Output = Output +> = InferenceTaskEventBase<OutputEventType.OutputComplete> & { + id: TId; + output: TOutput; +}; + +export type OutputEvent<TId extends string = string, TOutput extends Output = Output> = + | OutputUpdateEvent<TId> + | OutputCompleteEvent<TId, TOutput>; + +/** + * Generate a response with the LLM for a prompt, optionally based on a schema. + * + * @param {string} id The id of the operation + * @param {string} options.connectorId The ID of the connector that is to be used. + * @param {string} options.input The prompt for the LLM + * @param {ToolSchema} [options.schema] The schema the response from the LLM should adhere to. + */ +export type OutputAPI = < + TId extends string = string, + TOutputSchema extends ToolSchema | undefined = ToolSchema | undefined +>( + id: TId, + options: { + connectorId: string; + system?: string; + input: string; + schema?: TOutputSchema; + } +) => Observable< + OutputEvent<TId, TOutputSchema extends ToolSchema ? FromToolSchema<TOutputSchema> : undefined> +>; + +export function createOutputCompleteEvent<TId extends string, TOutput extends Output>( + id: TId, + output: TOutput +): OutputCompleteEvent<TId, TOutput> { + return { + id, + type: OutputEventType.OutputComplete, + output, + }; +} diff --git a/x-pack/plugins/inference/common/output/without_output_update_events.ts b/x-pack/plugins/inference/common/output/without_output_update_events.ts new file mode 100644 index 0000000000000..38f26c8c8ece1 --- /dev/null +++ b/x-pack/plugins/inference/common/output/without_output_update_events.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 { filter, OperatorFunction } from 'rxjs'; +import { OutputEvent, OutputEventType, OutputUpdateEvent } from '.'; + +export function withoutOutputUpdateEvents<T extends OutputEvent>(): OperatorFunction< + T, + Exclude<T, OutputUpdateEvent> +> { + return filter( + (event): event is Exclude<T, OutputUpdateEvent> => event.type !== OutputEventType.OutputUpdate + ); +} diff --git a/x-pack/plugins/inference/common/tasks.ts b/x-pack/plugins/inference/common/tasks.ts new file mode 100644 index 0000000000000..7b8f65b7af2c9 --- /dev/null +++ b/x-pack/plugins/inference/common/tasks.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 interface InferenceTaskEventBase<TEventType extends string> { + type: TEventType; +} + +export enum InferenceTaskEventType { + error = 'error', +} + +export type InferenceTaskEvent = InferenceTaskEventBase<string>; diff --git a/x-pack/plugins/inference/jest.config.js b/x-pack/plugins/inference/jest.config.js new file mode 100644 index 0000000000000..3bc2142bcdfc3 --- /dev/null +++ b/x-pack/plugins/inference/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['<rootDir>/x-pack/plugins/inference/public', '<rootDir>/x-pack/plugins/inference/server'], + setupFiles: [], + collectCoverage: true, + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/inference/{public,server,common}/**/*.{js,ts,tsx}', + ], + + coverageReporters: ['html'], +}; diff --git a/x-pack/plugins/inference/kibana.jsonc b/x-pack/plugins/inference/kibana.jsonc new file mode 100644 index 0000000000000..c52b194be7dc7 --- /dev/null +++ b/x-pack/plugins/inference/kibana.jsonc @@ -0,0 +1,18 @@ +{ + "type": "plugin", + "id": "@kbn/inference-plugin", + "owner": "@elastic/kibana-core", + "plugin": { + "id": "inference", + "server": true, + "browser": true, + "configPath": ["xpack", "inference"], + "requiredPlugins": [ + "actions" + ], + "requiredBundles": [ + ], + "optionalPlugins": [], + "extraPublicDirs": [] + } +} diff --git a/x-pack/plugins/inference/public/chat_complete/index.ts b/x-pack/plugins/inference/public/chat_complete/index.ts new file mode 100644 index 0000000000000..2509ea2dc1222 --- /dev/null +++ b/x-pack/plugins/inference/public/chat_complete/index.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 type { HttpStart } from '@kbn/core/public'; +import { from } from 'rxjs'; +import { ChatCompleteAPI } from '../../common/chat_complete'; +import type { ChatCompleteRequestBody } from '../../common/chat_complete/request'; +import { httpResponseIntoObservable } from '../util/http_response_into_observable'; + +export function createChatCompleteApi({ http }: { http: HttpStart }): ChatCompleteAPI { + return ({ connectorId, messages, system, toolChoice, tools }) => { + const body: ChatCompleteRequestBody = { + connectorId, + system, + messages, + toolChoice, + tools, + }; + + return from( + http.post('/internal/inference/chat_complete', { + asResponse: true, + rawResponse: true, + body: JSON.stringify(body), + }) + ).pipe(httpResponseIntoObservable()); + }; +} diff --git a/x-pack/plugins/inference/public/index.ts b/x-pack/plugins/inference/public/index.ts new file mode 100644 index 0000000000000..82d36a7abe82d --- /dev/null +++ b/x-pack/plugins/inference/public/index.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 { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; + +import { InferencePlugin } from './plugin'; +import type { + InferencePublicSetup, + InferencePublicStart, + InferenceSetupDependencies, + InferenceStartDependencies, + ConfigSchema, +} from './types'; + +export { httpResponseIntoObservable } from './util/http_response_into_observable'; + +export type { InferencePublicSetup, InferencePublicStart }; + +export const plugin: PluginInitializer< + InferencePublicSetup, + InferencePublicStart, + InferenceSetupDependencies, + InferenceStartDependencies +> = (pluginInitializerContext: PluginInitializerContext<ConfigSchema>) => + new InferencePlugin(pluginInitializerContext); diff --git a/x-pack/plugins/inference/public/plugin.tsx b/x-pack/plugins/inference/public/plugin.tsx new file mode 100644 index 0000000000000..9785efb7a8874 --- /dev/null +++ b/x-pack/plugins/inference/public/plugin.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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import type { Logger } from '@kbn/logging'; +import { createOutputApi } from '../common/output/create_output_api'; +import { createChatCompleteApi } from './chat_complete'; +import type { + ConfigSchema, + InferencePublicSetup, + InferencePublicStart, + InferenceSetupDependencies, + InferenceStartDependencies, +} from './types'; + +export class InferencePlugin + implements + Plugin< + InferencePublicSetup, + InferencePublicStart, + InferenceSetupDependencies, + InferenceStartDependencies + > +{ + logger: Logger; + + constructor(context: PluginInitializerContext<ConfigSchema>) { + this.logger = context.logger.get(); + } + setup( + coreSetup: CoreSetup<InferenceStartDependencies, InferencePublicStart>, + pluginsSetup: InferenceSetupDependencies + ): InferencePublicSetup { + return {}; + } + + start(coreStart: CoreStart, pluginsStart: InferenceStartDependencies): InferencePublicStart { + const chatComplete = createChatCompleteApi({ http: coreStart.http }); + return { + chatComplete, + output: createOutputApi(chatComplete), + getConnectors: () => { + return coreStart.http.get('/internal/inference/connectors'); + }, + }; + } +} diff --git a/x-pack/plugins/inference/public/types.ts b/x-pack/plugins/inference/public/types.ts new file mode 100644 index 0000000000000..df80256679ab4 --- /dev/null +++ b/x-pack/plugins/inference/public/types.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 { ChatCompleteAPI } from '../common/chat_complete'; +import type { InferenceConnector } from '../common/connectors'; +import type { OutputAPI } from '../common/output'; + +/* eslint-disable @typescript-eslint/no-empty-interface*/ + +export interface ConfigSchema {} + +export interface InferenceSetupDependencies {} + +export interface InferenceStartDependencies {} + +export interface InferencePublicSetup {} + +export interface InferencePublicStart { + chatComplete: ChatCompleteAPI; + output: OutputAPI; + getConnectors: () => Promise<InferenceConnector[]>; +} diff --git a/x-pack/plugins/inference/public/util/create_observable_from_http_response.ts b/x-pack/plugins/inference/public/util/create_observable_from_http_response.ts new file mode 100644 index 0000000000000..09e9b9b2d5f5e --- /dev/null +++ b/x-pack/plugins/inference/public/util/create_observable_from_http_response.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 { createParser } from 'eventsource-parser'; +import { Observable, throwError } from 'rxjs'; +import { createInferenceInternalError } from '../../common/errors'; + +export interface StreamedHttpResponse { + response?: { body: ReadableStream<Uint8Array> | null | undefined }; +} + +export function createObservableFromHttpResponse( + response: StreamedHttpResponse +): Observable<string> { + const rawResponse = response.response; + + const body = rawResponse?.body; + if (!body) { + return throwError(() => { + throw createInferenceInternalError(`No readable stream found in response`); + }); + } + + return new Observable<string>((subscriber) => { + const parser = createParser((event) => { + if (event.type === 'event') { + subscriber.next(event.data); + } + }); + + const readStream = async () => { + const reader = body.getReader(); + const decoder = new TextDecoder(); + + // Function to process each chunk + const processChunk = ({ + done, + value, + }: ReadableStreamReadResult<Uint8Array>): Promise<void> => { + if (done) { + return Promise.resolve(); + } + + parser.feed(decoder.decode(value, { stream: true })); + + return reader.read().then(processChunk); + }; + + // Start reading the stream + return reader.read().then(processChunk); + }; + + readStream() + .then(() => { + subscriber.complete(); + }) + .catch((error) => { + subscriber.error(error); + }); + }); +} diff --git a/x-pack/plugins/inference/public/util/http_response_into_observable.test.ts b/x-pack/plugins/inference/public/util/http_response_into_observable.test.ts new file mode 100644 index 0000000000000..f50ec402bdd77 --- /dev/null +++ b/x-pack/plugins/inference/public/util/http_response_into_observable.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { lastValueFrom, of, toArray } from 'rxjs'; +import { httpResponseIntoObservable } from './http_response_into_observable'; +import type { StreamedHttpResponse } from './create_observable_from_http_response'; +import { ChatCompletionEventType } from '../../common/chat_complete'; +import { InferenceTaskEventType } from '../../common/tasks'; +import { InferenceTaskErrorCode } from '../../common/errors'; + +function toSse(...events: Array<Record<string, any>>) { + return events.map((event) => new TextEncoder().encode(`data: ${JSON.stringify(event)}\n\n`)); +} + +describe('httpResponseIntoObservable', () => { + it('parses SSE output', async () => { + const events = [ + { + type: ChatCompletionEventType.ChatCompletionChunk, + content: 'Hello', + }, + { + type: ChatCompletionEventType.ChatCompletionChunk, + content: 'Hello again', + }, + ]; + + const messages = await lastValueFrom( + of<StreamedHttpResponse>({ + response: { + // @ts-expect-error + body: ReadableStream.from(toSse(...events)), + }, + }).pipe(httpResponseIntoObservable(), toArray()) + ); + + expect(messages).toEqual(events); + }); + + it('throws serialized errors', async () => { + const events = [ + { + type: InferenceTaskEventType.error, + error: { + code: InferenceTaskErrorCode.internalError, + message: 'Internal error', + }, + }, + ]; + + await expect(async () => { + await lastValueFrom( + of<StreamedHttpResponse>({ + response: { + // @ts-expect-error + body: ReadableStream.from(toSse(...events)), + }, + }).pipe(httpResponseIntoObservable(), toArray()) + ); + }).rejects.toThrowError(`Internal error`); + }); +}); diff --git a/x-pack/plugins/inference/public/util/http_response_into_observable.ts b/x-pack/plugins/inference/public/util/http_response_into_observable.ts new file mode 100644 index 0000000000000..5b0929762e25d --- /dev/null +++ b/x-pack/plugins/inference/public/util/http_response_into_observable.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 { map, OperatorFunction, pipe, switchMap, tap } from 'rxjs'; +import { InferenceTaskEvent, InferenceTaskEventType } from '../../common/tasks'; +import { + createObservableFromHttpResponse, + StreamedHttpResponse, +} from './create_observable_from_http_response'; +import { + createInferenceInternalError, + InferenceTaskError, + InferenceTaskErrorEvent, +} from '../../common/errors'; + +export function httpResponseIntoObservable< + T extends InferenceTaskEvent = never +>(): OperatorFunction<StreamedHttpResponse, T> { + return pipe( + switchMap((response) => createObservableFromHttpResponse(response)), + map((line): T => { + try { + return JSON.parse(line); + } catch (error) { + throw createInferenceInternalError(`Failed to parse JSON`); + } + }), + tap((event) => { + if (event.type === InferenceTaskEventType.error) { + const errorEvent = event as unknown as InferenceTaskErrorEvent; + throw new InferenceTaskError( + errorEvent.error.code, + errorEvent.error.message, + errorEvent.error.meta + ); + } + }) + ); +} diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.test.ts b/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.test.ts new file mode 100644 index 0000000000000..a3fdcd33bee94 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.test.ts @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { lastValueFrom, of } from 'rxjs'; +import { + ChatCompletionChunkEvent, + ChatCompletionEventType, + ChatCompletionTokenCountEvent, +} from '../../../common/chat_complete'; +import { ToolChoiceType } from '../../../common/chat_complete/tools'; +import { chunksIntoMessage } from './chunks_into_message'; + +describe('chunksIntoMessage', () => { + function fromEvents(...events: Array<ChatCompletionChunkEvent | ChatCompletionTokenCountEvent>) { + return of(...events); + } + + it('concatenates content chunks into a single message', async () => { + const message = await lastValueFrom( + chunksIntoMessage({})( + fromEvents( + { + content: 'Hey', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [], + }, + { + content: ' how is it', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [], + }, + { + content: ' going', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [], + } + ) + ) + ); + + expect(message).toEqual({ + content: 'Hey how is it going', + toolCalls: [], + type: ChatCompletionEventType.ChatCompletionMessage, + }); + }); + + it('parses tool calls', async () => { + const message = await lastValueFrom( + chunksIntoMessage({ + toolChoice: ToolChoiceType.auto, + tools: { + myFunction: { + description: 'myFunction', + schema: { + type: 'object', + properties: { + foo: { + type: 'string', + const: 'bar', + }, + }, + }, + }, + }, + })( + fromEvents( + { + content: '', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [ + { + function: { + name: 'myFunction', + arguments: '', + }, + index: 0, + toolCallId: '0', + }, + ], + }, + { + content: '', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [ + { + function: { + name: '', + arguments: '{', + }, + index: 0, + toolCallId: '0', + }, + ], + }, + { + content: '', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [ + { + function: { + name: '', + arguments: '"foo": "bar" }', + }, + index: 0, + toolCallId: '1', + }, + ], + } + ) + ) + ); + + expect(message).toEqual({ + content: '', + toolCalls: [ + { + function: { + name: 'myFunction', + arguments: { + foo: 'bar', + }, + }, + toolCallId: '001', + }, + ], + type: ChatCompletionEventType.ChatCompletionMessage, + }); + }); + + it('validates tool calls', async () => { + async function getMessage() { + return await lastValueFrom( + chunksIntoMessage({ + toolChoice: ToolChoiceType.auto, + tools: { + myFunction: { + description: 'myFunction', + schema: { + type: 'object', + properties: { + foo: { + type: 'string', + const: 'bar', + }, + }, + }, + }, + }, + })( + fromEvents({ + content: '', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [ + { + function: { + name: 'myFunction', + arguments: '{ "foo": "baz" }', + }, + index: 0, + toolCallId: '001', + }, + ], + }) + ) + ); + } + + await expect(async () => getMessage()).rejects.toThrowErrorMatchingInlineSnapshot( + `"Tool call arguments for myFunction were invalid"` + ); + }); + + it('concatenates multiple tool calls into a single message', async () => { + const message = await lastValueFrom( + chunksIntoMessage({ + toolChoice: ToolChoiceType.auto, + tools: { + myFunction: { + description: 'myFunction', + schema: { + type: 'object', + properties: { + foo: { + type: 'string', + }, + }, + }, + }, + }, + })( + fromEvents( + { + content: '', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [ + { + function: { + name: 'myFunction', + arguments: '', + }, + index: 0, + toolCallId: '001', + }, + ], + }, + { + content: '', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [ + { + function: { + name: '', + arguments: '{"foo": "bar"}', + }, + index: 0, + toolCallId: '', + }, + ], + }, + { + content: '', + type: ChatCompletionEventType.ChatCompletionChunk, + tool_calls: [ + { + function: { + name: 'myFunction', + arguments: '{ "foo": "baz" }', + }, + index: 1, + toolCallId: '002', + }, + ], + } + ) + ) + ); + + expect(message).toEqual({ + content: '', + toolCalls: [ + { + function: { + name: 'myFunction', + arguments: { + foo: 'bar', + }, + }, + toolCallId: '001', + }, + { + function: { + name: 'myFunction', + arguments: { + foo: 'baz', + }, + }, + toolCallId: '002', + }, + ], + type: ChatCompletionEventType.ChatCompletionMessage, + }); + }); +}); diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.ts b/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.ts new file mode 100644 index 0000000000000..786a4c4ff7fb3 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/chunks_into_message.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 { last, map, merge, OperatorFunction, scan, share } from 'rxjs'; +import type { UnvalidatedToolCall, ToolOptions } from '../../../common/chat_complete/tools'; +import { + ChatCompletionChunkEvent, + ChatCompletionEventType, + ChatCompletionMessageEvent, + ChatCompletionTokenCountEvent, +} from '../../../common/chat_complete'; +import { withoutTokenCountEvents } from '../../../common/chat_complete/without_token_count_events'; +import { validateToolCalls } from '../../util/validate_tool_calls'; + +export function chunksIntoMessage<TToolOptions extends ToolOptions>( + toolOptions: TToolOptions +): OperatorFunction< + ChatCompletionChunkEvent | ChatCompletionTokenCountEvent, + | ChatCompletionChunkEvent + | ChatCompletionTokenCountEvent + | ChatCompletionMessageEvent<TToolOptions> +> { + return (chunks$) => { + const shared$ = chunks$.pipe(share()); + + return merge( + shared$, + shared$.pipe( + withoutTokenCountEvents(), + scan( + (prev, chunk) => { + prev.content += chunk.content ?? ''; + + chunk.tool_calls?.forEach((toolCall) => { + let prevToolCall = prev.tool_calls[toolCall.index]; + if (!prevToolCall) { + prev.tool_calls[toolCall.index] = { + function: { + name: '', + arguments: '', + }, + toolCallId: '', + }; + + prevToolCall = prev.tool_calls[toolCall.index]; + } + + prevToolCall.function.name += toolCall.function.name; + prevToolCall.function.arguments += toolCall.function.arguments; + prevToolCall.toolCallId += toolCall.toolCallId; + }); + + return prev; + }, + { + content: '', + tool_calls: [] as UnvalidatedToolCall[], + } + ), + last(), + map((concatenatedChunk): ChatCompletionMessageEvent<TToolOptions> => { + const validatedToolCalls = validateToolCalls<TToolOptions>({ + ...toolOptions, + toolCalls: concatenatedChunk.tool_calls, + }); + + return { + type: ChatCompletionEventType.ChatCompletionMessage, + content: concatenatedChunk.content, + toolCalls: validatedToolCalls, + }; + }) + ) + ); + }; +} diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/event_source_stream_into_observable.ts b/x-pack/plugins/inference/server/chat_complete/adapters/event_source_stream_into_observable.ts new file mode 100644 index 0000000000000..ece32d76222cc --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/event_source_stream_into_observable.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 { createParser } from 'eventsource-parser'; +import { Readable } from 'node:stream'; +import { Observable } from 'rxjs'; + +export function eventSourceStreamIntoObservable(readable: Readable) { + return new Observable<string>((subscriber) => { + const parser = createParser((event) => { + if (event.type === 'event') { + subscriber.next(event.data); + } + }); + + async function processStream() { + for await (const chunk of readable) { + parser.feed(chunk.toString()); + } + } + + processStream().then( + () => { + subscriber.complete(); + }, + (error) => { + subscriber.error(error); + } + ); + }); +} diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.test.ts b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.test.ts new file mode 100644 index 0000000000000..7f55f8a8faa48 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.test.ts @@ -0,0 +1,382 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import OpenAI from 'openai'; +import { openAIAdapter } from '.'; +import type { ActionsClient } from '@kbn/actions-plugin/server/actions_client'; +import { ChatCompletionEventType, MessageRole } from '../../../../common/chat_complete'; +import { PassThrough } from 'stream'; +import { pick } from 'lodash'; +import { lastValueFrom, Subject, toArray } from 'rxjs'; +import { observableIntoEventSourceStream } from '../../../util/observable_into_event_source_stream'; +import { v4 } from 'uuid'; + +function createOpenAIChunk({ + delta, + usage, +}: { + delta: OpenAI.ChatCompletionChunk['choices'][number]['delta']; + usage?: OpenAI.ChatCompletionChunk['usage']; +}): OpenAI.ChatCompletionChunk { + return { + choices: [ + { + finish_reason: null, + index: 0, + delta, + }, + ], + created: new Date().getTime(), + id: v4(), + model: 'gpt-4o', + object: 'chat.completion.chunk', + usage, + }; +} + +describe('openAIAdapter', () => { + const actionsClientMock = { + execute: jest.fn(), + } as ActionsClient & { execute: jest.MockedFn<ActionsClient['execute']> }; + + beforeEach(() => { + actionsClientMock.execute.mockReset(); + }); + + const defaultArgs = { + connector: { + id: 'foo', + actionTypeId: '.gen-ai', + name: 'OpenAI', + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false, + }, + actionsClient: actionsClientMock, + }; + + describe('when creating the request', () => { + function getRequest() { + const params = actionsClientMock.execute.mock.calls[0][0].params.subActionParams as Record< + string, + any + >; + + return { stream: params.stream, body: JSON.parse(params.body) }; + } + + beforeEach(() => { + actionsClientMock.execute.mockImplementation(async () => { + return { + actionId: '', + status: 'ok', + data: new PassThrough(), + }; + }); + }); + it('correctly formats messages ', () => { + openAIAdapter.chatComplete({ + ...defaultArgs, + system: 'system', + messages: [ + { + role: MessageRole.User, + content: 'question', + }, + { + role: MessageRole.Assistant, + content: 'answer', + }, + { + role: MessageRole.User, + content: 'another question', + }, + ], + }); + + expect(getRequest().body.messages).toEqual([ + { + content: 'system', + role: 'system', + }, + { + content: 'question', + role: 'user', + }, + { + content: 'answer', + role: 'assistant', + }, + { + content: 'another question', + role: 'user', + }, + ]); + }); + + it('correctly formats tools and tool choice', () => { + openAIAdapter.chatComplete({ + ...defaultArgs, + system: 'system', + messages: [ + { + role: MessageRole.User, + content: 'question', + }, + { + role: MessageRole.Assistant, + content: 'answer', + toolCalls: [ + { + function: { + name: 'my_function', + arguments: { + foo: 'bar', + }, + }, + toolCallId: '0', + }, + ], + }, + { + role: MessageRole.Tool, + toolCallId: '0', + response: { + bar: 'foo', + }, + }, + ], + toolChoice: { function: 'myFunction' }, + tools: { + myFunction: { + description: 'myFunction', + }, + myFunctionWithArgs: { + description: 'myFunctionWithArgs', + schema: { + type: 'object', + properties: { + foo: { + type: 'string', + description: 'foo', + }, + }, + required: ['foo'], + }, + }, + }, + }); + + expect(pick(getRequest().body, 'messages', 'tools', 'tool_choice')).toEqual({ + messages: [ + { + content: 'system', + role: 'system', + }, + { + content: 'question', + role: 'user', + }, + { + content: 'answer', + role: 'assistant', + tool_calls: [ + { + function: { + name: 'my_function', + arguments: JSON.stringify({ foo: 'bar' }), + }, + id: '0', + type: 'function', + }, + ], + }, + { + role: 'tool', + tool_call_id: '0', + content: JSON.stringify({ bar: 'foo' }), + }, + ], + tools: [ + { + function: { + name: 'myFunction', + description: 'myFunction', + parameters: { + type: 'object', + properties: {}, + }, + }, + type: 'function', + }, + { + function: { + name: 'myFunctionWithArgs', + description: 'myFunctionWithArgs', + parameters: { + type: 'object', + properties: { + foo: { + type: 'string', + description: 'foo', + }, + }, + required: ['foo'], + }, + }, + type: 'function', + }, + ], + tool_choice: { + function: { + name: 'myFunction', + }, + type: 'function', + }, + }); + }); + + it('always sets streaming to true', () => { + openAIAdapter.chatComplete({ + ...defaultArgs, + messages: [ + { + role: MessageRole.User, + content: 'question', + }, + ], + }); + + expect(getRequest().stream).toBe(true); + expect(getRequest().body.stream).toBe(true); + }); + }); + + describe('when handling the response', () => { + let source$: Subject<Record<string, any>>; + + beforeEach(() => { + source$ = new Subject<Record<string, any>>(); + + actionsClientMock.execute.mockImplementation(async () => { + return { + actionId: '', + status: 'ok', + data: observableIntoEventSourceStream(source$), + }; + }); + }); + + it('emits chunk events', async () => { + const response$ = openAIAdapter.chatComplete({ + ...defaultArgs, + messages: [ + { + role: MessageRole.User, + content: 'Hello', + }, + ], + }); + + source$.next( + createOpenAIChunk({ + delta: { + content: 'First', + }, + }) + ); + + source$.next( + createOpenAIChunk({ + delta: { + content: ', second', + }, + }) + ); + + source$.complete(); + + const allChunks = await lastValueFrom(response$.pipe(toArray())); + + expect(allChunks).toEqual([ + { + content: 'First', + tool_calls: [], + type: ChatCompletionEventType.ChatCompletionChunk, + }, + { + content: ', second', + tool_calls: [], + type: ChatCompletionEventType.ChatCompletionChunk, + }, + ]); + }); + + it('emits token events', async () => { + const response$ = openAIAdapter.chatComplete({ + ...defaultArgs, + messages: [ + { + role: MessageRole.User, + content: 'Hello', + }, + ], + }); + + source$.next( + createOpenAIChunk({ + delta: { + content: 'First', + }, + }) + ); + + source$.next( + createOpenAIChunk({ + delta: { + tool_calls: [ + { + index: 0, + id: '0', + function: { + name: 'my_function', + arguments: '{}', + }, + }, + ], + }, + }) + ); + + source$.complete(); + + const allChunks = await lastValueFrom(response$.pipe(toArray())); + + expect(allChunks).toEqual([ + { + content: 'First', + tool_calls: [], + type: ChatCompletionEventType.ChatCompletionChunk, + }, + { + content: '', + tool_calls: [ + { + function: { + name: 'my_function', + arguments: '{}', + }, + index: 0, + toolCallId: '0', + }, + ], + type: ChatCompletionEventType.ChatCompletionChunk, + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.ts b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.ts new file mode 100644 index 0000000000000..c811ed9f400ea --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/adapters/openai/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import OpenAI from 'openai'; +import type { + ChatCompletionAssistantMessageParam, + ChatCompletionMessageParam, + ChatCompletionSystemMessageParam, + ChatCompletionToolMessageParam, + ChatCompletionUserMessageParam, +} from 'openai/resources'; +import { filter, from, map, switchMap, tap } from 'rxjs'; +import { Readable } from 'stream'; +import { + ChatCompletionChunkEvent, + ChatCompletionEventType, + Message, + MessageRole, +} from '../../../../common/chat_complete'; +import { createTokenLimitReachedError } from '../../../../common/chat_complete/errors'; +import { createInferenceInternalError } from '../../../../common/errors'; +import { InferenceConnectorAdapter } from '../../types'; +import { eventSourceStreamIntoObservable } from '../event_source_stream_into_observable'; + +export const openAIAdapter: InferenceConnectorAdapter = { + chatComplete: ({ connector, actionsClient, system, messages, toolChoice, tools }) => { + const openAIMessages = messagesToOpenAI({ system, messages }); + + const toolChoiceForOpenAI = + typeof toolChoice === 'string' + ? toolChoice + : toolChoice + ? { + function: { + name: toolChoice.function, + }, + type: 'function' as const, + } + : undefined; + + const stream = true; + + const request: Omit<OpenAI.ChatCompletionCreateParams, 'model'> & { model?: string } = { + stream, + messages: openAIMessages, + temperature: 0, + tool_choice: toolChoiceForOpenAI, + tools: tools + ? Object.entries(tools).map(([toolName, { description, schema }]) => { + return { + type: 'function', + function: { + name: toolName, + description, + parameters: (schema ?? { + type: 'object' as const, + properties: {}, + }) as unknown as Record<string, unknown>, + }, + }; + }) + : undefined, + }; + + return from( + actionsClient.execute({ + actionId: connector.id, + params: { + subAction: 'stream', + subActionParams: { + body: JSON.stringify(request), + stream, + }, + }, + }) + ).pipe( + switchMap((response) => { + const readable = response.data as Readable; + return eventSourceStreamIntoObservable(readable); + }), + filter((line) => !!line && line !== '[DONE]'), + map( + (line) => JSON.parse(line) as OpenAI.ChatCompletionChunk | { error: { message: string } } + ), + tap((line) => { + if ('error' in line) { + throw createInferenceInternalError(line.error.message); + } + if ( + 'choices' in line && + line.choices.length && + line.choices[0].finish_reason === 'length' + ) { + throw createTokenLimitReachedError(); + } + }), + filter( + (line): line is OpenAI.ChatCompletionChunk => + 'object' in line && line.object === 'chat.completion.chunk' + ), + map((chunk): ChatCompletionChunkEvent => { + const delta = chunk.choices[0].delta; + + return { + content: delta.content ?? '', + tool_calls: + delta.tool_calls?.map((toolCall) => { + return { + function: { + name: toolCall.function?.name ?? '', + arguments: toolCall.function?.arguments ?? '', + }, + toolCallId: toolCall.id ?? '', + index: toolCall.index, + }; + }) ?? [], + type: ChatCompletionEventType.ChatCompletionChunk, + }; + }) + ); + }, +}; + +function messagesToOpenAI({ + system, + messages, +}: { + system?: string; + messages: Message[]; +}): OpenAI.ChatCompletionMessageParam[] { + const systemMessage: ChatCompletionSystemMessageParam | undefined = system + ? { role: 'system', content: system } + : undefined; + + return [ + ...(systemMessage ? [systemMessage] : []), + ...messages.map((message): ChatCompletionMessageParam => { + const role = message.role; + + switch (role) { + case MessageRole.Assistant: + const assistantMessage: ChatCompletionAssistantMessageParam = { + role: 'assistant', + content: message.content, + tool_calls: message.toolCalls?.map((toolCall) => { + return { + function: { + name: toolCall.function.name, + arguments: + 'arguments' in toolCall.function + ? JSON.stringify(toolCall.function.arguments) + : '{}', + }, + id: toolCall.toolCallId, + type: 'function', + }; + }), + }; + return assistantMessage; + + case MessageRole.User: + const userMessage: ChatCompletionUserMessageParam = { + role: 'user', + content: message.content, + }; + return userMessage; + + case MessageRole.Tool: + const toolMessage: ChatCompletionToolMessageParam = { + role: 'tool', + content: JSON.stringify(message.response), + tool_call_id: message.toolCallId, + }; + return toolMessage; + } + }), + ]; +} diff --git a/x-pack/plugins/inference/server/chat_complete/index.ts b/x-pack/plugins/inference/server/chat_complete/index.ts new file mode 100644 index 0000000000000..e30afb58ca25a --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/index.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 type { KibanaRequest } from '@kbn/core-http-server'; +import { defer, switchMap, throwError } from 'rxjs'; +import type { ChatCompleteAPI, ChatCompletionResponse } from '../../common/chat_complete'; +import type { ToolOptions } from '../../common/chat_complete/tools'; +import { InferenceConnectorType } from '../../common/connectors'; +import { createInferenceRequestError } from '../../common/errors'; +import type { InferenceStartDependencies } from '../types'; +import { chunksIntoMessage } from './adapters/chunks_into_message'; +import { openAIAdapter } from './adapters/openai'; + +export function createChatCompleteApi({ + request, + actions, +}: { + request: KibanaRequest; + actions: InferenceStartDependencies['actions']; +}) { + const chatCompleteAPI: ChatCompleteAPI = ({ + connectorId, + messages, + toolChoice, + tools, + system, + }): ChatCompletionResponse<ToolOptions> => { + return defer(async () => { + const actionsClient = await actions.getActionsClientWithRequest(request); + + const connector = await actionsClient.get({ id: connectorId, throwIfSystemAction: true }); + + return { actionsClient, connector }; + }).pipe( + switchMap(({ actionsClient, connector }) => { + switch (connector.actionTypeId) { + case InferenceConnectorType.OpenAI: + return openAIAdapter.chatComplete({ + system, + connector, + actionsClient, + messages, + toolChoice, + tools, + }); + + case InferenceConnectorType.Bedrock: + break; + + case InferenceConnectorType.Gemini: + break; + } + + return throwError(() => + createInferenceRequestError( + `Adapter for type ${connector.actionTypeId} not implemented`, + 400 + ) + ); + }), + chunksIntoMessage({ + toolChoice, + tools, + }) + ); + }; + + return chatCompleteAPI; +} diff --git a/x-pack/plugins/inference/server/chat_complete/types.ts b/x-pack/plugins/inference/server/chat_complete/types.ts new file mode 100644 index 0000000000000..6c89df1498646 --- /dev/null +++ b/x-pack/plugins/inference/server/chat_complete/types.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 { ActionsClient } from '@kbn/actions-plugin/server'; +import type { Observable } from 'rxjs'; +import type { + ChatCompleteAPI, + ChatCompletionChunkEvent, + ChatCompletionTokenCountEvent, +} from '../../common/chat_complete'; + +type Connector = Awaited<ReturnType<ActionsClient['get']>>; + +export interface InferenceConnectorAdapter { + chatComplete: ( + options: Omit<Parameters<ChatCompleteAPI>[0], 'connectorId'> & { + actionsClient: ActionsClient; + connector: Connector; + } + ) => Observable<ChatCompletionChunkEvent | ChatCompletionTokenCountEvent>; +} diff --git a/x-pack/plugins/inference/server/config.ts b/x-pack/plugins/inference/server/config.ts new file mode 100644 index 0000000000000..f4cd1f886581b --- /dev/null +++ b/x-pack/plugins/inference/server/config.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 { schema, type TypeOf } from '@kbn/config-schema'; + +export const config = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type InferenceConfig = TypeOf<typeof config>; diff --git a/x-pack/plugins/inference/server/index.ts b/x-pack/plugins/inference/server/index.ts new file mode 100644 index 0000000000000..721aa05d06023 --- /dev/null +++ b/x-pack/plugins/inference/server/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 { PluginInitializer, PluginInitializerContext } from '@kbn/core/server'; +import type { InferenceConfig } from './config'; +import { InferencePlugin } from './plugin'; +import type { + InferenceServerSetup, + InferenceServerStart, + InferenceSetupDependencies, + InferenceStartDependencies, +} from './types'; + +export { withoutTokenCountEvents } from '../common/chat_complete/without_token_count_events'; +export { withoutChunkEvents } from '../common/chat_complete/without_chunk_events'; +export { withoutOutputUpdateEvents } from '../common/output/without_output_update_events'; + +export type { InferenceServerSetup, InferenceServerStart }; + +export const plugin: PluginInitializer< + InferenceServerSetup, + InferenceServerStart, + InferenceSetupDependencies, + InferenceStartDependencies +> = async (pluginInitializerContext: PluginInitializerContext<InferenceConfig>) => + new InferencePlugin(pluginInitializerContext); diff --git a/x-pack/plugins/inference/server/inference_client/index.ts b/x-pack/plugins/inference/server/inference_client/index.ts new file mode 100644 index 0000000000000..3c25cf29f6280 --- /dev/null +++ b/x-pack/plugins/inference/server/inference_client/index.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 type { KibanaRequest } from '@kbn/core-http-server'; +import { ActionsClient } from '@kbn/actions-plugin/server'; +import { isSupportedConnectorType } from '../../common/connectors'; +import { createInferenceRequestError } from '../../common/errors'; +import { createChatCompleteApi } from '../chat_complete'; +import type { InferenceClient, InferenceStartDependencies } from '../types'; +import { createOutputApi } from '../../common/output/create_output_api'; + +export function createInferenceClient({ + request, + actions, +}: { request: KibanaRequest } & Pick<InferenceStartDependencies, 'actions'>): InferenceClient { + const chatComplete = createChatCompleteApi({ request, actions }); + return { + chatComplete, + output: createOutputApi(chatComplete), + getConnectorById: async (id: string) => { + const actionsClient = await actions.getActionsClientWithRequest(request); + let connector: Awaited<ReturnType<ActionsClient['get']>>; + + try { + connector = await actionsClient.get({ + id, + throwIfSystemAction: true, + }); + } catch (error) { + throw createInferenceRequestError(`No connector found for id ${id}`, 400); + } + + const actionTypeId = connector.id; + + if (!isSupportedConnectorType(actionTypeId)) { + throw createInferenceRequestError( + `Type ${actionTypeId} not recognized as a supported connector type`, + 400 + ); + } + + return { + connectorId: connector.id, + name: connector.name, + type: actionTypeId, + }; + }, + }; +} diff --git a/x-pack/plugins/inference/server/plugin.ts b/x-pack/plugins/inference/server/plugin.ts new file mode 100644 index 0000000000000..26c56209df8ce --- /dev/null +++ b/x-pack/plugins/inference/server/plugin.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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; +import { createInferenceClient } from './inference_client'; +import { registerChatCompleteRoute } from './routes/chat_complete'; +import { registerConnectorsRoute } from './routes/connectors'; +import type { + ConfigSchema, + InferenceServerSetup, + InferenceServerStart, + InferenceSetupDependencies, + InferenceStartDependencies, +} from './types'; + +export class InferencePlugin + implements + Plugin< + InferenceServerSetup, + InferenceServerStart, + InferenceSetupDependencies, + InferenceStartDependencies + > +{ + logger: Logger; + + constructor(context: PluginInitializerContext<ConfigSchema>) { + this.logger = context.logger.get(); + } + setup( + coreSetup: CoreSetup<InferenceStartDependencies, InferenceServerStart>, + pluginsSetup: InferenceSetupDependencies + ): InferenceServerSetup { + const router = coreSetup.http.createRouter(); + + registerChatCompleteRoute({ + router, + coreSetup, + }); + + registerConnectorsRoute({ + router, + coreSetup, + }); + return {}; + } + + start(core: CoreStart, pluginsStart: InferenceStartDependencies): InferenceServerStart { + return { + getClient: ({ request }) => { + return createInferenceClient({ request, actions: pluginsStart.actions }); + }, + }; + } +} diff --git a/x-pack/plugins/inference/server/routes/chat_complete.ts b/x-pack/plugins/inference/server/routes/chat_complete.ts new file mode 100644 index 0000000000000..6c840f80466c2 --- /dev/null +++ b/x-pack/plugins/inference/server/routes/chat_complete.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 } from '@kbn/config-schema'; +import type { CoreSetup, IRouter, RequestHandlerContext } from '@kbn/core/server'; +import { isObservable } from 'rxjs'; +import { MessageRole } from '../../common/chat_complete'; +import type { ChatCompleteRequestBody } from '../../common/chat_complete/request'; +import { ToolCall, ToolChoiceType } from '../../common/chat_complete/tools'; +import { createInferenceClient } from '../inference_client'; +import { InferenceServerStart, InferenceStartDependencies } from '../types'; +import { observableIntoEventSourceStream } from '../util/observable_into_event_source_stream'; + +const toolCallSchema: Type<ToolCall[]> = schema.arrayOf( + schema.object({ + toolCallId: schema.string(), + function: schema.object({ + name: schema.string(), + arguments: schema.maybe(schema.object({}, { unknowns: 'allow' })), + }), + }) +); + +const chatCompleteBodySchema: Type<ChatCompleteRequestBody> = schema.object({ + connectorId: schema.string(), + system: schema.maybe(schema.string()), + tools: schema.maybe( + schema.recordOf( + schema.string(), + schema.object({ + description: schema.string(), + schema: schema.maybe( + schema.object({ + type: schema.literal('object'), + properties: schema.recordOf(schema.string(), schema.any()), + required: schema.maybe(schema.arrayOf(schema.string())), + }) + ), + }) + ) + ), + toolChoice: schema.maybe( + schema.oneOf([ + schema.literal(ToolChoiceType.auto), + schema.literal(ToolChoiceType.none), + schema.literal(ToolChoiceType.required), + schema.object({ + function: schema.string(), + }), + ]) + ), + messages: schema.arrayOf( + schema.oneOf([ + schema.object({ + role: schema.literal(MessageRole.Assistant), + content: schema.string(), + toolCalls: toolCallSchema, + }), + schema.object({ + role: schema.literal(MessageRole.User), + content: schema.string(), + name: schema.maybe(schema.string()), + }), + schema.object({ + role: schema.literal(MessageRole.Tool), + toolCallId: schema.string(), + response: schema.object({}, { unknowns: 'allow' }), + }), + ]) + ), +}); + +export function registerChatCompleteRoute({ + coreSetup, + router, +}: { + coreSetup: CoreSetup<InferenceStartDependencies, InferenceServerStart>; + router: IRouter<RequestHandlerContext>; +}) { + router.post( + { + path: '/internal/inference/chat_complete', + validate: { + body: chatCompleteBodySchema, + }, + }, + async (context, request, response) => { + const actions = await coreSetup + .getStartServices() + .then(([coreStart, pluginsStart]) => pluginsStart.actions); + + const client = createInferenceClient({ request, actions }); + + const { connectorId, messages, system, toolChoice, tools } = request.body; + + const chatCompleteResponse = await client.chatComplete({ + connectorId, + messages, + system, + toolChoice, + tools, + }); + + if (isObservable(chatCompleteResponse)) { + return response.ok({ + body: observableIntoEventSourceStream(chatCompleteResponse), + }); + } + + return response.ok({ body: chatCompleteResponse }); + } + ); +} diff --git a/x-pack/plugins/inference/server/routes/connectors.ts b/x-pack/plugins/inference/server/routes/connectors.ts new file mode 100644 index 0000000000000..8c69b68d55f14 --- /dev/null +++ b/x-pack/plugins/inference/server/routes/connectors.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 type { CoreSetup, IRouter, RequestHandlerContext } from '@kbn/core/server'; +import { InferenceConnector, InferenceConnectorType } from '../../common/connectors'; +import type { InferenceServerStart, InferenceStartDependencies } from '../types'; + +export function registerConnectorsRoute({ + coreSetup, + router, +}: { + coreSetup: CoreSetup<InferenceStartDependencies, InferenceServerStart>; + router: IRouter<RequestHandlerContext>; +}) { + router.get( + { + path: '/internal/inference/connectors', + validate: {}, + }, + async (_context, request, response) => { + const actions = await coreSetup + .getStartServices() + .then(([_coreStart, pluginsStart]) => pluginsStart.actions); + + const client = await actions.getActionsClientWithRequest(request); + + const allConnectors = await client.getAll({ + includeSystemActions: false, + }); + + const connectorTypes: string[] = [ + InferenceConnectorType.OpenAI, + InferenceConnectorType.Bedrock, + InferenceConnectorType.Gemini, + ]; + + const connectors: InferenceConnector[] = allConnectors + .filter((connector) => connectorTypes.includes(connector.actionTypeId)) + .map((connector) => { + return { + connectorId: connector.id, + name: connector.name, + type: connector.actionTypeId as InferenceConnectorType, + }; + }); + + return response.ok({ body: { connectors } }); + } + ); +} diff --git a/x-pack/plugins/inference/server/tasks/nl_to_esql/index.ts b/x-pack/plugins/inference/server/tasks/nl_to_esql/index.ts new file mode 100644 index 0000000000000..00ce53fe5d288 --- /dev/null +++ b/x-pack/plugins/inference/server/tasks/nl_to_esql/index.ts @@ -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 { switchMap, map } from 'rxjs'; +import { MessageRole } from '../../../common/chat_complete'; +import { ToolOptions } from '../../../common/chat_complete/tools'; +import { withoutChunkEvents } from '../../../common/chat_complete/without_chunk_events'; +import { withoutTokenCountEvents } from '../../../common/chat_complete/without_token_count_events'; +import { createOutputCompleteEvent } from '../../../common/output'; +import { withoutOutputUpdateEvents } from '../../../common/output/without_output_update_events'; +import { InferenceClient } from '../../types'; + +const ESQL_SYSTEM_MESSAGE = ''; + +async function getEsqlDocuments(documents: string[]) { + return [ + { + document: 'my-esql-function', + text: 'My ES|QL function', + }, + ]; +} + +export function naturalLanguageToEsql<TToolOptions extends ToolOptions>({ + client, + input, + connectorId, + tools, + toolChoice, +}: { + client: InferenceClient; + input: string; + connectorId: string; +} & TToolOptions) { + return client + .output('request_documentation', { + connectorId, + system: ESQL_SYSTEM_MESSAGE, + input: `Based on the following input, request documentation + from the ES|QL handbook to help you get the right information + needed to generate a query: + ${input} + `, + schema: { + type: 'object', + properties: { + documents: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + required: ['documents'], + } as const, + }) + .pipe( + withoutOutputUpdateEvents(), + switchMap((event) => { + return getEsqlDocuments(event.output.documents ?? []); + }), + switchMap((documents) => { + return client + .chatComplete({ + connectorId, + system: `${ESQL_SYSTEM_MESSAGE} + + The following documentation is provided: + + ${documents}`, + messages: [ + { + role: MessageRole.User, + content: input, + }, + ], + tools, + toolChoice, + }) + .pipe( + withoutTokenCountEvents(), + withoutChunkEvents(), + map((message) => { + return createOutputCompleteEvent('generated_query', { + content: message.content, + toolCalls: message.toolCalls, + }); + }) + ); + }), + withoutOutputUpdateEvents() + ); +} diff --git a/x-pack/plugins/inference/server/types.ts b/x-pack/plugins/inference/server/types.ts new file mode 100644 index 0000000000000..609b719b15236 --- /dev/null +++ b/x-pack/plugins/inference/server/types.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 type { + PluginStartContract as ActionsPluginStart, + PluginSetupContract as ActionsPluginSetup, +} from '@kbn/actions-plugin/server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import { ChatCompleteAPI } from '../common/chat_complete'; +import { InferenceConnector } from '../common/connectors'; +import { OutputAPI } from '../common/output'; + +/* eslint-disable @typescript-eslint/no-empty-interface*/ + +export interface ConfigSchema {} + +export interface InferenceSetupDependencies { + actions: ActionsPluginSetup; +} + +export interface InferenceStartDependencies { + actions: ActionsPluginStart; +} + +export interface InferenceServerSetup {} + +export interface InferenceClient { + /** + * `chatComplete` requests the LLM to generate a response to + * a prompt or conversation, which might be plain text + * or a tool call, or a combination of both. + */ + chatComplete: ChatCompleteAPI; + /** + * `output` asks the LLM to generate a structured (JSON) + * response based on a schema and a prompt or conversation. + */ + output: OutputAPI; + /** + * `getConnectorById` returns an inference connector by id. + * Non-inference connectors will throw an error. + */ + getConnectorById: (id: string) => Promise<InferenceConnector>; +} + +interface InferenceClientCreateOptions { + request: KibanaRequest; +} + +export interface InferenceServerStart { + /** + * Creates an inference client, scoped to a request. + * + * @param options {@link InferenceClientCreateOptions} + * @returns {@link InferenceClient} + */ + getClient: (options: InferenceClientCreateOptions) => InferenceClient; +} diff --git a/x-pack/plugins/inference/server/util/observable_into_event_source_stream.test.ts b/x-pack/plugins/inference/server/util/observable_into_event_source_stream.test.ts new file mode 100644 index 0000000000000..d7972bb970317 --- /dev/null +++ b/x-pack/plugins/inference/server/util/observable_into_event_source_stream.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createParser } from 'eventsource-parser'; +import { partition } from 'lodash'; +import { merge, of, throwError } from 'rxjs'; +import { InferenceTaskEvent } from '../../common/tasks'; +import { observableIntoEventSourceStream } from './observable_into_event_source_stream'; + +describe('observableIntoEventSourceStream', () => { + function renderStream<T extends InferenceTaskEvent>(events: Array<T | Error>) { + const [inferenceEvents, errors] = partition( + events, + (event): event is T => !(event instanceof Error) + ); + + const source$ = merge(of(...inferenceEvents), ...errors.map((error) => throwError(error))); + + const stream = observableIntoEventSourceStream(source$); + + return new Promise<string[]>((resolve, reject) => { + const chunks: string[] = []; + stream.on('data', (chunk) => { + chunks.push(chunk.toString()); + }); + stream.on('error', (error) => { + reject(error); + }); + stream.on('end', () => { + resolve(chunks); + }); + }); + } + + it('serializes error events', async () => { + const chunks = await renderStream([ + { + type: 'chunk', + }, + new Error('foo'), + ]); + + expect(chunks.map((chunk) => chunk.trim())).toEqual([ + `data: ${JSON.stringify({ type: 'chunk' })}`, + `data: ${JSON.stringify({ + type: 'error', + error: { code: 'internalError', message: 'foo' }, + })}`, + ]); + }); + + it('outputs data in SSE-compatible format', async () => { + const chunks = await renderStream([ + { + type: 'chunk', + id: 0, + }, + { + type: 'chunk', + id: 1, + }, + ]); + + const events: Array<Record<string, any>> = []; + + const parser = createParser((event) => { + if (event.type === 'event') { + events.push(JSON.parse(event.data)); + } + }); + + chunks.forEach((chunk) => { + parser.feed(chunk); + }); + + expect(events).toEqual([ + { + type: 'chunk', + id: 0, + }, + { + type: 'chunk', + id: 1, + }, + ]); + }); +}); diff --git a/x-pack/plugins/inference/server/util/observable_into_event_source_stream.ts b/x-pack/plugins/inference/server/util/observable_into_event_source_stream.ts new file mode 100644 index 0000000000000..2007b9842db69 --- /dev/null +++ b/x-pack/plugins/inference/server/util/observable_into_event_source_stream.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 { catchError, map, Observable, of } from 'rxjs'; +import { PassThrough } from 'stream'; +import { + InferenceTaskErrorCode, + InferenceTaskErrorEvent, + isInferenceError, +} from '../../common/errors'; +import { InferenceTaskEventType } from '../../common/tasks'; + +export function observableIntoEventSourceStream(source$: Observable<unknown>) { + const withSerializedErrors$ = source$.pipe( + catchError((error): Observable<InferenceTaskErrorEvent> => { + if (isInferenceError(error)) { + return of({ + type: InferenceTaskEventType.error, + error: { + code: error.code, + message: error.message, + meta: error.meta, + }, + }); + } + + return of({ + type: InferenceTaskEventType.error, + error: { + code: InferenceTaskErrorCode.internalError, + message: error.message as string, + }, + }); + }), + map((event) => { + return `data: ${JSON.stringify(event)}\n\n`; + }) + ); + + const stream = new PassThrough(); + + withSerializedErrors$.subscribe({ + next: (line) => { + stream.write(line); + }, + complete: () => { + stream.end(); + }, + error: (error) => { + stream.write( + `data: ${JSON.stringify({ + type: InferenceTaskEventType.error, + error: { + code: InferenceTaskErrorCode.internalError, + message: error.message, + }, + })}\n\n` + ); + stream.end(); + }, + }); + + return stream; +} diff --git a/x-pack/plugins/inference/server/util/validate_tool_calls.test.ts b/x-pack/plugins/inference/server/util/validate_tool_calls.test.ts new file mode 100644 index 0000000000000..96bf202fa236b --- /dev/null +++ b/x-pack/plugins/inference/server/util/validate_tool_calls.test.ts @@ -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 { isToolValidationError } from '../../common/chat_complete/errors'; +import { ToolChoiceType } from '../../common/chat_complete/tools'; +import { validateToolCalls } from './validate_tool_calls'; + +describe('validateToolCalls', () => { + it('throws an error if tools were called but toolChoice == none', () => { + expect(() => { + validateToolCalls({ + toolCalls: [ + { + function: { + name: 'my_function', + arguments: '{}', + }, + toolCallId: '1', + }, + ], + + toolChoice: ToolChoiceType.none, + tools: { + my_function: { + description: 'description', + }, + }, + }); + }).toThrowErrorMatchingInlineSnapshot( + `"tool_choice was \\"none\\" but my_function was/were called"` + ); + }); + + it('throws an error if an unknown tool was called', () => { + expect(() => + validateToolCalls({ + toolCalls: [ + { + function: { + name: 'my_unknown_function', + arguments: '{}', + }, + toolCallId: '1', + }, + ], + + tools: { + my_function: { + description: 'description', + }, + }, + }) + ).toThrowErrorMatchingInlineSnapshot(`"Tool my_unknown_function called but was not available"`); + }); + + it('throws an error if invalid JSON was generated', () => { + expect(() => + validateToolCalls({ + toolCalls: [ + { + function: { + name: 'my_function', + arguments: '{[]}', + }, + toolCallId: '1', + }, + ], + + tools: { + my_function: { + description: 'description', + }, + }, + }) + ).toThrowErrorMatchingInlineSnapshot(`"Failed parsing arguments for my_function"`); + }); + + it('throws an error if the function call has invalid arguments', () => { + function validate() { + validateToolCalls({ + toolCalls: [ + { + function: { + name: 'my_function', + arguments: JSON.stringify({ foo: 'bar' }), + }, + toolCallId: '1', + }, + ], + + tools: { + my_function: { + description: 'description', + schema: { + type: 'object', + properties: { + bar: { + type: 'string', + }, + }, + required: ['bar'], + }, + }, + }, + }); + } + expect(() => validate()).toThrowErrorMatchingInlineSnapshot( + `"Tool call arguments for my_function were invalid"` + ); + + try { + validate(); + } catch (error) { + if (isToolValidationError(error)) { + expect(error.meta).toEqual({ + arguments: JSON.stringify({ foo: 'bar' }), + errorsText: `data must have required property 'bar'`, + name: 'my_function', + }); + } else { + fail('Expected toolValidationError'); + } + } + }); + + it('successfully validates and parses a valid tool call', () => { + function runValidation() { + return validateToolCalls({ + toolCalls: [ + { + function: { + name: 'my_function', + arguments: '{ "foo": "bar" }', + }, + toolCallId: '1', + }, + ], + + tools: { + my_function: { + description: 'description', + schema: { + type: 'object', + properties: { + foo: { + type: 'string', + }, + }, + required: ['foo'], + }, + }, + }, + }); + } + expect(() => runValidation()).not.toThrowError(); + + const validated = runValidation(); + + expect(validated).toEqual([ + { + function: { + name: 'my_function', + arguments: { + foo: 'bar', + }, + }, + toolCallId: '1', + }, + ]); + }); +}); diff --git a/x-pack/plugins/inference/server/util/validate_tool_calls.ts b/x-pack/plugins/inference/server/util/validate_tool_calls.ts new file mode 100644 index 0000000000000..5d1e659bc36f5 --- /dev/null +++ b/x-pack/plugins/inference/server/util/validate_tool_calls.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 Ajv from 'ajv'; +import { + createToolNotFoundError, + createToolValidationError, +} from '../../common/chat_complete/errors'; +import { + ToolCallsOf, + ToolChoiceType, + ToolOptions, + UnvalidatedToolCall, +} from '../../common/chat_complete/tools'; + +export function validateToolCalls<TToolOptions extends ToolOptions>({ + toolCalls, + toolChoice, + tools, +}: TToolOptions & { toolCalls: UnvalidatedToolCall[] }): ToolCallsOf<TToolOptions>['toolCalls'] { + const validator = new Ajv(); + + if (toolCalls.length && toolChoice === ToolChoiceType.none) { + throw createToolValidationError( + `tool_choice was "none" but ${toolCalls + .map((toolCall) => toolCall.function.name) + .join(', ')} was/were called`, + { toolCalls } + ); + } + + return toolCalls.map((toolCall) => { + const tool = tools?.[toolCall.function.name]; + + if (!tool) { + throw createToolNotFoundError(toolCall.function.name); + } + + const toolSchema = tool.schema ?? { type: 'object', properties: {} }; + + let serializedArguments: ToolCallsOf<TToolOptions>['toolCalls'][0]['function']['arguments']; + + try { + serializedArguments = JSON.parse(toolCall.function.arguments); + } catch (error) { + throw createToolValidationError(`Failed parsing arguments for ${toolCall.function.name}`, { + name: toolCall.function.name, + arguments: toolCall.function.arguments, + toolCalls: [toolCall], + }); + } + + const valid = validator.validate(toolSchema, serializedArguments); + + if (!valid) { + throw createToolValidationError( + `Tool call arguments for ${toolCall.function.name} were invalid`, + { + name: toolCall.function.name, + errorsText: validator.errorsText(), + arguments: toolCall.function.arguments, + } + ); + } + + return { + toolCallId: toolCall.toolCallId, + function: { + name: toolCall.function.name, + arguments: serializedArguments, + }, + }; + }); +} diff --git a/x-pack/plugins/inference/tsconfig.json b/x-pack/plugins/inference/tsconfig.json new file mode 100644 index 0000000000000..16d7ca041582c --- /dev/null +++ b/x-pack/plugins/inference/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "../../../typings/**/*", + "common/**/*", + "public/**/*", + "typings/**/*", + "public/**/*.json", + "server/**/*", + ".storybook/**/*" + ], + "exclude": [ + "target/**/*", + ".storybook/**/*.js" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/i18n", + "@kbn/logging", + "@kbn/core-http-server", + "@kbn/actions-plugin", + "@kbn/config-schema" + ] +} diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx index af21526b7f14d..a137933afed3f 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.test.tsx @@ -40,7 +40,7 @@ describe('SampleLogsInput', () => { describe('when uploading a json logs sample', () => { const type = 'application/json'; - describe('when the file is valid', () => { + describe('when the file is valid json', () => { const logsSampleRaw = `{"message":"test message 1"},{"message":"test message 2"}`; beforeEach(async () => { await changeFile(input, new File([`[${logsSampleRaw}]`], 'test.json', { type })); @@ -73,9 +73,11 @@ describe('SampleLogsInput', () => { describe('when the file is invalid', () => { describe.each([ - ['[{"message":"test message 1"}', `The logs sample file has not a valid ${type} format`], + [ + '[{"message":"test message 1"}', + 'Cannot parse the logs sample file as either a JSON or NDJSON file', + ], ['["test message 1"]', 'The logs sample file contains non-object entries'], - ['{"message":"test message 1"}', 'The logs sample file is not an array'], ['[]', 'The logs sample file is empty'], ])('with logs content %s', (logsSample, errorMessage) => { beforeEach(async () => { @@ -98,7 +100,7 @@ describe('SampleLogsInput', () => { describe('when setting a ndjson logs sample', () => { const type = 'application/x-ndjson'; - describe('when the file is valid', () => { + describe('when the file is valid ndjson', () => { const logsSampleRaw = `{"message":"test message 1"}\n{"message":"test message 2"}`; beforeEach(async () => { await changeFile(input, new File([logsSampleRaw], 'test.json', { type })); @@ -131,7 +133,10 @@ describe('SampleLogsInput', () => { describe('when the file is invalid', () => { describe.each([ - ['{"message":"test message 1"]', `The logs sample file has not a valid ${type} format`], + [ + '{"message":"test message 1"}\n{"message": }', + 'Cannot parse the logs sample file as either a JSON or NDJSON file', + ], ['"test message 1"', 'The logs sample file contains non-object entries'], ['', 'The logs sample file is empty'], ])('with logs content %s', (logsSample, errorMessage) => { diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx index 072669a6bdd1d..cb4f735cc707c 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/sample_logs_input.tsx @@ -19,24 +19,34 @@ const MaxLogsSampleRows = 10; * Parse the logs sample file content (json or ndjson) and return the parsed logs sample */ const parseLogsContent = ( - fileContent: string | undefined, - fileType: string + fileContent: string | undefined ): { error?: string; isTruncated?: boolean; logsSampleParsed?: string[] } => { if (fileContent == null) { return { error: i18n.LOGS_SAMPLE_ERROR.CAN_NOT_READ }; } let parsedContent; try { - if (fileType === 'application/json') { + parsedContent = fileContent + .split('\n') + .filter((line) => line.trim() !== '') + .map((line) => JSON.parse(line)); + + // Special case for files that can be parsed as both JSON and NDJSON: + // for a one-line array [] -> extract its contents + // for a one-line object {} -> do nothing + if ( + Array.isArray(parsedContent) && + parsedContent.length === 1 && + Array.isArray(parsedContent[0]) + ) { + parsedContent = parsedContent[0]; + } + } catch (parseNDJSONError) { + try { parsedContent = JSON.parse(fileContent); - } else if (fileType === 'application/x-ndjson') { - parsedContent = fileContent - .split('\n') - .filter((line) => line.trim() !== '') - .map((line) => JSON.parse(line)); + } catch (parseJSONError) { + return { error: i18n.LOGS_SAMPLE_ERROR.CAN_NOT_PARSE }; } - } catch (_) { - return { error: i18n.LOGS_SAMPLE_ERROR.FORMAT(fileType) }; } if (!Array.isArray(parsedContent)) { @@ -81,10 +91,7 @@ export const SampleLogsInput = React.memo<SampleLogsInputProps>(({ integrationSe const reader = new FileReader(); reader.onload = function (e) { const fileContent = e.target?.result as string | undefined; // We can safely cast to string since we call `readAsText` to load the file. - const { error, isTruncated, logsSampleParsed } = parseLogsContent( - fileContent, - logsSampleFile.type - ); + const { error, isTruncated, logsSampleParsed } = parseLogsContent(fileContent); setIsParsing(false); setSampleFileError(error); if (error) { @@ -137,7 +144,6 @@ export const SampleLogsInput = React.memo<SampleLogsInputProps>(({ integrationSe onChange={onChangeLogsSample} display="large" aria-label="Upload logs sample file" - accept="application/json,application/x-ndjson" isLoading={isParsing} data-test-subj="logsSampleFilePicker" data-loading={isParsing} diff --git a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts index e4cc004e2673b..0af9f803f71fc 100644 --- a/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts +++ b/x-pack/plugins/integration_assistant/public/components/create_integration/create_integration_assistant/steps/data_stream_step/translations.ts @@ -126,11 +126,12 @@ export const LOGS_SAMPLE_ERROR = { defaultMessage: 'Failed to read the logs sample file', } ), - FORMAT: (fileType: string) => - i18n.translate('xpack.integrationAssistant.step.dataStream.logsSample.errorFormat', { - values: { fileType }, - defaultMessage: 'The logs sample file has not a valid {fileType} format', - }), + CAN_NOT_PARSE: i18n.translate( + 'xpack.integrationAssistant.step.dataStream.logsSample.errorCanNotParse', + { + defaultMessage: 'Cannot parse the logs sample file as either a JSON or NDJSON file', + } + ), NOT_ARRAY: i18n.translate('xpack.integrationAssistant.step.dataStream.logsSample.errorNotArray', { defaultMessage: 'The logs sample file is not an array', }), diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 1168c0d07ea4b..de20db385c9e9 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -471,14 +471,6 @@ export function App({ [dataViews, uiActions, http, notifications, uiSettings, initialContext, dispatch] ); - const onTextBasedSavedAndExit = useCallback(async ({ onSave, onCancel: _onCancel }) => { - setIsSaveModalVisible(true); - setShouldCloseAndSaveTextBasedQuery(true); - saveAndExit.current = () => { - onSave(); - }; - }, []); - // remember latest URL based on the configuration // url_panel_content has a similar logic const shareURLCache = useRef({ params: '', url: '' }); @@ -571,7 +563,6 @@ export function App({ topNavMenuEntryGenerators={topNavMenuEntryGenerators} initialContext={initialContext} indexPatternService={indexPatternService} - onTextBasedSavedAndExit={onTextBasedSavedAndExit} getUserMessages={getUserMessages} shortUrlService={shortUrlService} startServices={coreStart} diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 545a31032155a..3f12c6f63ca95 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -281,7 +281,6 @@ export const LensTopNavMenu = ({ initialContext, indexPatternService, currentDoc, - onTextBasedSavedAndExit, getUserMessages, shortUrlService, isCurrentStateDirty, @@ -1112,7 +1111,6 @@ export const LensTopNavMenu = ({ ) } textBasedLanguageModeErrors={textBasedLanguageModeErrors} - onTextBasedSavedAndExit={onTextBasedSavedAndExit} showFilterBar={true} data-test-subj="lnsApp_topNav" screenTitle={'lens'} diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts index 4e6f1ef70acbd..1274008d0de88 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts @@ -10,12 +10,14 @@ import { getESQLResults, formatESQLColumns, } from '@kbn/esql-utils'; -import type { AggregateQuery } from '@kbn/es-query'; +import { type AggregateQuery, buildEsQuery } from '@kbn/es-query'; import type { ESQLRow } from '@kbn/es-types'; import { getLensAttributesFromSuggestion } from '@kbn/visualization-utils'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; +import { getTime } from '@kbn/data-plugin/common'; +import { type DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { TypedLensByValueInput } from '../../../embeddable/embeddable_component'; import type { LensPluginStartDependencies } from '../../../plugin'; import type { DatasourceMap, VisualizationMap } from '../../../types'; @@ -27,6 +29,21 @@ export interface ESQLDataGridAttrs { columns: DatatableColumn[]; } +const getDSLFilter = (queryService: DataPublicPluginStart['query'], timeFieldName?: string) => { + const kqlQuery = queryService.queryString.getQuery(); + const filters = queryService.filterManager.getFilters(); + const timeFilter = + queryService.timefilter.timefilter.getTime() && + getTime(undefined, queryService.timefilter.timefilter.getTime(), { + fieldName: timeFieldName, + }); + + return buildEsQuery(undefined, kqlQuery || [], [ + ...(filters ?? []), + ...(timeFilter ? [timeFilter] : []), + ]); +}; + export const getGridAttrs = async ( query: AggregateQuery, adHocDataViews: DataViewSpec[], @@ -38,18 +55,20 @@ export const getGridAttrs = async ( return adHoc.name === indexPattern; }); - const [results, dataView] = await Promise.all([ - getESQLResults({ - esqlQuery: query.esql, - search: deps.data.search.search, - signal: abortController?.signal, - dropNullColumns: true, - timeRange: deps.data.query.timefilter.timefilter.getAbsoluteTime(), - }), - dataViewSpec - ? deps.dataViews.create(dataViewSpec) - : getESQLAdHocDataview(query.esql, deps.dataViews), - ]); + const dataView = dataViewSpec + ? await deps.dataViews.create(dataViewSpec) + : await getESQLAdHocDataview(query.esql, deps.dataViews); + + const filter = getDSLFilter(deps.data.query, dataView.timeFieldName); + + const results = await getESQLResults({ + esqlQuery: query.esql, + search: deps.data.search.search, + signal: abortController?.signal, + filter, + dropNullColumns: true, + timeRange: deps.data.query.timefilter.timefilter.getAbsoluteTime(), + }); const columns = formatESQLColumns(results.response.columns); diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx index a30fb5d3f52fc..42a84dc8b2f55 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/lens_configuration_flyout.tsx @@ -479,8 +479,6 @@ export function LensEditConfigurationFlyout({ setQuery(q); prevQuery.current = q; }} - expandCodeEditor={(status: boolean) => {}} - isCodeEditorExpanded detectedTimestamp={adHocDataViews?.[0]?.timeFieldName} hideTimeFilterInfo={hideTimeFilterInfo} errors={errors} @@ -492,7 +490,6 @@ export function LensEditConfigurationFlyout({ }) : undefined } - hideMinimizeButton editorIsInline hideRunQueryText onTextLangQuerySubmit={async (q, a) => { diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index 7bfa16b2ebd77..317efd5be507f 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -126,7 +126,6 @@ export interface LensTopNavMenuProps { initialContext?: VisualizeFieldContext | VisualizeEditorContext; currentDoc: Document | undefined; indexPatternService: IndexPatternServiceAPI; - onTextBasedSavedAndExit: ({ onSave }: { onSave: () => void }) => Promise<void>; getUserMessages: UserMessagesGetter; shortUrlService: (params: LensAppLocatorParams) => Promise<string>; isCurrentStateDirty: boolean; diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx index 7271fb8e1a80b..ba047564a1f07 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx @@ -339,7 +339,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({ editPermission ? async (fieldName: string) => { const indexPatternInstance = await dataViews.get(currentIndexPattern?.id); - closeFieldEditor.current = indexPatternFieldEditor.openDeleteModal({ + closeFieldEditor.current = await indexPatternFieldEditor.openDeleteModal({ ctx: { dataView: indexPatternInstance, }, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx index 011b824fd1766..149b67b04768a 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx @@ -857,6 +857,8 @@ export function FormulaEditor({ } ), }} + isHelpMenuOpen={isHelpOpen} + onHelpMenuVisibilityChange={setIsHelpOpen} /> )} </EuiFlexItem> diff --git a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts index 8768bc721480d..96cd0ab6877e3 100644 --- a/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts +++ b/x-pack/plugins/lens/public/trigger_actions/open_lens_config/create_action_helpers.ts @@ -14,6 +14,7 @@ import { getIndexForESQLQuery, ENABLE_ESQL, getESQLQueryColumns, + getInitialESQLQuery, } from '@kbn/esql-utils'; import type { Datasource, Visualization } from '../../types'; import type { LensPluginStartDependencies } from '../../plugin'; @@ -81,10 +82,10 @@ export async function executeCreateAction({ setVisualizationMap(visualizationMap); } - const defaultIndex = dataView.getIndexPattern(); + const esqlQuery = getInitialESQLQuery(dataView); const defaultEsqlQuery = { - esql: `FROM ${defaultIndex} | LIMIT 10`, + esql: esqlQuery, }; // For the suggestions api we need only the columns @@ -93,7 +94,7 @@ export async function executeCreateAction({ // all the table const abortController = new AbortController(); const columns = await getESQLQueryColumns({ - esqlQuery: `from ${defaultIndex}`, + esqlQuery, search: deps.data.search.search, signal: abortController.signal, timeRange: deps.data.query.timefilter.timefilter.getAbsoluteTime(), diff --git a/x-pack/plugins/licensing/common/register_analytics_context_provider.ts b/x-pack/plugins/licensing/common/register_analytics_context_provider.ts index 5defe217901b2..c66dc7b464c2d 100644 --- a/x-pack/plugins/licensing/common/register_analytics_context_provider.ts +++ b/x-pack/plugins/licensing/common/register_analytics_context_provider.ts @@ -7,7 +7,7 @@ import type { Observable } from 'rxjs'; import { map } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/ebt/client'; +import type { AnalyticsClient } from '@elastic/ebt/client'; import type { ILicense } from './types'; export function registerAnalyticsContextProvider( diff --git a/x-pack/plugins/licensing/tsconfig.json b/x-pack/plugins/licensing/tsconfig.json index 8eed250935b33..21e8517d466c9 100644 --- a/x-pack/plugins/licensing/tsconfig.json +++ b/x-pack/plugins/licensing/tsconfig.json @@ -14,7 +14,6 @@ "@kbn/i18n", "@kbn/logging-mocks", "@kbn/react-kibana-mount", - "@kbn/ebt" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/lists/server/routes/list/read_list_route.ts b/x-pack/plugins/lists/server/routes/list/read_list_route.ts index 5f4ecd5faeb76..fff8ef9e60971 100644 --- a/x-pack/plugins/lists/server/routes/list/read_list_route.ts +++ b/x-pack/plugins/lists/server/routes/list/read_list_route.ts @@ -8,7 +8,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_URL } from '@kbn/securitysolution-list-constants'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { GetListRequestQuery, GetListResponse } from '@kbn/securitysolution-lists-common/api'; +import { ReadListRequestQuery, ReadListResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; import { buildSiemResponse } from '../utils'; @@ -27,7 +27,7 @@ export const readListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidationWithZod(GetListRequestQuery), + query: buildRouteValidationWithZod(ReadListRequestQuery), }, }, version: '2023-10-31', @@ -46,7 +46,7 @@ export const readListRoute = (router: ListsPluginRouter): void => { }); } - return response.ok({ body: GetListResponse.parse(list) }); + return response.ok({ body: ReadListResponse.parse(list) }); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts b/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts index 80637788e00db..79c82e739ebe8 100644 --- a/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts @@ -7,7 +7,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_INDEX } from '@kbn/securitysolution-list-constants'; -import { GetListIndexResponse } from '@kbn/securitysolution-lists-common/api'; +import { ReadListIndexResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; import { buildSiemResponse } from '../utils'; @@ -37,7 +37,7 @@ export const readListIndexRoute = (router: ListsPluginRouter): void => { if (listDataStreamExists && listItemDataStreamExists) { return response.ok({ - body: GetListIndexResponse.parse({ + body: ReadListIndexResponse.parse({ list_index: listDataStreamExists, list_item_index: listItemDataStreamExists, }), diff --git a/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts index 8be3daa68eb49..29513aa23f74f 100644 --- a/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts @@ -9,8 +9,8 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { - GetListItemRequestQuery, - GetListItemResponse, + ReadListItemRequestQuery, + ReadListItemResponse, } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; @@ -30,7 +30,7 @@ export const readListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidationWithZod(GetListItemRequestQuery), + query: buildRouteValidationWithZod(ReadListItemRequestQuery), }, }, version: '2023-10-31', @@ -51,7 +51,7 @@ export const readListItemRoute = (router: ListsPluginRouter): void => { }); } - return response.ok({ body: GetListItemResponse.parse(listItem) }); + return response.ok({ body: ReadListItemResponse.parse(listItem) }); } else if (listId != null && value != null) { const list = await lists.getList({ id: listId }); @@ -75,7 +75,7 @@ export const readListItemRoute = (router: ListsPluginRouter): void => { }); } - return response.ok({ body: GetListItemResponse.parse(listItem) }); + return response.ok({ body: ReadListItemResponse.parse(listItem) }); } else { return siemResponse.error({ body: 'Either "list_id" or "id" needs to be defined in the request', diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts index 768592f1e450d..9f35da7fa6fe8 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts @@ -9,8 +9,8 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { - GetExceptionListItemRequestQuery, - GetExceptionListItemResponse, + ReadExceptionListItemRequestQuery, + ReadExceptionListItemResponse, } from '@kbn/securitysolution-exceptions-common/api'; import type { ListsPluginRouter } from '../types'; @@ -34,7 +34,7 @@ export const readExceptionListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidationWithZod(GetExceptionListItemRequestQuery), + query: buildRouteValidationWithZod(ReadExceptionListItemRequestQuery), }, }, version: '2023-10-31', @@ -62,7 +62,7 @@ export const readExceptionListItemRoute = (router: ListsPluginRouter): void => { }); } - return response.ok({ body: GetExceptionListItemResponse.parse(exceptionListItem) }); + return response.ok({ body: ReadExceptionListItemResponse.parse(exceptionListItem) }); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts index 899804ff9b9fa..b98b7dfe86ee8 100644 --- a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts @@ -9,8 +9,8 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { - GetExceptionListRequestQuery, - GetExceptionListResponse, + ReadExceptionListRequestQuery, + ReadExceptionListResponse, } from '@kbn/securitysolution-exceptions-common/api'; import type { ListsPluginRouter } from '../types'; @@ -30,7 +30,7 @@ export const readExceptionListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidationWithZod(GetExceptionListRequestQuery), + query: buildRouteValidationWithZod(ReadExceptionListRequestQuery), }, }, version: '2023-10-31', @@ -57,7 +57,7 @@ export const readExceptionListRoute = (router: ListsPluginRouter): void => { }); } - return response.ok({ body: GetExceptionListResponse.parse(exceptionList) }); + return response.ok({ body: ReadExceptionListResponse.parse(exceptionList) }); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/lists/server/routes/summary_exception_list_route.ts b/x-pack/plugins/lists/server/routes/summary_exception_list_route.ts index 3e8f8cc384d3e..28810283770be 100644 --- a/x-pack/plugins/lists/server/routes/summary_exception_list_route.ts +++ b/x-pack/plugins/lists/server/routes/summary_exception_list_route.ts @@ -9,8 +9,8 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { - GetExceptionListSummaryRequestQuery, - GetExceptionListSummaryResponse, + ReadExceptionListSummaryRequestQuery, + ReadExceptionListSummaryResponse, } from '@kbn/securitysolution-exceptions-common/api'; import type { ListsPluginRouter } from '../types'; @@ -30,7 +30,7 @@ export const summaryExceptionListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidationWithZod(GetExceptionListSummaryRequestQuery), + query: buildRouteValidationWithZod(ReadExceptionListSummaryRequestQuery), }, }, version: '2023-10-31', @@ -60,7 +60,7 @@ export const summaryExceptionListRoute = (router: ListsPluginRouter): void => { } return response.ok({ - body: GetExceptionListSummaryResponse.parse(exceptionListSummary), + body: ReadExceptionListSummaryResponse.parse(exceptionListSummary), }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js index b80c0522f9aa1..318b7dbd1e115 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js @@ -254,7 +254,6 @@ class PipelineListUi extends React.Component { { numSuccesses, numPipelinesSelected, - numPipelinesSelected, } ), text, @@ -288,7 +287,7 @@ class PipelineListUi extends React.Component { onSelectionChange = (selection) => this.setState({ selection }); render() { - const { clonePipeline, createPipeline, isReadOnly, openPipeline } = this.props; + const { clonePipeline, createPipeline, isReadOnly, openPipeline, isServerless } = this.props; const { isSelectable, message, pipelines, selection, showConfirmDeleteModal } = this.state; return ( <EuiPageSection data-test-subj="pipelineList"> @@ -326,8 +325,8 @@ class PipelineListUi extends React.Component { showConfirmDeleteModal={showConfirmDeleteModal} /> <InfoAlerts - showAddRoleAlert={this.state.showAddRoleAlert} - showEnableMonitoringAlert={this.state.showEnableMonitoringAlert} + showAddRoleAlert={!isServerless && this.state.showAddRoleAlert} + showEnableMonitoringAlert={!isServerless && this.state.showEnableMonitoringAlert} /> </EuiPageSection> ); diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js index 620e8081c6744..b3bdcf5a3aa63 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js @@ -35,6 +35,7 @@ describe('PipelineList component', () => { isClusterInfoAvailable: getIsClusterInfoAvailable(true), deleteSelectedPipelines: getDeleteSelectedPipelines(true), }, + isServerless: false, isReadOnly: false, licenseService: { checkValidity: () => Promise.resolve(), diff --git a/x-pack/plugins/logstash/public/application/index.tsx b/x-pack/plugins/logstash/public/application/index.tsx index f607cde0e7a66..6d1e939342186 100644 --- a/x-pack/plugins/logstash/public/application/index.tsx +++ b/x-pack/plugins/logstash/public/application/index.tsx @@ -32,7 +32,8 @@ export const renderApp = async ( core: CoreStart, { history, element, setBreadcrumbs }: ManagementAppMountParams, isMonitoringEnabled: boolean, - licenseService$: Observable<any> + licenseService$: Observable<any>, + isServerless: boolean ) => { const logstashLicenseService = await licenseService$.pipe(first()).toPromise(); const clusterService = new ClusterService(core.http); @@ -52,6 +53,7 @@ export const renderApp = async ( return ( <PipelineList clusterService={clusterService} + isServerless={isServerless} isReadOnly={logstashLicenseService.isReadOnly} isForbidden={true} isLoading={false} diff --git a/x-pack/plugins/logstash/public/index.ts b/x-pack/plugins/logstash/public/index.ts index ac4c61a6ac4ad..667b517705c38 100644 --- a/x-pack/plugins/logstash/public/index.ts +++ b/x-pack/plugins/logstash/public/index.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { PluginInitializerContext } from '@kbn/core-plugins-browser'; import { LogstashPlugin } from './plugin'; -export const plugin = () => new LogstashPlugin(); +export const plugin = (initializerContext: PluginInitializerContext) => + new LogstashPlugin(initializerContext); diff --git a/x-pack/plugins/logstash/public/plugin.ts b/x-pack/plugins/logstash/public/plugin.ts index 1b5067fdcecbb..36beacdfa86a5 100644 --- a/x-pack/plugins/logstash/public/plugin.ts +++ b/x-pack/plugins/logstash/public/plugin.ts @@ -16,6 +16,7 @@ import { ManagementSetup } from '@kbn/management-plugin/public'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/public'; // @ts-ignore +import type { PluginInitializerContext } from '@kbn/core-plugins-browser'; import { LogstashLicenseService } from './services'; interface SetupDeps { @@ -26,9 +27,14 @@ interface SetupDeps { } export class LogstashPlugin implements Plugin<void, void, SetupDeps> { + private readonly isServerless: boolean; private licenseSubscription?: Subscription; private capabilities$ = new Subject<Capabilities>(); + constructor(initializerContext: PluginInitializerContext) { + this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless'; + } + public setup(core: CoreSetup, plugins: SetupDeps) { const logstashLicense$ = plugins.licensing.license$.pipe( map((license) => new LogstashLicenseService(license)) @@ -54,7 +60,8 @@ export class LogstashPlugin implements Plugin<void, void, SetupDeps> { coreStart, params, isMonitoringEnabled, - logstashLicense$ + logstashLicense$, + this.isServerless ); return () => { diff --git a/x-pack/plugins/logstash/tsconfig.json b/x-pack/plugins/logstash/tsconfig.json index 7c5af6106c6f2..62240bd04b0cc 100644 --- a/x-pack/plugins/logstash/tsconfig.json +++ b/x-pack/plugins/logstash/tsconfig.json @@ -23,6 +23,7 @@ "@kbn/shared-ux-router", "@kbn/code-editor", "@kbn/react-kibana-context-render", + "@kbn/core-plugins-browser", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx index 5cfaf5abfcb72..be9b75dedf4d4 100644 --- a/x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/esql_source/esql_editor.tsx @@ -89,11 +89,6 @@ export function ESQLEditor(props: Props) { }} errors={error ? [error] : undefined} warning={warning} - expandCodeEditor={(status: boolean) => { - // never called because hideMinimizeButton hides UI - }} - isCodeEditorExpanded - hideMinimizeButton editorIsInline hideRunQueryText isLoading={isLoading} diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx index 6d0f516e5642c..e0d0f93c7967a 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx @@ -5,22 +5,50 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; +import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow, EuiSelect } from '@elastic/eui'; import type { FileLayer } from '@elastic/ems-client'; import { ChoroplethChartState } from './types'; import { EMSFileSelect } from '../../components/ems_file_select'; +import { getEmsFileLayers } from '../../util'; interface Props { - emsFileLayers: FileLayer[]; state: ChoroplethChartState; setState: (state: ChoroplethChartState) => void; } export function RegionKeyEditor(props: Props) { + const [emsFileLayers, setEmsFileLayers] = useState<FileLayer[]>([]); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + let ignore = false; + setIsLoading(true); + getEmsFileLayers() + .then((fileLayers) => { + if (!ignore) { + setEmsFileLayers(fileLayers); + setIsLoading(false); + } + }) + .catch(() => { + if (!ignore) { + // eslint-disable-next-line no-console + console.warn( + `Lens region map is unable to access administrative boundaries from Elastic Maps Service (EMS). To avoid unnecessary EMS requests, set 'map.includeElasticMapsService: false' in 'kibana.yml'.` + ); + setIsLoading(false); + } + }); + + return () => { + ignore = true; + }; + }, []); + function onEmsLayerSelect(emsLayerId: string) { - const emsFields = getEmsFields(props.emsFileLayers, emsLayerId); + const emsFields = getEmsFields(emsFileLayers, emsLayerId); props.setState({ ...props.state, emsLayerId, @@ -28,27 +56,30 @@ export function RegionKeyEditor(props: Props) { }); } - function onEmsFieldSelect(selectedOptions: Array<EuiComboBoxOptionOption<string>>) { - if (selectedOptions.length === 0) { - return; + const emsFieldSelect = useMemo(() => { + const emsFields = getEmsFields(emsFileLayers, props.state.emsLayerId); + if (emsFields.length === 0) { + return null; } - props.setState({ - ...props.state, - emsField: selectedOptions[0].value, - }); - } + const selectedOption = props.state.emsField + ? emsFields.find((option: EuiComboBoxOptionOption<string>) => { + return props.state.emsField === option.value; + }) + : undefined; + + function onEmsFieldSelect(selectedOptions: Array<EuiComboBoxOptionOption<string>>) { + if (selectedOptions.length === 0) { + return; + } - let emsFieldSelect; - const emsFields = getEmsFields(props.emsFileLayers, props.state.emsLayerId); - if (emsFields.length) { - let selectedOption; - if (props.state.emsField) { - selectedOption = emsFields.find((option: EuiComboBoxOptionOption<string>) => { - return props.state.emsField === option.value; + props.setState({ + ...props.state, + emsField: selectedOptions[0].value, }); } - emsFieldSelect = ( + + return ( <EuiFormRow label={i18n.translate('xpack.maps.choropleth.joinFieldLabel', { defaultMessage: 'Join field', @@ -64,8 +95,11 @@ export function RegionKeyEditor(props: Props) { /> </EuiFormRow> ); - } - return ( + }, [emsFileLayers, props]); + + return isLoading ? ( + <EuiSelect isLoading /> + ) : ( <> <EMSFileSelect isColumnCompressed diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/setup.ts b/x-pack/plugins/maps/public/lens/choropleth_chart/setup.ts index 6805044a0f996..479e4df7a7231 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/setup.ts +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/setup.ts @@ -8,7 +8,6 @@ import type { ExpressionsSetup } from '@kbn/expressions-plugin/public'; import type { CoreSetup, CoreStart } from '@kbn/core/public'; import type { LensPublicSetup } from '@kbn/lens-plugin/public'; -import type { FileLayer } from '@elastic/ems-client'; import type { MapsPluginStartDependencies } from '../../plugin'; import { getExpressionFunction } from './expression_function'; import { getExpressionRenderer } from './expression_renderer'; @@ -27,22 +26,10 @@ export function setupLensChoroplethChart( lens.registerVisualization(async () => { const [coreStart, plugins]: [CoreStart, MapsPluginStartDependencies, unknown] = await coreSetup.getStartServices(); - const { getEmsFileLayers } = await import('../../util'); const { getVisualization } = await import('./visualization'); - let emsFileLayers: FileLayer[] = []; - try { - emsFileLayers = await getEmsFileLayers(); - } catch (error) { - // eslint-disable-next-line no-console - console.warn( - `Lens region map setup is unable to access administrative boundaries from Elastic Maps Service (EMS). To avoid unnecessary EMS requests, set 'map.includeElasticMapsService: false' in 'kibana.yml'. For more details please visit ${coreStart.docLinks.links.maps.connectToEms}` - ); - } - return getVisualization({ theme: coreStart.theme, - emsFileLayers, paletteService: await plugins.charts.palettes.getPalettes(), }); }); diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/suggestions_lazy.ts b/x-pack/plugins/maps/public/lens/choropleth_chart/suggestions_lazy.ts new file mode 100644 index 0000000000000..64ba7ea4af831 --- /dev/null +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/suggestions_lazy.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 type { SuggestionRequest, VisualizationSuggestion } from '@kbn/lens-plugin/public'; +import type { FileLayer } from '@elastic/ems-client'; +import type { ChoroplethChartState } from './types'; + +let emsFileLayers: FileLayer[] | undefined; +let getSuggestionsActual: + | (( + suggestionRequest: SuggestionRequest<ChoroplethChartState>, + emsFileLayers: FileLayer[] + ) => Array<VisualizationSuggestion<ChoroplethChartState>>) + | undefined; +let promise: undefined | Promise<void>; + +/** + * Avoid loading file layers during plugin setup + * Instead, load file layers when getSuggestions is called + * Since getSuggestions is sync, the trade off is that + * getSuggestions will return no suggestions until file layers load + */ +export function getSuggestionsLazy( + suggestionRequest: SuggestionRequest<ChoroplethChartState> +): Array<VisualizationSuggestion<ChoroplethChartState>> { + if (!promise) { + promise = new Promise((resolve) => { + Promise.all([import('./suggestions'), import('../../util')]) + .then(async ([{ getSuggestions }, { getEmsFileLayers }]) => { + getSuggestionsActual = getSuggestions; + try { + emsFileLayers = await getEmsFileLayers(); + } catch (error) { + // eslint-disable-next-line no-console + console.warn( + `Lens region map is unable to access administrative boundaries from Elastic Maps Service (EMS). To avoid unnecessary EMS requests, set 'map.includeElasticMapsService: false' in 'kibana.yml'.` + ); + } + resolve(); + }) + .catch(resolve); + }); + return []; + } + + return emsFileLayers && getSuggestionsActual + ? getSuggestionsActual(suggestionRequest, emsFileLayers) + : []; +} diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx index 6b4fef07867f2..fd7ff91b78f8b 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/visualization.tsx @@ -7,15 +7,14 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import type { FileLayer } from '@elastic/ems-client'; +import { dynamic } from '@kbn/shared-ux-utility'; import type { PaletteRegistry } from '@kbn/coloring'; import { ThemeServiceStart } from '@kbn/core/public'; import { layerTypes } from '@kbn/lens-plugin/public'; import type { OperationMetadata, SuggestionRequest, Visualization } from '@kbn/lens-plugin/public'; import { IconRegionMap } from '@kbn/chart-icons'; -import { getSuggestions } from './suggestions'; +import { getSuggestionsLazy } from './suggestions_lazy'; import type { ChoroplethChartState } from './types'; -import { RegionKeyEditor } from './region_key_editor'; const REGION_KEY_GROUP_ID = 'region_key'; const METRIC_GROUP_ID = 'metric'; @@ -27,11 +26,9 @@ const CHART_LABEL = i18n.translate('xpack.maps.lens.choropleth.label', { export const getVisualization = ({ paletteService, theme, - emsFileLayers, }: { paletteService: PaletteRegistry; theme: ThemeServiceStart; - emsFileLayers: FileLayer[]; }): Visualization<ChoroplethChartState> => ({ id: 'lnsChoropleth', @@ -73,7 +70,7 @@ export const getVisualization = ({ }, getSuggestions(suggestionRequest: SuggestionRequest<ChoroplethChartState>) { - return getSuggestions(suggestionRequest, emsFileLayers); + return getSuggestionsLazy(suggestionRequest); }, initialize(addNewLayer, state) { @@ -194,13 +191,13 @@ export const getVisualization = ({ DimensionEditorComponent(props) { if (props.groupId === REGION_KEY_GROUP_ID) { - return ( - <RegionKeyEditor - emsFileLayers={emsFileLayers} - state={props.state} - setState={props.setState} - /> - ); + const DimensionEditor = dynamic(async () => { + const { RegionKeyEditor } = await import('./region_key_editor'); + return { + default: RegionKeyEditor, + }; + }); + return <DimensionEditor state={props.state} setState={props.setState} />; } return null; }, diff --git a/x-pack/plugins/ml/common/types/common.ts b/x-pack/plugins/ml/common/types/common.ts index 65801f2667f71..3e0213b8ab959 100644 --- a/x-pack/plugins/ml/common/types/common.ts +++ b/x-pack/plugins/ml/common/types/common.ts @@ -41,6 +41,7 @@ export interface ListingPageUrlState { sortField: string; sortDirection: string; queryText?: string; + showAll?: boolean; } export type AppPageState<T> = { diff --git a/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/ml_anomaly_alert_trigger.tsx b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/ml_anomaly_alert_trigger.tsx index d885d04053441..2f36f3f687caf 100644 --- a/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/ml_anomaly_alert_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/anomaly_detection_rule/ml_anomaly_alert_trigger.tsx @@ -197,6 +197,7 @@ const MlAnomalyAlertTrigger: FC<MlAnomalyAlertTriggerProps> = ({ // eslint-disable-next-line react-hooks/exhaustive-deps onChange={useCallback(onAlertParamChange('jobSelection'), [])} errors={Array.isArray(errors.jobSelection) ? errors.jobSelection : []} + shouldUseDropdownJobCreate /> <ConfigValidator diff --git a/x-pack/plugins/ml/public/alerting/job_selector.tsx b/x-pack/plugins/ml/public/alerting/job_selector.tsx index 11cc0e67181e8..80ef8a5fed0c3 100644 --- a/x-pack/plugins/ml/public/alerting/job_selector.tsx +++ b/x-pack/plugins/ml/public/alerting/job_selector.tsx @@ -10,12 +10,13 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui'; -import { EuiComboBox, EuiFormRow } from '@elastic/eui'; +import { EuiButton, EuiComboBox, EuiEmptyPrompt, EuiFormRow } from '@elastic/eui'; import useMountedState from 'react-use/lib/useMountedState'; import { useMlKibana } from '../application/contexts/kibana'; import type { JobId } from '../../common/types/anomaly_detection_jobs'; import type { MlApiServices } from '../application/services/ml_api_service'; import { ALL_JOBS_SELECTION } from '../../common/constants/alerts'; +import { LoadingIndicator } from '../application/components/loading_indicator'; interface JobSelection { jobIds?: JobId[]; @@ -43,6 +44,10 @@ export interface JobSelectorControlProps { * Available options to select. By default suggest all existing jobs. */ options?: Array<EuiComboBoxOptionOption<string>>; + /** + * Flag to indicate whether to use the job creation button in the empty prompt or the dropdown when no jobs are available. + */ + shouldUseDropdownJobCreate?: boolean; } export const JobSelectorControl: FC<JobSelectorControlProps> = ({ @@ -55,6 +60,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({ allowSelectAll = false, createJobUrl, options: defaultOptions, + shouldUseDropdownJobCreate = false, }) => { const { services: { @@ -66,6 +72,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({ const isMounted = useMountedState(); const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<string>>>([]); + const [areJobsLoading, setAreJobsLoading] = useState<boolean>(false); const jobIds = useMemo(() => new Set(), []); const groupIds = useMemo(() => new Set(), []); @@ -78,6 +85,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({ ); const fetchOptions = useCallback(async () => { + setAreJobsLoading(true); try { const { jobIds: jobIdOptions, groupIds: groupIdOptions } = await adJobsApiService.getAllJobAndGroupIds(); @@ -147,6 +155,7 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({ }), }); } + setAreJobsLoading(false); }, [ adJobsApiService, allowSelectAll, @@ -200,7 +209,9 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [createJobUrl]); - return ( + if (areJobsLoading === true) return <LoadingIndicator />; + + return jobIds.size || shouldUseDropdownJobCreate ? ( <EuiFormRow data-test-subj="mlAnomalyJobSelectionControls" fullWidth @@ -225,5 +236,32 @@ export const JobSelectorControl: FC<JobSelectorControlProps> = ({ isInvalid={!!errors?.length} /> </EuiFormRow> + ) : ( + <EuiEmptyPrompt + data-test-subj="mlAnomalyJobSelectionControls" + titleSize="xxs" + iconType="warning" + title={ + <h4> + <FormattedMessage + id="xpack.ml.embeddables.jobSelector.noJobsFoundTitle" + defaultMessage="No anomaly detection jobs found" + /> + </h4> + } + body={ + <EuiButton + fill + color="primary" + onClick={() => navigateToUrl(createJobUrl!)} + disabled={createJobUrl === undefined} + > + <FormattedMessage + id="xpack.ml.embeddables.jobSelector.createJobButtonLabel" + defaultMessage="Create job" + /> + </EuiButton> + } + /> ); }; diff --git a/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx b/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx index 9f89e049b937f..4e8d88f8b33ef 100644 --- a/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx @@ -123,6 +123,7 @@ const AnomalyDetectionJobsHealthRuleTrigger: FC<MlAnomalyAlertTriggerProps> = ({ defaultMessage="Include jobs or groups" /> } + shouldUseDropdownJobCreate /> <EuiSpacer size="m" /> @@ -148,6 +149,7 @@ const AnomalyDetectionJobsHealthRuleTrigger: FC<MlAnomalyAlertTriggerProps> = ({ /> } options={excludeJobsOptions} + shouldUseDropdownJobCreate /> <EuiSpacer size="m" /> diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx index 6d4ca53f72d51..b4da44047fd86 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_flyout.tsx @@ -242,7 +242,9 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({ </EuiButtonEmpty> )} </EuiFlexItem> - {withTimeRangeSelector && applyTimeRangeConfig !== undefined && ( + {withTimeRangeSelector && + applyTimeRangeConfig !== undefined && + jobs.length !== 0 ? ( <EuiFlexItem grow={false}> <EuiSwitch label={i18n.translate( @@ -256,7 +258,7 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({ data-test-subj="mlFlyoutJobSelectorSwitchApplyTimeRange" /> </EuiFlexItem> - )} + ) : null} </EuiFlexGroup> </EuiFlexItem> </EuiFlexGroup> @@ -278,19 +280,7 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({ </EuiFlyoutBody> <EuiFlyoutFooter> - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiButton - onClick={applySelection} - fill - isDisabled={newSelection.length === 0} - data-test-subj="mlFlyoutJobSelectorButtonApply" - > - {i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', { - defaultMessage: 'Apply', - })} - </EuiButton> - </EuiFlexItem> + <EuiFlexGroup justifyContent="spaceBetween"> <EuiFlexItem grow={false}> <EuiButtonEmpty iconType="cross" @@ -302,6 +292,20 @@ export const JobSelectorFlyoutContent: FC<JobSelectorFlyoutProps> = ({ })} </EuiButtonEmpty> </EuiFlexItem> + <EuiFlexItem grow={false}> + {jobs.length !== 0 ? ( + <EuiButton + onClick={applySelection} + fill + isDisabled={newSelection.length === 0} + data-test-subj="mlFlyoutJobSelectorButtonApply" + > + {i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', { + defaultMessage: 'Apply', + })} + </EuiButton> + ) : null} + </EuiFlexItem> </EuiFlexGroup> </EuiFlyoutFooter> </> diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js index 1afed1eaac8ca..b9086c31beded 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.js @@ -13,12 +13,11 @@ import { TimeRangeBar } from '../timerange_bar'; import { FormattedMessage } from '@kbn/i18n-react'; import { + EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiTabbedContent, - EuiCallOut, EuiButton, - EuiText, LEFT_ALIGNMENT, CENTER_ALIGNMENT, SortableProperties, @@ -265,17 +264,20 @@ export function JobSelectorTable({ <Fragment> <MlNodeAvailableWarningShared nodeAvailableCallback={setMlNodesAvailable} /> {jobs.length === 0 && ( - <EuiCallOut + <EuiEmptyPrompt + titleSize="xs" + iconType="warning" title={ - <FormattedMessage - id="xpack.ml.jobSelector.noJobsFoundTitle" - defaultMessage="No anomaly detection jobs found" - /> + <h4> + <FormattedMessage + id="xpack.ml.jobSelector.noJobsFoundTitle" + defaultMessage="No anomaly detection jobs found" + /> + </h4> } - iconType="iInCircle" - > - <EuiText textAlign="center"> + body={ <EuiButton + fill color="primary" onClick={navigateToWizard} disabled={mlCapabilities.canCreateJob === false || mlNodesAvailable === false} @@ -285,8 +287,8 @@ export function JobSelectorTable({ defaultMessage="Create job" /> </EuiButton> - </EuiText> - </EuiCallOut> + } + /> )} {jobs.length !== 0 && singleSelection === true && renderJobsTable()} {jobs.length !== 0 && !singleSelection && renderTabs()} diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index b9f17084bd81f..8578be7a57352 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -15,10 +15,7 @@ import { DEPLOYMENT_STATE, TRAINED_MODEL_TYPE, } from '@kbn/ml-trained-models-utils'; -import { - ELASTIC_MODEL_TAG, - MODEL_STATE, -} from '@kbn/ml-trained-models-utils/src/constants/trained_models'; +import { MODEL_STATE } from '@kbn/ml-trained-models-utils/src/constants/trained_models'; import { getAnalysisType, type DataFrameAnalysisConfigType, @@ -409,10 +406,7 @@ export function useModelActions({ icon: 'download', type: 'icon', isPrimary: true, - available: (item) => - canCreateTrainedModels && - item.tags.includes(ELASTIC_MODEL_TAG) && - item.state === MODEL_STATE.NOT_DOWNLOADED, + available: (item) => canCreateTrainedModels && item.state === MODEL_STATE.NOT_DOWNLOADED, enabled: (item) => !isLoading, onClick: async (item) => { onModelDownloadRequest(item.model_id); diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 79db82034b8d4..1080488a78895 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -17,13 +17,15 @@ import { EuiFlexGroup, EuiFlexItem, EuiHealth, + EuiIcon, EuiInMemoryTable, EuiLink, - type EuiSearchBarProps, + EuiProgress, EuiSpacer, + EuiSwitch, EuiTitle, EuiToolTip, - EuiProgress, + type EuiSearchBarProps, } from '@elastic/eui'; import { groupBy, isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; @@ -94,6 +96,7 @@ export type ModelItem = TrainedModelConfigResponse & { */ stateDescription?: string; recommended?: boolean; + supported: boolean; /** * Model name, e.g. elser */ @@ -129,6 +132,7 @@ export const getDefaultModelsListState = (): ListingPageUrlState => ({ pageSize: 10, sortField: modelIdColumnName, sortDirection: 'asc', + showAll: false, }); interface Props { @@ -286,9 +290,13 @@ export const ModelsList: FC<Props> = ({ ); const forDownload = await trainedModelsApiService.getTrainedModelDownloads(); const notDownloaded: ModelItem[] = forDownload - .filter(({ model_id: modelId, hidden, recommended }) => { - if (recommended && idMap.has(modelId)) { - idMap.get(modelId)!.recommended = true; + .filter(({ model_id: modelId, hidden, recommended, supported }) => { + if (idMap.has(modelId)) { + const model = idMap.get(modelId)!; + if (recommended) { + model.recommended = true; + } + model.supported = supported; } return !idMap.has(modelId) && !hidden; }) @@ -306,6 +314,7 @@ export const ModelsList: FC<Props> = ({ arch: modelDefinition.arch, softwareLicense: modelDefinition.license, licenseUrl: modelDefinition.licenseUrl, + supported: modelDefinition.supported, } as ModelItem; }); resultItems = [...resultItems, ...notDownloaded]; @@ -530,12 +539,6 @@ export const ModelsList: FC<Props> = ({ try { setIsLoading(true); await trainedModelsApiService.installElasticTrainedModelConfig(modelId); - displaySuccessToast( - i18n.translate('xpack.ml.trainedModels.modelsList.downloadSuccess', { - defaultMessage: '"{modelId}" model download has been started successfully.', - values: { modelId }, - }) - ); // Need to fetch model state updates await fetchModelsData(); } catch (e) { @@ -549,7 +552,7 @@ export const ModelsList: FC<Props> = ({ setIsLoading(true); } }, - [displayErrorToast, displaySuccessToast, fetchModelsData, trainedModelsApiService] + [displayErrorToast, fetchModelsData, trainedModelsApiService] ); /** @@ -633,26 +636,28 @@ export const ModelsList: FC<Props> = ({ }), truncateText: false, 'data-test-subj': 'mlModelsTableColumnDescription', - render: ({ description, recommended }: ModelItem) => { + render: ({ description, recommended, tags, supported }: ModelItem) => { if (!description) return null; const descriptionText = description.replace('(Tech Preview)', ''); - return recommended ? ( - <EuiToolTip - content={ - <FormattedMessage - id="xpack.ml.trainedModels.modelsList.recommendedDownloadContent" - defaultMessage="Recommended model version for your cluster's hardware configuration" - /> - } - > + + const tooltipContent = + supported === false ? ( + <FormattedMessage + id="xpack.ml.trainedModels.modelsList.notSupportedDownloadContent" + defaultMessage="Model version is not supported by your cluster's hardware configuration" + /> + ) : recommended === false ? ( + <FormattedMessage + id="xpack.ml.trainedModels.modelsList.notRecommendedDownloadContent" + defaultMessage="Model version is not optimized for your cluster's hardware configuration" + /> + ) : null; + + return tooltipContent ? ( + <EuiToolTip content={tooltipContent}> <> {descriptionText}  - <b> - <FormattedMessage - id="xpack.ml.trainedModels.modelsList.recommendedDownloadLabel" - defaultMessage="(Recommended)" - /> - </b> + <EuiIcon type={'warning'} color="warning" /> </> </EuiToolTip> ) : ( @@ -861,6 +866,14 @@ export const ModelsList: FC<Props> = ({ const isElserCalloutVisible = !isElserCalloutDismissed && items.findIndex((i) => i.model_id === ELSER_ID_V1) >= 0; + const tableItems = useMemo(() => { + if (pageState.showAll) { + return items; + } else { + return items.filter((item) => item.supported !== false); + } + }, [items, pageState.showAll]); + if (!isInitialized) return null; return ( @@ -868,8 +881,24 @@ export const ModelsList: FC<Props> = ({ <SavedObjectsWarning onCloseFlyout={fetchModelsData} forceRefresh={isLoading} /> <EuiFlexGroup justifyContent="spaceBetween"> {modelsStats ? ( - <EuiFlexItem grow={false}> - <StatsBar stats={modelsStats} dataTestSub={'mlInferenceModelsStatsBar'} /> + <EuiFlexItem> + <EuiFlexGroup alignItems="center"> + <EuiFlexItem grow={false}> + <StatsBar stats={modelsStats} dataTestSub={'mlInferenceModelsStatsBar'} /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiSwitch + label={ + <FormattedMessage + id="xpack.ml.trainedModels.modelsList.showAllLabel" + defaultMessage="Show all" + /> + } + checked={!!pageState.showAll} + onChange={(e) => updatePageState({ showAll: e.target.checked })} + /> + </EuiFlexItem> + </EuiFlexGroup> </EuiFlexItem> ) : null} <EuiFlexItem grow={false}> @@ -894,7 +923,7 @@ export const ModelsList: FC<Props> = ({ allowNeutralSort={false} columns={columns} itemIdToExpandedRowMap={itemIdToExpandedRowMap} - items={items} + items={tableItems} itemId={ModelsTableToConfigMapping.id} loading={isLoading} search={search} diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_initializer.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_initializer.tsx index 536084cc01496..93bf3110b3776 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_initializer.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_initializer.tsx @@ -101,57 +101,59 @@ export const AnomalyChartsInitializer: FC<AnomalyChartsInitializerProps> = ({ errors={jobIdsErrors} /> <EuiSpacer size="s" /> - <EuiForm> - <EuiFormRow - label={ - <FormattedMessage - id="xpack.ml.anomalyChartsEmbeddable.panelTitleLabel" - defaultMessage="Panel title" - /> - } - isInvalid={!isPanelTitleValid} - > - <EuiFieldText - data-test-subj="panelTitleInput" - id="panelTitle" - name="panelTitle" - value={panelTitle} - onChange={(e) => { - titleManuallyChanged.current = true; - setPanelTitle(e.target.value); - }} + {jobIds.length > 0 ? ( + <EuiForm> + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ml.anomalyChartsEmbeddable.panelTitleLabel" + defaultMessage="Panel title" + /> + } isInvalid={!isPanelTitleValid} - /> - </EuiFormRow> + > + <EuiFieldText + data-test-subj="panelTitleInput" + id="panelTitle" + name="panelTitle" + value={panelTitle} + onChange={(e) => { + titleManuallyChanged.current = true; + setPanelTitle(e.target.value); + }} + isInvalid={!isPanelTitleValid} + /> + </EuiFormRow> - <EuiFormRow - isInvalid={!isMaxSeriesToPlotValid} - error={ - !isMaxSeriesToPlotValid ? ( + <EuiFormRow + isInvalid={!isMaxSeriesToPlotValid} + error={ + !isMaxSeriesToPlotValid ? ( + <FormattedMessage + id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotError" + defaultMessage="Maximum number of series to plot must be between 1 and 50." + /> + ) : undefined + } + label={ <FormattedMessage - id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotError" - defaultMessage="Maximum number of series to plot must be between 1 and 50." + id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotLabel" + defaultMessage="Maximum number of series to plot" /> - ) : undefined - } - label={ - <FormattedMessage - id="xpack.ml.anomalyChartsEmbeddable.maxSeriesToPlotLabel" - defaultMessage="Maximum number of series to plot" + } + > + <EuiFieldNumber + data-test-subj="mlAnomalyChartsInitializerMaxSeries" + id="selectMaxSeriesToPlot" + name="selectMaxSeriesToPlot" + value={maxSeriesToPlot} + onChange={(e) => setMaxSeriesToPlot(parseInt(e.target.value, 10))} + min={1} + max={MAX_ANOMALY_CHARTS_ALLOWED} /> - } - > - <EuiFieldNumber - data-test-subj="mlAnomalyChartsInitializerMaxSeries" - id="selectMaxSeriesToPlot" - name="selectMaxSeriesToPlot" - value={maxSeriesToPlot} - onChange={(e) => setMaxSeriesToPlot(parseInt(e.target.value, 10))} - min={1} - max={MAX_ANOMALY_CHARTS_ALLOWED} - /> - </EuiFormRow> - </EuiForm> + </EuiFormRow> + </EuiForm> + ) : null} </EuiFlyoutBody> <EuiFlyoutFooter> diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx index 52c84770da12c..ceb22e0172c8d 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_initializer.tsx @@ -157,52 +157,58 @@ export const AnomalySwimlaneInitializer: FC<AnomalySwimlaneInitializerProps> = ( }} errors={jobIdsErrors} /> + {jobIds.length > 0 ? ( + <> + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ml.swimlaneEmbeddable.panelTitleLabel" + defaultMessage="Panel title" + /> + } + isInvalid={!isPanelTitleValid} + fullWidth + > + <EuiFieldText + id="panelTitle" + name="panelTitle" + value={panelTitle} + onChange={(e) => { + titleManuallyChanged.current = true; + setPanelTitle(e.target.value); + }} + isInvalid={!isPanelTitleValid} + fullWidth + /> + </EuiFormRow> - <EuiFormRow - label={ - <FormattedMessage - id="xpack.ml.swimlaneEmbeddable.panelTitleLabel" - defaultMessage="Panel title" - /> - } - isInvalid={!isPanelTitleValid} - fullWidth - > - <EuiFieldText - id="panelTitle" - name="panelTitle" - value={panelTitle} - onChange={(e) => { - titleManuallyChanged.current = true; - setPanelTitle(e.target.value); - }} - isInvalid={!isPanelTitleValid} - fullWidth - /> - </EuiFormRow> - - <EuiFormRow - label={ - <FormattedMessage - id="xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel" - defaultMessage="Swim lane type" - /> - } - fullWidth - > - <EuiButtonGroup - id="selectSwimlaneType" - name="selectSwimlaneType" - color="primary" - isFullWidth - legend={i18n.translate('xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel', { - defaultMessage: 'Swim lane type', - })} - options={swimlaneTypeOptions} - idSelected={swimlaneType} - onChange={(id) => setSwimlaneType(id as SwimlaneType)} - /> - </EuiFormRow> + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel" + defaultMessage="Swim lane type" + /> + } + fullWidth + > + <EuiButtonGroup + id="selectSwimlaneType" + name="selectSwimlaneType" + color="primary" + isFullWidth + legend={i18n.translate( + 'xpack.ml.swimlaneEmbeddable.setupModal.swimlaneTypeLabel', + { + defaultMessage: 'Swim lane type', + } + )} + options={swimlaneTypeOptions} + idSelected={swimlaneType} + onChange={(id) => setSwimlaneType(id as SwimlaneType)} + /> + </EuiFormRow> + </> + ) : null} {swimlaneType === SWIMLANE_TYPE.VIEW_BY && ( <> diff --git a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx index 4e2e338eee8ee..9f21ca3f4af75 100644 --- a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx +++ b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_initializer.tsx @@ -150,29 +150,31 @@ export const SingleMetricViewerInitializer: FC<SingleMetricViewerInitializerProp }} {...(errorMessage && { errors: [errorMessage] })} /> - <EuiFormRow - label={ - <FormattedMessage - id="xpack.ml.singleMetricViewerEmbeddable.panelTitleLabel" - defaultMessage="Panel title" - /> - } - isInvalid={!isPanelTitleValid} - fullWidth - > - <EuiFieldText - data-test-subj="panelTitleInput" - id="panelTitle" - name="panelTitle" - value={panelTitle} - onChange={(e) => { - titleManuallyChanged.current = true; - setPanelTitle(e.target.value); - }} + {job?.job_id && jobId && jobId === job.job_id ? ( + <EuiFormRow + label={ + <FormattedMessage + id="xpack.ml.singleMetricViewerEmbeddable.panelTitleLabel" + defaultMessage="Panel title" + /> + } isInvalid={!isPanelTitleValid} fullWidth - /> - </EuiFormRow> + > + <EuiFieldText + data-test-subj="panelTitleInput" + id="panelTitle" + name="panelTitle" + value={panelTitle} + onChange={(e) => { + titleManuallyChanged.current = true; + setPanelTitle(e.target.value); + }} + isInvalid={!isPanelTitleValid} + fullWidth + /> + </EuiFormRow> + ) : null} <EuiSpacer /> {job?.job_id && jobId && jobId === job.job_id ? ( <SeriesControls diff --git a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts index 85d11ebf983e5..33530bade5fcf 100644 --- a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts +++ b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts @@ -58,6 +58,7 @@ describe('modelsProvider', () => { config: { input: { field_names: ['text_field'] } }, description: 'Elastic Learned Sparse EncodeR v1 (Tech Preview)', hidden: true, + supported: false, model_id: '.elser_model_1', version: 1, modelName: 'elser', @@ -66,6 +67,7 @@ describe('modelsProvider', () => { { config: { input: { field_names: ['text_field'] } }, default: true, + supported: true, description: 'Elastic Learned Sparse EncodeR v2', model_id: '.elser_model_2', version: 2, @@ -79,6 +81,7 @@ describe('modelsProvider', () => { model_id: '.elser_model_2_linux-x86_64', os: 'Linux', recommended: true, + supported: true, version: 2, modelName: 'elser', type: ['elastic', 'pytorch', 'text_expansion'], @@ -88,6 +91,7 @@ describe('modelsProvider', () => { description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', model_id: '.multilingual-e5-small', default: true, + supported: true, version: 1, modelName: 'e5', license: 'MIT', @@ -102,6 +106,7 @@ describe('modelsProvider', () => { model_id: '.multilingual-e5-small_linux-x86_64', os: 'Linux', recommended: true, + supported: true, version: 1, modelName: 'e5', license: 'MIT', @@ -140,6 +145,7 @@ describe('modelsProvider', () => { config: { input: { field_names: ['text_field'] } }, description: 'Elastic Learned Sparse EncodeR v1 (Tech Preview)', hidden: true, + supported: false, model_id: '.elser_model_1', version: 1, modelName: 'elser', @@ -148,6 +154,7 @@ describe('modelsProvider', () => { { config: { input: { field_names: ['text_field'] } }, recommended: true, + supported: true, description: 'Elastic Learned Sparse EncodeR v2', model_id: '.elser_model_2', version: 2, @@ -163,12 +170,14 @@ describe('modelsProvider', () => { version: 2, modelName: 'elser', type: ['elastic', 'pytorch', 'text_expansion'], + supported: false, }, { config: { input: { field_names: ['text_field'] } }, description: 'E5 (EmbEddings from bidirEctional Encoder rEpresentations)', model_id: '.multilingual-e5-small', recommended: true, + supported: true, version: 1, modelName: 'e5', type: ['pytorch', 'text_embedding'], @@ -182,6 +191,7 @@ describe('modelsProvider', () => { 'E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimized for linux-x86_64', model_id: '.multilingual-e5-small_linux-x86_64', os: 'Linux', + supported: false, version: 1, modelName: 'e5', type: ['pytorch', 'text_embedding'], diff --git a/x-pack/plugins/ml/server/models/model_management/models_provider.ts b/x-pack/plugins/ml/server/models/model_management/models_provider.ts index 6f1cf3c39727c..3345c18ea2b55 100644 --- a/x-pack/plugins/ml/server/models/model_management/models_provider.ts +++ b/x-pack/plugins/ml/server/models/model_management/models_provider.ts @@ -476,6 +476,7 @@ export class ModelsProvider { const modelDefinitionResponse = { ...def, ...(recommended ? { recommended } : {}), + supported: !!def.default || recommended, model_id: modelId, }; diff --git a/x-pack/plugins/notifications/README.mdx b/x-pack/plugins/notifications/README.mdx index 75ea13570ec2b..d23e31f02ce75 100755 --- a/x-pack/plugins/notifications/README.mdx +++ b/x-pack/plugins/notifications/README.mdx @@ -18,10 +18,10 @@ The `start` function exposes the following interface: To use the exposed plugin start contract: -1. Make sure `notifications` is in your `optionalPlugins` in the `kibana.json` file: +1. Make sure `notifications` is in your `requiredPlugins` in the `kibana.jsonc` file: ```json5 -// <plugin>/kibana.json +// <plugin>/kibana.jsonc { "id": "...", "requiredPlugins": ["notifications"] diff --git a/x-pack/plugins/observability_solution/apm/common/data_source.ts b/x-pack/plugins/observability_solution/apm/common/data_source.ts index 217862e03e415..8a54757c17985 100644 --- a/x-pack/plugins/observability_solution/apm/common/data_source.ts +++ b/x-pack/plugins/observability_solution/apm/common/data_source.ts @@ -5,25 +5,4 @@ * 2.0. */ -import { ApmDocumentType } from './document_type'; -import { RollupInterval } from './rollup'; - -type AnyApmDocumentType = - | ApmDocumentType.ServiceTransactionMetric - | ApmDocumentType.TransactionMetric - | ApmDocumentType.TransactionEvent - | ApmDocumentType.ServiceDestinationMetric - | ApmDocumentType.ServiceSummaryMetric - | ApmDocumentType.ErrorEvent - | ApmDocumentType.SpanEvent; - -export interface ApmDataSource<TDocumentType extends AnyApmDocumentType = AnyApmDocumentType> { - rollupInterval: RollupInterval; - documentType: TDocumentType; -} - -export type ApmDataSourceWithSummary<T extends AnyApmDocumentType = AnyApmDocumentType> = - ApmDataSource<T> & { - hasDurationSummaryField: boolean; - hasDocs: boolean; - }; +export type { ApmDataSource, ApmDataSourceWithSummary } from '@kbn/apm-data-access-plugin/common'; diff --git a/x-pack/plugins/observability_solution/apm/common/document_type.ts b/x-pack/plugins/observability_solution/apm/common/document_type.ts index e8a29e8d08c43..6e9341a6d45ec 100644 --- a/x-pack/plugins/observability_solution/apm/common/document_type.ts +++ b/x-pack/plugins/observability_solution/apm/common/document_type.ts @@ -5,21 +5,8 @@ * 2.0. */ -export enum ApmDocumentType { - TransactionMetric = 'transactionMetric', - ServiceTransactionMetric = 'serviceTransactionMetric', - TransactionEvent = 'transactionEvent', - ServiceDestinationMetric = 'serviceDestinationMetric', - ServiceSummaryMetric = 'serviceSummaryMetric', - ErrorEvent = 'error', - SpanEvent = 'span', -} - -export type ApmServiceTransactionDocumentType = - | ApmDocumentType.ServiceTransactionMetric - | ApmDocumentType.TransactionMetric - | ApmDocumentType.TransactionEvent; - -export type ApmTransactionDocumentType = - | ApmDocumentType.TransactionMetric - | ApmDocumentType.TransactionEvent; +export { + ApmDocumentType, + type ApmServiceTransactionDocumentType, + type ApmTransactionDocumentType, +} from '@kbn/apm-data-access-plugin/common'; diff --git a/x-pack/plugins/observability_solution/apm/common/entities/types.ts b/x-pack/plugins/observability_solution/apm/common/entities/types.ts index f953fb5c593ed..bdca62bc66824 100644 --- a/x-pack/plugins/observability_solution/apm/common/entities/types.ts +++ b/x-pack/plugins/observability_solution/apm/common/entities/types.ts @@ -27,4 +27,5 @@ export interface EntityServiceListItem { environments: string[]; serviceName: string; agentName: AgentName; + hasLogMetrics: boolean; } diff --git a/x-pack/plugins/observability_solution/apm/common/es_fields/apm.ts b/x-pack/plugins/observability_solution/apm/common/es_fields/apm.ts index 539484fed182e..9ab614df857fe 100644 --- a/x-pack/plugins/observability_solution/apm/common/es_fields/apm.ts +++ b/x-pack/plugins/observability_solution/apm/common/es_fields/apm.ts @@ -4,192 +4,5 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export const TIMESTAMP = 'timestamp.us'; -export const AGENT = 'agent'; -export const AGENT_NAME = 'agent.name'; -export const AGENT_VERSION = 'agent.version'; -export const AGENT_ACTIVATION_METHOD = 'agent.activation_method'; -export const DESTINATION_ADDRESS = 'destination.address'; - -export const CLOUD = 'cloud'; -export const CLOUD_AVAILABILITY_ZONE = 'cloud.availability_zone'; -export const CLOUD_PROVIDER = 'cloud.provider'; -export const CLOUD_REGION = 'cloud.region'; -export const CLOUD_MACHINE_TYPE = 'cloud.machine.type'; -export const CLOUD_ACCOUNT_ID = 'cloud.account.id'; -export const CLOUD_INSTANCE_ID = 'cloud.instance.id'; -export const CLOUD_INSTANCE_NAME = 'cloud.instance.name'; -export const CLOUD_SERVICE_NAME = 'cloud.service.name'; - -export const EVENT_SUCCESS_COUNT = 'event.success_count'; - -export const SERVICE = 'service'; -export const SERVICE_NAME = 'service.name'; -export const SERVICE_ENVIRONMENT = 'service.environment'; -export const SERVICE_FRAMEWORK_NAME = 'service.framework.name'; -export const SERVICE_FRAMEWORK_VERSION = 'service.framework.version'; -export const SERVICE_LANGUAGE_NAME = 'service.language.name'; -export const SERVICE_LANGUAGE_VERSION = 'service.language.version'; -export const SERVICE_RUNTIME_NAME = 'service.runtime.name'; -export const SERVICE_RUNTIME_VERSION = 'service.runtime.version'; -export const SERVICE_NODE_NAME = 'service.node.name'; -export const SERVICE_VERSION = 'service.version'; -export const SERVICE_TARGET_TYPE = 'service.target.type'; -export const SERVICE_OVERFLOW_COUNT = 'service_transaction.aggregation.overflow_count'; - -export const URL_FULL = 'url.full'; -export const HTTP_REQUEST_METHOD = 'http.request.method'; -export const HTTP_RESPONSE_STATUS_CODE = 'http.response.status_code'; -export const USER_ID = 'user.id'; -export const USER_AGENT_ORIGINAL = 'user_agent.original'; -export const USER_AGENT_NAME = 'user_agent.name'; - -export const OBSERVER_HOSTNAME = 'observer.hostname'; -export const OBSERVER_LISTENING = 'observer.listening'; -export const PROCESSOR_EVENT = 'processor.event'; - -export const TRANSACTION_DURATION = 'transaction.duration.us'; -export const TRANSACTION_DURATION_HISTOGRAM = 'transaction.duration.histogram'; -export const TRANSACTION_DURATION_SUMMARY = 'transaction.duration.summary'; -export const TRANSACTION_TYPE = 'transaction.type'; -export const TRANSACTION_RESULT = 'transaction.result'; -export const TRANSACTION_NAME = 'transaction.name'; -export const TRANSACTION_ID = 'transaction.id'; -export const TRANSACTION_SAMPLED = 'transaction.sampled'; -export const TRANSACTION_PAGE_URL = 'transaction.page.url'; -export const TRANSACTION_FAILURE_COUNT = 'transaction.failure_count'; -export const TRANSACTION_SUCCESS_COUNT = 'transaction.success_count'; -export const TRANSACTION_OVERFLOW_COUNT = 'transaction.aggregation.overflow_count'; -// for transaction metrics -export const TRANSACTION_ROOT = 'transaction.root'; -export const TRANSACTION_PROFILER_STACK_TRACE_IDS = 'transaction.profiler_stack_trace_ids'; - -export const EVENT_OUTCOME = 'event.outcome'; - -export const TRACE_ID = 'trace.id'; - -export const SPAN_DURATION = 'span.duration.us'; -export const SPAN_TYPE = 'span.type'; -export const SPAN_SUBTYPE = 'span.subtype'; -export const SPAN_SELF_TIME_SUM = 'span.self_time.sum.us'; -export const SPAN_ACTION = 'span.action'; -export const SPAN_NAME = 'span.name'; -export const SPAN_ID = 'span.id'; -export const SPAN_DESTINATION_SERVICE_RESOURCE = 'span.destination.service.resource'; -export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT = - 'span.destination.service.response_time.count'; - -export const SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM = - 'span.destination.service.response_time.sum.us'; - -export const SPAN_LINKS = 'span.links'; -export const SPAN_LINKS_TRACE_ID = 'span.links.trace.id'; -export const SPAN_LINKS_SPAN_ID = 'span.links.span.id'; - -export const SPAN_COMPOSITE_COUNT = 'span.composite.count'; -export const SPAN_COMPOSITE_SUM = 'span.composite.sum.us'; -export const SPAN_COMPOSITE_COMPRESSION_STRATEGY = 'span.composite.compression_strategy'; - -export const SPAN_SYNC = 'span.sync'; - -// Parent ID for a transaction or span -export const PARENT_ID = 'parent.id'; - -export const ERROR_ID = 'error.id'; -export const ERROR_GROUP_ID = 'error.grouping_key'; -export const ERROR_GROUP_NAME = 'error.grouping_name'; -export const ERROR_CULPRIT = 'error.culprit'; -export const ERROR_LOG_LEVEL = 'error.log.level'; -export const ERROR_LOG_MESSAGE = 'error.log.message'; -export const ERROR_EXCEPTION = 'error.exception'; -export const ERROR_EXC_MESSAGE = 'error.exception.message'; // only to be used in es queries, since error.exception is now an array -export const ERROR_EXC_HANDLED = 'error.exception.handled'; // only to be used in es queries, since error.exception is now an array -export const ERROR_EXC_TYPE = 'error.exception.type'; -export const ERROR_PAGE_URL = 'error.page.url'; -export const ERROR_TYPE = 'error.type'; - -// METRICS -export const METRIC_SYSTEM_FREE_MEMORY = 'system.memory.actual.free'; -export const METRIC_SYSTEM_TOTAL_MEMORY = 'system.memory.total'; -export const METRIC_SYSTEM_CPU_PERCENT = 'system.cpu.total.norm.pct'; -export const METRIC_PROCESS_CPU_PERCENT = 'system.process.cpu.total.norm.pct'; -export const METRIC_CGROUP_MEMORY_LIMIT_BYTES = 'system.process.cgroup.memory.mem.limit.bytes'; -export const METRIC_CGROUP_MEMORY_USAGE_BYTES = 'system.process.cgroup.memory.mem.usage.bytes'; - -export const METRIC_JAVA_HEAP_MEMORY_MAX = 'jvm.memory.heap.max'; -export const METRIC_JAVA_HEAP_MEMORY_COMMITTED = 'jvm.memory.heap.committed'; -export const METRIC_JAVA_HEAP_MEMORY_USED = 'jvm.memory.heap.used'; -export const METRIC_JAVA_NON_HEAP_MEMORY_MAX = 'jvm.memory.non_heap.max'; -export const METRIC_JAVA_NON_HEAP_MEMORY_COMMITTED = 'jvm.memory.non_heap.committed'; -export const METRIC_JAVA_NON_HEAP_MEMORY_USED = 'jvm.memory.non_heap.used'; -export const METRIC_JAVA_THREAD_COUNT = 'jvm.thread.count'; -export const METRIC_JAVA_GC_COUNT = 'jvm.gc.count'; -export const METRIC_JAVA_GC_TIME = 'jvm.gc.time'; - -export const METRICSET_NAME = 'metricset.name'; -export const METRICSET_INTERVAL = 'metricset.interval'; - -export const LABEL_NAME = 'labels.name'; -export const LABEL_GC = 'labels.gc'; -export const LABEL_TYPE = 'labels.type'; -export const LABEL_TELEMETRY_AUTO_VERSION = 'labels.telemetry_auto_version'; -export const LABEL_LIFECYCLE_STATE = 'labels.lifecycle_state'; - -export const HOST = 'host'; -export const HOST_HOSTNAME = 'host.hostname'; // Do not use. Please use `HOST_NAME` instead. -export const HOST_NAME = 'host.name'; -export const HOST_OS_PLATFORM = 'host.os.platform'; -export const HOST_ARCHITECTURE = 'host.architecture'; -export const HOST_OS_VERSION = 'host.os.version'; - -export const CONTAINER_ID = 'container.id'; -export const CONTAINER = 'container'; -export const CONTAINER_IMAGE = 'container.image.name'; - -export const KUBERNETES = 'kubernetes'; -export const KUBERNETES_POD_NAME = 'kubernetes.pod.name'; -export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; - -export const FAAS_ID = 'faas.id'; -export const FAAS_NAME = 'faas.name'; -export const FAAS_COLDSTART = 'faas.coldstart'; -export const FAAS_TRIGGER_TYPE = 'faas.trigger.type'; -export const FAAS_DURATION = 'faas.duration'; -export const FAAS_COLDSTART_DURATION = 'faas.coldstart_duration'; -export const FAAS_BILLED_DURATION = 'faas.billed_duration'; - -// OpenTelemetry Metrics -export const METRIC_OTEL_SYSTEM_CPU_UTILIZATION = 'system.cpu.utilization'; -export const METRIC_OTEL_SYSTEM_MEMORY_UTILIZATION = 'system.memory.utilization'; - -export const METRIC_OTEL_JVM_PROCESS_CPU_PERCENT = 'process.runtime.jvm.cpu.utilization'; -export const METRIC_OTEL_JVM_PROCESS_MEMORY_USAGE = 'process.runtime.jvm.memory.usage'; -export const METRIC_OTEL_JVM_PROCESS_MEMORY_COMMITTED = 'process.runtime.jvm.memory.committed'; -export const METRIC_OTEL_JVM_PROCESS_MEMORY_LIMIT = 'process.runtime.jvm.memory.limit'; -export const METRIC_OTEL_JVM_PROCESS_THREADS_COUNT = 'process.runtime.jvm.threads.count'; -export const METRIC_OTEL_JVM_SYSTEM_CPU_PERCENT = 'process.runtime.jvm.system.cpu.utilization'; -export const METRIC_OTEL_JVM_GC_DURATION = 'process.runtime.jvm.gc.duration'; -export const VALUE_OTEL_JVM_PROCESS_MEMORY_HEAP = 'heap'; -export const VALUE_OTEL_JVM_PROCESS_MEMORY_NON_HEAP = 'non_heap'; - -// Metadata -export const TIER = '_tier'; -export const INDEX = '_index'; -export const DATA_STEAM_TYPE = 'data_stream.type'; - -// Mobile -export const NETWORK_CONNECTION_TYPE = 'network.connection.type'; -export const DEVICE_MODEL_IDENTIFIER = 'device.model.identifier'; -export const SESSION_ID = 'session.id'; -export const APP_LAUNCH_TIME = 'application.launch.time'; -export const EVENT_NAME = 'event.name'; - -// Location -export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code'; -export const CLIENT_GEO_REGION_ISO_CODE = 'client.geo.region_iso_code'; -export const CLIENT_GEO_COUNTRY_NAME = 'client.geo.country_name'; -export const CLIENT_GEO_CITY_NAME = 'client.geo.city_name'; -export const CLIENT_GEO_REGION_NAME = 'client.geo.region_name'; - -export const CHILD_ID = 'child.id'; +export * from '@kbn/apm-types/es_fields'; diff --git a/x-pack/plugins/observability_solution/apm/common/rollup.ts b/x-pack/plugins/observability_solution/apm/common/rollup.ts index 500337e3fc06b..d3bab49a01377 100644 --- a/x-pack/plugins/observability_solution/apm/common/rollup.ts +++ b/x-pack/plugins/observability_solution/apm/common/rollup.ts @@ -5,9 +5,4 @@ * 2.0. */ -export enum RollupInterval { - OneMinute = '1m', - TenMinutes = '10m', - SixtyMinutes = '60m', - None = 'none', -} +export { RollupInterval } from '@kbn/apm-data-access-plugin/common'; diff --git a/x-pack/plugins/observability_solution/apm/common/time_range_metadata.ts b/x-pack/plugins/observability_solution/apm/common/time_range_metadata.ts index f13ab5a89d6d1..7aa9d80a8906f 100644 --- a/x-pack/plugins/observability_solution/apm/common/time_range_metadata.ts +++ b/x-pack/plugins/observability_solution/apm/common/time_range_metadata.ts @@ -5,9 +5,4 @@ * 2.0. */ -import { ApmDataSource } from './data_source'; - -export interface TimeRangeMetadata { - isUsingServiceDestinationMetrics: boolean; - sources: Array<ApmDataSource & { hasDocs: boolean; hasDurationSummaryField: boolean }>; -} +export type { TimeRangeMetadata } from '@kbn/apm-data-access-plugin/common'; diff --git a/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/calculate_auto.js b/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/calculate_auto.js deleted file mode 100644 index bd4d6e51ccc0d..0000000000000 --- a/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/calculate_auto.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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'; -const d = moment.duration; - -const roundingRules = [ - [d(500, 'ms'), d(100, 'ms')], - [d(5, 'second'), d(1, 'second')], - [d(7.5, 'second'), d(5, 'second')], - [d(15, 'second'), d(10, 'second')], - [d(45, 'second'), d(30, 'second')], - [d(3, 'minute'), d(1, 'minute')], - [d(9, 'minute'), d(5, 'minute')], - [d(20, 'minute'), d(10, 'minute')], - [d(45, 'minute'), d(30, 'minute')], - [d(2, 'hour'), d(1, 'hour')], - [d(6, 'hour'), d(3, 'hour')], - [d(24, 'hour'), d(12, 'hour')], - [d(1, 'week'), d(1, 'd')], - [d(3, 'week'), d(1, 'week')], - [d(1, 'year'), d(1, 'month')], - [Infinity, d(1, 'year')], -]; - -const revRoundingRules = roundingRules.slice(0).reverse(); - -function find(rules, check, last) { - function pick(buckets, duration) { - const target = duration / buckets; - let lastResp = null; - - for (let i = 0; i < rules.length; i++) { - const rule = rules[i]; - const resp = check(rule[0], rule[1], target); - - if (resp == null) { - if (!last) continue; - if (lastResp) return lastResp; - break; - } - - if (!last) return resp; - lastResp = resp; - } - - // fallback to just a number of milliseconds, ensure ms is >= 1 - const ms = Math.max(Math.floor(target), 1); - return moment.duration(ms, 'ms'); - } - - return (buckets, duration) => { - const interval = pick(buckets, duration); - if (interval) return moment.duration(interval._data); - }; -} - -export const calculateAuto = { - near: find( - revRoundingRules, - function near(bound, interval, target) { - if (bound > target) return interval; - }, - true - ), - - lessThan: find(revRoundingRules, function lessThan(_bound, interval, target) { - if (interval < target) return interval; - }), - - atLeast: find(revRoundingRules, function atLeast(_bound, interval, target) { - if (interval <= target) return interval; - }), -}; diff --git a/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/index.ts b/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/index.ts index a2946137cf911..837b0295fa22b 100644 --- a/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/index.ts +++ b/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/index.ts @@ -5,25 +5,4 @@ * 2.0. */ -import moment from 'moment'; -import { calculateAuto } from './calculate_auto'; - -export function getBucketSize({ - start, - end, - numBuckets = 50, - minBucketSize, -}: { - start: number; - end: number; - numBuckets?: number; - minBucketSize?: number; -}) { - const duration = moment.duration(end - start, 'ms'); - const bucketSize = Math.max( - calculateAuto.near(numBuckets, duration)?.asSeconds() ?? 0, - minBucketSize || 1 - ); - - return { bucketSize, intervalString: `${bucketSize}s` }; -} +export { getBucketSize } from '@kbn/apm-data-access-plugin/common'; diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/custom_no_data_page/custom_no_data_page.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/custom_no_data_page/custom_no_data_page.cy.ts new file mode 100644 index 0000000000000..a246a2ccb7c34 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/custom_no_data_page/custom_no_data_page.cy.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. + */ + +describe('Custom no data page', () => { + beforeEach(() => { + cy.loginAsEditorUser(); + }); + + before(() => { + // make sure entity centric experience is disabled + cy.updateAdvancedSettings({ + 'observability:entityCentricExperience': false, + }); + }); + + after(() => { + cy.updateAdvancedSettings({ + 'observability:entityCentricExperience': false, + }); + }); + + it('shows the default no data screen when entity centric experience is disabled ', () => { + cy.visitKibana('/app/apm'); + cy.contains('Welcome to Elastic Observability!'); + }); + + it('shows the custom no data screen when entity centric experience is enabled', () => { + cy.updateAdvancedSettings({ + 'observability:entityCentricExperience': true, + }); + cy.visitKibana('/app/apm'); + cy.contains('Welcome to Elastic Observability!').should('not.exist'); + cy.contains('Detect and resolve problems with your application'); + cy.contains('Try collecting services from logs'); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/navigation.cy.ts b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/navigation.cy.ts index d1baefed136ae..97eb5cf1543f2 100644 --- a/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/navigation.cy.ts +++ b/x-pack/plugins/observability_solution/apm/ftr_e2e/cypress/e2e/navigation.cy.ts @@ -35,6 +35,9 @@ describe('When navigating between pages', () => { after(() => { synthtrace.clean(); + cy.updateAdvancedSettings({ + 'observability:entityCentricExperience': false, + }); }); beforeEach(() => { @@ -70,4 +73,14 @@ describe('When navigating between pages', () => { cy.get('@serviceIconsRequest.all').should('have.length', 1); cy.get('@apmPoliciesRequest.all').should('have.length', 1); }); + + it('should not show the custom no data screen', () => { + cy.updateAdvancedSettings({ + 'observability:entityCentricExperience': true, + }); + cy.visitKibana('/app/apm'); + cy.contains('Detect and resolve problems with your application').should('not.exist'); + cy.contains('Traces').click(); + cy.contains('Detect and resolve problems with your application').should('not.exist'); + }); }); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx index db893e717e099..d734da4fbf80f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/entities/charts/log_error_rate_chart.tsx @@ -68,7 +68,7 @@ export function LogErrorRateChart({ height }: { height: number }) { type: 'linemark', color: currentPeriodColor, title: i18n.translate('xpack.apm.logs.chart.logsErrorRate', { - defaultMessage: 'Log Error Rate', + defaultMessage: 'Log Error %', }), }, ]; @@ -80,7 +80,7 @@ export function LogErrorRateChart({ height }: { height: number }) { <EuiTitle size="xs"> <h2> {i18n.translate('xpack.apm.logErrorRate', { - defaultMessage: 'Log error rate', + defaultMessage: 'Log error %', })} </h2> </EuiTitle> diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx index 97cce9b757902..49856327dd703 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/add_apm_callout.tsx @@ -16,6 +16,7 @@ import { EuiTitle, EuiButtonEmpty, useEuiTheme, + EuiButtonIcon, } from '@elastic/eui'; import { apmLight } from '@kbn/shared-svg'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -23,7 +24,11 @@ import { useKibana } from '../../../../context/kibana_context/use_kibana'; import { ApmPluginStartDeps, ApmServices } from '../../../../plugin'; import { AddApmData } from '../../../shared/add_data_buttons/buttons'; -export function AddAPMCallOut() { +interface Props { + onClose: () => void; +} + +export function AddAPMCallOut({ onClose }: Props) { const { euiTheme } = useEuiTheme(); const { services } = useKibana<ApmPluginStartDeps & ApmServices>(); @@ -35,41 +40,56 @@ export function AddAPMCallOut() { return ( <EuiPanel color="subdued" hasShadow={false}> - <EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexStart"> - <EuiFlexItem grow={0}> - <EuiImage - css={{ - background: euiTheme.colors.emptyShade, - }} - width="160" - height="100" - size="m" - src={apmLight} - alt="apm-logo" - /> - </EuiFlexItem> - <EuiFlexItem grow={4}> - <EuiTitle size="xs"> - <h1> - <FormattedMessage - id="xpack.apm.addAPMCallOut.title" - defaultMessage="Detect and resolve issues faster with deep visibility into your application" + <EuiFlexGroup gutterSize="s"> + <EuiFlexItem> + <EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexStart"> + <EuiFlexItem grow={0}> + <EuiImage + css={{ + background: euiTheme.colors.emptyShade, + }} + width="160" + height="100" + size="m" + src={apmLight} + alt="apm-logo" /> - </h1> - </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={4}> + <EuiTitle size="xs"> + <h1> + <FormattedMessage + id="xpack.apm.addAPMCallOut.title" + defaultMessage="Detect and resolve issues faster with deep visibility into your application" + /> + </h1> + </EuiTitle> - <EuiSpacer size="m" /> + <EuiSpacer size="m" /> - <EuiText size="s"> - <p> - <FormattedMessage - id="xpack.apm.addAPMCallOut.description" - defaultMessage="Understanding your application performance, relationships and dependencies by - instrumenting with APM." + <EuiText size="s"> + <p> + <FormattedMessage + id="xpack.apm.addAPMCallOut.description" + defaultMessage="Understanding your application performance, relationships and dependencies by + instrumenting with APM." + /> + </p> + </EuiText> + <EuiSpacer size="s" /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiButtonIcon + data-test-subj="apmAddAPMCallOutButton" + iconType="cross" + onClick={onClose} /> - </p> - </EuiText> - <EuiSpacer size="s" /> + </EuiFlexItem> + </EuiFlexGroup> </EuiFlexItem> </EuiFlexGroup> <EuiFlexGroup alignItems="center" gutterSize="s" justifyContent="flexEnd"> @@ -83,6 +103,7 @@ export function AddAPMCallOut() { data-test-subj="apmAddApmCallOutLearnMoreButton" iconType="popout" iconSide="right" + target="_blank" href="https://www.elastic.co/observability/application-performance-monitoring" > {i18n.translate('xpack.apm.addAPMCallOut.linkToElasticcoButtonEmptyLabel', { diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx index d1f08b16eaf15..c89487b527fbe 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/entities/logs/logs_service_overview.tsx @@ -12,17 +12,29 @@ * 2.0. */ +import { + EuiCallOut, + EuiFlexGroup, + EuiFlexGroupProps, + EuiFlexItem, + EuiLink, + EuiLoadingSpinner, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -import { EuiFlexGroupProps, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { AnnotationsContextProvider } from '../../../../context/annotations/annotations_context'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; -import { useBreakpoints } from '../../../../hooks/use_breakpoints'; import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useBreakpoints } from '../../../../hooks/use_breakpoints'; import { useTimeRange } from '../../../../hooks/use_time_range'; -import { AddAPMCallOut } from './add_apm_callout'; -import { LogRateChart } from '../charts/log_rate_chart'; import { LogErrorRateChart } from '../charts/log_error_rate_chart'; +import { LogRateChart } from '../charts/log_rate_chart'; +import { AddAPMCallOut } from './add_apm_callout'; +import { useLocalStorage } from '../../../../hooks/use_local_storage'; +import { isPending, useFetcher } from '../../../../hooks/use_fetcher'; /** * The height a chart should be if it's next to a table with 5 rows and a title. * Add the height of the pagination row. @@ -32,6 +44,10 @@ const chartHeight = 400; export function LogsServiceOverview() { const { serviceName } = useApmServiceContext(); + const [isLogsApmCalloutEnabled, setIsLogsApmCalloutEnabled] = useLocalStorage( + 'apm.isLogsApmCalloutEnabled', + true + ); const { query: { environment, rangeFrom, rangeTo }, @@ -39,11 +55,28 @@ export function LogsServiceOverview() { const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { data, status } = useFetcher( + (callAPI) => { + return callAPI('GET /internal/apm/entities/services/{serviceName}/summary', { + params: { path: { serviceName }, query: { end, environment, start } }, + }); + }, + [end, environment, serviceName, start] + ); + const { isLarge } = useBreakpoints(); const isSingleColumn = isLarge; const rowDirection: EuiFlexGroupProps['direction'] = isSingleColumn ? 'column' : 'row'; + if (isPending(status)) { + return ( + <div style={{ textAlign: 'center' }}> + <EuiLoadingSpinner size="xl" /> + </div> + ); + } + return ( <AnnotationsContextProvider serviceName={serviceName} @@ -52,8 +85,60 @@ export function LogsServiceOverview() { end={end} > <ChartPointerEventContextProvider> - <AddAPMCallOut /> - <EuiSpacer size="l" /> + {isLogsApmCalloutEnabled ? ( + <> + <AddAPMCallOut + onClose={() => { + setIsLogsApmCalloutEnabled(false); + }} + /> + <EuiSpacer size="l" /> + </> + ) : null} + {data?.entity?.hasLogMetrics === false ? ( + <> + <EuiCallOut + title={i18n.translate( + 'xpack.apm.logsServiceOverview.euiCallOut.noLogMetricsHaveLabel', + { + defaultMessage: 'No log metrics have been detected against this service', + } + )} + color="warning" + iconType="warning" + > + <FormattedMessage + id="xpack.apm.logsServiceOverview.pleaseEnsureYouAreCallOutLabel" + defaultMessage="Please ensure you are surfacing {logLevelLink} in your logs to display log metrics. {learnMoreLink}" + values={{ + logLevelLink: ( + <EuiLink + data-test-subj="apmNotAvailableLogsMetricsLink" + href="https://www.elastic.co/guide/en/ecs/current/ecs-log.html#field-log-level" + target="_blank" + > + {i18n.translate('xpack.apm.logsServiceOverview.logLevelLink', { + defaultMessage: 'log.level', + })} + </EuiLink> + ), + learnMoreLink: ( + <EuiLink + data-test-subj="apmNotAvailableLogsMetricsLink" + href="https://ela.st/service-logs-level" + target="_blank" + > + {i18n.translate('xpack.apm.logsServiceOverview.learnMoreLink', { + defaultMessage: 'Learn more', + })} + </EuiLink> + ), + }} + /> + </EuiCallOut> + <EuiSpacer size="l" /> + </> + ) : null} <EuiFlexGroup direction="column" gutterSize="s"> <EuiFlexItem> diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/index.tsx index f1b38b652633f..e915ce01e24f6 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/index.tsx @@ -8,7 +8,6 @@ import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; -import { ApmDocumentType } from '../../../../../common/document_type'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; import { useApmParams } from '../../../../hooks/use_apm_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; @@ -24,20 +23,20 @@ import { ServiceInventoryFieldName, } from './table/multi_signal_services_table'; import { ServiceListItem } from '../../../../../common/service_inventory'; -import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_preferred_data_source_and_bucket_size'; -import { useProgressiveFetcher } from '../../../../hooks/use_progressive_fetcher'; import { NoEntitiesEmptyState } from './table/no_entities_empty_state'; import { Welcome } from '../../../shared/entity_enablement/welcome_modal'; -import { useServiceEcoTour } from '../../../../hooks/use_eco_tour'; import { useKibana } from '../../../../context/kibana_context/use_kibana'; import { ApmPluginStartDeps, ApmServices } from '../../../../plugin'; +import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context'; type MainStatisticsApiResponse = APIReturnType<'GET /internal/apm/entities/services'>; const INITIAL_PAGE_SIZE = 25; const INITIAL_SORT_DIRECTION = 'desc'; -const INITIAL_DATA: MainStatisticsApiResponse & { requestId: string } = { +type MainStatisticsApiResponseWithRequestId = MainStatisticsApiResponse & { requestId: string }; + +const INITIAL_DATA: MainStatisticsApiResponseWithRequestId = { services: [], requestId: '', }; @@ -84,10 +83,12 @@ function useServicesEntitiesMainStatisticsFetcher() { } function useServicesEntitiesDetailedStatisticsFetcher({ - mainStatisticsFetch, + mainStatisticsData, + mainStatisticsStatus, services, }: { - mainStatisticsFetch: ReturnType<typeof useServicesEntitiesMainStatisticsFetcher>; + mainStatisticsData: MainStatisticsApiResponseWithRequestId; + mainStatisticsStatus: FETCH_STATUS; services: ServiceListItem[]; }) { const { @@ -96,17 +97,7 @@ function useServicesEntitiesDetailedStatisticsFetcher({ const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const dataSourceOptions = usePreferredDataSourceAndBucketSize({ - start, - end, - kuery, - type: ApmDocumentType.ServiceTransactionMetric, - numBuckets: 20, - }); - - const { mainStatisticsData, mainStatisticsStatus } = mainStatisticsFetch; - - const timeseriesDataFetch = useProgressiveFetcher( + const timeseriesDataFetch = useFetcher( (callApmApi) => { const serviceNames = services.map(({ serviceName }) => serviceName); @@ -114,8 +105,7 @@ function useServicesEntitiesDetailedStatisticsFetcher({ start && end && serviceNames.length > 0 && - mainStatisticsStatus === FETCH_STATUS.SUCCESS && - dataSourceOptions + mainStatisticsStatus === FETCH_STATUS.SUCCESS ) { return callApmApi('POST /internal/apm/entities/services/detailed_statistics', { params: { @@ -124,9 +114,6 @@ function useServicesEntitiesDetailedStatisticsFetcher({ kuery, start, end, - documentType: dataSourceOptions.source.documentType, - rollupInterval: dataSourceOptions.source.rollupInterval, - bucketSizeInSeconds: dataSourceOptions.bucketSizeInSeconds, }, body: { // Service name is sorted to guarantee the same order every time this API is called so the result can be cached. @@ -149,8 +136,7 @@ export function MultiSignalInventory() { const [searchQuery, setSearchQuery] = React.useState(''); const { services } = useKibana<ApmPluginStartDeps & ApmServices>(); const { mainStatisticsData, mainStatisticsStatus } = useServicesEntitiesMainStatisticsFetcher(); - const { tourState, hideModal } = useServiceEcoTour(); - const mainStatisticsFetch = useServicesEntitiesMainStatisticsFetcher(); + const { tourState, updateTourState } = useEntityManagerEnablementContext(); const initialSortField = ServiceInventoryFieldName.Throughput; @@ -161,7 +147,8 @@ export function MultiSignalInventory() { }); const { timeseriesDataFetch } = useServicesEntitiesDetailedStatisticsFetcher({ - mainStatisticsFetch, + mainStatisticsData, + mainStatisticsStatus, services: mainStatisticsData.services, }); @@ -175,6 +162,10 @@ export function MultiSignalInventory() { } }, [services.telemetry, data?.hasData]); + function handleModalClose() { + updateTourState({ isModalVisible: false, isTourActive: true }); + } + return ( <> {!data?.hasData && status === FETCH_STATUS.SUCCESS ? ( @@ -219,8 +210,8 @@ export function MultiSignalInventory() { )} <Welcome isModalVisible={tourState.isModalVisible ?? false} - onClose={() => hideModal()} - onConfirm={() => hideModal()} + onClose={handleModalClose} + onConfirm={handleModalClose} /> </> ); diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx index 251bc3771f60c..45e8a9ca859a0 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/get_service_columns.tsx @@ -32,13 +32,14 @@ import { EnvironmentBadge } from '../../../../shared/environment_badge'; import { ServiceLink } from '../../../../shared/links/apm/service_link'; import { ListMetric } from '../../../../shared/list_metric'; import { ITableColumn } from '../../../../shared/managed_table'; -import { NotAvailableApmMetrics } from '../../../../shared/not_available_apm_metrics'; +import { NotAvailableApmMetrics } from '../../../../shared/not_available_popover/not_available_apm_metrics'; import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip'; import { ServiceInventoryFieldName } from './multi_signal_services_table'; import { EntityServiceListItem, SignalTypes } from '../../../../../../common/entities/types'; -import { isApmSignal } from '../../../../../utils/get_signal_type'; +import { isApmSignal, isLogsSignal } from '../../../../../utils/get_signal_type'; import { ColumnHeader } from './column_header'; import { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; +import { NotAvailableLogsMetrics } from '../../../../shared/not_available_popover/not_available_log_metrics'; type ServicesDetailedStatisticsAPIResponse = APIReturnType<'POST /internal/apm/entities/services/detailed_statistics'>; @@ -114,7 +115,7 @@ export function getServiceColumns({ ) : ( <ListMetric isLoading={timeseriesDataLoading} - series={timeseriesData?.currentPeriod?.apm[serviceName]?.latency} + series={timeseriesData?.currentPeriod?.[serviceName]?.latency} color={currentPeriodColor} valueLabel={asMillisecondDuration(metrics.latency)} hideSeries={!showWhenSmallOrGreaterThanLarge} @@ -140,7 +141,7 @@ export function getServiceColumns({ color={currentPeriodColor} valueLabel={asTransactionRate(metrics.throughput)} isLoading={timeseriesDataLoading} - series={timeseriesData?.currentPeriod?.apm[serviceName]?.throughput} + series={timeseriesData?.currentPeriod?.[serviceName]?.throughput} hideSeries={!showWhenSmallOrGreaterThanLarge} /> ); @@ -164,7 +165,7 @@ export function getServiceColumns({ color={currentPeriodColor} valueLabel={asPercent(metrics.failedTransactionRate, 1)} isLoading={timeseriesDataLoading} - series={timeseriesData?.currentPeriod?.apm[serviceName]?.transactionErrorRate} + series={timeseriesData?.currentPeriod?.[serviceName]?.failedTransactionRate} hideSeries={!showWhenSmallOrGreaterThanLarge} /> ); @@ -205,14 +206,17 @@ export function getServiceColumns({ sortable: true, dataType: 'number', align: RIGHT_ALIGNMENT, - render: (_, { metrics, serviceName }) => { - const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE); + render: (_, { metrics, serviceName, signalTypes, hasLogMetrics }) => { + if (isLogsSignal(signalTypes) && !hasLogMetrics) { + return <NotAvailableLogsMetrics />; + } + const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_RATE); return ( <ListMetric - isLoading={false} + isLoading={timeseriesDataLoading} color={currentPeriodColor} - series={timeseriesData?.currentPeriod?.logRate[serviceName] ?? []} + series={timeseriesData?.currentPeriod?.[serviceName]?.logRate} valueLabel={asDecimalOrInteger(metrics.logRate)} hideSeries={!showWhenSmallOrGreaterThanLarge} /> @@ -224,7 +228,7 @@ export function getServiceColumns({ name: ( <ColumnHeader label={i18n.translate('xpack.apm.multiSignal.servicesTable.logErrorRate', { - defaultMessage: 'Log error rate', + defaultMessage: 'Log error %', })} formula={getMetricsFormula(ChartMetricType.LOG_ERROR_RATE)} toolTip={ @@ -254,14 +258,18 @@ export function getServiceColumns({ sortable: true, dataType: 'number', align: RIGHT_ALIGNMENT, - render: (_, { metrics, serviceName }) => { + render: (_, { metrics, serviceName, signalTypes, hasLogMetrics }) => { + if (isLogsSignal(signalTypes) && !hasLogMetrics) { + return <NotAvailableLogsMetrics />; + } + const { currentPeriodColor } = getTimeSeriesColor(ChartType.LOG_ERROR_RATE); return ( <ListMetric - isLoading={false} + isLoading={timeseriesDataLoading} color={currentPeriodColor} - series={timeseriesData?.currentPeriod?.logErrorRate[serviceName] ?? []} + series={timeseriesData?.currentPeriod?.[serviceName]?.logErrorRate} valueLabel={asPercent(metrics.logErrorRate, 1)} hideSeries={!showWhenSmallOrGreaterThanLarge} /> diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/no_entities_empty_state.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/no_entities_empty_state.tsx index af97ea3093aaf..45e8b2ab8a0c9 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/no_entities_empty_state.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/multi_signal_inventory/table/no_entities_empty_state.tsx @@ -29,8 +29,10 @@ import { AssociateServiceLogs, CollectServiceLogs, } from '../../../../shared/add_data_buttons/buttons'; +import { useBreakpoints } from '../../../../../hooks/use_breakpoints'; export function NoEntitiesEmptyState() { + const { isLarge } = useBreakpoints(); const { services } = useKibana<ApmPluginStartDeps & ApmServices>(); const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage( 'apm.uiNewExperienceCallout', @@ -50,6 +52,34 @@ export function NoEntitiesEmptyState() { return ( <EuiFlexGroup direction="column"> + {!userHasDismissedCallout && ( + <EuiFlexItem> + <EuiCallOut + css={{ textAlign: 'left' }} + onDismiss={() => setUserHasDismissedCallout(true)} + title={i18n.translate('xpack.apm.noEntitiesEmptyState.callout.title', { + defaultMessage: 'Trying for the first time?', + })} + > + <p> + {i18n.translate('xpack.apm.noEntitiesEmptyState.description', { + defaultMessage: + 'It can take up to a couple of minutes for your services to show. Try refreshing the page in a minute.', + })} + </p> + <EuiLink + external + target="_blank" + data-test-subj="apmNewExperienceEmptyStateLink" + href="https://ela.st/elastic-entity-model-first-time" + > + {i18n.translate('xpack.apm.noEntitiesEmptyState.learnMore.link', { + defaultMessage: 'Learn more', + })} + </EuiLink> + </EuiCallOut> + </EuiFlexItem> + )} <EuiFlexItem> <EuiEmptyPrompt hasShadow={false} @@ -63,7 +93,7 @@ export function NoEntitiesEmptyState() { })} </h2> } - layout="horizontal" + layout={isLarge ? 'vertical' : 'horizontal'} color="plain" body={ <> @@ -94,46 +124,17 @@ export function NoEntitiesEmptyState() { reportButtonClick('add_apm_agent'); }} /> - <AssociateServiceLogs + <CollectServiceLogs onClick={() => { - reportButtonClick('associate_existing_service_logs'); + reportButtonClick('collect_new_service_logs'); }} /> - <CollectServiceLogs + <AssociateServiceLogs onClick={() => { - reportButtonClick('collect_new_service_logs'); + reportButtonClick('associate_existing_service_logs'); }} /> </EuiFlexGroup> - - {!userHasDismissedCallout && ( - <EuiFlexItem> - <EuiCallOut - css={{ textAlign: 'left' }} - onDismiss={() => setUserHasDismissedCallout(true)} - title={i18n.translate('xpack.apm.noEntitiesEmptyState.callout.title', { - defaultMessage: 'Trying for the first time?', - })} - > - <p> - {i18n.translate('xpack.apm.noEntitiesEmptyState.description', { - defaultMessage: - 'It can take up to a couple of minutes for your services to show. Try refreshing the page in a minute. ', - })} - </p> - <EuiLink - external - target="_blank" - data-test-subj="apmNewExperienceEmptyStateLink" - href="https://ela.st/elastic-entity-model-first-time" - > - {i18n.translate('xpack.apm.noEntitiesEmptyState.learnMore.link', { - defaultMessage: 'Learn more', - })} - </EuiLink> - </EuiCallOut> - </EuiFlexItem> - )} </EuiFlexGroup> } /> diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/app_root/apm_header_action_menu/add_data_context_menu.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/app_root/apm_header_action_menu/add_data_context_menu.tsx index edee8c419eb33..d94fc86ecf73b 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/app_root/apm_header_action_menu/add_data_context_menu.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/app_root/apm_header_action_menu/add_data_context_menu.tsx @@ -14,7 +14,6 @@ import { import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import { useServiceEcoTour } from '../../../../hooks/use_eco_tour'; import { useKibana } from '../../../../context/kibana_context/use_kibana'; import { ApmPluginStartDeps, ApmServices } from '../../../../plugin'; import { EntityInventoryAddDataParams } from '../../../../services/telemetry'; @@ -24,6 +23,7 @@ import { addApmData, } from '../../../shared/add_data_buttons/buttons'; import { ServiceEcoTour } from '../../../shared/entity_enablement/service_eco_tour'; +import { useEntityManagerEnablementContext } from '../../../../context/entity_manager_context/use_entity_manager_enablement_context'; const addData = i18n.translate('xpack.apm.addDataContextMenu.link', { defaultMessage: 'Add data', @@ -31,7 +31,7 @@ const addData = i18n.translate('xpack.apm.addDataContextMenu.link', { export function AddDataContextMenu() { const [popoverOpen, setPopoverOpen] = useState(false); - const { tourState, hideTour } = useServiceEcoTour(); + const { tourState, updateTourState } = useEntityManagerEnablementContext(); const { services } = useKibana<ApmPluginStartDeps & ApmServices>(); const { core: { @@ -94,7 +94,7 @@ export function AddDataContextMenu() { ]; const handleTourClose = () => { - hideTour(); + updateTourState({ isTourActive: false }); setPopoverOpen(false); }; return ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/entities/logs_service_details/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/entities/logs_service_details/index.tsx index 90cf280688eca..4971414440aa0 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/entities/logs_service_details/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/entities/logs_service_details/index.tsx @@ -19,6 +19,7 @@ import { ServiceDashboards } from '../../../app/service_dashboards'; import { ServiceLogs } from '../../../app/service_logs'; import { LogsServiceOverview } from '../../../app/entities/logs/logs_service_overview'; import { RedirectToDefaultLogsServiceRouteView } from '../../service_detail/redirect_to_default_service_route_view'; +import { SearchBar } from '../../../shared/search_bar/search_bar'; export function page({ title, @@ -29,13 +30,7 @@ export function page({ title: string; tabKey: React.ComponentProps<typeof LogsServiceTemplate>['selectedTabKey']; element: React.ReactElement<any, any>; - searchBarOptions?: { - showUnifiedSearchBar?: boolean; - showTransactionTypeSelector?: boolean; - showTimeComparison?: boolean; - showMobileFilters?: boolean; - hidden?: boolean; - }; + searchBarOptions?: React.ComponentProps<typeof SearchBar>; }): { element: React.ReactElement<any, any>; } { @@ -101,6 +96,7 @@ export const logsServiceDetailsRoute = { }), searchBarOptions: { showUnifiedSearchBar: true, + showQueryInput: false, }, }), params: t.partial({ diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx index 892d17490e3c0..257406ce1a8fc 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/apm_main_template.tsx @@ -120,6 +120,7 @@ export function ApmMainTemplate({ const hasApmIntegrations = !!fleetApmPoliciesData?.hasApmPolicies; const showCustomEmptyState = !hasApmData && + !isLoading && isEntityCentricExperienceSettingEnabled && serviceInventoryViewLocalStorageSetting === ServiceInventoryView.classic; diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx index 71aca162c1ae6..1588b6ffafece 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/entities/logs_service_template/index.tsx @@ -15,7 +15,6 @@ import { ServiceAnomalyTimeseriesContextProvider } from '../../../../../context/ import { useApmParams } from '../../../../../hooks/use_apm_params'; import { useApmRouter } from '../../../../../hooks/use_apm_router'; import { useTimeRange } from '../../../../../hooks/use_time_range'; -import { MobileSearchBar } from '../../../../app/mobile/search_bar'; import { SearchBar } from '../../../../shared/search_bar/search_bar'; import { ServiceIcons } from '../../../../shared/service_icons'; import { TechnicalPreviewBadge } from '../../../../shared/technical_preview_badge'; @@ -30,7 +29,7 @@ interface Props { title: string; children: React.ReactChild; selectedTabKey: Tab['key']; - searchBarOptions?: React.ComponentProps<typeof MobileSearchBar>; + searchBarOptions?: React.ComponentProps<typeof SearchBar>; } export function LogsServiceTemplate(props: Props) { diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx index b150aee11a888..0617e900c601f 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/routing/templates/service_group_template.tsx @@ -101,7 +101,7 @@ export function ServiceGroupTemplate({ ] : []), { - title: selectedTab.label, + title: selectedTab.breadcrumbLabel || selectedTab.label, href: selectedTab.href, } as { title: string; href: string }, ] @@ -147,6 +147,7 @@ export function ServiceGroupTemplate({ type ServiceGroupContextTab = NonNullable<EuiPageHeaderProps['tabs']>[0] & { key: 'service-inventory' | 'service-map'; + breadcrumbLabel?: string; }; function useTabs(selectedTab: ServiceGroupContextTab['key']) { @@ -157,6 +158,9 @@ function useTabs(selectedTab: ServiceGroupContextTab['key']) { const tabs: ServiceGroupContextTab[] = [ { key: 'service-inventory', + breadcrumbLabel: i18n.translate('xpack.apm.serviceGroup.serviceInventory', { + defaultMessage: 'Inventory', + }), label: ( <EuiFlexGroup justifyContent="flexStart" alignItems="baseline" gutterSize="s"> <EuiFlexItem grow={false}> @@ -184,9 +188,10 @@ function useTabs(selectedTab: ServiceGroupContextTab['key']) { return tabs .filter((t) => !t.hidden) - .map(({ href, key, label }) => ({ + .map(({ href, key, label, breadcrumbLabel }) => ({ href, label, isSelected: key === selectedTab, + breadcrumbLabel, })); } diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/add_data_buttons/buttons.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/add_data_buttons/buttons.tsx index b11e0d6e8e69d..db2cb9d41237b 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/add_data_buttons/buttons.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/add_data_buttons/buttons.tsx @@ -4,10 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; + +// Disabling it for now until the EUI team fixes it +/* eslint-disable @elastic/eui/href-or-on-click */ + import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useKibana } from '../../../context/kibana_context/use_kibana'; +import React from 'react'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; export const addApmData = { @@ -40,28 +43,30 @@ export function AddApmData({ }) { const { core } = useApmPluginContext(); const { basePath } = core.http; - const { - application: { navigateToUrl }, - } = useKibana().services; - function handleClick() { - navigateToUrl(basePath.prepend(addApmData.link)); - onClick?.(); - } return ( - <EuiButton data-test-subj={props['data-test-subj']} size="s" onClick={handleClick}> + <EuiButton + data-test-subj={props['data-test-subj']} + size="s" + onClick={onClick} + href={basePath.prepend(addApmData.link)} + > {addApmData.name} </EuiButton> ); } export function AssociateServiceLogs({ onClick }: { onClick?: () => void }) { - function handleClick() { - window.open(associateServiceLogs.link, '_blank'); - onClick?.(); - } return ( - <EuiButton data-test-subj="associateServiceLogsButton" size="s" onClick={handleClick}> + <EuiButton + data-test-subj="associateServiceLogsButton" + size="s" + onClick={onClick} + href={associateServiceLogs.link} + target="_blank" + iconType="popout" + iconSide="right" + > {associateServiceLogs.name} </EuiButton> ); @@ -70,16 +75,14 @@ export function AssociateServiceLogs({ onClick }: { onClick?: () => void }) { export function CollectServiceLogs({ onClick }: { onClick?: () => void }) { const { core } = useApmPluginContext(); const { basePath } = core.http; - const { - application: { navigateToUrl }, - } = useKibana().services; - function handleClick() { - navigateToUrl(basePath.prepend(collectServiceLogs.link)); - onClick?.(); - } return ( - <EuiButton data-test-subj="collectServiceLogsButton" size="s" onClick={handleClick}> + <EuiButton + data-test-subj="collectServiceLogsButton" + size="s" + onClick={onClick} + href={basePath.prepend(collectServiceLogs.link)} + > {collectServiceLogs.name} </EuiButton> ); diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx index 00195e0352856..62f5cf708bcf7 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/index.tsx @@ -6,7 +6,6 @@ */ import React, { useState } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { ERROR_USER_NOT_AUTHORIZED } from '@kbn/entityManager-plugin/public'; import useToggle from 'react-use/lib/useToggle'; import { EuiButtonIcon, @@ -22,18 +21,22 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; +import { EntityManagerUnauthorizedError } from '@kbn/entityManager-plugin/public'; import { TechnicalPreviewBadge } from '../technical_preview_badge'; import { ApmPluginStartDeps } from '../../../plugin'; import { useEntityManagerEnablementContext } from '../../../context/entity_manager_context/use_entity_manager_enablement_context'; import { FeedbackModal } from './feedback_modal'; import { ServiceInventoryView } from '../../../context/entity_manager_context/entity_manager_context'; import { Unauthorized } from './unauthorized_modal'; -import { useServiceEcoTour } from '../../../hooks/use_eco_tour'; +import { useLocalStorage } from '../../../hooks/use_local_storage'; export function EntityEnablement({ label, tooltip }: { label: string; tooltip?: string }) { - const [isFeedbackModalVisible, setsIsFeedbackModalVisible] = useState(false); + const [isFeedbackModalVisible, setsIsFeedbackModalVisible] = useLocalStorage<boolean | undefined>( + 'apm.isFeedbackModalVisible', + undefined + ); + const [isUnauthorizedModalVisible, setsIsUnauthorizedModalVisible] = useState(false); - const { tourState, showModal } = useServiceEcoTour(); const { services: { entityManager }, @@ -46,6 +49,8 @@ export function EntityEnablement({ label, tooltip }: { label: string; tooltip?: refetch, setServiceInventoryViewLocalStorageSetting, isEntityCentricExperienceViewEnabled, + tourState, + updateTourState, } = useEntityManagerEnablementContext(); const [isPopoverOpen, togglePopover] = useToggle(false); @@ -53,14 +58,16 @@ export function EntityEnablement({ label, tooltip }: { label: string; tooltip?: const handleRestoreView = async () => { setServiceInventoryViewLocalStorageSetting(ServiceInventoryView.classic); - setsIsFeedbackModalVisible(true); + if (isFeedbackModalVisible === undefined) { + setsIsFeedbackModalVisible(true); + } }; const handleEnablement = async () => { if (isEntityManagerEnabled) { setServiceInventoryViewLocalStorageSetting(ServiceInventoryView.entity); if (tourState.isModalVisible === undefined) { - showModal(); + updateTourState({ isModalVisible: true }); } return; } @@ -73,20 +80,20 @@ export function EntityEnablement({ label, tooltip }: { label: string; tooltip?: setServiceInventoryViewLocalStorageSetting(ServiceInventoryView.entity); if (tourState.isModalVisible === undefined) { - showModal(); + updateTourState({ isModalVisible: true }); } refetch(); } else { - if (response.reason === ERROR_USER_NOT_AUTHORIZED) { - setIsLoading(false); - setsIsUnauthorizedModalVisible(true); - return; - } - throw new Error(response.message); } } catch (error) { setIsLoading(false); + + if (error instanceof EntityManagerUnauthorizedError) { + setsIsUnauthorizedModalVisible(true); + return; + } + const err = error as Error | IHttpFetchError<ResponseErrorBody>; notifications.toasts.danger({ title: i18n.translate('xpack.apm.eemEnablement.errorTitle', { diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/service_eco_tour.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/service_eco_tour.tsx index caeaaaf9114ae..3a8a7272af46b 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/service_eco_tour.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/entity_enablement/service_eco_tour.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiText, EuiTourStep } from '@elastic/eui'; -import { useServiceEcoTour } from '../../../hooks/use_eco_tour'; +import { useEntityManagerEnablementContext } from '../../../context/entity_manager_context/use_entity_manager_enablement_context'; export function ServiceEcoTour({ children, @@ -17,7 +17,7 @@ export function ServiceEcoTour({ children: React.ReactElement; onFinish: () => void; }) { - const { tourState } = useServiceEcoTour(); + const { tourState } = useEntityManagerEnablementContext(); return ( <EuiTourStep diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx index 135848bae13d4..8e3752e5ce0bf 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/environment_badge/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { ItemsBadge } from '../item_badge'; -import { NotAvailableEnvironment } from '../not_available_environment'; +import { NotAvailableEnvironment } from '../not_available_popover/not_available_environment'; interface Props { environments: string[]; diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_apm_metrics.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_apm_metrics.tsx similarity index 81% rename from x-pack/plugins/observability_solution/apm/public/components/shared/not_available_apm_metrics.tsx rename to x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_apm_metrics.tsx index c4d2eb8563af6..66c466502bdd1 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_apm_metrics.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_apm_metrics.tsx @@ -7,10 +7,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { PopoverBadge } from './popover_badge'; -import { useKibana } from '../../context/kibana_context/use_kibana'; -import { ApmPluginStartDeps, ApmServices } from '../../plugin'; -import { AddApmData } from './add_data_buttons/buttons'; +import { PopoverBadge } from '../popover_badge'; +import { useKibana } from '../../../context/kibana_context/use_kibana'; +import { ApmPluginStartDeps, ApmServices } from '../../../plugin'; +import { AddApmData } from '../add_data_buttons/buttons'; export function NotAvailableApmMetrics() { const { services } = useKibana<ApmPluginStartDeps & ApmServices>(); diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_environment.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_environment.tsx similarity index 92% rename from x-pack/plugins/observability_solution/apm/public/components/shared/not_available_environment.tsx rename to x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_environment.tsx index aecc9f2586e71..af4cdd58ec1f9 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_environment.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_environment.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { EuiCode, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { PopoverBadge } from './popover_badge'; +import { PopoverBadge } from '../popover_badge'; -export const NotAvailableEnvironment = () => { +export function NotAvailableEnvironment() { return ( <PopoverBadge title={i18n.translate('xpack.apm.servicesTable.notAvailableEnv.title', { @@ -39,4 +39,4 @@ export const NotAvailableEnvironment = () => { } /> ); -}; +} diff --git a/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_log_metrics.tsx b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_log_metrics.tsx new file mode 100644 index 0000000000000..480795c533944 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/public/components/shared/not_available_popover/not_available_log_metrics.tsx @@ -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 { EuiLink } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { PopoverBadge } from '../popover_badge'; + +export function NotAvailableLogsMetrics() { + return ( + <PopoverBadge + title={i18n.translate('xpack.apm.servicesTable.notAvailableLogsMetrics.title', { + defaultMessage: 'Want to see more?', + })} + content={ + <FormattedMessage + id="xpack.apm.servicesTable.notAvailableLogsMetrics.content" + defaultMessage="In order to see log metrics against this service, please declare {logLevelLink} in your logs." + values={{ + logLevelLink: ( + <EuiLink + data-test-subj="apmNotAvailableLogsMetricsLink" + href="https://www.elastic.co/guide/en/ecs/current/ecs-log.html#field-log-level" + target="_blank" + > + {i18n.translate( + 'xpack.apm.servicesTable.notAvailableLogsMetrics.content.logLevelLink', + { defaultMessage: 'log.level' } + )} + </EuiLink> + ), + }} + /> + } + footer={ + <EuiLink + data-test-subj="apmNotAvailableLogsMetricsLink" + href="https://ela.st/service-logs-level" + target="_blank" + > + {i18n.translate('xpack.apm.servicesTable.notAvailableLogsMetrics.footer.learnMore', { + defaultMessage: 'Learn more', + })} + </EuiLink> + } + /> + ); +} diff --git a/x-pack/plugins/observability_solution/apm/public/context/entity_manager_context/entity_manager_context.tsx b/x-pack/plugins/observability_solution/apm/public/context/entity_manager_context/entity_manager_context.tsx index f68ca17bb8dad..e8c297cb13a92 100644 --- a/x-pack/plugins/observability_solution/apm/public/context/entity_manager_context/entity_manager_context.tsx +++ b/x-pack/plugins/observability_solution/apm/public/context/entity_manager_context/entity_manager_context.tsx @@ -24,6 +24,8 @@ export interface EntityManagerEnablementContextValue { serviceInventoryViewLocalStorageSetting: ServiceInventoryView; setServiceInventoryViewLocalStorageSetting: (view: ServiceInventoryView) => void; isEntityCentricExperienceViewEnabled: boolean; + tourState: TourState; + updateTourState: (newState: Partial<TourState>) => void; } export enum ServiceInventoryView { @@ -35,6 +37,15 @@ export const EntityManagerEnablementContext = createContext( {} as EntityManagerEnablementContextValue ); +interface TourState { + isModalVisible?: boolean; + isTourActive: boolean; +} +const TOUR_INITIAL_STATE: TourState = { + isModalVisible: undefined, + isTourActive: false, +}; + export function EntityManagerEnablementContextProvider({ children, }: { @@ -43,6 +54,7 @@ export function EntityManagerEnablementContextProvider({ const { core } = useApmPluginContext(); const { services } = useKibana<ApmPluginStartDeps & ApmServices>(); const { isEnabled: isEntityManagerEnabled, status, refetch } = useEntityManager(); + const [tourState, setTourState] = useLocalStorage('apm.serviceEcoTour', TOUR_INITIAL_STATE); const [serviceInventoryViewLocalStorageSetting, setServiceInventoryViewLocalStorageSetting] = useLocalStorage(SERVICE_INVENTORY_STORAGE_KEY, ServiceInventoryView.classic); @@ -57,6 +69,19 @@ export function EntityManagerEnablementContextProvider({ serviceInventoryViewLocalStorageSetting === ServiceInventoryView.entity && isEntityCentricExperienceSettingEnabled; + function handleServiceInventoryViewChange(nextView: ServiceInventoryView) { + setServiceInventoryViewLocalStorageSetting(nextView); + // Updates the telemetry context variable every time the user switches views + serviceInventoryViewType$.next({ serviceInventoryViewType: nextView }); + services.telemetry.reportEntityExperienceStatusChange({ + status: nextView === ServiceInventoryView.entity ? 'enabled' : 'disabled', + }); + } + + function handleTourStateUpdate(newTourState: Partial<TourState>) { + setTourState({ ...tourState, ...newTourState }); + } + return ( <EntityManagerEnablementContext.Provider value={{ @@ -65,15 +90,10 @@ export function EntityManagerEnablementContextProvider({ isEnablementPending: status === ENTITY_FETCH_STATUS.LOADING, refetch, serviceInventoryViewLocalStorageSetting, - setServiceInventoryViewLocalStorageSetting: (nextView) => { - setServiceInventoryViewLocalStorageSetting(nextView); - // Updates the telemetry context variable every time the user switches views - serviceInventoryViewType$.next({ serviceInventoryViewType: nextView }); - services.telemetry.reportEntityExperienceStatusChange({ - status: nextView === ServiceInventoryView.entity ? 'enabled' : 'disabled', - }); - }, + setServiceInventoryViewLocalStorageSetting: handleServiceInventoryViewChange, isEntityCentricExperienceViewEnabled, + tourState, + updateTourState: handleTourStateUpdate, }} > {children} diff --git a/x-pack/plugins/observability_solution/apm/public/hooks/use_eco_tour.tsx b/x-pack/plugins/observability_solution/apm/public/hooks/use_eco_tour.tsx deleted file mode 100644 index 7e2d5566b0a01..0000000000000 --- a/x-pack/plugins/observability_solution/apm/public/hooks/use_eco_tour.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useLocalStorage } from './use_local_storage'; - -type TourState = 'isModalVisible' | 'isTourActive'; - -const INITIAL_STATE: Record<TourState, boolean | undefined> = { - isModalVisible: undefined, - isTourActive: true, -}; - -export function useServiceEcoTour() { - const [tourState, setTourState] = useLocalStorage('apm.serviceEcoTour', INITIAL_STATE); - - return { - tourState, - hideModal: () => setTourState({ ...tourState, isModalVisible: false }), - showModal: () => setTourState({ ...tourState, isModalVisible: true }), - hideTour: () => setTourState({ ...tourState, isTourActive: false }), - }; -} diff --git a/x-pack/plugins/observability_solution/apm/public/hooks/use_preferred_data_source_and_bucket_size.ts b/x-pack/plugins/observability_solution/apm/public/hooks/use_preferred_data_source_and_bucket_size.ts index b8dfa5682a340..cf27bce80de40 100644 --- a/x-pack/plugins/observability_solution/apm/public/hooks/use_preferred_data_source_and_bucket_size.ts +++ b/x-pack/plugins/observability_solution/apm/public/hooks/use_preferred_data_source_and_bucket_size.ts @@ -6,10 +6,10 @@ */ import { useMemo } from 'react'; +import { getPreferredBucketSizeAndDataSource } from '@kbn/apm-data-access-plugin/common'; import { ApmDataSourceWithSummary } from '../../common/data_source'; import { ApmDocumentType } from '../../common/document_type'; import { getBucketSize } from '../../common/utils/get_bucket_size'; -import { getPreferredBucketSizeAndDataSource } from '../../common/utils/get_preferred_bucket_size_and_data_source'; import { useTimeRangeMetadata } from '../context/time_range_metadata/use_time_range_metadata_context'; /** diff --git a/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts b/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts index a72bb4d782e9c..e8a3c4c4313df 100644 --- a/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts +++ b/x-pack/plugins/observability_solution/apm/public/utils/get_signal_type.ts @@ -11,5 +11,5 @@ export function isApmSignal(signalTypes: SignalTypes[]) { return signalTypes.includes(SignalTypes.METRICS) || signalTypes.includes(SignalTypes.TRACES); } export function isLogsSignal(signalTypes: SignalTypes[]) { - return signalTypes.includes(SignalTypes.LOGS) && !isApmSignal(signalTypes); + return signalTypes.includes(SignalTypes.LOGS); } diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index 1d6592814526c..0ff0db5b36989 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -4,329 +4,4 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import type { - EqlSearchRequest, - FieldCapsRequest, - FieldCapsResponse, - MsearchMultisearchBody, - MsearchMultisearchHeader, - TermsEnumRequest, - TermsEnumResponse, -} from '@elastic/elasticsearch/lib/api/types'; -import { ElasticsearchClient, KibanaRequest } from '@kbn/core/server'; -import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { unwrapEsResponse } from '@kbn/observability-plugin/server'; -import { compact, omit } from 'lodash'; -import { ValuesType } from 'utility-types'; -import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; -import { ApmDataSource } from '../../../../../common/data_source'; -import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; -import { Metric } from '../../../../../typings/es_schemas/ui/metric'; -import { Span } from '../../../../../typings/es_schemas/ui/span'; -import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { Event } from '../../../../../typings/es_schemas/ui/event'; -import { withApmSpan } from '../../../../utils/with_apm_span'; -import { callAsyncWithDebug, getDebugBody, getDebugTitle } from '../call_async_with_debug'; -import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort'; -import { ProcessorEventOfDocumentType } from '../document_type'; -import { getRequestBase, processorEventsToIndex } from './get_request_base'; - -export type APMEventESSearchRequest = Omit<ESSearchRequest, 'index'> & { - apm: { - includeLegacyData?: boolean; - } & ({ events: ProcessorEvent[] } | { sources: ApmDataSource[] }); - body: { - size: number; - track_total_hits: boolean | number; - }; -}; - -export type APMLogEventESSearchRequest = Omit<ESSearchRequest, 'index'> & { - body: { - size: number; - track_total_hits: boolean | number; - }; -}; - -type APMEventWrapper<T> = Omit<T, 'index'> & { - apm: { events: ProcessorEvent[] }; -}; - -type APMEventTermsEnumRequest = APMEventWrapper<TermsEnumRequest>; -type APMEventEqlSearchRequest = APMEventWrapper<EqlSearchRequest>; -type APMEventFieldCapsRequest = APMEventWrapper<FieldCapsRequest>; - -type TypeOfProcessorEvent<T extends ProcessorEvent> = { - [ProcessorEvent.error]: APMError; - [ProcessorEvent.transaction]: Transaction; - [ProcessorEvent.span]: Span; - [ProcessorEvent.metric]: Metric; -}[T]; - -type TypedLogEventSearchResponse<TParams extends APMLogEventESSearchRequest> = - InferSearchResponseOf<Event, TParams>; - -type TypedSearchResponse<TParams extends APMEventESSearchRequest> = InferSearchResponseOf< - TypeOfProcessorEvent< - TParams['apm'] extends { events: ProcessorEvent[] } - ? ValuesType<TParams['apm']['events']> - : TParams['apm'] extends { sources: ApmDataSource[] } - ? ProcessorEventOfDocumentType<ValuesType<TParams['apm']['sources']>['documentType']> - : never - >, - TParams ->; - -interface TypedMSearchResponse<TParams extends APMEventESSearchRequest> { - responses: Array<TypedSearchResponse<TParams>>; -} - -export interface APMEventClientConfig { - esClient: ElasticsearchClient; - debug: boolean; - request: KibanaRequest; - indices: APMIndices; - options: { - includeFrozen: boolean; - }; -} - -export class APMEventClient { - private readonly esClient: ElasticsearchClient; - private readonly debug: boolean; - private readonly request: KibanaRequest; - public readonly indices: APMIndices; - private readonly includeFrozen: boolean; - - constructor(config: APMEventClientConfig) { - this.esClient = config.esClient; - this.debug = config.debug; - this.request = config.request; - this.indices = config.indices; - this.includeFrozen = config.options.includeFrozen; - } - - private callAsyncWithDebug<T extends { body: any }>({ - requestType, - params, - cb, - operationName, - }: { - requestType: string; - params: Record<string, any>; - cb: (requestOpts: { signal: AbortSignal; meta: true }) => Promise<T>; - operationName: string; - }): Promise<T['body']> { - return callAsyncWithDebug({ - getDebugMessage: () => ({ - body: getDebugBody({ - params, - requestType, - operationName, - }), - title: getDebugTitle(this.request), - }), - isCalledWithInternalUser: false, - debug: this.debug, - request: this.request, - operationName, - requestParams: params, - cb: () => { - const controller = new AbortController(); - - const promise = withApmSpan(operationName, () => { - return cancelEsRequestOnAbort( - cb({ signal: controller.signal, meta: true }), - this.request, - controller - ); - }); - - return unwrapEsResponse(promise); - }, - }); - } - - async search<TParams extends APMEventESSearchRequest>( - operationName: string, - params: TParams - ): Promise<TypedSearchResponse<TParams>> { - const { index, filters } = getRequestBase({ - apm: params.apm, - indices: this.indices, - }); - - const searchParams = { - ...omit(params, 'apm', 'body'), - index, - body: { - ...params.body, - query: { - bool: { - filter: filters, - must: compact([params.body.query]), - }, - }, - }, - ...(this.includeFrozen ? { ignore_throttled: false } : {}), - ignore_unavailable: true, - preference: 'any', - expand_wildcards: ['open' as const, 'hidden' as const], - }; - - return this.callAsyncWithDebug({ - cb: (opts) => - this.esClient.search(searchParams, opts) as unknown as Promise<{ - body: TypedSearchResponse<TParams>; - }>, - operationName, - params: searchParams, - requestType: 'search', - }); - } - - async logEventSearch<TParams extends APMLogEventESSearchRequest>( - operationName: string, - params: TParams - ): Promise<TypedLogEventSearchResponse<TParams>> { - // Reusing indices configured for errors since both events and errors are stored as logs. - const index = processorEventsToIndex([ProcessorEvent.error], this.indices); - - const searchParams = { - ...omit(params, 'body'), - index, - body: { - ...params.body, - query: { - bool: { - must: compact([params.body.query]), - }, - }, - }, - ...(this.includeFrozen ? { ignore_throttled: false } : {}), - ignore_unavailable: true, - preference: 'any', - expand_wildcards: ['open' as const, 'hidden' as const], - }; - - return this.callAsyncWithDebug({ - cb: (opts) => - this.esClient.search(searchParams, opts) as unknown as Promise<{ - body: TypedLogEventSearchResponse<TParams>; - }>, - operationName, - params: searchParams, - requestType: 'search', - }); - } - - async msearch<TParams extends APMEventESSearchRequest>( - operationName: string, - ...allParams: TParams[] - ): Promise<TypedMSearchResponse<TParams>> { - const searches = allParams - .map((params) => { - const { index, filters } = getRequestBase({ - apm: params.apm, - indices: this.indices, - }); - - const searchParams: [MsearchMultisearchHeader, MsearchMultisearchBody] = [ - { - index, - preference: 'any', - ...(this.includeFrozen ? { ignore_throttled: false } : {}), - ignore_unavailable: true, - expand_wildcards: ['open' as const, 'hidden' as const], - }, - { - ...omit(params, 'apm', 'body'), - ...params.body, - query: { - bool: { - filter: compact([params.body.query, ...filters]), - }, - }, - }, - ]; - - return searchParams; - }) - .flat(); - - return this.callAsyncWithDebug({ - cb: (opts) => - this.esClient.msearch( - { - searches, - }, - opts - ) as unknown as Promise<{ - body: TypedMSearchResponse<TParams>; - }>, - operationName, - params: searches, - requestType: 'msearch', - }); - } - - async eqlSearch(operationName: string, params: APMEventEqlSearchRequest) { - const index = processorEventsToIndex(params.apm.events, this.indices); - - const requestParams = { - ...omit(params, 'apm'), - index, - }; - - return this.callAsyncWithDebug({ - operationName, - requestType: 'eql_search', - params: requestParams, - cb: (opts) => this.esClient.eql.search(requestParams, opts), - }); - } - - async fieldCaps( - operationName: string, - params: APMEventFieldCapsRequest - ): Promise<FieldCapsResponse> { - const index = processorEventsToIndex(params.apm.events, this.indices); - - const requestParams = { - ...omit(params, 'apm'), - index, - }; - - return this.callAsyncWithDebug({ - operationName, - requestType: '_field_caps', - params: requestParams, - cb: (opts) => this.esClient.fieldCaps(requestParams, opts), - }); - } - - async termsEnum( - operationName: string, - params: APMEventTermsEnumRequest - ): Promise<TermsEnumResponse> { - const index = processorEventsToIndex(params.apm.events, this.indices); - - const requestParams = { - ...omit(params, 'apm'), - index: index.join(','), - }; - - return this.callAsyncWithDebug({ - operationName, - requestType: '_terms_enum', - params: requestParams, - cb: (opts) => this.esClient.termsEnum(requestParams, opts), - }); - } - - getIndicesFromProcessorEvent(processorEvent: ProcessorEvent) { - return processorEventsToIndex([processorEvent], this.indices); - } -} +export { APMEventClient, type APMEventESSearchRequest } from '@kbn/apm-data-access-plugin/server'; diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts index 17193a6d508b6..6754d0b75dc6d 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients.ts @@ -4,18 +4,27 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; import type { KibanaRequest } from '@kbn/core/server'; import { ElasticsearchClient } from '@kbn/core/server'; +import { entitiesAliasPattern, ENTITY_LATEST, ENTITY_HISTORY } from '@kbn/entities-schema'; import { unwrapEsResponse } from '@kbn/observability-plugin/common/utils/unwrap_es_response'; import { MsearchMultisearchBody, MsearchMultisearchHeader, } from '@elastic/elasticsearch/lib/api/types'; import { withApmSpan } from '../../../../utils/with_apm_span'; +import { EntityType } from '../../../../routes/entities/types'; -const ENTITIES_LATEST_INDEX_NAME = '.entities.v1.latest.builtin_services*'; -const ENTITIES_HISTORY_INDEX_NAME = '.entities.v1.history.builtin_services*'; +const SERVICE_ENTITIES_LATEST_ALIAS = entitiesAliasPattern({ + type: EntityType.SERVICE, + dataset: ENTITY_LATEST, +}); +const SERVICE_ENTITIES_HISTORY_ALIAS = entitiesAliasPattern({ + type: EntityType.SERVICE, + dataset: ENTITY_HISTORY, +}); export function cancelEsRequestOnAbort<T extends Promise<any>>( promise: T, @@ -60,7 +69,7 @@ export async function createEntitiesESClient({ const promise = withApmSpan(operationName, () => { return cancelEsRequestOnAbort( esClient.search( - { ...searchRequest, index: [indexName] }, + { ...searchRequest, index: [indexName], ignore_unavailable: true }, { signal: controller.signal, meta: true, @@ -81,14 +90,14 @@ export async function createEntitiesESClient({ operationName: string, searchRequest: TSearchRequest ): Promise<InferSearchResponseOf<TDocument, TSearchRequest>> { - return search(ENTITIES_LATEST_INDEX_NAME, operationName, searchRequest); + return search(SERVICE_ENTITIES_LATEST_ALIAS, operationName, searchRequest); }, searchHistory<TDocument = unknown, TSearchRequest extends ESSearchRequest = ESSearchRequest>( operationName: string, searchRequest: TSearchRequest ): Promise<InferSearchResponseOf<TDocument, TSearchRequest>> { - return search(ENTITIES_HISTORY_INDEX_NAME, operationName, searchRequest); + return search(SERVICE_ENTITIES_HISTORY_ALIAS, operationName, searchRequest); }, async msearch<TDocument = unknown, TSearchRequest extends ESSearchRequest = ESSearchRequest>( @@ -98,7 +107,8 @@ export async function createEntitiesESClient({ .map((params) => { const searchParams: [MsearchMultisearchHeader, MsearchMultisearchBody] = [ { - index: [ENTITIES_LATEST_INDEX_NAME], + index: [SERVICE_ENTITIES_LATEST_ALIAS], + ignore_unavailable: true, }, { ...params.body, diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts index a0f5a6dfbf319..272f482cdc8eb 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts @@ -9,9 +9,16 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { unwrapEsResponse } from '@kbn/observability-plugin/server'; import type { ESSearchResponse, ESSearchRequest } from '@kbn/es-types'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { APMRouteHandlerResources } from '../../../../routes/apm_routes/register_apm_server_routes'; -import { callAsyncWithDebug, getDebugBody, getDebugTitle } from '../call_async_with_debug'; -import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort'; +import { + callAsyncWithDebug, + getDebugBody, + getDebugTitle, + cancelEsRequestOnAbort, +} from '@kbn/apm-data-access-plugin/server/utils'; +import { + type APMRouteHandlerResources, + inspectableEsQueriesMap, +} from '../../../../routes/apm_routes/register_apm_server_routes'; export type APMIndexDocumentParams<T> = estypes.IndexRequest<T>; @@ -71,6 +78,7 @@ export async function createInternalESClient({ request, requestParams: params, operationName, + inspectableEsQueriesMap, }); } diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/document_type.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/document_type.ts deleted file mode 100644 index 8165c7329b6b1..0000000000000 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/document_type.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { ApmDocumentType } from '../../../../common/document_type'; -import { METRICSET_INTERVAL, METRICSET_NAME } from '../../../../common/es_fields/apm'; -import { RollupInterval } from '../../../../common/rollup'; -import { termQuery } from '../../../../common/utils/term_query'; -import { getDocumentTypeFilterForServiceDestinationStatistics } from '../spans/get_is_using_service_destination_metrics'; -import { getBackwardCompatibleDocumentTypeFilter } from '../transactions'; - -const defaultRollupIntervals = [ - RollupInterval.OneMinute, - RollupInterval.TenMinutes, - RollupInterval.SixtyMinutes, -]; - -function getDefaultFilter(metricsetName: string, rollupInterval: RollupInterval) { - return [ - ...termQuery(METRICSET_NAME, metricsetName), - ...termQuery(METRICSET_INTERVAL, rollupInterval), - ]; -} - -const documentTypeConfigMap = { - [ApmDocumentType.ServiceTransactionMetric]: { - processorEvent: ProcessorEvent.metric, - - getQuery: (rollupInterval: RollupInterval) => ({ - bool: { - filter: getDefaultFilter('service_transaction', rollupInterval), - }, - }), - rollupIntervals: defaultRollupIntervals, - }, - [ApmDocumentType.ServiceSummaryMetric]: { - processorEvent: ProcessorEvent.metric, - getQuery: (rollupInterval: RollupInterval) => ({ - bool: { - filter: getDefaultFilter('service_summary', rollupInterval), - }, - }), - rollupIntervals: defaultRollupIntervals, - }, - [ApmDocumentType.TransactionMetric]: { - processorEvent: ProcessorEvent.metric, - getQuery: (rollupInterval: RollupInterval) => ({ - bool: { - filter: - rollupInterval === RollupInterval.OneMinute - ? getBackwardCompatibleDocumentTypeFilter(true) - : getDefaultFilter('transaction', rollupInterval), - }, - }), - rollupIntervals: defaultRollupIntervals, - }, - [ApmDocumentType.TransactionEvent]: { - processorEvent: ProcessorEvent.transaction, - rollupIntervals: [RollupInterval.None], - }, - [ApmDocumentType.ServiceDestinationMetric]: { - processorEvent: ProcessorEvent.metric, - rollupIntervals: defaultRollupIntervals, - getQuery: (rollupInterval: RollupInterval) => ({ - bool: { - filter: - rollupInterval === RollupInterval.OneMinute - ? getDocumentTypeFilterForServiceDestinationStatistics(true) - : getDefaultFilter('service_destination', rollupInterval), - }, - }), - }, - [ApmDocumentType.ErrorEvent]: { - processorEvent: ProcessorEvent.error, - rollupIntervals: [RollupInterval.None], - }, - [ApmDocumentType.SpanEvent]: { - processorEvent: ProcessorEvent.span, - rollupIntervals: [RollupInterval.None], - }, -} as const; - -type DocumentTypeConfigOf<TApmDocumentType extends ApmDocumentType> = - (typeof documentTypeConfigMap)[TApmDocumentType]; - -export function getConfigForDocumentType<TApmDocumentType extends ApmDocumentType>( - docType: TApmDocumentType -): DocumentTypeConfigOf<TApmDocumentType> { - return documentTypeConfigMap[docType]; -} - -export type ProcessorEventOfDocumentType<TApmDocumentType extends ApmDocumentType> = - DocumentTypeConfigOf<TApmDocumentType>['processorEvent']; - -export function getProcessorEventForDocumentType<TApmDocumentType extends ApmDocumentType>( - documentType: TApmDocumentType -): ProcessorEventOfDocumentType<TApmDocumentType> { - return getConfigForDocumentType(documentType).processorEvent; -} diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_data_access_services.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_data_access_services.ts new file mode 100644 index 0000000000000..176507e6e3456 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_data_access_services.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 { ApmDataAccessServices, APMEventClient } from '@kbn/apm-data-access-plugin/server'; +import { MinimalAPMRouteHandlerResources } from '../../routes/apm_routes/register_apm_server_routes'; + +export async function getApmDataAccessServices({ + apmEventClient, + plugins, +}: { + apmEventClient: APMEventClient; +} & Pick<MinimalAPMRouteHandlerResources, 'plugins'>): Promise<ApmDataAccessServices> { + const { apmDataAccess } = plugins; + return apmDataAccess.setup.getServices({ + apmEventClient, + }); +} diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts index b756876eb3212..8f21bf8f1c691 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_apm_event_client.ts @@ -9,6 +9,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { APMEventClient } from './create_es_client/create_apm_event_client'; import { withApmSpan } from '../../utils/with_apm_span'; import { MinimalAPMRouteHandlerResources } from '../../routes/apm_routes/register_apm_server_routes'; +import { inspectableEsQueriesMap } from '../../routes/apm_routes/register_apm_server_routes'; export async function getApmEventClient({ context, @@ -35,6 +36,7 @@ export async function getApmEventClient({ indices, options: { includeFrozen, + inspectableEsQueriesMap, }, }); }); diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts index 7e53735bafabb..07ec546196707 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts @@ -6,11 +6,10 @@ */ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { kqlQuery, rangeQuery, termQuery } from '@kbn/observability-plugin/server'; +import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { getDocumentTypeFilterForServiceDestinationStatistics } from '@kbn/apm-data-access-plugin/server/utils'; import { - METRICSET_NAME, - METRICSET_INTERVAL, SPAN_DESTINATION_SERVICE_RESPONSE_TIME_COUNT, SPAN_DESTINATION_SERVICE_RESPONSE_TIME_SUM, SPAN_DURATION, @@ -24,25 +23,6 @@ export function getProcessorEventForServiceDestinationStatistics( return searchServiceDestinationMetrics ? ProcessorEvent.metric : ProcessorEvent.span; } -export function getDocumentTypeFilterForServiceDestinationStatistics( - searchServiceDestinationMetrics: boolean -) { - return searchServiceDestinationMetrics - ? [ - { - bool: { - filter: termQuery(METRICSET_NAME, 'service_destination'), - must_not: { - terms: { - [METRICSET_INTERVAL]: ['10m', '60m'], - }, - }, - }, - }, - ] - : []; -} - export function getLatencyFieldForServiceDestinationStatistics( searchServiceDestinationMetrics: boolean ) { @@ -117,3 +97,5 @@ export async function getIsUsingServiceDestinationMetrics({ anyServiceDestinationMetricsCount > 0 && serviceDestinationMetricsWithoutSpanNameCount === 0 ); } + +export { getDocumentTypeFilterForServiceDestinationStatistics }; diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/transactions/index.ts b/x-pack/plugins/observability_solution/apm/server/lib/helpers/transactions/index.ts index f1aa16f4e4f37..8bf8c0cb74a70 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/transactions/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/lib/helpers/transactions/index.ts @@ -14,14 +14,14 @@ import { TRANSACTION_DURATION_HISTOGRAM, TRANSACTION_ROOT, PARENT_ID, - METRICSET_INTERVAL, - METRICSET_NAME, TRANSACTION_DURATION_SUMMARY, } from '../../../../common/es_fields/apm'; import { APMConfig } from '../../..'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; import { ApmDocumentType } from '../../../../common/document_type'; +export { getBackwardCompatibleDocumentTypeFilter } from '@kbn/apm-data-access-plugin/server/utils'; + export async function getHasTransactionsEvents({ start, end, @@ -125,23 +125,6 @@ export function getDurationFieldForTransactions( return TRANSACTION_DURATION; } -// The function returns Document type filter for 1m Transaction Metrics -export function getBackwardCompatibleDocumentTypeFilter(searchAggregatedTransactions: boolean) { - return searchAggregatedTransactions - ? [ - { - bool: { - filter: [{ exists: { field: TRANSACTION_DURATION_HISTOGRAM } }], - must_not: [ - { terms: { [METRICSET_INTERVAL]: ['10m', '60m'] } }, - { term: { [METRICSET_NAME]: 'service_transaction' } }, - ], - }, - }, - ] - : []; -} - export function getProcessorEventForTransactions( searchAggregatedTransactions: boolean ): ProcessorEvent.metric | ProcessorEvent.transaction { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts index 901b375eb4e61..a69a906454ff7 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_entities.ts @@ -17,6 +17,7 @@ import { environmentQuery } from '../../../common/utils/environment_query'; import { EntitiesESClient } from '../../lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients'; import { getServiceEntitiesHistoryMetrics } from './get_service_entities_history_metrics'; import { EntitiesRaw, EntityType, ServiceEntities } from './types'; +import { isFiniteNumber } from '../../../common/utils/is_finite_number'; export function entitiesRangeQuery(start: number, end: number): QueryDslQueryContainer[] { return [ @@ -44,14 +45,16 @@ export async function getEntities({ environment, kuery, size, + serviceName, }: { entitiesESClient: EntitiesESClient; start: number; end: number; environment: string; - kuery: string; + kuery?: string; size: number; -}) { + serviceName?: string; +}): Promise<ServiceEntities[]> { const entities = ( await entitiesESClient.searchLatest(`get_entities`, { body: { @@ -65,6 +68,7 @@ export async function getEntities({ ...environmentQuery(environment, SERVICE_ENVIRONMENT), ...entitiesRangeQuery(start, end), ...termQuery(ENTITY_TYPE, EntityType.SERVICE), + ...termQuery(SERVICE_NAME, serviceName), ], }, }, @@ -78,10 +82,12 @@ export async function getEntities({ end, entitiesESClient, entityIds: entities.map((entity) => entity.entity.id), + size, }) : undefined; - return entities.map((entity): ServiceEntities => { + return entities.map((entity) => { + const historyLogRate = serviceEntitiesHistoryMetricsMap?.[entity.entity.id]?.logRate; return { serviceName: entity.service.name, environment: Array.isArray(entity.service?.environment) // TODO fix this in the EEM @@ -91,6 +97,7 @@ export async function getEntities({ signalTypes: entity.data_stream.type, entity: { ...entity.entity, + hasLogMetrics: isFiniteNumber(historyLogRate) ? historyLogRate > 0 : false, // History metrics undefined means that for the selected time range there was no ingestion happening. metrics: serviceEntitiesHistoryMetricsMap?.[entity.entity.id] || { latency: null, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entities_history_metrics.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entities_history_metrics.ts index 1040288a4dd75..606c1748aa0a0 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entities_history_metrics.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entities_history_metrics.ts @@ -23,6 +23,7 @@ interface Params { start: number; end: number; entityIds: string[]; + size: number; } export async function getServiceEntitiesHistoryMetrics({ @@ -30,6 +31,7 @@ export async function getServiceEntitiesHistoryMetrics({ entityIds, start, entitiesESClient, + size, }: Params) { const response = await entitiesESClient.searchHistory('get_entities_history', { body: { @@ -42,7 +44,7 @@ export async function getServiceEntitiesHistoryMetrics({ }, aggs: { entityIds: { - terms: { field: ENTITY_ID }, + terms: { field: ENTITY_ID, size }, aggs: { latency: { avg: { field: ENTITY_METRICS_LATENCY } }, logErrorRate: { avg: { field: ENTITY_METRICS_LOG_ERROR_RATE } }, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entities_history_timeseries.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entities_history_timeseries.ts new file mode 100644 index 0000000000000..6e969b6022bf2 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entities_history_timeseries.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { termsQuery, rangeQuery } from '@kbn/observability-plugin/server'; +import { getBucketSize } from '@kbn/apm-data-access-plugin/common'; +import { keyBy } from 'lodash'; +import { + ENTITY_METRICS_FAILED_TRANSACTION_RATE, + ENTITY_METRICS_LATENCY, + ENTITY_METRICS_LOG_ERROR_RATE, + ENTITY_METRICS_LOG_RATE, + ENTITY_METRICS_THROUGHPUT, + LAST_SEEN, +} from '../../../common/es_fields/entities'; +import { SERVICE_NAME } from '../../../common/es_fields/apm'; +import { EntitiesESClient } from '../../lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients'; +import { environmentQuery } from '../../../common/utils/environment_query'; + +interface Params { + entitiesESClient: EntitiesESClient; + start: number; + end: number; + serviceNames: string[]; + environment: string; +} + +export async function getServiceEntitiesHistoryTimeseries({ + start, + end, + serviceNames, + entitiesESClient, + environment, +}: Params) { + const { intervalString } = getBucketSize({ + start, + end, + minBucketSize: 60, + }); + + const response = await entitiesESClient.searchHistory('get_entities_history_timeseries', { + body: { + size: 0, + track_total_hits: false, + query: { + bool: { + filter: [ + ...rangeQuery(start, end, LAST_SEEN), + ...termsQuery(SERVICE_NAME, ...serviceNames), + ...environmentQuery(environment), + ], + }, + }, + aggs: { + serviceNames: { + terms: { field: SERVICE_NAME, size: serviceNames.length }, + aggs: { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { min: start, max: end }, + }, + aggs: { + latency: { avg: { field: ENTITY_METRICS_LATENCY } }, + logErrorRate: { avg: { field: ENTITY_METRICS_LOG_ERROR_RATE } }, + logRate: { avg: { field: ENTITY_METRICS_LOG_RATE } }, + throughput: { avg: { field: ENTITY_METRICS_THROUGHPUT } }, + failedTransactionRate: { avg: { field: ENTITY_METRICS_FAILED_TRANSACTION_RATE } }, + }, + }, + }, + }, + }, + }, + }); + + if (!response.aggregations) { + return {}; + } + + return keyBy( + response.aggregations.serviceNames.buckets.map((serviceBucket) => { + const serviceName = serviceBucket.key as string; + + return { + serviceName, + latency: serviceBucket.timeseries.buckets.map((bucket) => ({ + x: bucket.key, + y: bucket.latency.value ?? null, + })), + logErrorRate: serviceBucket.timeseries.buckets.map((bucket) => ({ + x: bucket.key, + y: bucket.logErrorRate.value ?? null, + })), + logRate: serviceBucket.timeseries.buckets.map((bucket) => ({ + x: bucket.key, + y: bucket.logRate.value ?? null, + })), + throughput: serviceBucket.timeseries.buckets.map((bucket) => ({ + x: bucket.key, + y: bucket.throughput.value ?? null, + })), + failedTransactionRate: serviceBucket.timeseries.buckets.map((bucket) => ({ + x: bucket.key, + y: bucket.failedTransactionRate.value ?? null, + })), + }; + }), + 'serviceName' + ); +} diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entity_summary.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entity_summary.ts new file mode 100644 index 0000000000000..0ea5d27e68971 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/get_service_entity_summary.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 { EntitiesESClient } from '../../lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients'; +import { withApmSpan } from '../../utils/with_apm_span'; +import { getEntities } from './get_entities'; +import { ServiceEntities } from './types'; + +interface Params { + entitiesESClient: EntitiesESClient; + serviceName: string; + environment: string; + start: number; + end: number; +} + +export async function getServiceEntitySummary({ + end, + entitiesESClient, + environment, + serviceName, + start, +}: Params): Promise<ServiceEntities> { + return withApmSpan('get_service_entity_summary', async () => { + const entities = await getEntities({ + end, + entitiesESClient, + environment, + size: 1, + start, + serviceName, + }); + + return entities[0]; + }); +} diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts index 52c8e6983384c..0f52a9956af5b 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/services/routes.ts @@ -5,29 +5,51 @@ * 2.0. */ import Boom from '@hapi/boom'; -import { toNumberRt, jsonRt } from '@kbn/io-ts-utils'; +import { jsonRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; -import { offsetRt } from '../../../../common/comparison_rt'; -import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; -import { getRandomSampler } from '../../../lib/helpers/get_random_sampler'; import { EntityServiceListItem } from '../../../../common/entities/types'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { createEntitiesESClient } from '../../../lib/helpers/create_es_client/create_assets_es_client/create_assets_es_clients'; import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; -import { - environmentRt, - kueryRt, - probabilityRt, - rangeRt, - serviceTransactionDataSourceRt, -} from '../../default_api_types'; -import { getServiceTransactionDetailedStatsPeriods } from '../../services/get_services_detailed_statistics/get_service_transaction_detailed_statistics'; +import { environmentRt, kueryRt, rangeRt } from '../../default_api_types'; import { getServiceEntities } from './get_service_entities'; +import { getServiceEntitySummary } from '../get_service_entity_summary'; +import { ServiceEntities } from '../types'; +import { getServiceEntitiesHistoryTimeseries } from '../get_service_entities_history_timeseries'; export interface EntityServicesResponse { services: EntityServiceListItem[]; } +const serviceEntitiesSummaryRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/entities/services/{serviceName}/summary', + params: t.type({ + path: t.type({ serviceName: t.string }), + query: t.intersection([environmentRt, rangeRt]), + }), + options: { tags: ['access:apm'] }, + async handler(resources): Promise<ServiceEntities> { + const { context, params, request } = resources; + const coreContext = await context.core; + + const entitiesESClient = await createEntitiesESClient({ + request, + esClient: coreContext.elasticsearch.client.asCurrentUser, + }); + + const { serviceName } = params.path; + const { start, end, environment } = params.query; + + return getServiceEntitySummary({ + entitiesESClient, + start, + end, + serviceName, + environment, + }); + }, +}); + const servicesEntitiesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/entities/services', params: t.type({ @@ -61,42 +83,20 @@ const servicesEntitiesRoute = createApmServerRoute({ const servicesEntitiesDetailedStatisticsRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/entities/services/detailed_statistics', params: t.type({ - query: t.intersection([ - environmentRt, - kueryRt, - rangeRt, - t.intersection([offsetRt, probabilityRt, serviceTransactionDataSourceRt]), - t.type({ - bucketSizeInSeconds: toNumberRt, - }), - ]), + query: t.intersection([environmentRt, kueryRt, rangeRt]), body: t.type({ serviceNames: jsonRt.pipe(t.array(t.string)) }), }), options: { tags: ['access:apm'] }, handler: async (resources) => { - const { - context, - params, - request, - plugins: { security, logsDataAccess }, - } = resources; + const { context, params, request } = resources; + const coreContext = await context.core; - const [coreContext, logsDataAccessStart] = await Promise.all([ - context.core, - logsDataAccess.start(), - ]); + const entitiesESClient = await createEntitiesESClient({ + request, + esClient: coreContext.elasticsearch.client.asCurrentUser, + }); - const { - environment, - kuery, - offset, - start, - end, - probability, - documentType, - rollupInterval, - bucketSizeInSeconds, - } = params.query; + const { environment, start, end } = params.query; const { serviceNames } = params.body; @@ -104,48 +104,17 @@ const servicesEntitiesDetailedStatisticsRoute = createApmServerRoute({ throw Boom.badRequest(`serviceNames cannot be empty`); } - const [apmEventClient, randomSampler] = await Promise.all([ - getApmEventClient(resources), - getRandomSampler({ security, request, probability }), - ]); - - const logsParams = { - esClient: coreContext.elasticsearch.client.asCurrentUser, - identifyingMetadata: 'service.name', - timeFrom: start, - timeTo: end, - kuery, - serviceEnvironmentQuery: environmentQuery(environment), + const serviceEntitiesTimeseries = await getServiceEntitiesHistoryTimeseries({ + start, + end, serviceNames, - }; - - const [ - currentPeriodLogsRateTimeseries, - currentPeriodLogsErrorRateTimeseries, - apmServiceTransactionDetailedStatsPeriods, - ] = await Promise.all([ - logsDataAccessStart.services.getLogsRateTimeseries(logsParams), - logsDataAccessStart.services.getLogsErrorRateTimeseries(logsParams), - getServiceTransactionDetailedStatsPeriods({ - environment, - kuery, - apmEventClient, - documentType, - rollupInterval, - bucketSizeInSeconds, - offset, - serviceNames, - start, - end, - randomSampler, - }), - ]); + environment, + entitiesESClient, + }); return { currentPeriod: { - apm: { ...apmServiceTransactionDetailedStatsPeriods.currentPeriod }, - logErrorRate: { ...currentPeriodLogsErrorRateTimeseries }, - logRate: { ...currentPeriodLogsRateTimeseries }, + ...serviceEntitiesTimeseries, }, }; }, @@ -223,4 +192,5 @@ export const servicesEntitiesRoutesRepository = { ...serviceLogRateTimeseriesRoute, ...serviceLogErrorRateTimeseriesRoute, ...servicesEntitiesDetailedStatisticsRoute, + ...serviceEntitiesSummaryRoute, }; diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts index 35a19a218eea0..f98cef107a3d1 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts @@ -16,6 +16,7 @@ export interface Entity { latestTimestamp: string; identityFields: string[]; metrics: EntityMetrics; + hasLogMetrics: boolean; } export interface TraceMetrics { @@ -52,4 +53,5 @@ export interface MergedServiceEntities { signalTypes: SignalTypes[]; environments: string[]; metrics: EntityMetrics[]; + hasLogMetrics: boolean; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts index 41d6e8e8b0979..f73d8cd5f4f8d 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.test.ts @@ -34,6 +34,7 @@ describe('calculateAverageMetrics', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, { agentName: 'java' as AgentName, @@ -57,6 +58,7 @@ describe('calculateAverageMetrics', () => { }, ], serviceName: 'service-2', + hasLogMetrics: true, }, ]; @@ -76,6 +78,7 @@ describe('calculateAverageMetrics', () => { throughput: 7.5, }, serviceName: 'service-1', + hasLogMetrics: true, }, { agentName: 'java' as AgentName, @@ -90,6 +93,7 @@ describe('calculateAverageMetrics', () => { throughput: 10, }, serviceName: 'service-2', + hasLogMetrics: true, }, ]); }); @@ -117,6 +121,7 @@ describe('calculateAverageMetrics', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]; @@ -135,6 +140,7 @@ describe('calculateAverageMetrics', () => { throughput: 7.5, }, serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); @@ -177,4 +183,54 @@ describe('mergeMetrics', () => { expect(result).toEqual({}); }); + + it('returns metrics with zero value', () => { + const metrics = [ + { + failedTransactionRate: 0, + latency: 4, + logErrorRate: 5, + logRate: 5, + throughput: 5, + }, + ]; + + const result = mergeMetrics(metrics); + + expect(result).toEqual({ + failedTransactionRate: [0], + latency: [4], + logErrorRate: [5], + logRate: [5], + throughput: [5], + }); + }); + + it('does not return metrics with null', () => { + const metrics = [ + { + failedTransactionRate: null, + latency: null, + logErrorRate: 5, + logRate: 5, + throughput: 5, + }, + { + failedTransactionRate: 5, + latency: null, + logErrorRate: 5, + logRate: 5, + throughput: 5, + }, + ]; + + const result = mergeMetrics(metrics); + + expect(result).toEqual({ + failedTransactionRate: [5], + logErrorRate: [5, 5], + logRate: [5, 5], + throughput: [5, 5], + }); + }); }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.ts index f477786786645..459361d0e5cad 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/calculate_avg_metrics.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { mapValues } from 'lodash'; +import { mapValues, isNumber } from 'lodash'; import { EntityMetrics } from '../../../../common/entities/types'; import { MergedServiceEntities } from '../types'; @@ -32,7 +32,7 @@ export function mergeMetrics(metrics: EntityMetrics[]) { const metricsKey = key as MetricsKey; const value = metric[metricsKey]; - if (value) { + if (isNumber(value)) { if (!acc[metricsKey]) { acc[metricsKey] = []; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts index 544513fd501d2..dc52a8369c1cc 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts @@ -27,6 +27,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -47,6 +48,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); @@ -69,6 +71,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:env-service-1', + hasLogMetrics: true, }, }, { @@ -87,6 +90,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'apm-only-1:synthtrace-env-2', + hasLogMetrics: true, }, }, { @@ -105,6 +109,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', + hasLogMetrics: true, }, }, { @@ -123,6 +128,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', + hasLogMetrics: true, }, }, ]; @@ -151,6 +157,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, { agentName: 'java' as AgentName, @@ -174,6 +181,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-2', + hasLogMetrics: true, }, ]); }); @@ -195,6 +203,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -213,6 +222,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -231,6 +241,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name', 'service.environment'], id: 'service-1:prod', + hasLogMetrics: true, }, }, ]; @@ -265,6 +276,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); @@ -286,6 +298,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -306,6 +319,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); @@ -325,6 +339,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -342,6 +357,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -369,6 +385,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); @@ -390,6 +407,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -410,6 +428,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); @@ -429,6 +448,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, { @@ -446,6 +466,7 @@ describe('mergeEntities', () => { }, identityFields: ['service.name'], id: 'service-1:test', + hasLogMetrics: true, }, }, ]; @@ -473,6 +494,7 @@ describe('mergeEntities', () => { }, ], serviceName: 'service-1', + hasLogMetrics: true, }, ]); }); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts index 7dd8bfdace7bf..6f96a25c63a93 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts @@ -36,6 +36,7 @@ function mergeFunc(entity: ServiceEntities, existingEntity?: MergedServiceEntiti environments: compact([entity?.environment]), latestTimestamp: entity.entity.latestTimestamp, metrics: [entity.entity.metrics], + hasLogMetrics: entity.entity.hasLogMetrics, }; } return { @@ -45,5 +46,6 @@ function mergeFunc(entity: ServiceEntities, existingEntity?: MergedServiceEntiti environments: uniq(compact([...existingEntity?.environments, entity?.environment])), latestTimestamp: entity.entity.latestTimestamp, metrics: [...existingEntity?.metrics, entity.entity.metrics], + hasLogMetrics: entity.entity.hasLogMetrics || existingEntity.hasLogMetrics, }; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/time_range_metadata/route.ts b/x-pack/plugins/observability_solution/apm/server/routes/time_range_metadata/route.ts index eaff14fd7c647..bf91af259249e 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/time_range_metadata/route.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/time_range_metadata/route.ts @@ -8,10 +8,10 @@ import { toBooleanRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; import { TimeRangeMetadata } from '../../../common/time_range_metadata'; import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; -import { getDocumentSources } from '../../lib/helpers/get_document_sources'; import { getIsUsingServiceDestinationMetrics } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; +import { getApmDataAccessServices } from '../../lib/helpers/get_apm_data_access_services'; export const timeRangeMetadataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/time_range_metadata', @@ -31,6 +31,7 @@ export const timeRangeMetadataRoute = createApmServerRoute({ }, handler: async (resources): Promise<TimeRangeMetadata> => { const apmEventClient = await getApmEventClient(resources); + const apmDataAccessServices = await getApmDataAccessServices({ apmEventClient, ...resources }); const { query: { @@ -51,8 +52,7 @@ export const timeRangeMetadataRoute = createApmServerRoute({ end, kuery, }), - getDocumentSources({ - apmEventClient, + apmDataAccessServices.getDocumentSources({ start, end, kuery, diff --git a/x-pack/plugins/observability_solution/apm/server/utils/with_apm_span.ts b/x-pack/plugins/observability_solution/apm/server/utils/with_apm_span.ts index 1343970f04a3f..f852c8cc102b0 100644 --- a/x-pack/plugins/observability_solution/apm/server/utils/with_apm_span.ts +++ b/x-pack/plugins/observability_solution/apm/server/utils/with_apm_span.ts @@ -4,22 +4,4 @@ * 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 withApmSpan<T>( - optionsOrName: SpanOptions | string, - cb: () => Promise<T> -): Promise<T> { - const options = parseSpanOptions(optionsOrName); - - const optionsWithDefaults = { - ...(options.intercept ? {} : { type: 'plugin:apm' }), - ...options, - labels: { - plugin: 'apm', - ...options.labels, - }, - }; - - return withSpan(optionsWithDefaults, cb); -} +export { withApmSpan } from '@kbn/apm-data-access-plugin/server/utils'; diff --git a/x-pack/plugins/observability_solution/apm/tsconfig.json b/x-pack/plugins/observability_solution/apm/tsconfig.json index f9db8e36fd63d..b05447bc19e6c 100644 --- a/x-pack/plugins/observability_solution/apm/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm/tsconfig.json @@ -64,13 +64,11 @@ "@kbn/rison", "@kbn/config-schema", "@kbn/repo-info", - "@kbn/apm-utils", "@kbn/apm-data-view", "@kbn/logging", "@kbn/std", "@kbn/core-saved-objects-api-server-mocks", "@kbn/field-types", - "@kbn/core-http-server-mocks", "@kbn/babel-register", "@kbn/core-saved-objects-migration-server-internal", "@kbn/core-elasticsearch-server", @@ -127,7 +125,9 @@ "@kbn/shared-ux-page-no-data-config-types", "@kbn/react-hooks", "@kbn/server-route-repository-utils", - "@kbn/core-analytics-browser" + "@kbn/core-analytics-browser", + "@kbn/apm-types", + "@kbn/entities-schema" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/apm_base_doc.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/apm_base_doc.ts index e39d90ff58b99..ffe2c708b2e59 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/apm_base_doc.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/apm_base_doc.ts @@ -5,19 +5,4 @@ * 2.0. */ -import { Observer } from './fields/observer'; - -// all documents types extend APMBaseDoc and inherit all properties -export interface APMBaseDoc { - '@timestamp': string; - agent: { - name: string; - version: string; - }; - parent?: { id: string }; // parent ID is not available on root transactions - trace?: { id: string }; - labels?: { - [key: string]: string | number | boolean; - }; - observer?: Observer; -} +export type { APMBaseDoc } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/error_raw.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/error_raw.ts index 34e24033db230..f86fb663dac4f 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/error_raw.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/error_raw.ts @@ -5,68 +5,4 @@ * 2.0. */ -import { APMBaseDoc } from './apm_base_doc'; -import { Container } from './fields/container'; -import { Host } from './fields/host'; -import { Http } from './fields/http'; -import { Kubernetes } from './fields/kubernetes'; -import { Page } from './fields/page'; -import { Process } from './fields/process'; -import { Service } from './fields/service'; -import { Stackframe } from './fields/stackframe'; -import { TimestampUs } from './fields/timestamp_us'; -import { Url } from './fields/url'; -import { User } from './fields/user'; - -export interface Processor { - name: 'error'; - event: 'error'; -} - -export interface Exception { - attributes?: { - response?: string; - }; - code?: string; - message?: string; // either message or type are given - type?: string; - module?: string; - handled?: boolean; - stacktrace?: Stackframe[]; -} - -export interface Log { - message: string; - stacktrace?: Stackframe[]; -} - -export interface ErrorRaw extends APMBaseDoc { - processor: Processor; - timestamp: TimestampUs; - transaction?: { - id: string; - sampled?: boolean; - type: string; - }; - error: { - id: string; - culprit?: string; - grouping_key: string; - // either exception or log are given - exception?: Exception[]; - page?: Page; // special property for RUM: shared by error and transaction - log?: Log; - stack_trace?: string; - custom?: Record<string, unknown>; - }; - - // Shared by errors and transactions - container?: Container; - host?: Host; - http?: Http; - kubernetes?: Kubernetes; - process?: Process; - service: Service; - url?: Url; - user?: User; -} +export type { ErrorRaw, Log, Exception, Processor } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/event_raw.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/event_raw.ts index 31a1952cdc03d..b0e4f134164bc 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/event_raw.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/event_raw.ts @@ -5,21 +5,4 @@ * 2.0. */ -import { APMBaseDoc } from './apm_base_doc'; -import { TimestampUs } from './fields/timestamp_us'; - -export interface EventRaw extends APMBaseDoc { - timestamp: TimestampUs; - transaction?: { - id: string; - sampled?: boolean; - type: string; - }; - log: { - message?: string; - }; - event: { - action: string; - category: string; - }; -} +export type { EventRaw } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/cloud.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/cloud.ts index bc0c3ea8002ad..b187506ef52d4 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/cloud.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/cloud.ts @@ -5,29 +5,4 @@ * 2.0. */ -export interface Cloud { - availability_zone?: string; - instance?: { - name: string; - id: string; - }; - machine?: { - type: string; - }; - project?: { - id: string; - name: string; - }; - provider?: string; - region?: string; - account?: { - id: string; - name: string; - }; - image?: { - id: string; - }; - service?: { - name: string; - }; -} +export type { Cloud } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/container.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/container.ts index c17517b7c5f2d..fc5f749f46f60 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/container.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/container.ts @@ -5,7 +5,4 @@ * 2.0. */ -export interface Container { - id?: string | null; - image?: string | null; -} +export type { Container } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/event_outcome.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/event_outcome.ts index d0ca41fcba4ed..8ec05dc0274ee 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/event_outcome.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/event_outcome.ts @@ -5,4 +5,4 @@ * 2.0. */ -export type EventOutcome = 'success' | 'failure' | 'unknown'; +export type { EventOutcome } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/faas.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/faas.ts index 1229b8134ac13..1779fc3528132 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/faas.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/faas.ts @@ -5,12 +5,4 @@ * 2.0. */ -export interface Faas { - id: string; - coldstart?: boolean; - execution?: string; - trigger?: { - type?: string; - request_id?: string; - }; -} +export type { Faas } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/host.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/host.ts index 6d1941ff0184c..96cf79ab1d659 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/host.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/host.ts @@ -5,12 +5,4 @@ * 2.0. */ -export interface Host { - architecture?: string; - hostname?: string; - name?: string; - ip?: string; - os?: { - platform?: string; - }; -} +export type { Host } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/http.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/http.ts index 547f117b41326..b2f7ffc6843ef 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/http.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/http.ts @@ -5,8 +5,4 @@ * 2.0. */ -export interface Http { - request?: { method: string; [key: string]: unknown }; - response?: { status_code: number; [key: string]: unknown }; - version?: string; -} +export type { Http } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/kubernetes.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/kubernetes.ts index 5cf0b497dad18..4c7cbf15dd2b2 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/kubernetes.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/kubernetes.ts @@ -5,17 +5,4 @@ * 2.0. */ -export interface Kubernetes { - pod?: { uid?: string | null; [key: string]: unknown }; - namespace?: string; - replicaset?: { - name?: string; - }; - deployment?: { - name?: string; - }; - container?: { - id?: string; - name?: string; - }; -} +export type { Kubernetes } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/observer.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/observer.ts index 81a0bf1e0bfd2..65fab1814a172 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/observer.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/observer.ts @@ -5,12 +5,4 @@ * 2.0. */ -export interface Observer { - ephemeral_id?: string; - hostname?: string; - id?: string; - name?: string; - type?: string; - version: string; - version_major: number; -} +export type { Observer } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/page.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/page.ts index f934435594786..3070d8ab6ec69 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/page.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/page.ts @@ -6,6 +6,4 @@ */ // only for RUM agent: shared by error and transaction -export interface Page { - url: string; -} +export type { Page } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/process.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/process.ts index 10973e3b66a5f..c372c6f3ef959 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/process.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/process.ts @@ -5,9 +5,4 @@ * 2.0. */ -export interface Process { - args?: string[]; - pid: number; - ppid?: number; - title?: string; -} +export type { Process } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/service.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/service.ts index 7158c886e8109..dc7f872117cdc 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/service.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/service.ts @@ -5,23 +5,4 @@ * 2.0. */ -export interface Service { - name: string; - environment?: string; - framework?: { - name: string; - version?: string; - }; - node?: { - name?: string; - }; - runtime?: { - name: string; - version: string; - }; - language?: { - name: string; - version?: string; - }; - version?: string; -} +export type { Service } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/span_links.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/span_links.ts index 5e0028ad58176..05027bfd70bf5 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/span_links.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/span_links.ts @@ -5,7 +5,4 @@ * 2.0. */ -export interface SpanLink { - trace: { id: string }; - span: { id: string }; -} +export type { SpanLink } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/stackframe.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/stackframe.ts index 90d7f20047573..ca357ec00732d 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/stackframe.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/stackframe.ts @@ -5,39 +5,4 @@ * 2.0. */ -interface Line { - column?: number; - number: number; -} - -interface Sourcemap { - error?: string; - updated?: boolean; -} - -interface StackframeBase { - abs_path?: string; - classname?: string; - context?: { - post?: string[]; - pre?: string[]; - }; - exclude_from_grouping?: boolean; - filename?: string; - function?: string; - module?: string; - library_frame?: boolean; - line?: Line; - sourcemap?: Sourcemap; - vars?: { - [key: string]: unknown; - }; -} - -export type StackframeWithLineContext = StackframeBase & { - line: Line & { - context: string; - }; -}; - -export type Stackframe = StackframeBase | StackframeWithLineContext; +export type { StackframeWithLineContext, Stackframe } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/timestamp_us.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/timestamp_us.ts index f6f944b6fe95f..a36b28a35635f 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/timestamp_us.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/timestamp_us.ts @@ -5,6 +5,4 @@ * 2.0. */ -export interface TimestampUs { - us: number; -} +export type { TimestampUs } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/url.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/url.ts index 001d6370e5f06..f30ba85fd474d 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/url.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/url.ts @@ -5,8 +5,4 @@ * 2.0. */ -export interface Url { - domain?: string; - full: string; - original?: string; -} +export type { Url } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user.ts index dcb5fa03dcd5a..a727d61d53005 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user.ts @@ -5,6 +5,4 @@ * 2.0. */ -export interface User { - id: string; -} +export type { User } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user_agent.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user_agent.ts index 884f627353d9b..71eb4bd41e434 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user_agent.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/fields/user_agent.ts @@ -5,16 +5,4 @@ * 2.0. */ -export interface UserAgent { - device?: { - name: string; - }; - name?: string; - original: string; - os?: { - name: string; - version?: string; - full?: string; - }; - version?: string; -} +export type { UserAgent } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/metric_raw.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/metric_raw.ts index d7d015fd21da5..7bd8dcbe6869c 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/metric_raw.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/metric_raw.ts @@ -5,119 +5,4 @@ * 2.0. */ -import { APMBaseDoc } from './apm_base_doc'; -import { Cloud } from './fields/cloud'; -import { Container } from './fields/container'; -import { Host } from './fields/host'; -import { Kubernetes } from './fields/kubernetes'; -import { Service } from './fields/service'; - -type BaseMetric = APMBaseDoc & { - processor: { - name: 'metric'; - event: 'metric'; - }; - cloud?: Cloud; - container?: Container; - kubernetes?: Kubernetes; - service?: Service; - host?: Host; -}; - -type BaseBreakdownMetric = BaseMetric & { - transaction: { - name: string; - type: string; - }; - span: { - self_time: { - count: number; - sum: { - us: number; - }; - }; - }; -}; - -type TransactionBreakdownMetric = BaseBreakdownMetric & { - transaction: { - duration: { - count: number; - sum: { - us: number; - }; - }; - breakdown: { - count: number; - }; - }; -}; - -type SpanBreakdownMetric = BaseBreakdownMetric & { - span: { - type: string; - subtype?: string; - }; -}; - -type SystemMetric = BaseMetric & { - system: unknown; - service: { - node?: { - name: string; - }; - }; -}; - -type CGroupMetric = SystemMetric; -type JVMMetric = SystemMetric & { - jvm: unknown; -}; - -type TransactionDurationMetric = BaseMetric & { - transaction: { - name: string; - type: string; - result?: string; - duration: { - histogram: { - values: number[]; - counts: number[]; - }; - }; - }; - service: { - name: string; - node?: { - name: string; - }; - environment?: string; - version?: string; - }; -}; - -export type SpanDestinationMetric = BaseMetric & { - span: { - destination: { - service: { - resource: string; - response_time: { - count: number; - sum: { - us: number; - }; - }; - }; - }; - }; -}; - -export type MetricRaw = - | BaseMetric - | TransactionBreakdownMetric - | SpanBreakdownMetric - | TransactionDurationMetric - | SpanDestinationMetric - | SystemMetric - | CGroupMetric - | JVMMetric; +export type { MetricRaw } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/span_raw.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/span_raw.ts index 301a4c96dfa35..42427f9d8623e 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/span_raw.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/span_raw.ts @@ -5,75 +5,4 @@ * 2.0. */ -import { APMBaseDoc } from './apm_base_doc'; -import { EventOutcome } from './fields/event_outcome'; -import { Http } from './fields/http'; -import { SpanLink } from './fields/span_links'; -import { Stackframe } from './fields/stackframe'; -import { TimestampUs } from './fields/timestamp_us'; -import { Url } from './fields/url'; - -interface Processor { - name: 'transaction'; - event: 'span'; -} - -export interface SpanRaw extends APMBaseDoc { - processor: Processor; - trace: { id: string }; // trace is required - event?: { outcome?: EventOutcome }; - service: { - name: string; - environment?: string; - }; - span: { - destination?: { - service: { - resource: string; - }; - }; - action?: string; - duration: { us: number }; - id: string; - name: string; - stacktrace?: Stackframe[]; - subtype?: string; - sync?: boolean; - type: string; - http?: { - url?: { - original?: string; - }; - response: { - status_code: number; - }; - method?: string; - }; - db?: { - statement?: string; - type?: string; - }; - message?: { - queue?: { name: string }; - age?: { ms: number }; - body?: string; - headers?: Record<string, unknown>; - }; - composite?: { - count: number; - sum: { us: number }; - compression_strategy: string; - }; - links?: SpanLink[]; - }; - timestamp: TimestampUs; - transaction?: { - id: string; - }; - child?: { id: string[] }; - code?: { - stacktrace?: string; - }; - http?: Http; - url?: Url; -} +export type { SpanRaw } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/transaction_raw.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/transaction_raw.ts index 4046bb9470fb7..adfc536ab06cf 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/transaction_raw.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/raw/transaction_raw.ts @@ -5,74 +5,4 @@ * 2.0. */ -import { APMBaseDoc } from './apm_base_doc'; -import { Cloud } from './fields/cloud'; -import { Container } from './fields/container'; -import { EventOutcome } from './fields/event_outcome'; -import { Host } from './fields/host'; -import { Http } from './fields/http'; -import { Kubernetes } from './fields/kubernetes'; -import { Page } from './fields/page'; -import { Process } from './fields/process'; -import { Service } from './fields/service'; -import { TimestampUs } from './fields/timestamp_us'; -import { Url } from './fields/url'; -import { User } from './fields/user'; -import { UserAgent } from './fields/user_agent'; -import { Faas } from './fields/faas'; -import { SpanLink } from './fields/span_links'; - -interface Processor { - name: 'transaction'; - event: 'transaction'; -} - -export interface TransactionRaw extends APMBaseDoc { - processor: Processor; - timestamp: TimestampUs; - trace: { id: string }; // trace is required - event?: { outcome?: EventOutcome }; - transaction: { - duration: { us: number }; - id: string; - marks?: { - // "agent": not defined by APM Server - only sent by RUM agent - agent?: { - [name: string]: number; - }; - }; - name?: string; - page?: Page; // special property for RUM: shared by error and transaction - result?: string; - sampled: boolean; - span_count?: { - started?: number; - dropped?: number; - }; - type: string; - custom?: Record<string, unknown>; - message?: { - queue?: { name: string }; - age?: { ms: number }; - body?: string; - headers?: Record<string, unknown>; - }; - }; - - // Shared by errors and transactions - container?: Container; - ecs?: { version?: string }; - host?: Host; - http?: Http; - kubernetes?: Kubernetes; - process?: Process; - service: Service; - url?: Url; - user?: User; - user_agent?: UserAgent; - cloud?: Cloud; - faas?: Faas; - span?: { - links?: SpanLink[]; - }; -} +export type { TransactionRaw } from '@kbn/apm-types/es_schemas_raw'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/apm_error.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/apm_error.ts index 13521d90a84aa..a4c5c7fe0867c 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/apm_error.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/apm_error.ts @@ -5,9 +5,4 @@ * 2.0. */ -import { ErrorRaw } from '../raw/error_raw'; -import { Agent } from './fields/agent'; - -export interface APMError extends ErrorRaw { - agent: Agent; -} +export type { APMError } from '@kbn/apm-types/es_schemas_ui'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/event.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/event.ts index 8d9fccea1c8bf..de726d110914c 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/event.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/event.ts @@ -5,9 +5,4 @@ * 2.0. */ -import { EventRaw } from '../raw/event_raw'; -import { Agent } from './fields/agent'; - -export interface Event extends EventRaw { - agent: Agent; -} +export type { Event } from '@kbn/apm-types/es_schemas_ui'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/fields/agent.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/fields/agent.ts index 67f4a6b2ba10b..b53da484ec747 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/fields/agent.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/fields/agent.ts @@ -5,12 +5,9 @@ * 2.0. */ -import type { AgentName } from '@kbn/elastic-agent-utils'; - -export type { ElasticAgentName, OpenTelemetryAgentName, AgentName } from '@kbn/elastic-agent-utils'; - -export interface Agent { - ephemeral_id?: string; - name: AgentName; - version: string; -} +export type { + Agent, + ElasticAgentName, + OpenTelemetryAgentName, + AgentName, +} from '@kbn/apm-types/es_schemas_ui'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/metric.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/metric.ts index b06a686c23ef7..d2c9833391564 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/metric.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/metric.ts @@ -5,6 +5,4 @@ * 2.0. */ -import { MetricRaw } from '../raw/metric_raw'; - -export type Metric = MetricRaw; +export type { Metric } from '@kbn/apm-types/es_schemas_ui'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/span.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/span.ts index cfee36de51429..92e2fd44eabd8 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/span.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/span.ts @@ -5,9 +5,4 @@ * 2.0. */ -import { SpanRaw } from '../raw/span_raw'; -import { Agent } from './fields/agent'; - -export interface Span extends SpanRaw { - agent: Agent; -} +export type { Span } from '@kbn/apm-types/es_schemas_ui'; diff --git a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/transaction.ts b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/transaction.ts index 2dfbc860ec05a..db8012401f398 100644 --- a/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/transaction.ts +++ b/x-pack/plugins/observability_solution/apm/typings/es_schemas/ui/transaction.ts @@ -5,18 +5,4 @@ * 2.0. */ -import { TransactionRaw } from '../raw/transaction_raw'; -import { Agent } from './fields/agent'; - -// Make `transaction.name` required instead of optional. -// `transaction.name` can be missing in Elasticsearch but the UI will only aggregate on transactions with a name, -// and thus it doesn't make sense to treat it as optional -type InnerTransaction = TransactionRaw['transaction']; -interface InnerTransactionWithName extends InnerTransaction { - name: string; -} - -export interface Transaction extends TransactionRaw { - agent: Agent; - transaction: InnerTransactionWithName; -} +export type { Transaction } from '@kbn/apm-types/es_schemas_ui'; diff --git a/x-pack/plugins/observability_solution/apm_data_access/common/data_source.ts b/x-pack/plugins/observability_solution/apm_data_access/common/data_source.ts new file mode 100644 index 0000000000000..b94af60d802b4 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/common/data_source.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 { ApmDocumentType } from './document_type'; +import type { RollupInterval } from './rollup'; + +type AnyApmDocumentType = + | ApmDocumentType.ServiceTransactionMetric + | ApmDocumentType.TransactionMetric + | ApmDocumentType.TransactionEvent + | ApmDocumentType.ServiceDestinationMetric + | ApmDocumentType.ServiceSummaryMetric + | ApmDocumentType.ErrorEvent + | ApmDocumentType.SpanEvent; + +export interface ApmDataSource<TDocumentType extends AnyApmDocumentType = AnyApmDocumentType> { + rollupInterval: RollupInterval; + documentType: TDocumentType; +} + +export type ApmDataSourceWithSummary<T extends AnyApmDocumentType = AnyApmDocumentType> = + ApmDataSource<T> & { + hasDurationSummaryField: boolean; + hasDocs: boolean; + }; diff --git a/x-pack/plugins/observability_solution/apm_data_access/common/document_type.ts b/x-pack/plugins/observability_solution/apm_data_access/common/document_type.ts new file mode 100644 index 0000000000000..e8a29e8d08c43 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/common/document_type.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum ApmDocumentType { + TransactionMetric = 'transactionMetric', + ServiceTransactionMetric = 'serviceTransactionMetric', + TransactionEvent = 'transactionEvent', + ServiceDestinationMetric = 'serviceDestinationMetric', + ServiceSummaryMetric = 'serviceSummaryMetric', + ErrorEvent = 'error', + SpanEvent = 'span', +} + +export type ApmServiceTransactionDocumentType = + | ApmDocumentType.ServiceTransactionMetric + | ApmDocumentType.TransactionMetric + | ApmDocumentType.TransactionEvent; + +export type ApmTransactionDocumentType = + | ApmDocumentType.TransactionMetric + | ApmDocumentType.TransactionEvent; diff --git a/x-pack/plugins/observability_solution/apm_data_access/common/index.ts b/x-pack/plugins/observability_solution/apm_data_access/common/index.ts index 19d4963c3cec5..df61d8d9c3702 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/common/index.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/common/index.ts @@ -7,3 +7,17 @@ export const PLUGIN_ID = 'apmDataAccess'; export const PLUGIN_NAME = 'apmDataAccess'; + +export type { ApmDataSource, ApmDataSourceWithSummary } from './data_source'; +export { + ApmDocumentType, + type ApmServiceTransactionDocumentType, + type ApmTransactionDocumentType, +} from './document_type'; + +export type { TimeRangeMetadata } from './time_range_metadata'; + +export { getPreferredBucketSizeAndDataSource } from './utils/get_preferred_bucket_size_and_data_source'; +export { getBucketSize } from './utils/get_bucket_size'; + +export { RollupInterval } from './rollup'; diff --git a/x-pack/plugins/observability_solution/apm_data_access/common/rollup.ts b/x-pack/plugins/observability_solution/apm_data_access/common/rollup.ts new file mode 100644 index 0000000000000..500337e3fc06b --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/common/rollup.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 RollupInterval { + OneMinute = '1m', + TenMinutes = '10m', + SixtyMinutes = '60m', + None = 'none', +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/common/time_range_metadata.ts b/x-pack/plugins/observability_solution/apm_data_access/common/time_range_metadata.ts new file mode 100644 index 0000000000000..f13ab5a89d6d1 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/common/time_range_metadata.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 { ApmDataSource } from './data_source'; + +export interface TimeRangeMetadata { + isUsingServiceDestinationMetrics: boolean; + sources: Array<ApmDataSource & { hasDocs: boolean; hasDurationSummaryField: boolean }>; +} diff --git a/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/calculate_auto.test.ts b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/calculate_auto.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/calculate_auto.test.ts rename to x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/calculate_auto.test.ts diff --git a/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/calculate_auto.ts b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/calculate_auto.ts new file mode 100644 index 0000000000000..720a924dddcb5 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/calculate_auto.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 moment, { Duration } from 'moment'; +const d = moment.duration; + +type RoundingRule = [Duration, Duration]; + +const roundingRules: RoundingRule[] = [ + [d(500, 'ms'), d(100, 'ms')], + [d(5, 'second'), d(1, 'second')], + [d(7.5, 'second'), d(5, 'second')], + [d(15, 'second'), d(10, 'second')], + [d(45, 'second'), d(30, 'second')], + [d(3, 'minute'), d(1, 'minute')], + [d(9, 'minute'), d(5, 'minute')], + [d(20, 'minute'), d(10, 'minute')], + [d(45, 'minute'), d(30, 'minute')], + [d(2, 'hour'), d(1, 'hour')], + [d(6, 'hour'), d(3, 'hour')], + [d(24, 'hour'), d(12, 'hour')], + [d(1, 'week'), d(1, 'd')], + [d(3, 'week'), d(1, 'week')], + [d(1, 'year'), d(1, 'month')], + [d(Infinity, 'year'), d(1, 'year')], +]; + +const revRoundingRules = [...roundingRules].reverse(); + +type CheckFunction = (bound: Duration, interval: Duration, target: number) => Duration | null; + +function find(rules: RoundingRule[], check: CheckFunction, last?: boolean) { + function pick(buckets: number, duration: Duration): Duration | null { + const target = duration.asMilliseconds() / buckets; + let lastResp: Duration | null = null; + + for (let i = 0; i < rules.length; i++) { + const [bound, interval] = rules[i]; + const resp = check(bound, interval, target); + + if (resp == null) { + if (!last) continue; + if (lastResp) return lastResp; + break; + } + + if (!last) return resp; + lastResp = resp; + } + + // fallback to just a number of milliseconds, ensure ms is >= 1 + const ms = Math.max(Math.floor(target), 1); + return moment.duration(ms, 'ms'); + } + + return (buckets: number, duration: Duration): Duration | undefined => { + const interval = pick(buckets, duration); + if (interval) return moment.duration(interval); + }; +} + +export const calculateAuto = { + near: find( + revRoundingRules, + function near(bound, interval, target) { + if (bound.asMilliseconds() > target) return interval; + return null; + }, + true + ), + + lessThan: find(revRoundingRules, function lessThan(_bound, interval, target) { + if (interval.asMilliseconds() < target) return interval; + return null; + }), + + atLeast: find(revRoundingRules, function atLeast(_bound, interval, target) { + if (interval.asMilliseconds() <= target) return interval; + return null; + }), +}; diff --git a/x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/get_bucket_size.test.ts b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/get_bucket_size.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/common/utils/get_bucket_size/get_bucket_size.test.ts rename to x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/get_bucket_size.test.ts diff --git a/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/index.ts new file mode 100644 index 0000000000000..a2946137cf911 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_bucket_size/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 moment from 'moment'; +import { calculateAuto } from './calculate_auto'; + +export function getBucketSize({ + start, + end, + numBuckets = 50, + minBucketSize, +}: { + start: number; + end: number; + numBuckets?: number; + minBucketSize?: number; +}) { + const duration = moment.duration(end - start, 'ms'); + const bucketSize = Math.max( + calculateAuto.near(numBuckets, duration)?.asSeconds() ?? 0, + minBucketSize || 1 + ); + + return { bucketSize, intervalString: `${bucketSize}s` }; +} diff --git a/x-pack/plugins/observability_solution/apm/common/utils/get_preferred_bucket_size_and_data_source.test.ts b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/common/utils/get_preferred_bucket_size_and_data_source.test.ts rename to x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.test.ts diff --git a/x-pack/plugins/observability_solution/apm/common/utils/get_preferred_bucket_size_and_data_source.ts b/x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/common/utils/get_preferred_bucket_size_and_data_source.ts rename to x-pack/plugins/observability_solution/apm_data_access/common/utils/get_preferred_bucket_size_and_data_source.ts diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/index.ts b/x-pack/plugins/observability_solution/apm_data_access/server/index.ts index f322ff2eb910d..9dfcc5a454cc5 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/index.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/index.ts @@ -82,4 +82,15 @@ export async function plugin(initializerContext: PluginInitializerContext) { return new ApmDataAccessPlugin(initializerContext); } -export type { ApmDataAccessPluginSetup, ApmDataAccessPluginStart } from './types'; +export type { + ApmDataAccessPluginSetup, + ApmDataAccessPluginStart, + ApmDataAccessServices, + ApmDataAccessServicesParams, + APMEventClientConfig, + APMEventESSearchRequest, + APMLogEventESSearchRequest, + DocumentSourcesRequest, +} from './types'; + +export { APMEventClient } from './lib/helpers'; diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/call_async_with_debug.ts similarity index 91% rename from x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts rename to x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/call_async_with_debug.ts index f1899b8f4c2db..9fbd6eb4cefa5 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/call_async_with_debug.ts @@ -8,11 +8,11 @@ /* eslint-disable no-console */ import chalk from 'chalk'; -import { KibanaRequest } from '@kbn/core/server'; +import type { KibanaRequest } from '@kbn/core/server'; import { RequestStatus } from '@kbn/inspector-plugin/common'; import { WrappedElasticsearchClientError } from '@kbn/observability-plugin/server'; import { getInspectResponse } from '@kbn/observability-shared-plugin/common'; -import { inspectableEsQueriesMap } from '../../../routes/apm_routes/register_apm_server_routes'; +import type { InspectResponse } from '@kbn/observability-plugin/typings/common'; function formatObj(obj: Record<string, any>) { return JSON.stringify(obj, null, 2); @@ -26,6 +26,7 @@ export async function callAsyncWithDebug<T>({ requestParams, operationName, isCalledWithInternalUser, + inspectableEsQueriesMap = new WeakMap<KibanaRequest, InspectResponse>(), }: { cb: () => Promise<T>; getDebugMessage: () => { body: string; title: string }; @@ -34,6 +35,7 @@ export async function callAsyncWithDebug<T>({ requestParams: Record<string, any>; operationName: string; isCalledWithInternalUser: boolean; // only allow inspection of queries that were retrieved with credentials of the end user + inspectableEsQueriesMap?: WeakMap<KibanaRequest, InspectResponse>; }): Promise<T> { if (!debug) { return cb(); diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts rename to x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.test.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.test.ts similarity index 91% rename from x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.test.ts rename to x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.test.ts index 253a0993d1a8c..09fca8ab2331b 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.test.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { APMEventESSearchRequest } from '.'; -import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; +import type { APMIndices } from '../../../..'; +import type { APMEventESSearchRequest } from '.'; import { getRequestBase } from './get_request_base'; describe('getRequestBase', () => { diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts similarity index 89% rename from x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts rename to x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts index 8a305c9601de5..54cd8e9eeb9a8 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/get_request_base.ts @@ -6,12 +6,12 @@ */ import type { ESFilter } from '@kbn/es-types'; -import type { APMIndices } from '@kbn/apm-data-access-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { uniq } from 'lodash'; -import { ApmDataSource } from '../../../../../common/data_source'; -import { PROCESSOR_EVENT } from '../../../../../common/es_fields/apm'; +import { PROCESSOR_EVENT } from '@kbn/apm-types/es_fields'; +import type { APMIndices } from '../../../..'; import { getConfigForDocumentType, getProcessorEventForDocumentType } from '../document_type'; +import type { ApmDataSource } from '../../../../../common/data_source'; const processorEventIndexMap = { [ProcessorEvent.transaction]: 'transaction', diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts rename to x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.ts new file mode 100644 index 0000000000000..3c195b752c854 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/create_apm_event_client/index.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 type { + EqlSearchRequest, + FieldCapsRequest, + FieldCapsResponse, + MsearchMultisearchBody, + MsearchMultisearchHeader, + TermsEnumRequest, + TermsEnumResponse, +} from '@elastic/elasticsearch/lib/api/types'; +import type { ElasticsearchClient, KibanaRequest } from '@kbn/core/server'; +import type { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { unwrapEsResponse } from '@kbn/observability-plugin/server'; +import { compact, omit } from 'lodash'; +import { ValuesType } from 'utility-types'; +import type { APMError, Metric, Span, Transaction, Event } from '@kbn/apm-types/es_schemas_ui'; +import type { InspectResponse } from '@kbn/observability-plugin/typings/common'; +import { withApmSpan } from '../../../../utils'; +import type { ApmDataSource } from '../../../../../common/data_source'; +import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort'; +import { callAsyncWithDebug, getDebugBody, getDebugTitle } from '../call_async_with_debug'; +import type { ProcessorEventOfDocumentType } from '../document_type'; +import type { APMIndices } from '../../../..'; +import { getRequestBase, processorEventsToIndex } from './get_request_base'; + +export type APMEventESSearchRequest = Omit<ESSearchRequest, 'index'> & { + apm: { + includeLegacyData?: boolean; + } & ({ events: ProcessorEvent[] } | { sources: ApmDataSource[] }); + body: { + size: number; + track_total_hits: boolean | number; + }; +}; + +export type APMLogEventESSearchRequest = Omit<ESSearchRequest, 'index'> & { + body: { + size: number; + track_total_hits: boolean | number; + }; +}; + +type APMEventWrapper<T> = Omit<T, 'index'> & { + apm: { events: ProcessorEvent[] }; +}; + +type APMEventTermsEnumRequest = APMEventWrapper<TermsEnumRequest>; +type APMEventEqlSearchRequest = APMEventWrapper<EqlSearchRequest>; +type APMEventFieldCapsRequest = APMEventWrapper<FieldCapsRequest>; + +type TypeOfProcessorEvent<T extends ProcessorEvent> = { + [ProcessorEvent.error]: APMError; + [ProcessorEvent.transaction]: Transaction; + [ProcessorEvent.span]: Span; + [ProcessorEvent.metric]: Metric; +}[T]; + +type TypedLogEventSearchResponse<TParams extends APMLogEventESSearchRequest> = + InferSearchResponseOf<Event, TParams>; + +type TypedSearchResponse<TParams extends APMEventESSearchRequest> = InferSearchResponseOf< + TypeOfProcessorEvent< + TParams['apm'] extends { events: ProcessorEvent[] } + ? ValuesType<TParams['apm']['events']> + : TParams['apm'] extends { sources: ApmDataSource[] } + ? ProcessorEventOfDocumentType<ValuesType<TParams['apm']['sources']>['documentType']> + : never + >, + TParams +>; + +interface TypedMSearchResponse<TParams extends APMEventESSearchRequest> { + responses: Array<TypedSearchResponse<TParams>>; +} + +export interface APMEventClientConfig { + esClient: ElasticsearchClient; + debug: boolean; + request: KibanaRequest; + indices: APMIndices; + options: { + includeFrozen: boolean; + inspectableEsQueriesMap?: WeakMap<KibanaRequest, InspectResponse>; + }; +} + +export class APMEventClient { + private readonly esClient: ElasticsearchClient; + private readonly debug: boolean; + private readonly request: KibanaRequest; + public readonly indices: APMIndices; + private readonly includeFrozen: boolean; + private readonly inspectableEsQueriesMap?: WeakMap<KibanaRequest, InspectResponse>; + + constructor(config: APMEventClientConfig) { + this.esClient = config.esClient; + this.debug = config.debug; + this.request = config.request; + this.indices = config.indices; + this.includeFrozen = config.options.includeFrozen; + this.inspectableEsQueriesMap = config.options.inspectableEsQueriesMap; + } + + private callAsyncWithDebug<T extends { body: any }>({ + requestType, + params, + cb, + operationName, + }: { + requestType: string; + params: Record<string, any>; + cb: (requestOpts: { signal: AbortSignal; meta: true }) => Promise<T>; + operationName: string; + }): Promise<T['body']> { + return callAsyncWithDebug({ + getDebugMessage: () => ({ + body: getDebugBody({ + params, + requestType, + operationName, + }), + title: getDebugTitle(this.request), + }), + isCalledWithInternalUser: false, + debug: this.debug, + request: this.request, + operationName, + requestParams: params, + inspectableEsQueriesMap: this.inspectableEsQueriesMap, + cb: () => { + const controller = new AbortController(); + + const promise = withApmSpan(operationName, () => { + return cancelEsRequestOnAbort( + cb({ signal: controller.signal, meta: true }), + this.request, + controller + ); + }); + + return unwrapEsResponse(promise); + }, + }); + } + + async search<TParams extends APMEventESSearchRequest>( + operationName: string, + params: TParams + ): Promise<TypedSearchResponse<TParams>> { + const { index, filters } = getRequestBase({ + apm: params.apm, + indices: this.indices, + }); + + const searchParams = { + ...omit(params, 'apm', 'body'), + index, + body: { + ...params.body, + query: { + bool: { + filter: filters, + must: compact([params.body.query]), + }, + }, + }, + ...(this.includeFrozen ? { ignore_throttled: false } : {}), + ignore_unavailable: true, + preference: 'any', + expand_wildcards: ['open' as const, 'hidden' as const], + }; + + return this.callAsyncWithDebug({ + cb: (opts) => + this.esClient.search(searchParams, opts) as unknown as Promise<{ + body: TypedSearchResponse<TParams>; + }>, + operationName, + params: searchParams, + requestType: 'search', + }); + } + + async logEventSearch<TParams extends APMLogEventESSearchRequest>( + operationName: string, + params: TParams + ): Promise<TypedLogEventSearchResponse<TParams>> { + // Reusing indices configured for errors since both events and errors are stored as logs. + const index = processorEventsToIndex([ProcessorEvent.error], this.indices); + + const searchParams = { + ...omit(params, 'body'), + index, + body: { + ...params.body, + query: { + bool: { + must: compact([params.body.query]), + }, + }, + }, + ...(this.includeFrozen ? { ignore_throttled: false } : {}), + ignore_unavailable: true, + preference: 'any', + expand_wildcards: ['open' as const, 'hidden' as const], + }; + + return this.callAsyncWithDebug({ + cb: (opts) => + this.esClient.search(searchParams, opts) as unknown as Promise<{ + body: TypedLogEventSearchResponse<TParams>; + }>, + operationName, + params: searchParams, + requestType: 'search', + }); + } + + async msearch<TParams extends APMEventESSearchRequest>( + operationName: string, + ...allParams: TParams[] + ): Promise<TypedMSearchResponse<TParams>> { + const searches = allParams + .map((params) => { + const { index, filters } = getRequestBase({ + apm: params.apm, + indices: this.indices, + }); + + const searchParams: [MsearchMultisearchHeader, MsearchMultisearchBody] = [ + { + index, + preference: 'any', + ...(this.includeFrozen ? { ignore_throttled: false } : {}), + ignore_unavailable: true, + expand_wildcards: ['open' as const, 'hidden' as const], + }, + { + ...omit(params, 'apm', 'body'), + ...params.body, + query: { + bool: { + filter: compact([params.body.query, ...filters]), + }, + }, + }, + ]; + + return searchParams; + }) + .flat(); + + return this.callAsyncWithDebug({ + cb: (opts) => + this.esClient.msearch( + { + searches, + }, + opts + ) as unknown as Promise<{ + body: TypedMSearchResponse<TParams>; + }>, + operationName, + params: searches, + requestType: 'msearch', + }); + } + + async eqlSearch(operationName: string, params: APMEventEqlSearchRequest) { + const index = processorEventsToIndex(params.apm.events, this.indices); + + const requestParams = { + ...omit(params, 'apm'), + index, + }; + + return this.callAsyncWithDebug({ + operationName, + requestType: 'eql_search', + params: requestParams, + cb: (opts) => this.esClient.eql.search(requestParams, opts), + }); + } + + async fieldCaps( + operationName: string, + params: APMEventFieldCapsRequest + ): Promise<FieldCapsResponse> { + const index = processorEventsToIndex(params.apm.events, this.indices); + + const requestParams = { + ...omit(params, 'apm'), + index, + }; + + return this.callAsyncWithDebug({ + operationName, + requestType: '_field_caps', + params: requestParams, + cb: (opts) => this.esClient.fieldCaps(requestParams, opts), + }); + } + + async termsEnum( + operationName: string, + params: APMEventTermsEnumRequest + ): Promise<TermsEnumResponse> { + const index = processorEventsToIndex(params.apm.events, this.indices); + + const requestParams = { + ...omit(params, 'apm'), + index: index.join(','), + }; + + return this.callAsyncWithDebug({ + operationName, + requestType: '_terms_enum', + params: requestParams, + cb: (opts) => this.esClient.termsEnum(requestParams, opts), + }); + } + + getIndicesFromProcessorEvent(processorEvent: ProcessorEvent) { + return processorEventsToIndex([processorEvent], this.indices); + } +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/document_type.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/document_type.ts new file mode 100644 index 0000000000000..c142fa932ff42 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/create_es_client/document_type.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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { termQuery } from '@kbn/observability-plugin/server'; +import { METRICSET_INTERVAL, METRICSET_NAME } from '@kbn/apm-types/es_fields'; +import { ApmDocumentType } from '../../../../common/document_type'; +import { RollupInterval } from '../../../../common/rollup'; +import { getDocumentTypeFilterForServiceDestinationStatistics } from '../spans/get_is_using_service_destination_metrics'; +import { getBackwardCompatibleDocumentTypeFilter } from '../transactions'; + +const defaultRollupIntervals = [ + RollupInterval.OneMinute, + RollupInterval.TenMinutes, + RollupInterval.SixtyMinutes, +]; + +function getDefaultFilter(metricsetName: string, rollupInterval: RollupInterval) { + return [ + ...termQuery(METRICSET_NAME, metricsetName), + ...termQuery(METRICSET_INTERVAL, rollupInterval), + ]; +} + +const documentTypeConfigMap = { + [ApmDocumentType.ServiceTransactionMetric]: { + processorEvent: ProcessorEvent.metric, + + getQuery: (rollupInterval: RollupInterval) => ({ + bool: { + filter: getDefaultFilter('service_transaction', rollupInterval), + }, + }), + rollupIntervals: defaultRollupIntervals, + }, + [ApmDocumentType.ServiceSummaryMetric]: { + processorEvent: ProcessorEvent.metric, + getQuery: (rollupInterval: RollupInterval) => ({ + bool: { + filter: getDefaultFilter('service_summary', rollupInterval), + }, + }), + rollupIntervals: defaultRollupIntervals, + }, + [ApmDocumentType.TransactionMetric]: { + processorEvent: ProcessorEvent.metric, + getQuery: (rollupInterval: RollupInterval) => ({ + bool: { + filter: + rollupInterval === RollupInterval.OneMinute + ? getBackwardCompatibleDocumentTypeFilter(true) + : getDefaultFilter('transaction', rollupInterval), + }, + }), + rollupIntervals: defaultRollupIntervals, + }, + [ApmDocumentType.TransactionEvent]: { + processorEvent: ProcessorEvent.transaction, + rollupIntervals: [RollupInterval.None], + }, + [ApmDocumentType.ServiceDestinationMetric]: { + processorEvent: ProcessorEvent.metric, + rollupIntervals: defaultRollupIntervals, + getQuery: (rollupInterval: RollupInterval) => ({ + bool: { + filter: + rollupInterval === RollupInterval.OneMinute + ? getDocumentTypeFilterForServiceDestinationStatistics(true) + : getDefaultFilter('service_destination', rollupInterval), + }, + }), + }, + [ApmDocumentType.ErrorEvent]: { + processorEvent: ProcessorEvent.error, + rollupIntervals: [RollupInterval.None], + }, + [ApmDocumentType.SpanEvent]: { + processorEvent: ProcessorEvent.span, + rollupIntervals: [RollupInterval.None], + }, +} as const; + +type DocumentTypeConfigOf<TApmDocumentType extends ApmDocumentType> = + (typeof documentTypeConfigMap)[TApmDocumentType]; + +export function getConfigForDocumentType<TApmDocumentType extends ApmDocumentType>( + docType: TApmDocumentType +): DocumentTypeConfigOf<TApmDocumentType> { + return documentTypeConfigMap[docType]; +} + +export type ProcessorEventOfDocumentType<TApmDocumentType extends ApmDocumentType> = + DocumentTypeConfigOf<TApmDocumentType>['processorEvent']; + +export function getProcessorEventForDocumentType<TApmDocumentType extends ApmDocumentType>( + documentType: TApmDocumentType +): ProcessorEventOfDocumentType<TApmDocumentType> { + return getConfigForDocumentType(documentType).processorEvent; +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/index.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/index.ts new file mode 100644 index 0000000000000..30a2ff30d98ee --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getDocumentTypeFilterForServiceDestinationStatistics } from './spans/get_is_using_service_destination_metrics'; +export { getBackwardCompatibleDocumentTypeFilter } from './transactions'; +export { + APMEventClient, + type APMEventESSearchRequest, + type APMEventClientConfig, + type APMLogEventESSearchRequest, +} from './create_es_client/create_apm_event_client'; + +export { + callAsyncWithDebug, + getDebugBody, + getDebugTitle, +} from './create_es_client/call_async_with_debug'; + +export { cancelEsRequestOnAbort } from './create_es_client/cancel_es_request_on_abort'; diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts new file mode 100644 index 0000000000000..de895259edecb --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/spans/get_is_using_service_destination_metrics.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 { termQuery, termsQuery } from '@kbn/observability-plugin/server'; +import { METRICSET_NAME, METRICSET_INTERVAL } from '@kbn/apm-types/es_fields'; +import { RollupInterval } from '../../../../common/rollup'; + +export function getDocumentTypeFilterForServiceDestinationStatistics( + searchServiceDestinationMetrics: boolean +) { + return searchServiceDestinationMetrics + ? [ + { + bool: { + filter: termQuery(METRICSET_NAME, 'service_destination'), + must_not: [ + ...termsQuery( + METRICSET_INTERVAL, + RollupInterval.TenMinutes, + RollupInterval.SixtyMinutes + ), + ], + }, + }, + ] + : []; +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/transactions/index.ts b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/transactions/index.ts new file mode 100644 index 0000000000000..c93d549e2b1dd --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/lib/helpers/transactions/index.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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + TRANSACTION_DURATION_HISTOGRAM, + METRICSET_INTERVAL, + METRICSET_NAME, + TRANSACTION_DURATION_SUMMARY, +} from '@kbn/apm-types/es_fields'; +import { existsQuery, termQuery, termsQuery } from '@kbn/observability-plugin/server'; +import { RollupInterval } from '../../../../common/rollup'; + +// The function returns Document type filter for 1m Transaction Metrics +export function getBackwardCompatibleDocumentTypeFilter(searchAggregatedTransactions: boolean) { + return searchAggregatedTransactions + ? [ + { + bool: { + filter: [...existsQuery(TRANSACTION_DURATION_HISTOGRAM)], + must_not: [ + ...termsQuery( + METRICSET_INTERVAL, + RollupInterval.TenMinutes, + RollupInterval.SixtyMinutes + ), + ...termQuery(METRICSET_NAME, 'service_transaction'), + ], + }, + }, + ] + : []; +} + +export function isDurationSummaryNotSupportedFilter(): QueryDslQueryContainer { + return { + bool: { + must_not: [...existsQuery(TRANSACTION_DURATION_SUMMARY)], + }, + }; +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts b/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts index bba13bc6fea36..71b878794180f 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/plugin.ts @@ -19,6 +19,7 @@ import { apmIndicesSavedObjectDefinition, getApmIndicesSavedObject, } from './saved_objects/apm_indices'; +import { getServices } from './services/get_services'; export class ApmDataAccessPlugin implements Plugin<ApmDataAccessPluginSetup, ApmDataAccessPluginStart> @@ -32,16 +33,18 @@ export class ApmDataAccessPlugin const apmDataAccessConfig = this.initContext.config.get<APMDataAccessConfig>(); const apmIndicesFromConfigFile = apmDataAccessConfig.indices; + const getApmIndices = async (savedObjectsClient: SavedObjectsClientContract) => { + const apmIndicesFromSavedObject = await getApmIndicesSavedObject(savedObjectsClient); + return { ...apmIndicesFromConfigFile, ...apmIndicesFromSavedObject }; + }; // register saved object core.savedObjects.registerType(apmIndicesSavedObjectDefinition); // expose return { apmIndicesFromConfigFile, - getApmIndices: async (savedObjectsClient: SavedObjectsClientContract) => { - const apmIndicesFromSavedObject = await getApmIndicesSavedObject(savedObjectsClient); - return { ...apmIndicesFromConfigFile, ...apmIndicesFromSavedObject }; - }, + getApmIndices, + getServices, }; } diff --git a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_document_sources.ts b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts similarity index 92% rename from x-pack/plugins/observability_solution/apm/server/lib/helpers/get_document_sources.ts rename to x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts index ca049603b5c52..3e1c9fcbb1c78 100644 --- a/x-pack/plugins/observability_solution/apm/server/lib/helpers/get_document_sources.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/get_document_sources.ts @@ -6,18 +6,27 @@ */ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ApmDocumentType } from '../../../common/document_type'; import { RollupInterval } from '../../../common/rollup'; -import { APMEventClient } from './create_es_client/create_apm_event_client'; -import { getConfigForDocumentType } from './create_es_client/document_type'; import { TimeRangeMetadata } from '../../../common/time_range_metadata'; -import { isDurationSummaryNotSupportedFilter } from './transactions'; +import { isDurationSummaryNotSupportedFilter } from '../../lib/helpers/transactions'; +import { ApmDocumentType } from '../../../common/document_type'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { getConfigForDocumentType } from '../../lib/helpers/create_es_client/document_type'; const QUERY_INDEX = { DOCUMENT_TYPE: 0, DURATION_SUMMARY_NOT_SUPPORTED: 1, } as const; +export interface DocumentSourcesRequest { + apmEventClient: APMEventClient; + start: number; + end: number; + kuery: string; + enableServiceTransactionMetrics: boolean; + enableContinuousRollups: boolean; +} + const getRequest = ({ documentType, rollupInterval, diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/index.ts b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/index.ts new file mode 100644 index 0000000000000..e8bee4e431dc3 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_document_sources/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 { ApmDataAccessServicesParams } from '../get_services'; +import { getDocumentSources, type DocumentSourcesRequest } from './get_document_sources'; + +export function createGetDocumentSources({ apmEventClient }: ApmDataAccessServicesParams) { + return async ({ + enableContinuousRollups, + enableServiceTransactionMetrics, + end, + kuery, + start, + }: Omit<DocumentSourcesRequest, 'apmEventClient'>) => { + return getDocumentSources({ + apmEventClient, + enableContinuousRollups, + enableServiceTransactionMetrics, + end, + kuery, + start, + }); + }; +} + +export { getDocumentSources, type DocumentSourcesRequest }; diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/services/get_host_names/index.ts b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_host_names/index.ts new file mode 100644 index 0000000000000..10507a0803d9a --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_host_names/index.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 { estypes } from '@elastic/elasticsearch'; +import { rangeQuery } from '@kbn/observability-plugin/server'; +import { HOST_NAME } from '@kbn/apm-types/es_fields'; +import { getBucketSize, type TimeRangeMetadata } from '../../../common'; +import { getPreferredBucketSizeAndDataSource } from '../../../common/utils/get_preferred_bucket_size_and_data_source'; +import { ApmDocumentType } from '../../../common/document_type'; +import type { ApmDataAccessServicesParams } from '../get_services'; + +const MAX_SIZE = 1000; + +export interface HostNamesRequest { + query: estypes.QueryDslQueryContainer; + kuery?: string; + start: number; + end: number; + size?: number; + documentSources: TimeRangeMetadata['sources']; +} + +const suitableTypes = [ApmDocumentType.TransactionMetric]; + +export function createGetHostNames({ apmEventClient }: ApmDataAccessServicesParams) { + return async ({ start, end, size = MAX_SIZE, query, documentSources }: HostNamesRequest) => { + const sourcesToUse = getPreferredBucketSizeAndDataSource({ + sources: documentSources.filter((s) => suitableTypes.includes(s.documentType)), + bucketSizeInSeconds: getBucketSize({ start, end, numBuckets: 50 }).bucketSize, + }); + + const esResponse = await apmEventClient.search('get_apm_host_names', { + apm: { + sources: [ + { + documentType: sourcesToUse.source.documentType, + rollupInterval: sourcesToUse.source.rollupInterval, + }, + ], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [query, ...rangeQuery(start, end)], + }, + }, + aggs: { + hostNames: { + terms: { + field: HOST_NAME, + size: Math.min(size, MAX_SIZE), + }, + }, + }, + }, + }); + + return esResponse.aggregations?.hostNames.buckets.map((bucket) => bucket.key as string) ?? []; + }; +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/services/get_services.ts b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_services.ts new file mode 100644 index 0000000000000..1d72a1ae8c009 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/services/get_services.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 { APMEventClient } from '../lib/helpers/create_es_client/create_apm_event_client'; +import { createGetDocumentSources } from './get_document_sources'; +import { createGetHostNames } from './get_host_names'; + +export interface ApmDataAccessServicesParams { + apmEventClient: APMEventClient; +} + +export function getServices(params: ApmDataAccessServicesParams) { + return { + getDocumentSources: createGetDocumentSources(params), + getHostNames: createGetHostNames(params), + }; +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/types.ts b/x-pack/plugins/observability_solution/apm_data_access/server/types.ts index 39c21c8aa0016..c6c3d316a9685 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/server/types.ts +++ b/x-pack/plugins/observability_solution/apm_data_access/server/types.ts @@ -7,10 +7,23 @@ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { APMIndices } from '.'; +import { getServices } from './services/get_services'; export interface ApmDataAccessPluginSetup { apmIndicesFromConfigFile: APMIndices; getApmIndices: (soClient: SavedObjectsClientContract) => Promise<APMIndices>; + getServices: typeof getServices; } + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ApmDataAccessPluginStart {} + +export type ApmDataAccessServices = ReturnType<typeof getServices>; +export type { ApmDataAccessServicesParams } from './services/get_services'; +export type { DocumentSourcesRequest } from './services/get_document_sources'; +export type { HostNamesRequest } from './services/get_host_names'; +export type { + APMEventClientConfig, + APMEventESSearchRequest, + APMLogEventESSearchRequest, +} from './lib/helpers'; diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/utils.ts b/x-pack/plugins/observability_solution/apm_data_access/server/utils.ts new file mode 100644 index 0000000000000..b1e768edf3733 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/utils.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 { + getDocumentTypeFilterForServiceDestinationStatistics, + getBackwardCompatibleDocumentTypeFilter, + callAsyncWithDebug, + cancelEsRequestOnAbort, + getDebugBody, + getDebugTitle, +} from './lib/helpers'; + +export { withApmSpan } from './utils/with_apm_span'; diff --git a/x-pack/plugins/observability_solution/apm_data_access/server/utils/with_apm_span.ts b/x-pack/plugins/observability_solution/apm_data_access/server/utils/with_apm_span.ts new file mode 100644 index 0000000000000..1343970f04a3f --- /dev/null +++ b/x-pack/plugins/observability_solution/apm_data_access/server/utils/with_apm_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 withApmSpan<T>( + optionsOrName: SpanOptions | string, + cb: () => Promise<T> +): Promise<T> { + const options = parseSpanOptions(optionsOrName); + + const optionsWithDefaults = { + ...(options.intercept ? {} : { type: 'plugin:apm' }), + ...options, + labels: { + plugin: 'apm', + ...options.labels, + }, + }; + + return withSpan(optionsWithDefaults, cb); +} diff --git a/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json b/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json index faa5185404fd0..cdcfd3ec3d023 100644 --- a/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm_data_access/tsconfig.json @@ -9,6 +9,14 @@ "@kbn/config-schema", "@kbn/core", "@kbn/i18n", - "@kbn/core-saved-objects-api-server" + "@kbn/core-saved-objects-api-server", + "@kbn/data-plugin", + "@kbn/inspector-plugin", + "@kbn/observability-plugin", + "@kbn/observability-shared-plugin", + "@kbn/es-types", + "@kbn/apm-types", + "@kbn/core-http-server-mocks", + "@kbn/apm-utils" ] } diff --git a/x-pack/plugins/observability_solution/assets_data_access/jest.config.js b/x-pack/plugins/observability_solution/assets_data_access/jest.config.js deleted file mode 100644 index 3c145b378762f..0000000000000 --- a/x-pack/plugins/observability_solution/assets_data_access/jest.config.js +++ /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. - */ - -const path = require('path'); - -module.exports = { - preset: '@kbn/test', - rootDir: path.resolve(__dirname, '../../../..'), - roots: ['<rootDir>/x-pack/plugins/observability_solution/assets_data_access'], -}; diff --git a/x-pack/plugins/observability_solution/assets_data_access/kibana.jsonc b/x-pack/plugins/observability_solution/assets_data_access/kibana.jsonc deleted file mode 100644 index b5c33ee7e9e90..0000000000000 --- a/x-pack/plugins/observability_solution/assets_data_access/kibana.jsonc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "plugin", - "id": "@kbn/assets-data-access-plugin", - "owner": "@elastic/obs-knowledge-team", - "plugin": { - "id": "assetsDataAccess", - "server": true, - "browser": false, - "requiredPlugins": [], - "optionalPlugins": [], - "requiredBundles": [] - } -} diff --git a/x-pack/plugins/observability_solution/assets_data_access/server/index.ts b/x-pack/plugins/observability_solution/assets_data_access/server/index.ts deleted file mode 100644 index cca2f9d457638..0000000000000 --- a/x-pack/plugins/observability_solution/assets_data_access/server/index.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 type { PluginInitializerContext } from '@kbn/core/server'; -import type { AssetsDataAccessPluginSetup, AssetsDataAccessPluginStart } from './plugin'; - -export type { AssetsDataAccessPluginSetup, AssetsDataAccessPluginStart }; - -export async function plugin(initializerContext: PluginInitializerContext) { - const { AssetsDataAccessPlugin } = await import('./plugin'); - return new AssetsDataAccessPlugin(initializerContext); -} diff --git a/x-pack/plugins/observability_solution/assets_data_access/server/plugin.ts b/x-pack/plugins/observability_solution/assets_data_access/server/plugin.ts deleted file mode 100644 index 7de93968ec3ab..0000000000000 --- a/x-pack/plugins/observability_solution/assets_data_access/server/plugin.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - CoreSetup, - CoreStart, - Logger, - Plugin, - PluginInitializerContext, -} from '@kbn/core/server'; -import { registerServices } from './services/register_services'; -import { AssetsPluginStartDeps } from './types'; - -export type AssetsDataAccessPluginSetup = ReturnType<AssetsDataAccessPlugin['setup']>; -export type AssetsDataAccessPluginStart = ReturnType<AssetsDataAccessPlugin['start']>; - -export class AssetsDataAccessPlugin implements Plugin { - private readonly logger: Logger; - - constructor(initializerContext: PluginInitializerContext) { - this.logger = initializerContext.logger.get(); - } - public setup(core: CoreSetup) {} - - public start(core: CoreStart, plugins: AssetsPluginStartDeps) { - const services = registerServices({ - logger: this.logger, - deps: {}, - }); - - return { services }; - } -} diff --git a/x-pack/plugins/observability_solution/assets_data_access/server/services/register_services.ts b/x-pack/plugins/observability_solution/assets_data_access/server/services/register_services.ts deleted file mode 100644 index dfac73c43df13..0000000000000 --- a/x-pack/plugins/observability_solution/assets_data_access/server/services/register_services.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Logger } from '@kbn/core/server'; - -export interface RegisterServicesParams { - logger: Logger; - deps: {}; -} - -export function registerServices(params: RegisterServicesParams) { - return {}; -} diff --git a/x-pack/plugins/observability_solution/assets_data_access/server/types.ts b/x-pack/plugins/observability_solution/assets_data_access/server/types.ts deleted file mode 100644 index bfecff00b2729..0000000000000 --- a/x-pack/plugins/observability_solution/assets_data_access/server/types.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. - */ - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface AssetsPluginStartDeps {} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx index 836580565b2d8..b186c16c0c0f8 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_quality_indicators.tsx @@ -33,13 +33,24 @@ import { mapPercentagesToQualityCounts } from '../../quality_indicator'; export function DatasetsQualityIndicators() { const { onPageReady } = usePerformanceContext(); - const { datasetsQuality, isDatasetsQualityLoading, datasetsActivity } = useSummaryPanelContext(); + const { + datasetsQuality, + isDatasetsQualityLoading, + datasetsActivity, + numberOfDatasets, + numberOfDocuments, + } = useSummaryPanelContext(); const qualityCounts = mapPercentagesToQualityCounts(datasetsQuality.percentages); const datasetsWithoutIgnoredField = datasetsActivity.total > 0 ? datasetsActivity.total - datasetsQuality.percentages.length : 0; - if (!isDatasetsQualityLoading) { - onPageReady(); + if (!isDatasetsQualityLoading && (numberOfDatasets || numberOfDocuments)) { + onPageReady({ + key1: 'datasets', + value1: numberOfDatasets, + key2: 'documents', + value2: numberOfDocuments, + }); } return ( diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx index a000115c82284..622f495fc4774 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx @@ -85,6 +85,9 @@ const useSummaryPanel = () => { isDatasetsActivityLoading, datasetsActivity, + + numberOfDatasets: filteredItems.length, + numberOfDocuments: filteredItems.reduce((acc, curr) => acc + curr.degradedDocs.docsCount, 0), }; }; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts index c5c6ce1501159..effbee62cfb7b 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/telemetry/telemetry_events.ts @@ -5,7 +5,8 @@ * 2.0. */ import { omit } from 'lodash'; -import { SchemaObject, SchemaValue } from '@kbn/ebt'; +import { SchemaObject, SchemaValue } from '@elastic/ebt'; + import { DatasetEbtFilter, DatasetEbtProps, diff --git a/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json b/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json index c509b18758f95..0397a6f185bf1 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json +++ b/x-pack/plugins/observability_solution/dataset_quality/tsconfig.json @@ -50,7 +50,6 @@ "@kbn/calculate-auto", "@kbn/discover-plugin", "@kbn/shared-ux-prompt-no-data-views-types", - "@kbn/ebt", "@kbn/ebt-tools", "@kbn/fields-metadata-plugin", "@kbn/server-route-repository-utils", diff --git a/x-pack/plugins/observability_solution/entities_data_access/README.md b/x-pack/plugins/observability_solution/entities_data_access/README.md new file mode 100644 index 0000000000000..fdcbce8d19a68 --- /dev/null +++ b/x-pack/plugins/observability_solution/entities_data_access/README.md @@ -0,0 +1,3 @@ +# Entities data access + +Exposes services to access entities data. diff --git a/x-pack/plugins/observability_solution/entities_data_access/jest.config.js b/x-pack/plugins/observability_solution/entities_data_access/jest.config.js new file mode 100644 index 0000000000000..9a813d5ebfee0 --- /dev/null +++ b/x-pack/plugins/observability_solution/entities_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: ['<rootDir>/x-pack/plugins/observability_solution/entities_data_access'], +}; diff --git a/x-pack/plugins/observability_solution/entities_data_access/kibana.jsonc b/x-pack/plugins/observability_solution/entities_data_access/kibana.jsonc new file mode 100644 index 0000000000000..78c2e5db5c2b0 --- /dev/null +++ b/x-pack/plugins/observability_solution/entities_data_access/kibana.jsonc @@ -0,0 +1,13 @@ +{ + "type": "plugin", + "id": "@kbn/entities-data-access-plugin", + "owner": "@elastic/obs-entities", + "plugin": { + "id": "entitiesDataAccess", + "server": true, + "browser": false, + "requiredPlugins": [], + "optionalPlugins": [], + "requiredBundles": [] + } +} diff --git a/x-pack/plugins/observability_solution/entities_data_access/server/index.ts b/x-pack/plugins/observability_solution/entities_data_access/server/index.ts new file mode 100644 index 0000000000000..282f8d7031338 --- /dev/null +++ b/x-pack/plugins/observability_solution/entities_data_access/server/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. + */ + +import type { PluginInitializerContext } from '@kbn/core/server'; +import type { EntitiesDataAccessPluginSetup, EntitiesDataAccessPluginStart } from './plugin'; + +export type { EntitiesDataAccessPluginSetup, EntitiesDataAccessPluginStart }; + +export async function plugin(initializerContext: PluginInitializerContext) { + const { EntitiesDataAccessPlugin } = await import('./plugin'); + return new EntitiesDataAccessPlugin(initializerContext); +} diff --git a/x-pack/plugins/observability_solution/entities_data_access/server/plugin.ts b/x-pack/plugins/observability_solution/entities_data_access/server/plugin.ts new file mode 100644 index 0000000000000..7e71a13f16bec --- /dev/null +++ b/x-pack/plugins/observability_solution/entities_data_access/server/plugin.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 type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { EntitiesPluginSetupDeps, EntitiesPluginStartDeps } from './types'; + +export type EntitiesDataAccessPluginSetup = ReturnType<EntitiesDataAccessPlugin['setup']>; +export type EntitiesDataAccessPluginStart = ReturnType<EntitiesDataAccessPlugin['start']>; + +export class EntitiesDataAccessPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, plugins: EntitiesPluginSetupDeps) {} + + public start(core: CoreStart, plugins: EntitiesPluginStartDeps) {} +} diff --git a/x-pack/plugins/observability_solution/entities_data_access/server/types.ts b/x-pack/plugins/observability_solution/entities_data_access/server/types.ts new file mode 100644 index 0000000000000..4b7a7448df23b --- /dev/null +++ b/x-pack/plugins/observability_solution/entities_data_access/server/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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface EntitiesPluginSetupDeps {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface EntitiesPluginStartDeps {} diff --git a/x-pack/plugins/observability_solution/assets_data_access/tsconfig.json b/x-pack/plugins/observability_solution/entities_data_access/tsconfig.json similarity index 100% rename from x-pack/plugins/observability_solution/assets_data_access/tsconfig.json rename to x-pack/plugins/observability_solution/entities_data_access/tsconfig.json diff --git a/x-pack/plugins/observability_solution/entity_manager/common/constants_entities.ts b/x-pack/plugins/observability_solution/entity_manager/common/constants_entities.ts index a3194339fac1f..c53847afbb548 100644 --- a/x-pack/plugins/observability_solution/entity_manager/common/constants_entities.ts +++ b/x-pack/plugins/observability_solution/entity_manager/common/constants_entities.ts @@ -5,11 +5,15 @@ * 2.0. */ +import { + ENTITY_BASE_PREFIX, + ENTITY_SCHEMA_VERSION_V1, + ENTITY_HISTORY, + ENTITY_LATEST, +} from '@kbn/entities-schema'; + // Base constants -export const ENTITY_BASE_PREFIX = 'entities'; -export const ENTITY_SCHEMA_VERSION_V1 = 'v1'; -export const ENTITY_INDEX_PREFIX = `.${ENTITY_BASE_PREFIX}` as const; -export const ENTITY_INDICES_PATTERN = `${ENTITY_INDEX_PREFIX}*` as const; +export const ENTITY_INTERNAL_INDICES_PATTERN = `.${ENTITY_BASE_PREFIX}*` as const; export const ENTITY_ENTITY_COMPONENT_TEMPLATE_V1 = `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_entity` as const; @@ -17,24 +21,16 @@ export const ENTITY_EVENT_COMPONENT_TEMPLATE_V1 = `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_event` as const; // History constants -export const ENTITY_HISTORY = 'history' as const; export const ENTITY_HISTORY_BASE_COMPONENT_TEMPLATE_V1 = `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_${ENTITY_HISTORY}_base` as const; export const ENTITY_HISTORY_PREFIX_V1 = `${ENTITY_BASE_PREFIX}-${ENTITY_SCHEMA_VERSION_V1}-${ENTITY_HISTORY}` as const; -export const ENTITY_HISTORY_BACKFILL_PREFIX_V1 = - `${ENTITY_BASE_PREFIX}-${ENTITY_SCHEMA_VERSION_V1}-${ENTITY_HISTORY}-backfill` as const; -export const ENTITY_HISTORY_INDEX_PREFIX_V1 = - `${ENTITY_INDEX_PREFIX}.${ENTITY_SCHEMA_VERSION_V1}.${ENTITY_HISTORY}` as const; // Latest constants -export const ENTITY_LATEST = 'latest' as const; export const ENTITY_LATEST_BASE_COMPONENT_TEMPLATE_V1 = `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_${ENTITY_LATEST}_base` as const; export const ENTITY_LATEST_PREFIX_V1 = `${ENTITY_BASE_PREFIX}-${ENTITY_SCHEMA_VERSION_V1}-${ENTITY_LATEST}` as const; -export const ENTITY_LATEST_INDEX_PREFIX_V1 = - `${ENTITY_INDEX_PREFIX}.${ENTITY_SCHEMA_VERSION_V1}.${ENTITY_LATEST}` as const; // Transform constants export const ENTITY_DEFAULT_HISTORY_FREQUENCY = '1m'; diff --git a/x-pack/plugins/observability_solution/entity_manager/common/errors.ts b/x-pack/plugins/observability_solution/entity_manager/common/errors.ts index 27e9406771da5..0c141b71b6b7e 100644 --- a/x-pack/plugins/observability_solution/entity_manager/common/errors.ts +++ b/x-pack/plugins/observability_solution/entity_manager/common/errors.ts @@ -7,7 +7,6 @@ export const ERROR_API_KEY_NOT_FOUND = 'api_key_not_found'; export const ERROR_API_KEY_NOT_VALID = 'api_key_not_valid'; -export const ERROR_USER_NOT_AUTHORIZED = 'user_not_authorized'; export const ERROR_API_KEY_SERVICE_DISABLED = 'api_key_service_disabled'; export const ERROR_PARTIAL_BUILTIN_INSTALLATION = 'partial_builtin_installation'; export const ERROR_DEFINITION_STOPPED = 'error_definition_stopped'; diff --git a/x-pack/plugins/observability_solution/entity_manager/common/helpers.ts b/x-pack/plugins/observability_solution/entity_manager/common/helpers.ts index 97a6317fee283..f18e8526c0c21 100644 --- a/x-pack/plugins/observability_solution/entity_manager/common/helpers.ts +++ b/x-pack/plugins/observability_solution/entity_manager/common/helpers.ts @@ -7,10 +7,10 @@ import { ENTITY_BASE_PREFIX, + ENTITY_SCHEMA_VERSION_V1, ENTITY_HISTORY, ENTITY_LATEST, - ENTITY_SCHEMA_VERSION_V1, -} from './constants_entities'; +} from '@kbn/entities-schema'; export const getEntityHistoryIndexTemplateV1 = (definitionId: string) => `${ENTITY_BASE_PREFIX}_${ENTITY_SCHEMA_VERSION_V1}_${ENTITY_HISTORY}_${definitionId}_index_template` as const; diff --git a/x-pack/plugins/observability_solution/entity_manager/kibana.jsonc b/x-pack/plugins/observability_solution/entity_manager/kibana.jsonc index 1b9a56cd77da5..0b292b322c74b 100644 --- a/x-pack/plugins/observability_solution/entity_manager/kibana.jsonc +++ b/x-pack/plugins/observability_solution/entity_manager/kibana.jsonc @@ -1,7 +1,7 @@ { "type": "plugin", "id": "@kbn/entityManager-plugin", - "owner": "@elastic/obs-knowledge-team", + "owner": "@elastic/obs-entities", "description": "Entity manager plugin for entity assets (inventory, topology, etc)", "plugin": { "id": "entityManager", diff --git a/x-pack/plugins/observability_solution/entity_manager/public/index.ts b/x-pack/plugins/observability_solution/entity_manager/public/index.ts index e8b77529c2eac..85a9285b1692d 100644 --- a/x-pack/plugins/observability_solution/entity_manager/public/index.ts +++ b/x-pack/plugins/observability_solution/entity_manager/public/index.ts @@ -22,8 +22,9 @@ export type EntityManagerAppId = 'entityManager'; export { ERROR_API_KEY_NOT_FOUND, ERROR_API_KEY_NOT_VALID, - ERROR_USER_NOT_AUTHORIZED, ERROR_API_KEY_SERVICE_DISABLED, ERROR_PARTIAL_BUILTIN_INSTALLATION, ERROR_DEFINITION_STOPPED, } from '../common/errors'; + +export { EntityManagerUnauthorizedError } from './lib/errors'; diff --git a/x-pack/plugins/observability_solution/entity_manager/public/lib/entity_client.ts b/x-pack/plugins/observability_solution/entity_manager/public/lib/entity_client.ts index 99a935145e190..c7be6ef0970a2 100644 --- a/x-pack/plugins/observability_solution/entity_manager/public/lib/entity_client.ts +++ b/x-pack/plugins/observability_solution/entity_manager/public/lib/entity_client.ts @@ -6,6 +6,7 @@ */ import { HttpStart } from '@kbn/core/public'; +import { EntityManagerUnauthorizedError } from './errors'; import { IEntityClient } from '../types'; import { ManagedEntityEnabledResponse, @@ -21,10 +22,24 @@ export class EntityClient implements IEntityClient { } async enableManagedEntityDiscovery(): Promise<EnableManagedEntityResponse> { - return await this.http.put('/internal/entities/managed/enablement'); + try { + return await this.http.put('/internal/entities/managed/enablement'); + } catch (err) { + if (err.body?.statusCode === 403) { + throw new EntityManagerUnauthorizedError(err.body.message); + } + throw err; + } } async disableManagedEntityDiscovery(): Promise<DisableManagedEntityResponse> { - return await this.http.delete('/internal/entities/managed/enablement'); + try { + return await this.http.delete('/internal/entities/managed/enablement'); + } catch (err) { + if (err.body?.statusCode === 403) { + throw new EntityManagerUnauthorizedError(err.body.message); + } + throw err; + } } } diff --git a/x-pack/plugins/observability_solution/entity_manager/public/lib/errors.ts b/x-pack/plugins/observability_solution/entity_manager/public/lib/errors.ts new file mode 100644 index 0000000000000..f8d3cd860ade8 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager/public/lib/errors.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 class EntityManagerUnauthorizedError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/privileges.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/privileges.ts index 9de76ac7b4c5c..5ca3da77d7eba 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/privileges.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/privileges.ts @@ -6,7 +6,7 @@ */ import { ElasticsearchClient } from '@kbn/core/server'; -import { ENTITY_INDICES_PATTERN } from '../../../common/constants_entities'; +import { ENTITY_INTERNAL_INDICES_PATTERN } from '../../../common/constants_entities'; import { SO_ENTITY_DEFINITION_TYPE, SO_ENTITY_DISCOVERY_API_KEY_TYPE } from '../../saved_objects'; import { BUILT_IN_ALLOWED_INDICES } from '../entities/built_in/constants'; @@ -58,11 +58,11 @@ export const entityDefinitionRuntimePrivileges = { cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'], index: [ { - names: [ENTITY_INDICES_PATTERN], + names: [ENTITY_INTERNAL_INDICES_PATTERN], privileges: ['create_index', 'index', 'create_doc', 'auto_configure', 'read'], }, { - names: [...BUILT_IN_ALLOWED_INDICES, ENTITY_INDICES_PATTERN], + names: [...BUILT_IN_ALLOWED_INDICES, ENTITY_INTERNAL_INDICES_PATTERN], privileges: ['read', 'view_index_metadata'], }, ], @@ -79,7 +79,7 @@ export const entityDefinitionDeletionPrivileges = { cluster: ['manage_transform', 'manage_ingest_pipelines', 'manage_index_templates'], index: [ { - names: [ENTITY_INDICES_PATTERN], + names: [ENTITY_INTERNAL_INDICES_PATTERN], privileges: ['delete_index'], }, ], diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts index 5c92c46374857..c733860793ca1 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts @@ -27,12 +27,12 @@ export const builtInServicesFromLogsEntityDefinition: EntityDefinition = 'This definition extracts service entities from common data streams by looking for the ECS field service.name', type: 'service', managed: true, - filter: '@timestamp >= now-10m', indexPatterns: ['logs-*', 'filebeat*', 'metrics-apm.service_transaction.1m*'], history: { timestampField: '@timestamp', interval: '1m', settings: { + lookbackPeriod: '10m', frequency: '2m', syncDelay: '2m', }, diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_transform.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_transform.ts index 99c089ac14600..d6379773479fc 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_transform.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/create_and_install_transform.ts @@ -9,7 +9,10 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { EntityDefinition } from '@kbn/entities-schema'; import { retryTransientEsErrors } from './helpers/retry'; import { generateLatestTransform } from './transform/generate_latest_transform'; -import { generateHistoryTransform } from './transform/generate_history_transform'; +import { + generateBackfillHistoryTransform, + generateHistoryTransform, +} from './transform/generate_history_transform'; export async function createAndInstallHistoryTransform( esClient: ElasticsearchClient, @@ -33,7 +36,7 @@ export async function createAndInstallHistoryBackfillTransform( logger: Logger ) { try { - const historyTransform = generateHistoryTransform(definition, true); + const historyTransform = generateBackfillHistoryTransform(definition); await retryTransientEsErrors(() => esClient.transform.putTransform(historyTransform), { logger, }); diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_index.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_index.ts index a371962833de6..985ca9f3c7a72 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_index.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_index.ts @@ -15,13 +15,11 @@ export async function deleteIndices( logger: Logger ) { try { - const response = await esClient.indices.resolveIndex({ - name: `${generateHistoryIndexName(definition)}.*,${generateLatestIndexName(definition)}`, - }); - const indices = response.indices.map((doc) => doc.name); - if (indices.length) { - await esClient.indices.delete({ index: indices, ignore_unavailable: true }); - } + const indices = [ + `${generateHistoryIndexName(definition)}.*`, + generateLatestIndexName(definition), + ]; + await esClient.indices.delete({ index: indices, ignore_unavailable: true }); } catch (e) { logger.error(`Unable to remove entity definition index [${definition.id}}]`); throw e; diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/generate_component_id.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/generate_component_id.ts index 1be53fc0af8c9..2aa3fb992c9ca 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/generate_component_id.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/generate_component_id.ts @@ -5,40 +5,51 @@ * 2.0. */ -import { EntityDefinition } from '@kbn/entities-schema'; import { - ENTITY_HISTORY_BACKFILL_PREFIX_V1, - ENTITY_HISTORY_INDEX_PREFIX_V1, + ENTITY_HISTORY, + ENTITY_LATEST, + ENTITY_SCHEMA_VERSION_V1, + EntityDefinition, + entitiesIndexPattern, +} from '@kbn/entities-schema'; +import { ENTITY_HISTORY_PREFIX_V1, - ENTITY_LATEST_INDEX_PREFIX_V1, ENTITY_LATEST_PREFIX_V1, } from '../../../../common/constants_entities'; // History function generateHistoryId(definition: EntityDefinition) { - return `${ENTITY_HISTORY_PREFIX_V1}-${definition.id}`; + return `${ENTITY_HISTORY_PREFIX_V1}-${definition.id}` as const; } // History Backfill export function generateHistoryBackfillTransformId(definition: EntityDefinition) { - return `${ENTITY_HISTORY_BACKFILL_PREFIX_V1}-${definition.id}`; + return `${ENTITY_HISTORY_PREFIX_V1}-backfill-${definition.id}` as const; } export const generateHistoryTransformId = generateHistoryId; export const generateHistoryIngestPipelineId = generateHistoryId; export function generateHistoryIndexName(definition: EntityDefinition) { - return `${ENTITY_HISTORY_INDEX_PREFIX_V1}.${definition.id}`; + return entitiesIndexPattern({ + schemaVersion: ENTITY_SCHEMA_VERSION_V1, + dataset: ENTITY_HISTORY, + definitionId: definition.id, + }); } // Latest function generateLatestId(definition: EntityDefinition) { - return `${ENTITY_LATEST_PREFIX_V1}-${definition.id}`; + return `${ENTITY_LATEST_PREFIX_V1}-${definition.id}` as const; } export const generateLatestTransformId = generateLatestId; export const generateLatestIngestPipelineId = generateLatestId; export function generateLatestIndexName(definition: EntityDefinition) { - return `${ENTITY_LATEST_INDEX_PREFIX_V1}.${definition.id}`; + return entitiesIndexPattern({ + schemaVersion: ENTITY_SCHEMA_VERSION_V1, + dataset: ENTITY_LATEST, + definitionId: definition.id, + }); } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts index 6a97d3c950eec..4c34f5d3c0256 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/is_backfill_enabled.ts @@ -8,5 +8,5 @@ import { EntityDefinition } from '@kbn/entities-schema'; export function isBackfillEnabled(definition: EntityDefinition) { - return definition.history.settings?.backfillSyncDelay != null; + return definition.history.settings.backfillSyncDelay != null; } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts index 36c3f32342477..0dd2bb0a8f465 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_history_processors.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { EntityDefinition } from '@kbn/entities-schema'; -import { ENTITY_SCHEMA_VERSION_V1 } from '../../../../common/constants_entities'; +import { EntityDefinition, ENTITY_SCHEMA_VERSION_V1 } from '@kbn/entities-schema'; import { initializePathScript, cleanScript, diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts index f5cd75d218e97..f1a45d297554e 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { EntityDefinition } from '@kbn/entities-schema'; -import { ENTITY_SCHEMA_VERSION_V1 } from '../../../../common/constants_entities'; +import { EntityDefinition, ENTITY_SCHEMA_VERSION_V1 } from '@kbn/entities-schema'; import { initializePathScript, cleanScript, diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts index a58019bf236ae..5c3c954b45333 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts @@ -81,13 +81,13 @@ export async function installEntityDefinition({ await upsertTemplate({ esClient, logger, - template: getEntitiesHistoryIndexTemplateConfig(definition.id), + template: getEntitiesHistoryIndexTemplateConfig(definition), }); installState.indexTemplates.history = true; await upsertTemplate({ esClient, logger, - template: getEntitiesLatestIndexTemplateConfig(definition.id), + template: getEntitiesLatestIndexTemplateConfig(definition), }); installState.indexTemplates.latest = true; diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap index 94af9f3307f04..464293cc1bca8 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_history_template.test.ts.snap @@ -29,6 +29,9 @@ Object { "name": "entities_v1_history_admin-console-services_index_template", "priority": 200, "template": Object { + "aliases": Object { + "entities-service-history": Object {}, + }, "mappings": Object { "_meta": Object { "version": "1.6.0", diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap index b4247098d9498..448286b16d84a 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/__snapshots__/entities_latest_template.test.ts.snap @@ -29,6 +29,9 @@ Object { "name": "entities_v1_latest_admin-console-services_index_template", "priority": 200, "template": Object { + "aliases": Object { + "entities-service-latest": Object {}, + }, "mappings": Object { "_meta": Object { "version": "1.6.0", diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts index 11aad78741020..055ebe75cd608 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.test.ts @@ -10,7 +10,7 @@ import { getEntitiesHistoryIndexTemplateConfig } from './entities_history_templa describe('getEntitiesHistoryIndexTemplateConfig(definitionId)', () => { it('should generate a valid index template', () => { - const template = getEntitiesHistoryIndexTemplateConfig(entityDefinition.id); + const template = getEntitiesHistoryIndexTemplateConfig(entityDefinition); expect(template).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts index a0fb4b032a6e1..d6007e8287138 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_history_template.ts @@ -6,19 +6,25 @@ */ import { IndicesPutIndexTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { + ENTITY_HISTORY, + EntityDefinition, + entitiesIndexPattern, + entitiesAliasPattern, + ENTITY_SCHEMA_VERSION_V1, +} from '@kbn/entities-schema'; import { getEntityHistoryIndexTemplateV1 } from '../../../../common/helpers'; import { ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, ENTITY_HISTORY_BASE_COMPONENT_TEMPLATE_V1, - ENTITY_HISTORY_INDEX_PREFIX_V1, } from '../../../../common/constants_entities'; import { getCustomHistoryTemplateComponents } from '../../../templates/components/helpers'; export const getEntitiesHistoryIndexTemplateConfig = ( - definitionId: string + definition: EntityDefinition ): IndicesPutIndexTemplateRequest => ({ - name: getEntityHistoryIndexTemplateV1(definitionId), + name: getEntityHistoryIndexTemplateV1(definition.id), _meta: { description: "Index template for indices managed by the Elastic Entity Model's entity discovery framework for the history dataset", @@ -26,16 +32,25 @@ export const getEntitiesHistoryIndexTemplateConfig = ( managed: true, managed_by: 'elastic_entity_model', }, - ignore_missing_component_templates: getCustomHistoryTemplateComponents(definitionId), + ignore_missing_component_templates: getCustomHistoryTemplateComponents(definition.id), composed_of: [ ENTITY_HISTORY_BASE_COMPONENT_TEMPLATE_V1, ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, - ...getCustomHistoryTemplateComponents(definitionId), + ...getCustomHistoryTemplateComponents(definition.id), + ], + index_patterns: [ + `${entitiesIndexPattern({ + schemaVersion: ENTITY_SCHEMA_VERSION_V1, + dataset: ENTITY_HISTORY, + definitionId: definition.id, + })}.*`, ], - index_patterns: [`${ENTITY_HISTORY_INDEX_PREFIX_V1}.${definitionId}.*`], priority: 200, template: { + aliases: { + [entitiesAliasPattern({ type: definition.type, dataset: ENTITY_HISTORY })]: {}, + }, mappings: { _meta: { version: '1.6.0', diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts index 72583d941492c..f1012d0bcb7f7 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.test.ts @@ -10,7 +10,7 @@ import { getEntitiesLatestIndexTemplateConfig } from './entities_latest_template describe('getEntitiesLatestIndexTemplateConfig(definitionId)', () => { it('should generate a valid index template', () => { - const template = getEntitiesLatestIndexTemplateConfig(entityDefinition.id); + const template = getEntitiesLatestIndexTemplateConfig(entityDefinition); expect(template).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts index 466346f86b44d..e97be68e696d1 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/templates/entities_latest_template.ts @@ -6,19 +6,25 @@ */ import { IndicesPutIndexTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import { + ENTITY_LATEST, + ENTITY_SCHEMA_VERSION_V1, + EntityDefinition, + entitiesIndexPattern, + entitiesAliasPattern, +} from '@kbn/entities-schema'; import { getEntityLatestIndexTemplateV1 } from '../../../../common/helpers'; import { ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, ENTITY_LATEST_BASE_COMPONENT_TEMPLATE_V1, - ENTITY_LATEST_INDEX_PREFIX_V1, } from '../../../../common/constants_entities'; import { getCustomLatestTemplateComponents } from '../../../templates/components/helpers'; export const getEntitiesLatestIndexTemplateConfig = ( - definitionId: string + definition: EntityDefinition ): IndicesPutIndexTemplateRequest => ({ - name: getEntityLatestIndexTemplateV1(definitionId), + name: getEntityLatestIndexTemplateV1(definition.id), _meta: { description: "Index template for indices managed by the Elastic Entity Model's entity discovery framework for the latest dataset", @@ -26,16 +32,25 @@ export const getEntitiesLatestIndexTemplateConfig = ( managed: true, managed_by: 'elastic_entity_model', }, - ignore_missing_component_templates: getCustomLatestTemplateComponents(definitionId), + ignore_missing_component_templates: getCustomLatestTemplateComponents(definition.id), composed_of: [ ENTITY_LATEST_BASE_COMPONENT_TEMPLATE_V1, ENTITY_ENTITY_COMPONENT_TEMPLATE_V1, ENTITY_EVENT_COMPONENT_TEMPLATE_V1, - ...getCustomLatestTemplateComponents(definitionId), + ...getCustomLatestTemplateComponents(definition.id), + ], + index_patterns: [ + entitiesIndexPattern({ + schemaVersion: ENTITY_SCHEMA_VERSION_V1, + dataset: ENTITY_LATEST, + definitionId: definition.id, + }), ], - index_patterns: [`${ENTITY_LATEST_INDEX_PREFIX_V1}.${definitionId}`], priority: 200, template: { + aliases: { + [entitiesAliasPattern({ type: definition.type, dataset: ENTITY_LATEST })]: {}, + }, mappings: { _meta: { version: '1.6.0', diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap index 551b9761341d2..30b8912a532d7 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/__snapshots__/generate_history_transform.test.ts.snap @@ -133,6 +133,11 @@ Object { }, }, }, + Object { + "exists": Object { + "field": "log.logger", + }, + }, ], }, }, @@ -270,6 +275,24 @@ Object { "index": Array [ "kbn-data-forge-fake_stack.*", ], + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "log.logger", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "gte": "now-1h", + }, + }, + }, + ], + }, + }, }, "sync": Object { "time": Object { diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts index cde87d670c8c2..f49ec0cd88a37 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.test.ts @@ -7,7 +7,10 @@ import { entityDefinition } from '../helpers/fixtures/entity_definition'; import { entityDefinitionWithBackfill } from '../helpers/fixtures/entity_definition_with_backfill'; -import { generateHistoryTransform } from './generate_history_transform'; +import { + generateBackfillHistoryTransform, + generateHistoryTransform, +} from './generate_history_transform'; describe('generateHistoryTransform(definition)', () => { it('should generate a valid history transform', () => { @@ -15,7 +18,7 @@ describe('generateHistoryTransform(definition)', () => { expect(transform).toMatchSnapshot(); }); it('should generate a valid history backfill transform', () => { - const transform = generateHistoryTransform(entityDefinitionWithBackfill, true); + const transform = generateBackfillHistoryTransform(entityDefinitionWithBackfill); expect(transform).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.ts index 05b0e7ee7fd54..239359738624c 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_history_transform.ts @@ -26,12 +26,45 @@ import { import { isBackfillEnabled } from '../helpers/is_backfill_enabled'; export function generateHistoryTransform( - definition: EntityDefinition, - backfill = false + definition: EntityDefinition ): TransformPutTransformRequest { - if (backfill && !isBackfillEnabled(definition)) { + const filter: QueryDslQueryContainer[] = []; + + if (definition.filter) { + filter.push(getElasticsearchQueryOrThrow(definition.filter)); + } + + if (definition.identityFields.some(({ optional }) => !optional)) { + definition.identityFields + .filter(({ optional }) => !optional) + .forEach(({ field }) => { + filter.push({ exists: { field } }); + }); + } + + filter.push({ + range: { + [definition.history.timestampField]: { + gte: `now-${definition.history.settings.lookbackPeriod}`, + }, + }, + }); + + return generateTransformPutRequest({ + definition, + filter, + transformId: generateHistoryTransformId(definition), + frequency: definition.history.settings.frequency, + syncDelay: definition.history.settings.syncDelay, + }); +} + +export function generateBackfillHistoryTransform( + definition: EntityDefinition +): TransformPutTransformRequest { + if (!isBackfillEnabled(definition)) { throw new Error( - 'This function was called with backfill=true without history.settings.backfillSyncDelay' + 'generateBackfillHistoryTransform called without history.settings.backfillSyncDelay set' ); } @@ -41,28 +74,46 @@ export function generateHistoryTransform( filter.push(getElasticsearchQueryOrThrow(definition.filter)); } - if (backfill && definition.history.settings?.backfillLookbackPeriod) { + if (definition.history.settings.backfillLookbackPeriod) { filter.push({ range: { [definition.history.timestampField]: { - gte: `now-${definition.history.settings?.backfillLookbackPeriod.toJSON()}`, + gte: `now-${definition.history.settings.backfillLookbackPeriod}`, }, }, }); } - const syncDelay = backfill - ? definition.history.settings?.backfillSyncDelay - : definition.history.settings?.syncDelay; - - const transformId = backfill - ? generateHistoryBackfillTransformId(definition) - : generateHistoryTransformId(definition); + if (definition.identityFields.some(({ optional }) => !optional)) { + definition.identityFields + .filter(({ optional }) => !optional) + .forEach(({ field }) => { + filter.push({ exists: { field } }); + }); + } - const frequency = backfill - ? definition.history.settings?.backfillFrequency - : definition.history.settings?.frequency; + return generateTransformPutRequest({ + definition, + filter, + transformId: generateHistoryBackfillTransformId(definition), + frequency: definition.history.settings.backfillFrequency, + syncDelay: definition.history.settings.backfillSyncDelay, + }); +} +const generateTransformPutRequest = ({ + definition, + filter, + transformId, + frequency, + syncDelay, +}: { + definition: EntityDefinition; + transformId: string; + filter: QueryDslQueryContainer[]; + frequency?: string; + syncDelay?: string; +}) => { return { transform_id: transformId, _meta: { @@ -87,7 +138,7 @@ export function generateHistoryTransform( frequency: frequency || ENTITY_DEFAULT_HISTORY_FREQUENCY, sync: { time: { - field: definition.history.settings?.syncField ?? definition.history.timestampField, + field: definition.history.settings.syncField || definition.history.timestampField, delay: syncDelay || ENTITY_DEFAULT_HISTORY_SYNC_DELAY, }, }, @@ -109,7 +160,7 @@ export function generateHistoryTransform( ['@timestamp']: { date_histogram: { field: definition.history.timestampField, - fixed_interval: definition.history.interval.toJSON(), + fixed_interval: definition.history.interval, }, }, }, @@ -124,4 +175,4 @@ export function generateHistoryTransform( }, }, }; -} +}; diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts index 31ba3e9add0dc..264d5da12ee04 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/transform/generate_metadata_aggregations.ts @@ -38,7 +38,7 @@ export function generateLatestMetadataAggregations(definition: EntityDefinition) filter: { range: { 'event.ingested': { - gte: `now-${definition.history.interval.toJSON()}`, + gte: `now-${definition.history.interval}`, }, }, }, diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts index 4a0500e7efbca..3a1c7bd5bd110 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts @@ -9,7 +9,6 @@ import { RequestHandlerContext } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { SetupRouteOptions } from '../types'; import { deleteEntityDiscoveryAPIKey, readEntityDiscoveryAPIKey } from '../../lib/auth'; -import { ERROR_USER_NOT_AUTHORIZED } from '../../../common/errors'; import { uninstallBuiltInEntityDefinitions } from '../../lib/entities/uninstall_entity_definition'; import { canDisableEntityDiscovery } from '../../lib/auth/privileges'; import { EntityDiscoveryApiKeyType } from '../../saved_objects'; @@ -33,10 +32,8 @@ export function disableEntityDiscoveryRoute<T extends RequestHandlerContext>({ const esClient = (await context.core).elasticsearch.client.asCurrentUser; const canDisable = await canDisableEntityDiscovery(esClient); if (!canDisable) { - return res.ok({ + return res.forbidden({ body: { - success: false, - reason: ERROR_USER_NOT_AUTHORIZED, message: 'Current Kibana user does not have the required permissions to disable entity discovery', }, diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts index d9af1105e42b4..8e4e07fd00b8f 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts @@ -18,7 +18,7 @@ import { } from '../../lib/auth'; import { builtInDefinitions } from '../../lib/entities/built_in'; import { installBuiltInEntityDefinitions } from '../../lib/entities/install_entity_definition'; -import { ERROR_API_KEY_SERVICE_DISABLED, ERROR_USER_NOT_AUTHORIZED } from '../../../common/errors'; +import { ERROR_API_KEY_SERVICE_DISABLED } from '../../../common/errors'; import { EntityDiscoveryApiKeyType } from '../../saved_objects'; export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({ @@ -48,10 +48,8 @@ export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({ const esClient = (await context.core).elasticsearch.client.asCurrentUser; const canEnable = await canEnableEntityDiscovery(esClient); if (!canEnable) { - return res.ok({ + return res.forbidden({ body: { - success: false, - reason: ERROR_USER_NOT_AUTHORIZED, message: 'Current Kibana user does not have the required permissions to enable entity discovery', }, @@ -75,7 +73,6 @@ export function enableEntityDiscoveryRoute<T extends RequestHandlerContext>({ } const apiKey = await generateEntityDiscoveryAPIKey(server, req); - if (apiKey === undefined) { return res.customError({ statusCode: 500, diff --git a/x-pack/plugins/observability_solution/infra/common/constants.ts b/x-pack/plugins/observability_solution/infra/common/constants.ts index 26e41846d2fb1..71507ba78b904 100644 --- a/x-pack/plugins/observability_solution/infra/common/constants.ts +++ b/x-pack/plugins/observability_solution/infra/common/constants.ts @@ -27,6 +27,9 @@ export const SYSTEM_PROCESS_CMDLINE_FIELD = 'system.process.cmdline'; export const EVENT_MODULE = 'event.module'; export const METRICSET_MODULE = 'metricset.module'; +// integrations +export const SYSTEM_INTEGRATION = 'system'; + // logs export const MESSAGE_FIELD = 'message'; diff --git a/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts b/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts index 1715a28b1caab..8ca585709db21 100644 --- a/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts +++ b/x-pack/plugins/observability_solution/infra/common/formatters/snapshot_metric_formats.ts @@ -29,12 +29,18 @@ export const METRIC_FORMATTERS: MetricFormatters = { formatter: InfraFormatterType.percent, template: '{{value}}', }, + ['cpuTotal']: { + formatter: InfraFormatterType.percent, + template: '{{value}}', + }, ['memory']: { formatter: InfraFormatterType.percent, template: '{{value}}', }, ['rx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, ['tx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + ['rxV2']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + ['txV2']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, ['logRate']: { formatter: InfraFormatterType.abbreviatedNumber, template: '{{value}}/s', diff --git a/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts b/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts index 03114642146ff..d4f7f1f7d3635 100644 --- a/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts +++ b/x-pack/plugins/observability_solution/infra/common/http_api/infra/get_infra_metrics.ts @@ -10,12 +10,15 @@ import * as rt from 'io-ts'; export const InfraMetricTypeRT = rt.keyof({ cpu: null, + cpuTotal: null, normalizedLoad1m: null, diskSpaceUsage: null, memory: null, memoryFree: null, rx: null, tx: null, + rxV2: null, + txV2: null, }); export const RangeRT = rt.type({ diff --git a/x-pack/plugins/observability_solution/infra/common/inventory_models/intl_strings.ts b/x-pack/plugins/observability_solution/infra/common/inventory_models/intl_strings.ts index 5a90113c9069e..c8e0c4d0d2ae3 100644 --- a/x-pack/plugins/observability_solution/infra/common/inventory_models/intl_strings.ts +++ b/x-pack/plugins/observability_solution/infra/common/inventory_models/intl_strings.ts @@ -6,7 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import { SnapshotMetricType, SnapshotMetricTypeKeys } from '@kbn/metrics-data-access-plugin/common'; +import { + type InventoryItemType, + type SnapshotMetricType, + SnapshotMetricTypeKeys, +} from '@kbn/metrics-data-access-plugin/common'; import { toMetricOpt } from '../snapshot_metric_i18n'; interface Lookup { @@ -44,10 +48,11 @@ export const fieldToName = (field: string) => { }; const snapshotTypeKeys = Object.keys(SnapshotMetricTypeKeys) as SnapshotMetricType[]; -export const SNAPSHOT_METRIC_TRANSLATIONS = snapshotTypeKeys.reduce((result, metric) => { - const text = toMetricOpt(metric)?.text; - if (text) { - result[metric] = text; - } - return result; -}, {} as Record<SnapshotMetricType, string>); +export const getSnapshotMetricTranslations = (nodeType: InventoryItemType) => + snapshotTypeKeys.reduce((result, metric) => { + const text = toMetricOpt(metric, nodeType)?.text; + if (text) { + result[metric] = text; + } + return result; + }, {} as Record<SnapshotMetricType, string>); diff --git a/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts b/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts index 305c322ba0d23..047b79cada84e 100644 --- a/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts +++ b/x-pack/plugins/observability_solution/infra/common/inventory_views/defaults.ts @@ -18,7 +18,7 @@ export const staticInventoryViewAttributes: InventoryViewAttributes = { isDefault: false, isStatic: true, metric: { - type: 'cpu', + type: 'cpuTotal', }, groupBy: [], nodeType: 'host', diff --git a/x-pack/plugins/observability_solution/infra/common/metrics_sources/index.ts b/x-pack/plugins/observability_solution/infra/common/metrics_sources/index.ts index 29ca416af524e..09b1530ad5986 100644 --- a/x-pack/plugins/observability_solution/infra/common/metrics_sources/index.ts +++ b/x-pack/plugins/observability_solution/infra/common/metrics_sources/index.ts @@ -51,7 +51,6 @@ const metricsSourceConfigurationOriginRT = rt.keyof({ export const metricsSourceStatusRT = rt.strict({ metricIndicesExist: SourceStatusRuntimeType.props.metricIndicesExist, remoteClustersExist: SourceStatusRuntimeType.props.metricIndicesExist, - indexFields: SourceStatusRuntimeType.props.indexFields, }); export type MetricsSourceStatus = rt.TypeOf<typeof metricsSourceStatusRT>; diff --git a/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts b/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts index 3dc948d7bc82d..75a8b9cb2d0f1 100644 --- a/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts +++ b/x-pack/plugins/observability_solution/infra/common/snapshot_metric_i18n.ts @@ -7,15 +7,19 @@ import { i18n } from '@kbn/i18n'; import { mapValues } from 'lodash'; -import { SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; +import type { InventoryItemType, SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; // Lowercase versions of all metrics, for when they need to be used in the middle of a sentence; // these may need to be translated differently depending on language, e.g. still capitalizing "CPU" const TranslationsLowercase = { - CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', { + CPUUsageTotal: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageTotalText', { defaultMessage: 'CPU usage', }), + CPUUsageLegacy: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageLegacyText', { + defaultMessage: 'CPU usage (legacy)', + }), + MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', { defaultMessage: 'memory usage', }), @@ -28,6 +32,14 @@ const TranslationsLowercase = { defaultMessage: 'outbound traffic', }), + InboundTrafficLegacy: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', { + defaultMessage: 'inbound traffic (Legacy)', + }), + + OutboundTrafficLegacy: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', { + defaultMessage: 'outbound traffic (Legacy)', + }), + LogRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', { defaultMessage: 'log rate', }), @@ -94,14 +106,23 @@ const Translations = mapValues( (translation) => `${translation[0].toUpperCase()}${translation.slice(1)}` ); +const showLegacyLabel = (nodeType?: InventoryItemType) => nodeType === 'host'; + export const toMetricOpt = ( - metric: SnapshotMetricType + metric: SnapshotMetricType, + nodeType?: InventoryItemType ): { text: string; textLC: string; value: SnapshotMetricType } | undefined => { switch (metric) { + case 'cpuTotal': + return { + text: Translations.CPUUsageTotal, + textLC: TranslationsLowercase.CPUUsageTotal, + value: 'cpuTotal', + }; case 'cpu': return { - text: Translations.CPUUsage, - textLC: TranslationsLowercase.CPUUsage, + text: Translations.CPUUsageLegacy, + textLC: TranslationsLowercase.CPUUsageLegacy, value: 'cpu', }; case 'memory': @@ -112,15 +133,35 @@ export const toMetricOpt = ( }; case 'rx': return { - text: Translations.InboundTraffic, - textLC: TranslationsLowercase.InboundTraffic, + text: showLegacyLabel(nodeType) + ? Translations.InboundTrafficLegacy + : Translations.InboundTraffic, + textLC: showLegacyLabel(nodeType) + ? TranslationsLowercase.InboundTrafficLegacy + : TranslationsLowercase.InboundTraffic, value: 'rx', }; case 'tx': + return { + text: showLegacyLabel(nodeType) + ? Translations.OutboundTrafficLegacy + : Translations.OutboundTraffic, + textLC: showLegacyLabel(nodeType) + ? TranslationsLowercase.OutboundTrafficLegacy + : TranslationsLowercase.OutboundTraffic, + value: 'tx', + }; + case 'rxV2': + return { + text: Translations.InboundTraffic, + textLC: TranslationsLowercase.InboundTraffic, + value: 'rxV2', + }; + case 'txV2': return { text: Translations.OutboundTraffic, textLC: TranslationsLowercase.OutboundTraffic, - value: 'tx', + value: 'txV2', }; case 'logRate': return { diff --git a/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts b/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts index ac3724c80d70a..05988909e3f36 100644 --- a/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts +++ b/x-pack/plugins/observability_solution/infra/common/source_configuration/defaults.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { LOGS_INDEX_PATTERN, METRICS_INDEX_PATTERN } from '../constants'; +import { METRICS_INDEX_PATTERN } from '../constants'; import { InfraSourceConfiguration } from './source_configuration'; export const defaultSourceConfiguration: InfraSourceConfiguration = { @@ -13,8 +13,7 @@ export const defaultSourceConfiguration: InfraSourceConfiguration = { description: '', metricAlias: METRICS_INDEX_PATTERN, logIndices: { - type: 'index_name', - indexName: LOGS_INDEX_PATTERN, + type: 'kibana_advanced_setting', }, inventoryDefaultView: '0', metricsExplorerDefaultView: '0', diff --git a/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts b/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts index b23c9478e3335..e9779c192788d 100644 --- a/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts +++ b/x-pack/plugins/observability_solution/infra/common/source_configuration/source_configuration.ts @@ -80,7 +80,16 @@ export const logIndexNameReferenceRT = rt.type({ }); export type LogIndexNameReference = rt.TypeOf<typeof logIndexNameReferenceRT>; -export const logIndexReferenceRT = rt.union([logIndexPatternReferenceRT, logIndexNameReferenceRT]); +// Kibana advanced setting +export const logSourcesKibanaAdvancedSettingRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + +export const logIndexReferenceRT = rt.union([ + logIndexPatternReferenceRT, + logIndexNameReferenceRT, + logSourcesKibanaAdvancedSettingRT, +]); export type LogIndexReference = rt.TypeOf<typeof logIndexReferenceRT>; export const SourceConfigurationRT = rt.type({ @@ -130,21 +139,11 @@ export interface InfraSourceConfiguration /** * Source status */ -const SourceStatusFieldRuntimeType = rt.type({ - name: rt.string, - type: rt.string, - searchable: rt.boolean, - aggregatable: rt.boolean, - displayable: rt.boolean, -}); - -export type InfraSourceIndexField = rt.TypeOf<typeof SourceStatusFieldRuntimeType>; export const SourceStatusRuntimeType = rt.type({ logIndicesExist: rt.boolean, metricIndicesExist: rt.boolean, remoteClustersExist: rt.boolean, - indexFields: rt.array(SourceStatusFieldRuntimeType), }); export interface InfraSourceStatus extends rt.TypeOf<typeof SourceStatusRuntimeType> {} diff --git a/x-pack/plugins/observability_solution/infra/kibana.jsonc b/x-pack/plugins/observability_solution/infra/kibana.jsonc index 973acf3e75d55..cf73b1636d93e 100644 --- a/x-pack/plugins/observability_solution/infra/kibana.jsonc +++ b/x-pack/plugins/observability_solution/infra/kibana.jsonc @@ -34,7 +34,8 @@ "unifiedSearch", "usageCollection", "visTypeTimeseries", - "apmDataAccess" + "apmDataAccess", + "logsDataAccess" ], "optionalPlugins": [ "spaces", diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx index 18bd2f1d3711f..e3ae562fe80f3 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression.tsx @@ -577,7 +577,7 @@ export const ExpressionRow: FC<PropsWithChildren<ExpressionRowProps>> = (props) myMetrics = containerSnapshotMetricTypes; break; } - return myMetrics.map(toMetricOpt); + return myMetrics.map((myMetric) => toMetricOpt(myMetric, props.nodeType)); }, [props.nodeType]); return ( @@ -772,9 +772,12 @@ export const nodeTypes: { [key: string]: any } = { const metricUnit: Record<string, { label: string }> = { count: { label: '' }, cpu: { label: '%' }, + cpuTotal: { label: '%' }, memory: { label: '%' }, rx: { label: 'bits/s' }, tx: { label: 'bits/s' }, + rxV2: { label: 'bits/s' }, + txV2: { label: 'bits/s' }, logRate: { label: '/s' }, diskIOReadBytes: { label: 'bytes/s' }, diskIOWriteBytes: { label: 'bytes/s' }, diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx index 5bea3d01ca231..b2b280d182a4b 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/components/expression_chart.tsx @@ -231,6 +231,7 @@ const convertMetricValue = (metric: SnapshotMetricType, value: number) => { }; const converters: Record<string, (n: number) => number> = { cpu: (n) => Number(n) / 100, + cpuTotal: (n) => Number(n) / 100, memory: (n) => Number(n) / 100, tx: (n) => Number(n) / 8, rx: (n) => Number(n) / 8, diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts index 2ba9f1f36180a..4c3ade788e25e 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts +++ b/x-pack/plugins/observability_solution/infra/public/alerting/inventory/hooks/use_inventory_alert_prefill.ts @@ -15,7 +15,7 @@ import { export const useInventoryAlertPrefill = () => { const [nodeType, setNodeType] = useState<InventoryItemType>('host'); const [filterQuery, setFilterQuery] = useState<string | undefined>(); - const [metric, setMetric] = useState<SnapshotMetricInput>({ type: 'cpu' }); + const [metric, setMetric] = useState<SnapshotMetricInput>({ type: 'cpuTotal' }); const [customMetrics, setCustomMetrics] = useState<SnapshotCustomMetricInput[]>([]); // only shows for AWS when there are regions info const [region, setRegion] = useState(''); diff --git a/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts b/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts index 1a0446584e944..de1e8e16d1d51 100644 --- a/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts +++ b/x-pack/plugins/observability_solution/infra/public/common/visualizations/translations.ts @@ -17,7 +17,7 @@ export const METRICS_TOOLTIP = { cpuUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.cpuUsage', { defaultMessage: - 'Percentage of CPU time spent in states other than Idle and IOWait, normalized by the number of CPU cores. This includes both time spent on user space and kernel space.', + 'Average of percentage of CPU time spent in states other than Idle and IOWait, normalised by the number of CPU cores. Includes both time spent on user space and kernel space. 100% means all CPUs of the host are busy.', }), diskUsage: i18n.translate('xpack.infra.hostsViewPage.metrics.tooltip.diskSpaceUsage', { defaultMessage: 'Percentage of disk space used.', diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx index 7a012b63b0caf..5ac8809be5443 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/template/page.tsx @@ -9,6 +9,8 @@ import { EuiFlexGroup } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; +import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; +import { SYSTEM_INTEGRATION } from '../../../../common/constants'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { useParentBreadcrumbResolver } from '../../../hooks/use_parent_breadcrumb_resolver'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; @@ -21,6 +23,12 @@ import { usePageHeader } from '../hooks/use_page_header'; import { useTabSwitcherContext } from '../hooks/use_tab_switcher'; import { ContentTemplateProps } from '../types'; import { getIntegrationsAvailable } from '../utils'; +import { InfraPageTemplate } from '../../shared/templates/infra_page_template'; +import { OnboardingFlow } from '../../shared/templates/no_data_config'; + +const DATA_AVAILABILITY_PER_TYPE: Partial<Record<InventoryItemType, string[]>> = { + host: [SYSTEM_INTEGRATION], +}; export const Page = ({ tabs = [], links = [] }: ContentTemplateProps) => { const { loading } = useAssetDetailsRenderPropsContext(); @@ -31,12 +39,7 @@ export const Page = ({ tabs = [], links = [] }: ContentTemplateProps) => { const { activeTabId } = useTabSwitcherContext(); const { - services: { - telemetry, - observabilityShared: { - navigation: { PageTemplate }, - }, - }, + services: { telemetry }, } = useKibanaContextForPlugin(); const parentBreadcrumbResolver = useParentBreadcrumbResolver(); @@ -79,7 +82,9 @@ export const Page = ({ tabs = [], links = [] }: ContentTemplateProps) => { }, [activeTabId, asset.type, metadata, metadataLoading, telemetry]); return ( - <PageTemplate + <InfraPageTemplate + onboardingFlow={asset.type === 'host' ? OnboardingFlow.Hosts : OnboardingFlow.Infra} + dataAvailabilityModules={DATA_AVAILABILITY_PER_TYPE[asset.type] || undefined} pageHeader={{ pageTitle: asset.name, tabs: tabEntries, @@ -107,6 +112,6 @@ export const Page = ({ tabs = [], links = [] }: ContentTemplateProps) => { ) : ( <Content /> )} - </PageTemplate> + </InfraPageTemplate> ); }; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/error_callout.tsx b/x-pack/plugins/observability_solution/infra/public/components/error_callout.tsx similarity index 97% rename from x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/error_callout.tsx rename to x-pack/plugins/observability_solution/infra/public/components/error_callout.tsx index 41aab614f21cd..179f9206f467e 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/error_callout.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/error_callout.tsx @@ -17,8 +17,7 @@ import { import { KQLSyntaxError } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; - -import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; +import { useKibanaContextForPlugin } from '../hooks/use_kibana'; interface Props { error: Error; diff --git a/x-pack/plugins/observability_solution/infra/public/components/ml/anomaly_detection/job_setup_screen.tsx b/x-pack/plugins/observability_solution/infra/public/components/ml/anomaly_detection/job_setup_screen.tsx index 52150d0f9066a..87f58129ebf8b 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/ml/anomaly_detection/job_setup_screen.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/ml/anomaly_detection/job_setup_screen.tsx @@ -223,11 +223,13 @@ export const JobSetupScreen = (props: Props) => { /> <EuiSpacer /> {setupStatus.reasons.map((errorMessage, i) => ( - <EuiCallOut key={i} color="danger" iconType="warning" title={errorCalloutTitle}> - <EuiCode transparentBackground>{errorMessage}</EuiCode> - </EuiCallOut> + <> + <EuiCallOut key={i} color="danger" iconType="warning" title={errorCalloutTitle}> + <EuiCode transparentBackground>{errorMessage}</EuiCode> + </EuiCallOut> + <EuiSpacer /> + </> ))} - <EuiSpacer /> <EuiButton data-test-subj="infraJobSetupScreenTryAgainButton" fill onClick={createJobs}> <FormattedMessage id="xpack.infra.ml.steps.setupProcess.tryAgainButton" diff --git a/x-pack/plugins/observability_solution/infra/public/components/shared/alerts/alerts_overview.tsx b/x-pack/plugins/observability_solution/infra/public/components/shared/alerts/alerts_overview.tsx index 2cdc0ee81d7fe..55ac129e54664 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/shared/alerts/alerts_overview.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/shared/alerts/alerts_overview.tsx @@ -16,7 +16,6 @@ import { AlertsCount } from '../../../hooks/use_alerts_count'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; import { createAlertsEsQuery } from '../../../utils/filters/create_alerts_es_query'; import { ALERT_STATUS_ALL, infraAlertFeatureIds } from './constants'; -import { HostsStateUpdater } from '../../../pages/metrics/hosts/hooks/use_unified_search_url_state'; import AlertsStatusFilter from './alerts_status_filter'; import { useAssetDetailsUrlState } from '../../asset_details/hooks/use_asset_details_url_state'; @@ -24,7 +23,7 @@ interface AlertsOverviewProps { assetId: string; dateRange: TimeRange; onLoaded: (alertsCount?: AlertsCount) => void; - onRangeSelection?: HostsStateUpdater; + onRangeSelection?: (dateRange: TimeRange) => void; assetType?: InventoryItemType; } @@ -86,7 +85,7 @@ export const AlertsOverview = ({ const from = new Date(start).toISOString(); const to = new Date(end).toISOString(); - onRangeSelection({ dateRange: { from, to } }); + onRangeSelection({ from, to }); } }, [onRangeSelection] diff --git a/x-pack/plugins/observability_solution/infra/public/components/shared/templates/infra_page_template.tsx b/x-pack/plugins/observability_solution/infra/public/components/shared/templates/infra_page_template.tsx new file mode 100644 index 0000000000000..a20c32b42b7df --- /dev/null +++ b/x-pack/plugins/observability_solution/infra/public/components/shared/templates/infra_page_template.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; +import React, { useEffect } from 'react'; +import { GetHasDataResponse } from '../../../../common/metrics_sources/get_has_data'; +import { NoRemoteCluster } from '../../empty_states'; +import { SourceErrorPage } from '../../source_error_page'; +import { useMetricsDataViewContext, useSourceContext } from '../../../containers/metrics_source'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +import { ErrorCallout } from '../../error_callout'; +import { isPending, useFetcher } from '../../../hooks/use_fetcher'; +import { OnboardingFlow, getNoDataConfig } from './no_data_config'; + +export const InfraPageTemplate = ({ + 'data-test-subj': _dataTestSubj, + dataAvailabilityModules, + onboardingFlow = OnboardingFlow.Infra, + ...pageTemplateProps +}: Omit<LazyObservabilityPageTemplateProps, 'noDataConfig'> & { + dataAvailabilityModules?: string[]; + onboardingFlow?: OnboardingFlow; +}) => { + const { + services: { + observabilityAIAssistant, + observabilityShared: { + navigation: { PageTemplate }, + }, + share, + docLinks, + }, + } = useKibanaContextForPlugin(); + + const { source, error: sourceError, loadSource, isLoading: isSourceLoading } = useSourceContext(); + const { error: dataViewLoadError, refetch: loadDataView } = useMetricsDataViewContext(); + const { remoteClustersExist } = source?.status ?? {}; + + const { data, status } = useFetcher(async (callApi) => { + return await callApi<GetHasDataResponse>('/api/metrics/source/hasData', { + method: 'GET', + query: { + modules: dataAvailabilityModules, + }, + }); + }); + + const hasData = !!data?.hasData; + const noDataConfig = getNoDataConfig({ + hasData, + loading: isPending(status), + onboardingFlow, + docsLink: docLinks.links.observability.guide, + locators: share.url.locators, + }); + + const { setScreenContext } = observabilityAIAssistant?.service || {}; + + useEffect(() => { + return setScreenContext?.({ + data: [ + { + name: 'Metrics configuration', + value: source, + description: 'The configuration of the Metrics app', + }, + ], + starterPrompts: [ + ...(!hasData + ? [ + { + title: i18n.translate( + 'xpack.infra.metrics.aiAssistant.starterPrompts.explainNoData.title', + { + defaultMessage: 'Explain', + } + ), + prompt: i18n.translate( + 'xpack.infra.metrics.aiAssistant.starterPrompts.explainNoData.prompt', + { + defaultMessage: "Why don't I see any data?", + } + ), + icon: 'sparkles', + }, + ] + : []), + ], + }); + }, [hasData, setScreenContext, source]); + + if (!isSourceLoading && !remoteClustersExist) { + return <NoRemoteCluster />; + } + + if (sourceError) { + <SourceErrorPage errorMessage={sourceError} retry={loadSource} />; + } + + if (dataViewLoadError) { + <ErrorCallout + error={dataViewLoadError} + titleOverride={i18n.translate('xpack.infra.hostsViewPage.errorOnCreateOrLoadDataviewTitle', { + defaultMessage: 'Error creating Data View', + })} + messageOverride={i18n.translate('xpack.infra.hostsViewPage.errorOnCreateOrLoadDataview', { + defaultMessage: + 'There was an error trying to create a Data View: {metricAlias}. Try reloading the page.', + values: { metricAlias: source?.configuration.metricAlias ?? '' }, + })} + onTryAgainClick={loadDataView} + hasTryAgainButton + />; + } + + return ( + <PageTemplate + data-test-subj={hasData ? _dataTestSubj : 'noDataPage'} + noDataConfig={noDataConfig} + {...pageTemplateProps} + /> + ); +}; diff --git a/x-pack/plugins/observability_solution/infra/public/components/shared/templates/no_data_config.ts b/x-pack/plugins/observability_solution/infra/public/components/shared/templates/no_data_config.ts new file mode 100644 index 0000000000000..faf53cccca4cd --- /dev/null +++ b/x-pack/plugins/observability_solution/infra/public/components/shared/templates/no_data_config.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { EuiCardProps } from '@elastic/eui'; +import type { NoDataConfig } from '@kbn/shared-ux-page-kibana-template'; +import { NoDataPageProps } from '@kbn/shared-ux-page-no-data-types'; +import { LocatorClient } from '@kbn/share-plugin/common/url_service'; +import { + OBSERVABILITY_ONBOARDING_LOCATOR, + type ObservabilityOnboardingLocatorParams, +} from '@kbn/deeplinks-observability'; +import { noMetricIndicesPromptDescription, noMetricIndicesPromptTitle } from '../../empty_states'; + +export enum OnboardingFlow { + Infra = 'infra', + Hosts = 'logs', +} + +interface NoDataConfigDetails { + onboardingFlow: OnboardingFlow; + docsLink?: string; + locators: LocatorClient; +} + +const createCardConfig = ( + onboardingFlow: OnboardingFlow, + locators: LocatorClient +): Pick<EuiCardProps, 'title' | 'description' | 'href'> => { + const onboardingLocator = locators.get<ObservabilityOnboardingLocatorParams>( + OBSERVABILITY_ONBOARDING_LOCATOR + ); + switch (onboardingFlow) { + case OnboardingFlow.Hosts: { + return { + title: i18n.translate('xpack.infra.hostsViewPage.noData.card.cta', { + defaultMessage: 'Add data', + }), + description: i18n.translate('xpack.infra.hostsViewPage.noData.card.description', { + defaultMessage: + 'Start collecting data for your hosts to understand metric trends, explore logs and deep insight into their performance', + }), + href: onboardingLocator?.getRedirectUrl({ category: onboardingFlow }), + }; + } + default: { + return { + title: noMetricIndicesPromptTitle, + description: noMetricIndicesPromptDescription, + href: onboardingLocator?.getRedirectUrl({ category: onboardingFlow }), + }; + } + } +}; + +const createPageConfig = ( + onboardingFlow: OnboardingFlow, + docsLink?: string +): Pick<NoDataPageProps, 'pageTitle' | 'pageDescription' | 'docsLink'> | undefined => { + switch (onboardingFlow) { + case OnboardingFlow.Hosts: { + return { + pageTitle: i18n.translate('xpack.infra.hostsViewPage.noData.page.title', { + defaultMessage: 'Detect and resolve problems with your hosts', + }), + pageDescription: i18n.translate('xpack.infra.hostsViewPage.noData.page.description', { + defaultMessage: + 'Understand how your hosts are performing so you can take action before it becomes a problem.', + }), + }; + } + default: { + return { + docsLink, + }; + } + } +}; + +const getNoDataConfigDetails = ({ + onboardingFlow, + locators, + docsLink, +}: NoDataConfigDetails): NoDataConfig => { + return { + ...createPageConfig(onboardingFlow, docsLink), + solution: i18n.translate('xpack.infra.metrics.noDataConfig.solutionName', { + defaultMessage: 'Observability', + }), + action: { + beats: { + ...createCardConfig(onboardingFlow, locators), + }, + }, + }; +}; + +export const getNoDataConfig = ({ + hasData, + loading, + locators, + onboardingFlow, + docsLink, +}: { + hasData: boolean; + loading: boolean; + onboardingFlow: OnboardingFlow; + locators: LocatorClient; + docsLink?: string; +}): NoDataConfig | undefined => { + if (hasData || loading) { + return; + } + + return getNoDataConfigDetails({ onboardingFlow, locators, docsLink }); +}; diff --git a/x-pack/plugins/observability_solution/infra/public/containers/ml/api/ml_setup_module_api.ts b/x-pack/plugins/observability_solution/infra/public/containers/ml/api/ml_setup_module_api.ts index 49a6d70cd859b..86e4e7fafea72 100644 --- a/x-pack/plugins/observability_solution/infra/public/containers/ml/api/ml_setup_module_api.ts +++ b/x-pack/plugins/observability_solution/infra/public/containers/ml/api/ml_setup_module_api.ts @@ -92,8 +92,19 @@ const setupMlModuleRequestPayloadRT = rt.intersection([ setupMlModuleRequestParamsRT, ]); +const setupErrorRT = rt.type({ + reason: rt.string, + type: rt.string, +}); + const setupErrorResponseRT = rt.type({ - msg: rt.string, + status: rt.number, + error: rt.intersection([ + setupErrorRT, + rt.type({ + root_cause: rt.array(setupErrorRT), + }), + ]), }); const datafeedSetupResponseRT = rt.intersection([ diff --git a/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.test.ts b/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.test.ts new file mode 100644 index 0000000000000..80d538ef1e50b --- /dev/null +++ b/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.test.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 { renderHook, act } from '@testing-library/react-hooks'; +import { useModuleStatus } from './infra_ml_module_status'; + +describe('useModuleStatus', () => { + it('should handle failedSetup action with job and datafeed error reasons', () => { + const { result } = renderHook(() => useModuleStatus(['job'])); + const datafeedSetupResults = datafeedSetupResultsWithError('datafeed'); + const jobSetupResults = jobSetupResultsWithError('job'); + + act(() => { + result.current[1]({ + type: 'finishedSetup', + sourceId: 'sourceId', + spaceId: 'spaceId', + jobSetupResults, + jobSummaries: [], + datafeedSetupResults, + }); + }); + + expect(result.current[0].setupStatus).toEqual({ + type: 'failed', + reasons: [datafeedSetupResults[0].error.error.reason, jobSetupResults[0].error.error.reason], + }); + }); + + const setupResultError = (id: string) => { + const errorType = 'error'; + + return { + error: { + status: 500, + error: { + reason: `${id} failed`, + type: errorType, + root_cause: [ + { + reason: `${id} failed (root cause)`, + type: errorType, + }, + ], + }, + }, + }; + }; + + const datafeedSetupResultsWithError = (id: string) => { + return [ + { + id, + success: true, + started: true, + ...setupResultError(id), + }, + ]; + }; + + const jobSetupResultsWithError = (id: string) => { + return [ + { + id, + success: true, + ...setupResultError(id), + }, + ]; + }; +}); diff --git a/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.tsx b/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.tsx index ea222080d410c..26b8a51da6d2b 100644 --- a/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.tsx +++ b/x-pack/plugins/observability_solution/infra/public/containers/ml/infra_ml_module_status.tsx @@ -107,10 +107,10 @@ const createStatusReducer = reasons: [ ...Object.values(datafeedSetupResults) .filter(hasError) - .map((datafeed) => datafeed.error.msg), + .map((datafeed) => datafeed.error.error.reason), ...Object.values(jobSetupResults) .filter(hasError) - .map((job) => job.error.msg), + .map((job) => job.error.error.reason), ], }; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts index 46b5ce97ee4ca..581d8d3011cd6 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_form_state.ts @@ -12,6 +12,8 @@ import { LogDataViewReference, LogIndexNameReference, logIndexNameReferenceRT, + LogSourcesKibanaAdvancedSettingReference, + logSourcesKibanaAdvancedSettingRT, } from '@kbn/logs-shared-plugin/common'; import { useKibanaIndexPatternService } from '../../../hooks/use_kibana_index_patterns'; import { useFormElement } from './form_elements'; @@ -22,7 +24,11 @@ import { validateStringNoSpaces, } from './validation_errors'; -export type LogIndicesFormState = LogIndexNameReference | LogDataViewReference | undefined; +export type LogIndicesFormState = + | LogIndexNameReference + | LogDataViewReference + | LogSourcesKibanaAdvancedSettingReference + | undefined; export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => { const indexPatternService = useKibanaIndexPatternService(); @@ -35,6 +41,8 @@ export const useLogIndicesFormElement = (initialValue: LogIndicesFormState) => { () => async (logIndices) => { if (logIndices == null) { return validateStringNotEmpty('log data view', ''); + } else if (logSourcesKibanaAdvancedSettingRT.is(logIndices)) { + return []; } else if (logIndexNameReferenceRT.is(logIndices)) { return [ ...validateStringNotEmpty('log indices', logIndices.indexName), diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx index bcde2cf84bb4a..0aad03315c8e1 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/indices_configuration_panel.tsx @@ -14,6 +14,7 @@ import { LogDataViewReference, logDataViewReferenceRT, LogIndexReference, + logSourcesKibanaAdvancedSettingRT, } from '@kbn/logs-shared-plugin/common'; import { EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -27,6 +28,7 @@ import { FormElement, isFormElementForType } from './form_elements'; import { IndexNamesConfigurationPanel } from './index_names_configuration_panel'; import { IndexPatternConfigurationPanel } from './index_pattern_configuration_panel'; import { FormValidationError } from './validation_errors'; +import { KibanaAdvancedSettingConfigurationPanel } from './kibana_advanced_setting_configuration_panel'; export const IndicesConfigurationPanel = React.memo<{ isLoading: boolean; @@ -75,6 +77,17 @@ export const IndicesConfigurationPanel = React.memo<{ }); }, [indicesFormElement, trackChangeIndexSourceType]); + const changeToKibanaAdvancedSettingType = useCallback(() => { + // This is always a readonly value, synced with the setting, we just reset back to the correct type. + indicesFormElement.updateValue(() => ({ + type: 'kibana_advanced_setting', + })); + + trackChangeIndexSourceType({ + metric: 'configuration_switch_to_kibana_advanced_setting_reference', + }); + }, [indicesFormElement, trackChangeIndexSourceType]); + useEffect(() => { const getNumberOfInfraRules = async () => { if (http) { @@ -107,6 +120,34 @@ export const IndicesConfigurationPanel = React.memo<{ ), }} > + {' '} + <EuiCheckableCard + id="kibanaAdvancedSetting" + label={ + <EuiTitle size="xs"> + <h2> + <FormattedMessage + id="xpack.infra.logSourceConfiguration.kibanaAdvancedSettingSectionTitle" + defaultMessage="Kibana log sources advanced setting" + /> + </h2> + </EuiTitle> + } + name="kibanaAdvancedSetting" + value="kibanaAdvancedSetting" + checked={isKibanaAdvancedSettingFormElement(indicesFormElement)} + onChange={changeToKibanaAdvancedSettingType} + disabled={isReadOnly} + > + {isKibanaAdvancedSettingFormElement(indicesFormElement) && ( + <KibanaAdvancedSettingConfigurationPanel + isLoading={isLoading} + isReadOnly={isReadOnly} + advancedSettingFormElement={indicesFormElement} + /> + )} + </EuiCheckableCard> + <EuiSpacer size="m" /> <EuiCheckableCard id="dataView" label={ @@ -114,7 +155,7 @@ export const IndicesConfigurationPanel = React.memo<{ <h2> <FormattedMessage id="xpack.infra.logSourceConfiguration.dataViewSectionTitle" - defaultMessage="Data view (recommended)" + defaultMessage="Data view (deprecated)" /> </h2> </EuiTitle> @@ -134,15 +175,14 @@ export const IndicesConfigurationPanel = React.memo<{ )} </EuiCheckableCard> <EuiSpacer size="m" /> - <EuiCheckableCard id="indexNames" label={ <EuiTitle size="xs"> <h2> <FormattedMessage - id="xpack.infra.sourceConfiguration.indicesSectionTitle" - defaultMessage="Indices" + id="xpack.infra.sourceConfiguration.logsIndicesSectionTitle" + defaultMessage="Indices (deprecated)" /> </h2> </EuiTitle> @@ -152,6 +192,7 @@ export const IndicesConfigurationPanel = React.memo<{ checked={isIndexNamesFormElement(indicesFormElement)} onChange={changeToIndexNameType} disabled={isReadOnly} + data-test-subj="logIndicesCheckableCard" > {isIndexNamesFormElement(indicesFormElement) && ( <IndexNamesConfigurationPanel @@ -201,3 +242,7 @@ const isDataViewFormElement = isFormElementForType( ); const isIndexNamesFormElement = isFormElementForType(logIndexNameReferenceRT.is); + +const isKibanaAdvancedSettingFormElement = isFormElementForType( + logSourcesKibanaAdvancedSettingRT.is +); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/kibana_advanced_setting_configuration_panel.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/kibana_advanced_setting_configuration_panel.tsx new file mode 100644 index 0000000000000..1d7d4ad997407 --- /dev/null +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/kibana_advanced_setting_configuration_panel.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiDescribedFormGroup, EuiFieldText, EuiFormRow } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useEffect, useMemo, useState } from 'react'; +import { useTrackPageview } from '@kbn/observability-shared-plugin/public'; +import { LogSourcesKibanaAdvancedSettingReference } from '@kbn/logs-shared-plugin/common'; +import { ApplicationStart } from '@kbn/core-application-browser'; +import { EuiLink } from '@elastic/eui'; +import { useTrackedPromise } from '../../../hooks/use_tracked_promise'; +import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +import { FormElement } from './form_elements'; +import { getFormRowProps } from './form_field_props'; +import { FormValidationError } from './validation_errors'; + +function getKibanaAdvancedSettingsHref(application: ApplicationStart) { + return application.getUrlForApp('management', { + path: `/kibana/settings?query=${encodeURIComponent('Log sources')}`, + }); +} + +export const KibanaAdvancedSettingConfigurationPanel: React.FC<{ + isLoading: boolean; + isReadOnly: boolean; + advancedSettingFormElement: FormElement< + LogSourcesKibanaAdvancedSettingReference, + FormValidationError + >; +}> = ({ isLoading, isReadOnly, advancedSettingFormElement }) => { + const { + services: { application, logsDataAccess }, + } = useKibanaContextForPlugin(); + + useTrackPageview({ app: 'infra_logs', path: 'log_source_configuration_kibana_advanced_setting' }); + useTrackPageview({ + app: 'infra_logs', + path: 'log_source_configuration_kibana_advanced_setting', + delay: 15000, + }); + + const advancedSettingsHref = useMemo( + () => getKibanaAdvancedSettingsHref(application), + [application] + ); + + const [logSourcesSettingValue, setLogSourcesSettingValue] = useState<string | undefined>( + undefined + ); + + const [getLogSourcesRequest, getLogSources] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + return await logsDataAccess.services.logSourcesService.getLogSources(); + }, + onResolve: (response) => { + setLogSourcesSettingValue(response.map((logSource) => logSource.indexPattern).join(',')); + }, + }, + [] + ); + + const isLoadingLogSourcesSetting = useMemo( + () => getLogSourcesRequest.state === 'pending', + [getLogSourcesRequest.state] + ); + + useEffect(() => { + getLogSources(); + }, [getLogSources]); + + return ( + <> + <EuiDescribedFormGroup + title={ + <h4> + <FormattedMessage + id="xpack.infra.sourceConfiguration.logSourcesSettingTitle" + defaultMessage="Advanced setting" + /> + </h4> + } + description={ + <FormattedMessage + id="xpack.infra.sourceConfiguration.logSourcesSettingDescription" + defaultMessage="This value is synchronised with the Kibana log sources advanced setting. It can be changed via the {advancedSettingsLink}." + values={{ + advancedSettingsLink: ( + <EuiLink + data-test-subj="xpack.infra.sourceConfiguration.logSourcesSettingLink" + href={advancedSettingsHref} + > + <FormattedMessage + id="xpack.infra.sourceConfiguration.logSourcesSettingLinkText" + defaultMessage="advanced settings page" + /> + </EuiLink> + ), + }} + /> + } + > + <EuiFormRow + fullWidth + helpText={ + <FormattedMessage + id="xpack.infra.sourceConfiguration.logSourcesSettingValue" + defaultMessage="The current setting value" + /> + } + label={ + <FormattedMessage + id="xpack.infra.sourceConfiguration.logSourcesSettingLabel" + defaultMessage="Log sources advanced setting" + /> + } + {...getFormRowProps(advancedSettingFormElement)} + > + <EuiFieldText + data-test-subj="logSourcesSettingInput" + fullWidth + disabled={isLoading} + isLoading={isLoadingLogSourcesSetting} + readOnly={true} + value={logSourcesSettingValue} + isInvalid={false} + /> + </EuiFormRow> + </EuiDescribedFormGroup> + </> + ); +}; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx index 5ea244e5e0a52..01035f8259a0f 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/settings/source_configuration_form_state.tsx @@ -19,8 +19,7 @@ export const useLogSourceConfigurationFormState = (logViewAttributes?: LogViewAt useMemo( () => logViewAttributes?.logIndices ?? { - type: 'index_name', - indexName: '', + type: 'kibana_advanced_setting', }, [logViewAttributes] ) diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/hosts_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/hosts_content.tsx index 50feb6491578b..c3e53c2d82369 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/hosts_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/hosts_content.tsx @@ -7,13 +7,13 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ErrorCallout } from '../../../../components/error_callout'; import { HostsTable } from './hosts_table'; import { KPIGrid } from './kpis/kpi_grid'; import { Tabs } from './tabs/tabs'; import { AlertsQueryProvider } from '../hooks/use_alerts_query'; import { HostsViewProvider } from '../hooks/use_hosts_view'; import { HostsTableProvider } from '../hooks/use_hosts_table'; -import { ErrorCallout } from './error_callout'; import { useUnifiedSearchContext } from '../hooks/use_unified_search'; import { HostCountProvider } from '../hooks/use_host_count'; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx index f485f8b6dedfb..084584c37c147 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx @@ -6,7 +6,7 @@ */ import React, { useCallback } from 'react'; -import type { Query, TimeRange, Filter } from '@kbn/es-query'; +import type { TimeRange } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { useEuiTheme, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { css } from '@emotion/react'; @@ -22,28 +22,21 @@ export const UnifiedSearchBar = () => { services: { unifiedSearch, application }, } = useKibanaContextForPlugin(); const { metricsView } = useMetricsDataViewContext(); - const { searchCriteria, onSubmit } = useUnifiedSearchContext(); + const { searchCriteria, onLimitChange, onPanelFiltersChange, onSubmit } = + useUnifiedSearchContext(); const { SearchBar } = unifiedSearch.ui; - const onLimitChange = (limit: number) => { - onSubmit({ limit }); - }; - - const onPanelFiltersChange = useCallback( - (panelFilters: Filter[]) => { - onSubmit({ panelFilters }); + const handleRefresh = useCallback( + (payload: { dateRange: TimeRange }, isUpdate?: boolean) => { + // This makes sure `onSubmit` is only called when the submit button is clicked + if (isUpdate === false) { + onSubmit(payload); + } }, [onSubmit] ); - const handleRefresh = (payload: { query?: Query; dateRange: TimeRange }, isUpdate?: boolean) => { - // This makes sure `onQueryChange` is only called when the submit button is clicked - if (isUpdate === false) { - onSubmit(payload); - } - }; - return ( <StickyContainer> <EuiFlexGroup direction="column" gutterSize="s"> diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx index 6350cab5e3ff6..a3926b95383d9 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/alerts/alerts_tab_content.tsx @@ -10,11 +10,12 @@ import { AlertConsumers, ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils'; import { BrushEndListener, type XYBrushEvent } from '@elastic/charts'; import { useSummaryTimeRange } from '@kbn/observability-plugin/public'; import { useBoolean } from '@kbn/react-hooks'; +import type { TimeRange } from '@kbn/es-query'; import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; import { HeightRetainer } from '../../../../../../components/height_retainer'; import { useUnifiedSearchContext } from '../../../hooks/use_unified_search'; import { useAlertsQuery } from '../../../hooks/use_alerts_query'; -import { HostsState, HostsStateUpdater } from '../../../hooks/use_unified_search_url_state'; +import type { HostsState } from '../../../hooks/use_unified_search_url_state'; import { AlertsEsQuery } from '../../../../../../utils/filters/create_alerts_es_query'; import { ALERTS_PER_PAGE, @@ -35,7 +36,7 @@ export const AlertsTabContent = () => { const { alertStatus, setAlertStatus, alertsEsQueryByStatus } = useAlertsQuery(); const [isAlertFlyoutVisible, { toggle: toggleAlertFlyout }] = useBoolean(false); - const { onSubmit, searchCriteria } = useUnifiedSearchContext(); + const { onDateRangeChange, searchCriteria } = useUnifiedSearchContext(); const { triggersActionsUi } = services; @@ -71,7 +72,7 @@ export const AlertsTabContent = () => { <MemoAlertSummaryWidget alertsQuery={alertsEsQueryByStatus} dateRange={searchCriteria.dateRange} - onRangeSelection={onSubmit} + onRangeSelection={onDateRangeChange} /> </EuiFlexItem> {alertsEsQueryByStatus && ( @@ -102,7 +103,7 @@ export const AlertsTabContent = () => { interface MemoAlertSummaryWidgetProps { alertsQuery: AlertsEsQuery; dateRange: HostsState['dateRange']; - onRangeSelection: HostsStateUpdater; + onRangeSelection: (dateRange: TimeRange) => void; } const MemoAlertSummaryWidget = React.memo( @@ -122,7 +123,7 @@ const MemoAlertSummaryWidget = React.memo( const from = new Date(start).toISOString(); const to = new Date(end).toISOString(); - onRangeSelection({ dateRange: { from, to } }); + onRangeSelection({ from, to }); } }; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts index 5df80a9e3634d..2596d984c6b3a 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts @@ -41,7 +41,7 @@ const mockHostNode: InfraAssetMetricsItem[] = [ { metrics: [ { - name: 'cpu', + name: 'cpuTotal', value: 0.6353277777777777, }, { @@ -79,7 +79,7 @@ const mockHostNode: InfraAssetMetricsItem[] = [ { metrics: [ { - name: 'cpu', + name: 'cpuTotal', value: 0.8647805555555556, }, { @@ -169,7 +169,7 @@ describe('useHostTable hook', () => { rx: 252456.92916666667, tx: 252758.425, memory: 0.94525, - cpu: 0.6353277777777777, + cpuTotal: 0.6353277777777777, diskSpaceUsage: 0.2040001, memoryFree: 34359.738368, normalizedLoad1m: 239.2040001, @@ -187,7 +187,7 @@ describe('useHostTable hook', () => { rx: 95.86339715321859, tx: 110.38566859563191, memory: 0.5400000214576721, - cpu: 0.8647805555555556, + cpuTotal: 0.8647805555555556, diskSpaceUsage: 0.5400000214576721, memoryFree: 9.194304, normalizedLoad1m: 100, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx index cdafb71784ab9..ed5b29d10b9ed 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -287,10 +287,10 @@ export const useHostsTable = () => { /> ), width: metricColumnsWidth, - field: 'cpu', + field: 'cpuTotal', sortable: true, 'data-test-subj': 'hostsView-tableRow-cpuUsage', - render: (avg: number) => formatMetric('cpu', avg), + render: (avg: number) => formatMetric('cpuTotal', avg), align: 'right', }, { diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts index 700046a9f936c..655db7941d753 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts @@ -26,7 +26,7 @@ import { import { StringDateRange } from './use_unified_search_url_state'; const HOST_TABLE_METRICS: Array<{ type: InfraAssetMetricType }> = [ - { type: 'cpu' }, + { type: 'cpuTotal' }, { type: 'diskSpaceUsage' }, { type: 'memory' }, { type: 'memoryFree' }, @@ -43,13 +43,15 @@ export const useHostsView = () => { } = useKibanaContextForPlugin(); const { buildQuery, parsedDateRange, searchCriteria } = useUnifiedSearchContext(); - const baseRequest = useMemo( + const payload = useMemo( () => - createInfraMetricsRequest({ - dateRange: parsedDateRange, - esQuery: buildQuery(), - limit: searchCriteria.limit, - }), + JSON.stringify( + createInfraMetricsRequest({ + dateRange: parsedDateRange, + esQuery: buildQuery(), + limit: searchCriteria.limit, + }) + ), [buildQuery, parsedDateRange, searchCriteria.limit] ); @@ -60,7 +62,7 @@ export const useHostsView = () => { BASE_INFRA_METRICS_PATH, { method: 'POST', - body: JSON.stringify(baseRequest), + body: payload, } ); const duration = performance.now() - start; @@ -72,7 +74,7 @@ export const useHostsView = () => { ); return metricsResponse; }, - [baseRequest, searchCriteria.limit, telemetry] + [payload, searchCriteria.limit, telemetry] ); return { diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts index d0458aad78646..8912ec480e3b7 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts @@ -6,9 +6,8 @@ */ import createContainer from 'constate'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { buildEsQuery, fromKueryExpression, type Query } from '@kbn/es-query'; -import { map, skip, startWith } from 'rxjs'; -import { combineLatest } from 'rxjs'; +import { buildEsQuery, Filter, fromKueryExpression, TimeRange, type Query } from '@kbn/es-query'; +import { Subscription, map, tap } from 'rxjs'; import deepEqual from 'fast-deep-equal'; import useEffectOnce from 'react-use/lib/useEffectOnce'; import { useSearchSessionContext } from '../../../../hooks/use_search_session'; @@ -18,10 +17,10 @@ import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; import { telemetryTimeRangeFormatter } from '../../../../../common/formatters/telemetry_time_range'; import { useMetricsDataViewContext } from '../../../../containers/metrics_source'; import { - HostsSearchPayload, useHostsUrlState, type HostsState, type StringDateRangeTimestamp, + StringDateRange, } from './use_unified_search_url_state'; import { retrieveFieldsFromFilter } from '../../../../utils/filters/build'; @@ -60,11 +59,7 @@ export const useUnifiedSearch = () => { const { data: { - query: { - filterManager: filterManagerService, - queryString: queryStringService, - timefilter: timeFilterService, - }, + query: { filterManager: filterManagerService, queryString: queryStringService }, }, telemetry, } = services; @@ -76,30 +71,53 @@ export const useUnifiedSearch = () => { [kibanaQuerySettings] ); - const onSubmit = useCallback( - (params?: HostsSearchPayload) => { + const onFiltersChange = useCallback( + (filters: Filter[]) => { + setSearch({ type: 'SET_FILTERS', filters }); + }, + [setSearch] + ); + + const onPanelFiltersChange = useCallback( + (panelFilters: Filter[]) => { + setSearch({ type: 'SET_PANEL_FILTERS', panelFilters }); + }, + [setSearch] + ); + + const onLimitChange = useCallback( + (limit: number) => { + setSearch({ type: 'SET_LIMIT', limit }); + }, + [setSearch] + ); + + const onDateRangeChange = useCallback( + (dateRange: StringDateRange) => { + setSearch({ type: 'SET_DATE_RANGE', dateRange }); + }, + [setSearch] + ); + + const onQueryChange = useCallback( + (query: Query) => { try { setError(null); - /* - / Validates the Search Bar input values before persisting them in the state. - / Since the search can be triggered by components that are unaware of the Unified Search state (e.g Controls and Host Limit), - / this will always validates the query bar value, regardless of whether it's been sent in the current event or not. - */ - validateQuery(params?.query ?? (queryStringService.getQuery() as Query)); - setSearch(params ?? {}); - updateSearchSessionId(); + validateQuery(query); + setSearch({ type: 'SET_QUERY', query }); } catch (err) { - /* - / Persists in the state the params so they can be used in case the query bar is fixed by the user. - / This is needed because the Unified Search observables are unnaware of the other componets in the search bar. - / Invalid query isn't persisted because it breaks the Control component - */ - const { query, ...validParams } = params ?? {}; - setSearch(validParams ?? {}); setError(err); } }, - [queryStringService, setSearch, updateSearchSessionId, validateQuery] + [validateQuery, setSearch] + ); + + const onSubmit = useCallback( + ({ dateRange }: { dateRange: TimeRange }) => { + onDateRangeChange(dateRange); + updateSearchSessionId(); + }, + [onDateRangeChange, updateSearchSessionId] ); const parsedDateRange = useMemo(() => { @@ -153,27 +171,31 @@ export const useUnifiedSearch = () => { }); useEffect(() => { - const filters$ = filterManagerService.getUpdates$().pipe( - startWith(undefined), - map(() => filterManagerService.getFilters()) + const subscription = new Subscription(); + subscription.add( + filterManagerService + .getUpdates$() + .pipe( + map(() => filterManagerService.getFilters()), + tap((filters) => onFiltersChange(filters)) + ) + .subscribe() ); - const query$ = queryStringService.getUpdates$().pipe( - startWith(undefined), - map(() => queryStringService.getQuery() as Query) + subscription.add( + queryStringService + .getUpdates$() + .pipe( + map(() => queryStringService.getQuery() as Query), + tap((query) => onQueryChange(query)) + ) + .subscribe() ); - const subscription = combineLatest({ - filters: filters$, - query: query$, - }) - .pipe(skip(1)) - .subscribe(onSubmit); - return () => { subscription.unsubscribe(); }; - }, [filterManagerService, onSubmit, queryStringService, timeFilterService.timefilter]); + }, [filterManagerService, queryStringService, onQueryChange, onFiltersChange]); // Track telemetry event on query/filter/date changes useEffect(() => { @@ -190,6 +212,9 @@ export const useUnifiedSearch = () => { parsedDateRange, getDateRangeAsTimestamp, searchCriteria, + onDateRangeChange, + onLimitChange, + onPanelFiltersChange, }; }; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts index e4308b34c3de1..4c96fd93524be 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useReducer } from 'react'; +import { Dispatch, useReducer } from 'react'; import deepEqual from 'fast-deep-equal'; import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; @@ -37,16 +37,31 @@ const INITIAL_HOSTS_STATE: HostsState = { limit: DEFAULT_HOST_LIMIT, }; -const reducer = (prevState: HostsState, params: HostsSearchPayload) => { - const payload = Object.fromEntries(Object.entries(params).filter(([_, v]) => !!v)); - - return { - ...prevState, - ...payload, - }; +export type HostsStateAction = + | { type: 'SET_DATE_RANGE'; dateRange: StringDateRange } + | { type: 'SET_LIMIT'; limit: number } + | { type: 'SET_FILTERS'; filters: HostsState['filters'] } + | { type: 'SET_QUERY'; query: HostsState['query'] } + | { type: 'SET_PANEL_FILTERS'; panelFilters: HostsState['panelFilters'] }; + +const reducer = (state: HostsState, action: HostsStateAction): HostsState => { + switch (action.type) { + case 'SET_DATE_RANGE': + return { ...state, dateRange: action.dateRange }; + case 'SET_LIMIT': + return { ...state, limit: action.limit }; + case 'SET_FILTERS': + return { ...state, filters: action.filters }; + case 'SET_QUERY': + return { ...state, query: action.query }; + case 'SET_PANEL_FILTERS': + return { ...state, panelFilters: action.panelFilters }; + default: + return state; + } }; -export const useHostsUrlState = (): [HostsState, HostsStateUpdater] => { +export const useHostsUrlState = (): [HostsState, Dispatch<HostsStateAction>] => { const [getTime] = useKibanaTimefilterTime(INITIAL_DATE_RANGE); const [localStorageHostLimit, setLocalStorageHostLimit] = useLocalStorage<number>( LOCAL_STORAGE_HOST_LIMIT_KEY, @@ -74,7 +89,7 @@ export const useHostsUrlState = (): [HostsState, HostsStateUpdater] => { } useSyncKibanaTimeFilterTime(INITIAL_DATE_RANGE, urlState.dateRange, (dateRange) => - setSearch({ dateRange }) + setSearch({ type: 'SET_DATE_RANGE', dateRange }) ); return [search, setSearch]; @@ -131,8 +146,6 @@ export type HostsState = rt.TypeOf<typeof HostsStateRT>; export type HostsSearchPayload = Partial<HostsState>; -export type HostsStateUpdater = (params: HostsSearchPayload) => void; - export type StringDateRange = rt.TypeOf<typeof StringDateRangeRT>; export interface StringDateRangeTimestamp { from: number; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/index.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/index.tsx index 998262283cf9a..c5853f533a222 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/index.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/index.tsx @@ -11,9 +11,11 @@ import { useTrackPageview, FeatureFeedbackButton } from '@kbn/observability-shar import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; +import { OnboardingFlow } from '../../../components/shared/templates/no_data_config'; +import { InfraPageTemplate } from '../../../components/shared/templates/infra_page_template'; +import { SYSTEM_INTEGRATION } from '../../../../common/constants'; import { useKibanaEnvironmentContext } from '../../../hooks/use_kibana'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; -import { MetricsPageTemplate } from '../page_template'; import { hostsTitle } from '../../../translations'; import { fullHeightContentStyles } from '../../../page_template.styles'; import { HostContainer } from './components/hosts_container'; @@ -22,6 +24,8 @@ import { BetaBadge } from '../../../components/beta_badge'; const HOSTS_FEEDBACK_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLScRHG8TIVb1Oq8ZhD4aks3P1TmgiM58TY123QpDCcBz83YC6w/viewform'; +const DATA_AVAILABILITY_MODULES = [SYSTEM_INTEGRATION]; + export const HostsPage = () => { const { kibanaVersion, isCloudEnv, isServerlessEnv } = useKibanaEnvironmentContext(); @@ -37,7 +41,9 @@ export const HostsPage = () => { return ( <EuiErrorBoundary> <div className={APP_WRAPPER_CLASS}> - <MetricsPageTemplate + <InfraPageTemplate + dataAvailabilityModules={DATA_AVAILABILITY_MODULES} + onboardingFlow={OnboardingFlow.Hosts} pageHeader={{ alignItems: 'center', pageTitle: ( @@ -74,7 +80,7 @@ export const HostsPage = () => { }} > <HostContainer /> - </MetricsPageTemplate> + </InfraPageTemplate> </div> </EuiErrorBoundary> ); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/index.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/index.tsx index eec662de1a9dc..73e15b4e362a8 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/index.tsx @@ -42,6 +42,7 @@ import { usePluginConfig } from '../../containers/plugin_config_context'; import { HostsPage } from './hosts'; import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params'; import { SearchSessionProvider } from '../../hooks/use_search_session'; +import { OnboardingFlow } from '../../components/shared/templates/no_data_config'; const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { defaultMessage: 'Add data', @@ -49,14 +50,11 @@ const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLab export const InfrastructurePage = () => { const config = usePluginConfig(); - const { application, share } = useKibana<{ share: SharePublicStart }>().services; + const { application } = useKibana<{ share: SharePublicStart }>().services; const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext); const isHostsViewEnabled = useUiSetting(enableInfrastructureHostsView); const uiCapabilities = application?.capabilities; - const onboardingLocator = share?.url.locators.get<ObservabilityOnboardingLocatorParams>( - OBSERVABILITY_ONBOARDING_LOCATOR - ); const settingsTabTitle = i18n.translate('xpack.infra.metrics.settingsTabTitle', { defaultMessage: 'Settings', @@ -89,25 +87,27 @@ export const InfrastructurePage = () => { <EuiHeaderLink color={'text'} {...settingsLinkProps}> {settingsTabTitle} </EuiHeaderLink> - <Route path="/inventory" component={AnomalyDetectionFlyout} /> - <Route - path="/hosts" - render={() => <AnomalyDetectionFlyout hideJobType hideSelectGroup />} - /> - <Route - path="/detail/host" - render={() => <AnomalyDetectionFlyout hideJobType hideSelectGroup />} - /> + <Routes> + <HeaderLinkAnomalyFlyoutRoute path="/inventory" /> + <HeaderLinkAnomalyFlyoutRoute path="/hosts" /> + <HeaderLinkAnomalyFlyoutRoute path="/detail/host/:node" /> + </Routes> {config.featureFlags.alertsAndRulesDropdownEnabled && ( <MetricsAlertDropdown /> )} - <EuiHeaderLink - href={onboardingLocator?.useUrl({ category: 'infra' })} - color="primary" - iconType="indexOpen" - > - {ADD_DATA_LABEL} - </EuiHeaderLink> + <Routes> + <HeaderLinkAddDataRoute + path="/hosts" + onboardingFlow={OnboardingFlow.Hosts} + exact + /> + <HeaderLinkAddDataRoute + path="/detail/host/:node" + onboardingFlow={OnboardingFlow.Hosts} + exact + /> + <HeaderLinkAddDataRoute path="/" onboardingFlow={OnboardingFlow.Infra} /> + </Routes> </EuiHeaderLinks> </EuiFlexItem> </EuiFlexGroup> @@ -144,3 +144,46 @@ export const InfrastructurePage = () => { </EuiErrorBoundary> ); }; + +const HeaderLinkAnomalyFlyoutRoute = ({ path }: { path: string }) => { + const isInventory = path !== '/inventory'; + return ( + <Route + path={path} + render={() => ( + <AnomalyDetectionFlyout hideJobType={isInventory} hideSelectGroup={isInventory} /> + )} + /> + ); +}; + +const HeaderLinkAddDataRoute = ({ + path, + onboardingFlow, + exact, +}: { + path: string; + onboardingFlow: OnboardingFlow; + exact?: boolean; +}) => { + const { share } = useKibana<{ share: SharePublicStart }>().services; + const onboardingLocator = share?.url.locators.get<ObservabilityOnboardingLocatorParams>( + OBSERVABILITY_ONBOARDING_LOCATOR + ); + + return ( + <Route + path={path} + exact={exact} + render={() => ( + <EuiHeaderLink + href={onboardingLocator?.getRedirectUrl({ category: onboardingFlow })} + color="primary" + iconType="indexOpen" + > + {ADD_DATA_LABEL} + </EuiHeaderLink> + )} + /> + ); +}; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx index 78c5ea3371872..545bc57c65315 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/timeline/timeline.tsx @@ -113,8 +113,8 @@ export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible } }, [nodeType, metricsHostsAnomalies, metricsK8sAnomalies]); - const metricLabel = toMetricOpt(metric.type)?.textLC; - const metricPopoverLabel = toMetricOpt(metric.type)?.text; + const metricLabel = toMetricOpt(metric.type, nodeType)?.textLC; + const metricPopoverLabel = toMetricOpt(metric.type, nodeType)?.text; const chartMetric = { color: Color.color0, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/toolbars/metrics_and_groupby_toolbar_items.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/toolbars/metrics_and_groupby_toolbar_items.tsx index cdfefd037e0c7..81a82bd93f766 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/toolbars/metrics_and_groupby_toolbar_items.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/toolbars/metrics_and_groupby_toolbar_items.tsx @@ -23,8 +23,10 @@ interface Props extends ToolbarProps { export const MetricsAndGroupByToolbarItems = (props: Props) => { const metricOptions = useMemo( () => - props.metricTypes.map(toMetricOpt).filter((v) => v) as Array<{ text: string; value: string }>, - [props.metricTypes] + props.metricTypes + .map((metric) => toMetricOpt(metric, props.nodeType)) + .filter((v) => v) as Array<{ text: string; value: string }>, + [props.metricTypes, props.nodeType] ); const groupByOptions = useMemo( diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap index f3368be815f1b..70b15408b38df 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/__snapshots__/conditional_tooltip.test.tsx.snap @@ -26,6 +26,22 @@ exports[`ConditionalToolTip renders correctly 1`] = ` 10% </div> </div> + <div + class="euiFlexGroup emotion-euiFlexGroup-responsive-s-flexStart-stretch-row" + > + <div + class="euiFlexItem eui-textTruncate eui-displayBlock emotion-euiFlexItem-grow-1" + data-test-subj="conditionalTooltipContent-metric" + > + CPU usage (legacy) + </div> + <div + class="euiFlexItem emotion-euiFlexItem-growZero" + data-test-subj="conditionalTooltipContent-value" + > + 10% + </div> + </div> <div class="euiFlexGroup emotion-euiFlexGroup-responsive-s-flexStart-stretch-row" > diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx index 8ad60dad6a33c..b15779cbd2e9e 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx @@ -30,7 +30,7 @@ const NODE: InfraWaffleMapNode = { id: 'host-01', name: 'host-01', path: [{ value: 'host-01', label: 'host-01' }], - metrics: [{ name: 'cpu' }], + metrics: [{ name: 'cpuTotal' }], }; export const nextTick = () => new Promise((res) => process.nextTick(res)); @@ -45,10 +45,11 @@ describe('ConditionalToolTip', () => { name: 'host-01', path: [{ label: 'host-01', value: 'host-01', ip: '192.168.1.10' }], metrics: [ + { name: 'cpuTotal', value: 0.1, avg: 0.4, max: 0.7 }, { name: 'cpu', value: 0.1, avg: 0.4, max: 0.7 }, { name: 'memory', value: 0.8, avg: 0.8, max: 1 }, - { name: 'tx', value: 1000000, avg: 1000000, max: 1000000 }, - { name: 'rx', value: 1000000, avg: 1000000, max: 1000000 }, + { name: 'txV2', value: 1000000, avg: 1000000, max: 1000000 }, + { name: 'rxV2', value: 1000000, avg: 1000000, max: 1000000 }, { name: 'cedd6ca0-5775-11eb-a86f-adb714b6c486', max: 0.34164999922116596, @@ -78,13 +79,14 @@ describe('ConditionalToolTip', () => { }, }); const expectedMetrics = [ + { type: 'cpuTotal' }, { type: 'cpu' }, { type: 'memory' }, - { type: 'tx' }, - { type: 'rx' }, + { type: 'txV2' }, + { type: 'rxV2' }, { aggregation: 'avg', - field: 'host.cpu.pct', + field: 'host.cpuTotal.pct', id: 'cedd6ca0-5775-11eb-a86f-adb714b6c486', label: 'My Custom Label', type: 'custom', @@ -139,11 +141,11 @@ const mockedUseWaffleOptionsContexReturnValue: ReturnType<typeof useWaffleOption nodeType: 'host', customOptions: [], view: 'map', - metric: { type: 'cpu' }, + metric: { type: 'cpuTotal' }, customMetrics: [ { aggregation: 'avg', - field: 'host.cpu.pct', + field: 'host.cpuTotal.pct', id: 'cedd6ca0-5775-11eb-a86f-adb714b6c486', label: 'My Custom Label', type: 'custom', diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx index 0140dd54e77b2..0558dd6ad67ee 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx @@ -20,7 +20,7 @@ import { useSourceContext } from '../../../../../containers/metrics_source'; import { InfraWaffleMapNode } from '../../../../../common/inventory/types'; import { useSnapshot } from '../../hooks/use_snaphot'; import { createInventoryMetricFormatter } from '../../lib/create_inventory_metric_formatter'; -import { SNAPSHOT_METRIC_TRANSLATIONS } from '../../../../../../common/inventory_models/intl_strings'; +import { getSnapshotMetricTranslations } from '../../../../../../common/inventory_models/intl_strings'; import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; import { createFormatterForMetric } from '../../../metrics_explorer/components/helpers/create_formatter_for_metric'; @@ -86,7 +86,7 @@ export const ConditionalToolTip = ({ node, nodeType, currentTime }: Props) => { ) : ( metrics.map((metric) => { const metricName = SnapshotMetricTypeRT.is(metric.name) ? metric.name : 'custom'; - const name = SNAPSHOT_METRIC_TRANSLATIONS[metricName] || metricName; + const name = getSnapshotMetricTranslations(nodeType)[metricName] || metricName; // if custom metric, find field and label from waffleOptionsContext result // because useSnapshot does not return it const customMetric = diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts index 24b9dbc8a1ef3..561ff4e4e749d 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts @@ -33,7 +33,7 @@ export const DEFAULT_LEGEND: WaffleLegendOptions = { }; export const DEFAULT_WAFFLE_OPTIONS_STATE: WaffleOptionsState = { - metric: { type: 'cpu' }, + metric: { type: 'cpuTotal' }, groupBy: [], nodeType: 'host', view: 'map', diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/index.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/index.tsx index 8445be3d84e7b..c013cebb42e70 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/index.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/index.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { useTrackPageview } from '@kbn/observability-shared-plugin/public'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { css } from '@emotion/react'; +import { InfraPageTemplate } from '../../../components/shared/templates/infra_page_template'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; -import { MetricsPageTemplate } from '../page_template'; import { inventoryTitle } from '../../../translations'; import { SavedViews } from './components/saved_views'; import { SnapshotContainer } from './components/snapshot_container'; @@ -37,7 +37,7 @@ export const SnapshotPage = () => { <WaffleTimeProvider> <WaffleFiltersProvider> <div className={APP_WRAPPER_CLASS}> - <MetricsPageTemplate + <InfraPageTemplate pageHeader={{ pageTitle: inventoryTitle, rightSideItems: [<SavedViews />, <SurveySection />], @@ -52,7 +52,7 @@ export const SnapshotPage = () => { }} > <SnapshotContainer /> - </MetricsPageTemplate> + </InfraPageTemplate> </div> </WaffleFiltersProvider> </WaffleTimeProvider> diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts index 934d17a570080..6786cb427632f 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts @@ -31,6 +31,10 @@ const METRIC_FORMATTERS: MetricFormatters = { formatter: InfraFormatterType.percent, template: '{{value}}', }, + cpuTotal: { + formatter: InfraFormatterType.percent, + template: '{{value}}', + }, diskIOReadBytes: { formatter: InfraFormatterType.bytes, template: '{{value}}/s', @@ -65,6 +69,8 @@ const METRIC_FORMATTERS: MetricFormatters = { }, rx: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, tx: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + rxV2: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + txV2: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, logRate: { formatter: InfraFormatterType.abbreviatedNumber, template: '{{value}}/s', diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx index 0bf1448ca51b2..3934abb5478f9 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/node_details_page.tsx @@ -11,6 +11,7 @@ import moment from 'moment'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { InventoryMetric, InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; import { decodeOrThrow } from '@kbn/io-ts-utils'; +import { InfraPageTemplate } from '../../../../components/shared/templates/infra_page_template'; import { NodeDetailsMetricDataResponseRT } from '../../../../../common/http_api/node_details_api'; import { isPending, useFetcher } from '../../../../hooks/use_fetcher'; import { useTemplateHeaderBreadcrumbs } from '../../../../components/asset_details/hooks/use_page_header'; @@ -22,7 +23,6 @@ import { MetricsTimeInput } from '../hooks/use_metrics_time'; import { InfraMetadata } from '../../../../../common/http_api/metadata_api'; import { PageError } from './page_error'; import { MetadataContext } from '../containers/metadata_context'; -import { MetricsPageTemplate } from '../../page_template'; interface Props { name: string; @@ -90,7 +90,7 @@ export const NodeDetailsPage = (props: Props) => { } return ( - <MetricsPageTemplate + <InfraPageTemplate pageHeader={{ pageTitle: props.name, rightSideItems: [ @@ -132,6 +132,6 @@ export const NodeDetailsPage = (props: Props) => { </SideNavContext.Provider> </EuiFlexItem> </EuiFlexGroup> - </MetricsPageTemplate> + </InfraPageTemplate> ); }; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/time_controls.test.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/time_controls.test.tsx index 959ded8e0cdbf..70dc780efe590 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/time_controls.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/components/time_controls.test.tsx @@ -44,10 +44,13 @@ describe('MetricsTimeControls', () => { /> ); component - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); - component.find('[data-test-subj="superDatePickerCommonlyUsed_Today"]').last().simulate('click'); + component + .find('button[data-test-subj="superDatePickerCommonlyUsed_Today"]') + .last() + .simulate('click'); expect(handleTimeChange.mock.calls.length).toBe(1); const timeRangeInput = handleTimeChange.mock.calls[0][0]; expect(timeRangeInput.from).toBe('now/d'); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx index 395b608694013..2f9455d054406 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/metric_detail_page.tsx @@ -10,6 +10,7 @@ import React, { useState } from 'react'; import { useRouteMatch } from 'react-router-dom'; import { findInventoryModel } from '@kbn/metrics-data-access-plugin/common'; import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; +import { InfraPageTemplate } from '../../../components/shared/templates/infra_page_template'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { useParentBreadcrumbResolver } from '../../../hooks/use_parent_breadcrumb_resolver'; import { useMetadata } from '../../../components/asset_details/hooks/use_metadata'; @@ -18,7 +19,6 @@ import { InfraLoadingPanel } from '../../../components/loading'; import type { NavItem } from './lib/side_nav_context'; import { NodeDetailsPage } from './components/node_details_page'; import { useMetricsTimeContext } from './hooks/use_metrics_time'; -import { MetricsPageTemplate } from '../page_template'; export const MetricDetailPage = () => { const { @@ -79,7 +79,7 @@ export const MetricDetailPage = () => { if (metadataLoading && !filteredRequiredMetrics.length) { return ( - <MetricsPageTemplate> + <InfraPageTemplate> <InfraLoadingPanel height="100vh" width="100%" @@ -87,7 +87,7 @@ export const MetricDetailPage = () => { defaultMessage: 'Loading data', })} /> - </MetricsPageTemplate> + </InfraPageTemplate> ); } diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/index.tsx index 887b6be224a45..cc4852a7edf84 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import { useTrackPageview, FeatureFeedbackButton } from '@kbn/observability-shared-plugin/public'; +import { InfraPageTemplate } from '../../../components/shared/templates/infra_page_template'; import { WithMetricsExplorerOptionsUrlState } from '../../../containers/metrics_explorer/with_metrics_explorer_options_url_state'; import { useKibanaEnvironmentContext } from '../../../hooks/use_kibana'; import { useMetricsExplorerViews } from '../../../hooks/use_metrics_explorer_views'; @@ -16,7 +17,6 @@ import { NoData } from '../../../components/empty_states'; import { MetricsExplorerCharts } from './components/charts'; import { MetricsExplorerToolbar } from './components/toolbar'; import { useMetricsExplorerState } from './hooks/use_metric_explorer_state'; -import { MetricsPageTemplate } from '../page_template'; import { metricsExplorerTitle } from '../../../translations'; import { SavedViews } from './components/saved_views'; import { MetricsExplorerOptionsContainer } from './hooks/use_metrics_explorer_options'; @@ -93,7 +93,7 @@ const MetricsExplorerContent = () => { }; return ( - <MetricsPageTemplate + <InfraPageTemplate pageHeader={{ pageTitle: metricsExplorerTitle, rightSideItems: [ @@ -143,6 +143,6 @@ const MetricsExplorerContent = () => { onTimeChange={handleTimeChange} /> )} - </MetricsPageTemplate> + </InfraPageTemplate> ); }; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/page_template.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/page_template.tsx deleted file mode 100644 index d48b77404767d..0000000000000 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/page_template.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { OBSERVABILITY_ONBOARDING_LOCATOR } from '@kbn/deeplinks-observability'; -import { i18n } from '@kbn/i18n'; -import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-shared-plugin/public'; -import type { NoDataConfig } from '@kbn/shared-ux-page-kibana-template'; -import React, { useEffect } from 'react'; -import { - noMetricIndicesPromptDescription, - noMetricIndicesPromptPrimaryActionTitle, - NoRemoteCluster, -} from '../../components/empty_states'; -import { SourceErrorPage } from '../../components/source_error_page'; -import { SourceLoadingPage } from '../../components/source_loading_page'; -import { useMetricsDataViewContext, useSourceContext } from '../../containers/metrics_source'; -import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; -import { ErrorCallout } from './hosts/components/error_callout'; - -export const MetricsPageTemplate: React.FC<LazyObservabilityPageTemplateProps> = ({ - 'data-test-subj': _dataTestSubj, - ...pageTemplateProps -}) => { - const { - services: { - observabilityAIAssistant, - observabilityShared: { - navigation: { PageTemplate }, - }, - share, - docLinks, - }, - } = useKibanaContextForPlugin(); - - const onboardingLocator = share.url.locators.get(OBSERVABILITY_ONBOARDING_LOCATOR); - const href = onboardingLocator?.getRedirectUrl({ category: 'infra' }); - const { source, error: sourceError, loadSource, isLoading } = useSourceContext(); - const { error: dataViewLoadError, refetch: loadDataView } = useMetricsDataViewContext(); - const { remoteClustersExist, metricIndicesExist } = source?.status ?? {}; - - const noDataConfig: NoDataConfig | undefined = metricIndicesExist - ? undefined - : { - solution: i18n.translate('xpack.infra.metrics.noDataConfig.solutionName', { - defaultMessage: 'Observability', - }), - action: { - beats: { - title: noMetricIndicesPromptPrimaryActionTitle, - description: noMetricIndicesPromptDescription, - href, - }, - }, - docsLink: docLinks.links.observability.guide, - }; - - const { setScreenContext } = observabilityAIAssistant?.service || {}; - - useEffect(() => { - return setScreenContext?.({ - data: [ - { - name: 'Metrics configuration', - value: source, - description: 'The configuration of the Metrics app', - }, - ], - starterPrompts: [ - ...(!metricIndicesExist - ? [ - { - title: i18n.translate( - 'xpack.infra.metrics.aiAssistant.starterPrompts.explainNoData.title', - { - defaultMessage: 'Explain', - } - ), - prompt: i18n.translate( - 'xpack.infra.metrics.aiAssistant.starterPrompts.explainNoData.prompt', - { - defaultMessage: "Why don't I see any data?", - } - ), - icon: 'sparkles', - }, - ] - : []), - ], - }); - }, [metricIndicesExist, setScreenContext, source]); - - if (isLoading && !source) return <SourceLoadingPage />; - - if (!remoteClustersExist) { - return <NoRemoteCluster />; - } - - if (sourceError) { - <SourceErrorPage errorMessage={sourceError} retry={loadSource} />; - } - - if (dataViewLoadError) { - <ErrorCallout - error={dataViewLoadError} - titleOverride={i18n.translate('xpack.infra.hostsViewPage.errorOnCreateOrLoadDataviewTitle', { - defaultMessage: 'Error creating Data View', - })} - messageOverride={i18n.translate('xpack.infra.hostsViewPage.errorOnCreateOrLoadDataview', { - defaultMessage: - 'There was an error trying to create a Data View: {metricAlias}. Try reloading the page.', - values: { metricAlias: source?.configuration.metricAlias ?? '' }, - })} - onTryAgainClick={loadDataView} - hasTryAgainButton - />; - } - - return ( - <PageTemplate - data-test-subj={metricIndicesExist ? _dataTestSubj : 'noDataPage'} - noDataConfig={noDataConfig} - {...pageTemplateProps} - /> - ); -}; diff --git a/x-pack/plugins/observability_solution/infra/public/types.ts b/x-pack/plugins/observability_solution/infra/public/types.ts index 982f191941ab4..d7a7d339f41be 100644 --- a/x-pack/plugins/observability_solution/infra/public/types.ts +++ b/x-pack/plugins/observability_solution/infra/public/types.ts @@ -49,6 +49,7 @@ import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { DashboardStart } from '@kbn/dashboard-plugin/public'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import type { UnwrapPromise } from '../common/utility_types'; import { InventoryViewsServiceStart } from './services/inventory_views'; import { MetricsExplorerViewsServiceStart } from './services/metrics_explorer_views'; @@ -95,6 +96,7 @@ export interface InfraClientStartDeps { embeddable?: EmbeddableStart; lens: LensPublicStart; logsShared: LogsSharedClientStartExports; + logsDataAccess: LogsDataAccessPluginStart; ml?: MlPluginStart; observability: ObservabilityPublicStart; observabilityShared: ObservabilitySharedPluginStart; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/adapter_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/adapter_types.ts deleted file mode 100644 index 60b95acf29e02..0000000000000 --- a/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/adapter_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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { InfraPluginRequestHandlerContext } from '../../../types'; - -export interface FieldsAdapter { - getIndexFields( - requestContext: InfraPluginRequestHandlerContext, - indices: string - ): Promise<IndexFieldDescriptor[]>; -} - -export interface IndexFieldDescriptor { - name: string; - type: string; - searchable: boolean; - aggregatable: boolean; - displayable: boolean; -} diff --git a/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/framework_fields_adapter.ts b/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/framework_fields_adapter.ts deleted file mode 100644 index 45bb8dc0957d9..0000000000000 --- a/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/framework_fields_adapter.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 { FieldSpec } from '@kbn/data-views-plugin/common'; -import type { InfraPluginRequestHandlerContext } from '../../../types'; -import { isNoSuchRemoteClusterMessage, NoSuchRemoteClusterError } from '../../sources/errors'; -import { KibanaFramework } from '../framework/kibana_framework_adapter'; -import { FieldsAdapter, IndexFieldDescriptor } from './adapter_types'; - -export class FrameworkFieldsAdapter implements FieldsAdapter { - private framework: KibanaFramework; - - constructor(framework: KibanaFramework) { - this.framework = framework; - } - - public async getIndexFields( - requestContext: InfraPluginRequestHandlerContext, - indices: string - ): Promise<IndexFieldDescriptor[]> { - const indexPatternsService = await this.framework.getIndexPatternsServiceWithRequestContext( - requestContext - ); - - try { - // NOTE: Unfortunately getFieldsForWildcard is typed to "any" here in the data plugin, FieldSpec is used below in the map. - const response = await indexPatternsService.getFieldsForWildcard({ - pattern: indices, - allowNoIndex: true, - }); - - return response.map((field: FieldSpec) => ({ - ...field, - displayable: true, - })); - } catch (error) { - if (isNoSuchRemoteClusterMessage(error.message)) { - throw new NoSuchRemoteClusterError(); - } - throw error; - } - } -} diff --git a/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/index.ts b/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/index.ts deleted file mode 100644 index 5d7c09c54b8c1..0000000000000 --- a/x-pack/plugins/observability_solution/infra/server/lib/adapters/fields/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 './adapter_types'; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts index 8fe4101d7ebca..7b068424a7cc8 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/adapters/framework/adapter_types.ts @@ -38,6 +38,7 @@ import { ApmDataAccessPluginSetup, ApmDataAccessPluginStart, } from '@kbn/apm-data-access-plugin/server'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; export interface InfraServerPluginSetupDeps { alerting: AlertingPluginContract; @@ -64,6 +65,7 @@ export interface InfraServerPluginStartDeps { profilingDataAccess?: ProfilingDataAccessPluginStart; ruleRegistry: RuleRegistryPluginStartContract; apmDataAccess: ApmDataAccessPluginStart; + logsDataAccess: LogsDataAccessPluginStart; } export interface CallWithRequestParams extends estypes.RequestBase { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index d109669aa90f0..f76a6e82e67d5 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -22,6 +22,7 @@ import { ConditionResult } from './evaluate_condition'; import { InfraBackendLibs } from '../../infra_types'; import { infraPluginMock } from '../../../mocks'; import { logsSharedPluginMock } from '@kbn/logs-shared-plugin/server/mocks'; +import { createLogSourcesServiceMock } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/log_sources_service.mocks'; jest.mock('./evaluate_condition', () => ({ evaluateCondition: jest.fn() })); @@ -115,7 +116,16 @@ const mockLibs = { }, getStartServices: () => [ null, - { logsShared: logsSharedPluginMock.createStartContract() }, + { + logsShared: logsSharedPluginMock.createStartContract(), + logsDataAccess: { + services: { + logSourcesServiceFactory: { + getLogSourcesService: () => createLogSourcesServiceMock(), + }, + }, + }, + }, infraPluginMock.createStartContract(), ], configuration: createMockStaticConfiguration({}), diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 94879de3f8070..80da1034df5ac 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -22,7 +22,7 @@ import { } from '@kbn/alerting-plugin/common'; import { AlertsClientError, RuleExecutorOptions, RuleTypeState } from '@kbn/alerting-plugin/server'; import { convertToBuiltInComparators, getAlertUrl } from '@kbn/observability-plugin/common'; -import { SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; +import type { InventoryItemType, SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; import { ObservabilityMetricsAlert } from '@kbn/alerts-as-data-utils'; import { getOriginalActionGroup } from '../../../utils/get_original_action_group'; import { @@ -165,9 +165,13 @@ export const createInventoryMetricThresholdExecutor = } const source = await libs.sources.getSourceConfiguration(savedObjectsClient, sourceId); - const [, { logsShared }] = await libs.getStartServices(); + const [, { logsShared, logsDataAccess }] = await libs.getStartServices(); + + const logSourcesService = + logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService(savedObjectsClient); + const logQueryFields: LogQueryFields | undefined = await logsShared.logViews - .getClient(savedObjectsClient, esClient) + .getClient(savedObjectsClient, esClient, logSourcesService) .getResolvedLogView({ type: 'log-view-reference', logViewId: sourceId, @@ -226,12 +230,13 @@ export const createInventoryMetricThresholdExecutor = if (nextState === AlertStates.ALERT || nextState === AlertStates.WARNING) { reason = results .map((result) => - buildReasonWithVerboseMetricName( + buildReasonWithVerboseMetricName({ group, - result[group], - buildFiredAlertReason, - nextState === AlertStates.WARNING - ) + resultItem: result[group], + buildReason: buildFiredAlertReason, + useWarningThreshold: nextState === AlertStates.WARNING, + nodeType, + }) ) .join('\n'); } @@ -240,14 +245,24 @@ export const createInventoryMetricThresholdExecutor = reason = results .filter((result) => result[group].isNoData) .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildNoDataAlertReason) + buildReasonWithVerboseMetricName({ + group, + resultItem: result[group], + buildReason: buildNoDataAlertReason, + nodeType, + }) ) .join('\n'); } else if (nextState === AlertStates.ERROR) { reason = results .filter((result) => result[group].isError) .map((result) => - buildReasonWithVerboseMetricName(group, result[group], buildErrorAlertReason) + buildReasonWithVerboseMetricName({ + group, + resultItem: result[group], + buildReason: buildErrorAlertReason, + nodeType, + }) ) .join('\n'); } @@ -384,12 +399,19 @@ const formatThreshold = (metric: SnapshotMetricType, value: number | number[]) = return threshold; }; -const buildReasonWithVerboseMetricName = ( - group: string, - resultItem: ConditionResult, - buildReason: (r: any) => string, - useWarningThreshold?: boolean -) => { +const buildReasonWithVerboseMetricName = ({ + group, + resultItem, + buildReason, + useWarningThreshold, + nodeType, +}: { + group: string; + resultItem: ConditionResult; + buildReason: (r: any) => string; + useWarningThreshold?: boolean; + nodeType?: InventoryItemType; +}) => { if (!resultItem) return ''; const thresholdToFormat = useWarningThreshold @@ -399,7 +421,7 @@ const buildReasonWithVerboseMetricName = ( ...resultItem, group, metric: - toMetricOpt(resultItem.metric)?.text || + toMetricOpt(resultItem.metric, nodeType)?.text || (resultItem.metric === 'custom' && resultItem.customMetric ? getCustomMetricLabel(resultItem.customMetric) : resultItem.metric), diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/lib/convert_metric_value.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/lib/convert_metric_value.ts index d2301072d4854..9866665e423af 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/lib/convert_metric_value.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/lib/convert_metric_value.ts @@ -17,6 +17,7 @@ export const convertMetricValue = (metric: SnapshotMetricType, value: number) => }; const converters: Record<string, (n: number) => number> = { cpu: (n) => Number(n) / 100, + cpuTotal: (n) => Number(n) / 100, memory: (n) => Number(n) / 100, tx: (n) => Number(n) / 8, rx: (n) => Number(n) / 8, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index c757f374f8855..0ac06618a3ba2 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -203,13 +203,16 @@ export const createLogThresholdExecutor = } }; - const [, { logsShared }] = await libs.getStartServices(); + const [, { logsShared, logsDataAccess }] = await libs.getStartServices(); try { const validatedParams = decodeOrThrow(ruleParamsRT)(params); + const logSourcesService = + logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService(savedObjectsClient); + const { indices, timestampField, runtimeMappings } = await logsShared.logViews - .getClient(savedObjectsClient, scopedClusterClient.asCurrentUser) + .getClient(savedObjectsClient, scopedClusterClient.asCurrentUser, logSourcesService) .getResolvedLogView(validatedParams.logView); if (!isRatioRuleParams(validatedParams)) { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/domains/fields_domain.ts b/x-pack/plugins/observability_solution/infra/server/lib/domains/fields_domain.ts deleted file mode 100644 index b80fa9d796021..0000000000000 --- a/x-pack/plugins/observability_solution/infra/server/lib/domains/fields_domain.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { InfraPluginRequestHandlerContext } from '../../types'; -import { FieldsAdapter } from '../adapters/fields'; -import { InfraSourceIndexField, InfraSources } from '../sources'; - -export class InfraFieldsDomain { - constructor( - private readonly adapter: FieldsAdapter, - private readonly libs: { sources: InfraSources } - ) {} - - public async getFields( - requestContext: InfraPluginRequestHandlerContext, - sourceId: string, - indexType: 'METRICS' - ): Promise<InfraSourceIndexField[]> { - const soClient = (await requestContext.core).savedObjects.client; - const { configuration } = await this.libs.sources.getSourceConfiguration(soClient, sourceId); - - const fields = await this.adapter.getIndexFields(requestContext, configuration.metricAlias); - - return fields; - } -} diff --git a/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts index 8cf0fc81a7321..08cf030a16219 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts @@ -18,13 +18,11 @@ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { RulesServiceSetup } from '../services/rules'; import { InfraConfig, InfraPluginStartServicesAccessor } from '../types'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; -import { InfraFieldsDomain } from './domains/fields_domain'; import { InfraMetricsDomain } from './domains/metrics_domain'; import { InfraSources } from './sources'; import { InfraSourceStatus } from './source_status'; export interface InfraDomainLibs { - fields: InfraFieldsDomain; logEntries: ILogsSharedLogEntriesDomain; metrics: InfraMetricsDomain; } diff --git a/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts b/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts index 22cc5108c35c9..9d32269548632 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/sources/types.ts @@ -61,9 +61,14 @@ export const logIndexNameSavedObjectReferenceRT = rt.type({ indexName: rt.string, }); +export const kibanaAdvancedSettingSavedObjectReferenceRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + export const logIndexSavedObjectReferenceRT = rt.union([ logIndexPatternSavedObjectReferenceRT, logIndexNameSavedObjectReferenceRT, + kibanaAdvancedSettingSavedObjectReferenceRT, ]); export const SourceConfigurationSavedObjectAttributesRT = rt.type({ diff --git a/x-pack/plugins/observability_solution/infra/server/plugin.ts b/x-pack/plugins/observability_solution/infra/server/plugin.ts index 0182d34b76866..61f53b4b9260d 100644 --- a/x-pack/plugins/observability_solution/infra/server/plugin.ts +++ b/x-pack/plugins/observability_solution/infra/server/plugin.ts @@ -23,7 +23,6 @@ import { LOGS_FEATURE_ID, METRICS_FEATURE_ID } from '../common/constants'; import { publicConfigKeys } from '../common/plugin_config_types'; import { LOGS_FEATURE, METRICS_FEATURE } from './features'; import { initInfraServer } from './infra_server'; -import { FrameworkFieldsAdapter } from './lib/adapters/fields/framework_fields_adapter'; import { InfraServerPluginSetupDeps, InfraServerPluginStartDeps } from './lib/adapters/framework'; import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter'; import { KibanaMetricsAdapter } from './lib/adapters/metrics/kibana_metrics_adapter'; @@ -33,7 +32,6 @@ import { LOGS_RULES_ALERT_CONTEXT, METRICS_RULES_ALERT_CONTEXT, } from './lib/alerting/register_rule_types'; -import { InfraFieldsDomain } from './lib/domains/fields_domain'; import { InfraMetricsDomain } from './lib/domains/metrics_domain'; import { InfraBackendLibs, InfraDomainLibs } from './lib/infra_types'; import { infraSourceConfigurationSavedObjectType, InfraSources } from './lib/sources'; @@ -210,9 +208,6 @@ export class InfraServerPlugin // and make them available via the request context so we can do away with // the wrapper classes const domainLibs: InfraDomainLibs = { - fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), { - sources, - }), logEntries: plugins.logsShared.logEntries, metrics: new InfraMetricsDomain(new KibanaMetricsAdapter(framework)), }; diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts index 7172a0c3da4d4..15bc0df8d76e5 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_all_hosts.ts @@ -23,8 +23,8 @@ export const getAllHosts = async ( const result = (response.aggregations?.nodes.buckets ?? []) .sort((a, b) => { - const aValue = getMetricValue(a?.cpu) ?? 0; - const bValue = getMetricValue(b?.cpu) ?? 0; + const aValue = getMetricValue(a?.cpuTotal) ?? 0; + const bValue = getMetricValue(b?.cpuTotal) ?? 0; return bValue - aValue; }) .map((bucket) => { diff --git a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts index 7172a44d70417..18bb645bff44d 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/infra/lib/host/get_hosts_count.ts @@ -8,7 +8,12 @@ import { rangeQuery, termQuery } from '@kbn/observability-plugin/server'; import { BoolQuery } from '@kbn/es-query'; import { InfraMetricsClient } from '../../../../lib/helpers/get_infra_metrics_client'; -import { HOST_NAME_FIELD, EVENT_MODULE, METRICSET_MODULE } from '../../../../../common/constants'; +import { + HOST_NAME_FIELD, + EVENT_MODULE, + METRICSET_MODULE, + SYSTEM_INTEGRATION, +} from '../../../../../common/constants'; export async function getHostsCount({ infraMetricsClient, @@ -39,8 +44,8 @@ export async function getHostsCount({ { bool: { should: [ - ...termQuery(EVENT_MODULE, 'system'), - ...termQuery(METRICSET_MODULE, 'system'), + ...termQuery(EVENT_MODULE, SYSTEM_INTEGRATION), + ...termQuery(METRICSET_MODULE, SYSTEM_INTEGRATION), ], minimum_should_match: 1, }, diff --git a/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts b/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts index 7bafa78750690..0f8c00c51b0db 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/ip_to_hostname.ts @@ -52,10 +52,10 @@ export const initIpToHostName = ({ framework }: InfraBackendLibs) => { } const hostDoc = first(hits.hits)!; return response.ok({ body: { host: hostDoc._source.host.name } }); - } catch ({ statusCode = 500, message = 'Unknown error occurred' }) { + } catch (error) { return response.customError({ - statusCode, - body: { message }, + statusCode: error.statusCode || 500, + body: { message: error.message || 'Unknown error occurred' }, }); } } diff --git a/x-pack/plugins/observability_solution/infra/server/routes/metrics_sources/index.ts b/x-pack/plugins/observability_solution/infra/server/routes/metrics_sources/index.ts index 8183611a7a058..3540dac3d311c 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/metrics_sources/index.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/metrics_sources/index.ts @@ -24,12 +24,11 @@ import { MetricsSourceStatus, partialMetricsSourceConfigurationReqPayloadRT, } from '../../../common/metrics_sources'; -import { InfraSource, InfraSourceIndexField } from '../../lib/sources'; +import { InfraSource } from '../../lib/sources'; import { InfraPluginRequestHandlerContext } from '../../types'; import { getInfraMetricsClient } from '../../lib/helpers/get_infra_metrics_client'; const defaultStatus = { - indexFields: [], metricIndicesExist: false, remoteClustersExist: false, }; @@ -43,40 +42,24 @@ export const initMetricsSourceConfigurationRoutes = (libs: InfraBackendLibs) => requestContext: InfraPluginRequestHandlerContext, sourceId: string ): Promise<MetricsSourceStatus> => { - const [metricIndicesExistSettled, indexFieldsSettled] = await Promise.allSettled([ - libs.sourceStatus.hasMetricIndices(requestContext, sourceId), - libs.fields.getFields(requestContext, sourceId, 'METRICS'), - ]); - - /** - * Extract values from promises settlements - */ - const indexFields = isFulfilled<InfraSourceIndexField[]>(indexFieldsSettled) - ? indexFieldsSettled.value - : defaultStatus.indexFields; - const metricIndicesExist = isFulfilled<boolean>(metricIndicesExistSettled) - ? metricIndicesExistSettled.value - : defaultStatus.metricIndicesExist; - const remoteClustersExist = hasRemoteCluster<boolean | InfraSourceIndexField[]>( - indexFieldsSettled, - metricIndicesExistSettled - ); - - /** - * Report gracefully handled rejections - */ - if (!isFulfilled<InfraSourceIndexField[]>(indexFieldsSettled)) { - logger.error(indexFieldsSettled.reason); - } - if (!isFulfilled<boolean>(metricIndicesExistSettled)) { - logger.error(metricIndicesExistSettled.reason); - } + try { + const hasMetricIndices = await libs.sourceStatus.hasMetricIndices(requestContext, sourceId); + return { + metricIndicesExist: hasMetricIndices, + remoteClustersExist: true, + }; + } catch (err) { + logger.error(err); + + if (err instanceof NoSuchRemoteClusterError) { + return defaultStatus; + } - return { - indexFields, - metricIndicesExist, - remoteClustersExist, - }; + return { + metricIndicesExist: false, + remoteClustersExist: true, + }; + } }; framework.registerRoute( @@ -288,12 +271,3 @@ export const initMetricsSourceConfigurationRoutes = (libs: InfraBackendLibs) => const isFulfilled = <Type>( promiseSettlement: PromiseSettledResult<Type> ): promiseSettlement is PromiseFulfilledResult<Type> => promiseSettlement.status === 'fulfilled'; - -const hasRemoteCluster = <Type>(...promiseSettlements: Array<PromiseSettledResult<Type>>) => { - const isRemoteMissing = promiseSettlements.some( - (settlement) => - !isFulfilled<Type>(settlement) && settlement.reason instanceof NoSuchRemoteClusterError - ); - - return !isRemoteMissing; -}; diff --git a/x-pack/plugins/observability_solution/infra/tsconfig.json b/x-pack/plugins/observability_solution/infra/tsconfig.json index c796ae14486a0..e0e6750550fbb 100644 --- a/x-pack/plugins/observability_solution/infra/tsconfig.json +++ b/x-pack/plugins/observability_solution/infra/tsconfig.json @@ -106,10 +106,11 @@ "@kbn/presentation-containers", "@kbn/deeplinks-observability", "@kbn/event-annotation-common", + "@kbn/logs-data-access-plugin", "@kbn/core-analytics-browser", - "@kbn/observability-alerting-rule-utils" + "@kbn/observability-alerting-rule-utils", + "@kbn/core-application-browser", + "@kbn/shared-ux-page-no-data-types" ], - "exclude": [ - "target/**/*" - ] + "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/investigate/common/index.ts b/x-pack/plugins/observability_solution/investigate/common/index.ts index 467a892ca55d1..d97a5c9e3c4d2 100644 --- a/x-pack/plugins/observability_solution/investigate/common/index.ts +++ b/x-pack/plugins/observability_solution/investigate/common/index.ts @@ -4,13 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export type { - Investigation, - InvestigationRevision, - InvestigateWidget, - InvestigateWidgetCreate, - WorkflowBlock, -} from './types'; +export type { Investigation, InvestigateWidget, InvestigateWidgetCreate } from './types'; export { mergePlainObjects } from './utils/merge_plain_objects'; diff --git a/x-pack/plugins/observability_solution/investigate/common/types.ts b/x-pack/plugins/observability_solution/investigate/common/types.ts index 756e346ffe7af..e8ca999e8af7f 100644 --- a/x-pack/plugins/observability_solution/investigate/common/types.ts +++ b/x-pack/plugins/observability_solution/investigate/common/types.ts @@ -5,17 +5,14 @@ * 2.0. */ -import type { EuiThemeComputed } from '@elastic/eui'; -import type { Filter } from '@kbn/es-query'; -import type { DeepPartial, PickByValue } from 'utility-types'; import type { AuthenticatedUser } from '@kbn/core/public'; +import type { DeepPartial } from 'utility-types'; export interface GlobalWidgetParameters { timeRange: { from: string; to: string; }; - filters: Filter[]; } export enum InvestigateWidgetColumnSpan { @@ -25,19 +22,13 @@ export enum InvestigateWidgetColumnSpan { Four = 4, } -export interface InvestigationRevision { - id: string; - items: InvestigateWidget[]; - parameters: GlobalWidgetParameters; -} - export interface Investigation { id: string; '@timestamp': number; user: AuthenticatedUser; - revisions: InvestigationRevision[]; title: string; - revision: string; + items: InvestigateWidget[]; + parameters: GlobalWidgetParameters; } export interface InvestigateWidget< @@ -55,22 +46,11 @@ export interface InvestigateWidget< description?: string; columns: InvestigateWidgetColumnSpan; rows: number; - locked: boolean; } export type InvestigateWidgetCreate<TParameters extends Record<string, any> = {}> = Pick< InvestigateWidget, - 'title' | 'description' | 'columns' | 'rows' | 'type' | 'locked' + 'title' | 'description' | 'columns' | 'rows' | 'type' > & { parameters: DeepPartial<GlobalWidgetParameters> & TParameters; }; - -export interface WorkflowBlock { - id: string; - content?: string; - description?: string; - loading: boolean; - onClick?: () => void; - color?: keyof PickByValue<EuiThemeComputed<{}>['colors'], string>; - children?: React.ReactNode; -} diff --git a/x-pack/plugins/observability_solution/investigate/public/create_widget.ts b/x-pack/plugins/observability_solution/investigate/public/create_widget.ts index 7528565d06165..29058298c674d 100644 --- a/x-pack/plugins/observability_solution/investigate/public/create_widget.ts +++ b/x-pack/plugins/observability_solution/investigate/public/create_widget.ts @@ -12,7 +12,7 @@ import { GlobalWidgetParameters } from '../common/types'; type MakePartial<T extends Record<string, any>, K extends keyof T> = Omit<T, K> & DeepPartial<Pick<T, K>>; -type PredefinedKeys = 'rows' | 'columns' | 'locked' | 'type'; +type PredefinedKeys = 'rows' | 'columns' | 'type'; type AllowedDefaultKeys = 'rows' | 'columns'; @@ -31,7 +31,6 @@ export function createWidgetFactory<TParameters extends Record<string, any>>( return { rows: 12, columns: InvestigateWidgetColumnSpan.Four, - locked: false, type, ...defaults, ...widgetCreate, diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx index 3ace787c71439..a29614f74782b 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigate_widget.tsx @@ -5,18 +5,13 @@ * 2.0. */ import { useContext, createContext } from 'react'; -import type { InvestigateWidgetCreate, WorkflowBlock } from '../../common'; - -type UnregisterBlocksFunction = () => void; +import type { InvestigateWidgetCreate } from '../../common'; export interface UseInvestigateWidgetApi< TParameters extends Record<string, any> = {}, TData extends Record<string, any> = {} > { onWidgetAdd: (create: InvestigateWidgetCreate) => Promise<void>; - blocks: { - publish: (blocks: WorkflowBlock[]) => UnregisterBlocksFunction; - }; } const InvestigateWidgetApiContext = createContext<UseInvestigateWidgetApi | undefined>(undefined); diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts index 51c0a68c782c8..7eab4a192ca3c 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/create_new_investigation.ts @@ -8,7 +8,7 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { v4 } from 'uuid'; import { i18n } from '@kbn/i18n'; -import type { Investigation, InvestigationRevision } from '../../../common'; +import type { Investigation } from '../../../common'; import { GlobalWidgetParameters } from '../../../common/types'; export function createNewInvestigation({ @@ -20,14 +20,6 @@ export function createNewInvestigation({ user: AuthenticatedUser; globalWidgetParameters: GlobalWidgetParameters; }): Investigation { - const revisionId = v4(); - - const revision: InvestigationRevision = { - id: revisionId, - items: [], - parameters: globalWidgetParameters, - }; - return { '@timestamp': new Date().getTime(), user, @@ -35,7 +27,7 @@ export function createNewInvestigation({ title: i18n.translate('xpack.investigate.newInvestigationTitle', { defaultMessage: 'New investigation', }), - revision: revisionId, - revisions: [revision], + items: [], + parameters: globalWidgetParameters, }; } diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx index 4bf2e10cad4be..069255e20e233 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/index.tsx @@ -6,17 +6,12 @@ */ import type { AuthenticatedUser, NotificationsStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { last, omit, pull } from 'lodash'; +import { pull } from 'lodash'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { v4 } from 'uuid'; import type { GlobalWidgetParameters } from '../..'; -import type { - InvestigateWidget, - InvestigateWidgetCreate, - Investigation, - WorkflowBlock, -} from '../../../common'; +import type { InvestigateWidget, InvestigateWidgetCreate, Investigation } from '../../../common'; import type { WidgetDefinition } from '../../types'; import { InvestigateWidgetApiContextProvider, @@ -24,56 +19,28 @@ import { } from '../use_investigate_widget'; import { useLocalStorage } from '../use_local_storage'; import { createNewInvestigation } from './create_new_investigation'; -import { - createInvestigationStore, - StatefulInvestigation, - StatefulInvestigationRevision, -} from './investigation_store'; +import { StatefulInvestigation, createInvestigationStore } from './investigation_store'; export type RenderableInvestigateWidget = InvestigateWidget & { loading: boolean; element: React.ReactNode; }; -export type RenderableInvestigationRevision = Omit<StatefulInvestigationRevision, 'items'> & { +export type RenderableInvestigation = Omit<StatefulInvestigation, 'items'> & { items: RenderableInvestigateWidget[]; }; -export type RenderableInvestigateTimeline = Omit<StatefulInvestigation, 'revisions'> & { - revisions: RenderableInvestigationRevision[]; -}; - export interface UseInvestigationApi { startNewInvestigation: (id: string) => void; loadInvestigation: (id: string) => void; - investigation?: Omit<StatefulInvestigation, 'revisions'>; - revision?: RenderableInvestigationRevision; - isAtLatestRevision: boolean; - isAtEarliestRevision: boolean; - setItemPositions: ( - positions: Array<{ id: string; columns: number; rows: number }> - ) => Promise<void>; - setItemTitle: (id: string, title: string) => Promise<void>; - updateItem: ( - id: string, - cb: (item: InvestigateWidget) => Promise<InvestigateWidget> - ) => Promise<void>; + investigations: Investigation[]; + investigation?: StatefulInvestigation; + renderableInvestigation?: RenderableInvestigation; copyItem: (id: string) => Promise<void>; deleteItem: (id: string) => Promise<void>; addItem: (options: InvestigateWidgetCreate) => Promise<void>; - lockItem: (id: string) => Promise<void>; - unlockItem: (id: string) => Promise<void>; - setItemParameters: ( - id: string, - parameters: GlobalWidgetParameters & Record<string, any> - ) => Promise<void>; setGlobalParameters: (parameters: GlobalWidgetParameters) => Promise<void>; - blocks: WorkflowBlock[]; - setRevision: (revisionId: string) => void; - gotoPreviousRevision: () => Promise<void>; - gotoNextRevision: () => Promise<void>; setTitle: (title: string) => Promise<void>; - investigations: Investigation[]; deleteInvestigation: (id: string) => Promise<void>; } @@ -98,7 +65,6 @@ function useInvestigationWithoutContext({ user, id: v4(), globalWidgetParameters: { - filters: [], timeRange: { from, to, @@ -109,22 +75,10 @@ function useInvestigationWithoutContext({ ); const investigation$ = investigationStore.asObservable(); - const investigation = useObservable(investigation$)?.investigation; - const currentRevision = useMemo(() => { - return investigation?.revisions.find((revision) => revision.id === investigation.revision); - }, [investigation?.revision, investigation?.revisions]); - - const isAtEarliestRevision = investigation?.revisions[0].id === currentRevision?.id; - - const isAtLatestRevision = last(investigation?.revisions)?.id === currentRevision?.id; - - const [blocksById, setBlocksById] = useState<Record<string, WorkflowBlock[]>>({}); - const deleteItem = useCallback( async (id: string) => { - setBlocksById((prevBlocks) => omit(prevBlocks, id)); return investigationStore.deleteItem(id); }, [investigationStore] @@ -141,7 +95,7 @@ function useInvestigationWithoutContext({ const unusedComponentIds = Object.keys(widgetComponentsById); const nextItemsWithContext = - currentRevision?.items.map((item) => { + investigation?.items.map((item) => { let Component = widgetComponentsById.current[item.id]; if (!Component) { const id = item.id; @@ -149,21 +103,6 @@ function useInvestigationWithoutContext({ onWidgetAdd: async (create) => { return addItemRef.current(create); }, - blocks: { - publish: (nextBlocks) => { - const nextIds = nextBlocks.map((block) => block.id); - setBlocksById((prevBlocksById) => ({ - ...prevBlocksById, - [id]: (prevBlocksById[id] ?? []).concat(nextBlocks), - })); - return () => { - setBlocksById((prevBlocksById) => ({ - ...prevBlocksById, - [id]: (prevBlocksById[id] ?? []).filter((block) => !nextIds.includes(block.id)), - })); - }; - }, - }, }; const onDelete = () => { @@ -179,7 +118,6 @@ function useInvestigationWithoutContext({ <InvestigateWidgetApiContextProvider value={api}> {widgetDefinition ? widgetDefinition.render({ - blocks: api.blocks, onWidgetAdd: api.onWidgetAdd, onDelete, widget: props.widget, @@ -203,15 +141,13 @@ function useInvestigationWithoutContext({ }); return nextItemsWithContext; - }, [currentRevision?.items, widgetDefinitions, investigationStore]); + }, [investigation?.items, widgetDefinitions, investigationStore]); const addItem = useCallback( async (widget: InvestigateWidgetCreate) => { try { const id = v4(); - setBlocksById((prevBlocksById) => ({ ...prevBlocksById, [id]: [] })); - await investigationStore.addItem(id, widget); } catch (error) { notifications.showErrorDialog({ @@ -228,10 +164,10 @@ function useInvestigationWithoutContext({ const addItemRef = useRef(addItem); addItemRef.current = addItem; - const renderableRevision = useMemo(() => { - return currentRevision + const renderableInvestigation = useMemo(() => { + return investigation ? { - ...currentRevision, + ...investigation, items: itemsWithContext.map((item) => { const { Component, ...rest } = item; return { @@ -241,17 +177,15 @@ function useInvestigationWithoutContext({ }), } : undefined; - }, [currentRevision, itemsWithContext]); + }, [investigation, itemsWithContext]); const startNewInvestigation = useCallback( async (id: string) => { const prevInvestigation = await investigationStore.getInvestigation(); - const lastRevision = last(prevInvestigation.revisions)!; - const createdInvestigationStore = createInvestigationStore({ investigation: createNewInvestigation({ - globalWidgetParameters: lastRevision.parameters, + globalWidgetParameters: prevInvestigation.parameters, user, id, }) as StatefulInvestigation, @@ -260,38 +194,11 @@ function useInvestigationWithoutContext({ }); setInvestigationStore(createdInvestigationStore); - - setBlocksById({}); }, [user, widgetDefinitions, investigationStore] ); - const setItemParameters = useCallback( - async (id: string, nextGlobalWidgetParameters: GlobalWidgetParameters) => { - return investigationStore.setItemParameters(id, nextGlobalWidgetParameters); - }, - [investigationStore] - ); - - const lastItemId = last(currentRevision?.items)?.id; - - const blocks = useMemo(() => { - return lastItemId && blocksById[lastItemId] ? blocksById[lastItemId] : []; - }, [blocksById, lastItemId]); - - const { - copyItem, - gotoNextRevision, - gotoPreviousRevision, - lockItem, - setGlobalParameters, - setItemPositions, - setItemTitle, - setRevision, - setTitle, - unlockItem, - updateItem, - } = investigationStore; + const { copyItem, setGlobalParameters, setTitle } = investigationStore; const { storedItem: investigations, setStoredItem: setInvestigations } = useLocalStorage< Investigation[] @@ -337,7 +244,7 @@ function useInvestigationWithoutContext({ } const subscription = investigation$.subscribe(({ investigation: investigationFromStore }) => { - const isEmpty = investigationFromStore.revisions.length === 1; + const isEmpty = investigationFromStore.items.length === 0; if (isEmpty) { return; @@ -345,14 +252,9 @@ function useInvestigationWithoutContext({ const toSerialize = { ...investigationFromStore, - revisions: investigationFromStore.revisions.map((revision) => { - return { - ...revision, - items: revision.items.map((item) => { - const { loading, ...rest } = item; - return rest; - }), - }; + items: investigationFromStore.items.map((item) => { + const { loading, ...rest } = item; + return rest; }), }; @@ -403,26 +305,14 @@ function useInvestigationWithoutContext({ return { addItem, - blocks, copyItem, deleteItem, - gotoNextRevision, - gotoPreviousRevision, investigation, - isAtEarliestRevision, - isAtLatestRevision, loadInvestigation, - lockItem, - revision: renderableRevision, + renderableInvestigation, setGlobalParameters, - setItemParameters, - setItemPositions, - setItemTitle, - setRevision, setTitle, startNewInvestigation, - unlockItem, - updateItem, investigations, deleteInvestigation, }; diff --git a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts index 54281b6347ea1..8502079a4b228 100644 --- a/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts +++ b/x-pack/plugins/observability_solution/investigate/public/hooks/use_investigation/investigation_store.ts @@ -5,17 +5,15 @@ * 2.0. */ -import { BehaviorSubject, Observable } from 'rxjs'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { v4 } from 'uuid'; import { MaybePromise } from '@kbn/utility-types'; -import { keyBy } from 'lodash'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { v4 } from 'uuid'; import { InvestigateWidget, mergePlainObjects } from '../../../common'; import { GlobalWidgetParameters, InvestigateWidgetCreate, Investigation, - InvestigationRevision, } from '../../../common/types'; import { WidgetDefinition } from '../../types'; @@ -23,37 +21,19 @@ export type StatefulInvestigateWidget = InvestigateWidget & { loading: boolean; }; -export type StatefulInvestigation = Omit<Investigation, 'revisions'> & { - revisions: StatefulInvestigationRevision[]; -}; - -export type StatefulInvestigationRevision = Omit<InvestigationRevision, 'items'> & { +export type StatefulInvestigation = Omit<Investigation, 'items'> & { items: StatefulInvestigateWidget[]; }; interface InvestigationStore { - setItemPositions: ( - positions: Array<{ id: string; columns: number; rows: number }> - ) => Promise<void>; copyItem: (id: string) => Promise<void>; deleteItem: (id: string) => Promise<void>; addItem: (id: string, item: InvestigateWidgetCreate) => Promise<void>; - updateItem: ( - id: string, - cb: (prevItem: InvestigateWidget) => Promise<InvestigateWidget> - ) => Promise<void>; - lockItem: (id: string) => Promise<void>; - unlockItem: (id: string) => Promise<void>; - setItemTitle: (id: string, title: string) => Promise<void>; - setRevision: (revisionId: string) => Promise<void>; - gotoPreviousRevision: () => Promise<void>; - gotoNextRevision: () => Promise<void>; asObservable: () => Observable<{ investigation: StatefulInvestigation; }>; getInvestigation: () => Promise<Readonly<StatefulInvestigation>>; setGlobalParameters: (globalWidgetParameters: GlobalWidgetParameters) => Promise<void>; - setItemParameters: (id: string, parameters: GlobalWidgetParameters) => Promise<void>; setTitle: (title: string) => Promise<void>; destroy: () => void; } @@ -81,11 +61,7 @@ async function regenerateItem({ throw new Error(`Definition for widget ${widget.type} not found`); } - const nextParameters = mergePlainObjects( - globalWidgetParameters, - widget.parameters, - widget.locked ? {} : globalWidgetParameters - ); + const nextParameters = mergePlainObjects(widget.parameters, globalWidgetParameters); const widgetData = await definition.generate({ parameters: nextParameters, @@ -117,12 +93,7 @@ export function createInvestigationStore({ const observable$ = new BehaviorSubject<{ investigation: StatefulInvestigation }>({ investigation: { ...investigation, - revisions: investigation.revisions.map((revision) => { - return { - ...revision, - items: revision.items.map((item) => ({ ...item, loading: false })), - }; - }), + items: investigation.items.map((item) => ({ ...item, loading: false })), }, }); @@ -132,137 +103,14 @@ export function createInvestigationStore({ observable$.next({ investigation: await cb(observable$.value.investigation) }); } - async function updateRevisionInPlace( - cb: (prevRevision: StatefulInvestigationRevision) => MaybePromise<StatefulInvestigationRevision> - ) { - return updateInvestigationInPlace(async (prevInvestigation) => { - const currentRevision = prevInvestigation.revisions.find((revision) => { - return revision.id === prevInvestigation.revision; - })!; - - const newRevision = await cb(currentRevision); - - return { - ...prevInvestigation, - revisions: prevInvestigation.revisions.map((revision) => { - return revision.id === currentRevision.id ? newRevision : revision; - }), - }; - }); - } - - async function nextRevision( - cb: (prevRevision: StatefulInvestigationRevision) => MaybePromise<StatefulInvestigationRevision> - ) { - return updateInvestigationInPlace(async (prevInvestigation) => { - const indexOfCurrentRevision = prevInvestigation.revisions.findIndex( - (revision) => revision.id === prevInvestigation.revision - ); - - const currentRevision = prevInvestigation.revisions[indexOfCurrentRevision]; - - const newRevision = { - ...(await cb(currentRevision)), - id: v4(), - }; - - return { - ...prevInvestigation, - revisions: prevInvestigation.revisions - .slice(0, indexOfCurrentRevision + 1) - .concat(newRevision) - .slice(-10), - revision: newRevision.id, - }; - }); - } - - async function updateItem( - itemId: string, - cb: (prevItem: InvestigateWidget) => MaybePromise<InvestigateWidget> - ) { - await updateRevisionInPlace(async (prevRevision) => { - const prevItem = prevRevision.items.find((item) => item.id === itemId); - if (!prevItem) { - throw new Error('Could not find item by id ' + itemId); - } - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - if (item === prevItem) { - return { ...prevItem, loading: true }; - } - return item; - }), - }; - }); - - await nextRevision(async (prevRevision) => { - const prevItem = prevRevision.items.find((item) => item.id === itemId); - if (!prevItem) { - throw new Error('Could not find item by id ' + itemId); - } - const nextItem = await cb(prevItem); - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - if (item === prevItem) { - return { ...nextItem, loading: false }; - } - return item; - }), - }; - }); - } - - const regenerateItemAndUpdateRevision = async ( - itemId: string, - partials: Partial<InvestigateWidget> - ) => { - await updateRevisionInPlace((prevRevision) => { - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - if (item.id === itemId) { - return { ...item, loading: true, ...partials }; - } - return item; - }), - }; - }); - - await nextRevision(async (prevRevision) => { - return { - ...prevRevision, - items: await Promise.all( - prevRevision.items.map(async (item) => { - if (item.id === itemId) { - return { - ...(await regenerateItem({ - user, - globalWidgetParameters: prevRevision.parameters, - signal: controller.signal, - widget: item, - widgetDefinitions, - })), - loading: false, - }; - } - return item; - }) - ), - }; - }); - }; - const asObservable = observable$.asObservable(); return { addItem: (itemId, item) => { - return nextRevision(async (prevRevision) => { + return updateInvestigationInPlace(async (prevInvestigation) => { return { - ...prevRevision, - items: prevRevision.items.concat({ + ...prevInvestigation, + items: prevInvestigation.items.concat({ ...(await regenerateItem({ user, widgetDefinitions, @@ -271,25 +119,22 @@ export function createInvestigationStore({ ...item, id: itemId, }, - globalWidgetParameters: prevRevision.parameters, + globalWidgetParameters: prevInvestigation.parameters, })), loading: false, }), }; }); }, - updateItem: async (itemId, cb) => { - return updateItem(itemId, cb); - }, copyItem: (itemId) => { - return nextRevision((prevRevision) => { - const itemToCopy = prevRevision.items.find((item) => item.id === itemId); + return updateInvestigationInPlace((prevInvestigation) => { + const itemToCopy = prevInvestigation.items.find((item) => item.id === itemId); if (!itemToCopy) { throw new Error('Cannot find item for id ' + itemId); } return { - ...prevRevision, - items: prevRevision.items.concat({ + ...prevInvestigation, + items: prevInvestigation.items.concat({ ...itemToCopy, id: v4(), }), @@ -297,72 +142,17 @@ export function createInvestigationStore({ }); }, deleteItem: (itemId) => { - return nextRevision((prevRevision) => { - const itemToDelete = prevRevision.items.find((item) => item.id === itemId); - if (!itemToDelete) { - return prevRevision; - } - return { - ...prevRevision, - items: prevRevision.items.filter((itemAtIndex) => itemAtIndex.id !== itemToDelete.id), - }; - }); - }, - setItemPositions: (positions) => { - return nextRevision((prevRevision) => { - const positionsById = keyBy(positions, (position) => position.id); - return { - ...prevRevision, - items: prevRevision.items.map((item) => { - const position = positionsById[item.id]; - - return { - ...item, - ...position, - }; - }), - }; - }); - }, - setRevision: (revision) => { - return updateInvestigationInPlace((prevInvestigation) => { - return { - ...prevInvestigation, - revision, - }; - }); - }, - gotoPreviousRevision: () => { return updateInvestigationInPlace((prevInvestigation) => { - const indexOfCurrentRevision = prevInvestigation.revisions.findIndex( - (revision) => revision.id === prevInvestigation.revision - ); - - const targetRevision = prevInvestigation.revisions[indexOfCurrentRevision - 1]; - if (!targetRevision) { - throw new Error('Could not find previous revision'); - } - - return { - ...prevInvestigation, - revision: targetRevision.id, - }; - }); - }, - gotoNextRevision: () => { - return updateInvestigationInPlace((prevInvestigation) => { - const indexOfCurrentRevision = prevInvestigation.revisions.findIndex( - (revision) => revision.id === prevInvestigation.revision - ); - - const targetRevision = prevInvestigation.revisions[indexOfCurrentRevision + 1]; - if (!targetRevision) { - throw new Error('Could not find previous revision'); + const itemToDelete = prevInvestigation.items.find((item) => item.id === itemId); + if (!itemToDelete) { + return prevInvestigation; } return { ...prevInvestigation, - revision: targetRevision.id, + items: prevInvestigation.items.filter( + (itemAtIndex) => itemAtIndex.id !== itemToDelete.id + ), }; }); }, @@ -372,54 +162,40 @@ export function createInvestigationStore({ return controller.abort(); }, setGlobalParameters: async (parameters) => { - await updateRevisionInPlace((prevRevision) => { + await updateInvestigationInPlace((prevInvestigation) => { return { - ...prevRevision, - items: prevRevision.items.map((item) => { - return { ...item, loading: !item.locked }; + ...prevInvestigation, + items: prevInvestigation.items.map((item) => { + return { ...item, loading: true }; }), }; }); - await nextRevision(async (prevRevision) => { + await updateInvestigationInPlace(async (prevInvestigation) => { return { - ...prevRevision, + ...prevInvestigation, parameters, items: await Promise.all( - prevRevision.items.map(async (item) => { - return item.locked - ? item - : { - ...(await regenerateItem({ - widget: item, - globalWidgetParameters: parameters, - signal: controller.signal, - user, - widgetDefinitions, - })), - loading: false, - }; + prevInvestigation.items.map(async (item) => { + return { + ...(await regenerateItem({ + widget: item, + globalWidgetParameters: parameters, + signal: controller.signal, + user, + widgetDefinitions, + })), + loading: false, + }; }) ), }; }); }, - setItemTitle: async (itemId, title) => { - return updateItem(itemId, (prev) => ({ ...prev, title })); - }, - lockItem: async (itemId) => { - return updateItem(itemId, (prev) => ({ ...prev, locked: true })); - }, - unlockItem: async (itemId) => { - await regenerateItemAndUpdateRevision(itemId, { locked: false }); - }, setTitle: async (title: string) => { - return nextRevision((prevRevision) => { - return { ...prevRevision, title }; + return updateInvestigationInPlace((prevInvestigation) => { + return { ...prevInvestigation, title }; }); }, - setItemParameters: async (itemId, nextParameters) => { - await regenerateItemAndUpdateRevision(itemId, { parameters: nextParameters }); - }, }; } diff --git a/x-pack/plugins/observability_solution/investigate/public/index.ts b/x-pack/plugins/observability_solution/investigate/public/index.ts index b830aebc89947..8d296a321c94d 100644 --- a/x-pack/plugins/observability_solution/investigate/public/index.ts +++ b/x-pack/plugins/observability_solution/investigate/public/index.ts @@ -21,12 +21,10 @@ export type { InvestigatePublicSetup, InvestigatePublicStart, OnWidgetAdd, Widge export { type Investigation, - type InvestigationRevision, type InvestigateWidget, type InvestigateWidgetCreate, InvestigateWidgetColumnSpan, type GlobalWidgetParameters, - type WorkflowBlock, } from '../common/types'; export { mergePlainObjects } from '../common/utils/merge_plain_objects'; diff --git a/x-pack/plugins/observability_solution/investigate/public/types.ts b/x-pack/plugins/observability_solution/investigate/public/types.ts index fc6ca0376d20f..8951781be99f2 100644 --- a/x-pack/plugins/observability_solution/investigate/public/types.ts +++ b/x-pack/plugins/observability_solution/investigate/public/types.ts @@ -9,7 +9,7 @@ import type { FromSchema } from 'json-schema-to-ts'; import type { CompatibleJSONSchema } from '@kbn/observability-ai-assistant-plugin/public'; import type { AuthenticatedUser } from '@kbn/core/public'; -import type { InvestigateWidget, WorkflowBlock } from '../common'; +import type { InvestigateWidget } from '../common'; import type { GlobalWidgetParameters, InvestigateWidgetCreate } from '../common/types'; import type { UseInvestigationApi } from './hooks/use_investigation'; import type { UseInvestigateWidgetApi } from './hooks/use_investigate_widget'; @@ -22,14 +22,9 @@ export enum ChromeOption { export type OnWidgetAdd = (create: InvestigateWidgetCreate) => Promise<void>; -type UnregisterFunction = () => void; - export interface WidgetRenderAPI { onDelete: () => void; onWidgetAdd: OnWidgetAdd; - blocks: { - publish: (blocks: WorkflowBlock[]) => UnregisterFunction; - }; } type WidgetRenderOptions<TInvestigateWidget extends InvestigateWidget> = { diff --git a/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts b/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts index 8d8592bf09a9a..10d3c76a86d03 100644 --- a/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts +++ b/x-pack/plugins/observability_solution/investigate/public/util/get_es_filters_from_global_parameters.ts @@ -8,11 +8,10 @@ import { type BoolQuery, buildEsQuery } from '@kbn/es-query'; import type { GlobalWidgetParameters } from '../../common/types'; -export function getEsFilterFromGlobalParameters({ - filters, - timeRange, -}: Partial<GlobalWidgetParameters>): { bool: BoolQuery } { - const esFilter = buildEsQuery(undefined, [], filters ?? []); +export function getEsFilterFromGlobalParameters({ timeRange }: Partial<GlobalWidgetParameters>): { + bool: BoolQuery; +} { + const esFilter = buildEsQuery(undefined, [], []); if (timeRange) { esFilter.bool.filter.push({ diff --git a/x-pack/plugins/observability_solution/investigate/server/config.ts b/x-pack/plugins/observability_solution/investigate/server/config.ts index 081a30150dbea..cbc60b7ed2f32 100644 --- a/x-pack/plugins/observability_solution/investigate/server/config.ts +++ b/x-pack/plugins/observability_solution/investigate/server/config.ts @@ -8,7 +8,7 @@ import { schema, type TypeOf } from '@kbn/config-schema'; export const config = schema.object({ - enabled: schema.boolean({ defaultValue: true }), + enabled: schema.boolean({ defaultValue: false }), }); export type InvestigateConfig = TypeOf<typeof config>; diff --git a/x-pack/plugins/observability_solution/investigate_app/common/schema/create.ts b/x-pack/plugins/observability_solution/investigate_app/common/schema/create.ts new file mode 100644 index 0000000000000..050fa67d5cbaf --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/common/schema/create.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 * as t from 'io-ts'; +import { investigationResponseSchema } from './investigation'; + +const createInvestigationParamsSchema = t.type({ + body: t.type({ + id: t.string, + title: t.string, + parameters: t.type({ + timeRange: t.type({ from: t.number, to: t.number }), + }), + }), +}); + +const createInvestigationResponseSchema = investigationResponseSchema; + +type CreateInvestigationInput = t.OutputOf<typeof createInvestigationParamsSchema.props.body>; // Raw payload sent by the frontend +type CreateInvestigationParams = t.TypeOf<typeof createInvestigationParamsSchema.props.body>; // Parsed payload used by the backend +type CreateInvestigationResponse = t.OutputOf<typeof createInvestigationResponseSchema>; // Raw response sent to the frontend + +export { createInvestigationParamsSchema, createInvestigationResponseSchema }; +export type { CreateInvestigationInput, CreateInvestigationParams, CreateInvestigationResponse }; diff --git a/x-pack/plugins/observability_solution/investigate_app/common/schema/find.ts b/x-pack/plugins/observability_solution/investigate_app/common/schema/find.ts new file mode 100644 index 0000000000000..dc76a39fce679 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/common/schema/find.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 * as t from 'io-ts'; +import { investigationResponseSchema } from './investigation'; + +const findInvestigationsParamsSchema = t.partial({ + query: t.partial({ + page: t.string, + perPage: t.string, + }), +}); + +const findInvestigationsResponseSchema = t.type({ + page: t.number, + perPage: t.number, + total: t.number, + results: t.array(investigationResponseSchema), +}); + +type FindInvestigationsParams = t.TypeOf<typeof findInvestigationsParamsSchema.props.query>; // Parsed payload used by the backend +type FindInvestigationsResponse = t.OutputOf<typeof findInvestigationsResponseSchema>; // Raw response sent to the frontend + +export { findInvestigationsParamsSchema, findInvestigationsResponseSchema }; +export type { FindInvestigationsParams, FindInvestigationsResponse }; diff --git a/x-pack/plugins/observability_solution/investigate_app/common/schema/get.ts b/x-pack/plugins/observability_solution/investigate_app/common/schema/get.ts new file mode 100644 index 0000000000000..b30fb9f61cff8 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/common/schema/get.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 * as t from 'io-ts'; +import { investigationResponseSchema } from './investigation'; + +const getInvestigationParamsSchema = t.type({ + path: t.type({ + id: t.string, + }), +}); + +const getInvestigationResponseSchema = investigationResponseSchema; + +type GetInvestigationParams = t.TypeOf<typeof getInvestigationParamsSchema.props.path>; // Parsed payload used by the backend +type GetInvestigationResponse = t.OutputOf<typeof getInvestigationResponseSchema>; // Raw response sent to the frontend + +export { getInvestigationParamsSchema, getInvestigationResponseSchema }; +export type { GetInvestigationParams, GetInvestigationResponse }; diff --git a/x-pack/plugins/observability_solution/investigate_app/common/schema/investigation.ts b/x-pack/plugins/observability_solution/investigate_app/common/schema/investigation.ts new file mode 100644 index 0000000000000..3046a5c4c6d8a --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/common/schema/investigation.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 * as t from 'io-ts'; + +const investigationResponseSchema = t.type({ + id: t.string, + title: t.string, + createdAt: t.number, + createdBy: t.string, + parameters: t.type({ + timeRange: t.type({ from: t.number, to: t.number }), + }), +}); + +type InvestigationResponse = t.OutputOf<typeof investigationResponseSchema>; + +export { investigationResponseSchema }; +export type { InvestigationResponse }; diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx index edb714c6d388e..0db8b3b67ee35 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/add_note_ui/index.stories.tsx @@ -34,7 +34,6 @@ const defaultStory: Story = { username: 'johndoe', full_name: 'John Doe', }, - filters: [], timeRange: { from: moment().subtract(15, 'minutes').toISOString(), to: moment().toISOString(), diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx index 2bd84639e67dc..a661e6fdc8eae 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/esql_widget_preview.tsx @@ -52,7 +52,6 @@ function getWidgetFromSuggestion({ }, columns: makeItWide ? InvestigateWidgetColumnSpan.Four : InvestigateWidgetColumnSpan.One, rows, - locked: false, }); } @@ -79,7 +78,6 @@ function PreviewContainer({ children }: { children: React.ReactNode }) { export function EsqlWidgetPreview({ esqlQuery, onWidgetAdd, - filters, timeRange, }: { esqlQuery: string; @@ -91,10 +89,9 @@ export function EsqlWidgetPreview({ const filter = useMemo(() => { return getEsFilterFromOverrides({ - filters, timeRange, }); - }, [filters, timeRange]); + }, [timeRange]); const [selectedSuggestion, setSelectedSuggestion] = useState<Suggestion | undefined>(undefined); diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx index 182b72bd56750..69f43ef515146 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/add_observation_ui/index.tsx @@ -21,16 +21,14 @@ const emptyPreview = css` padding: 36px 0px 36px 0px; `; -export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) { +export function AddObservationUI({ onWidgetAdd, timeRange }: Props) { const [isOpen, setIsOpen] = React.useState(false); - const [isExpanded, setIsExpanded] = React.useState(false); const [query, setQuery] = React.useState({ esql: '' }); const [submittedQuery, setSubmittedQuery] = React.useState({ esql: '' }); const [isPreviewOpen, setIsPreviewOpen] = React.useState(false); const resetState = () => { - setIsExpanded(false); setIsPreviewOpen(false); setQuery({ esql: '' }); setSubmittedQuery({ esql: '' }); @@ -83,11 +81,6 @@ export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) { }} errors={undefined} warning={undefined} - expandCodeEditor={(expanded: boolean) => { - setIsExpanded(() => expanded); - }} - isCodeEditorExpanded={isExpanded} - hideMinimizeButton={false} editorIsInline={false} hideRunQueryText isLoading={false} @@ -118,7 +111,6 @@ export function AddObservationUI({ onWidgetAdd, timeRange, filters }: Props) { </EuiFlexGroup> ) : ( <EsqlWidgetPreview - filters={filters} esqlQuery={submittedQuery.esql} timeRange={timeRange} onWidgetAdd={(widget) => { diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx index aff1eab514351..0f66c5403e172 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.stories.tsx @@ -40,18 +40,11 @@ const defaultProps: Story = { return ( <div style={{ width: 800, height: 600 }}> <Component - faded={false} - locked={false} loading={false} onCopy={() => {}} onDelete={() => {}} - onLockToggle={() => {}} - onOverrideRemove={async () => {}} - onTitleChange={() => {}} - overrides={[]} title="My visualization" description="A long description" - onEditClick={() => {}} {...props} /> </div> diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx index df418271fac22..1068d59ce304c 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/grid_item/index.tsx @@ -4,14 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; import { css } from '@emotion/css'; -import classNames from 'classnames'; import React from 'react'; -import { i18n } from '@kbn/i18n'; import { useTheme } from '../../hooks/use_theme'; import { InvestigateTextButton } from '../investigate_text_button'; -import { InvestigateWidgetGridItemOverride } from '../investigate_widget_grid'; export const GRID_ITEM_HEADER_HEIGHT = 40; @@ -20,16 +17,9 @@ interface GridItemProps { title: string; description: string; children: React.ReactNode; - locked: boolean; onCopy: () => void; - onTitleChange: (title: string) => void; onDelete: () => void; - onLockToggle: () => void; loading: boolean; - faded: boolean; - onOverrideRemove: (override: InvestigateWidgetGridItemOverride) => Promise<void>; - onEditClick: () => void; - overrides: InvestigateWidgetGridItemOverride[]; } const editTitleButtonClassName = `investigateGridItemTitleEditButton`; @@ -46,17 +36,6 @@ const titleItemClassName = css` } `; -const fadedClassName = css` - opacity: 0.5 !important; -`; - -const lockedControlClassName = css` - opacity: 0.9 !important; - &:hover { - opacity: 1 !important; - } -`; - const panelContainerClassName = css` overflow: clip; overflow-clip-margin: 20px; @@ -78,28 +57,14 @@ const headerClassName = css` height: ${GRID_ITEM_HEADER_HEIGHT}px; `; -const changeBadgeClassName = css` - max-width: 96px; - .euiText { - text-overflow: ellipsis; - overflow: hidden; - } -`; - export function GridItem({ id, title, description, children, - locked, - onLockToggle, onDelete, onCopy, loading, - faded, - overrides, - onOverrideRemove, - onEditClick, }: GridItemProps) { const theme = useTheme(); @@ -118,7 +83,7 @@ export function GridItem({ <EuiFlexGroup direction="column" gutterSize="none" - className={faded ? classNames(containerClassName, fadedClassName) : containerClassName} + className={containerClassName} alignItems="stretch" > <EuiFlexItem grow={false}> @@ -133,33 +98,6 @@ export function GridItem({ {title} </EuiText> </EuiFlexItem> - <EuiFlexItem grow={false}> - {overrides.length ? ( - <EuiFlexGroup direction="row" gutterSize="xs" justifyContent="flexStart"> - {overrides.map((override) => ( - <EuiFlexItem key={override.id} grow={false}> - <EuiBadge - color="primary" - className={changeBadgeClassName} - iconType="cross" - iconSide="right" - iconOnClick={() => { - onOverrideRemove(override); - }} - iconOnClickAriaLabel={i18n.translate( - 'xpack.investigateApp.gridItem.removeOverrideButtonAriaLabel', - { - defaultMessage: 'Remove filter', - } - )} - > - <EuiText size="xs">{override.label}</EuiText> - </EuiBadge> - </EuiFlexItem> - ))} - </EuiFlexGroup> - ) : null} - </EuiFlexItem> <EuiFlexItem grow={false} className="gridItemControls"> <EuiFlexGroup direction="row" @@ -185,26 +123,6 @@ export function GridItem({ disabled={loading} /> </EuiFlexItem> - <EuiFlexItem grow={false}> - <InvestigateTextButton - iconType="pencil" - onClick={() => { - onEditClick(); - }} - disabled={loading} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <InvestigateTextButton - iconType={locked ? 'lock' : 'lockOpen'} - className={locked ? lockedControlClassName : ''} - color={locked ? 'primary' : 'text'} - onClick={() => { - onLockToggle(); - }} - disabled={loading} - /> - </EuiFlexItem> </EuiFlexGroup> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx index a562c3647fcd4..37ed996162ed2 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_view/index.tsx @@ -6,15 +6,13 @@ */ import datemath from '@elastic/datemath'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; -import type { InvestigateWidget, InvestigateWidgetCreate } from '@kbn/investigate-plugin/public'; -import { DATE_FORMAT_ID } from '@kbn/management-settings-ids'; +import type { InvestigateWidgetCreate } from '@kbn/investigate-plugin/public'; import { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { keyBy, omit, pick } from 'lodash'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { keyBy, noop } from 'lodash'; +import React, { useEffect, useMemo, useRef } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { useDateRange } from '../../hooks/use_date_range'; import { useKibana } from '../../hooks/use_kibana'; -import { getOverridesFromGlobalParameters } from '../../utils/get_overrides_from_global_parameters'; import { AddNoteUI } from '../add_note_ui'; import { AddObservationUI } from '../add_observation_ui'; import { InvestigateSearchBar } from '../investigate_search_bar'; @@ -22,35 +20,26 @@ import { InvestigateWidgetGrid } from '../investigate_widget_grid'; function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { const { - core: { uiSettings }, dependencies: { start: { investigate }, }, } = useKibana(); const widgetDefinitions = useMemo(() => investigate.getWidgetDefinitions(), [investigate]); const [range, setRange] = useDateRange(); - const [searchBarFocused, setSearchBarFocused] = useState(false); const { addItem, - setItemPositions, - setItemTitle, copyItem, deleteItem, investigation, - lockItem, - setItemParameters, setGlobalParameters, - unlockItem, - revision, + renderableInvestigation, } = investigate.useInvestigation({ user, from: range.start.toISOString(), to: range.end.toISOString(), }); - const [_editingItem, setEditingItem] = useState<InvestigateWidget | undefined>(undefined); - const createWidget = (widgetCreate: InvestigateWidgetCreate) => { return addItem(widgetCreate); }; @@ -58,31 +47,21 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { const createWidgetRef = useRef(createWidget); createWidgetRef.current = createWidget; - useEffect(() => { - const itemIds = revision?.items.map((item) => item.id) ?? []; - setEditingItem((prevEditingItem) => { - if (prevEditingItem && !itemIds.includes(prevEditingItem.id)) { - return undefined; - } - return prevEditingItem; - }); - }, [revision]); - useEffect(() => { if ( - revision?.parameters.timeRange.from && - revision?.parameters.timeRange.to && - range.start.toISOString() !== revision.parameters.timeRange.from && - range.end.toISOString() !== revision.parameters.timeRange.to + renderableInvestigation?.parameters.timeRange.from && + renderableInvestigation?.parameters.timeRange.to && + range.start.toISOString() !== renderableInvestigation.parameters.timeRange.from && + range.end.toISOString() !== renderableInvestigation.parameters.timeRange.to ) { setRange({ - from: revision.parameters.timeRange.from, - to: revision.parameters.timeRange.to, + from: renderableInvestigation.parameters.timeRange.from, + to: renderableInvestigation.parameters.timeRange.to, }); } }, [ - revision?.parameters.timeRange.from, - revision?.parameters.timeRange.to, + renderableInvestigation?.parameters.timeRange.from, + renderableInvestigation?.parameters.timeRange.to, range.start, range.end, setRange, @@ -91,7 +70,7 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { const gridItems = useMemo(() => { const widgetDefinitionsByType = keyBy(widgetDefinitions, 'type'); - return revision?.items.map((item) => { + return renderableInvestigation?.items.map((item) => { const definitionForType = widgetDefinitionsByType[item.type]; return ( @@ -103,21 +82,13 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { columns: item.columns, rows: item.rows, chrome: definitionForType.chrome, - locked: item.locked, loading: item.loading, - overrides: item.locked - ? getOverridesFromGlobalParameters( - pick(item.parameters, 'filters', 'timeRange'), - revision.parameters, - uiSettings.get<string>(DATE_FORMAT_ID) ?? 'Browser' - ) - : [], } ?? [] ); }); - }, [revision, widgetDefinitions, uiSettings]); + }, [renderableInvestigation, widgetDefinitions]); - if (!investigation || !revision || !gridItems) { + if (!investigation || !renderableInvestigation || !gridItems) { return <EuiLoadingSpinner />; } @@ -136,18 +107,12 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { to: datemath.parse(dateRange.to)!.toISOString(), }; await setGlobalParameters({ - ...revision.parameters, + ...renderableInvestigation.parameters, timeRange: nextDateRange, }); setRange(nextDateRange); }} - onFocus={() => { - setSearchBarFocused(true); - }} - onBlur={() => { - setSearchBarFocused(false); - }} /> </EuiFlexItem> @@ -155,16 +120,7 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { <InvestigateWidgetGrid items={gridItems} onItemsChange={async (nextGridItems) => { - return setItemPositions( - nextGridItems.map((gridItem) => ({ - columns: gridItem.columns, - rows: gridItem.rows, - id: gridItem.id, - })) - ); - }} - onItemTitleChange={async (item, title) => { - return setItemTitle(item.id, title); + noop(); }} onItemCopy={async (copiedItem) => { return copyItem(copiedItem.id); @@ -172,30 +128,12 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { onItemDelete={async (deletedItem) => { return deleteItem(deletedItem.id); }} - onItemLockToggle={async (toggledItem) => { - return toggledItem.locked ? unlockItem(toggledItem.id) : lockItem(toggledItem.id); - }} - fadeLockedItems={searchBarFocused} - onItemOverrideRemove={async (updatedItem, override) => { - // TODO: remove filters - const itemToUpdate = revision.items.find((item) => item.id === updatedItem.id); - if (itemToUpdate) { - return setItemParameters(updatedItem.id, { - ...revision.parameters, - ...omit(itemToUpdate.parameters, override.id), - }); - } - }} - onItemEditClick={(itemToEdit) => { - setEditingItem(revision.items.find((item) => item.id === itemToEdit.id)); - }} /> </EuiFlexItem> </EuiFlexGroup> <AddObservationUI - filters={revision.parameters.filters} - timeRange={revision.parameters.timeRange} + timeRange={renderableInvestigation.parameters.timeRange} onWidgetAdd={(widget) => { return createWidgetRef.current(widget); }} @@ -206,8 +144,7 @@ function InvestigateViewWithUser({ user }: { user: AuthenticatedUser }) { <EuiFlexItem grow={2}> <AddNoteUI user={user} - filters={revision.parameters.filters} - timeRange={revision.parameters.timeRange} + timeRange={renderableInvestigation.parameters.timeRange} onWidgetAdd={(widget) => { return createWidgetRef.current(widget); }} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx index f4436051d76c2..5962a55fa22f0 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.stories.tsx @@ -62,7 +62,6 @@ function createItem<T extends Partial<InvestigateWidgetGridItem>>(overrides: T) columns: 4, rows: 2, description: '', - locked: false, loading: false, overrides: [], }; @@ -96,7 +95,6 @@ export const InvestigateWidgetGridStory: ComponentStoryObj<typeof Component> = { ), columns: 4, rows: 12, - locked: true, }), createItem({ title: '5', diff --git a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx index eea40cdca391d..053433b83383e 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/components/investigate_widget_grid/index.tsx @@ -12,9 +12,9 @@ import React, { useCallback, useMemo, useRef } from 'react'; import { ItemCallback, Layout, Responsive, WidthProvider } from 'react-grid-layout'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; -import { EuiBreakpoint, EUI_BREAKPOINTS, useBreakpoints } from '../../hooks/use_breakpoints'; +import { EUI_BREAKPOINTS, EuiBreakpoint, useBreakpoints } from '../../hooks/use_breakpoints'; import { useTheme } from '../../hooks/use_theme'; -import { GridItem, GRID_ITEM_HEADER_HEIGHT } from '../grid_item'; +import { GRID_ITEM_HEADER_HEIGHT, GridItem } from '../grid_item'; import './styles.scss'; const gridContainerClassName = css` @@ -36,11 +36,6 @@ interface GridSection { type Section = SingleComponentSection | GridSection; -export interface InvestigateWidgetGridItemOverride { - id: string; - label: React.ReactNode; -} - export interface InvestigateWidgetGridItem { title: string; description: string; @@ -48,10 +43,8 @@ export interface InvestigateWidgetGridItem { id: string; columns: number; rows: number; - locked: boolean; chrome?: ChromeOption; loading: boolean; - overrides: InvestigateWidgetGridItemOverride[]; } interface InvestigateWidgetGridProps { @@ -59,14 +52,6 @@ interface InvestigateWidgetGridProps { onItemsChange: (items: InvestigateWidgetGridItem[]) => Promise<void>; onItemCopy: (item: InvestigateWidgetGridItem) => Promise<void>; onItemDelete: (item: InvestigateWidgetGridItem) => Promise<void>; - onItemLockToggle: (item: InvestigateWidgetGridItem) => Promise<void>; - onItemOverrideRemove: ( - item: InvestigateWidgetGridItem, - override: InvestigateWidgetGridItemOverride - ) => Promise<void>; - onItemTitleChange: (item: InvestigateWidgetGridItem, title: string) => Promise<void>; - onItemEditClick: (item: InvestigateWidgetGridItem) => void; - fadeLockedItems: boolean; } const ROW_HEIGHT = 32; @@ -130,11 +115,6 @@ function GridSectionRenderer({ onItemsChange, onItemDelete, onItemCopy, - onItemLockToggle, - onItemOverrideRemove, - onItemTitleChange, - onItemEditClick, - fadeLockedItems, }: InvestigateWidgetGridProps) { const WithFixedWidth = useMemo(() => WidthProvider(Responsive), []); @@ -144,14 +124,9 @@ function GridSectionRenderer({ onItemsChange, onItemCopy, onItemDelete, - onItemLockToggle, - onItemOverrideRemove, - onItemTitleChange, - onItemEditClick, }; const itemCallbacksRef = useRef(callbacks); - itemCallbacksRef.current = callbacks; const { currentBreakpoint } = useBreakpoints(); @@ -167,34 +142,19 @@ function GridSectionRenderer({ id={item.id} title={item.title} description={item.description} - onTitleChange={(title) => { - return itemCallbacksRef.current.onItemTitleChange(item, title); - }} onCopy={() => { return itemCallbacksRef.current.onItemCopy(item); }} onDelete={() => { return itemCallbacksRef.current.onItemDelete(item); }} - locked={item.locked} - onLockToggle={() => { - itemCallbacksRef.current.onItemLockToggle(item); - }} - onOverrideRemove={(override) => { - return itemCallbacksRef.current.onItemOverrideRemove(item, override); - }} - onEditClick={() => { - return itemCallbacksRef.current.onItemEditClick(item); - }} - overrides={item.overrides} loading={item.loading} - faded={fadeLockedItems && item.locked} > {item.element} </GridItem> </div> )); - }, [items, fadeLockedItems]); + }, [items]); // react-grid calls `onLayoutChange` every time // `layouts` changes, except when on mount. So... @@ -273,11 +233,6 @@ export function InvestigateWidgetGrid({ onItemsChange, onItemDelete, onItemCopy, - onItemLockToggle, - fadeLockedItems, - onItemOverrideRemove, - onItemTitleChange, - onItemEditClick, }: InvestigateWidgetGridProps) { const sections = useMemo<Section[]>(() => { let currentGrid: GridSection = { items: [] }; @@ -317,9 +272,6 @@ export function InvestigateWidgetGrid({ onItemDelete={(deletedItem) => { return onItemDelete(deletedItem); }} - onItemLockToggle={(toggledItem) => { - return onItemLockToggle(toggledItem); - }} onItemsChange={(itemsInSection) => { const nextItems = sections.flatMap((sectionAtIndex) => { if ('item' in sectionAtIndex) { @@ -333,16 +285,6 @@ export function InvestigateWidgetGrid({ return onItemsChange(nextItems); }} - onItemOverrideRemove={(item, override) => { - return onItemOverrideRemove(item, override); - }} - onItemTitleChange={(item, title) => { - return onItemTitleChange(item, title); - }} - onItemEditClick={(item) => { - return onItemEditClick(item); - }} - fadeLockedItems={fadeLockedItems} /> </EuiFlexItem> ); @@ -356,28 +298,13 @@ export function InvestigateWidgetGrid({ id={section.item.id} title={section.item.title} description={section.item.description} - faded={section.item.locked && fadeLockedItems} loading={section.item.loading} - locked={section.item.locked} - overrides={section.item.overrides} onCopy={() => { return onItemCopy(section.item); }} onDelete={() => { return onItemDelete(section.item); }} - onOverrideRemove={(override) => { - return onItemOverrideRemove(section.item, override); - }} - onTitleChange={(nextTitle) => { - return onItemTitleChange(section.item, nextTitle); - }} - onLockToggle={() => { - return onItemLockToggle(section.item); - }} - onEditClick={() => { - return onItemEditClick(section.item); - }} > {section.item.element} </GridItem> diff --git a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts index ccc385701fb0f..18b28770772eb 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts +++ b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_es_filter_from_overrides.ts @@ -5,19 +5,17 @@ * 2.0. */ -import { type BoolQuery, buildEsQuery, type Filter } from '@kbn/es-query'; +import { buildEsQuery, type BoolQuery } from '@kbn/es-query'; export function getEsFilterFromOverrides({ - filters, timeRange, }: { - filters?: Filter[]; timeRange?: { from: string; to: string; }; }): { bool: BoolQuery } { - const esFilter = buildEsQuery(undefined, [], filters ?? []); + const esFilter = buildEsQuery(undefined, [], []); if (timeRange) { esFilter.bool.filter.push({ diff --git a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_overrides_from_global_parameters.tsx b/x-pack/plugins/observability_solution/investigate_app/public/utils/get_overrides_from_global_parameters.tsx deleted file mode 100644 index 8da9daf040e36..0000000000000 --- a/x-pack/plugins/observability_solution/investigate_app/public/utils/get_overrides_from_global_parameters.tsx +++ /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 React from 'react'; -import { isEqual } from 'lodash'; -import type { GlobalWidgetParameters } from '@kbn/investigate-plugin/public'; -import type { Filter } from '@kbn/es-query'; -import objectHash from 'object-hash'; -import { i18n } from '@kbn/i18n'; -import { PrettyDuration } from '@elastic/eui'; -import type { InvestigateWidgetGridItemOverride } from '../components/investigate_widget_grid'; - -enum OverrideType { - timeRange = 'timeRange', - filters = 'filters', -} - -function getIdForFilter(filter: Filter) { - return objectHash({ meta: filter.meta, query: filter.query }); -} - -function getLabelForFilter(filter: Filter) { - return ( - filter.meta.alias ?? - filter.meta.key ?? - JSON.stringify({ meta: filter.meta, query: filter.query }) - ); -} - -export function getOverridesFromGlobalParameters( - itemParameters: GlobalWidgetParameters, - globalParameters: GlobalWidgetParameters, - uiSettingsDateFormat: string -) { - const overrides: InvestigateWidgetGridItemOverride[] = []; - - if (!isEqual(itemParameters.timeRange, globalParameters.timeRange)) { - overrides.push({ - id: OverrideType.timeRange, - label: ( - <PrettyDuration - timeFrom={itemParameters.timeRange.from} - timeTo={itemParameters.timeRange.to} - dateFormat={uiSettingsDateFormat} - /> - ), - }); - } - - if (!isEqual(itemParameters.filters, globalParameters.filters)) { - if (!itemParameters.filters.length) { - overrides.push({ - id: OverrideType.filters, - label: i18n.translate('xpack.investigateApp.overrides.noFilters', { - defaultMessage: 'No filters', - }), - }); - } - - itemParameters.filters.forEach((filter) => { - overrides.push({ - id: `${OverrideType.filters}_${getIdForFilter(filter)}`, - label: getLabelForFilter(filter), - }); - }); - } - - return overrides; -} diff --git a/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx b/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx index e4a1ea7e3396d..779fb9c5301b9 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/widgets/embeddable_widget/register_embeddable_widget.tsx @@ -28,17 +28,16 @@ type Props = EmbeddableWidgetParameters & GlobalWidgetParameters; type ParentApi = ReturnType<React.ComponentProps<typeof ReactEmbeddableRenderer>['getParentApi']>; -function ReactEmbeddable({ type, config, filters, timeRange: { from, to }, savedObjectId }: Props) { +function ReactEmbeddable({ type, config, timeRange: { from, to }, savedObjectId }: Props) { const configWithOverrides = useMemo(() => { return { ...config, - filters, timeRange: { from, to, }, }; - }, [config, filters, from, to]); + }, [config, from, to]); const configWithOverridesRef = useRef(configWithOverrides); @@ -66,13 +65,7 @@ function ReactEmbeddable({ type, config, filters, timeRange: { from, to }, saved ); } -function LegacyEmbeddable({ - type, - config, - filters, - timeRange: { from, to }, - savedObjectId, -}: Props) { +function LegacyEmbeddable({ type, config, timeRange: { from, to }, savedObjectId }: Props) { const { dependencies: { start: { embeddable }, @@ -95,7 +88,6 @@ function LegacyEmbeddable({ const configWithOverrides = { ...configWithId, - filters, timeRange: { from, to, @@ -109,7 +101,7 @@ function LegacyEmbeddable({ const instance = await factory.create(configWithOverrides); return instance; - }, [type, savedObjectId, config, from, to, embeddable, filters]); + }, [type, savedObjectId, config, from, to, embeddable]); const embeddableInstance = embeddableInstanceAsync.value; @@ -193,7 +185,6 @@ export function registerEmbeddableWidget({ registerWidget }: RegisterWidgetOptio config: widget.parameters.config, savedObjectId: widget.parameters.savedObjectId, timeRange: widget.parameters.timeRange, - filters: widget.parameters.filters, query: widget.parameters.query, }; diff --git a/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx b/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx index ea990d4e4ac4c..14d18302ae516 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/widgets/esql_widget/register_esql_widget.tsx @@ -82,7 +82,7 @@ export function EsqlWidget({ const timestampColumn = datatable.columns.find((column) => column.name === '@timestamp'); const messageColumn = datatable.columns.find((column) => column.name === 'message'); - if (datatable.columns.length > 100 && timestampColumn && messageColumn) { + if (datatable.columns.length > 20 && timestampColumn && messageColumn) { const hasDataForBothColumns = datatable.rows.every((row) => { const timestampValue = row['@timestamp']; const messageValue = row.message; @@ -183,6 +183,7 @@ export function EsqlWidget({ query={memoizedQueryObject} flyoutType="overlay" initialColumns={initialColumns} + initialRowHeight={1} /> </EuiFlexItem> </EuiFlexGroup> @@ -217,7 +218,6 @@ export function registerEsqlWidget({ async ({ parameters, signal }) => { const { esql: esqlQuery, - filters, timeRange, suggestion: suggestionFromParameters, } = parameters as EsqlWidgetParameters & GlobalWidgetParameters; @@ -226,7 +226,6 @@ export function registerEsqlWidget({ const esFilters = [ getEsFilterFromOverrides({ - filters, timeRange, }), ]; @@ -265,7 +264,7 @@ export function registerEsqlWidget({ dateHistogram: dateHistoResponse, }; }, - ({ widget, blocks }) => { + ({ widget }) => { const { main: { dataView, columns, values, suggestion }, dateHistogram, diff --git a/x-pack/plugins/observability_solution/investigate_app/server/models/investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/models/investigation.ts new file mode 100644 index 0000000000000..f079d922d1a9d --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/server/models/investigation.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 * as t from 'io-ts'; + +export const investigationSchema = t.type({ + id: t.string, + title: t.string, + createdAt: t.number, + createdBy: t.string, + parameters: t.type({ + timeRange: t.type({ from: t.number, to: t.number }), + }), +}); + +export type Investigation = t.TypeOf<typeof investigationSchema>; +export type StoredInvestigation = t.OutputOf<typeof investigationSchema>; diff --git a/x-pack/plugins/observability_solution/investigate_app/server/models/pagination.ts b/x-pack/plugins/observability_solution/investigate_app/server/models/pagination.ts new file mode 100644 index 0000000000000..255614932fe5e --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/server/models/pagination.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 interface Paginated<T> { + total: number; + page: number; + perPage: number; + results: T[]; +} + +export interface Pagination { + page: number; + perPage: number; +} diff --git a/x-pack/plugins/observability_solution/investigate_app/server/plugin.ts b/x-pack/plugins/observability_solution/investigate_app/server/plugin.ts index 7b8a1772858ec..f1ee1cacd155b 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/plugin.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/plugin.ts @@ -17,6 +17,8 @@ import type { InvestigateAppSetupDependencies, InvestigateAppStartDependencies, } from './types'; +import { investigation } from './saved_objects/investigation'; +import { InvestigateAppConfig } from './config'; export class InvestigateAppPlugin implements @@ -28,9 +30,11 @@ export class InvestigateAppPlugin > { logger: Logger; + config: InvestigateAppConfig; constructor(context: PluginInitializerContext<ConfigSchema>) { this.logger = context.logger.get(); + this.config = context.config.get<InvestigateAppConfig>(); } setup( coreSetup: CoreSetup<InvestigateAppStartDependencies, InvestigateAppServerStart>, @@ -47,13 +51,17 @@ export class InvestigateAppPlugin }; }) as InvestigateAppRouteHandlerResources['plugins']; - registerServerRoutes({ - core: coreSetup, - logger: this.logger, - dependencies: { - plugins: routeHandlerPlugins, - }, - }); + if (this.config.enabled === true) { + coreSetup.savedObjects.registerType(investigation); + + registerServerRoutes({ + core: coreSetup, + logger: this.logger, + dependencies: { + plugins: routeHandlerPlugins, + }, + }); + } return {}; } diff --git a/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts b/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts index 63931f717385c..7565330316fed 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/routes/get_global_investigate_app_server_route_repository.ts @@ -5,8 +5,63 @@ * 2.0. */ +import { findInvestigationsParamsSchema } from '../../common/schema/find'; +import { createInvestigationParamsSchema } from '../../common/schema/create'; +import { createInvestigation } from '../services/create_investigation'; +import { investigationRepositoryFactory } from '../services/investigation_repository'; +import { createInvestigateAppServerRoute } from './create_investigate_app_server_route'; +import { findInvestigations } from '../services/find_investigations'; +import { getInvestigationParamsSchema } from '../../common/schema/get'; +import { getInvestigation } from '../services/get_investigation'; + +const createInvestigationRoute = createInvestigateAppServerRoute({ + endpoint: 'POST /api/observability/investigations 2023-10-31', + options: { + tags: [], + }, + params: createInvestigationParamsSchema, + handler: async (params) => { + const soClient = (await params.context.core).savedObjects.client; + const repository = investigationRepositoryFactory({ soClient, logger: params.logger }); + + return await createInvestigation(params.params.body, repository); + }, +}); + +const findInvestigationsRoute = createInvestigateAppServerRoute({ + endpoint: 'GET /api/observability/investigations 2023-10-31', + options: { + tags: [], + }, + params: findInvestigationsParamsSchema, + handler: async (params) => { + const soClient = (await params.context.core).savedObjects.client; + const repository = investigationRepositoryFactory({ soClient, logger: params.logger }); + + return await findInvestigations(params.params?.query ?? {}, repository); + }, +}); + +const getInvestigationRoute = createInvestigateAppServerRoute({ + endpoint: 'GET /api/observability/investigations/{id} 2023-10-31', + options: { + tags: [], + }, + params: getInvestigationParamsSchema, + handler: async (params) => { + const soClient = (await params.context.core).savedObjects.client; + const repository = investigationRepositoryFactory({ soClient, logger: params.logger }); + + return await getInvestigation(params.params.path, repository); + }, +}); + export function getGlobalInvestigateAppServerRouteRepository() { - return {}; + return { + ...createInvestigationRoute, + ...findInvestigationsRoute, + ...getInvestigationRoute, + }; } export type InvestigateAppServerRouteRepository = ReturnType< diff --git a/x-pack/plugins/observability_solution/investigate_app/server/saved_objects/investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/saved_objects/investigation.ts new file mode 100644 index 0000000000000..47fa9f17749c1 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/server/saved_objects/investigation.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 { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SavedObject } from '@kbn/core/server'; +import { StoredInvestigation } from '../models/investigation'; + +export const SO_INVESTIGATION_TYPE = 'investigation'; + +export const investigation: SavedObjectsType = { + name: SO_INVESTIGATION_TYPE, + hidden: false, + namespaceType: 'multiple-isolated', + switchToModelVersionAt: '8.10.0', + mappings: { + dynamic: false, + properties: { + id: { type: 'keyword' }, + title: { type: 'text' }, + }, + }, + management: { + displayName: 'Investigation', + importableAndExportable: false, + getTitle(savedObject: SavedObject<StoredInvestigation>) { + return `Investigation: [${savedObject.attributes.title}]`; + }, + }, +}; diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.ts new file mode 100644 index 0000000000000..9d28136c06f6c --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/create_investigation.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 { CreateInvestigationInput, CreateInvestigationResponse } from '../../common/schema/create'; +import { InvestigationRepository } from './investigation_repository'; + +export async function createInvestigation( + params: CreateInvestigationInput, + repository: InvestigationRepository +): Promise<CreateInvestigationResponse> { + const investigation = { ...params, createdAt: Date.now(), createdBy: 'elastic' }; + await repository.save(investigation); + + return investigation; +} diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/find_investigations.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/find_investigations.ts new file mode 100644 index 0000000000000..2c08125aff734 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/find_investigations.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 { + FindInvestigationsParams, + FindInvestigationsResponse, + findInvestigationsResponseSchema, +} from '../../common/schema/find'; +import { InvestigationRepository } from './investigation_repository'; + +export async function findInvestigations( + params: FindInvestigationsParams, + repository: InvestigationRepository +): Promise<FindInvestigationsResponse> { + const investigations = await repository.search(toPagination(params)); + + return findInvestigationsResponseSchema.encode(investigations); +} + +function toPagination(params: FindInvestigationsParams) { + const DEFAULT_PER_PAGE = 10; + const DEFAULT_PAGE = 1; + return { + page: params.page ? parseInt(params.page, 10) : DEFAULT_PAGE, + perPage: params.perPage ? parseInt(params.perPage, 10) : DEFAULT_PER_PAGE, + }; +} diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/get_investigation.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/get_investigation.ts new file mode 100644 index 0000000000000..9bd6025f8e8a2 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/get_investigation.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 { GetInvestigationParams, GetInvestigationResponse } from '../../common/schema/get'; +import { InvestigationRepository } from './investigation_repository'; + +export async function getInvestigation( + params: GetInvestigationParams, + repository: InvestigationRepository +): Promise<GetInvestigationResponse> { + const investigation = await repository.findById(params.id); + + return investigation; +} diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.ts new file mode 100644 index 0000000000000..6d9c5ddbc0d03 --- /dev/null +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/investigation_repository.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 { Logger, SavedObjectsClientContract } from '@kbn/core/server'; +import { isLeft } from 'fp-ts/lib/Either'; +import { Investigation, StoredInvestigation, investigationSchema } from '../models/investigation'; +import { SO_INVESTIGATION_TYPE } from '../saved_objects/investigation'; +import { Paginated, Pagination } from '../models/pagination'; + +export interface InvestigationRepository { + save(investigation: Investigation): Promise<void>; + findById(id: string): Promise<Investigation>; + deleteById(id: string): Promise<void>; + search(pagination: Pagination): Promise<Paginated<Investigation>>; +} + +export function investigationRepositoryFactory({ + soClient, + logger, +}: { + soClient: SavedObjectsClientContract; + logger: Logger; +}): InvestigationRepository { + function toInvestigation(stored: StoredInvestigation): Investigation | undefined { + const result = investigationSchema.decode({ + ...stored, + }); + + if (isLeft(result)) { + logger.error(`Invalid stored Investigation with id [${stored.id}]`); + return undefined; + } + + return result.right; + } + + function toStoredInvestigation(investigation: Investigation): StoredInvestigation { + return investigationSchema.encode(investigation); + } + + return { + async save(investigation: Investigation): Promise<void> { + await soClient.create<StoredInvestigation>( + SO_INVESTIGATION_TYPE, + toStoredInvestigation(investigation), + { + id: investigation.id, + overwrite: true, + } + ); + }, + + async findById(id: string): Promise<Investigation> { + const response = await soClient.find<StoredInvestigation>({ + type: SO_INVESTIGATION_TYPE, + page: 1, + perPage: 1, + filter: `investigation.attributes.id:(${id})`, + }); + + if (response.total === 0) { + throw new Error(`Investigation [${id}] not found`); + } + + const investigation = toInvestigation(response.saved_objects[0].attributes); + if (investigation === undefined) { + throw new Error('Invalid stored Investigation'); + } + + return investigation; + }, + + async deleteById(id: string): Promise<void> { + const response = await soClient.find<StoredInvestigation>({ + type: SO_INVESTIGATION_TYPE, + page: 1, + perPage: 1, + filter: `investigation.attributes.id:(${id})`, + }); + + if (response.total === 0) { + throw new Error(`Investigation [${id}] not found`); + } + + await soClient.delete(SO_INVESTIGATION_TYPE, response.saved_objects[0].id); + }, + + async search(pagination: Pagination): Promise<Paginated<Investigation>> { + const response = await soClient.find<StoredInvestigation>({ + type: SO_INVESTIGATION_TYPE, + page: pagination.page, + perPage: pagination.perPage, + }); + + return { + total: response.total, + perPage: response.per_page, + page: response.page, + results: response.saved_objects + .map((savedObject) => toInvestigation(savedObject.attributes)) + .filter((investigation) => investigation !== undefined) as Investigation[], + }; + }, + }; +} diff --git a/x-pack/plugins/observability_solution/investigate_app/tsconfig.json b/x-pack/plugins/observability_solution/investigate_app/tsconfig.json index 7ad57bf2c01a7..b4b586601ebaa 100644 --- a/x-pack/plugins/observability_solution/investigate_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/investigate_app/tsconfig.json @@ -47,10 +47,10 @@ "@kbn/unified-search-plugin", "@kbn/es-query", "@kbn/server-route-repository", - "@kbn/management-settings-ids", "@kbn/security-plugin", "@kbn/ui-actions-plugin", "@kbn/esql-datagrid", "@kbn/server-route-repository-utils", + "@kbn/core-saved-objects-server", ], } diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts b/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts index 83acb8bcfff15..c0caaa846f56e 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/common/constants.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const DEFAULT_LOG_SOURCES = ['logs-*-*']; +export const DEFAULT_LOG_SOURCES = ['logs-*-*,logs-*,filebeat-*,kibana_sample_data_logs*']; diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/log_sources_service.mocks.ts b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/log_sources_service.mocks.ts new file mode 100644 index 0000000000000..3f1f8b9db5979 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/log_sources_service.mocks.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 { LogSource, LogSourcesService } from './types'; + +const LOG_SOURCES: LogSource[] = [{ indexPattern: 'logs-*-*' }]; +export const createLogSourcesServiceMock = ( + logSources: LogSource[] = LOG_SOURCES +): LogSourcesService => { + let sources = logSources; + return { + async getLogSources() { + return Promise.resolve(sources); + }, + async setLogSources(nextLogSources: LogSource[]) { + sources = nextLogSources; + return Promise.resolve(); + }, + }; +}; diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/types.ts b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/types.ts new file mode 100644 index 0000000000000..0d4cb51051237 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/common/services/log_sources_service/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. + */ + +export interface LogSource { + indexPattern: string; +} + +export interface LogSourcesService { + getLogSources: () => Promise<LogSource[]>; + setLogSources: (sources: LogSource[]) => Promise<void>; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/types.ts b/x-pack/plugins/observability_solution/logs_data_access/common/types.ts index d021617f294ae..0600b162e1b3d 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/common/types.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/common/types.ts @@ -5,6 +5,4 @@ * 2.0. */ -export interface LogSource { - indexPattern: string; -} +export * from './services/log_sources_service/types'; diff --git a/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts b/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts index 500011231ee38..97259784971c1 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/common/ui_settings.ts @@ -23,7 +23,7 @@ export const uiSettings: Record<string, UiSettingsParams> = { value: DEFAULT_LOG_SOURCES, description: i18n.translate('xpack.logsDataAccess.logSourcesDescription', { defaultMessage: - 'Sources to be used for logs data. If the data contained in these indices is not logs data, you may experience degraded functionality.', + 'Sources to be used for logs data. If the data contained in these indices is not logs data, you may experience degraded functionality. Changes to this setting can potentially impact the sources queried in Log Threshold rules.', }), type: 'array', schema: schema.arrayOf(schema.string()), diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/index.ts b/x-pack/plugins/observability_solution/logs_data_access/public/index.ts index ed4a2be8a1b09..bdb45d3d0a490 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/public/index.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/public/index.ts @@ -11,6 +11,9 @@ import { LogsDataAccessPluginSetup, LogsDataAccessPluginStart, } from './plugin'; + +export type { LogsDataAccessPluginSetup, LogsDataAccessPluginStart }; + import { LogsDataAccessPluginSetupDeps, LogsDataAccessPluginStartDeps } from './types'; export const plugin: PluginInitializer< diff --git a/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts index 3fd4674ea5509..a75bbd65c26e3 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/public/services/log_sources_service/index.ts @@ -6,23 +6,24 @@ */ import { OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID } from '@kbn/management-settings-ids'; -import { LogSource } from '../../../common/types'; +import { LogSource, LogSourcesService } from '../../../common/services/log_sources_service/types'; import { RegisterServicesParams } from '../register_services'; -export function createLogSourcesService(params: RegisterServicesParams) { +export function createLogSourcesService(params: RegisterServicesParams): LogSourcesService { const { uiSettings } = params.deps; return { - getLogSources: (): LogSource[] => { + getLogSources: async () => { const logSources = uiSettings.get<string[]>(OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID); return logSources.map((logSource) => ({ indexPattern: logSource, })); }, setLogSources: async (sources: LogSource[]) => { - return await uiSettings.set( + await uiSettings.set( OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, sources.map((source) => source.indexPattern) ); + return; }, }; } diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts index 1d80171e7d0b0..ab8ae16ba4455 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_error_rate_timeseries/get_logs_error_rate_timeseries.ts @@ -7,10 +7,10 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import type { AggregationOptionsByType, AggregationResultOf } from '@kbn/es-types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { existsQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { estypes } from '@elastic/elasticsearch'; import { getBucketSizeFromTimeRangeAndBucketCount, getLogErrorRate } from '../../utils'; import { LOG_LEVEL } from '../../es_fields'; +import { existsQuery, kqlQuery } from '../../utils/es_queries'; export interface LogsErrorRateTimeseries { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts index 49b2d9cb578fb..3341170832a39 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/get_logs_rate_timeseries/get_logs_rate_timeseries.ts @@ -7,10 +7,10 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import type { AggregationOptionsByType, AggregationResultOf } from '@kbn/es-types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { existsQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { estypes } from '@elastic/elasticsearch'; import { getBucketSizeFromTimeRangeAndBucketCount } from '../../utils'; import { LOG_LEVEL } from '../../es_fields'; +import { existsQuery, kqlQuery } from '../../utils/es_queries'; export interface LogsRateTimeseries { esClient: ElasticsearchClient; diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts index c6075d1d20834..e8907d7537932 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/log_sources_service/index.ts @@ -5,31 +5,40 @@ * 2.0. */ -import { KibanaRequest } from '@kbn/core-http-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID } from '@kbn/management-settings-ids'; -import { LogSource } from '../../../common/types'; +import { LogSource, LogSourcesService } from '../../../common/services/log_sources_service/types'; import { RegisterServicesParams } from '../register_services'; -export function createGetLogSourcesService(params: RegisterServicesParams) { - return async (request: KibanaRequest) => { - const { savedObjects, uiSettings } = params.deps; - const soClient = savedObjects.getScopedClient(request); - const uiSettingsClient = uiSettings.asScopedToClient(soClient); - return { - getLogSources: async (): Promise<LogSource[]> => { - const logSources = await uiSettingsClient.get<string[]>( - OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID - ); - return logSources.map((logSource) => ({ - indexPattern: logSource, - })); - }, - setLogSources: async (sources: LogSource[]) => { - return await uiSettingsClient.set( - OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, - sources.map((source) => source.indexPattern) - ); - }, - }; +export function createLogSourcesServiceFactory(params: RegisterServicesParams) { + return { + async getLogSourcesService( + savedObjectsClient: SavedObjectsClientContract + ): Promise<LogSourcesService> { + const { uiSettings } = params.deps; + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + return { + getLogSources: async () => { + const logSources = await uiSettingsClient.get<string[]>( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID + ); + return logSources.map((logSource) => ({ + indexPattern: logSource, + })); + }, + setLogSources: async (sources: LogSource[]) => { + return await uiSettingsClient.set( + OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID, + sources.map((source) => source.indexPattern) + ); + }, + }; + }, + async getScopedLogSourcesService(request: KibanaRequest): Promise<LogSourcesService> { + const { savedObjects } = params.deps; + const soClient = savedObjects.getScopedClient(request); + return this.getLogSourcesService(soClient); + }, }; } diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts b/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts index 4756bb17f25b1..98c0c95530672 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts +++ b/x-pack/plugins/observability_solution/logs_data_access/server/services/register_services.ts @@ -11,7 +11,7 @@ import { Logger } from '@kbn/logging'; import { createGetLogsRateTimeseries } from './get_logs_rate_timeseries/get_logs_rate_timeseries'; import { createGetLogErrorRateTimeseries } from './get_logs_error_rate_timeseries/get_logs_error_rate_timeseries'; import { createGetLogsRatesService } from './get_logs_rates_service'; -import { createGetLogSourcesService } from './log_sources_service'; +import { createLogSourcesServiceFactory } from './log_sources_service'; export interface RegisterServicesParams { logger: Logger; @@ -26,6 +26,6 @@ export function registerServices(params: RegisterServicesParams) { getLogsRatesService: createGetLogsRatesService(), getLogsRateTimeseries: createGetLogsRateTimeseries(), getLogsErrorRateTimeseries: createGetLogErrorRateTimeseries(), - getLogSourcesService: createGetLogSourcesService(params), + logSourcesServiceFactory: createLogSourcesServiceFactory(params), }; } diff --git a/x-pack/plugins/observability_solution/logs_data_access/server/utils/es_queries.ts b/x-pack/plugins/observability_solution/logs_data_access/server/utils/es_queries.ts new file mode 100644 index 0000000000000..282b21af495c9 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_data_access/server/utils/es_queries.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 { estypes } from '@elastic/elasticsearch'; +import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; + +export function existsQuery(field: string): QueryDslQueryContainer[] { + return [{ exists: { field } }]; +} + +export function kqlQuery(kql?: string): estypes.QueryDslQueryContainer[] { + if (!kql) { + return []; + } + + const ast = fromKueryExpression(kql); + return [toElasticsearchQuery(ast)]; +} diff --git a/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json b/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json index 09a57dea36e49..45c96d862d9ff 100644 --- a/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_data_access/tsconfig.json @@ -9,7 +9,6 @@ "@kbn/core", "@kbn/data-plugin", "@kbn/data-views-plugin", - "@kbn/observability-plugin", "@kbn/calculate-auto", "@kbn/es-types", "@kbn/core-http-server", @@ -20,6 +19,8 @@ "@kbn/core-saved-objects-server", "@kbn/core-ui-settings-server", "@kbn/core-ui-settings-browser", - "@kbn/logging" + "@kbn/logging", + "@kbn/core-saved-objects-api-server", + "@kbn/es-query" ] } diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx index 577e068483427..826fcdab65915 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/components/common/translations.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; export const contentLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.content', { @@ -21,17 +20,6 @@ export const resourceLabel = i18n.translate( } ); -export const actionsLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.actions', { - defaultMessage: 'Actions', -}); - -export const actionsLabelLowerCase = i18n.translate( - 'xpack.logsExplorer.dataTable.header.popover.actions.lowercase', - { - defaultMessage: 'actions', - } -); - export const actionFilterForText = (text: string) => i18n.translate('xpack.logsExplorer.flyoutDetail.value.hover.filterFor', { defaultMessage: 'Filter for this {value}', @@ -109,35 +97,18 @@ export const resourceHeaderTooltipParagraph = i18n.translate( } ); -export const actionsHeaderTooltipParagraph = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph', +export const actionsHeaderAriaLabelDegradedAction = i18n.translate( + 'xpack.logsExplorer.dataTable.controlColumnHeader.degradedDocArialLabel', { - defaultMessage: 'Fields that provide actionable information, such as:', + defaultMessage: 'Access to degraded docs', } ); -export const actionsHeaderTooltipExpandAction = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.expand', - { defaultMessage: 'Expand log details' } -); - -export const actionsHeaderTooltipDegradedAction = ( - <FormattedMessage - id="xpack.logsExplorer.dataTable.controlColumn.actions.button.degradedDoc" - defaultMessage="Access to degraded doc with {ignoredProperty} field" - values={{ - ignoredProperty: ( - <EuiCode language="json" transparentBackground> - _ignored - </EuiCode> - ), - }} - /> -); - -export const actionsHeaderTooltipStacktraceAction = i18n.translate( - 'xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace', - { defaultMessage: 'Access to available stacktraces based on:' } +export const actionsHeaderAriaLabelStacktraceAction = i18n.translate( + 'xpack.logsExplorer.dataTable.controlColumnHeader.stacktraceArialLabel', + { + defaultMessage: 'Access to available stacktraces', + } ); export const degradedDocButtonLabelWhenPresent = i18n.translate( diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.tsx deleted file mode 100644 index c33c514bf3789..0000000000000 --- a/x-pack/plugins/observability_solution/logs_explorer/public/components/virtual_columns/column_tooltips/actions_column_tooltip.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 { css } from '@emotion/react'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { - actionsHeaderTooltipExpandAction, - actionsHeaderTooltipDegradedAction, - actionsHeaderTooltipParagraph, - actionsHeaderTooltipStacktraceAction, - actionsLabel, - actionsLabelLowerCase, -} from '../../common/translations'; -import { TooltipButton } from './tooltip_button'; -import * as constants from '../../../../common/constants'; -import { FieldWithToken } from './field_with_token'; - -const spacingCSS = css` - margin-bottom: ${euiThemeVars.euiSizeS}; -`; - -export const ActionsColumnTooltip = () => { - return ( - <TooltipButton displayText={actionsLabelLowerCase} popoverTitle={actionsLabel}> - <div style={{ width: '230px' }}> - <EuiText size="s" css={spacingCSS}> - <p>{actionsHeaderTooltipParagraph}</p> - </EuiText> - <EuiFlexGroup - responsive={false} - alignItems="baseline" - justifyContent="flexStart" - gutterSize="s" - css={spacingCSS} - > - <EuiFlexItem grow={false}> - <EuiIcon type="expand" size="s" /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <p>{actionsHeaderTooltipExpandAction}</p> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexGroup - responsive={false} - alignItems="baseline" - justifyContent="flexStart" - gutterSize="s" - css={spacingCSS} - > - <EuiFlexItem grow={false}> - <EuiIcon type="indexClose" size="s" color="danger" /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <p>{actionsHeaderTooltipDegradedAction}</p> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - <EuiFlexGroup - responsive={false} - alignItems="baseline" - justifyContent="flexStart" - gutterSize="s" - css={spacingCSS} - > - <EuiFlexItem grow={false}> - <EuiIcon type="apmTrace" size="s" /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <p>{actionsHeaderTooltipStacktraceAction}</p> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - <div style={{ marginLeft: '15px' }}> - {[ - constants.ERROR_STACK_TRACE, - constants.ERROR_EXCEPTION_STACKTRACE, - constants.ERROR_LOG_STACKTRACE, - ].map((field) => ( - <FieldWithToken field={field} key={field} /> - ))} - </div> - </div> - </TooltipButton> - ); -}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx index 43edee4cf73af..89bc38482c803 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/custom_control_column.tsx @@ -5,19 +5,16 @@ * 2.0. */ -import React, { ComponentClass } from 'react'; -import { - OPEN_DETAILS, - SELECT_ROW, - type ControlColumnsProps, - DataTableRowControl, -} from '@kbn/unified-data-table'; -import { EuiButtonIcon, EuiDataGridCellValueElementProps, EuiToolTip } from '@elastic/eui'; -import type { DataTableRecord } from '@kbn/discover-utils/src/types'; -import { useActor } from '@xstate/react'; +import React from 'react'; import { LogDocument } from '@kbn/discover-utils/src'; -import { LogsExplorerControllerStateService } from '../state_machines/logs_explorer_controller'; +import type { + UnifiedDataTableProps, + RowControlComponent, + RowControlRowProps, +} from '@kbn/unified-data-table'; import { + actionsHeaderAriaLabelDegradedAction, + actionsHeaderAriaLabelStacktraceAction, degradedDocButtonLabelWhenNotPresent, degradedDocButtonLabelWhenPresent, stacktraceAvailableControlButton, @@ -25,122 +22,79 @@ import { } from '../components/common/translations'; import * as constants from '../../common/constants'; import { getStacktraceFields } from '../utils/get_stack_trace'; -import { ActionsColumnTooltip } from '../components/virtual_columns/column_tooltips/actions_column_tooltip'; - -const ConnectedDegradedDocs = ({ - rowIndex, - service, -}: { - rowIndex: number; - service: LogsExplorerControllerStateService; -}) => { - const [state] = useActor(service); - if (state.matches('initialized') && state.context.rows[rowIndex]) { - return <DegradedDocs row={state.context.rows[rowIndex]} rowIndex={rowIndex} />; - } - - return null; -}; -const ConnectedStacktraceDocs = ({ - rowIndex, - service, +const DegradedDocs = ({ + Control, + rowProps: { record }, }: { - rowIndex: number; - service: LogsExplorerControllerStateService; + Control: RowControlComponent; + rowProps: RowControlRowProps; }) => { - const [state] = useActor(service); - if (state.matches('initialized') && state.context.rows[rowIndex]) { - return <Stacktrace row={state.context.rows[rowIndex]} rowIndex={rowIndex} />; - } - - return null; -}; - -const DegradedDocs = ({ row, rowIndex }: { row: DataTableRecord; rowIndex: number }) => { - const isDegradedDocumentExists = constants.DEGRADED_DOCS_FIELD in row.raw; + const isDegradedDocumentExists = constants.DEGRADED_DOCS_FIELD in record.raw; return isDegradedDocumentExists ? ( - <DataTableRowControl> - <EuiToolTip content={degradedDocButtonLabelWhenPresent} delay="long"> - <EuiButtonIcon - id={`degradedDocExists_${rowIndex}`} - size="xs" - iconSize="s" - data-test-subj={'docTableDegradedDocExist'} - color={'danger'} - aria-label={degradedDocButtonLabelWhenPresent} - iconType={'indexClose'} - /> - </EuiToolTip> - </DataTableRowControl> + <Control + data-test-subj="docTableDegradedDocExist" + color="danger" + label={degradedDocButtonLabelWhenPresent} + iconType="indexClose" + onClick={undefined} + /> ) : ( - <DataTableRowControl> - <EuiToolTip content={degradedDocButtonLabelWhenNotPresent} delay="long"> - <EuiButtonIcon - id={`degradedDocExists_${rowIndex}`} - size="xs" - iconSize="s" - data-test-subj={'docTableDegradedDocDoesNotExist'} - color={'text'} - iconType={'pagesSelect'} - aria-label={degradedDocButtonLabelWhenNotPresent} - /> - </EuiToolTip> - </DataTableRowControl> + <Control + data-test-subj="docTableDegradedDocDoesNotExist" + color="text" + label={degradedDocButtonLabelWhenNotPresent} + iconType="indexClose" + onClick={undefined} + /> ); }; -const Stacktrace = ({ row, rowIndex }: { row: DataTableRecord; rowIndex: number }) => { - const stacktrace = getStacktraceFields(row as LogDocument); +const Stacktrace = ({ + Control, + rowProps: { record }, +}: { + Control: RowControlComponent; + rowProps: RowControlRowProps; +}) => { + const stacktrace = getStacktraceFields(record as LogDocument); const hasValue = Object.values(stacktrace).some((value) => value); - return ( - <DataTableRowControl> - <EuiToolTip - content={hasValue ? stacktraceAvailableControlButton : stacktraceNotAvailableControlButton} - delay="long" - > - <EuiButtonIcon - id={`stacktrace_${rowIndex}`} - size="xs" - iconSize="s" - data-test-subj={hasValue ? 'docTableStacktraceExist' : 'docTableStacktraceDoesNotExist'} - color={'text'} - iconType={'apmTrace'} - aria-label={ - hasValue ? stacktraceAvailableControlButton : stacktraceNotAvailableControlButton - } - disabled={!hasValue} - /> - </EuiToolTip> - </DataTableRowControl> + return hasValue ? ( + <Control + data-test-subj="docTableStacktraceExist" + label={stacktraceAvailableControlButton} + iconType="apmTrace" + onClick={undefined} + /> + ) : ( + <Control + disabled + data-test-subj="docTableStacktraceDoesNotExist" + label={stacktraceNotAvailableControlButton} + iconType="apmTrace" + onClick={undefined} + /> ); }; -export const createCustomControlColumnsConfiguration = - (service: LogsExplorerControllerStateService) => - ({ controlColumns }: ControlColumnsProps) => { - const checkBoxColumn = controlColumns[SELECT_ROW]; - const openDetails = controlColumns[OPEN_DETAILS]; - const ExpandButton = - openDetails.rowCellRender as ComponentClass<EuiDataGridCellValueElementProps>; - const actionsColumn = { - id: 'actionsColumn', - width: constants.ACTIONS_COLUMN_WIDTH, - headerCellRender: ActionsColumnTooltip, - rowCellRender: ({ rowIndex, setCellProps, ...rest }: EuiDataGridCellValueElementProps) => { - return ( - <span> - <ExpandButton rowIndex={rowIndex} setCellProps={setCellProps} {...rest} /> - <ConnectedDegradedDocs rowIndex={rowIndex} service={service} /> - <ConnectedStacktraceDocs rowIndex={rowIndex} service={service} /> - </span> - ); +export const getRowAdditionalControlColumns = + (): UnifiedDataTableProps['rowAdditionalLeadingControls'] => { + return [ + { + id: 'connectedDegradedDocs', + headerAriaLabel: actionsHeaderAriaLabelDegradedAction, + renderControl: (Control, rowProps) => { + return <DegradedDocs Control={Control} rowProps={rowProps} />; + }, }, - }; - - return { - leadingControlColumns: [checkBoxColumn, actionsColumn], - }; + { + id: 'connectedStacktraceDocs', + headerAriaLabel: actionsHeaderAriaLabelStacktraceAction, + renderControl: (Control, rowProps) => { + return <Stacktrace Control={Control} rowProps={rowProps} />; + }, + }, + ]; }; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx index 7b193f4aafff8..557cbe4dd2728 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx +++ b/x-pack/plugins/observability_solution/logs_explorer/public/customizations/logs_explorer_profile.tsx @@ -82,8 +82,8 @@ export const createLogsExplorerProfileCustomizations = customizations.set({ id: 'data_table', logsEnabled: true, - customControlColumnsConfiguration: await import('./custom_control_column').then((module) => - module.createCustomControlColumnsConfiguration(service) + rowAdditionalLeadingControls: await import('./custom_control_column').then((module) => + module.getRowAdditionalControlColumns() ), }); diff --git a/x-pack/plugins/observability_solution/logs_shared/common/index.ts b/x-pack/plugins/observability_solution/logs_shared/common/index.ts index f6b1e9ea27e43..104b68ce2fddf 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/index.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/index.ts @@ -17,12 +17,14 @@ export { logViewReferenceRT, persistedLogViewReferenceRT, defaultLogViewAttributes, + logSourcesKibanaAdvancedSettingRT, } from './log_views'; // LogView types export type { LogDataViewReference, LogIndexNameReference, + LogSourcesKibanaAdvancedSettingReference, LogIndexReference, LogView, LogViewAttributes, diff --git a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts index 8c09f16e3b53e..568a25ecef1d7 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.mock.ts @@ -11,6 +11,7 @@ import { defaultLogViewsStaticConfig } from './defaults'; import { ResolvedLogView, resolveLogView } from './resolved_log_view'; import { LogViewAttributes } from './types'; import { DataViewSpec } from '@kbn/data-views-plugin/common'; +import { createLogSourcesServiceMock } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/log_sources_service.mocks'; export const createResolvedLogViewMock = ( resolvedLogViewOverrides: Partial<ResolvedLogView> = {} @@ -63,5 +64,6 @@ export const createResolvedLogViewMockFromAttributes = (logViewAttributes: LogVi spec, }), } as unknown as DataViewsContract, + createLogSourcesServiceMock(), defaultLogViewsStaticConfig ); diff --git a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts index b9419fbd51f22..1521aa67e3d92 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/log_views/resolved_log_view.ts @@ -7,6 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { DataView, DataViewsContract, FieldSpec } from '@kbn/data-views-plugin/common'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/types'; import { TIEBREAKER_FIELD, TIMESTAMP_FIELD } from '../constants'; import { defaultLogViewsStaticConfig } from './defaults'; import { ResolveLogViewError } from './errors'; @@ -31,12 +32,20 @@ export const resolveLogView = ( logViewId: string, logViewAttributes: LogViewAttributes, dataViewsService: DataViewsContract, + logSourcesService: LogSourcesService, config: LogViewsStaticConfig ): Promise<ResolvedLogView> => { if (logViewAttributes.logIndices.type === 'index_name') { return resolveLegacyReference(logViewId, logViewAttributes, dataViewsService, config); - } else { + } else if (logViewAttributes.logIndices.type === 'data_view') { return resolveDataViewReference(logViewAttributes, dataViewsService); + } else { + return resolveKibanaAdvancedSettingReference( + logViewId, + logViewAttributes, + dataViewsService, + logSourcesService + ); } }; @@ -110,6 +119,52 @@ const resolveDataViewReference = async ( }; }; +const resolveKibanaAdvancedSettingReference = async ( + logViewId: string, + logViewAttributes: LogViewAttributes, + dataViewsService: DataViewsContract, + logSourcesService: LogSourcesService +): Promise<ResolvedLogView> => { + if (logViewAttributes.logIndices.type !== 'kibana_advanced_setting') { + throw new Error( + 'This function can only resolve references to the Log Sources Kibana advanced setting' + ); + } + + const indices = (await logSourcesService.getLogSources()) + .map((logSource) => logSource.indexPattern) + .join(','); + + const dataViewReference = await dataViewsService + .create( + { + id: `log-view-${logViewId}`, + name: logViewAttributes.name, + title: indices, + timeFieldName: TIMESTAMP_FIELD, + allowNoIndex: true, + }, + false, + false + ) + .catch((error) => { + throw new ResolveLogViewError(`Failed to create Data View reference: ${error}`, error); + }); + + return { + indices, + timestampField: TIMESTAMP_FIELD, + tiebreakerField: TIEBREAKER_FIELD, + messageField: ['message'], + fields: dataViewReference.fields, + runtimeMappings: {}, + columns: logViewAttributes.logColumns, + name: logViewAttributes.name, + description: logViewAttributes.description, + dataViewReference, + }; +}; + // this might take other sources of runtime fields into account in the future const resolveRuntimeMappings = (dataView: DataView): estypes.MappingRuntimeFields => { return dataView.getRuntimeMappings(); diff --git a/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts b/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts index f94601f9e0f84..6cc8d60191a34 100644 --- a/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/common/log_views/types.ts @@ -38,7 +38,20 @@ export const logIndexNameReferenceRT = rt.type({ }); export type LogIndexNameReference = rt.TypeOf<typeof logIndexNameReferenceRT>; -export const logIndexReferenceRT = rt.union([logDataViewReferenceRT, logIndexNameReferenceRT]); +// Kibana advanced setting +export const logSourcesKibanaAdvancedSettingRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + +export type LogSourcesKibanaAdvancedSettingReference = rt.TypeOf< + typeof logSourcesKibanaAdvancedSettingRT +>; + +export const logIndexReferenceRT = rt.union([ + logDataViewReferenceRT, + logIndexNameReferenceRT, + logSourcesKibanaAdvancedSettingRT, +]); export type LogIndexReference = rt.TypeOf<typeof logIndexReferenceRT>; const logViewCommonColumnConfigurationRT = rt.strict({ diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc index 7e79614b56e5a..ea93fd326dac7 100644 --- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc +++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc @@ -14,7 +14,8 @@ "discoverShared", "usageCollection", "observabilityShared", - "share" + "share", + "logsDataAccess" ], "optionalPlugins": [ "observabilityAIAssistant", diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx index 98be3f7567880..f0c9c411249a9 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx @@ -5,14 +5,17 @@ * 2.0. */ +import type { HttpStart } from '@kbn/core-http-browser'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { buildEsQuery, Filter, Query } from '@kbn/es-query'; -import { JsonValue } from '@kbn/utility-types'; -import React, { useCallback, useEffect, useMemo } from 'react'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import { JsonValue } from '@kbn/utility-types'; import { noop } from 'lodash'; +import React, { useCallback, useEffect, useMemo } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; +import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import { LogEntryCursor } from '../../../common/log_entry'; import { defaultLogViewsStaticConfig, LogViewReference } from '../../../common/log_views'; import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream'; @@ -20,12 +23,15 @@ import { useLogView } from '../../hooks/use_log_view'; import { LogViewsClient } from '../../services/log_views'; import { LogColumnRenderConfiguration } from '../../utils/log_column_render_configuration'; import { useKibanaQuerySettings } from '../../utils/use_kibana_query_settings'; +import { useLogEntryFlyout } from '../logging/log_entry_flyout'; import { ScrollableLogTextStreamView } from '../logging/log_text_stream'; import { LogStreamErrorBoundary } from './log_stream_error_boundary'; -import { useLogEntryFlyout } from '../logging/log_entry_flyout'; interface LogStreamPluginDeps { data: DataPublicPluginStart; + logsDataAccess: LogsDataAccessPluginStart; + http: HttpStart; + share: SharePluginStart; } const PAGE_THRESHOLD = 2; @@ -109,9 +115,9 @@ export const LogStreamContent = ({ ); const { - services: { http, data }, + services: { http, data, share, logsDataAccess }, } = useKibana<LogStreamPluginDeps>(); - if (http == null || data == null) { + if (http == null || data == null || share == null || logsDataAccess == null) { throw new Error( `<LogStream /> cannot access kibana core services. @@ -126,8 +132,15 @@ Read more at https://github.com/elastic/kibana/blob/main/src/plugins/kibana_reac const kibanaQuerySettings = useKibanaQuerySettings(); const logViews = useMemo( - () => new LogViewsClient(data.dataViews, http, data.search.search, defaultLogViewsStaticConfig), - [data.dataViews, data.search.search, http] + () => + new LogViewsClient( + data.dataViews, + logsDataAccess.services.logSourcesService, + http, + data.search.search, + defaultLogViewsStaticConfig + ), + [data.dataViews, data.search.search, http, logsDataAccess.services.logSourcesService] ); const { diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx index 32ddf912bf740..ffb1881122540 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_entry_flyout/log_entry_flyout.tsx @@ -42,7 +42,7 @@ export interface LogEntryFlyoutProps { export const useLogEntryFlyout = (logViewReference: LogViewReference) => { const flyoutRef = useRef<OverlayRef>(); const { - services: { http, data, uiSettings, application, observabilityAIAssistant }, + services: { http, data, share, uiSettings, application, observabilityAIAssistant }, overlays: { openFlyout }, } = useKibanaContextForPlugin(); @@ -55,6 +55,7 @@ export const useLogEntryFlyout = (logViewReference: LogViewReference) => { const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ http, data, + share, uiSettings, application, observabilityAIAssistant, @@ -71,14 +72,15 @@ export const useLogEntryFlyout = (logViewReference: LogViewReference) => { ); }, [ - http, - data, - uiSettings, application, - openFlyout, - logViewReference, closeLogEntryFlyout, + data, + http, + logViewReference, observabilityAIAssistant, + openFlyout, + share, + uiSettings, ] ); diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/column_headers.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/column_headers.tsx index 88161e794c37e..e1478123ab29b 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/column_headers.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/column_headers.tsx @@ -108,14 +108,12 @@ export const LogColumnHeader: FC< 'data-test-subj'?: string; }> > = ({ children, columnWidth, 'data-test-subj': dataTestSubj }) => ( - <LogColumnHeaderWrapper data-test-subj={dataTestSubj} {...columnWidth}> + <LogColumnHeaderWrapper data-test-subj={dataTestSubj} {...columnWidth} role="columnheader"> <LogColumnHeaderContent>{children}</LogColumnHeaderContent> </LogColumnHeaderWrapper> ); -const LogColumnHeaderWrapper = euiStyled(LogEntryColumn).attrs((props) => ({ - role: props.role ?? 'columnheader', -}))` +const LogColumnHeaderWrapper = euiStyled(LogEntryColumn)` align-items: center; display: flex; flex-direction: row; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/log_entry_column.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/log_entry_column.tsx index a62f08903764a..fb7560a4e93b8 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/log_entry_column.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logging/log_text_stream/log_entry_column.tsx @@ -30,8 +30,8 @@ export interface LogEntryColumnProps { shrinkWeight: number; } -export const LogEntryColumn = euiStyled.div.attrs(() => ({ - role: 'cell', +export const LogEntryColumn = euiStyled.div.attrs((props) => ({ + role: props.role ?? 'cell', }))<LogEntryColumnProps>` align-items: stretch; display: flex; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts index 4655a7a62f087..d6f4ac81fe266 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts @@ -52,11 +52,12 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { public start(core: CoreStart, plugins: LogsSharedClientStartDeps) { const { http } = core; - const { data, dataViews, discoverShared, observabilityAIAssistant } = plugins; + const { data, dataViews, discoverShared, observabilityAIAssistant, logsDataAccess } = plugins; const logViews = this.logViews.start({ http, dataViews, + logSourcesService: logsDataAccess.services.logSourcesService, search: data.search, }); diff --git a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts index a53ac542c5f03..b1a71cea73cb1 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_client.ts @@ -10,6 +10,7 @@ import { HttpStart } from '@kbn/core/public'; import type { ISearchGeneric } from '@kbn/search-types'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; import { lastValueFrom } from 'rxjs'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { getLogViewResponsePayloadRT, putLogViewRequestPayloadRT } from '../../../common/http_api'; import { getLogViewUrl } from '../../../common/http_api/log_views'; import { @@ -31,6 +32,7 @@ import { ILogViewsClient } from './types'; export class LogViewsClient implements ILogViewsClient { constructor( private readonly dataViews: DataViewsContract, + private readonly logSourcesService: LogSourcesService, private readonly http: HttpStart, private readonly search: ISearchGeneric, private readonly config: LogViewsStaticConfig @@ -152,7 +154,13 @@ export class LogViewsClient implements ILogViewsClient { logViewId: string, logViewAttributes: LogViewAttributes ): Promise<ResolvedLogView> { - return await resolveLogView(logViewId, logViewAttributes, this.dataViews, this.config); + return await resolveLogView( + logViewId, + logViewAttributes, + this.dataViews, + this.logSourcesService, + this.config + ); } } diff --git a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts index 712196c95205c..66ddfde911176 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/log_views_service.ts @@ -20,8 +20,19 @@ export class LogViewsService { }; } - public start({ dataViews, http, search }: LogViewsServiceStartDeps): LogViewsServiceStart { - const client = new LogViewsClient(dataViews, http, search.search, this.logViewsStaticConfig); + public start({ + dataViews, + http, + search, + logSourcesService, + }: LogViewsServiceStartDeps): LogViewsServiceStart { + const client = new LogViewsClient( + dataViews, + logSourcesService, + http, + search.search, + this.logViewsStaticConfig + ); return { client, diff --git a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts index 58a504be789bc..a6d516f00669d 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/services/log_views/types.ts @@ -8,6 +8,7 @@ import { HttpStart } from '@kbn/core/public'; import { ISearchStart } from '@kbn/data-plugin/public'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { LogView, LogViewAttributes, @@ -29,6 +30,7 @@ export interface LogViewsServiceStartDeps { dataViews: DataViewsContract; http: HttpStart; search: ISearchStart; + logSourcesService: LogSourcesService; } export interface ILogViewsClient { diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts index 9f0d344294880..58b180ee8b6ef 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts @@ -9,6 +9,7 @@ import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/publ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; @@ -37,6 +38,7 @@ export interface LogsSharedClientStartDeps { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; discoverShared: DiscoverSharedPublicStart; + logsDataAccess: LogsDataAccessPluginStart; observabilityAIAssistant?: ObservabilityAIAssistantPublicStart; share: SharePluginStart; uiActions: UiActionsStart; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts index 2601167f2d988..f3cbfb57b09c4 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -172,10 +172,13 @@ export class LogsSharedLogEntriesDomain implements ILogsSharedLogEntriesDomain { params: LogEntriesParams, columnOverrides?: LogViewColumnConfiguration[] ): Promise<{ entries: LogEntry[]; hasMoreBefore?: boolean; hasMoreAfter?: boolean }> { - const [, , { logViews }] = await this.libs.getStartServices(); + const [, { logsDataAccess }, { logViews }] = await this.libs.getStartServices(); const { savedObjects, elasticsearch } = await requestContext.core; + const logSourcesService = logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + savedObjects.client + ); const resolvedLogView = await logViews - .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) + .getClient(savedObjects.client, elasticsearch.client.asCurrentUser, logSourcesService) .getResolvedLogView(logView); const columnDefinitions = columnOverrides ?? resolvedLogView.columns; @@ -232,11 +235,15 @@ export class LogsSharedLogEntriesDomain implements ILogsSharedLogEntriesDomain { bucketSize: number, filterQuery?: LogEntryQuery ): Promise<LogEntriesSummaryBucket[]> { - const [, , { logViews }] = await this.libs.getStartServices(); + const [, { logsDataAccess }, { logViews }] = await this.libs.getStartServices(); const { savedObjects, elasticsearch } = await requestContext.core; + const logSourcesService = logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + savedObjects.client + ); const resolvedLogView = await logViews - .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) + .getClient(savedObjects.client, elasticsearch.client.asCurrentUser, logSourcesService) .getResolvedLogView(logView); + const dateRangeBuckets = await this.adapter.getContainedLogSummaryBuckets( requestContext, resolvedLogView, @@ -257,11 +264,16 @@ export class LogsSharedLogEntriesDomain implements ILogsSharedLogEntriesDomain { highlightQueries: string[], filterQuery?: LogEntryQuery ): Promise<LogEntriesSummaryHighlightsBucket[][]> { - const [, , { logViews }] = await this.libs.getStartServices(); + const [, { logsDataAccess }, { logViews }] = await this.libs.getStartServices(); const { savedObjects, elasticsearch } = await requestContext.core; + const logSourcesService = logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + savedObjects.client + ); + const resolvedLogView = await logViews - .getClient(savedObjects.client, elasticsearch.client.asCurrentUser) + .getClient(savedObjects.client, elasticsearch.client.asCurrentUser, logSourcesService) .getResolvedLogView(logView); + const messageFormattingRules = compileFormattingRules( getBuiltinRules(resolvedLogView.messageField) ); diff --git a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts index 7e3fad84e4c30..6bc9560764a7b 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts @@ -97,6 +97,7 @@ export class LogsSharedPlugin const logViews = this.logViews.start({ savedObjects: core.savedObjects, dataViews: plugins.dataViews, + logsDataAccess: plugins.logsDataAccess, elasticsearch: core.elasticsearch, }); diff --git a/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts b/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts index fb8bf49781a2d..341e9bbed608b 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/saved_objects/log_view/types.ts @@ -19,9 +19,14 @@ export const logIndexNameSavedObjectReferenceRT = rt.type({ indexName: rt.string, }); +export const logSourcesKibanaAdvancedSettingSavedObjectRT = rt.type({ + type: rt.literal('kibana_advanced_setting'), +}); + export const logIndexSavedObjectReferenceRT = rt.union([ logDataViewSavedObjectReferenceRT, logIndexNameSavedObjectReferenceRT, + logSourcesKibanaAdvancedSettingSavedObjectRT, ]); const logViewSavedObjectCommonColumnConfigurationRT = rt.strict({ diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts index a1175432a4ac7..f6df48b22ba7e 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.test.ts @@ -22,6 +22,7 @@ import { logViewSavedObjectName, } from '../../saved_objects/log_view'; import { LogViewsClient } from './log_views_client'; +import { createLogSourcesServiceMock } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/log_sources_service.mocks'; describe('LogViewsClient class', () => { it('getLogView resolves the default id to a real saved object id if it exists', async () => { @@ -341,6 +342,7 @@ describe('LogViewsClient class', () => { const createLogViewsClient = () => { const logger = loggerMock.create(); const dataViews = dataViewsServiceMock; + const logSourcesService = createLogSourcesServiceMock(); const savedObjectsClient = savedObjectsClientMock.create(); const logViewFallbackHandler = jest.fn(); const internalLogViews = new Map<string, LogView>(); @@ -351,6 +353,7 @@ const createLogViewsClient = () => { const logViewsClient = new LogViewsClient( logger, Promise.resolve(dataViews), + Promise.resolve(logSourcesService), savedObjectsClient, logViewFallbackHandler, internalLogViews, diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts index 0b9bfe9febf4a..cea43f02b358d 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_client.ts @@ -13,6 +13,7 @@ import { SavedObjectsUtils, SavedObjectsErrorHelpers, } from '@kbn/core/server'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { defaultLogViewAttributes, defaultLogViewId, @@ -44,6 +45,7 @@ export class LogViewsClient implements ILogViewsClient { constructor( private readonly logger: Logger, private readonly dataViews: DataViewsService, + private readonly logSourcesService: Promise<LogSourcesService>, private readonly savedObjectsClient: SavedObjectsClientContract, private readonly logViewFallbackHandler: LogViewFallbackHandler, private readonly internalLogViews: Map<string, LogView>, @@ -117,7 +119,13 @@ export class LogViewsClient implements ILogViewsClient { logViewId: string, logViewAttributes: LogViewAttributes ): Promise<ResolvedLogView> { - return await resolveLogView(logViewId, logViewAttributes, await this.dataViews, this.config); + return await resolveLogView( + logViewId, + logViewAttributes, + await this.dataViews, + await this.logSourcesService, + this.config + ); } private async getSavedLogView(logViewId: string): Promise<LogView> { diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts index 295b1fd77452f..11fbe43a6b9cb 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.mock.ts @@ -15,8 +15,9 @@ export const createLogViewsServiceSetupMock = (): jest.Mocked<LogViewsServiceSet }); export const createLogViewsServiceStartMock = (): jest.Mocked<LogViewsServiceStart> => ({ - getClient: jest.fn((_savedObjectsClient: any, _elasticsearchClient: any) => - createLogViewsClientMock() + getClient: jest.fn( + (_savedObjectsClient: any, _elasticsearchClient: any, _logSourcesService: any) => + createLogViewsClientMock() ), getScopedClient: jest.fn((_request: any) => createLogViewsClientMock()), }); diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts index 5479c16dff411..2f429dc7612ff 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/log_views_service.ts @@ -11,6 +11,7 @@ import { Logger, SavedObjectsClientContract, } from '@kbn/core/server'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; import { defaultLogViewAttributes, defaultLogViewsStaticConfig, @@ -56,6 +57,7 @@ export class LogViewsService { public start({ dataViews, + logsDataAccess, elasticsearch, savedObjects, }: LogViewsServiceStartDeps): LogViewsServiceStart { @@ -65,11 +67,13 @@ export class LogViewsService { getClient( savedObjectsClient: SavedObjectsClientContract, elasticsearchClient: ElasticsearchClient, + logSourcesService: Promise<LogSourcesService>, request?: KibanaRequest ) { return new LogViewsClient( logger, dataViews.dataViewsServiceFactory(savedObjectsClient, elasticsearchClient, request), + logSourcesService, savedObjectsClient, logViewFallbackHandler, internalLogViews, @@ -79,8 +83,9 @@ export class LogViewsService { getScopedClient(request: KibanaRequest) { const savedObjectsClient = savedObjects.getScopedClient(request); const elasticsearchClient = elasticsearch.client.asScoped(request).asCurrentUser; - - return this.getClient(savedObjectsClient, elasticsearchClient, request); + const logSourcesService = + logsDataAccess.services.logSourcesServiceFactory.getScopedLogSourcesService(request); + return this.getClient(savedObjectsClient, elasticsearchClient, logSourcesService, request); }, }; } diff --git a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts index b0aa2784e4cd2..becf32881acf8 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/services/log_views/types.ts @@ -13,6 +13,8 @@ import { SavedObjectsServiceStart, } from '@kbn/core/server'; import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; +import { LogSourcesService } from '@kbn/logs-data-access-plugin/common/types'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; import { LogView, LogViewAttributes, @@ -23,6 +25,7 @@ import { export interface LogViewsServiceStartDeps { dataViews: DataViewsServerPluginStart; + logsDataAccess: LogsDataAccessPluginStart; elasticsearch: ElasticsearchServiceStart; savedObjects: SavedObjectsServiceStart; } @@ -45,6 +48,7 @@ export interface LogViewsServiceStart { getClient( savedObjectsClient: SavedObjectsClientContract, elasticsearchClient: ElasticsearchClient, + logSourcesService: Promise<LogSourcesService>, request?: KibanaRequest ): ILogViewsClient; getScopedClient(request: KibanaRequest): ILogViewsClient; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/types.ts b/x-pack/plugins/observability_solution/logs_shared/server/types.ts index 2e922eceeb183..73365ece21a14 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/types.ts @@ -11,6 +11,7 @@ import { PluginStart as DataPluginStart, } from '@kbn/data-plugin/server'; import { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; +import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; import { LogsSharedDomainLibs } from './lib/logs_shared_types'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; @@ -36,6 +37,7 @@ export interface LogsSharedServerPluginSetupDeps { export interface LogsSharedServerPluginStartDeps { data: DataPluginStart; dataViews: DataViewsPluginStart; + logsDataAccess: LogsDataAccessPluginStart; } export interface UsageCollector { diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json index d826cd78618c5..f1bb2527f9311 100644 --- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json @@ -41,5 +41,6 @@ "@kbn/react-kibana-context-theme", "@kbn/test-jest-helpers", "@kbn/router-utils", + "@kbn/logs-data-access-plugin", ] } diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts index e1d4f60bc9f74..d45fbfb5477f1 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/index.ts @@ -42,6 +42,7 @@ export const host: InventoryModel<typeof metrics> = { metrics, requiredMetrics: [ 'hostSystemOverview', + 'hostCpuUsageTotal', 'hostCpuUsage', 'hostLoad', 'hostMemoryUsage', @@ -54,5 +55,5 @@ export const host: InventoryModel<typeof metrics> = { ...awsRequiredMetrics, ...nginxRequireMetrics, ], - tooltipMetrics: ['cpu', 'memory', 'tx', 'rx'], + tooltipMetrics: ['cpuTotal', 'cpu', 'memory', 'txV2', 'rxV2'], }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts index fa75dc071666c..3ee08c028f8fb 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/cpu.ts @@ -80,7 +80,7 @@ export const cpuUsageUser: LensBaseLayer = { export const cpuUsage: LensBaseLayer = { label: CPU_USAGE_LABEL, - value: '(average(system.cpu.user.pct) + average(system.cpu.system.pct)) / max(system.cpu.cores)', + value: 'average(system.cpu.total.norm.pct)', format: 'percent', decimals: 0, }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/network.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/network.ts index d35beeb7469d0..9c45b276f3af9 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/network.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/formulas/network.ts @@ -12,20 +12,16 @@ export const rx: LensBaseLayer = { label: i18n.translate('xpack.metricsData.assetDetails.formulas.rx', { defaultMessage: 'Network Inbound (RX)', }), - value: - "average(host.network.ingress.bytes) * 8 / (max(metricset.period, kql='host.network.ingress.bytes: *') / 1000)", + value: 'sum(host.network.ingress.bytes) * 8', format: 'bits', decimals: 1, - normalizeByUnit: 's', }; export const tx: LensBaseLayer = { label: i18n.translate('xpack.metricsData.assetDetails.formulas.tx', { defaultMessage: 'Network Outbound (TX)', }), - value: - "average(host.network.egress.bytes) * 8 / (max(metricset.period, kql='host.network.egress.bytes: *') / 1000)", + value: 'sum(host.network.egress.bytes) * 8', format: 'bits', decimals: 1, - normalizeByUnit: 's', }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts index 6d2bc3381fdf1..5983b2a56f817 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/index.ts @@ -22,6 +22,6 @@ export const metrics: InventoryMetricsWithCharts<HostFormulas, HostCharts> = { snapshot, getFormulas: async () => await import('./formulas').then(({ formulas }) => formulas), getCharts: async () => await import('./charts').then(({ charts }) => charts), - defaultSnapshot: 'cpu', + defaultSnapshot: 'cpuTotal', defaultTimeRangeInSeconds: 3600, // 1 hour }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/cpu_total.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/cpu_total.ts new file mode 100644 index 0000000000000..f7b4661b65d99 --- /dev/null +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/cpu_total.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 { MetricsUIAggregation } from '../../../types'; + +export const cpuTotal: MetricsUIAggregation = { + cpuTotal: { + avg: { + field: 'system.cpu.total.norm.pct', + }, + }, +}; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts index 8cf567b40165e..4bf48e287fab7 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { cpuTotal } from './cpu_total'; import { cpu } from './cpu'; import { diskLatency } from './disk_latency'; import { diskSpaceUsage } from './disk_space_usage'; @@ -17,8 +18,11 @@ import { memoryTotal } from './memory_total'; import { normalizedLoad1m } from './normalized_load_1m'; import { rx } from './rx'; import { tx } from './tx'; +import { txV2 } from './tx_v2'; +import { rxV2 } from './rx_v2'; export const snapshot = { + cpuTotal, cpu, diskLatency, diskSpaceUsage, @@ -31,4 +35,6 @@ export const snapshot = { normalizedLoad1m, rx, tx, + rxV2, + txV2, }; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/rx_v2.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/rx_v2.ts new file mode 100644 index 0000000000000..3f8466010a518 --- /dev/null +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/rx_v2.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 { MetricsUIAggregation } from '../../../types'; +export const rxV2: MetricsUIAggregation = { + rx_sum: { + sum: { + field: 'host.network.ingress.bytes', + }, + }, + min_timestamp: { + min: { + field: '@timestamp', + }, + }, + max_timestamp: { + max: { + field: '@timestamp', + }, + }, + rxV2: { + bucket_script: { + buckets_path: { + value: 'rx_sum', + minTime: 'min_timestamp', + maxTime: 'max_timestamp', + }, + script: { + source: 'params.value / ((params.maxTime - params.minTime) / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, +}; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/tx_v2.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/tx_v2.ts new file mode 100644 index 0000000000000..100bd3d0bf306 --- /dev/null +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/host/metrics/snapshot/tx_v2.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 { MetricsUIAggregation } from '../../../types'; +export const txV2: MetricsUIAggregation = { + tx_sum: { + sum: { + field: 'host.network.egress.bytes', + }, + }, + min_timestamp: { + min: { + field: '@timestamp', + }, + }, + max_timestamp: { + max: { + field: '@timestamp', + }, + }, + txV2: { + bucket_script: { + buckets_path: { + value: 'tx_sum', + minTime: 'min_timestamp', + maxTime: 'max_timestamp', + }, + script: { + source: 'params.value / ((params.maxTime - params.minTime) / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, +}; diff --git a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts index 4967324be21fc..2ced976e564a8 100644 --- a/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts +++ b/x-pack/plugins/observability_solution/metrics_data_access/common/inventory_models/types.ts @@ -43,6 +43,7 @@ export type InventoryItemType = rt.TypeOf<typeof ItemTypeRT>; export const InventoryMetricRT = rt.keyof({ hostSystemOverview: null, + hostCpuUsageTotal: null, hostCpuUsage: null, hostFilesystem: null, hostK8sOverview: null, @@ -348,6 +349,7 @@ export type MetricsUIAggregation = rt.TypeOf<typeof MetricsUIAggregationRT>; export const SnapshotMetricTypeKeys = { count: null, + cpuTotal: null, cpu: null, diskLatency: null, diskSpaceUsage: null, @@ -358,6 +360,8 @@ export const SnapshotMetricTypeKeys = { normalizedLoad1m: null, tx: null, rx: null, + txV2: null, + rxV2: null, logRate: null, diskIOReadBytes: null, diskIOWriteBytes: null, @@ -385,7 +389,7 @@ export interface InventoryMetrics { tsvb: { [name: string]: TSVBMetricModelCreator }; snapshot: { [name: string]: MetricsUIAggregation | undefined }; defaultSnapshot: SnapshotMetricType; - /** This is used by the inventory view to calculate the appropriate amount of time for the metrics detail page. Some metris like awsS3 require multiple days where others like host only need an hour.*/ + /** This is used by the inventory view to calculate the appropriate amount of time for the metrics detail page. Some metrics like awsS3 require multiple days where others like host only need an hour.*/ defaultTimeRangeInSeconds: number; } diff --git a/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations_list_chart.tsx b/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations_list_chart.tsx index 71103299f0fe8..9a80528a0e703 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations_list_chart.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/annotations/annotations_list_chart.tsx @@ -25,6 +25,7 @@ import { i18n } from '@kbn/i18n'; import { parse } from '@kbn/datemath'; import { TooltipValue } from '@elastic/charts/dist/specs'; import moment from 'moment'; +import { useChartThemes } from '../../hooks/use_chart_themes'; import { AnnotationsPermissions } from '../../components/annotations/hooks/use_annotation_permissions'; import { createAnnotationPortal } from './create_annotation_btn'; import { useAnnotations } from '../../components/annotations/use_annotations'; @@ -76,6 +77,8 @@ export function AnnotationsListChart({ { x: domain.max, y: 1 }, ]; + const { baseTheme } = useChartThemes(); + return ( <> <InPortal node={createAnnotationPortal}> @@ -113,6 +116,7 @@ export function AnnotationsListChart({ onBrushEnd={brushEndListener} onAnnotationClick={onAnnotationClick} xDomain={domain} + baseTheme={baseTheme} theme={{ chartMargins: { top: 50, diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts index edd59274a5496..601811fa43f26 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.test.ts @@ -969,7 +969,7 @@ describe('The custom threshold alert type', () => { stateResult2 ); expect(stateResult3.missingGroups).toEqual([{ key: 'b', bucketKey: { groupBy0: 'b' } }]); - expect(mockedEvaluateRule.mock.calls[2][9]).toEqual([ + expect(mockedEvaluateRule.mock.calls[2][10]).toEqual([ { bucketKey: { groupBy0: 'b' }, key: 'b' }, ]); }); @@ -2958,7 +2958,7 @@ describe('The custom threshold alert type', () => { stateResult2 ); expect(stateResult3.missingGroups).toEqual([{ key: 'b', bucketKey: { groupBy0: 'b' } }]); - expect(mockedEvaluateRule.mock.calls[2][9]).toEqual([ + expect(mockedEvaluateRule.mock.calls[2][10]).toEqual([ { bucketKey: { groupBy0: 'b' }, key: 'b' }, ]); }); diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 6578b91bf89c9..32d4c9979d52f 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -18,10 +18,11 @@ import { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; import { IBasePath, Logger } from '@kbn/core/server'; import { AlertsClientError, RuleExecutorOptions } from '@kbn/alerting-plugin/server'; import { getEcsGroups } from '@kbn/observability-alerting-rule-utils'; -import { getEvaluationValues, getThreshold } from './lib/get_values'; +import { getEsQueryConfig } from '../../../utils/get_es_query_config'; import { AlertsLocatorParams, getAlertDetailsUrl } from '../../../../common'; import { getViewInAppUrl } from '../../../../common/custom_threshold_rule/get_view_in_app_url'; import { ObservabilityConfig } from '../../..'; +import { getEvaluationValues, getThreshold } from './lib/get_values'; import { FIRED_ACTIONS_ID, NO_DATA_ACTIONS_ID, UNGROUPED_FACTORY_KEY } from './constants'; import { AlertStates, @@ -94,7 +95,7 @@ export const createCustomThresholdExecutor = ({ executionId, }); - const { searchSourceClient, alertsClient } = services; + const { searchSourceClient, alertsClient, uiSettingsClient } = services; if (!alertsClient) { throw new AlertsClientError(); @@ -134,6 +135,7 @@ export const createCustomThresholdExecutor = ({ // Calculate initial start and end date with no time window, as each criterion has its own time window const { dateStart, dateEnd } = getTimeRange(); + const esQueryConfig = await getEsQueryConfig(uiSettingsClient); const alertResults = await evaluateRule( services.scopedClusterClient.asCurrentUser, params as EvaluatedRuleParams, @@ -143,6 +145,7 @@ export const createCustomThresholdExecutor = ({ alertOnGroupDisappear, logger, { end: dateEnd, start: dateStart }, + esQueryConfig, state.lastRunTimestamp, previousMissingGroups ); diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts index 213dd12562eed..8a2d2911915d7 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/check_missing_group.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { ElasticsearchClient } from '@kbn/core/server'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { EsQueryConfig } from '@kbn/es-query'; import type { Logger } from '@kbn/logging'; import { isString, get, identity } from 'lodash'; import { @@ -30,6 +31,7 @@ export const checkMissingGroups = async ( searchConfiguration: SearchConfigurationType, logger: Logger, timeframe: { start: number; end: number }, + esQueryConfig: EsQueryConfig, missingGroups: MissingGroupsRecord[] = [] ): Promise<MissingGroupsRecord[]> => { if (missingGroups.length === 0) { @@ -52,8 +54,10 @@ export const checkMissingGroups = async ( currentTimeFrame, timeFieldName, searchConfiguration, + esQueryConfig, groupByQueries ); + return [ { index: indexPattern }, { diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index 88ee39526e428..2e2d1e5af48b2 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -6,7 +6,8 @@ */ import moment from 'moment'; -import { ElasticsearchClient } from '@kbn/core/server'; +import type { ElasticsearchClient } from '@kbn/core/server'; +import { EsQueryConfig } from '@kbn/es-query'; import type { Logger } from '@kbn/logging'; import { getIntervalInSeconds } from '../../../../../common/utils/get_interval_in_seconds'; import { @@ -43,6 +44,7 @@ export const evaluateRule = async <Params extends EvaluatedRuleParams = Evaluate alertOnGroupDisappear: boolean, logger: Logger, timeframe: { start: string; end: string }, + esQueryConfig: EsQueryConfig, lastPeriodEnd?: number, missingGroups: MissingGroupsRecord[] = [] ): Promise<Array<Record<string, Evaluation>>> => { @@ -70,6 +72,7 @@ export const evaluateRule = async <Params extends EvaluatedRuleParams = Evaluate timeFieldName, groupBy, searchConfiguration, + esQueryConfig, compositeSize, alertOnGroupDisappear, calculatedTimerange, @@ -86,6 +89,7 @@ export const evaluateRule = async <Params extends EvaluatedRuleParams = Evaluate searchConfiguration, logger, calculatedTimerange, + esQueryConfig, missingGroups ); diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/get_data.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/get_data.ts index a102a9d23152e..9d1b8fdf9d0ae 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/get_data.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/get_data.ts @@ -7,6 +7,7 @@ import type { SearchResponse, AggregationsAggregate } from '@elastic/elasticsearch/lib/api/types'; import type { ElasticsearchClient } from '@kbn/core/server'; +import type { EsQueryConfig } from '@kbn/es-query'; import type { Logger } from '@kbn/logging'; import type { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common'; import type { @@ -104,6 +105,7 @@ export const getData = async ( timeFieldName: string, groupBy: string | undefined | string[], searchConfiguration: SearchConfigurationType, + esQueryConfig: EsQueryConfig, compositeSize: number, alertOnGroupDisappear: boolean, timeframe: { start: number; end: number }, @@ -163,6 +165,7 @@ export const getData = async ( timeFieldName, groupBy, searchConfiguration, + esQueryConfig, compositeSize, alertOnGroupDisappear, timeframe, @@ -205,6 +208,7 @@ export const getData = async ( compositeSize, alertOnGroupDisappear, searchConfiguration, + esQueryConfig, lastPeriodEnd, groupBy, afterKey, diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts index e10a4f4cae9be..efaf7b36e3a0b 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.test.ts @@ -42,6 +42,11 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { query: '', }, }; + const esQueryConfig = { + allowLeadingWildcards: false, + queryStringOptions: {}, + ignoreFilterIfFieldNotInIndex: false, + }; const groupBy = 'host.doggoname'; const timeFieldName = 'mockedTimeFieldName'; @@ -58,6 +63,7 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { 100, true, searchConfiguration, + esQueryConfig, void 0, groupBy ); @@ -114,6 +120,7 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { 100, true, currentSearchConfiguration, + esQueryConfig, void 0, groupBy ); @@ -225,6 +232,7 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { 100, true, currentSearchConfiguration, + esQueryConfig, void 0, groupBy ); diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.ts index fbdf7429f0d66..4e7fc236bfdf5 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/lib/metric_query.ts @@ -7,7 +7,7 @@ import moment from 'moment'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { Filter } from '@kbn/es-query'; +import type { EsQueryConfig, Filter } from '@kbn/es-query'; import { Aggregators, CustomMetricExpressionParams, @@ -52,6 +52,7 @@ export const createBoolQuery = ( timeframe: { start: number; end: number }, timeFieldName: string, searchConfiguration: SearchConfigurationType, + esQueryConfig: EsQueryConfig, additionalQueries: QueryDslQueryContainer[] = [] ) => { const rangeQuery: QueryDslQueryContainer = { @@ -64,7 +65,7 @@ export const createBoolQuery = ( }; const filters = QueryDslQueryContainerToFilter([rangeQuery, ...additionalQueries]); - return getSearchConfigurationBoolQuery(searchConfiguration, filters); + return getSearchConfigurationBoolQuery(searchConfiguration, filters, esQueryConfig); }; export const getElasticsearchMetricQuery = ( @@ -74,6 +75,7 @@ export const getElasticsearchMetricQuery = ( compositeSize: number, alertOnGroupDisappear: boolean, searchConfiguration: SearchConfigurationType, + esQueryConfig: EsQueryConfig, lastPeriodEnd?: number, groupBy?: string | string[], afterKey?: Record<string, string>, @@ -204,7 +206,7 @@ export const getElasticsearchMetricQuery = ( aggs.groupings.composite.after = afterKey; } - const query = createBoolQuery(timeframe, timeFieldName, searchConfiguration); + const query = createBoolQuery(timeframe, timeFieldName, searchConfiguration, esQueryConfig); return { track_total_hits: true, diff --git a/x-pack/plugins/observability_solution/observability/server/utils/get_es_query_config.test.ts b/x-pack/plugins/observability_solution/observability/server/utils/get_es_query_config.test.ts new file mode 100644 index 0000000000000..b74e9410da2d8 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability/server/utils/get_es_query_config.test.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 { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; +import { getEsQueryConfig } from './get_es_query_config'; + +describe('getEsQueryConfig', () => { + const uiSettingsClient = uiSettingsServiceMock.createClient(); + + it('should get the es query config correctly', async () => { + const settings = await getEsQueryConfig(uiSettingsClient); + + expect(settings).toEqual({ + allowLeadingWildcards: false, + ignoreFilterIfFieldNotInIndex: false, + queryStringOptions: false, + }); + }); +}); diff --git a/x-pack/plugins/observability_solution/observability/server/utils/get_es_query_config.ts b/x-pack/plugins/observability_solution/observability/server/utils/get_es_query_config.ts new file mode 100644 index 0000000000000..2c0668371a4de --- /dev/null +++ b/x-pack/plugins/observability_solution/observability/server/utils/get_es_query_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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IUiSettingsClient } from '@kbn/core/server'; +import { UI_SETTINGS } from '@kbn/data-plugin/server'; + +export async function getEsQueryConfig(uiSettings: IUiSettingsClient) { + const allowLeadingWildcards = await uiSettings.get(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS); + const queryStringOptions = await uiSettings.get(UI_SETTINGS.QUERY_STRING_OPTIONS); + const ignoreFilterIfFieldNotInIndex = await uiSettings.get( + UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX + ); + + return { + allowLeadingWildcards, + queryStringOptions, + ignoreFilterIfFieldNotInIndex, + }; +} diff --git a/x-pack/plugins/observability_solution/observability/server/utils/get_parsed_filtered_query.ts b/x-pack/plugins/observability_solution/observability/server/utils/get_parsed_filtered_query.ts index e1e3680df73a8..0bc8dcfb24606 100644 --- a/x-pack/plugins/observability_solution/observability/server/utils/get_parsed_filtered_query.ts +++ b/x-pack/plugins/observability_solution/observability/server/utils/get_parsed_filtered_query.ts @@ -5,9 +5,11 @@ * 2.0. */ +import Boom from '@hapi/boom'; import { BoolQuery, buildEsQuery, + EsQueryConfig, Filter, fromKueryExpression, toElasticsearchQuery, @@ -23,27 +25,21 @@ export const getParsedFilterQuery: (filter: string | undefined) => Array<Record< const parsedQuery = toElasticsearchQuery(fromKueryExpression(filter)); return [parsedQuery]; } catch (error) { - return []; + throw Boom.badRequest(`Invalid filter query: ${error.message}`); } }; export const getSearchConfigurationBoolQuery: ( searchConfiguration: SearchConfigurationType, - additionalFilters: Filter[] -) => { bool: BoolQuery } = (searchConfiguration, additionalFilters) => { + additionalFilters: Filter[], + esQueryConfig?: EsQueryConfig +) => { bool: BoolQuery } = (searchConfiguration, additionalFilters, esQueryConfig) => { try { const searchConfigurationFilters = (searchConfiguration.filter as Filter[]) || []; const filters = [...additionalFilters, ...searchConfigurationFilters]; - return buildEsQuery(undefined, searchConfiguration.query, filters, {}); + return buildEsQuery(undefined, searchConfiguration.query, filters, esQueryConfig); } catch (error) { - return { - bool: { - must: [], - must_not: [], - filter: [], - should: [], - }, - }; + throw Boom.badRequest(`Invalid search query: ${error.message}`); } }; diff --git a/x-pack/plugins/observability_solution/observability/tsconfig.json b/x-pack/plugins/observability_solution/observability/tsconfig.json index c6e6dcbffeec8..28f06a81d17c0 100644 --- a/x-pack/plugins/observability_solution/observability/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability/tsconfig.json @@ -109,6 +109,7 @@ "@kbn/slo-schema", "@kbn/license-management-plugin", "@kbn/observability-alerting-rule-utils", + "@kbn/core-ui-settings-server-mocks", ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts index 61c67ad3e19a9..abb4590c89696 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/config.ts @@ -9,6 +9,7 @@ import { schema, type TypeOf } from '@kbn/config-schema'; export const config = schema.object({ enabled: schema.boolean({ defaultValue: true }), + modelId: schema.maybe(schema.string()), }); export type ObservabilityAIAssistantConfig = TypeOf<typeof config>; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts index 731dd0ce98f33..8a6f414ec92de 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/plugin.ts @@ -47,10 +47,12 @@ export class ObservabilityAIAssistantPlugin > { logger: Logger; + config: ObservabilityAIAssistantConfig; service: ObservabilityAIAssistantService | undefined; constructor(context: PluginInitializerContext<ObservabilityAIAssistantConfig>) { this.logger = context.logger.get(); + this.config = context.config.get<ObservabilityAIAssistantConfig>(); initLangtrace(); } public setup( @@ -112,10 +114,14 @@ export class ObservabilityAIAssistantPlugin // Using once to make sure the same model ID is used during service init and Knowledge base setup const getModelId = once(async () => { + const configModelId = this.config.modelId; + if (configModelId) { + return configModelId; + } const defaultModelId = '.elser_model_2'; const [_, pluginsStart] = await core.getStartServices(); + // Wait for the license to be available so the ML plugin's guards pass once we ask for ELSER stats const license = await firstValueFrom(pluginsStart.licensing.license$); - if (!license.hasAtLeast('enterprise')) { return defaultModelId; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts index 86a18d5a8efa4..4de6c77666170 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { errors } from '@elastic/elasticsearch'; -import { serverUnavailable, gatewayTimeout } from '@hapi/boom'; +import { serverUnavailable, gatewayTimeout, badRequest } from '@hapi/boom'; import type { ElasticsearchClient, IUiSettingsClient } from '@kbn/core/server'; import type { Logger } from '@kbn/logging'; import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; @@ -39,14 +39,20 @@ export interface RecalledEntry { labels?: Record<string, string>; } -function isAlreadyExistsError(error: Error) { +function isModelMissingOrUnavailableError(error: Error) { return ( error instanceof errors.ResponseError && (error.body.error.type === 'resource_not_found_exception' || error.body.error.type === 'status_exception') ); } - +function isCreateModelValidationError(error: Error) { + return ( + error instanceof errors.ResponseError && + error.statusCode === 400 && + error.body?.error?.type === 'action_request_validation_exception' + ); +} function throwKnowledgeBaseNotReady(body: any) { throw serverUnavailable(`Knowledge base is not ready yet`, body); } @@ -84,52 +90,73 @@ export class KnowledgeBaseService { const elserModelId = await this.dependencies.getModelId(); const retryOptions = { factor: 1, minTimeout: 10000, retries: 12 }; - - const installModel = async () => { - this.dependencies.logger.info('Installing ELSER model'); - await this.dependencies.esClient.asInternalUser.ml.putTrainedModel( - { - model_id: elserModelId, - input: { - field_names: ['text_field'], - }, - wait_for_completion: true, - }, - { requestTimeout: '20m' } - ); - this.dependencies.logger.info('Finished installing ELSER model'); - }; - - const getIsModelInstalled = async () => { - const getResponse = await this.dependencies.esClient.asInternalUser.ml.getTrainedModels({ + const getModelInfo = async () => { + return await this.dependencies.esClient.asInternalUser.ml.getTrainedModels({ model_id: elserModelId, include: 'definition_status', }); + }; - this.dependencies.logger.debug( - () => 'Model definition status:\n' + JSON.stringify(getResponse.trained_model_configs[0]) - ); + const isModelInstalledAndReady = async () => { + try { + const getResponse = await getModelInfo(); + this.dependencies.logger.debug( + () => 'Model definition status:\n' + JSON.stringify(getResponse.trained_model_configs[0]) + ); - return Boolean(getResponse.trained_model_configs[0]?.fully_defined); + return Boolean(getResponse.trained_model_configs[0]?.fully_defined); + } catch (error) { + if (isModelMissingOrUnavailableError(error)) { + return false; + } else { + throw error; + } + } }; - await pRetry(async () => { - let isModelInstalled: boolean = false; + const installModelIfDoesNotExist = async () => { + const modelInstalledAndReady = await isModelInstalledAndReady(); + if (!modelInstalledAndReady) { + await installModel(); + } + }; + + const installModel = async () => { + this.dependencies.logger.info('Installing ELSER model'); try { - isModelInstalled = await getIsModelInstalled(); + await this.dependencies.esClient.asInternalUser.ml.putTrainedModel( + { + model_id: elserModelId, + input: { + field_names: ['text_field'], + }, + wait_for_completion: true, + }, + { requestTimeout: '20m' } + ); } catch (error) { - if (isAlreadyExistsError(error)) { - await installModel(); - isModelInstalled = await getIsModelInstalled(); + if (isCreateModelValidationError(error)) { + throw badRequest(error); + } else { + throw error; } } + this.dependencies.logger.info('Finished installing ELSER model'); + }; - if (!isModelInstalled) { - throwKnowledgeBaseNotReady({ - message: 'Model is not fully defined', - }); - } - }, retryOptions); + const pollForModelInstallCompleted = async () => { + await pRetry(async () => { + this.dependencies.logger.info('Polling installation of ELSER model'); + const modelInstalledAndReady = await isModelInstalledAndReady(); + if (!modelInstalledAndReady) { + throwKnowledgeBaseNotReady({ + message: 'Model is not fully defined', + }); + } + }, retryOptions); + }; + await installModelIfDoesNotExist(); + await pollForModelInstallCompleted(); try { await this.dependencies.esClient.asInternalUser.ml.startTrainedModelDeployment({ @@ -139,7 +166,7 @@ export class KnowledgeBaseService { } catch (error) { this.dependencies.logger.debug('Error starting model deployment'); this.dependencies.logger.debug(error); - if (!isAlreadyExistsError(error)) { + if (!isModelMissingOrUnavailableError(error)) { throw error; } } @@ -380,7 +407,7 @@ export class KnowledgeBaseService { namespace, modelId, }).catch((error) => { - if (isAlreadyExistsError(error)) { + if (isModelMissingOrUnavailableError(error)) { throwKnowledgeBaseNotReady(error.body); } throw error; @@ -521,7 +548,7 @@ export class KnowledgeBaseService { })), }; } catch (error) { - if (isAlreadyExistsError(error)) { + if (isModelMissingOrUnavailableError(error)) { throwKnowledgeBaseNotReady(error.body); } throw error; @@ -588,7 +615,7 @@ export class KnowledgeBaseService { return Promise.resolve(); } catch (error) { - if (isAlreadyExistsError(error)) { + if (isModelMissingOrUnavailableError(error)) { throwKnowledgeBaseNotReady(error.body); } throw error; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx index 77e8289deb6c5..a264a7cd30489 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_connectors.tsx @@ -47,7 +47,7 @@ export function WelcomeMessageConnectors({ (connectors.error.body as { statusCode: number }).statusCode === 403; return ( - <div className={fadeInClassName}> + <div className={fadeInClassName} data-test-subj="observabilityAiAssistantConnectorsError"> <EuiFlexGroup direction="row" alignItems="center" justifyContent="center" gutterSize="xs"> <EuiFlexItem grow={false}> <EuiIcon type="alert" color="danger" /> diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx index 4bb7f1404117c..b70d5dbade35b 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx @@ -67,6 +67,7 @@ export function NavControl({}: {}) { useEffect(() => { const conversationSubscription = service.conversations.predefinedConversation$.subscribe(() => { + keyRef.current = v4(); setHasBeenOpened(true); setIsOpen(true); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx index e81e30b0beec7..404ff9e32a4db 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx @@ -353,6 +353,7 @@ export function VisualizeESQL({ query={{ esql: query }} flyoutType="overlay" isTableView + initialRowHeight={0} /> ) : ( <lens.EmbeddableComponent @@ -375,6 +376,7 @@ export function VisualizeESQL({ dataView={dataViewAsync.value} query={{ esql: query }} flyoutType="overlay" + initialRowHeight={0} /> </EuiFlexItem> )} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx index 3ac7df9f0fcd8..da34c98b86fbc 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx @@ -135,7 +135,12 @@ export function ConversationView() { `; return ( - <EuiFlexGroup direction="row" className={containerClassName} gutterSize="none"> + <EuiFlexGroup + direction="row" + className={containerClassName} + gutterSize="none" + data-test-subj="observabilityAiAssistantConversationsPage" + > <EuiFlexItem grow={false} className={conversationListContainerName}> <ConversationList selectedConversationId={conversationId} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx index caa8a47461c4c..c329e6de8e673 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_page.tsx @@ -107,7 +107,7 @@ export function SettingsPage() { }; return ( - <> + <div data-test-subj="aiAssistantSettingsPage"> <EuiTitle size="l"> <h2> {i18n.translate( @@ -141,6 +141,6 @@ export function SettingsPage() { {selectedTabContent} <EuiSpacer size="l" /> - </> + </div> ); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx index 39045fdf998ab..9b7022efc324c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/settings_tab.test.tsx @@ -83,7 +83,7 @@ describe('SettingsTab', () => { } ); - fireEvent.click(getByTestId('apmBottomBarActionsButton')); + fireEvent.click(getByTestId('observabilityAiAssistantManagementBottomBarActionsButton')); await waitFor(() => expect(windowLocationReloadMock).toHaveBeenCalledTimes(1)); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx index 4e7b60c780f81..5b1f1c36fd3dd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_management/public/routes/components/settings_tab/ui_settings.tsx @@ -27,11 +27,17 @@ const settingsKeys = [ ]; export function UISettings() { - const { docLinks, settings, notifications } = useKibana().services; + const { + docLinks, + settings, + notifications, + application: { capabilities }, + } = useKibana().services; const { fields, handleFieldChange, unsavedChanges, saveAll, isSaving, cleanUnsavedChanges } = useEditableSettings(settingsKeys); + const canEditAdvancedSettings = capabilities.advancedSettings?.save; async function handleSave() { try { await saveAll(); @@ -71,7 +77,7 @@ export function UISettings() { > <FieldRow field={field} - isSavingEnabled={true} + isSavingEnabled={!!canEditAdvancedSettings} onFieldChange={handleFieldChange} unsavedChange={unsavedChanges[settingKey]} /> @@ -84,11 +90,11 @@ export function UISettings() { onDiscardChanges={cleanUnsavedChanges} onSave={handleSave} saveLabel={i18n.translate( - 'xpack.observabilityAiAssistantManagement.apmSettings.saveButton', + 'xpack.observabilityAiAssistantManagement.settings.saveButton', { defaultMessage: 'Save changes' } )} unsavedChangesCount={Object.keys(unsavedChanges).length} - appTestSubj="apm" + appTestSubj="observabilityAiAssistantManagement" areChangesInvalid={hasInvalidChanges} /> )} diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts b/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts index 8776d2fd44dbb..a8f3fb348900e 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type EventTypeOpts } from '@kbn/ebt/client'; +import { type EventTypeOpts } from '@elastic/ebt/client'; export const DATA_RECEIVED_TELEMETRY_EVENT: EventTypeOpts<{ rowCount: number; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json index 26db8756fb9a1..782e7f3280be3 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json @@ -49,7 +49,6 @@ "@kbn/es-query", "@kbn/core-analytics-browser", "@kbn/react-hooks", - "@kbn/ebt", ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts b/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts index 998c3e9cc9122..060c33aca7693 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type EventTypeOpts } from '@kbn/ebt/client'; +import { type EventTypeOpts } from '@elastic/ebt/client'; export const OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT: EventTypeOpts<{ flow?: string; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/custom_header.test.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/custom_header.test.tsx new file mode 100644 index 0000000000000..d82b22903bbe8 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/custom_header.test.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 { render } from '@testing-library/react'; +import React from 'react'; +import { Wrapper } from '../shared/test_wrapper'; +import { CustomHeader } from './custom_header'; + +describe('CustomHeaderSection', () => { + it('should render the section for logo text', () => { + const { getByText } = render( + <CustomHeader + logo="kubernetes" + headlineCopy="Setting up Kubernetes with Elastic Agent" + captionCopy="This installation is tailored for configuring and collecting metrics and logs by deploying a new Elastic Agent within your host" + />, + { + wrapper: Wrapper({ location: '/kubernetes' }), + } + ); + expect(getByText('Return')).toBeInTheDocument(); + expect(getByText('Setting up Kubernetes with Elastic Agent')).toBeInTheDocument(); + expect( + getByText( + 'This installation is tailored for configuring and collecting metrics and logs by deploying a new Elastic Agent within your host' + ) + ); + }); + + it('should render the section for euiIconType text', () => { + const { getByText, container } = render( + <CustomHeader + euiIconType="consoleApp" + headlineCopy="Auto-detect logs and metrics" + captionCopy="This installation scans your host and auto-detects log and metric files." + />, + { + wrapper: Wrapper({ location: '/auto-detect?category=infra' }), + } + ); + + container.querySelector('[data-euiicon-type="consoleApp"]'); + + expect(getByText('Return')).toBeInTheDocument(); + expect(getByText('Auto-detect logs and metrics')).toBeInTheDocument(); + expect(getByText('This installation scans your host and auto-detects log and metric files.')); + }); +}); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/custom_header.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/custom_header.tsx new file mode 100644 index 0000000000000..bbd8b8aacf705 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/custom_header.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 { + EuiFlexGroup, + EuiFlexItem, + EuiPageTemplate, + EuiSpacer, + EuiText, + EuiTitle, + useEuiShadow, + useEuiTheme, +} from '@elastic/eui'; +import type { EuiIconType } from '@elastic/eui/src/components/icon/icon'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import React from 'react'; +import { BackButton } from '../shared/back_button'; +import { LogoIcon } from '../shared/logo_icon'; +import type { SupportedLogo } from '../shared/logo_icon'; + +interface Props { + logo?: SupportedLogo; + euiIconType?: EuiIconType; + headlineCopy: string; + captionCopy: string; +} + +export function CustomHeader({ euiIconType, logo, headlineCopy, captionCopy }: Props) { + const theme = useEuiTheme(); + const shadow = useEuiShadow('s'); + return ( + <EuiPageTemplate.Section + css={css` + border-bottom: ${theme.euiTheme.border.thin}; + `} + grow={false} + paddingSize="l" + restrictWidth + > + <BackButton> + {i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.button.returnButtonLabel', + { + defaultMessage: 'Return', + } + )} + </BackButton> + <EuiFlexGroup alignItems="center"> + <EuiFlexItem grow={false}> + <div + css={css` + border-radius: ${theme.euiTheme.border.radius.medium}; + ${shadow} + `} + > + <LogoIcon + euiIconType={euiIconType} + isAvatar={!!euiIconType} + logo={logo} + size="xxl" + css={css` + margin: 12px; + width: 56px; + height: 56px; + `} + /> + </div> + </EuiFlexItem> + <EuiFlexItem> + <EuiTitle size="l"> + <h1>{headlineCopy}</h1> + </EuiTitle> + <EuiSpacer size="s" /> + <EuiText size="m"> + <p>{captionCopy}</p> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPageTemplate.Section> + ); +} diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/header.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/header.tsx index 79789525c8318..3f066f695a3d5 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/header.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/header.tsx @@ -4,35 +4,55 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { i18n } from '@kbn/i18n'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { + EuiPageTemplate, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import React from 'react'; +import backgroundImageUrl from './background.svg'; -export const Header = () => { +export function Header() { return ( - <EuiFlexGroup> - <EuiFlexItem> - <EuiTitle size="l" data-test-subj="obltOnboardingHomeTitle"> - <h1> - {i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.addObservabilityDataTitleLabel', - { defaultMessage: 'Add Observability data' } - )} - </h1> - </EuiTitle> - <EuiSpacer size="s" /> - <EuiText color="subdued"> - {i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.startIngestingDataIntoTextLabel', - { - defaultMessage: - 'Start ingesting Observability data into Elastic. Return to this page at any time by clicking Add data.', - } - )} - </EuiText> - </EuiFlexItem> - <EuiFlexItem /> - </EuiFlexGroup> + <EuiPageTemplate.Section + paddingSize="xl" + css={css` + & > div { + background-image: url(${backgroundImageUrl}); + background-position: right center; + background-repeat: no-repeat; + } + `} + grow={false} + restrictWidth + > + <EuiSpacer size="xl" /> + <EuiFlexGroup> + <EuiFlexItem> + <EuiTitle size="l" data-test-subj="obltOnboardingHomeTitle"> + <h1> + <FormattedMessage + id="xpack.observability_onboarding.experimentalOnboardingFlow.addObservabilityDataTitleLabel" + defaultMessage="Add Observability data" + /> + </h1> + </EuiTitle> + <EuiSpacer size="s" /> + <EuiText color="subdued"> + <FormattedMessage + id="xpack.observability_onboarding.experimentalOnboardingFlow.startIngestingDataIntoTextLabel" + defaultMessage="Start ingesting Observability data into Elastic. Return to this page at any time by clicking Add data." + /> + </EuiText> + </EuiFlexItem> + <EuiFlexItem /> + </EuiFlexGroup> + </EuiPageTemplate.Section> ); -}; +} diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/index.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/index.ts new file mode 100644 index 0000000000000..10c0326a5ad79 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/header/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 { CustomHeader } from './custom_header'; +export { Header } from './header'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx index dd22b1735fc9f..216b849356452 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx @@ -9,18 +9,14 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React, { useEffect } from 'react'; import { Route, Routes } from '@kbn/shared-ux-router'; import { useLocation } from 'react-router-dom-v5-compat'; -import { EuiPageTemplate, EuiPanel, EuiSpacer } from '@elastic/eui'; -import { css } from '@emotion/react'; -import backgroundImageUrl from './header/background.svg'; -import { Footer } from './footer/footer'; -import { OnboardingFlowForm } from './onboarding_flow_form/onboarding_flow_form'; -import { Header } from './header/header'; -import { SystemLogsPanel } from './quickstart_flows/system_logs'; -import { CustomLogsPanel } from './quickstart_flows/custom_logs'; -import { OtelLogsPanel } from './quickstart_flows/otel_logs'; -import { AutoDetectPanel } from './quickstart_flows/auto_detect'; -import { KubernetesPanel } from './quickstart_flows/kubernetes'; -import { BackButton } from './shared/back_button'; +import { + AutoDetectPage, + CustomLogsPage, + KubernetesPage, + LandingPage, + OtelLogsPage, + SystemLogsPage, +} from './pages'; const queryClient = new QueryClient(); @@ -33,74 +29,26 @@ export function ObservabilityOnboardingFlow() { return ( <QueryClientProvider client={queryClient}> - <EuiPageTemplate - css={css` - padding-top: 0px !important; - `} - > - <EuiPageTemplate.Section - paddingSize="xl" - css={css` - & > div { - background-image: url(${backgroundImageUrl}); - background-position: right center; - background-repeat: no-repeat; - } - `} - grow={false} - restrictWidth - > - <EuiSpacer size="xl" /> - <Header /> - </EuiPageTemplate.Section> - <EuiPageTemplate.Section paddingSize="xl" color="subdued" restrictWidth> - <Routes> - <Route path="/auto-detect"> - <BackButton /> - <AutoDetectPanel /> - </Route> - <Route path="/systemLogs"> - <BackButton /> - <SystemLogsPanel /> - </Route> - <Route path="/customLogs"> - <BackButton /> - <CustomLogsPanel /> - </Route> - <Route path="/kubernetes"> - <BackButton /> - <KubernetesPanel /> - </Route> - <Route path="/otel-logs"> - <BackButton /> - <OtelLogsPanel /> - </Route> - <Route> - <OnboardingFlowForm /> - </Route> - </Routes> - <EuiSpacer size="xl" /> - </EuiPageTemplate.Section> - <EuiPageTemplate.Section - contentProps={{ css: { paddingBlock: 0 } }} - css={css` - padding-inline: 0px; - `} - > - <EuiPanel - hasBorder - css={css` - border-radius: 0px; - border-left: none; - border-bottom: none; - border-right: none; - `} - > - <Footer /> - <EuiSpacer size="xl" /> - </EuiPanel> - </EuiPageTemplate.Section> - </EuiPageTemplate> + <Routes> + <Route path="/auto-detect"> + <AutoDetectPage /> + </Route> + <Route path="/systemLogs"> + <SystemLogsPage /> + </Route> + <Route path="/customLogs"> + <CustomLogsPage /> + </Route> + <Route path="/kubernetes"> + <KubernetesPage /> + </Route> + <Route path="/otel-logs"> + <OtelLogsPage /> + </Route> + <Route> + <LandingPage /> + </Route> + </Routes> </QueryClientProvider> ); } diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts index 1da487e5a17cd..39f3de40450e7 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/onboarding_flow_form/use_custom_cards_for_category.ts @@ -23,11 +23,7 @@ export function useCustomCardsForCategory( const history = useHistory(); const location = useLocation(); const { - services: { - application, - http, - context: { isServerless }, - }, + services: { application, http }, } = useKibana<ObservabilityOnboardingAppServices>(); const getUrlForApp = application?.getUrlForApp; @@ -132,7 +128,7 @@ export function useCustomCardsForCategory( integration: '', }, toFeaturedCard('docker'), - isServerless ? toFeaturedCard('prometheus') : otelCard, + otelCard, { id: 'azure-virtual', type: 'virtual', @@ -213,7 +209,7 @@ export function useCustomCardsForCategory( version: '', integration: '', }, - isServerless ? toFeaturedCard('nginx') : otelCard, + otelCard, { id: 'azure-logs-virtual', type: 'virtual', diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/auto_detect.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/auto_detect.tsx new file mode 100644 index 0000000000000..585e1061291a5 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/auto_detect.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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { PageTemplate } from './template'; +import { CustomHeader } from '../header/custom_header'; +import { AutoDetectPanel } from '../quickstart_flows/auto_detect'; + +export const AutoDetectPage = () => ( + <PageTemplate + customHeader={ + <CustomHeader + euiIconType="consoleApp" + headlineCopy={i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.customHeader.system.text', + { + defaultMessage: 'Auto-detect logs and metrics', + } + )} + captionCopy={i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.customHeader.system.description', + { + defaultMessage: + 'This installation scans your host and auto-detects log and metric files.', + } + )} + /> + } + > + <AutoDetectPanel /> + </PageTemplate> +); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/custom_logs.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/custom_logs.tsx new file mode 100644 index 0000000000000..1932c5d2baba5 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/custom_logs.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 { PageTemplate } from './template'; +import { BackButton } from '../shared/back_button'; +import { CustomLogsPanel } from '../quickstart_flows/custom_logs'; + +export const CustomLogsPage = () => ( + <PageTemplate> + <BackButton /> + <CustomLogsPanel /> + </PageTemplate> +); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/index.ts b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/index.ts new file mode 100644 index 0000000000000..acee895fe5ac4 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/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 { AutoDetectPage } from './auto_detect'; +export { CustomLogsPage } from './custom_logs'; +export { KubernetesPage } from './kubernetes'; +export { LandingPage } from './landing'; +export { OtelLogsPage } from './otel_logs'; +export { SystemLogsPage } from './system_logs'; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/kubernetes.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/kubernetes.tsx new file mode 100644 index 0000000000000..41162c70d9f19 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/kubernetes.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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { KubernetesPanel } from '../quickstart_flows/kubernetes'; +import { PageTemplate } from './template'; +import { CustomHeader } from '../header'; + +export const KubernetesPage = () => ( + <PageTemplate + customHeader={ + <CustomHeader + logo="kubernetes" + headlineCopy={i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.customHeader.kubernetes.text', + { + defaultMessage: 'Setting up Kubernetes with Elastic Agent', + } + )} + captionCopy={i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.customHeader.kubernetes.caption.description', + { + defaultMessage: + 'This installation is tailored for configuring and collecting metrics and logs by deploying a new Elastic Agent within your host.', + } + )} + /> + } + > + <KubernetesPanel /> + </PageTemplate> +); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/landing.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/landing.tsx new file mode 100644 index 0000000000000..d485465c88d85 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/landing.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 from 'react'; +import { PageTemplate } from './template'; +import { OnboardingFlowForm } from '../onboarding_flow_form/onboarding_flow_form'; + +export const LandingPage = () => ( + <PageTemplate> + <OnboardingFlowForm /> + </PageTemplate> +); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/otel_logs.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/otel_logs.tsx new file mode 100644 index 0000000000000..53183b57e4b2a --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/otel_logs.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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { PageTemplate } from './template'; +import { CustomHeader } from '../header'; +import { OtelLogsPanel } from '../quickstart_flows/otel_logs'; + +export const OtelLogsPage = () => ( + <PageTemplate + customHeader={ + <CustomHeader + logo="opentelemetry" + headlineCopy={i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.customHeader.otel.text', + { + defaultMessage: 'OpenTelemetry', + } + )} + captionCopy={i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.customHeader.otel.description', + { + defaultMessage: 'Collect logs and host metrics using the OTel collector.', + } + )} + /> + } + > + <OtelLogsPanel /> + </PageTemplate> +); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/system_logs.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/system_logs.tsx new file mode 100644 index 0000000000000..78b7179af88a7 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/system_logs.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 { PageTemplate } from './template'; +import { SystemLogsPanel } from '../quickstart_flows/system_logs'; +import { BackButton } from '../shared/back_button'; + +export const SystemLogsPage = () => ( + <PageTemplate> + <BackButton /> + <SystemLogsPanel /> + </PageTemplate> +); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/template.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/template.tsx new file mode 100644 index 0000000000000..c349b76927b7e --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/pages/template.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 { EuiPageTemplate, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; +import { Footer } from '../footer/footer'; +import { Header } from '../header'; + +interface TemplateProps { + customHeader?: React.ReactNode; +} + +export const PageTemplate: React.FC<TemplateProps> = ({ children, customHeader }) => { + return ( + <EuiPageTemplate + css={css` + padding-top: 0px !important; + `} + > + {!!customHeader ? customHeader : <Header />} + <EuiPageTemplate.Section paddingSize="xl" color="subdued" restrictWidth> + {children} + </EuiPageTemplate.Section> + <EuiSpacer size="xl" /> + <EuiPageTemplate.Section + contentProps={{ css: { paddingBlock: 0 } }} + css={css` + padding-inline: 0px; + `} + > + <EuiPanel + hasBorder + css={css` + border-radius: 0px; + border-left: none; + border-bottom: none; + border-right: none; + `} + > + <Footer /> + <EuiSpacer size="xl" /> + </EuiPanel> + </EuiPageTemplate.Section> + </EuiPageTemplate> + ); +}; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/index.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/index.tsx index 41314e37460e8..75f1376b35f7c 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/index.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/kubernetes/index.tsx @@ -15,6 +15,7 @@ import { EuiStepStatus, } from '@elastic/eui'; import useEvent from 'react-use/lib/useEvent'; +import { i18n } from '@kbn/i18n'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { EmptyPrompt } from '../shared/empty_prompt'; import { CommandSnippet } from './command_snippet'; @@ -37,7 +38,12 @@ export const KubernetesPanel: React.FC = () => { const steps = [ { - title: 'Install Elastic Agent on your host', + title: i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.kubernetes.installStepTitle', + { + defaultMessage: 'Install Elastic Agent on your Kubernetes cluster', + } + ), children: ( <> {status !== FETCH_STATUS.SUCCESS && ( @@ -60,7 +66,12 @@ export const KubernetesPanel: React.FC = () => { ), }, { - title: 'Monitor your Kubernetes cluster', + title: i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.kubernetes.monitorStepTitle', + { + defaultMessage: 'Monitor your Kubernetes cluster', + } + ), status: (isMonitoringStepActive ? 'current' : 'incomplete') as EuiStepStatus, children: isMonitoringStepActive && <DataIngestStatus onboardingId={data.onboardingId} />, }, diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx index e1081caab7e85..f3f0766b999d4 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx @@ -71,12 +71,8 @@ export const OtelLogsPanel: React.FC = () => { }, } = useKibana<ObservabilityOnboardingAppServices>(); - const AGENT_CDN_BASE_URL = isServerless - ? 'snapshots.elastic.co/8.15.0-bc431a00/downloads/beats/elastic-agent' - : 'artifacts.elastic.co/downloads/beats/elastic-agent'; - // TODO change once otel flow is shown on serverless - // const agentVersion = isServerless ? setup?.elasticAgentVersion : stackVersion; - const agentVersion = isServerless ? '8.15.0-SNAPSHOT' : stackVersion; + const AGENT_CDN_BASE_URL = 'artifacts.elastic.co/downloads/beats/elastic-agent'; + const agentVersion = isServerless ? setup?.elasticAgentVersion : stackVersion; const allDatasetsLocator = share.url.locators.get<AllDatasetsLocatorParams>(ALL_DATASETS_LOCATOR_ID); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/back_button.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/back_button.tsx index 6b868ef99f7d4..d524b66f2bf82 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/back_button.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/back_button.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { useNavigate, useLocation } from 'react-router-dom-v5-compat'; import { EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; -export const BackButton = () => { +export const BackButton: React.FC = ({ children }) => { const navigate = useNavigate(); const location = useLocation(); @@ -22,10 +22,12 @@ export const BackButton = () => { flush="left" onClick={() => navigate(`../${location.search}`)} > - {i18n.translate( - 'xpack.observability_onboarding.experimentalOnboardingFlow.button.backToSelectionLabel', - { defaultMessage: 'Back to selection' } - )} + {children + ? children + : i18n.translate( + 'xpack.observability_onboarding.experimentalOnboardingFlow.button.backToSelectionLabel', + { defaultMessage: 'Back to selection' } + )} </EuiButtonEmpty> <EuiSpacer size="m" /> </> diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/logo_icon.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/logo_icon.tsx index 6f4eeb1ba71e7..a3da5f171a236 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/logo_icon.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/logo_icon.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import { EuiIcon, EuiIconProps } from '@elastic/eui'; +import { EuiAvatar, EuiAvatarProps, EuiIcon, EuiIconProps } from '@elastic/eui'; +import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import React from 'react'; @@ -46,6 +47,7 @@ function useIconForLogo(logo?: SupportedLogo): string | undefined { const { services: { http }, } = useKibana(); + if (!logo) return undefined; switch (logo) { case 'aws': return 'logoAWS'; @@ -66,10 +68,35 @@ function useIconForLogo(logo?: SupportedLogo): string | undefined { } } -export function LogoIcon({ logo, size }: { logo: SupportedLogo; size?: EuiIconProps['size'] }) { +type LogoIconSizeProp = EuiIconProps['size'] | EuiAvatarProps['size'] | undefined; + +export interface LogoIconProps { + logo?: SupportedLogo; + euiIconType?: EuiIconType; + isAvatar?: boolean; + size?: LogoIconSizeProp; + className?: string; +} + +function isAvatarSize(size: LogoIconSizeProp): size is EuiAvatarProps['size'] { + return size !== 'original' && size !== 'xxl'; +} + +export function LogoIcon({ logo, euiIconType, isAvatar, size, className }: LogoIconProps) { const iconType = useIconForLogo(logo); - if (iconType) { - return <EuiIcon type={iconType} size={size} />; + if (euiIconType && isAvatar && isAvatarSize(size)) { + return ( + <EuiAvatar + color="subdued" + iconType={euiIconType} + name="logoIcon" + size={size} + className={className} + /> + ); + } + if (iconType || euiIconType) { + return <EuiIcon type={euiIconType ?? iconType!} size={size} className={className} />; } return null; } diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/test_wrapper.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/test_wrapper.tsx new file mode 100644 index 0000000000000..0188a92507a3a --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/shared/test_wrapper.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 { I18nProvider } from '@kbn/i18n-react'; +import React from 'react'; +import { Router } from 'react-router-dom-v5-compat'; + +interface WrapperProps { + location: string; +} + +export const Wrapper = + (props: WrapperProps): React.FC => + ({ children }) => { + return ( + <I18nProvider> + <Router + location={props.location} + navigator={{ + createHref: jest.fn(), + go: jest.fn(), + push: jest.fn(), + replace: jest.fn(), + }} + > + {children} + </Router> + </I18nProvider> + ); + }; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts index d035a7fa02716..b4b2541416e2f 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/server/routes/flow/route.ts @@ -383,7 +383,8 @@ async function ensureInstalledIntegrations( const { pkgName, installSource } = integration; if (installSource === 'registry') { - const pkg = await packageClient.ensureInstalledPackage({ pkgName }); + const installation = await packageClient.ensureInstalledPackage({ pkgName }); + const pkg = installation.package; const inputs = await packageClient.getAgentPolicyInputs(pkg.name, pkg.version); const { packageInfo } = await packageClient.getPackage(pkg.name, pkg.version); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json b/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json index 202db65ba9936..12a908624cfd9 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json @@ -41,7 +41,6 @@ "@kbn/discover-plugin", "@kbn/utility-types", "@kbn/spaces-plugin", - "@kbn/ebt", "@kbn/deeplinks-analytics", "@kbn/custom-integrations-plugin", "@kbn/server-route-repository-utils" diff --git a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx index 235e8e2ab50f1..7f05722019d7e 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_editable_settings.tsx @@ -80,6 +80,10 @@ export function useEditableSettings(settingsKeys: string[]) { async function saveAll() { if (settings && !isEmpty(unsavedChanges)) { + let updateErrorOccurred = false; + const subscription = settings.client.getUpdateErrors$().subscribe((error) => { + updateErrorOccurred = true; + }); try { setIsSaving(true); const arr = Object.entries(unsavedChanges).map(([key, value]) => @@ -88,8 +92,16 @@ export function useEditableSettings(settingsKeys: string[]) { await Promise.all(arr); setForceReloadSettings((state) => ++state); cleanUnsavedChanges(); + if (updateErrorOccurred) { + throw new Error('One or more settings updates failed'); + } + } catch (e) { + throw e; } finally { setIsSaving(false); + if (subscription) { + subscription.unsubscribe(); + } } } } diff --git a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts index 5f9f69180e449..db752969f14dd 100644 --- a/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts +++ b/x-pack/plugins/observability_solution/profiling/e2e/cypress/e2e/profiling_views/storage_explorer.cy.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + describe('Storage explorer page', () => { const rangeFrom = '2023-04-18T00:00:00.000Z'; const rangeTo = '2023-04-18T00:05:00.000Z'; @@ -59,6 +60,40 @@ describe('Storage explorer page', () => { }); }); + describe('summary stats', () => { + it('will still load with kuery', () => { + cy.intercept('GET', '/internal/profiling/storage_explorer/summary?*', { + fixture: 'storage_explorer_summary.json', + }).as('summaryStats'); + cy.visitKibana('/app/profiling/storage-explorer', { + rangeFrom, + rangeTo, + kuery: 'host.id : "1234"', + }); + cy.wait('@summaryStats').then(({ request, response }) => { + const { + dailyDataGenerationBytes, + diskSpaceUsedPct, + totalNumberOfDistinctProbabilisticValues, + totalNumberOfHosts, + totalProfilingSizeBytes, + totalSymbolsSizeBytes, + } = response?.body; + + const { kuery } = request.query; + + expect(parseFloat(dailyDataGenerationBytes)).to.be.gt(0); + expect(parseFloat(diskSpaceUsedPct)).to.be.gt(0); + expect(parseFloat(totalNumberOfDistinctProbabilisticValues)).to.be.gt(0); + expect(parseFloat(totalNumberOfHosts)).to.be.gt(0); + expect(parseFloat(totalProfilingSizeBytes)).to.be.gt(0); + expect(parseFloat(totalSymbolsSizeBytes)).to.be.gt(0); + /* eslint-disable @typescript-eslint/no-unused-expressions */ + expect(kuery).to.be.empty; + }); + }); + }); + describe('Data breakdown', () => { it('displays correct values per index', () => { cy.intercept('GET', '/internal/profiling/storage_explorer/indices_storage_details?*').as( diff --git a/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx b/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx index 8969a389df01d..ff111c4bebcd0 100644 --- a/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/views/storage_explorer/index.tsx @@ -30,7 +30,7 @@ import { Summary } from './summary'; export function StorageExplorerView() { const { query } = useProfilingParams('/storage-explorer'); - const { rangeFrom, rangeTo, kuery, indexLifecyclePhase } = query; + const { rangeFrom, rangeTo, indexLifecyclePhase } = query; const timeRange = useTimeRange({ rangeFrom, rangeTo }); const [selectedTab, setSelectedTab] = useState<'host_breakdown' | 'data_breakdown'>( @@ -47,7 +47,7 @@ export function StorageExplorerView() { http, timeFrom: timeRange.inSeconds.start, timeTo: timeRange.inSeconds.end, - kuery, + kuery: '', indexLifecyclePhase, }); }, @@ -55,7 +55,6 @@ export function StorageExplorerView() { fetchStorageExplorerSummary, timeRange.inSeconds.start, timeRange.inSeconds.end, - kuery, indexLifecyclePhase, ] ); diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/simple_burn_rate/burn_rate.tsx b/x-pack/plugins/observability_solution/slo/public/components/slo/simple_burn_rate/burn_rate.tsx new file mode 100644 index 0000000000000..4a9fc50ed9370 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/components/slo/simple_burn_rate/burn_rate.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 { EuiIcon, EuiLoadingChart, EuiStat, EuiTextColor, EuiToolTip } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import moment from 'moment'; +import React from 'react'; +import { useFetchSloBurnRates } from '../../../hooks/use_fetch_slo_burn_rates'; +import { toDuration, toMinutes } from '../../../utils/slo/duration'; + +interface Props { + slo: SLOWithSummaryResponse; + duration: string; + lastRefreshTime?: number; +} + +export function SimpleBurnRate({ slo, duration, lastRefreshTime }: Props) { + const [refreshTime, setRefreshTime] = React.useState(lastRefreshTime); + const { isLoading, data, refetch } = useFetchSloBurnRates({ + slo, + windows: [{ name: 'burn_rate', duration }], + }); + + React.useEffect(() => { + if (lastRefreshTime !== refreshTime) { + setRefreshTime(lastRefreshTime); + refetch(); + } + }, [refreshTime, lastRefreshTime, refetch]); + + const durationLabel = i18n.translate('xpack.slo.burnRate.durationLabel', { + defaultMessage: 'Last {duration}', + values: { duration }, + }); + + if (isLoading || data === undefined) { + return ( + <EuiStat + title={<EuiLoadingChart />} + textAlign="left" + isLoading={isLoading} + titleColor={'subdued'} + description={ + <EuiTextColor color="subdued"> + <span> + <EuiIcon type="clock" color={'subdued'} /> {durationLabel} + </span> + </EuiTextColor> + } + /> + ); + } + + const burnRate = data.burnRates[0]; + const color = burnRate.burnRate > 1 ? 'danger' : 'success'; + const timeToExhaustLabel = i18n.translate('xpack.slo.burnRate.exhaustionTimeLabel', { + defaultMessage: 'At this rate, the entire error budget will be exhausted in {hour} hours.', + values: { + hour: numeral( + moment + .duration(toMinutes(toDuration(slo.timeWindow.duration)) / burnRate.burnRate, 'minutes') + .asHours() + ).format('0'), + }, + }); + + return ( + <EuiStat + title={i18n.translate('xpack.slo.burnRates.value', { + defaultMessage: '{value}x', + values: { value: numeral(burnRate.burnRate).format('0.00') }, + })} + textAlign="left" + isLoading={isLoading} + titleColor={color} + description={ + burnRate.burnRate > 1 ? ( + <EuiToolTip position="top" content={timeToExhaustLabel}> + <EuiTextColor color={color}> + <span> + <EuiIcon type="clock" color={color} /> {durationLabel} + </span> + </EuiTextColor> + </EuiToolTip> + ) : ( + <EuiTextColor color={color}> + <span> + <EuiIcon type="clock" color={color} /> {durationLabel} + </span> + </EuiTextColor> + ) + } + /> + ); +} diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_selector.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_selector.tsx index 2a46e9404e52d..9835335e5e29f 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_selector.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_selector.tsx @@ -29,6 +29,7 @@ export function SloSelector({ initialSlos, onSelected, hasError, singleSelection label: slo.instanceId !== ALL_VALUE ? `${slo.name} (${slo.instanceId})` : slo.name, value: `${slo.id}-${slo.instanceId}`, })) ?? []; + const [options, setOptions] = useState<Array<EuiComboBoxOptionOption<string>>>([]); const [selectedOptions, setSelectedOptions] = useState<Array<EuiComboBoxOptionOption<string>>>( mapSlosToOptions(initialSlos) diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate.tsx new file mode 100644 index 0000000000000..bb92363359112 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate.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 { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLink, EuiLoadingChart } from '@elastic/eui'; +import { css } from '@emotion/css'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React, { useEffect, useRef, useState } from 'react'; +import { SimpleBurnRate } from '../../../components/slo/simple_burn_rate/burn_rate'; +import { useFetchSloDetails } from '../../../hooks/use_fetch_slo_details'; +import { SloOverviewDetails } from '../common/slo_overview_details'; +import { EmbeddableProps } from './types'; + +export function BurnRate({ sloId, sloInstanceId, duration, reloadSubject }: EmbeddableProps) { + const containerRef = useRef<HTMLDivElement>(null); + const [lastRefreshTime, setLastRefreshTime] = useState<number | undefined>(undefined); + const [selectedSlo, setSelectedSlo] = useState<SLOWithSummaryResponse | null>(null); + const [showAllGroups, setShowAllGroups] = useState(false); + + const { isLoading, data: slo } = useFetchSloDetails({ + sloId, + instanceId: sloInstanceId, + }); + + useEffect(() => { + reloadSubject?.subscribe(() => { + setLastRefreshTime(Date.now()); + }); + + return () => { + reloadSubject?.unsubscribe(); + }; + }, [reloadSubject]); + + const isSloNotFound = !isLoading && slo === undefined; + + if (isLoading || !slo) { + return ( + <EuiFlexGroup + direction="column" + alignItems="center" + justifyContent="center" + className={container} + > + <EuiFlexItem grow={false}> + <EuiLoadingChart /> + </EuiFlexItem> + </EuiFlexGroup> + ); + } + + if (isSloNotFound) { + return ( + <EuiFlexGroup + direction="column" + alignItems="center" + justifyContent="center" + className={container} + > + <EuiFlexItem grow={false}> + {i18n.translate('xpack.slo.sloEmbeddable.overview.sloNotFoundText', { + defaultMessage: + 'The SLO has been deleted. You can safely delete the widget from the dashboard.', + })} + </EuiFlexItem> + </EuiFlexGroup> + ); + } + + const hasGroupings = Object.keys(slo.groupings).length > 0; + const firstGrouping = hasGroupings ? Object.entries(slo.groupings)[0] : undefined; + const firstGroupLabel = firstGrouping ? `${firstGrouping[0]}: ${firstGrouping[1]}` : null; + const hasMoreThanOneGrouping = Object.keys(slo.groupings).length > 1; + + return ( + <div data-shared-item="" ref={containerRef} style={{ width: '100%', padding: 10 }}> + <EuiFlexGroup direction="column" gutterSize="l"> + <EuiFlexGroup direction="column" gutterSize="xs"> + <EuiFlexItem> + <EuiLink + data-test-subj="sloBurnRateLink" + className={link} + color="text" + onClick={() => { + setSelectedSlo(slo); + }} + > + <h2>{slo.name}</h2> + </EuiLink> + </EuiFlexItem> + {hasGroupings && ( + <EuiFlexGroup direction="row" gutterSize="xs"> + <EuiFlexItem grow={false}> + <EuiBadge>{firstGroupLabel}</EuiBadge> + </EuiFlexItem> + + {hasMoreThanOneGrouping && !showAllGroups ? ( + <EuiFlexItem grow={false}> + <EuiBadge + onClick={() => setShowAllGroups(true)} + onClickAriaLabel={i18n.translate( + 'xpack.slo.burnRateEmbeddable.moreInstanceAriaLabel', + { defaultMessage: 'Show more' } + )} + > + <FormattedMessage + id="xpack.slo.burnRateEmbeddable.moreInstanceLabel" + defaultMessage="+{groupingsMore} more instance" + values={{ groupingsMore: Object.keys(slo.groupings).length - 1 }} + /> + </EuiBadge> + </EuiFlexItem> + ) : null} + + {hasMoreThanOneGrouping && showAllGroups + ? Object.entries(slo.groupings) + .splice(1) + .map(([key, value]) => ( + <EuiFlexItem grow={false}> + <EuiBadge> + {key}: {value} + </EuiBadge> + </EuiFlexItem> + )) + : null} + </EuiFlexGroup> + )} + </EuiFlexGroup> + + <EuiFlexGroup direction="row" justifyContent="flexEnd"> + <SimpleBurnRate slo={slo} duration={duration} lastRefreshTime={lastRefreshTime} /> + </EuiFlexGroup> + </EuiFlexGroup> + + <SloOverviewDetails slo={selectedSlo} setSelectedSlo={setSelectedSlo} /> + </div> + ); +} + +const container = css` + height: 100%; +`; + +const link = css` + font-size: 16px; + font-weight: 700; +`; diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate_react_embeddable_factory.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate_react_embeddable_factory.tsx new file mode 100644 index 0000000000000..d0370877b511e --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/burn_rate_react_embeddable_factory.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import { Router } from '@kbn/shared-ux-router'; +import { createBrowserHistory } from 'history'; +import { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public'; +import { + initializeTitles, + useBatchedPublishingSubjects, + fetch$, +} from '@kbn/presentation-publishing'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { SLO_BURN_RATE_EMBEDDABLE_ID } from './constants'; +import { SloBurnRateEmbeddableState, SloEmbeddableDeps, BurnRateApi } from './types'; +import { BurnRate } from './burn_rate'; + +export const getTitle = () => + i18n.translate('xpack.slo.burnRateEmbeddable.title', { + defaultMessage: 'SLO Burn Rate', + }); + +const queryClient = new QueryClient(); + +export const getBurnRateEmbeddableFactory = (deps: SloEmbeddableDeps) => { + const factory: ReactEmbeddableFactory< + SloBurnRateEmbeddableState, + SloBurnRateEmbeddableState, + BurnRateApi + > = { + type: SLO_BURN_RATE_EMBEDDABLE_ID, + deserializeState: (state) => { + return state.rawState as SloBurnRateEmbeddableState; + }, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); + const defaultTitle$ = new BehaviorSubject<string | undefined>(getTitle()); + const sloId$ = new BehaviorSubject(state.sloId); + const sloInstanceId$ = new BehaviorSubject(state.sloInstanceId); + const duration$ = new BehaviorSubject(state.duration); + const reload$ = new Subject<boolean>(); + + const api = buildApi( + { + ...titlesApi, + defaultPanelTitle: defaultTitle$, + serializeState: () => { + return { + rawState: { + ...serializeTitles(), + sloId: sloId$.getValue(), + sloInstanceId: sloInstanceId$.getValue(), + duration: duration$.getValue(), + }, + }; + }, + }, + { + sloId: [sloId$, (value) => sloId$.next(value)], + sloInstanceId: [sloInstanceId$, (value) => sloInstanceId$.next(value)], + duration: [duration$, (value) => duration$.next(value)], + ...titleComparators, + } + ); + + const fetchSubscription = fetch$(api) + .pipe() + .subscribe((next) => { + reload$.next(next.isReload); + }); + + return { + api, + Component: () => { + const [sloId, sloInstanceId, duration] = useBatchedPublishingSubjects( + sloId$, + sloInstanceId$, + duration$ + ); + + const I18nContext = deps.i18n.Context; + + useEffect(() => { + return () => { + fetchSubscription.unsubscribe(); + }; + }, []); + + return ( + <I18nContext> + <Router history={createBrowserHistory()}> + <KibanaContextProvider services={deps}> + <QueryClientProvider client={queryClient}> + <BurnRate + sloId={sloId} + sloInstanceId={sloInstanceId} + duration={duration} + reloadSubject={reload$} + /> + </QueryClientProvider> + </KibanaContextProvider> + </Router> + </I18nContext> + ); + }, + }; + }, + }; + return factory; +}; diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/configuration.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/configuration.tsx new file mode 100644 index 0000000000000..55305f076d893 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/configuration.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiFormRow, + EuiIcon, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ALL_VALUE } from '@kbn/slo-schema'; +import React, { useState } from 'react'; +import { SloSelector } from '../alerts/slo_selector'; +import type { EmbeddableProps } from './types'; + +interface Props { + onCreate: (props: EmbeddableProps) => void; + onCancel: () => void; +} + +interface SloConfig { + sloId: string; + sloInstanceId: string; +} + +export function Configuration({ onCreate, onCancel }: Props) { + const [selectedSlo, setSelectedSlo] = useState<SloConfig>(); + const [duration, setDuration] = useState<string>('1h'); + const [hasError, setHasError] = useState(false); + + const isDurationValid = duration.match(/^\d+[mhd]$/); // matches 1m, 78m, 1h, 6h, 1d, 24d + + const isValid = !!selectedSlo && isDurationValid; + + const onConfirmClick = () => { + if (isValid) { + onCreate({ + sloId: selectedSlo.sloId, + sloInstanceId: selectedSlo.sloInstanceId, + duration, + }); + } + }; + + return ( + <EuiFlyout onClose={onCancel} style={{ minWidth: 550 }}> + <EuiFlyoutHeader> + <EuiTitle> + <h2> + {i18n.translate('xpack.slo.burnRateEmbeddable.configuration.headerTitle', { + defaultMessage: 'Burn rate configuration', + })} + </h2> + </EuiTitle> + </EuiFlyoutHeader> + <EuiFlyoutBody> + <EuiFlexGroup direction="column" gutterSize="l"> + <EuiFlexItem grow> + <SloSelector + singleSelection={true} + hasError={hasError} + onSelected={(slo) => { + setHasError(slo === undefined); + if (slo && 'id' in slo) { + setSelectedSlo({ sloId: slo.id, sloInstanceId: slo.instanceId ?? ALL_VALUE }); + } + }} + /> + </EuiFlexItem> + <EuiFlexItem grow> + <EuiFormRow + fullWidth + isInvalid={!isDurationValid} + label={i18n.translate('xpack.slo.burnRateEmbeddable.configuration.durationLabel', { + defaultMessage: 'Duration', + })} + > + <EuiFieldText + data-test-subj="sloConfigurationDuration" + placeholder="1h" + value={duration} + onChange={(e) => setDuration(e.target.value)} + isInvalid={!isDurationValid} + append={ + <EuiToolTip + content={i18n.translate( + 'xpack.slo.burnRateEmbeddable.configuration.durationTooltip', + { + defaultMessage: + 'Duration must be in the format of [value][unit], for example 5m, 3h, or 6d', + } + )} + > + <EuiIcon type="questionInCircle" /> + </EuiToolTip> + } + /> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlyoutBody> + <EuiFlyoutFooter> + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiButtonEmpty data-test-subj="sloConfigurationCancelButton" onClick={onCancel}> + <FormattedMessage + id="xpack.slo.burnRateEmbeddable.configuration.cancelButtonLabel" + defaultMessage="Cancel" + /> + </EuiButtonEmpty> + + <EuiButton + data-test-subj="sloConfigurationConfirmButton" + isDisabled={!isValid || hasError} + onClick={onConfirmClick} + fill + > + <FormattedMessage + id="xpack.slo.burnRateEmbeddable.configuration.cancelButtonLabel" + defaultMessage="Confirm" + /> + </EuiButton> + </EuiFlexGroup> + </EuiFlyoutFooter> + </EuiFlyout> + ); +} diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/constants.ts b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/constants.ts new file mode 100644 index 0000000000000..b6147bb3f4145 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SLO_BURN_RATE_EMBEDDABLE_ID = 'SLO_BURN_RATE_EMBEDDABLE'; +export const ADD_BURN_RATE_ACTION_ID = 'CREATE_SLO_BURN_RATE_EMBEDDABLE'; diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/open_configuration.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/open_configuration.tsx new file mode 100644 index 0000000000000..e8a7777b29a62 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/open_configuration.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 type { CoreStart } from '@kbn/core/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; +import { SloPublicPluginsStart } from '../../..'; +import { Configuration } from './configuration'; +import type { EmbeddableProps, SloBurnRateEmbeddableState } from './types'; + +export async function openConfiguration( + coreStart: CoreStart, + pluginStart: SloPublicPluginsStart, + initialState?: SloBurnRateEmbeddableState +): Promise<EmbeddableProps> { + const { overlays } = coreStart; + const queryClient = new QueryClient(); + return new Promise(async (resolve, reject) => { + try { + const flyoutSession = overlays.openFlyout( + toMountPoint( + <KibanaContextProvider + services={{ + ...coreStart, + ...pluginStart, + }} + > + <QueryClientProvider client={queryClient}> + <Configuration + onCreate={(update: EmbeddableProps) => { + flyoutSession.close(); + resolve(update); + }} + onCancel={() => { + flyoutSession.close(); + reject(); + }} + /> + </QueryClientProvider> + </KibanaContextProvider>, + coreStart + ) + ); + } catch (error) { + reject(error); + } + }); +} diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/types.ts b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/types.ts new file mode 100644 index 0000000000000..3e95151afb986 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/burn_rate/types.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 { + ApplicationStart, + IUiSettingsClient, + NotificationsStart, + type CoreStart, +} from '@kbn/core/public'; +import { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; +import { + PublishesPanelTitle, + PublishesWritablePanelTitle, + SerializedTitles, +} from '@kbn/presentation-publishing'; +import { Subject } from 'rxjs'; + +export interface EmbeddableProps { + sloId: string; + sloInstanceId: string; + duration: string; + reloadSubject?: Subject<boolean>; +} + +interface BurnRateCustomInput { + sloId: string; + sloInstanceId: string; + duration: string; +} + +export type SloBurnRateEmbeddableState = SerializedTitles & BurnRateCustomInput; +export type BurnRateApi = DefaultEmbeddableApi<SloBurnRateEmbeddableState> & + PublishesWritablePanelTitle & + PublishesPanelTitle; + +export interface SloEmbeddableDeps { + uiSettings: IUiSettingsClient; + http: CoreStart['http']; + i18n: CoreStart['i18n']; + application: ApplicationStart; + notifications: NotificationsStart; +} diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/query_key_factory.ts b/x-pack/plugins/observability_solution/slo/public/hooks/query_key_factory.ts index 11f10b624106f..93f8a76b71db0 100644 --- a/x-pack/plugins/observability_solution/slo/public/hooks/query_key_factory.ts +++ b/x-pack/plugins/observability_solution/slo/public/hooks/query_key_factory.ts @@ -41,7 +41,8 @@ export const sloKeys = { groups: () => [...sloKeys.all, 'group'] as const, overview: (filters: SLOOverviewFilter) => ['overview', filters] as const, details: () => [...sloKeys.all, 'details'] as const, - detail: (sloId?: string) => [...sloKeys.details(), sloId] as const, + detail: (sloId: string, instanceId: string | undefined, remoteName: string | undefined) => + [...sloKeys.details(), { sloId, instanceId, remoteName }] as const, rules: () => [...sloKeys.all, 'rules'] as const, rule: (sloIds: string[]) => [...sloKeys.rules(), sloIds] as const, activeAlerts: () => [...sloKeys.all, 'activeAlerts'] as const, diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_burn_rates.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_burn_rates.ts index 1623e6609b85b..3da6e09072dfd 100644 --- a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_burn_rates.ts +++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_burn_rates.ts @@ -4,23 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { ALL_VALUE, GetSLOBurnRatesResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import { QueryObserverResult, RefetchOptions, RefetchQueryFilters, useQuery, } from '@tanstack/react-query'; -import { ALL_VALUE, GetSLOBurnRatesResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import { SLO_LONG_REFETCH_INTERVAL } from '../constants'; import { useKibana } from '../utils/kibana_react'; import { sloKeys } from './query_key_factory'; -import { SLO_LONG_REFETCH_INTERVAL } from '../constants'; export interface UseFetchSloBurnRatesResponse { - isInitialLoading: boolean; isLoading: boolean; - isRefetching: boolean; - isSuccess: boolean; - isError: boolean; data: GetSLOBurnRatesResponse | undefined; refetch: <TPageData>( options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined @@ -39,41 +35,35 @@ export function useFetchSloBurnRates({ shouldRefetch, }: UseFetchSloBurnRatesParams): UseFetchSloBurnRatesResponse { const { http } = useKibana().services; - const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( - { - queryKey: sloKeys.burnRates(slo.id, slo.instanceId, windows), - queryFn: async ({ signal }) => { - try { - const response = await http.post<GetSLOBurnRatesResponse>( - `/internal/observability/slos/${slo.id}/_burn_rates`, - { - body: JSON.stringify({ - windows, - instanceId: slo.instanceId ?? ALL_VALUE, - remoteName: slo.remote?.remoteName, - }), - signal, - } - ); + const { isLoading, data, refetch } = useQuery({ + queryKey: sloKeys.burnRates(slo.id, slo.instanceId, windows), + queryFn: async ({ signal }) => { + try { + const response = await http.post<GetSLOBurnRatesResponse>( + `/internal/observability/slos/${slo.id}/_burn_rates`, + { + body: JSON.stringify({ + windows, + instanceId: slo.instanceId ?? ALL_VALUE, + remoteName: slo.remote?.remoteName, + }), + signal, + } + ); - return response; - } catch (error) { - // ignore error - } - }, - refetchInterval: shouldRefetch ? SLO_LONG_REFETCH_INTERVAL : undefined, - refetchOnWindowFocus: false, - keepPreviousData: true, - } - ); + return response; + } catch (error) { + // ignore error + } + }, + refetchInterval: shouldRefetch ? SLO_LONG_REFETCH_INTERVAL : undefined, + refetchOnWindowFocus: false, + keepPreviousData: true, + }); return { data, - refetch, isLoading, - isRefetching, - isInitialLoading, - isSuccess, - isError, + refetch, }; } diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_details.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_details.ts index 9f5e2ef718182..589701be319ae 100644 --- a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_details.ts +++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_details.ts @@ -36,14 +36,14 @@ export function useFetchSloDetails({ }: { sloId?: string; instanceId?: string; - remoteName?: string | null; + remoteName?: string; shouldRefetch?: boolean; }): UseFetchSloDetailsResponse { const { http } = useKibana().services; const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery( { - queryKey: sloKeys.detail(sloId), + queryKey: sloKeys.detail(sloId!, instanceId, remoteName), queryFn: async ({ signal }) => { try { const response = await http.get<GetSLOResponse>(`/api/observability/slos/${sloId}`, { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts index bea5080d91884..163e2cd526a6a 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/hooks/use_get_query_params.ts @@ -52,7 +52,7 @@ export function useGetQueryParams() { return { instanceId: !!instanceId && instanceId !== ALL_VALUE ? instanceId : undefined, - remoteName, + remoteName: remoteName !== null ? remoteName : undefined, isDeletingSlo: deleteSlo === 'true', removeDeleteQueryParam, isResettingSlo: resetSlo === 'true', diff --git a/x-pack/plugins/observability_solution/slo/public/plugin.ts b/x-pack/plugins/observability_solution/slo/public/plugin.ts index 8147512e019e9..3e320238794bc 100644 --- a/x-pack/plugins/observability_solution/slo/public/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/public/plugin.ts @@ -15,20 +15,21 @@ import { PluginInitializerContext, } from '@kbn/core/public'; import { BehaviorSubject, firstValueFrom } from 'rxjs'; -import { SloPublicPluginsSetup, SloPublicPluginsStart } from './types'; import { PLUGIN_NAME, sloAppId } from '../common'; -import type { SloPublicSetup, SloPublicStart } from './types'; +import { ExperimentalFeatures, SloConfig } from '../common/config'; +import { SLOS_BASE_PATH } from '../common/locators/paths'; +import { SLO_ALERTS_EMBEDDABLE_ID } from './embeddable/slo/alerts/constants'; +import { SLO_BURN_RATE_EMBEDDABLE_ID } from './embeddable/slo/burn_rate/constants'; +import { SLO_ERROR_BUDGET_ID } from './embeddable/slo/error_budget/constants'; +import { SLO_OVERVIEW_EMBEDDABLE_ID } from './embeddable/slo/overview/constants'; +import { SloOverviewEmbeddableState } from './embeddable/slo/overview/types'; import { SloDetailsLocatorDefinition } from './locators/slo_details'; import { SloEditLocatorDefinition } from './locators/slo_edit'; import { SloListLocatorDefinition } from './locators/slo_list'; -import { SLOS_BASE_PATH } from '../common/locators/paths'; import { getCreateSLOFlyoutLazy } from './pages/slo_edit/shared_flyout/get_create_slo_flyout'; import { registerBurnRateRuleType } from './rules/register_burn_rate_rule_type'; -import { ExperimentalFeatures, SloConfig } from '../common/config'; -import { SLO_OVERVIEW_EMBEDDABLE_ID } from './embeddable/slo/overview/constants'; -import { SloOverviewEmbeddableState } from './embeddable/slo/overview/types'; -import { SLO_ERROR_BUDGET_ID } from './embeddable/slo/error_budget/constants'; -import { SLO_ALERTS_EMBEDDABLE_ID } from './embeddable/slo/alerts/constants'; +import type { SloPublicSetup, SloPublicStart } from './types'; +import { SloPublicPluginsSetup, SloPublicPluginsStart } from './types'; export class SloPlugin implements Plugin<SloPublicSetup, SloPublicStart, SloPublicPluginsSetup, SloPublicPluginsStart> @@ -95,6 +96,7 @@ export class SloPlugin const hasPlatinumLicense = license.hasAtLeast('platinum'); if (hasPlatinumLicense) { const [coreStart, pluginsStart] = await coreSetup.getStartServices(); + pluginsStart.dashboard.registerDashboardPanelPlacementSetting( SLO_OVERVIEW_EMBEDDABLE_ID, (serializedState: SloOverviewEmbeddableState | undefined) => { @@ -104,6 +106,7 @@ export class SloPlugin return { width: 12, height: 8 }; } ); + pluginsSetup.embeddable.registerReactEmbeddableFactory( SLO_OVERVIEW_EMBEDDABLE_ID, async () => { @@ -134,6 +137,24 @@ export class SloPlugin return getErrorBudgetEmbeddableFactory(deps); }); + pluginsStart.dashboard.registerDashboardPanelPlacementSetting( + SLO_BURN_RATE_EMBEDDABLE_ID, + () => { + return { width: 14, height: 7 }; + } + ); + pluginsSetup.embeddable.registerReactEmbeddableFactory( + SLO_BURN_RATE_EMBEDDABLE_ID, + async () => { + const deps = { ...coreStart, ...pluginsStart }; + + const { getBurnRateEmbeddableFactory } = await import( + './embeddable/slo/burn_rate/burn_rate_react_embeddable_factory' + ); + return getBurnRateEmbeddableFactory(deps); + } + ); + const registerAsyncSloUiActions = async () => { if (pluginsSetup.uiActions) { const { registerSloUiActions } = await import('./ui_actions'); diff --git a/x-pack/plugins/observability_solution/slo/public/ui_actions/create_burn_rate_panel_action.tsx b/x-pack/plugins/observability_solution/slo/public/ui_actions/create_burn_rate_panel_action.tsx new file mode 100644 index 0000000000000..0b00d33b4cf6a --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/ui_actions/create_burn_rate_panel_action.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 { CoreSetup } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { apiIsPresentationContainer } from '@kbn/presentation-containers'; +import { EmbeddableApiContext } from '@kbn/presentation-publishing'; +import { + IncompatibleActionError, + type UiActionsActionDefinition, +} from '@kbn/ui-actions-plugin/public'; +import { SloPublicPluginsStart, SloPublicStart } from '..'; +import { + ADD_BURN_RATE_ACTION_ID, + SLO_BURN_RATE_EMBEDDABLE_ID, +} from '../embeddable/slo/burn_rate/constants'; +import { COMMON_SLO_GROUPING } from '../embeddable/slo/common/constants'; + +export function createBurnRatePanelAction( + getStartServices: CoreSetup<SloPublicPluginsStart, SloPublicStart>['getStartServices'] +): UiActionsActionDefinition<EmbeddableApiContext> { + return { + id: ADD_BURN_RATE_ACTION_ID, + grouping: COMMON_SLO_GROUPING, + order: 30, + getIconType: () => 'visGauge', + isCompatible: async ({ embeddable }) => { + return apiIsPresentationContainer(embeddable); + }, + execute: async ({ embeddable }) => { + if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError(); + const [coreStart, deps] = await getStartServices(); + try { + const { openConfiguration } = await import( + '../embeddable/slo/burn_rate/open_configuration' + ); + const initialState = await openConfiguration(coreStart, deps); + embeddable.addNewPanel( + { + panelType: SLO_BURN_RATE_EMBEDDABLE_ID, + initialState, + }, + true + ); + } catch (e) { + return Promise.reject(); + } + }, + getDisplayName: () => + i18n.translate('xpack.slo.burnRateEmbeddable.ariaLabel', { + defaultMessage: 'SLO Burn Rate', + }), + }; +} diff --git a/x-pack/plugins/observability_solution/slo/public/ui_actions/index.ts b/x-pack/plugins/observability_solution/slo/public/ui_actions/index.ts index 95c1f19a8842a..26411f02e753d 100644 --- a/x-pack/plugins/observability_solution/slo/public/ui_actions/index.ts +++ b/x-pack/plugins/observability_solution/slo/public/ui_actions/index.ts @@ -11,6 +11,7 @@ import { createOverviewPanelAction } from './create_overview_panel_action'; import { createAddErrorBudgetPanelAction } from './create_error_budget_action'; import { createAddAlertsPanelAction } from './create_alerts_panel_action'; import { SloPublicPluginsStart, SloPublicStart, SloPublicPluginsSetup } from '..'; +import { createBurnRatePanelAction } from './create_burn_rate_panel_action'; export function registerSloUiActions( core: CoreSetup<SloPublicPluginsStart, SloPublicStart>, @@ -24,6 +25,7 @@ export function registerSloUiActions( const addOverviewPanelAction = createOverviewPanelAction(core.getStartServices); const addErrorBudgetPanelAction = createAddErrorBudgetPanelAction(core.getStartServices); const addAlertsPanelAction = createAddAlertsPanelAction(core.getStartServices); + const addBurnRatePanelAction = createBurnRatePanelAction(core.getStartServices); // Assign triggers // Only register these actions in stateful kibana, and the serverless observability project @@ -31,5 +33,6 @@ export function registerSloUiActions( uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addOverviewPanelAction); uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addErrorBudgetPanelAction); uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addAlertsPanelAction); + uiActions.addTriggerAction(ADD_PANEL_TRIGGER, addBurnRatePanelAction); } } diff --git a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts index c9ee257335c99..cfbfef34f16da 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/install_index_templates.ts @@ -27,9 +27,9 @@ export async function installSyntheticsIndexTemplates(server: SyntheticsServerSe pkgName: 'synthetics', }); - if (!installation) { + if (!installation.package) { return Promise.reject('No package installation found.'); } - return installation; + return installation.package; } diff --git a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.test.ts b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.test.ts index da3a25a5fc9df..734b018a6b624 100644 --- a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.test.ts +++ b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.test.ts @@ -4,10 +4,39 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { addUpdatedField } from './use_filter_update'; +import { renderHook } from '@testing-library/react-hooks'; +import { addUpdatedField, useFilterUpdate } from './use_filter_update'; +import * as params from './use_url_params'; describe('useFilterUpdate', () => { + describe('useFilterUpdate hook', () => { + let getUrlParamsSpy; + let updateUrlSpy: jest.Mock; + + beforeEach(() => { + getUrlParamsSpy = jest.fn().mockReturnValue({ + filters: '[["testField",["tag1"]]]', + excludedFilters: '[["testField",["tag2"]]]', + }); + updateUrlSpy = jest.fn(); + jest.spyOn(params, 'useUrlParams').mockReturnValue([getUrlParamsSpy, updateUrlSpy]); + }); + + it('does not update url when filters have not been updated', () => { + renderHook(() => useFilterUpdate('testField', ['tag1'], ['tag2'])); + expect(updateUrlSpy).not.toBeCalled(); + }); + + it('does update url when filters have been updated', () => { + renderHook(() => useFilterUpdate('testField', ['tag1', 'tag2'], [])); + expect(updateUrlSpy).toBeCalledWith({ + filters: '[["testField",["tag1","tag2"]]]', + excludedFilters: '', + pagination: '', + }); + }); + }); + describe('addUpdatedField', () => { it('conditionally adds fields if they are new', () => { const testVal = {}; diff --git a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.ts b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.ts index 5578230ab2cf0..498cdcda93f3b 100644 --- a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.ts +++ b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/hooks/use_filter_update.ts @@ -62,6 +62,11 @@ export const useFilterUpdate = ( const newFiltersString = getUpdateFilters(currentFiltersMap, fieldName, values); const newExclusionsString = getUpdateFilters(currentExclusionsMap, fieldName, notValues); + // no new filters to apply + if (filters === newFiltersString && excludedFilters === newExclusionsString) { + return; + } + const update: { [key: string]: string } = {}; addUpdatedField(filters, 'filters', newFiltersString, update); diff --git a/x-pack/plugins/observability_solution/uptime/public/plugin.ts b/x-pack/plugins/observability_solution/uptime/public/plugin.ts index 1e0c967d0ef27..b02ee95e12032 100644 --- a/x-pack/plugins/observability_solution/uptime/public/plugin.ts +++ b/x-pack/plugins/observability_solution/uptime/public/plugin.ts @@ -309,17 +309,20 @@ function setUptimeAppStatus( registerAlertRules(coreStart, pluginsStart, stackVersion, false); updater.next(() => ({ status: AppStatus.accessible })); } else { - const indexStatusPromise = UptimeDataHelper(coreStart).indexStatus('now-7d', 'now'); - indexStatusPromise.then((indexStatus) => { - if (indexStatus.indexExists) { - registerUptimeRoutesWithNavigation(coreStart, pluginsStart); - updater.next(() => ({ status: AppStatus.accessible })); - registerAlertRules(coreStart, pluginsStart, stackVersion, false); - } else { - updater.next(() => ({ status: AppStatus.inaccessible })); - registerAlertRules(coreStart, pluginsStart, stackVersion, true); - } - }); + const hasUptimePrivileges = coreStart.application.capabilities.uptime?.show; + if (hasUptimePrivileges) { + const indexStatusPromise = UptimeDataHelper(coreStart).indexStatus('now-7d', 'now'); + indexStatusPromise.then((indexStatus) => { + if (indexStatus.indexExists) { + registerUptimeRoutesWithNavigation(coreStart, pluginsStart); + updater.next(() => ({ status: AppStatus.accessible })); + registerAlertRules(coreStart, pluginsStart, stackVersion, false); + } else { + updater.next(() => ({ status: AppStatus.inaccessible })); + registerAlertRules(coreStart, pluginsStart, stackVersion, true); + } + }); + } } }); } diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts index d9cbe5c69ed6c..6c342f2ee4c72 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_cases.cy.ts @@ -66,7 +66,7 @@ describe('Alert Event Details - Cases', { tags: ['@ess', '@serverless'] }, () => it('runs osquery against alert and creates a new case', () => { const [caseName, caseDescription] = generateRandomStringName(2); cy.getBySel('expand-event').first().click(); - cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('securitySolutionFlyoutFooterDropdownButton').click(); cy.getBySel('osquery-action-item').click(); cy.contains(/^\d+ agen(t|ts) selected/); cy.getBySel('globalLoadingIndicator').should('not.exist'); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts index f1284bf8b528f..386093b971c8c 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts_linked_apps.cy.ts @@ -69,7 +69,7 @@ describe( it('should be able to run live query and add to timeline', () => { const TIMELINE_NAME = 'Untitled timeline'; cy.getBySel('expand-event').first().click(); - cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('securitySolutionFlyoutFooterDropdownButton').click(); cy.getBySel('osquery-action-item').click(); cy.contains('1 agent selected.'); selectAllAgents(); 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 ca10dc80fe6b6..95f0d947b8e84 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 @@ -82,7 +82,7 @@ describe( it('should be able to run take action query against all enrolled agents', () => { cy.getBySel('expand-event').first().click(); - cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('securitySolutionFlyoutFooterDropdownButton').click(); cy.getBySel('osquery-action-item').click(); cy.getBySel('agentSelection').within(() => { cy.getBySel('comboBoxClearButton').click(); diff --git a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts index 85ae94dd3266d..a8461dd9742a8 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/live_query_packs.cy.ts @@ -18,7 +18,8 @@ import { LIVE_QUERY_EDITOR } from '../../screens/live_query'; import { loadPack, cleanupPack, cleanupCase, loadCase } from '../../tasks/api_fixtures'; import { ServerlessRoleName } from '../../support/roles'; -describe('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/169888 +describe.skip('ALL - Live Query Packs', { tags: ['@ess', '@serverless'] }, () => { let packName: string; let packId: string; let caseId: string; diff --git a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts index f85b979500786..56c0b478d9621 100644 --- a/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/all/saved_queries.cy.ts @@ -203,7 +203,8 @@ describe('ALL - Saved queries', { tags: ['@ess', '@serverless'] }, () => { }); }); - describe('prebuilt', () => { + // FLAKY: https://github.com/elastic/kibana/issues/169787 + describe.skip('prebuilt', () => { let packName: string; let packId: string; let savedQueryId: string; diff --git a/x-pack/plugins/osquery/cypress/tasks/live_query.ts b/x-pack/plugins/osquery/cypress/tasks/live_query.ts index ad6bfb1183bfd..b8e97e4a26f02 100644 --- a/x-pack/plugins/osquery/cypress/tasks/live_query.ts +++ b/x-pack/plugins/osquery/cypress/tasks/live_query.ts @@ -165,7 +165,7 @@ export const checkActionItemsInResults = ({ }; export const takeOsqueryActionWithParams = () => { - cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('securitySolutionFlyoutFooterDropdownButton').click(); cy.getBySel('osquery-action-item').click(); selectAllAgents(); inputQuery("SELECT * FROM os_version where name='{{host.os.name}}';", { diff --git a/x-pack/plugins/osquery/cypress/tasks/login.ts b/x-pack/plugins/osquery/cypress/tasks/login.ts index 38d71318d01f9..9dad4966b0fc5 100644 --- a/x-pack/plugins/osquery/cypress/tasks/login.ts +++ b/x-pack/plugins/osquery/cypress/tasks/login.ts @@ -15,6 +15,7 @@ export const initializeDataViews = () => { onBeforeLoad: (win) => disableNewFeaturesTours(win), }); cy.getBySel('globalLoadingIndicator').should('exist'); - cy.getBySel('globalLoadingIndicator').should('not.exist'); + // In serverless the app sometimes takes a long time to load with this check causing flakiness. + cy.getBySel('globalLoadingIndicator', { timeout: 1.5 * 60 * 1000 }).should('not.exist'); cy.getBySel('manage-alert-detection-rules').should('exist'); }; diff --git a/x-pack/plugins/osquery/docs/openapi/ess/osquery_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/osquery/docs/openapi/ess/osquery_api_2023_10_31.bundled.schema.yaml index 6457bcb8c040f..e8635fbc478e4 100644 --- a/x-pack/plugins/osquery/docs/openapi/ess/osquery_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/osquery/docs/openapi/ess/osquery_api_2023_10_31.bundled.schema.yaml @@ -28,6 +28,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Find live queries + tags: + - Security Solution Osquery API post: operationId: OsqueryCreateLiveQuery requestBody: @@ -44,6 +46,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Create a live query + tags: + - Security Solution Osquery API '/api/osquery/live_queries/{id}': get: operationId: OsqueryGetLiveQueryDetails @@ -66,6 +70,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get live query details + tags: + - Security Solution Osquery API '/api/osquery/live_queries/{id}/results/{actionId}': get: operationId: OsqueryGetLiveQueryResults @@ -93,6 +99,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get live query results + tags: + - Security Solution Osquery API /api/osquery/packs: get: operationId: OsqueryFindPacks @@ -110,6 +118,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Find packs + tags: + - Security Solution Osquery API post: operationId: OsqueryCreatePacks requestBody: @@ -126,6 +136,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Create a packs + tags: + - Security Solution Osquery API '/api/osquery/packs/{id}': delete: operationId: OsqueryDeletePacks @@ -143,6 +155,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Delete packs + tags: + - Security Solution Osquery API get: operationId: OsqueryGetPacksDetails parameters: @@ -159,6 +173,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get packs details + tags: + - Security Solution Osquery API put: operationId: OsqueryUpdatePacks parameters: @@ -181,6 +197,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Update packs + tags: + - Security Solution Osquery API /api/osquery/saved_queries: get: operationId: OsqueryFindSavedQueries @@ -198,6 +216,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Find saved queries + tags: + - Security Solution Osquery API post: operationId: OsqueryCreateSavedQuery requestBody: @@ -214,6 +234,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Create a saved query + tags: + - Security Solution Osquery API '/api/osquery/saved_queries/{id}': delete: operationId: OsqueryDeleteSavedQuery @@ -231,6 +253,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Delete saved query + tags: + - Security Solution Osquery API get: operationId: OsqueryGetSavedQueryDetails parameters: @@ -247,6 +271,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get saved query details + tags: + - Security Solution Osquery API put: operationId: OsqueryUpdateSavedQuery parameters: @@ -269,6 +295,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Update saved query + tags: + - Security Solution Osquery API components: schemas: ArrayQueries: @@ -588,3 +616,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: 'Run live queries, manage packs and saved queries.' + name: Security Solution Osquery API diff --git a/x-pack/plugins/osquery/docs/openapi/serverless/osquery_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/osquery/docs/openapi/serverless/osquery_api_2023_10_31.bundled.schema.yaml index 0af3441e888c1..5ee7cc382c480 100644 --- a/x-pack/plugins/osquery/docs/openapi/serverless/osquery_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/osquery/docs/openapi/serverless/osquery_api_2023_10_31.bundled.schema.yaml @@ -28,6 +28,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Find live queries + tags: + - Security Solution Osquery API post: operationId: OsqueryCreateLiveQuery requestBody: @@ -44,6 +46,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Create a live query + tags: + - Security Solution Osquery API '/api/osquery/live_queries/{id}': get: operationId: OsqueryGetLiveQueryDetails @@ -66,6 +70,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get live query details + tags: + - Security Solution Osquery API '/api/osquery/live_queries/{id}/results/{actionId}': get: operationId: OsqueryGetLiveQueryResults @@ -93,6 +99,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get live query results + tags: + - Security Solution Osquery API /api/osquery/packs: get: operationId: OsqueryFindPacks @@ -110,6 +118,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Find packs + tags: + - Security Solution Osquery API post: operationId: OsqueryCreatePacks requestBody: @@ -126,6 +136,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Create a packs + tags: + - Security Solution Osquery API '/api/osquery/packs/{id}': delete: operationId: OsqueryDeletePacks @@ -143,6 +155,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Delete packs + tags: + - Security Solution Osquery API get: operationId: OsqueryGetPacksDetails parameters: @@ -159,6 +173,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get packs details + tags: + - Security Solution Osquery API put: operationId: OsqueryUpdatePacks parameters: @@ -181,6 +197,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Update packs + tags: + - Security Solution Osquery API /api/osquery/saved_queries: get: operationId: OsqueryFindSavedQueries @@ -198,6 +216,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Find saved queries + tags: + - Security Solution Osquery API post: operationId: OsqueryCreateSavedQuery requestBody: @@ -214,6 +234,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Create a saved query + tags: + - Security Solution Osquery API '/api/osquery/saved_queries/{id}': delete: operationId: OsqueryDeleteSavedQuery @@ -231,6 +253,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Delete saved query + tags: + - Security Solution Osquery API get: operationId: OsqueryGetSavedQueryDetails parameters: @@ -247,6 +271,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Get saved query details + tags: + - Security Solution Osquery API put: operationId: OsqueryUpdateSavedQuery parameters: @@ -269,6 +295,8 @@ paths: $ref: '#/components/schemas/DefaultSuccessResponse' description: OK summary: Update saved query + tags: + - Security Solution Osquery API components: schemas: ArrayQueries: @@ -588,3 +616,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: 'Run live queries, manage packs and saved queries.' + name: Security Solution Osquery API diff --git a/x-pack/plugins/osquery/scripts/openapi/bundle.js b/x-pack/plugins/osquery/scripts/openapi/bundle.js index ac45505ae6f88..519b83bcc8a56 100644 --- a/x-pack/plugins/osquery/scripts/openapi/bundle.js +++ b/x-pack/plugins/osquery/scripts/openapi/bundle.js @@ -20,9 +20,17 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..'); outputFilePath: 'docs/openapi/serverless/osquery_api_{version}.bundled.schema.yaml', options: { includeLabels: ['serverless'], - specInfo: { - title: 'Security Solution Osquery API (Elastic Cloud Serverless)', - description: 'Run live queries, manage packs and saved queries.', + prototypeDocument: { + info: { + title: 'Security Solution Osquery API (Elastic Cloud Serverless)', + description: 'Run live queries, manage packs and saved queries.', + }, + tags: [ + { + name: 'Security Solution Osquery API', + description: 'Run live queries, manage packs and saved queries.', + }, + ], }, }, }); @@ -33,9 +41,17 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..'); outputFilePath: 'docs/openapi/ess/osquery_api_{version}.bundled.schema.yaml', options: { includeLabels: ['ess'], - specInfo: { - title: 'Security Solution Osquery API (Elastic Cloud and self-hosted)', - description: 'Run live queries, manage packs and saved queries.', + prototypeDocument: { + info: { + title: 'Security Solution Osquery API (Elastic Cloud and self-hosted)', + description: 'Run live queries, manage packs and saved queries.', + }, + tags: [ + { + name: 'Security Solution Osquery API', + description: 'Run live queries, manage packs and saved queries.', + }, + ], }, }, }); diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/constants.ts b/x-pack/plugins/reporting/server/routes/common/jobs/constants.ts new file mode 100644 index 0000000000000..13c2c7ee50bac --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/common/jobs/constants.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 const STATUS_CODES = { + COMPLETED: 200, + PENDING: { + INTERNAL: 202, + PUBLIC: 503, + }, + FAILED: { + INTERNAL: 202, + PUBLIC: 500, + }, +}; diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts index cbd5f93425847..12de35cd9f32d 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.test.ts @@ -13,8 +13,10 @@ import { CSV_JOB_TYPE } from '@kbn/reporting-export-types-csv-common'; import { PDF_JOB_TYPE, PDF_JOB_TYPE_V2 } from '@kbn/reporting-export-types-pdf-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; +import { ReportingCore } from '../../..'; import { ContentStream, getContentStream } from '../../../lib'; import { createMockReportingCore } from '../../../test_helpers'; +import { STATUS_CODES } from './constants'; import { getDocumentPayloadFactory } from './get_document_payload'; import { jobsQueryFactory } from './jobs_query'; @@ -22,13 +24,14 @@ jest.mock('../../../lib/content_stream'); jest.mock('./jobs_query'); describe('getDocumentPayload', () => { + let core: ReportingCore; let getDocumentPayload: ReturnType<typeof getDocumentPayloadFactory>; beforeEach(async () => { const schema = createMockConfigSchema(); - const core = await createMockReportingCore(schema); + core = await createMockReportingCore(schema); - getDocumentPayload = getDocumentPayloadFactory(core); + getDocumentPayload = getDocumentPayloadFactory(core, { isInternal: false }); (getContentStream as jest.MockedFunction<typeof getContentStream>).mockResolvedValue( new Readable({ @@ -66,7 +69,7 @@ describe('getDocumentPayload', () => { headers: expect.objectContaining({ 'Content-Length': '1024', }), - statusCode: 200, + statusCode: STATUS_CODES.COMPLETED, }) ); }); @@ -96,57 +99,115 @@ describe('getDocumentPayload', () => { 'kbn-csv-contains-formulas': true, 'kbn-max-size-reached': true, }), - statusCode: 200, + statusCode: STATUS_CODES.COMPLETED, }) ); }); }); - describe('when the report is failed', () => { - it('should return payload for the failed report', async () => { - await expect( - getDocumentPayload({ - id: 'id1', - index: '.reporting-12345', - status: JOB_STATUS.FAILED, - jobtype: PDF_JOB_TYPE_V2, - output: {}, - payload: {}, - } as ReportApiJSON) - ).resolves.toEqual( - expect.objectContaining({ - contentType: 'application/json', - content: { - message: expect.stringContaining('Some error'), - }, - headers: {}, - statusCode: 500, - }) - ); + describe('public API behavior', () => { + beforeEach(() => { + getDocumentPayload = getDocumentPayloadFactory(core, { isInternal: false }); + }); + + describe('when the report is failed', () => { + it('should return payload for the failed report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.FAILED, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'application/json', + content: { + message: expect.stringContaining('Some error'), + }, + headers: {}, + statusCode: STATUS_CODES.FAILED.PUBLIC, + }) + ); + }); + }); + + describe('when the report is incomplete', () => { + it('should return payload for the pending report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.PENDING, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'text/plain', + content: 'pending', + headers: { + 'retry-after': '30', + }, + statusCode: STATUS_CODES.PENDING.PUBLIC, + }) + ); + }); }); }); - describe('when the report is incomplete', () => { - it('should return payload for the pending report', async () => { - await expect( - getDocumentPayload({ - id: 'id1', - index: '.reporting-12345', - status: JOB_STATUS.PENDING, - jobtype: PDF_JOB_TYPE_V2, - output: {}, - payload: {}, - } as ReportApiJSON) - ).resolves.toEqual( - expect.objectContaining({ - contentType: 'text/plain', - content: 'pending', - headers: { - 'retry-after': '30', - }, - statusCode: 503, - }) - ); + describe('internal API behavior', () => { + beforeEach(() => { + getDocumentPayload = getDocumentPayloadFactory(core, { isInternal: true }); + }); + + describe('when the report is failed', () => { + it('should return payload for the failed report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.FAILED, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'application/json', + content: { + message: expect.stringContaining('Some error'), + }, + headers: {}, + statusCode: STATUS_CODES.FAILED.INTERNAL, + }) + ); + }); + }); + + describe('when the report is incomplete', () => { + it('should return payload for the pending report', async () => { + await expect( + getDocumentPayload({ + id: 'id1', + index: '.reporting-12345', + status: JOB_STATUS.PENDING, + jobtype: PDF_JOB_TYPE_V2, + output: {}, + payload: {}, + } as ReportApiJSON) + ).resolves.toEqual( + expect.objectContaining({ + contentType: 'text/plain', + content: 'pending', + headers: { 'retry-after': '30' }, + statusCode: STATUS_CODES.PENDING.INTERNAL, + }) + ); + }); }); }); }); diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts index c77e04ec66c22..2a20fa0b475c4 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/get_document_payload.ts @@ -8,13 +8,14 @@ import { Stream } from 'stream'; import { ResponseHeaders } from '@kbn/core-http-server'; +import { JOB_STATUS } from '@kbn/reporting-common'; import { ReportApiJSON } from '@kbn/reporting-common/types'; import { CSV_JOB_TYPE, CSV_JOB_TYPE_DEPRECATED } from '@kbn/reporting-export-types-csv-common'; -import { JOB_STATUS } from '@kbn/reporting-common'; import { ExportType } from '@kbn/reporting-server'; import { ReportingCore } from '../../..'; import { getContentStream } from '../../../lib'; +import { STATUS_CODES } from './constants'; import { jobsQueryFactory } from './jobs_query'; export interface ErrorFromPayload { @@ -53,7 +54,10 @@ const getReportingHeaders = (output: TaskRunResult, exportType: ExportType) => { return metaDataHeaders; }; -export function getDocumentPayloadFactory(reporting: ReportingCore) { +export function getDocumentPayloadFactory( + reporting: ReportingCore, + { isInternal }: { isInternal: boolean } +) { const { logger: _logger } = reporting.getPluginSetupDeps(); const logger = _logger.get('download-report'); const exportTypesRegistry = reporting.getExportTypesRegistry(); @@ -75,7 +79,7 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { return { filename, content, - statusCode: 200, + statusCode: STATUS_CODES.COMPLETED, contentType, headers: { ...headers, @@ -84,29 +88,29 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { }; } - // @TODO: These should be semantic HTTP codes as 500/503's indicate - // error then these are really operating properly. async function getFailure({ id }: ReportApiJSON): Promise<Payload> { - const jobsQuery = jobsQueryFactory(reporting); + const jobsQuery = jobsQueryFactory(reporting, { isInternal }); const error = await jobsQuery.getError(id); - logger.debug(`Report job ${id} has failed. Sending statusCode: 500`); + // For download requested over public API, status code for failed job must be 500 to integrate with Watcher + const statusCode = isInternal ? STATUS_CODES.FAILED.INTERNAL : STATUS_CODES.FAILED.PUBLIC; + logger.debug(`Report job ${id} has failed. Sending statusCode: ${statusCode}`); return { - statusCode: 500, - content: { - message: `Reporting generation failed: ${error}`, - }, + statusCode, + content: { message: `Reporting generation failed: ${error}` }, contentType: 'application/json', headers: {}, }; } function getIncomplete({ id, status }: ReportApiJSON): Payload { - logger.debug(`Report job ${id} is processing. Sending statusCode: 503`); + // For download requested over public API, status code for processing/pending job must be 503 to integrate with Watcher + const statusCode = isInternal ? STATUS_CODES.PENDING.INTERNAL : STATUS_CODES.PENDING.PUBLIC; + logger.debug(`Report job ${id} is processing. Sending statusCode: ${statusCode}`); return { - statusCode: 503, + statusCode, content: status, contentType: 'text/plain', headers: { 'retry-after': '30' }, @@ -124,7 +128,6 @@ export function getDocumentPayloadFactory(reporting: ReportingCore) { } } - // send a 503 indicating that the report isn't completed yet return getIncomplete(report); }; } diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts b/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts index 64a3b0224ad71..dc01a29db780a 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/get_job_routes.ts @@ -30,8 +30,11 @@ interface HandlerOpts { res: KibanaResponseFactory; } -export const commonJobsRouteHandlerFactory = (reporting: ReportingCore) => { - const jobsQuery = jobsQueryFactory(reporting); +export const commonJobsRouteHandlerFactory = ( + reporting: ReportingCore, + { isInternal }: { isInternal: boolean } +) => { + const jobsQuery = jobsQueryFactory(reporting, { isInternal }); const handleDownloadReport = ({ path, user, context, req, res }: HandlerOpts) => { const counters = getCounters(req.route.method, path, reporting.getUsageCounter()); @@ -43,36 +46,48 @@ export const commonJobsRouteHandlerFactory = (reporting: ReportingCore) => { const { docId } = req.params; - return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => { - const payload = await jobsQuery.getDocumentPayload(doc); - const { contentType, content, filename, statusCode } = payload; - - if (!contentType || !ALLOWED_JOB_CONTENT_TYPES.includes(contentType)) { - return res.badRequest({ - body: `Unsupported content-type of ${contentType} specified by job output`, - }); - } - - const body = typeof content === 'string' ? Buffer.from(content) : content; - - const headers = { - ...payload.headers, - 'content-type': contentType, - }; - - if (filename) { - // event tracking of the downloaded file, if - // the report job was completed successfully - // and a file is available - const eventTracker = reporting.getEventTracker(docId, doc.jobtype, doc.payload.objectType); - const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); - eventTracker?.downloadReport({ timeSinceCreation }); - - return res.file({ body, headers, filename }); + return jobManagementPreRouting( + reporting, + res, + docId, + user, + counters, + { isInternal }, + async (doc) => { + const payload = await jobsQuery.getDocumentPayload(doc); + const { contentType, content, filename, statusCode } = payload; + + if (!contentType || !ALLOWED_JOB_CONTENT_TYPES.includes(contentType)) { + return res.badRequest({ + body: `Unsupported content-type of ${contentType} specified by job output`, + }); + } + + const body = typeof content === 'string' ? Buffer.from(content) : content; + + const headers = { + ...payload.headers, + 'content-type': contentType, + }; + + if (filename) { + // event tracking of the downloaded file, if + // the report job was completed successfully + // and a file is available + const eventTracker = reporting.getEventTracker( + docId, + doc.jobtype, + doc.payload.objectType + ); + const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); + eventTracker?.downloadReport({ timeSinceCreation }); + + return res.file({ body, headers, filename }); + } + + return res.custom({ body, headers, statusCode }); } - - return res.custom({ body, headers, statusCode }); - }); + ); }; const handleDeleteReport = ({ path, user, context, req, res }: HandlerOpts) => { @@ -85,52 +100,64 @@ export const commonJobsRouteHandlerFactory = (reporting: ReportingCore) => { const { docId } = req.params; - return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => { - const docIndex = doc.index; - const stream = await getContentStream(reporting, { id: docId, index: docIndex }); - const reportingSetup = reporting.getPluginSetupDeps(); - const logger = reportingSetup.logger.get('delete-report'); - - // An "error" event is emitted if an error is - // passed to the `stream.end` callback from - // the _final method of the ContentStream. - // This event must be handled. - stream.on('error', (err) => { - logger.error(err); - }); - - try { - // Overwriting existing content with an - // empty buffer to remove all the chunks. - await new Promise<void>((resolve, reject) => { - stream.end('', 'utf8', (error?: Error) => { - if (error) { - // handle error that could be thrown - // from the _write method of the ContentStream - reject(error); - } else { - resolve(); - } - }); + return jobManagementPreRouting( + reporting, + res, + docId, + user, + counters, + { isInternal }, + async (doc) => { + const docIndex = doc.index; + const stream = await getContentStream(reporting, { id: docId, index: docIndex }); + const reportingSetup = reporting.getPluginSetupDeps(); + const logger = reportingSetup.logger.get('delete-report'); + + // An "error" event is emitted if an error is + // passed to the `stream.end` callback from + // the _final method of the ContentStream. + // This event must be handled. + stream.on('error', (err) => { + logger.error(err); }); - await jobsQuery.delete(docIndex, docId); + try { + // Overwriting existing content with an + // empty buffer to remove all the chunks. + await new Promise<void>((resolve, reject) => { + stream.end('', 'utf8', (error?: Error) => { + if (error) { + // handle error that could be thrown + // from the _write method of the ContentStream + reject(error); + } else { + resolve(); + } + }); + }); - // event tracking of the deleted report - const eventTracker = reporting.getEventTracker(docId, doc.jobtype, doc.payload.objectType); - const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); - eventTracker?.deleteReport({ timeSinceCreation }); + await jobsQuery.delete(docIndex, docId); - return res.ok({ - body: { deleted: true }, - }); - } catch (error) { - logger.error(error); - return res.customError({ - statusCode: 500, - }); + // event tracking of the deleted report + const eventTracker = reporting.getEventTracker( + docId, + doc.jobtype, + doc.payload.objectType + ); + const timeSinceCreation = Date.now() - new Date(doc.created_at).valueOf(); + eventTracker?.deleteReport({ timeSinceCreation }); + + return res.ok({ + body: { deleted: true }, + }); + } catch (error) { + logger.error(error); + return res.customError({ + statusCode: 500, + }); + } } - }); + ); }; return { diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts index cc06ef2e0826c..2f796fd83fbe0 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.test.ts @@ -31,6 +31,7 @@ const mockCounters = { errorCounter: jest.fn(), }; const mockUser = { username: 'joeuser' }; +const options = { isInternal: false }; beforeEach(async () => { mockSetupDeps = createMockPluginSetup({ @@ -70,6 +71,7 @@ it(`should return 404 if the docId isn't resolve`, async function () { 'doc123', mockUser, mockCounters, + options, handler ); @@ -97,6 +99,7 @@ it(`should return forbidden if job type is unrecognized`, async function () { 'doc123', mockUser, mockCounters, + options, handler ); @@ -124,6 +127,7 @@ it(`should call callback when document is available`, async function () { 'doc123', mockUser, mockCounters, + options, handler ); @@ -154,6 +158,7 @@ describe('usage counters', () => { 'doc123', mockUser, mockCounters, + options, handler ); @@ -177,6 +182,7 @@ describe('usage counters', () => { 'doc123', mockUser, mockCounters, + options, handler ); diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts index e9e89c61289b7..e42874468cccf 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/job_management_pre_routing.ts @@ -29,6 +29,7 @@ export const jobManagementPreRouting = async ( jobId: JobId, user: ReportingUser, counters: Counters, + { isInternal }: { isInternal: boolean }, cb: JobManagementResponseHandler ) => { const licenseInfo = await reporting.getLicenseInfo(); @@ -36,7 +37,7 @@ export const jobManagementPreRouting = async ( management: { jobTypes = [] }, } = licenseInfo; - const jobsQuery = jobsQueryFactory(reporting); + const jobsQuery = jobsQueryFactory(reporting, { isInternal }); const doc = await jobsQuery.get(user, jobId); if (!doc) { diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts index 616079c0aa27e..8875c7eb874c7 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.test.ts @@ -23,7 +23,7 @@ describe('jobsQuery', () => { const core = await createMockReportingCore(schema); client = (await core.getEsClient()).asInternalUser as typeof client; - jobsQuery = jobsQueryFactory(core); + jobsQuery = jobsQueryFactory(core, { isInternal: false }); }); describe('list', () => { diff --git a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts index 56b0ac4677449..3d602bf81f44f 100644 --- a/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts +++ b/x-pack/plugins/reporting/server/routes/common/jobs/jobs_query.ts @@ -53,7 +53,10 @@ export interface JobsQueryFactory { delete(deleteIndex: string, id: string): Promise<TransportResult<estypes.DeleteResponse>>; } -export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory { +export function jobsQueryFactory( + reportingCore: ReportingCore, + { isInternal }: { isInternal: boolean } +): JobsQueryFactory { async function execQuery< T extends (client: ElasticsearchClient) => Promise<Awaited<ReturnType<T>> | undefined> >(callback: T): Promise<Awaited<ReturnType<T>> | undefined> { @@ -202,7 +205,7 @@ export function jobsQueryFactory(reportingCore: ReportingCore): JobsQueryFactory }, async getDocumentPayload(doc: ReportApiJSON) { - const getDocumentPayload = getDocumentPayloadFactory(reportingCore); + const getDocumentPayload = getDocumentPayloadFactory(reportingCore, { isInternal }); return await getDocumentPayload(doc); }, diff --git a/x-pack/plugins/reporting/server/routes/internal/deprecations/integration_tests/deprecations.test.ts b/x-pack/plugins/reporting/server/routes/internal/deprecations/integration_tests/deprecations.test.ts index ef3fcb8605eba..7b23e21622e98 100644 --- a/x-pack/plugins/reporting/server/routes/internal/deprecations/integration_tests/deprecations.test.ts +++ b/x-pack/plugins/reporting/server/routes/internal/deprecations/integration_tests/deprecations.test.ts @@ -80,6 +80,7 @@ describe(`GET ${INTERNAL_ROUTES.MIGRATE.GET_ILM_POLICY_STATUS}`, () => { it('increments the download api counter', async () => { const core = await createReportingCore({}); const usageCounter = { + domainId: 'abc123', incrementCounter: jest.fn(), }; core.getUsageCounter = jest.fn().mockReturnValue(usageCounter); diff --git a/x-pack/plugins/reporting/server/routes/internal/diagnostic/integration_tests/browser.test.ts b/x-pack/plugins/reporting/server/routes/internal/diagnostic/integration_tests/browser.test.ts index cccf6eaacafdc..d546d3d29ed47 100644 --- a/x-pack/plugins/reporting/server/routes/internal/diagnostic/integration_tests/browser.test.ts +++ b/x-pack/plugins/reporting/server/routes/internal/diagnostic/integration_tests/browser.test.ts @@ -69,6 +69,7 @@ describe(`POST ${INTERNAL_ROUTES.DIAGNOSE.BROWSER}`, () => { ); usageCounter = { + domainId: 'abc123', incrementCounter: jest.fn(), }; core.getUsageCounter = jest.fn().mockReturnValue(usageCounter); diff --git a/x-pack/plugins/reporting/server/routes/internal/generate/integration_tests/generation_from_jobparams.test.ts b/x-pack/plugins/reporting/server/routes/internal/generate/integration_tests/generation_from_jobparams.test.ts index a2296ad67db2e..1ee9a44865c39 100644 --- a/x-pack/plugins/reporting/server/routes/internal/generate/integration_tests/generation_from_jobparams.test.ts +++ b/x-pack/plugins/reporting/server/routes/internal/generate/integration_tests/generation_from_jobparams.test.ts @@ -89,6 +89,7 @@ describe(`POST ${INTERNAL_ROUTES.GENERATE_PREFIX}`, () => { reportingCore = await createMockReportingCore(mockConfigSchema, mockSetupDeps, mockStartDeps); usageCounter = { + domainId: 'abc123', incrementCounter: jest.fn(), }; jest.spyOn(reportingCore, 'getUsageCounter').mockReturnValue(usageCounter); diff --git a/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts b/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts index 1462b27a42126..143922d2cfedd 100644 --- a/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/internal/management/integration_tests/jobs.test.ts @@ -34,6 +34,7 @@ import { } from '../../../../test_helpers'; import { ReportingRequestHandlerContext } from '../../../../types'; import { EventTracker } from '../../../../usage'; +import { STATUS_CODES } from '../../../common/jobs/constants'; import { registerJobInfoRoutesInternal as registerJobInfoRoutes } from '../jobs'; type SetupServerReturn = Awaited<ReturnType<typeof setupServer>>; @@ -110,6 +111,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { reportingCore = await createMockReportingCore(mockConfigSchema, mockSetupDeps, mockStartDeps); usageCounter = { + domainId: 'abc123', incrementCounter: jest.fn(), }; jest.spyOn(reportingCore, 'getUsageCounter').mockReturnValue(usageCounter); @@ -253,7 +255,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { .expect(403); }); - it('when a job is incomplete', async () => { + it('when a job is incomplete, "internal" API endpoint should return appropriate response', async () => { mockEsClient.search.mockResponseOnce( getHits({ jobtype: mockJobTypeUnencoded, @@ -266,13 +268,13 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(503) + .expect(STATUS_CODES.PENDING.INTERNAL) .expect('Content-Type', 'text/plain; charset=utf-8') .expect('Retry-After', '30') .then(({ text }) => expect(text).toEqual('pending')); }); - it('when a job fails', async () => { + it('when a job fails, "internal" API endpoint should return appropriate response', async () => { mockEsClient.search.mockResponse( getHits({ jobtype: mockJobTypeUnencoded, @@ -286,7 +288,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(500) + .expect(STATUS_CODES.FAILED.INTERNAL) .expect('Content-Type', 'application/json; charset=utf-8') .then(({ body }) => expect(body.message).toEqual('Reporting generation failed: job failure message') @@ -300,7 +302,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .expect('content-disposition', 'attachment; filename=report.csv'); }); @@ -317,7 +319,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dope`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .expect('content-disposition', 'attachment; filename=report.csv'); }); @@ -333,7 +335,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .then(({ text }) => expect(text).toEqual('test')); }); @@ -372,7 +374,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/japanese-dashboard`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'application/pdf') .expect( 'content-disposition', @@ -445,7 +447,7 @@ describe(`Reporting Job Management Routes: Internal`, () => { await server.start(); await supertest(httpSetup.server.listener) .get(`${INTERNAL_ROUTES.JOBS.DOWNLOAD_PREFIX}/dank`) - .expect(200) + .expect(STATUS_CODES.COMPLETED) .expect('Content-Type', 'text/csv; charset=utf-8') .expect('content-disposition', 'attachment; filename=report.csv'); diff --git a/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts b/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts index 44a085a76dd91..21e7b92cc4b8f 100644 --- a/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/internal/management/jobs.ts @@ -22,7 +22,7 @@ const { JOBS } = INTERNAL_ROUTES; export function registerJobInfoRoutesInternal(reporting: ReportingCore) { const setupDeps = reporting.getPluginSetupDeps(); const { router } = setupDeps; - const jobsQuery = jobsQueryFactory(reporting); + const jobsQuery = jobsQueryFactory(reporting, { isInternal: true }); const registerInternalGetList = () => { // list jobs in the queue, paginated @@ -105,7 +105,7 @@ export function registerJobInfoRoutesInternal(reporting: ReportingCore) { }; // use common route handlers that are shared for public and internal routes - const jobHandlers = commonJobsRouteHandlerFactory(reporting); + const jobHandlers = commonJobsRouteHandlerFactory(reporting, { isInternal: true }); const registerInternalGetInfo = () => { // return some info about the job @@ -126,13 +126,20 @@ export function registerJobInfoRoutesInternal(reporting: ReportingCore) { } const { docId } = req.params; - return jobManagementPreRouting(reporting, res, docId, user, counters, async (doc) => - res.ok({ - body: doc, - headers: { - 'content-type': 'application/json', - }, - }) + return jobManagementPreRouting( + reporting, + res, + docId, + user, + counters, + { isInternal: true }, + async (doc) => + res.ok({ + body: doc, + headers: { + 'content-type': 'application/json', + }, + }) ); }) ); diff --git a/x-pack/plugins/reporting/server/routes/public/integration_tests/generation_from_jobparams.test.ts b/x-pack/plugins/reporting/server/routes/public/integration_tests/generation_from_jobparams.test.ts index 471f1456c8b68..f0e55052e4a65 100644 --- a/x-pack/plugins/reporting/server/routes/public/integration_tests/generation_from_jobparams.test.ts +++ b/x-pack/plugins/reporting/server/routes/public/integration_tests/generation_from_jobparams.test.ts @@ -88,6 +88,7 @@ describe(`POST ${PUBLIC_ROUTES.GENERATE_PREFIX}`, () => { reportingCore = await createMockReportingCore(mockConfigSchema, mockSetupDeps, mockStartDeps); usageCounter = { + domainId: 'abc123', incrementCounter: jest.fn(), }; jest.spyOn(reportingCore, 'getUsageCounter').mockReturnValue(usageCounter); diff --git a/x-pack/plugins/reporting/server/routes/public/integration_tests/jobs.test.ts b/x-pack/plugins/reporting/server/routes/public/integration_tests/jobs.test.ts index 96776102ebb92..21ea95568b9aa 100644 --- a/x-pack/plugins/reporting/server/routes/public/integration_tests/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/public/integration_tests/jobs.test.ts @@ -107,6 +107,7 @@ describe(`Reporting Job Management Routes: Public`, () => { reportingCore = await createMockReportingCore(mockConfigSchema, mockSetupDeps, mockStartDeps); usageCounter = { + domainId: 'abc123', incrementCounter: jest.fn(), }; jest.spyOn(reportingCore, 'getUsageCounter').mockReturnValue(usageCounter); diff --git a/x-pack/plugins/reporting/server/routes/public/jobs.ts b/x-pack/plugins/reporting/server/routes/public/jobs.ts index 54ebf3d4e0c1c..04d417c4eb89f 100644 --- a/x-pack/plugins/reporting/server/routes/public/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/public/jobs.ts @@ -16,7 +16,7 @@ export function registerJobInfoRoutesPublic(reporting: ReportingCore) { const { router } = setupDeps; // use common route handlers that are shared for public and internal routes - const jobHandlers = commonJobsRouteHandlerFactory(reporting); + const jobHandlers = commonJobsRouteHandlerFactory(reporting, { isInternal: false }); const registerPublicDownloadReport = () => { // trigger a download of the output from a job diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.test.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.test.ts index 6e955fa3928aa..d66386deb1669 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.test.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.test.ts @@ -7,13 +7,10 @@ import os from 'os'; import { args } from './args'; -import { getChromiumPackage } from '../../../utils'; // Since chromium v111 headless mode in arm based macs is not working with `--disable-gpu` // This is a known issue: headless uses swiftshader by default and swiftshader's support for WebGL is currently disabled on Arm pending the resolution of https://issuetracker.google.com/issues/165000222. -// As a workaround, we force hardware GL drivers on mac. -// The best way to do this starting with v112 is by passing --enable-gpu, -// v111 and older versions should work with --use-angle. +// As a workaround, we pass --enable-gpu to stop forcing swiftshader, see https://issues.chromium.org/issues/40256775#comment4 describe('headless webgl arm mac workaround', () => { const originalPlatform = process.platform; afterEach(() => { @@ -37,21 +34,15 @@ describe('headless webgl arm mac workaround', () => { expect(flags.includes(`--disable-gpu`)).toBe(true); }); - test("doesn't disable gpu when on an arm mac, adds --use-angle", () => { + test("doesn't disable gpu when on an arm mac, adds --enable-gpu", () => { simulateEnv('darwin', 'arm64'); const flags = args({ userDataDir: '/', proxy: { enabled: false }, }); + expect(flags.includes(`--disable-gpu`)).toBe(false); - expect(flags.includes(`--use-angle`)).toBe(true); - - // if you're updating this, then you're likely updating chromium - // please double-check that the --use-angle flag is still needed for arm macs - // instead of --use-angle you may need --enable-gpu - expect(getChromiumPackage().binaryChecksum).toBe( - 'e5d4ccdbf3b66532c7c0973be2d5bbd3189079646ced55fe4db61d8e7e7bfc7e' - ); // just putting this here so that someone updating the chromium version will see this comment + expect(flags.includes(`--enable-gpu`)).toBe(true); }); }); diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts index fc1bf51d77493..2337e452cdc94 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver_factory/args.ts @@ -73,12 +73,11 @@ export const args = ({ flags.push('--no-sandbox'); } - // Since chromium v111 headless mode in arm based macs is not working with `--disable-gpu` + // Headless mode in arm based macs is not working with `--disable-gpu` // This is a known issue: headless uses swiftshader by default and swiftshader's support for WebGL is currently disabled on Arm pending the resolution of https://issuetracker.google.com/issues/165000222. - // As a workaround, we force hardware GL drivers on mac: v111 and older versions should work with --use-angle. - // The best way to do this when the issue is resolved will be to pass --enable-gpu, + // As a workaround, we pass --enable-gpu to stop forcing swiftshader, see https://issues.chromium.org/issues/40256775#comment4 if (os.arch() === 'arm64' && process.platform === 'darwin') { - flags.push('--use-angle'); + flags.push('--enable-gpu'); } else { flags.push('--disable-gpu'); } diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/paths.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/paths.ts index 21dde6af6b7de..ba82fe21734e4 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/paths.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/paths.ts @@ -44,10 +44,10 @@ export class ChromiumArchivePaths { platform: 'darwin', architecture: 'x64', archiveFilename: 'chrome-mac.zip', - archiveChecksum: 'f69bb2f5f402aa2bdd28ccab3a9b36857d1591a1f443fa5b8865df644805cb96', - binaryChecksum: 'c6289a1a1d45021b2259ea0c9ae65ea37199452b5457831680a5e192a3f19add', + archiveChecksum: 'fa8004f3c8c5574c089c901e48429d1b01720bf3dd25e05ac56c41d0ab470c10', + binaryChecksum: '56f25cb6881e5c2b1aac0d8e87630517d1af8effdc9319d35f872add048df1ca', binaryRelativePath: 'chrome-mac/Chromium.app/Contents/MacOS/Chromium', - revision: 1274550, + revision: 1300317, // 1300313 is not available for Mac_x64 location: 'common', archivePath: 'Mac', isPreInstalled: false, @@ -56,10 +56,10 @@ export class ChromiumArchivePaths { platform: 'darwin', architecture: 'arm64', archiveFilename: 'chrome-mac.zip', - archiveChecksum: '55d8cfe56d4461645a663de263ae670f78cc69b69aee16a62c47f361fa9a62f2', - binaryChecksum: 'e5d4ccdbf3b66532c7c0973be2d5bbd3189079646ced55fe4db61d8e7e7bfc7e', + archiveChecksum: 'bea49fd3ccd6aaccd7cdc4df38306f002a2934aaa2c044f3b5a3272b31ec77ca', + binaryChecksum: '4c55d9e47deb1179c377c9785afdcdb5f3d3f351bff62b414d43e32ff195bd55', binaryRelativePath: 'chrome-mac/Chromium.app/Contents/MacOS/Chromium', - revision: 1274557, // 1274542 is not available for Mac_Arm + revision: 1300314, // 1300313 is not available for Mac_Arm location: 'common', archivePath: 'Mac_Arm', isPreInstalled: false, @@ -67,22 +67,22 @@ export class ChromiumArchivePaths { { platform: 'linux', architecture: 'x64', - archiveFilename: 'chromium-46cf136-locales-linux_x64.zip', - archiveChecksum: '8ea5f2d72352230f7927725a6ffd6d5fb462a0c8ad76e320003748b62df6d221', - binaryChecksum: 'e7f8109ef7535dab500166b335524dfa4e92324fa31a2a61f510a5fa5afc2eee', + archiveFilename: 'chromium-5b5d829-locales-linux_x64.zip', + archiveChecksum: '799e8fd5f47ea70b8a3972d39b2617c9cbebc7fc433a89251dae312a7c77534b', + binaryChecksum: '216b8f7ff9b41e985397342c2df54e4f8e07a01a3b8a929f39b9a10931d26ff5', binaryRelativePath: 'headless_shell-linux_x64/headless_shell', - revision: 1274542, + revision: 1300313, location: 'custom', isPreInstalled: true, }, { platform: 'linux', architecture: 'arm64', - archiveFilename: 'chromium-46cf136-locales-linux_arm64.zip', - archiveChecksum: '7d01fe8a78a019cf714c913d37353f85d54c4c7d4fde91d6c96605d259a66414', - binaryChecksum: 'd1a8b8708dc5ced8a9c526a0d823a4074325573f9c06fabe14e18fd3a36691c9', + archiveFilename: 'chromium-5b5d829-locales-linux_arm64.zip', + archiveChecksum: '961e20c45c61f8e948efdc4128bb17c23217bbcb28537f270ccf5bf0826981e7', + binaryChecksum: 'fc4027fb6b1c96bef9374d5d9f791097fae2ec2ddc4e0134167075bd52d1458f', binaryRelativePath: 'headless_shell-linux_arm64/headless_shell', - revision: 1274542, + revision: 1300313, location: 'custom', isPreInstalled: true, }, @@ -90,10 +90,10 @@ export class ChromiumArchivePaths { platform: 'win32', architecture: 'x64', archiveFilename: 'chrome-win.zip', - archiveChecksum: 'd5e9691fb9d378ae13c8956be0d9eb45674d033e8b38125ace2f2fdd458e5ca0', - binaryChecksum: '4d8f95e4f218fc3010ab2f0d8f8674f16d554622218e73f9a7c8a22dbba2e078', + archiveChecksum: '27a2ed1473cefc6f48ff5665faa1fbcc69ef5be47ee21777a60e87c8379fdd93', + binaryChecksum: 'd603401a5e6f8bd734b329876e4221a4d24a1999f14df6e32eeb5e6a72520d96', binaryRelativePath: path.join('chrome-win', 'chrome.exe'), - revision: 1274557, + revision: 1300320, // 1300313 is not available for win32 location: 'common', archivePath: 'Win', isPreInstalled: true, diff --git a/x-pack/plugins/search_notebooks/common/constants.ts b/x-pack/plugins/search_notebooks/common/constants.ts index 46cc4b339003b..b4112a2d2ad9d 100644 --- a/x-pack/plugins/search_notebooks/common/constants.ts +++ b/x-pack/plugins/search_notebooks/common/constants.ts @@ -65,3 +65,5 @@ export const INTRODUCTION_NOTEBOOK: Notebook = { ], }, }; + +export const DEFAULT_NOTEBOOK_ID = 'introduction'; diff --git a/x-pack/plugins/search_notebooks/public/components/notebooks_list.tsx b/x-pack/plugins/search_notebooks/public/components/notebooks_list.tsx index a79464c2db1c0..e43ff61e6e404 100644 --- a/x-pack/plugins/search_notebooks/public/components/notebooks_list.tsx +++ b/x-pack/plugins/search_notebooks/public/components/notebooks_list.tsx @@ -5,7 +5,6 @@ * 2.0. */ import React from 'react'; -import {} from '@elastic/eui'; import { NotebookInformation } from '../../common/types'; import { LoadingPanel } from './loading_panel'; diff --git a/x-pack/plugins/search_notebooks/public/components/search_notebooks.tsx b/x-pack/plugins/search_notebooks/public/components/search_notebooks.tsx index 127de94877c22..1058397a8abaa 100644 --- a/x-pack/plugins/search_notebooks/public/components/search_notebooks.tsx +++ b/x-pack/plugins/search_notebooks/public/components/search_notebooks.tsx @@ -4,17 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + import { EuiResizableContainer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { INTRODUCTION_NOTEBOOK } from '../../common/constants'; +import { INTRODUCTION_NOTEBOOK, DEFAULT_NOTEBOOK_ID } from '../../common/constants'; import { useNotebooksCatalog } from '../hooks/use_notebook_catalog'; import { NotebooksList } from './notebooks_list'; import { SelectionPanel } from './selection_panel'; import { TitlePanel } from './title_panel'; import { SearchNotebook } from './search_notebook'; import { SearchLabsButtonPanel } from './search_labs_button_panel'; +import { readNotebookParameter } from '../utils/notebook_query_param'; const LIST_PANEL_ID = 'notebooksList'; const OUTPUT_PANEL_ID = 'notebooksOutput'; @@ -25,7 +27,9 @@ const defaultSizes: Record<string, number> = { export const SearchNotebooks = () => { const [sizes, setSizes] = useState(defaultSizes); - const [selectedNotebookId, setSelectedNotebookId] = useState<string>('introduction'); + const [selectedNotebookId, setSelectedNotebookId] = useState<string>( + readNotebookParameter() ?? DEFAULT_NOTEBOOK_ID + ); const { data } = useNotebooksCatalog(); const onPanelWidthChange = useCallback((newSizes: Record<string, number>) => { setSizes((prevSizes: Record<string, number>) => ({ @@ -33,6 +37,16 @@ export const SearchNotebooks = () => { ...newSizes, })); }, []); + useEffect(() => { + if (!data) return; + const selectedNotebookFound = + data.notebooks.find((nb) => nb.id === selectedNotebookId) !== undefined; + if (!selectedNotebookFound) { + // If the currently selected notebook is not in the list of notebooks revert + // to the default notebook selection. + setSelectedNotebookId(DEFAULT_NOTEBOOK_ID); + } + }, [data, selectedNotebookId]); const notebooks = useMemo(() => { if (data) return data.notebooks; return null; @@ -40,6 +54,7 @@ export const SearchNotebooks = () => { const onNotebookSelectionClick = useCallback((id: string) => { setSelectedNotebookId(id); }, []); + return ( <EuiResizableContainer style={{ height: '100%', width: '100%' }} diff --git a/x-pack/plugins/search_notebooks/public/plugin.ts b/x-pack/plugins/search_notebooks/public/plugin.ts index e4e1c2b4f0d00..b7a42889a2d47 100644 --- a/x-pack/plugins/search_notebooks/public/plugin.ts +++ b/x-pack/plugins/search_notebooks/public/plugin.ts @@ -18,6 +18,7 @@ import { } from './types'; import { getErrorCode, getErrorMessage, isKibanaServerError } from './utils/get_error_message'; import { createUsageTracker } from './utils/usage_tracker'; +import { removeNotebookParameter, setNotebookParameter } from './utils/notebook_query_param'; export class SearchNotebooksPlugin implements Plugin<SearchNotebooksPluginSetup, SearchNotebooksPluginStart> @@ -66,7 +67,7 @@ export class SearchNotebooksPlugin core, this.queryClient!, this.usageTracker, - this.clearNotebookList.bind(this), + this.clearNotebooksState.bind(this), this.getNotebookList.bind(this) ) ); @@ -75,12 +76,16 @@ export class SearchNotebooksPlugin setNotebookList: (value: NotebookListValue) => { this.setNotebookList(value); }, + setSelectedNotebook: (value: string) => { + setNotebookParameter(value); + }, }; } public stop() {} - private clearNotebookList() { + private clearNotebooksState() { this.setNotebookList(null); + removeNotebookParameter(); } private setNotebookList(value: NotebookListValue) { diff --git a/x-pack/plugins/search_notebooks/public/types.ts b/x-pack/plugins/search_notebooks/public/types.ts index 63955ceb85029..93146d0ac41ee 100644 --- a/x-pack/plugins/search_notebooks/public/types.ts +++ b/x-pack/plugins/search_notebooks/public/types.ts @@ -13,6 +13,7 @@ export interface SearchNotebooksPluginSetup {} export interface SearchNotebooksPluginStart { setNotebookList: (value: NotebookListValue) => void; + setSelectedNotebook: (notebookId: string) => void; } export interface SearchNotebooksPluginStartDependencies { diff --git a/x-pack/plugins/search_notebooks/public/utils/notebook_query_param.test.ts b/x-pack/plugins/search_notebooks/public/utils/notebook_query_param.test.ts new file mode 100644 index 0000000000000..f27e6a17c87c2 --- /dev/null +++ b/x-pack/plugins/search_notebooks/public/utils/notebook_query_param.test.ts @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setNotebookParameter, removeNotebookParameter } from './notebook_query_param'; + +const baseMockWindow = () => { + return { + history: { + pushState: jest.fn(), + }, + location: { + host: 'my-kibana.elastic.co', + pathname: '', + protocol: 'https:', + search: '', + hash: '', + }, + }; +}; +let windowSpy: jest.SpyInstance; +let mockWindow = baseMockWindow(); + +describe('notebook query parameter utility', () => { + beforeEach(() => { + mockWindow = baseMockWindow(); + windowSpy = jest.spyOn(globalThis, 'window', 'get'); + windowSpy.mockImplementation(() => mockWindow); + }); + + afterEach(() => { + windowSpy.mockRestore(); + }); + + describe('setNotebookParameter', () => { + it('adds notebookId query param', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + }; + const notebook = '00_quick_start'; + const expectedUrl = + 'https://my-kibana.elastic.co/foo/app/elasticsearch?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; + + setNotebookParameter(notebook); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + it('can replace an existing value', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + search: '?notebookId=AwRg+g1gpgng7gewE4BMwEcCuUkwJYB2A5mAGZ4A2ALjoUUA', + }; + const notebook = '00_quick_start'; + const expectedUrl = + 'https://my-kibana.elastic.co/foo/app/elasticsearch?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; + + setNotebookParameter(notebook); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + it('leaves other query parameters in place', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + search: '?foo=bar', + }; + const notebook = '00_quick_start'; + const expectedUrl = + 'https://my-kibana.elastic.co/foo/app/elasticsearch?foo=bar¬ebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; + + setNotebookParameter(notebook); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + it('works with hash routes', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + hash: '#/home', + }; + const notebook = '00_quick_start'; + const expectedUrl = + 'https://my-kibana.elastic.co/foo/app/elasticsearch#/home?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA'; + + setNotebookParameter(notebook); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + }); + describe('removeNotebookParameter', () => { + it('leaves other params in place', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + search: `?foo=bar¬ebookId=AzD6EcFcEsGMGtQGcAuBDATioA`, + }; + + const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch?foo=bar'; + + removeNotebookParameter(); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + it('leaves other params with a hashroute', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + hash: `#/home?foo=bar¬ebookId=AzD6EcFcEsGMGtQGcAuBDATioA`, + }; + + const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch#/home?foo=bar'; + + removeNotebookParameter(); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + it('removes ? if load_from was the only param', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + search: `?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA`, + }; + + const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch'; + + removeNotebookParameter(); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + it('removes ? if load_from was the only param in a hashroute', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + hash: '#/home?notebookId=AzD6EcFcEsGMGtQGcAuBDATioA', + }; + + const expectedUrl = 'https://my-kibana.elastic.co/foo/app/elasticsearch#/home'; + + removeNotebookParameter(); + expect(mockWindow.history.pushState).toHaveBeenCalledTimes(1); + expect(mockWindow.history.pushState).toHaveBeenCalledWith( + { + path: expectedUrl, + }, + '', + expectedUrl + ); + }); + it('noop if load_from not currently defined on QS', () => { + mockWindow.location = { + ...mockWindow.location, + pathname: '/foo/app/elasticsearch', + hash: `#/home?foo=bar`, + }; + + removeNotebookParameter(); + expect(mockWindow.history.pushState).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/search_notebooks/public/utils/notebook_query_param.ts b/x-pack/plugins/search_notebooks/public/utils/notebook_query_param.ts new file mode 100644 index 0000000000000..205d87de16218 --- /dev/null +++ b/x-pack/plugins/search_notebooks/public/utils/notebook_query_param.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 qs from 'query-string'; +import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string'; + +function getBaseUrl() { + return `${window.location.protocol}//${window.location.host}${window.location.pathname}`; +} + +function parseQueryString() { + const [hashRoute, queryString] = (window.location.hash || window.location.search || '').split( + '?' + ); + + const parsedQueryString = qs.parse(queryString || '', { sort: false }); + return { + hasHash: !!window.location.hash, + hashRoute, + queryString: parsedQueryString, + }; +} + +export const setNotebookParameter = (value: string) => { + const baseUrl = getBaseUrl(); + const { hasHash, hashRoute, queryString } = parseQueryString(); + const notebookId = compressToEncodedURIComponent(value); + queryString.notebookId = notebookId; + const params = `?${qs.stringify(queryString)}`; + const newUrl = hasHash ? `${baseUrl}${hashRoute}${params}` : `${baseUrl}${params}`; + + window.history.pushState({ path: newUrl }, '', newUrl); +}; +export const removeNotebookParameter = () => { + const baseUrl = getBaseUrl(); + const { hasHash, hashRoute, queryString } = parseQueryString(); + if (queryString.notebookId) { + delete queryString.notebookId; + + const params = Object.keys(queryString).length ? `?${qs.stringify(queryString)}` : ''; + const newUrl = hasHash ? `${baseUrl}${hashRoute}${params}` : `${baseUrl}${params}`; + window.history.pushState({ path: newUrl }, '', newUrl); + } +}; +export const readNotebookParameter = (): string | undefined => { + const { queryString } = parseQueryString(); + if (queryString.notebookId && typeof queryString.notebookId === 'string') { + try { + const notebookId = decompressFromEncodedURIComponent(queryString.notebookId); + if (notebookId.length > 0) return notebookId; + } catch { + return undefined; + } + } + return undefined; +}; diff --git a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx index f3a903331cf3b..4608546616a51 100644 --- a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx +++ b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.test.tsx @@ -38,7 +38,7 @@ describe('AssistantMessage component', () => { createdAt: new Date(), citations: [], retrievalDocs: [{ content: '', metadata: { _id: '1', _index: 'index', _score: 1 } }], - inputTokens: { context: 20, total: 10 }, + inputTokens: { context: 20, total: 10, searchQuery: 'Test question' }, }; it('renders message content correctly', () => { diff --git a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx index 7e94059bdc272..e0b14c2a31934 100644 --- a/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx +++ b/x-pack/plugins/search_playground/public/components/message_list/assistant_message.tsx @@ -53,53 +53,79 @@ export const AssistantMessage: React.FC<AssistantMessageProps> = ({ message }) = return ( <> {!!retrievalDocs?.length && ( - <EuiComment - username={username} - timelineAvatar="dot" - data-test-subj="retrieval-docs-comment" - eventColor="subdued" - css={{ - '.euiAvatar': { backgroundColor: euiTheme.colors.ghost }, - '.euiCommentEvent': { - border: euiTheme.border.thin, - borderRadius: euiTheme.border.radius.medium, - }, - }} - event={ - <> + <> + <EuiComment + username={username} + timelineAvatar="dot" + data-test-subj="assistant-message-searching" + eventColor="subdued" + css={{ + '.euiAvatar': { backgroundColor: euiTheme.colors.ghost }, + '.euiCommentEvent': { + border: euiTheme.border.thin, + borderRadius: euiTheme.border.radius.medium, + }, + }} + event={ <EuiText size="s"> <p> <FormattedMessage - id="xpack.searchPlayground.chat.message.assistant.retrievalDocs" - defaultMessage="Grounding answer based on" + id="xpack.searchPlayground.chat.message.assistant.searchingQuestion" + defaultMessage='Searching for "{question}"' + values={{ question: inputTokens.searchQuery }} /> - {` `} </p> </EuiText> + } + /> + <EuiComment + username={username} + timelineAvatar="dot" + data-test-subj="retrieval-docs-comment" + eventColor="subdued" + css={{ + '.euiAvatar': { backgroundColor: euiTheme.colors.ghost }, + '.euiCommentEvent': { + border: euiTheme.border.thin, + borderRadius: euiTheme.border.radius.medium, + }, + }} + event={ + <> + <EuiText size="s"> + <p> + <FormattedMessage + id="xpack.searchPlayground.chat.message.assistant.retrievalDocs" + defaultMessage="Grounding answer based on" + /> + {` `} + </p> + </EuiText> - <EuiButtonEmpty - css={{ blockSize: 'auto' }} - size="s" - flush="left" - data-test-subj="retrieval-docs-button" - onClick={() => setIsDocsFlyoutOpen(true)} - > - <FormattedMessage - id="xpack.searchPlayground.chat.message.assistant.retrievalDocButton" - defaultMessage="{count} document sources" - values={{ count: retrievalDocs.length }} - /> - </EuiButtonEmpty> + <EuiButtonEmpty + css={{ blockSize: 'auto' }} + size="s" + flush="left" + data-test-subj="retrieval-docs-button" + onClick={() => setIsDocsFlyoutOpen(true)} + > + <FormattedMessage + id="xpack.searchPlayground.chat.message.assistant.retrievalDocButton" + defaultMessage="{count} document sources" + values={{ count: retrievalDocs.length }} + /> + </EuiButtonEmpty> - {isDocsFlyoutOpen && ( - <RetrievalDocsFlyout - onClose={() => setIsDocsFlyoutOpen(false)} - retrievalDocs={retrievalDocs} - /> - )} - </> - } - /> + {isDocsFlyoutOpen && ( + <RetrievalDocsFlyout + onClose={() => setIsDocsFlyoutOpen(false)} + retrievalDocs={retrievalDocs} + /> + )} + </> + } + /> + </> )} {retrievalDocs?.length === 0 && ( <EuiComment diff --git a/x-pack/plugins/search_playground/public/hooks/use_ai_assist_chat.ts b/x-pack/plugins/search_playground/public/hooks/use_ai_assist_chat.ts index f29bb7de11f88..d046b9425c60a 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_ai_assist_chat.ts +++ b/x-pack/plugins/search_playground/public/hooks/use_ai_assist_chat.ts @@ -184,7 +184,7 @@ export function useAIAssistChat({ if (messagesRef.current.length === 0) return null; const chatRequest: ChatRequest = { - messages: messagesRef.current, + messages: messagesRef.current.slice(0, messagesRef.current.length - 1), options, data, }; diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx index 73def5031615e..f5b8f50a43b6a 100644 --- a/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx +++ b/x-pack/plugins/search_playground/public/providers/form_provider.test.tsx @@ -130,6 +130,8 @@ describe('FormProvider', () => { act(() => { setValue(ChatFormFields.prompt, 'New prompt'); + // omit question from the session state + setValue(ChatFormFields.question, 'dont save me'); }); await waitFor(() => { diff --git a/x-pack/plugins/search_playground/public/providers/form_provider.tsx b/x-pack/plugins/search_playground/public/providers/form_provider.tsx index 03c0ce5652e19..fc7c1e8a4c12f 100644 --- a/x-pack/plugins/search_playground/public/providers/form_provider.tsx +++ b/x-pack/plugins/search_playground/public/providers/form_provider.tsx @@ -36,7 +36,10 @@ const getLocalSession = (storage: Storage): PartialChatForm => { } }; -const setLocalSession = (state: PartialChatForm, storage: Storage) => { +const setLocalSession = (formState: PartialChatForm, storage: Storage) => { + // omit question from the session state + const { question, ...state } = formState; + storage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(state)); }; diff --git a/x-pack/plugins/search_playground/public/types.ts b/x-pack/plugins/search_playground/public/types.ts index 2bbd45ff16230..90f6cdc72e04f 100644 --- a/x-pack/plugins/search_playground/public/types.ts +++ b/x-pack/plugins/search_playground/public/types.ts @@ -100,8 +100,9 @@ export interface AnnotationDoc { } export interface AnnotationTokens { - type: 'prompt_token_count' | 'context_token_count' | 'context_clipped'; + type: 'prompt_token_count' | 'context_token_count' | 'context_clipped' | 'search_query'; count: number; + question?: string; } export interface Doc { @@ -117,6 +118,7 @@ export interface AIMessage extends Message { context: number; total: number; contextClipped?: number; + searchQuery: string; }; } diff --git a/x-pack/plugins/search_playground/public/utils/stream.ts b/x-pack/plugins/search_playground/public/utils/stream.ts index 57146263d2653..d06e3f0d10201 100644 --- a/x-pack/plugins/search_playground/public/utils/stream.ts +++ b/x-pack/plugins/search_playground/public/utils/stream.ts @@ -27,7 +27,8 @@ type StreamPartValueType = { export type StreamPartType = | ReturnType<typeof textStreamPart.parse> | ReturnType<typeof errorStreamPart.parse> - | ReturnType<typeof messageAnnotationsStreamPart.parse>; + | ReturnType<typeof messageAnnotationsStreamPart.parse> + | ReturnType<typeof bufferStreamPart.parse>; const NEWLINE = '\n'.charCodeAt(0); @@ -121,7 +122,20 @@ const messageAnnotationsStreamPart = createStreamPart('8', 'message_annotations' return { type: 'message_annotations', value }; }); -const streamParts = [textStreamPart, errorStreamPart, messageAnnotationsStreamPart] as const; +const bufferStreamPart = createStreamPart('10', 'buffer', (value) => { + if (typeof value !== 'string') { + throw new Error('"buffer" parts expect a string value.'); + } + + return { type: 'buffer', value }; +}); + +const streamParts = [ + textStreamPart, + errorStreamPart, + bufferStreamPart, + messageAnnotationsStreamPart, +] as const; type StreamPartMap = { [P in StreamParts as P['code']]: P; diff --git a/x-pack/plugins/search_playground/public/utils/transform_to_messages.ts b/x-pack/plugins/search_playground/public/utils/transform_to_messages.ts index 4500ac6f10890..af8d75a8ee7f1 100644 --- a/x-pack/plugins/search_playground/public/utils/transform_to_messages.ts +++ b/x-pack/plugins/search_playground/public/utils/transform_to_messages.ts @@ -44,6 +44,9 @@ export const transformFromChatMessages = (messages: UseChatHelpers['messages']): contextClipped: annotations?.find( (annotation): annotation is AnnotationTokens => annotation.type === 'context_clipped' )?.count, + searchQuery: annotations?.find( + (annotation): annotation is AnnotationTokens => annotation.type === 'search_query' + )?.question, }, } as AIMessage; } diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts index c63481e93c98f..922f672bda5c6 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.ts @@ -198,6 +198,13 @@ class ConversationalChainFn { context: RunnableSequence.from([(input) => input.question, retrievalChain]), question: (input) => input.question, }, + RunnableLambda.from((inputs) => { + data.appendMessageAnnotation({ + type: 'search_query', + question: inputs.question, + }); + return inputs; + }), RunnableLambda.from(clipContext(this.options?.rag?.inputTokensLimit, prompt, data)), RunnableLambda.from(registerContextTokenCounts(data)), prompt, @@ -236,6 +243,10 @@ class ConversationalChainFn { type: 'prompt_token_count', count: getTokenEstimateFromMessages(msg), }); + data.appendMessageAnnotation({ + type: 'search_query', + question, + }); } }, // callback for prompt based models (Bedrock uses ActionsClientLlm) diff --git a/x-pack/plugins/search_playground/server/routes.ts b/x-pack/plugins/search_playground/server/routes.ts index a71e650a8dae8..fc02973f41b7f 100644 --- a/x-pack/plugins/search_playground/server/routes.ts +++ b/x-pack/plugins/search_playground/server/routes.ts @@ -90,7 +90,8 @@ export function defineRoutes({ }, }, errorHandler(async (context, request, response) => { - const [{ analytics }, { actions }] = await getStartServices(); + const [{ analytics }, { actions, cloud }] = await getStartServices(); + const { client } = (await context.core).elasticsearch; const aiClient = Assist({ es_client: client.asCurrentUser, @@ -149,7 +150,13 @@ export function defineRoutes({ isCitationsEnabled: data.citations, }); - return handleStreamResponse({ logger, stream, response, request }); + return handleStreamResponse({ + logger, + stream, + response, + request, + isCloud: cloud?.isCloudEnabled ?? false, + }); } catch (e) { logger.error('Failed to create the chat stream', e); diff --git a/x-pack/plugins/search_playground/server/types.ts b/x-pack/plugins/search_playground/server/types.ts index ecec570a85caf..c3f1e8571a6b7 100644 --- a/x-pack/plugins/search_playground/server/types.ts +++ b/x-pack/plugins/search_playground/server/types.ts @@ -6,14 +6,17 @@ */ import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; +import { CloudStart } from '@kbn/cloud-plugin/server'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SearchPlaygroundPluginSetup {} + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SearchPlaygroundPluginStart {} export interface SearchPlaygroundPluginStartDependencies { actions: ActionsPluginStartContract; + cloud?: CloudStart; } export * from '../common/types'; diff --git a/x-pack/plugins/search_playground/server/utils/handle_stream_response.test.ts b/x-pack/plugins/search_playground/server/utils/handle_stream_response.test.ts index 464a2bf8548d0..a4ddf1178e00e 100644 --- a/x-pack/plugins/search_playground/server/utils/handle_stream_response.test.ts +++ b/x-pack/plugins/search_playground/server/utils/handle_stream_response.test.ts @@ -7,7 +7,7 @@ import { handleStreamResponse } from './handle_stream_response'; -jest.mock('@kbn/ml-response-stream/server', () => ({ +jest.mock('./stream_factory', () => ({ streamFactory: jest.fn().mockReturnValue({ end: jest.fn(), push: jest.fn(), diff --git a/x-pack/plugins/search_playground/server/utils/handle_stream_response.ts b/x-pack/plugins/search_playground/server/utils/handle_stream_response.ts index 2e2ed7c9e765b..3f08a698a07f8 100644 --- a/x-pack/plugins/search_playground/server/utils/handle_stream_response.ts +++ b/x-pack/plugins/search_playground/server/utils/handle_stream_response.ts @@ -5,41 +5,41 @@ * 2.0. */ -import { streamFactory } from '@kbn/ml-response-stream/server'; import type { KibanaRequest, KibanaResponseFactory, Logger } from '@kbn/core/server'; -const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +import { streamFactory } from './stream_factory'; export const handleStreamResponse = async ({ stream, request, response, logger, - maxTimeoutMs = 250, + isCloud = false, }: { stream: ReadableStream; logger: Logger; request: KibanaRequest; response: KibanaResponseFactory; maxTimeoutMs?: number; + isCloud?: boolean; }) => { - const { end, push, responseWithHeaders } = streamFactory(request.headers, logger); - const reader = (stream as ReadableStream).getReader(); + const { end, push, responseWithHeaders } = streamFactory(logger, isCloud); + const reader = stream.getReader(); const textDecoder = new TextDecoder(); - let handleStopRequest = false; + const abortController = new AbortController(); + request.events.aborted$.subscribe(() => { - handleStopRequest = true; + abortController.abort(); }); request.events.completed$.subscribe(() => { - handleStopRequest = true; + abortController.abort(); }); async function pushStreamUpdate() { try { const { done, value }: { done: boolean; value?: Uint8Array } = await reader.read(); - - if (done || handleStopRequest) { + if (done || abortController.signal.aborted) { end(); return; } @@ -54,15 +54,21 @@ export const handleStreamResponse = async ({ push(decodedValue); - await timeout(Math.floor(Math.random() * maxTimeoutMs)); - void pushStreamUpdate(); - } catch (e) { - logger.error(`There was an error: ${e.toString()}`); + } catch (error) { + logger.error(`Error occurred while pushing the next chunk: ${error.toString()}`); + end(); + abortController.abort(); + throw error; } } - void pushStreamUpdate(); + try { + void pushStreamUpdate(); + } catch (error) { + logger.error('Failed to push stream update', error); + throw error; + } return response.ok(responseWithHeaders); }; diff --git a/x-pack/plugins/search_playground/server/utils/stream_factory.test.ts b/x-pack/plugins/search_playground/server/utils/stream_factory.test.ts new file mode 100644 index 0000000000000..e6085377b7802 --- /dev/null +++ b/x-pack/plugins/search_playground/server/utils/stream_factory.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { streamFactory } from './stream_factory'; +import { Logger } from '@kbn/logging'; +import { PassThrough } from 'stream'; + +describe('streamFactory', () => { + it('should create a stream with the correct initial state', () => { + const logger = { + error: jest.fn(), + } as unknown as Logger; + + const { DELIMITER, responseWithHeaders } = streamFactory(logger); + + expect(DELIMITER).toBe('\n'); + expect(responseWithHeaders.headers).toEqual({ + 'X-Accel-Buffering': 'no', + 'X-Content-Type-Options': 'nosniff', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Transfer-Encoding': 'chunked', + }); + expect(responseWithHeaders.body).toBeInstanceOf(PassThrough); + }); + + it('should push data to the stream correctly', () => { + const logger = { + error: jest.fn(), + } as unknown as Logger; + + const { push, responseWithHeaders } = streamFactory(logger); + + const data = 'test data'; + push(data); + + let output = ''; + responseWithHeaders.body.on('data', (chunk) => { + output += chunk.toString(); + }); + + responseWithHeaders.body.end(() => { + expect(output).toContain(data); + }); + }); + + it('should handle flush buffer mechanism', () => { + const logger = { + error: jest.fn(), + } as unknown as Logger; + + const { push, responseWithHeaders } = streamFactory(logger); + + const data = 'short'; + push(data); + + let output = ''; + responseWithHeaders.body.on('data', (chunk) => { + output += chunk.toString(); + }); + + responseWithHeaders.body.end(() => { + expect(output).toContain(data); + }); + }); +}); diff --git a/x-pack/plugins/search_playground/server/utils/stream_factory.ts b/x-pack/plugins/search_playground/server/utils/stream_factory.ts new file mode 100644 index 0000000000000..19f56e3890480 --- /dev/null +++ b/x-pack/plugins/search_playground/server/utils/stream_factory.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. + */ + +// adapted from @kbn/ml-response-stream with the following changes: +// - removed gzip compression +// - removed support for ndjson +// - improved the cloud proxy buffer to work for our use case (works for newline string chunks vs ndjson only) + +import type { Logger } from '@kbn/core/server'; + +import { PassThrough } from 'stream'; + +import type { ResponseHeaders } from '@kbn/core-http-server'; +import { repeat } from 'lodash'; + +const DELIMITER = ` +`; + +export interface StreamResponseWithHeaders { + body: PassThrough; + headers?: ResponseHeaders; +} + +export interface StreamFactoryReturnType { + DELIMITER: string; + end: () => void; + push: (d: string) => void; + responseWithHeaders: StreamResponseWithHeaders; +} + +export function streamFactory(logger: Logger, flushFix: boolean = true): StreamFactoryReturnType { + const cloudProxyBufferSize = 4096; + + const flushPayload = flushFix + ? DELIMITER + '10: "' + repeat('0', cloudProxyBufferSize * 2) + '"' + DELIMITER + : undefined; + let currentBufferSize = 0; + + const stream = new PassThrough(); + const backPressureBuffer: string[] = []; + let tryToEnd = false; + + const flushBufferIfNeeded = () => { + if (currentBufferSize && currentBufferSize <= cloudProxyBufferSize) { + push(flushPayload as unknown as string); + currentBufferSize = 0; + } + }; + + const flushIntervalId = setInterval(flushBufferIfNeeded, 250); + + function end() { + tryToEnd = true; + clearInterval(flushIntervalId); + + if (backPressureBuffer.length > 0) { + const el = backPressureBuffer.shift(); + if (el !== undefined) { + push(el); + } + return; + } + + stream.end(); + } + + function push(d: string) { + if (d === undefined) { + logger.error('Stream chunk must not be undefined.'); + return; + } + + if (backPressureBuffer.length > 0) { + backPressureBuffer.push(d); + return; + } + + if (tryToEnd) { + return; + } + + try { + const line = d as unknown as string; + const writeOk = stream.write(line); + + currentBufferSize = + currentBufferSize <= cloudProxyBufferSize + ? JSON.stringify(line).length + currentBufferSize + : cloudProxyBufferSize; + + if (!writeOk) { + backPressureBuffer.push(d); + } + } catch (e) { + logger.error(`Could not serialize or stream data chunk: ${e.toString()}`); + } + } + + const responseWithHeaders: StreamResponseWithHeaders = { + body: stream, + headers: { + 'X-Accel-Buffering': 'no', + 'X-Content-Type-Options': 'nosniff', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Transfer-Encoding': 'chunked', + }, + }; + + return { DELIMITER, end, push, responseWithHeaders }; +} diff --git a/x-pack/plugins/search_playground/tsconfig.json b/x-pack/plugins/search_playground/tsconfig.json index 7e7217975cc85..20ede9bad89ad 100644 --- a/x-pack/plugins/search_playground/tsconfig.json +++ b/x-pack/plugins/search_playground/tsconfig.json @@ -17,7 +17,6 @@ "@kbn/i18n", "@kbn/i18n-react", "@kbn/kibana-react-plugin", - "@kbn/ml-response-stream", "@kbn/security-plugin", "@kbn/user-profile-components", "@kbn/shared-ux-router", diff --git a/x-pack/plugins/security/common/index.ts b/x-pack/plugins/security/common/index.ts index 1f5767fc56b64..2d5e6fd6ec7f1 100644 --- a/x-pack/plugins/security/common/index.ts +++ b/x-pack/plugins/security/common/index.ts @@ -12,6 +12,11 @@ export type { BuiltinESPrivileges, RawKibanaPrivileges, RoleMapping, + RoleMappingRule, + RoleMappingAllRule, + RoleMappingAnyRule, + RoleMappingExceptRule, + RoleMappingFieldRule, RoleTemplate, StoredRoleTemplate, InvalidRoleTemplate, diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index 4ad46b29212d8..1e73ead22655e 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -40,4 +40,9 @@ export type { InvalidRoleTemplate, RoleTemplate, RoleMapping, + RoleMappingRule, + RoleMappingAllRule, + RoleMappingAnyRule, + RoleMappingExceptRule, + RoleMappingFieldRule, } from './role_mapping'; diff --git a/x-pack/plugins/security/common/model/role_mapping.ts b/x-pack/plugins/security/common/model/role_mapping.ts index 1c6e43e8c5a7a..06520e8169909 100644 --- a/x-pack/plugins/security/common/model/role_mapping.ts +++ b/x-pack/plugins/security/common/model/role_mapping.ts @@ -5,23 +5,23 @@ * 2.0. */ -interface RoleMappingAnyRule { +export interface RoleMappingAnyRule { any: RoleMappingRule[]; } -interface RoleMappingAllRule { +export interface RoleMappingAllRule { all: RoleMappingRule[]; } -interface RoleMappingFieldRule { +export interface RoleMappingFieldRule { field: Record<string, any>; } -interface RoleMappingExceptRule { +export interface RoleMappingExceptRule { except: RoleMappingRule; } -type RoleMappingRule = +export type RoleMappingRule = | RoleMappingAnyRule | RoleMappingAllRule | RoleMappingFieldRule diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx index 8e2f27c2db817..30102964b3438 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx @@ -437,7 +437,18 @@ function UserAvatarEditor({ ) } onChange={createImageHandler((imageUrl) => { - formik.setFieldValue('data.avatar.imageUrl', imageUrl ?? ''); + if (!imageUrl) { + formik.setFieldError( + 'data.avatar.imageUrl', + i18n.translate( + 'xpack.security.accountManagement.userProfile.imageUrlRequiredError', + { defaultMessage: 'Upload an image.' } + ) + ); + formik.setFieldTouched('data.avatar.imageUrl', true); + } else { + formik.setFieldValue('data.avatar.imageUrl', imageUrl ?? ''); + } })} validate={{ required: i18n.translate( diff --git a/x-pack/plugins/security/public/account_management/user_profile/utils.ts b/x-pack/plugins/security/public/account_management/user_profile/utils.ts index 2621ba0b65f1f..31580fd7fdfd2 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/utils.ts +++ b/x-pack/plugins/security/public/account_management/user_profile/utils.ts @@ -62,6 +62,8 @@ export function createImageHandler(callback: (imageUrl: string | undefined) => v const imageUrl = await readFile(file); const resizedImageUrl = await resizeImage(imageUrl, MAX_IMAGE_SIZE); callback(resizedImageUrl); + } else { + callback(undefined); } }; } diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx index 8fdd6dce1e3db..e74d2f7703f31 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx @@ -441,4 +441,43 @@ describe('EditRoleMappingPage', () => { expect(rulePanels).toHaveLength(1); expect(rulePanels.at(0).props().readOnly).toBeTruthy(); }); + + it('renders a warning when empty any or all rules are present', async () => { + const roleMappingsAPI = roleMappingsAPIClientMock.create(); + const securityFeaturesAPI = securityFeaturesAPIClientMock.create(); + roleMappingsAPI.saveRoleMapping.mockResolvedValue(null); + roleMappingsAPI.getRoleMapping.mockResolvedValue({ + name: 'foo', + role_templates: [ + { + template: { id: 'foo' }, + }, + ], + enabled: true, + rules: { + all: [ + { + field: { + username: '*', + }, + }, + { + all: [], + }, + ], + }, + }); + securityFeaturesAPI.checkFeatures.mockResolvedValue({ + canReadSecurity: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }); + + const wrapper = renderView(roleMappingsAPI, securityFeaturesAPI, 'foo'); + await nextTick(); + wrapper.update(); + + expect(findTestSubject(wrapper, 'emptyAnyOrAllRulesWarning')).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx index 7886c55f46ed7..f9f1160bcca79 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx @@ -8,6 +8,7 @@ import { EuiButton, EuiButtonEmpty, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiForm, @@ -26,7 +27,12 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { MappingInfoPanel } from './mapping_info_panel'; import { RuleEditorPanel } from './rule_editor_panel'; import { validateRoleMappingForSave } from './services/role_mapping_validation'; -import type { RoleMapping } from '../../../../common'; +import type { + RoleMapping, + RoleMappingAllRule, + RoleMappingAnyRule, + RoleMappingRule, +} from '../../../../common'; import type { RolesAPIClient } from '../../roles'; import type { SecurityFeaturesAPIClient } from '../../security_features'; import { @@ -176,6 +182,8 @@ export class EditRoleMappingPage extends Component<Props, State> { readOnly={this.props.readOnly} /> <EuiSpacer /> + {this.getFormWarnings()} + <EuiSpacer /> {this.getFormButtons()} </EuiForm> </> @@ -211,6 +219,65 @@ export class EditRoleMappingPage extends Component<Props, State> { ); }; + private isObject = (record?: any): record is object => { + return typeof record === 'object' && record !== null; + }; + + private isRoleMappingAnyRule = (obj: unknown): obj is RoleMappingAnyRule => { + return this.isObject(obj) && 'any' in obj && Array.isArray(obj.any); + }; + + private isRoleMappingAllRule = (obj: unknown): obj is RoleMappingAllRule => { + return this.isObject(obj) && 'all' in obj && Array.isArray(obj.all); + }; + + private checkEmptyAnyAllMappings = (obj: RoleMappingRule) => { + const arrToCheck: RoleMappingRule[] = [obj]; + + while (arrToCheck.length > 0) { + const currentObj = arrToCheck.pop(); + if (this.isObject(obj)) { + for (const key in currentObj) { + if (Object.hasOwn(currentObj, key)) { + if (this.isRoleMappingAnyRule(currentObj)) { + if (currentObj.any.length === 0) { + return true; + } + arrToCheck.push(...currentObj.any); + } else if (this.isRoleMappingAllRule(currentObj)) { + if (currentObj.all.length === 0) { + return true; + } + arrToCheck.push(...currentObj.all); + } else if (this.isObject(currentObj[key as keyof RoleMappingRule])) { + arrToCheck.push(currentObj[key as keyof RoleMappingRule] as RoleMappingRule); + } + } + } + } + } + return false; + }; + + private getFormWarnings = () => { + if (this.checkEmptyAnyAllMappings(this.state.roleMapping!.rules as RoleMappingRule)) { + return ( + <EuiCallOut + title="Warning" + color="warning" + iconType="alert" + data-test-subj="emptyAnyOrAllRulesWarning" + > + <FormattedMessage + id="xpack.security.management.editRoleMapping.emptyAnyAllMappingsWarning" + defaultMessage="Role mapping rules contains empty 'any' or 'all' rules. These empty rule groups will always evaluate to true. Please proceed with caution" + /> + </EuiCallOut> + ); + } + return null; + }; + private getFormButtons = () => { if (this.props.readOnly === true) { return this.getReturnToRoleMappingListButton(); diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts index 3d784f6c1cf3f..a271371e5584d 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.test.ts @@ -362,6 +362,8 @@ describe('usingPrivileges', () => { { privilege: actions.ui.get('kibanaFeature2', 'bar'), authorized: false }, { privilege: actions.ui.get('optOutFeature', 'foo'), authorized: false }, { privilege: actions.ui.get('optOutFeature', 'bar'), authorized: false }, + { privilege: actions.ui.get('spaces', 'manage'), authorized: false }, + { privilege: actions.ui.get('globalSettings', 'show'), authorized: false }, ], elasticsearch: { cluster: [ @@ -419,6 +421,12 @@ describe('usingPrivileges', () => { es_manage_sec: true, }, esManagementFeature: {}, + spaces: { + manage: true, + }, + globalSettings: { + show: true, + }, }) ); @@ -457,6 +465,12 @@ describe('usingPrivileges', () => { es_manage_sec: true, }, esManagementFeature: {}, + spaces: { + manage: false, + }, + globalSettings: { + show: false, + }, }); }); @@ -475,6 +489,8 @@ describe('usingPrivileges', () => { { privilege: actions.ui.get('kibanaFeature2', 'bar'), authorized: true }, { privilege: actions.ui.get('optOutFeature', 'foo'), authorized: true }, { privilege: actions.ui.get('optOutFeature', 'bar'), authorized: true }, + { privilege: actions.ui.get('spaces', 'manage'), authorized: true }, + { privilege: actions.ui.get('globalSettings', 'show'), authorized: true }, ], elasticsearch: { cluster: [ @@ -528,6 +544,12 @@ describe('usingPrivileges', () => { es_manage_sec: false, }, esManagementFeature: {}, + spaces: { + manage: false, + }, + globalSettings: { + show: false, + }, }); const result = await usingPrivileges(allFalseCapabilities); diff --git a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts index cf2429a1d657d..d9a83806abf51 100644 --- a/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts +++ b/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts @@ -75,11 +75,15 @@ export function disableUICapabilitiesFactory( const shouldAffectCapability = (featureId: keyof UICapabilities, uiCapability: string) => { // This method answers: 'Should we affect a capability based on privileges?' - // 'spaces' and 'fileUpload' feature ID's are handled independently - // The spaces and file_upload plugins have their own capabilites switchers + // 'fileUpload' feature ID is handled independently + // The spaces and file_upload plugins have their own capabilities switchers. - // Always affect global settings - if (featureId === 'globalSettings') { + // The spaces capabilities switcher handles disabling capabilities within a specific + // space, but the root 'spaces' feature ID needs to be affected here, where we can check + // if the current user is privileged to for general spaces capabilities (e.g., manage). + + // Always affect global settings, and the "spaces" feature (see above) + if (featureId === 'globalSettings' || featureId === 'spaces') { return true; } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/index.ts b/x-pack/plugins/security/server/routes/authorization/roles/index.ts index d4e3633f4677e..d5af481ca3c11 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/index.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/index.ts @@ -9,6 +9,7 @@ import { defineDeleteRolesRoutes } from './delete'; import { defineGetRolesRoutes } from './get'; import { defineGetAllRolesRoutes } from './get_all'; import { defineGetAllRolesBySpaceRoutes } from './get_all_by_space'; +import { defineBulkCreateOrUpdateRolesRoutes } from './post'; import { definePutRolesRoutes } from './put'; import type { RouteDefinitionParams } from '../..'; @@ -18,4 +19,5 @@ export function defineRolesRoutes(params: RouteDefinitionParams) { defineDeleteRolesRoutes(params); definePutRolesRoutes(params); defineGetAllRolesBySpaceRoutes(params); + defineBulkCreateOrUpdateRolesRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/authorization/roles/lib/index.ts b/x-pack/plugins/security/server/routes/authorization/roles/lib/index.ts new file mode 100644 index 0000000000000..06318eb2bacd7 --- /dev/null +++ b/x-pack/plugins/security/server/routes/authorization/roles/lib/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 { roleGrantsSubFeaturePrivileges } from './role_privileges'; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/lib/role_privileges.ts b/x-pack/plugins/security/server/routes/authorization/roles/lib/role_privileges.ts new file mode 100644 index 0000000000000..49413b0d22d89 --- /dev/null +++ b/x-pack/plugins/security/server/routes/authorization/roles/lib/role_privileges.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 { KibanaFeature } from '@kbn/features-plugin/common'; + +import type { RolePayloadSchemaType } from '../model/put_payload'; + +export const roleGrantsSubFeaturePrivileges = ( + features: KibanaFeature[], + role: RolePayloadSchemaType +) => { + if (!role.kibana) { + return false; + } + + const subFeaturePrivileges = new Map( + features.map((feature) => [ + feature.id, + feature.subFeatures.map((sf) => sf.privilegeGroups.map((pg) => pg.privileges)).flat(2), + ]) + ); + + const hasAnySubFeaturePrivileges = role.kibana.some((kibanaPrivilege) => + Object.entries(kibanaPrivilege.feature ?? {}).some(([featureId, privileges]) => { + return !!subFeaturePrivileges.get(featureId)?.some(({ id }) => privileges.includes(id)); + }) + ); + + return hasAnySubFeaturePrivileges; +}; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/bulk_create_or_update_payload.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/bulk_create_or_update_payload.test.ts new file mode 100644 index 0000000000000..c26bb9ff24793 --- /dev/null +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/bulk_create_or_update_payload.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getBulkCreateOrUpdatePayloadSchema } from './bulk_create_or_update_payload'; + +describe('getBulkCreateOrUpdatePayloadSchema', () => { + const mockGetBasePrivilegeNames = jest.fn(() => ({ + global: ['all', 'read'], + space: ['all', 'read'], + })); + + const bulkCreateOrUpdatePayloadSchema = + getBulkCreateOrUpdatePayloadSchema(mockGetBasePrivilegeNames); + + it('should validate a correct payload', () => { + const payload = { + roles: { + role1: { + kibana: [ + { + feature: { + feature1: ['all'], + }, + spaces: ['*'], + }, + ], + }, + }, + }; + + expect(() => bulkCreateOrUpdatePayloadSchema.validate(payload)).not.toThrow(); + }); + + it('should throw an error for missing roles', () => { + const payload = {}; + + expect(() => + bulkCreateOrUpdatePayloadSchema.validate(payload) + ).toThrowErrorMatchingInlineSnapshot( + `"[roles]: expected value of type [object] but got [undefined]"` + ); + }); + + it('should throw an error for invalid role structure', () => { + const payload = { + roles: { + role1: 'invalid_structure', + }, + }; + + expect(() => + bulkCreateOrUpdatePayloadSchema.validate(payload) + ).toThrowErrorMatchingInlineSnapshot( + `"[roles.role1]: could not parse object value from json input"` + ); + }); +}); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/bulk_create_or_update_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/bulk_create_or_update_payload.ts new file mode 100644 index 0000000000000..cfe21261719f9 --- /dev/null +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/bulk_create_or_update_payload.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 type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +import { getPutPayloadSchema } from './put_payload'; + +export function getBulkCreateOrUpdatePayloadSchema( + getBasePrivilegeNames: () => { global: string[]; space: string[] } +) { + return schema.object({ + roles: schema.recordOf(schema.string(), getPutPayloadSchema(getBasePrivilegeNames)), + }); +} + +export type BulkCreateOrUpdateRolesPayloadSchemaType = TypeOf< + ReturnType<typeof getBulkCreateOrUpdatePayloadSchema> +>; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts index f42d6f01573a9..3b9e59f10046b 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/index.ts @@ -7,3 +7,6 @@ export type { RolePayloadSchemaType } from './put_payload'; export { transformPutPayloadToElasticsearchRole, getPutPayloadSchema } from './put_payload'; + +export { getBulkCreateOrUpdatePayloadSchema } from './bulk_create_or_update_payload'; +export type { BulkCreateOrUpdateRolesPayloadSchemaType } from './bulk_create_or_update_payload'; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/post.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/post.test.ts new file mode 100644 index 0000000000000..0ef752423606d --- /dev/null +++ b/x-pack/plugins/security/server/routes/authorization/roles/post.test.ts @@ -0,0 +1,999 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kibanaResponseFactory } from '@kbn/core/server'; +import { coreMock, httpServerMock } from '@kbn/core/server/mocks'; +import { KibanaFeature } from '@kbn/features-plugin/server'; +import type { LicenseCheck } from '@kbn/licensing-plugin/server'; +import { GLOBAL_RESOURCE } from '@kbn/security-plugin-types-server'; + +import type { BulkCreateOrUpdateRolesPayloadSchemaType } from './model/bulk_create_or_update_payload'; +import { defineBulkCreateOrUpdateRolesRoutes } from './post'; +import { securityFeatureUsageServiceMock } from '../../../feature_usage/index.mock'; +import { routeDefinitionParamsMock } from '../../index.mock'; + +const application = 'kibana-.kibana'; +const privilegeMap = { + global: { + all: [], + read: [], + }, + space: { + all: [], + read: [], + }, + features: { + foo: { + 'foo-privilege-1': [], + 'foo-privilege-2': [], + }, + bar: { + 'bar-privilege-1': [], + 'bar-privilege-2': [], + }, + }, + reserved: { + customApplication1: [], + customApplication2: [], + }, +}; + +const kibanaFeature = new KibanaFeature({ + id: 'bar', + name: 'bar', + privileges: { + all: { + requireAllSpaces: true, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + read: { + disabled: true, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + app: [], + category: { id: 'bar', label: 'bar' }, +}); + +interface TestOptions { + licenseCheckResult?: LicenseCheck; + apiResponses?: { + get: () => unknown; + post: () => Record<string, unknown>; + }; + payload: BulkCreateOrUpdateRolesPayloadSchemaType; + asserts: { + statusCode: number; + result?: Record<string, any>; + apiArguments?: { get: unknown[]; post: unknown[] }; + recordSubFeaturePrivilegeUsage?: boolean; + }; + features?: KibanaFeature[]; +} + +const postRolesTest = ( + description: string, + { payload, licenseCheckResult = { state: 'valid' }, apiResponses, asserts, features }: TestOptions +) => { + test(description, async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.authz.applicationName = application; + mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); + + const mockCoreContext = coreMock.createRequestHandlerContext(); + const mockLicensingContext = { + license: { check: jest.fn().mockReturnValue(licenseCheckResult) }, + } as any; + const mockContext = coreMock.createCustomRequestHandlerContext({ + core: mockCoreContext, + licensing: mockLicensingContext, + }); + + if (apiResponses?.get) { + mockCoreContext.elasticsearch.client.asCurrentUser.security.getRole.mockResponseImplementationOnce( + (() => ({ body: apiResponses?.get() })) as any + ); + } + + if (apiResponses?.post) { + mockCoreContext.elasticsearch.client.asCurrentUser.transport.request.mockImplementationOnce( + (() => ({ ...apiResponses?.post() })) as any + ); + } + + mockRouteDefinitionParams.getFeatureUsageService.mockReturnValue( + securityFeatureUsageServiceMock.createStartContract() + ); + + mockRouteDefinitionParams.getFeatures.mockResolvedValue( + features ?? [ + new KibanaFeature({ + id: 'feature_1', + name: 'feature 1', + app: [], + category: { id: 'foo', label: 'foo' }, + privileges: { + all: { + ui: [], + savedObject: { all: [], read: [] }, + }, + read: { + ui: [], + savedObject: { all: [], read: [] }, + }, + }, + subFeatures: [ + { + name: 'sub feature 1', + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'sub_feature_privilege_1', + name: 'first sub-feature privilege', + includeIn: 'none', + ui: [], + savedObject: { all: [], read: [] }, + }, + ], + }, + ], + }, + ], + }), + ] + ); + + defineBulkCreateOrUpdateRolesRoutes(mockRouteDefinitionParams); + const [[{ validate }, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'post', + path: '/api/security/roles', + body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, + headers, + }); + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (asserts.apiArguments?.get) { + expect( + mockCoreContext.elasticsearch.client.asCurrentUser.security.getRole + ).toHaveBeenCalledWith(...asserts.apiArguments?.get); + } + if (asserts.apiArguments?.post) { + const [body] = asserts.apiArguments?.post ?? []; + expect( + mockCoreContext.elasticsearch.client.asCurrentUser.transport.request + ).toHaveBeenCalledWith({ + method: 'POST', + path: '/_security/role', + body, + }); + } + expect(mockLicensingContext.license.check).toHaveBeenCalledWith('security', 'basic'); + + if (asserts.recordSubFeaturePrivilegeUsage) { + expect( + mockRouteDefinitionParams.getFeatureUsageService().recordSubFeaturePrivilegeUsage + ).toHaveBeenCalledTimes( + (response.payload?.created?.length ?? 0) + + (response.payload?.updated?.length ?? 0) + + (response.payload?.noop?.length ?? 0) + ); + } else { + expect( + mockRouteDefinitionParams.getFeatureUsageService().recordSubFeaturePrivilegeUsage + ).not.toHaveBeenCalled(); + } + }); +}; + +describe('POST roles', () => { + describe('failure', () => { + postRolesTest('returns result of license checker', { + payload: { roles: {} } as BulkCreateOrUpdateRolesPayloadSchemaType, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, + asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, + }); + }); + + describe('success', () => { + postRolesTest(`creates empty roles`, { + payload: { + roles: { + 'role-1': { + elasticsearch: {}, + kibana: [], + }, + 'role-2': { + elasticsearch: {}, + kibana: [], + }, + }, + }, + apiResponses: { + get: () => ({}), + post: () => ({ created: ['role-1', 'role-2'] }), + }, + asserts: { + apiArguments: { + get: [{ name: 'role-1,role-2' }, { ignore: [404] }], + post: [ + { + roles: { + 'role-1': { + applications: [], + cluster: [], + indices: [], + remote_indices: undefined, + remote_cluster: undefined, + run_as: [], + metadata: undefined, + }, + 'role-2': { + applications: [], + cluster: [], + indices: [], + remote_indices: undefined, + remote_cluster: undefined, + run_as: [], + metadata: undefined, + }, + }, + }, + ], + }, + statusCode: 200, + result: { created: ['role-1', 'role-2'] }, + }, + }); + + postRolesTest('returns validation errors', { + payload: { + roles: { + 'role-1': { + elasticsearch: {}, + kibana: [ + { + spaces: ['bar-space'], + base: [], + feature: { + bar: ['all', 'read'], + }, + }, + ], + }, + }, + }, + apiResponses: { + get: () => ({}), + post: () => ({}), + }, + features: [kibanaFeature], + asserts: { + statusCode: 200, + result: { + errors: { + 'role-1': { + type: 'kibana_privilege_validation_exception', + reason: + 'Role cannot be updated due to validation errors: ["Feature privilege [bar.all] requires all spaces to be selected but received [bar-space]","Feature [bar] does not support privilege [read]."]', + }, + }, + }, + }, + }); + + postRolesTest(`returns errors for not updated/created roles`, { + payload: { + roles: { + 'role-1': { + elasticsearch: {}, + kibana: [ + { + spaces: ['bar-space'], + base: [], + feature: { + bar: ['all', 'read'], + }, + }, + ], + }, + 'role-2': { + elasticsearch: { + indices: [ + { + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test'], + }, + ], + }, + }, + 'role-3': { + elasticsearch: {}, + kibana: [], + }, + }, + }, + features: [kibanaFeature], + apiResponses: { + get: () => ({}), + post: () => ({ + created: ['role-3'], + errors: { + count: 1, + details: { + 'role-2': { + type: 'action_request_validation_exception', + reason: 'Validation Failed', + }, + }, + }, + }), + }, + asserts: { + apiArguments: { + get: [{ name: 'role-2,role-3' }, { ignore: [404] }], + post: [ + { + roles: { + 'role-2': { + applications: [], + cluster: [], + indices: [ + { + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test'], + }, + ], + remote_indices: undefined, + remote_cluster: undefined, + run_as: [], + metadata: undefined, + }, + 'role-3': { + applications: [], + cluster: [], + indices: [], + remote_indices: undefined, + remote_cluster: undefined, + run_as: [], + metadata: undefined, + }, + }, + }, + ], + }, + statusCode: 200, + result: { + created: ['role-3'], + errors: { + 'role-1': { + type: 'kibana_privilege_validation_exception', + reason: + 'Role cannot be updated due to validation errors: ["Feature privilege [bar.all] requires all spaces to be selected but received [bar-space]","Feature [bar] does not support privilege [read]."]', + }, + 'role-2': { + reason: 'Validation Failed', + type: 'action_request_validation_exception', + }, + }, + }, + }, + }); + + postRolesTest( + `creates non-existing role and updates role which has existing kibana privileges`, + { + payload: { + roles: { + 'new-role': { + kibana: [], + elasticsearch: { + remote_cluster: [ + { + clusters: ['cluster1', 'cluster2'], + privileges: ['monitor_enrich'], + }, + { + clusters: ['cluster3', 'cluster4'], + privileges: ['monitor_enrich'], + }, + ], + }, + }, + 'existing-role': { + elasticsearch: { + cluster: ['test-cluster-privilege'], + indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + kibana: [ + { + feature: { + foo: ['foo-privilege-1'], + bar: ['bar-privilege-1'], + }, + spaces: ['*'], + }, + { + base: ['all'], + spaces: ['test-space-1', 'test-space-2'], + }, + { + feature: { + bar: ['bar-privilege-2'], + }, + spaces: ['test-space-3'], + }, + ], + }, + }, + }, + apiResponses: { + get: () => ({ + 'existing-role': { + cluster: ['old-cluster-privilege'], + indices: [ + { + field_security: { + grant: ['old-field-security-grant-1', 'old-field-security-grant-2'], + except: ['old-field-security-except-1', 'old-field-security-except-2'], + }, + names: ['old-index-name'], + privileges: ['old-privilege'], + query: `{ "match": { "old-title": "foo" } }`, + }, + ], + run_as: ['old-run-as'], + applications: [ + { + application, + privileges: ['old-kibana-privilege'], + resources: ['old-resource'], + }, + ], + }, + }), + post: () => ({ updated: ['existing-role'], created: ['new-role'] }), + }, + asserts: { + apiArguments: { + get: [{ name: 'new-role,existing-role' }, { ignore: [404] }], + post: [ + { + roles: { + 'new-role': { + applications: [], + cluster: [], + indices: [], + remote_indices: undefined, + run_as: [], + remote_cluster: [ + { + clusters: ['cluster1', 'cluster2'], + privileges: ['monitor_enrich'], + }, + { + clusters: ['cluster3', 'cluster4'], + privileges: ['monitor_enrich'], + }, + ], + metadata: undefined, + }, + 'existing-role': { + applications: [ + { + application, + privileges: ['feature_foo.foo-privilege-1', 'feature_bar.bar-privilege-1'], + resources: [GLOBAL_RESOURCE], + }, + { + application, + privileges: ['space_all'], + resources: ['space:test-space-1', 'space:test-space-2'], + }, + { + application, + privileges: ['feature_bar.bar-privilege-2'], + resources: ['space:test-space-3'], + }, + ], + cluster: ['test-cluster-privilege'], + indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + remote_indices: undefined, + metadata: undefined, + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + }, + }, + ], + }, + statusCode: 200, + result: { updated: ['existing-role'], created: ['new-role'] }, + }, + } + ); + + postRolesTest(`notifies when sub-feature privileges are included`, { + payload: { + roles: { + 'role-1': { + elasticsearch: {}, + kibana: [ + { + spaces: ['*'], + feature: { + feature_1: ['sub_feature_privilege_1'], + }, + }, + ], + }, + 'role-2': { + elasticsearch: {}, + kibana: [ + { + spaces: ['*'], + feature: { + feature_1: ['sub_feature_privilege_1'], + }, + }, + ], + }, + }, + }, + apiResponses: { + get: () => ({}), + post: () => ({ created: ['role-1', 'role-2'] }), + }, + asserts: { + recordSubFeaturePrivilegeUsage: true, + apiArguments: { + get: [{ name: 'role-1,role-2' }, { ignore: [404] }], + post: [ + { + roles: { + 'role-1': { + cluster: [], + indices: [], + remote_indices: undefined, + run_as: [], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_feature_1.sub_feature_privilege_1'], + resources: ['*'], + }, + ], + metadata: undefined, + }, + 'role-2': { + cluster: [], + indices: [], + remote_indices: undefined, + run_as: [], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_feature_1.sub_feature_privilege_1'], + resources: ['*'], + }, + ], + metadata: undefined, + }, + }, + }, + ], + }, + statusCode: 200, + result: { created: ['role-1', 'role-2'] }, + }, + }); + + postRolesTest(`creates roles with everything`, { + payload: { + roles: { + 'role-1': { + description: 'role 1 test description', + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['test-cluster-privilege'], + indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + remote_indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + clusters: ['test-cluster-name-1', 'test-cluster-name-2'], + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + kibana: [ + { + base: ['all', 'read'], + spaces: ['*'], + }, + { + base: ['all', 'read'], + spaces: ['test-space-1', 'test-space-2'], + }, + { + feature: { + foo: ['foo-privilege-1', 'foo-privilege-2'], + }, + spaces: ['test-space-3'], + }, + ], + }, + 'role-2': { + description: 'role 2 test description', + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['test-cluster-privilege'], + remote_cluster: [ + { + clusters: ['cluster1', 'cluster2'], + privileges: ['monitor_enrich'], + }, + { + clusters: ['cluster3', 'cluster4'], + privileges: ['monitor_enrich'], + }, + ], + indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + remote_indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + clusters: ['test-cluster-name-1', 'test-cluster-name-2'], + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + kibana: [ + { + base: ['all', 'read'], + spaces: ['test-space-1', 'test-space-2'], + }, + { + feature: { + foo: ['foo-privilege-1', 'foo-privilege-2'], + }, + spaces: ['test-space-3'], + }, + ], + }, + }, + }, + apiResponses: { + get: () => ({}), + post: () => ({ created: ['role-1', 'role-2'] }), + }, + asserts: { + apiArguments: { + get: [{ name: 'role-1,role-2' }, { ignore: [404] }], + post: [ + { + roles: { + 'role-1': { + applications: [ + { + application, + privileges: ['all', 'read'], + resources: [GLOBAL_RESOURCE], + }, + { + application, + privileges: ['space_all', 'space_read'], + resources: ['space:test-space-1', 'space:test-space-2'], + }, + { + application, + privileges: ['feature_foo.foo-privilege-1', 'feature_foo.foo-privilege-2'], + resources: ['space:test-space-3'], + }, + ], + cluster: ['test-cluster-privilege'], + description: 'role 1 test description', + indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + remote_indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + clusters: ['test-cluster-name-1', 'test-cluster-name-2'], + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + metadata: { foo: 'test-metadata' }, + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + 'role-2': { + applications: [ + { + application, + privileges: ['space_all', 'space_read'], + resources: ['space:test-space-1', 'space:test-space-2'], + }, + { + application, + privileges: ['feature_foo.foo-privilege-1', 'feature_foo.foo-privilege-2'], + resources: ['space:test-space-3'], + }, + ], + cluster: ['test-cluster-privilege'], + remote_cluster: [ + { + clusters: ['cluster1', 'cluster2'], + privileges: ['monitor_enrich'], + }, + { + clusters: ['cluster3', 'cluster4'], + privileges: ['monitor_enrich'], + }, + ], + description: 'role 2 test description', + indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + remote_indices: [ + { + field_security: { + grant: ['test-field-security-grant-1', 'test-field-security-grant-2'], + except: ['test-field-security-except-1', 'test-field-security-except-2'], + }, + clusters: ['test-cluster-name-1', 'test-cluster-name-2'], + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + query: `{ "match": { "title": "foo" } }`, + }, + ], + metadata: { foo: 'test-metadata' }, + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + }, + }, + ], + }, + statusCode: 200, + result: { created: ['role-1', 'role-2'] }, + }, + }); + + postRolesTest(`updates roles which have existing other application privileges`, { + payload: { + roles: { + 'role-1': { + elasticsearch: { + cluster: ['test-cluster-privilege'], + indices: [ + { + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + }, + ], + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + kibana: [ + { + base: ['all', 'read'], + spaces: ['*'], + }, + ], + }, + 'role-2': { + elasticsearch: { + cluster: ['test-cluster-privilege'], + indices: [ + { + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + }, + ], + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + kibana: [ + { + base: ['all', 'read'], + spaces: ['*'], + }, + ], + }, + }, + }, + apiResponses: { + get: () => ({ + 'role-1': { + cluster: ['old-cluster-privilege'], + indices: [], + run_as: ['old-run-as'], + applications: [ + { + application, + privileges: ['old-kibana-privilege'], + resources: ['old-resource'], + }, + { + application: 'logstash-foo', + privileges: ['logstash-privilege'], + resources: ['logstash-resource'], + }, + ], + }, + 'role-2': { + cluster: ['old-cluster-privilege'], + indices: [ + { + names: ['old-index-name'], + privileges: ['old-privilege'], + }, + ], + run_as: ['old-run-as'], + applications: [ + { + application, + privileges: ['old-kibana-privilege'], + resources: ['old-resource'], + }, + { + application: 'beats-foo', + privileges: ['beats-privilege'], + resources: ['beats-resource'], + }, + ], + }, + }), + post: () => ({ updated: ['role-1', 'role-2'] }), + }, + asserts: { + apiArguments: { + get: [{ name: 'role-1,role-2' }, { ignore: [404] }], + post: [ + { + roles: { + 'role-1': { + applications: [ + { + application, + privileges: ['all', 'read'], + resources: [GLOBAL_RESOURCE], + }, + { + application: 'logstash-foo', + privileges: ['logstash-privilege'], + resources: ['logstash-resource'], + }, + ], + cluster: ['test-cluster-privilege'], + indices: [ + { + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + }, + ], + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + 'role-2': { + applications: [ + { + application, + privileges: ['all', 'read'], + resources: [GLOBAL_RESOURCE], + }, + { + application: 'beats-foo', + privileges: ['beats-privilege'], + resources: ['beats-resource'], + }, + ], + cluster: ['test-cluster-privilege'], + indices: [ + { + names: ['test-index-name-1', 'test-index-name-2'], + privileges: ['test-index-privilege-1', 'test-index-privilege-2'], + }, + ], + run_as: ['test-run-as-1', 'test-run-as-2'], + }, + }, + }, + ], + }, + statusCode: 200, + result: { updated: ['role-1', 'role-2'] }, + }, + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/post.ts b/x-pack/plugins/security/server/routes/authorization/roles/post.ts new file mode 100644 index 0000000000000..07b9886c4072c --- /dev/null +++ b/x-pack/plugins/security/server/routes/authorization/roles/post.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { roleGrantsSubFeaturePrivileges } from './lib'; +import { + getBulkCreateOrUpdatePayloadSchema, + transformPutPayloadToElasticsearchRole, +} from './model'; +import type { RouteDefinitionParams } from '../..'; +import { wrapIntoCustomErrorResponse } from '../../../errors'; +import { validateKibanaPrivileges } from '../../../lib'; +import { createLicensedRouteHandler } from '../../licensed_route_handler'; + +type RolesErrorsDetails = Record< + string, + { + type: string; + reason: string; + } +>; + +interface ESRolesResponse { + noop?: string[]; + created?: string[]; + updated?: string[]; + errors?: { + count: number; + details: RolesErrorsDetails; + }; +} + +export function defineBulkCreateOrUpdateRolesRoutes({ + router, + authz, + getFeatures, + getFeatureUsageService, +}: RouteDefinitionParams) { + router.post( + { + path: '/api/security/roles', + options: { + summary: 'Create or update roles', + }, + validate: { + body: getBulkCreateOrUpdatePayloadSchema(() => { + const privileges = authz.privileges.get(); + return { + global: Object.keys(privileges.global), + space: Object.keys(privileges.space), + }; + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const esClient = (await context.core).elasticsearch.client; + const features = await getFeatures(); + + const { roles } = request.body; + const validatedRolesNames = []; + const kibanaErrors: RolesErrorsDetails = {}; + + for (const [roleName, role] of Object.entries(roles)) { + const { validationErrors } = validateKibanaPrivileges(features, role.kibana); + + if (validationErrors.length) { + kibanaErrors[roleName] = { + type: 'kibana_privilege_validation_exception', + reason: `Role cannot be updated due to validation errors: ${JSON.stringify( + validationErrors + )}`, + }; + + continue; + } + + validatedRolesNames.push(roleName); + } + + const rawRoles = await esClient.asCurrentUser.security.getRole( + { name: validatedRolesNames.join(',') }, + { ignore: [404] } + ); + + const esRolesPayload = Object.fromEntries( + validatedRolesNames.map((roleName) => [ + roleName, + transformPutPayloadToElasticsearchRole( + roles[roleName], + authz.applicationName, + rawRoles[roleName] ? rawRoles[roleName].applications : [] + ), + ]) + ); + + const esResponse = await esClient.asCurrentUser.transport.request<ESRolesResponse>({ + method: 'POST', + path: '/_security/role', + body: { roles: esRolesPayload }, + }); + + for (const roleName of [ + ...(esResponse.created ?? []), + ...(esResponse.updated ?? []), + ...(esResponse.noop ?? []), + ]) { + if (roleGrantsSubFeaturePrivileges(features, roles[roleName])) { + getFeatureUsageService().recordSubFeaturePrivilegeUsage(); + } + } + + const { created, noop, updated, errors: esErrors } = esResponse; + const hasAnyErrors = Object.keys(kibanaErrors).length || esErrors?.count; + + return response.ok({ + body: { + created, + noop, + updated, + ...(hasAnyErrors && { + errors: { ...kibanaErrors, ...(esErrors?.details ?? {}) }, + }), + }, + }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index 3a62b93819bd6..57271235add36 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -6,36 +6,14 @@ */ import { schema } from '@kbn/config-schema'; -import type { KibanaFeature } from '@kbn/features-plugin/common'; -import type { RolePayloadSchemaType } from './model'; +import { roleGrantsSubFeaturePrivileges } from './lib'; import { getPutPayloadSchema, transformPutPayloadToElasticsearchRole } from './model'; import type { RouteDefinitionParams } from '../..'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { validateKibanaPrivileges } from '../../../lib'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -const roleGrantsSubFeaturePrivileges = (features: KibanaFeature[], role: RolePayloadSchemaType) => { - if (!role.kibana) { - return false; - } - - const subFeaturePrivileges = new Map( - features.map((feature) => [ - feature.id, - feature.subFeatures.map((sf) => sf.privilegeGroups.map((pg) => pg.privileges)).flat(2), - ]) - ); - - const hasAnySubFeaturePrivileges = role.kibana.some((kibanaPrivilege) => - Object.entries(kibanaPrivilege.feature ?? {}).some(([featureId, privileges]) => { - return !!subFeaturePrivileges.get(featureId)?.some(({ id }) => privileges.includes(id)); - }) - ); - - return hasAnySubFeaturePrivileges; -}; - export function definePutRolesRoutes({ router, authz, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen.ts index 3640d78cd33b2..a0bf7efe39ea8 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen.ts @@ -10,7 +10,7 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Manage alert tags API endpoint + * title: Set alert tags API endpoint * version: 2023-10-31 */ @@ -18,21 +18,21 @@ import { z } from 'zod'; import { AlertIds, AlertTags } from '../../../model/alert.gen'; -export type ManageAlertTags = z.infer<typeof ManageAlertTags>; -export const ManageAlertTags = z.object({ +export type SetAlertTags = z.infer<typeof SetAlertTags>; +export const SetAlertTags = z.object({ tags_to_add: AlertTags, tags_to_remove: AlertTags, }); -export type ManageAlertTagsRequestBody = z.infer<typeof ManageAlertTagsRequestBody>; -export const ManageAlertTagsRequestBody = z.object({ +export type SetAlertTagsRequestBody = z.infer<typeof SetAlertTagsRequestBody>; +export const SetAlertTagsRequestBody = z.object({ ids: AlertIds, - tags: ManageAlertTags, + tags: SetAlertTags, }); -export type ManageAlertTagsRequestBodyInput = z.input<typeof ManageAlertTagsRequestBody>; +export type SetAlertTagsRequestBodyInput = z.input<typeof SetAlertTagsRequestBody>; /** * Elasticsearch update by query response */ -export type ManageAlertTagsResponse = z.infer<typeof ManageAlertTagsResponse>; -export const ManageAlertTagsResponse = z.object({}).catchall(z.unknown()); +export type SetAlertTagsResponse = z.infer<typeof SetAlertTagsResponse>; +export const SetAlertTagsResponse = z.object({}).catchall(z.unknown()); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.schema.yaml index 97833e368ab16..2e712ed3fec40 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.schema.yaml @@ -1,12 +1,12 @@ openapi: 3.0.0 info: - title: Manage alert tags API endpoint + title: Set alert tags API endpoint version: '2023-10-31' paths: /api/detection_engine/signals/tags: post: x-labels: [serverless, ess] - operationId: ManageAlertTags + operationId: SetAlertTags x-codegen-enabled: true summary: Add and remove detection alert tags description: | @@ -26,7 +26,7 @@ paths: ids: $ref: '../../../model/alert.schema.yaml#/components/schemas/AlertIds' tags: - $ref: '#/components/schemas/ManageAlertTags' + $ref: '#/components/schemas/SetAlertTags' required: - ids - tags @@ -62,7 +62,7 @@ paths: components: schemas: - ManageAlertTags: + SetAlertTags: type: object properties: tags_to_add: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts index a14b36fb7b2c5..2b4ef02f61795 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { ManageAlertTagsRequestBody } from './set_alert_tags.gen'; +import type { SetAlertTagsRequestBody } from './set_alert_tags.gen'; export const getSetAlertTagsRequestMock = ( tagsToAdd: string[] = [], tagsToRemove: string[] = [], ids: string[] = [] -): ManageAlertTagsRequestBody => ({ +): SetAlertTagsRequestBody => ({ tags: { tags_to_add: tagsToAdd, tags_to_remove: tagsToRemove }, ids, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.gen.ts index 071d96b89fe9c..b957a5282d857 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.gen.ts @@ -10,14 +10,14 @@ * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. * * info: - * title: Get alerts index name API endpoint + * title: Read alerts index name API endpoint * version: 2023-10-31 */ import { z } from 'zod'; -export type GetAlertsIndexResponse = z.infer<typeof GetAlertsIndexResponse>; -export const GetAlertsIndexResponse = z.object({ +export type ReadAlertsIndexResponse = z.infer<typeof ReadAlertsIndexResponse>; +export const ReadAlertsIndexResponse = z.object({ name: z.string(), index_mapping_outdated: z.boolean().nullable(), }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.schema.yaml index ddfbf564de2ac..70283f59ef79d 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_index/read_index.schema.yaml @@ -1,14 +1,14 @@ openapi: 3.0.0 info: - title: Get alerts index name API endpoint + title: Read alerts index name API endpoint version: '2023-10-31' paths: /api/detection_engine/index: get: x-labels: [ess] - operationId: GetAlertsIndex + operationId: ReadAlertsIndex x-codegen-enabled: true - summary: Gets the alert index name if it exists + summary: Reads the alert index name if it exists tags: - Alert index API responses: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.gen.ts index f3186b3d730c7..65b42d3d2ae88 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.gen.ts @@ -16,8 +16,8 @@ import { z } from 'zod'; -export type GetPrivilegesResponse = z.infer<typeof GetPrivilegesResponse>; -export const GetPrivilegesResponse = z.object({ +export type ReadPrivilegesResponse = z.infer<typeof ReadPrivilegesResponse>; +export const ReadPrivilegesResponse = z.object({ is_authenticated: z.boolean(), has_encryption_key: z.boolean(), }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.schema.yaml index 6bf04afd814d3..168ad44849014 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/index_management/read_privileges/read_privileges.schema.yaml @@ -6,7 +6,7 @@ paths: /api/detection_engine/privileges: get: x-labels: [serverless, ess] - operationId: GetPrivileges + operationId: ReadPrivileges x-codegen-enabled: true summary: Returns user privileges for the Kibana space description: | diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen.ts new file mode 100644 index 0000000000000..5613a117aceb5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Bootstrap Prebuilt Rules API endpoint + * version: 1 + */ + +import { z } from 'zod'; + +export type PackageInstallStatus = z.infer<typeof PackageInstallStatus>; +export const PackageInstallStatus = z.object({ + /** + * The name of the package + */ + name: z.string(), + /** + * The version of the package + */ + version: z.string(), + /** + * The status of the package installation + */ + status: z.enum(['installed', 'already_installed']), +}); + +export type BootstrapPrebuiltRulesResponse = z.infer<typeof BootstrapPrebuiltRulesResponse>; +export const BootstrapPrebuiltRulesResponse = z.object({ + /** + * The list of packages that were installed or upgraded + */ + packages: z.array(PackageInstallStatus), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml new file mode 100644 index 0000000000000..92cb6ccaf2ad1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.schema.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.0 +info: + title: Bootstrap Prebuilt Rules API endpoint + version: '1' +paths: + /internal/detection_engine/prebuilt_rules/_bootstrap: + post: + x-labels: [ess, serverless] + x-codegen-enabled: true + operationId: BootstrapPrebuiltRules + summary: Bootstrap Prebuilt Rules + description: Ensures that the packages needed for prebuilt detection rules to work are installed and up to date + tags: + - Prebuilt Rules API + responses: + 200: + description: Indicates a successful call + content: + application/json: + schema: + type: object + properties: + packages: + type: array + description: The list of packages that were installed or upgraded + items: + $ref: '#/components/schemas/PackageInstallStatus' + required: + - packages + +components: + schemas: + PackageInstallStatus: + type: object + properties: + name: + type: string + description: The name of the package + version: + type: string + description: The version of the package + status: + type: string + description: The status of the package installation + enum: + - installed + - already_installed + required: + - name + - version + - status diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.ts deleted file mode 100644 index 3c32e7b9d1451..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Prebuilt Rules Status API endpoint - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -export type GetPrebuiltRulesAndTimelinesStatusResponse = z.infer< - typeof GetPrebuiltRulesAndTimelinesStatusResponse ->; -export const GetPrebuiltRulesAndTimelinesStatusResponse = z - .object({ - /** - * The total number of custom rules - */ - rules_custom_installed: z.number().int().min(0), - /** - * The total number of installed prebuilt rules - */ - rules_installed: z.number().int().min(0), - /** - * The total number of available prebuilt rules that are not installed - */ - rules_not_installed: z.number().int().min(0), - /** - * The total number of outdated prebuilt rules - */ - rules_not_updated: z.number().int().min(0), - /** - * The total number of installed prebuilt timelines - */ - timelines_installed: z.number().int().min(0), - /** - * The total number of available prebuilt timelines that are not installed - */ - timelines_not_installed: z.number().int().min(0), - /** - * The total number of outdated prebuilt timelines - */ - timelines_not_updated: z.number().int().min(0), - }) - .strict(); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.schema.yaml deleted file mode 100644 index bc44026806f6f..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.schema.yaml +++ /dev/null @@ -1,59 +0,0 @@ -openapi: 3.0.0 -info: - title: Prebuilt Rules Status API endpoint - version: '2023-10-31' -paths: - /api/detection_engine/rules/prepackaged/_status: - get: - x-labels: [ess] - x-codegen-enabled: true - operationId: GetPrebuiltRulesAndTimelinesStatus - summary: Retrieve the status of prebuilt detection rules and Timelines - description: Retrieve the status of all Elastic prebuilt detection rules and Timelines. - tags: - - Prebuilt Rules API - responses: - 200: - description: Indicates a successful call - content: - application/json: - schema: - type: object - additionalProperties: false - properties: - rules_custom_installed: - type: integer - description: The total number of custom rules - minimum: 0 - rules_installed: - type: integer - description: The total number of installed prebuilt rules - minimum: 0 - rules_not_installed: - type: integer - description: The total number of available prebuilt rules that are not installed - minimum: 0 - rules_not_updated: - type: integer - description: The total number of outdated prebuilt rules - minimum: 0 - timelines_installed: - type: integer - description: The total number of installed prebuilt timelines - minimum: 0 - timelines_not_installed: - type: integer - description: The total number of available prebuilt timelines that are not installed - minimum: 0 - timelines_not_updated: - type: integer - description: The total number of outdated prebuilt timelines - minimum: 0 - required: - - rules_custom_installed - - rules_installed - - rules_not_installed - - rules_not_updated - - timelines_installed - - timelines_not_installed - - timelines_not_updated diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.test.ts deleted file mode 100644 index 1dcf57d10fb50..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.test.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 { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; -import { GetPrebuiltRulesAndTimelinesStatusResponse } from './get_prebuilt_rules_and_timelines_status_route.gen'; - -describe('Get prebuilt rules and timelines status response schema', () => { - test('it should validate an empty prepackaged response with defaults', () => { - const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: 0, - rules_custom_installed: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }; - const result = GetPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); - - expectParseSuccess(result); - expect(result.data).toEqual(payload); - }); - - test('it should not validate an extra invalid field added', () => { - const payload: GetPrebuiltRulesAndTimelinesStatusResponse & { invalid_field: string } = { - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: 0, - rules_custom_installed: 0, - invalid_field: 'invalid', - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }; - const result = GetPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); - - expectParseError(result); - expect(stringifyZodError(result.error)).toEqual( - "Unrecognized key(s) in object: 'invalid_field'" - ); - }); - - test('it should NOT validate an empty prepackaged response with a negative "rules_installed" number', () => { - const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { - rules_installed: -1, - rules_not_installed: 0, - rules_not_updated: 0, - rules_custom_installed: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }; - const result = GetPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); - - expectParseError(result); - expect(stringifyZodError(result.error)).toEqual( - 'rules_installed: Number must be greater than or equal to 0' - ); - }); - - test('it should NOT validate an empty prepackaged response with a negative "rules_not_installed"', () => { - const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { - rules_installed: 0, - rules_not_installed: -1, - rules_not_updated: 0, - rules_custom_installed: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }; - const result = GetPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); - - expectParseError(result); - expect(stringifyZodError(result.error)).toEqual( - 'rules_not_installed: Number must be greater than or equal to 0' - ); - }); - - test('it should NOT validate an empty prepackaged response with a negative "rules_not_updated"', () => { - const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: -1, - rules_custom_installed: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }; - const result = GetPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); - - expectParseError(result); - expect(stringifyZodError(result.error)).toEqual( - 'rules_not_updated: Number must be greater than or equal to 0' - ); - }); - - test('it should NOT validate an empty prepackaged response with a negative "rules_custom_installed"', () => { - const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: 0, - rules_custom_installed: -1, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }; - const result = GetPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); - - expectParseError(result); - expect(stringifyZodError(result.error)).toEqual( - 'rules_custom_installed: Number must be greater than or equal to 0' - ); - }); - - test('it should NOT validate an empty prepackaged response if "rules_installed" is not there', () => { - const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: 0, - rules_custom_installed: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }; - // @ts-expect-error - delete payload.rules_installed; - const result = GetPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); - - expectParseError(result); - expect(stringifyZodError(result.error)).toEqual('rules_installed: Required'); - }); -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts index 08d91061f58e8..02ab5c8a3cc0c 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export * from './get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.gen'; +export * from './read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.gen'; export * from './get_prebuilt_rules_status/get_prebuilt_rules_status_route'; export * from './install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.gen'; export * from './perform_rule_installation/perform_rule_installation_route'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/aggregated_prebuilt_rules_error.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/aggregated_prebuilt_rules_error.ts index 9a90a69c43fe5..19e13ee5515cb 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/aggregated_prebuilt_rules_error.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/aggregated_prebuilt_rules_error.ts @@ -5,11 +5,17 @@ * 2.0. */ -export interface AggregatedPrebuiltRuleError { - message: string; - status_code?: number; - rules: Array<{ - rule_id: string; - name?: string; - }>; -} +import { z } from 'zod'; +import { RuleName, RuleSignatureId } from '../../model/rule_schema/common_attributes.gen'; + +export type AggregatedPrebuiltRuleError = z.infer<typeof AggregatedPrebuiltRuleError>; +export const AggregatedPrebuiltRuleError = z.object({ + message: z.string(), + status_code: z.number().optional(), + rules: z.array( + z.object({ + rule_id: RuleSignatureId, + name: RuleName.optional(), + }) + ), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts index 44885d1932070..87ec80d1ac292 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts @@ -7,6 +7,8 @@ import { isEqual } from 'lodash'; import { MissingVersion } from './three_way_diff'; +import type { RuleDataSource } from '../diffable_rule/diffable_field_types'; +import { DataSourceType } from '../diffable_rule/diffable_field_types'; /** * Result of comparing three versions of a value against each other. @@ -75,6 +77,33 @@ export const determineOrderAgnosticDiffOutcome = <TValue>( }); }; +/** + * Determines diff outcome for `data_source` field + * + * NOTE: uses order agnostic comparison for nested array fields (e.g. `index`) + */ +export const determineDiffOutcomeForDataSource = ( + baseVersion: RuleDataSource | undefined | MissingVersion, + currentVersion: RuleDataSource | undefined, + targetVersion: RuleDataSource | undefined +): ThreeWayDiffOutcome => { + const isBaseVersionMissing = baseVersion === MissingVersion; + + if ( + (isBaseVersionMissing || isIndexPatternDataSourceType(baseVersion)) && + isIndexPatternDataSourceType(currentVersion) && + isIndexPatternDataSourceType(targetVersion) + ) { + return determineOrderAgnosticDiffOutcome( + isBaseVersionMissing ? MissingVersion : baseVersion.index_patterns, + currentVersion.index_patterns, + targetVersion.index_patterns + ); + } + + return determineDiffOutcome(baseVersion, currentVersion, targetVersion); +}; + interface DetermineDiffOutcomeProps { baseEqlCurrent: boolean; baseEqlTarget: boolean; @@ -121,3 +150,8 @@ export const determineIfValueCanUpdate = (diffCase: ThreeWayDiffOutcome): boolea diffCase === ThreeWayDiffOutcome.MissingBaseCanUpdate ); }; + +export const isIndexPatternDataSourceType = ( + version: RuleDataSource | undefined +): version is Extract<RuleDataSource, { type: DataSourceType.index_patterns }> => + version !== undefined && version.type === DataSourceType.index_patterns; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.test.ts new file mode 100644 index 0000000000000..b58a254f9dc49 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.test.ts @@ -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 { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; +import { + PickVersionValues, + RuleUpgradeSpecifier, + UpgradeSpecificRulesRequest, + UpgradeAllRulesRequest, + PerformRuleUpgradeResponseBody, + PerformRuleUpgradeRequestBody, +} from './perform_rule_upgrade_route'; + +describe('Perform Rule Upgrade Route Schemas', () => { + describe('PickVersionValues', () => { + test('validates correct enum values', () => { + const validValues = ['BASE', 'CURRENT', 'TARGET', 'MERGED']; + validValues.forEach((value) => { + const result = PickVersionValues.safeParse(value); + expectParseSuccess(result); + expect(result.data).toBe(value); + }); + }); + + test('rejects invalid enum values', () => { + const invalidValues = ['RESOLVED', 'MALFORMED_STRING']; + invalidValues.forEach((value) => { + const result = PickVersionValues.safeParse(value); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"Invalid enum value. Expected 'BASE' | 'CURRENT' | 'TARGET' | 'MERGED', received '${value}'"` + ); + }); + }); + }); + + describe('RuleUpgradeSpecifier', () => { + const validSpecifier = { + rule_id: 'rule-1', + revision: 1, + version: 1, + pick_version: 'TARGET', + }; + + test('validates a valid upgrade specifier without fields property', () => { + const result = RuleUpgradeSpecifier.safeParse(validSpecifier); + expectParseSuccess(result); + expect(result.data).toEqual(validSpecifier); + }); + + test('validates a valid upgrade specifier with a fields property', () => { + const specifierWithFields = { + ...validSpecifier, + fields: { + name: { + pick_version: 'CURRENT', + }, + }, + }; + const result = RuleUpgradeSpecifier.safeParse(specifierWithFields); + expectParseSuccess(result); + expect(result.data).toEqual(specifierWithFields); + }); + + test('rejects upgrade specifier with invalid pick_version rule_id', () => { + const invalid = { ...validSpecifier, rule_id: 123 }; + const result = RuleUpgradeSpecifier.safeParse(invalid); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"rule_id: Expected string, received number"` + ); + }); + }); + + describe('UpgradeSpecificRulesRequest', () => { + const validRequest = { + mode: 'SPECIFIC_RULES', + rules: [ + { + rule_id: 'rule-1', + revision: 1, + version: 1, + }, + ], + }; + + test('validates a correct upgrade specific rules request', () => { + const result = UpgradeSpecificRulesRequest.safeParse(validRequest); + expectParseSuccess(result); + expect(result.data).toEqual(validRequest); + }); + + test('rejects invalid mode', () => { + const invalid = { ...validRequest, mode: 'INVALID_MODE' }; + const result = UpgradeSpecificRulesRequest.safeParse(invalid); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"mode: Invalid literal value, expected \\"SPECIFIC_RULES\\""` + ); + }); + + test('rejects paylaod with missing rules array', () => { + const invalid = { ...validRequest, rules: undefined }; + const result = UpgradeSpecificRulesRequest.safeParse(invalid); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"rules: Required"`); + }); + }); + + describe('UpgradeAllRulesRequest', () => { + const validRequest = { + mode: 'ALL_RULES', + }; + + test('validates a correct upgrade all rules request', () => { + const result = UpgradeAllRulesRequest.safeParse(validRequest); + expectParseSuccess(result); + expect(result.data).toEqual(validRequest); + }); + + test('allows optional pick_version', () => { + const withPickVersion = { ...validRequest, pick_version: 'BASE' }; + const result = UpgradeAllRulesRequest.safeParse(withPickVersion); + expectParseSuccess(result); + expect(result.data).toEqual(withPickVersion); + }); + }); + + describe('PerformRuleUpgradeRequestBody', () => { + test('validates a correct upgrade specific rules request', () => { + const validRequest = { + mode: 'SPECIFIC_RULES', + pick_version: 'BASE', + rules: [ + { + rule_id: 'rule-1', + revision: 1, + version: 1, + }, + ], + }; + const result = PerformRuleUpgradeRequestBody.safeParse(validRequest); + expectParseSuccess(result); + expect(result.data).toEqual(validRequest); + }); + + test('validates a correct upgrade all rules request', () => { + const validRequest = { + mode: 'ALL_RULES', + pick_version: 'BASE', + }; + const result = PerformRuleUpgradeRequestBody.safeParse(validRequest); + expectParseSuccess(result); + expect(result.data).toEqual(validRequest); + }); + + test('rejects invalid mode', () => { + const invalid = { mode: 'INVALID_MODE' }; + const result = PerformRuleUpgradeRequestBody.safeParse(invalid); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot( + `"mode: Invalid discriminator value. Expected 'ALL_RULES' | 'SPECIFIC_RULES'"` + ); + }); + }); +}); + +describe('PerformRuleUpgradeResponseBody', () => { + const validResponse = { + summary: { + total: 1, + succeeded: 1, + skipped: 0, + failed: 0, + }, + results: { + updated: [], + skipped: [], + }, + errors: [], + }; + + test('validates a correct perform rule upgrade response', () => { + const result = PerformRuleUpgradeResponseBody.safeParse(validResponse); + expectParseSuccess(result); + expect(result.data).toEqual(validResponse); + }); + + test('rejects missing required fields', () => { + const propsToDelete = Object.keys(validResponse); + propsToDelete.forEach((deletedProp) => { + const invalidResponse = Object.fromEntries( + Object.entries(validResponse).filter(([key]) => key !== deletedProp) + ); + const result = PerformRuleUpgradeResponseBody.safeParse(invalidResponse); + expectParseError(result); + expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"${deletedProp}: Required"`); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts index b1d3b166a513e..e22574267d6fe 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -5,94 +5,176 @@ * 2.0. */ -import { enumeration } from '@kbn/securitysolution-io-ts-types'; -import * as t from 'io-ts'; +import { z } from 'zod'; -import type { RuleResponse } from '../../model'; -import type { AggregatedPrebuiltRuleError } from '../model'; +import { + RuleSignatureId, + RuleVersion, + RuleName, + RuleTagArray, + RuleDescription, + Severity, + SeverityMapping, + RiskScore, + RiskScoreMapping, + RuleReferenceArray, + RuleFalsePositiveArray, + ThreatArray, + InvestigationGuide, + SetupGuide, + RelatedIntegrationArray, + RequiredFieldArray, + MaxSignals, + BuildingBlockType, + RuleIntervalFrom, + RuleInterval, + RuleExceptionList, + RuleNameOverride, + TimestampOverride, + TimestampOverrideFallbackDisabled, + TimelineTemplateId, + TimelineTemplateTitle, + IndexPatternArray, + DataViewId, + RuleQuery, + QueryLanguage, + RuleFilterArray, + SavedQueryId, + KqlQueryLanguage, +} from '../../model/rule_schema/common_attributes.gen'; +import { + MachineLearningJobId, + AnomalyThreshold, +} from '../../model/rule_schema/specific_attributes/ml_attributes.gen'; +import { + ThreatQuery, + ThreatMapping, + ThreatIndex, + ThreatFilters, + ThreatIndicatorPath, +} from '../../model/rule_schema/specific_attributes/threat_match_attributes.gen'; +import { + NewTermsFields, + HistoryWindowStart, +} from '../../model/rule_schema/specific_attributes/new_terms_attributes.gen'; +import { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; +import { AggregatedPrebuiltRuleError } from '../model'; -export enum PickVersionValues { - BASE = 'BASE', - CURRENT = 'CURRENT', - TARGET = 'TARGET', -} +export type PickVersionValues = z.infer<typeof PickVersionValues>; +export const PickVersionValues = z.enum(['BASE', 'CURRENT', 'TARGET', 'MERGED']); +export type PickVersionValuesEnum = typeof PickVersionValues.enum; +export const PickVersionValuesEnum = PickVersionValues.enum; -export const TPickVersionValues = enumeration('PickVersionValues', PickVersionValues); +const createUpgradeFieldSchema = <T extends z.ZodType>(fieldSchema: T) => + z + .discriminatedUnion('pick_version', [ + z.object({ + pick_version: PickVersionValues, + }), + z.object({ + pick_version: z.literal('RESOLVED'), + resolved_value: fieldSchema, + }), + ]) + .optional(); -export const RuleUpgradeSpecifier = t.exact( - t.intersection([ - t.type({ - rule_id: t.string, - /** - * This parameter is needed for handling race conditions with Optimistic Concurrency Control. - * Two or more users can call upgrade/_review and upgrade/_perform endpoints concurrently. - * Also, in general the time between these two calls can be anything. - * The idea is to only allow the user to install a rule if the user has reviewed the exact version - * of it that had been returned from the _review endpoint. If the version changed on the BE, - * upgrade/_perform endpoint will return a version mismatch error for this rule. - */ - revision: t.number, - /** - * The target version to upgrade to. - */ - version: t.number, - }), - t.partial({ - pick_version: TPickVersionValues, - }), - ]) -); -export type RuleUpgradeSpecifier = t.TypeOf<typeof RuleUpgradeSpecifier>; +export type RuleUpgradeSpecifier = z.infer<typeof RuleUpgradeSpecifier>; +export const RuleUpgradeSpecifier = z.object({ + rule_id: RuleSignatureId, + revision: z.number(), + version: RuleVersion, + pick_version: PickVersionValues.optional(), + // Fields that can be customized during the upgrade workflow + // as decided in: https://github.com/elastic/kibana/issues/186544 + fields: z + .object({ + name: createUpgradeFieldSchema(RuleName), + tags: createUpgradeFieldSchema(RuleTagArray), + description: createUpgradeFieldSchema(RuleDescription), + severity: createUpgradeFieldSchema(Severity), + severity_mapping: createUpgradeFieldSchema(SeverityMapping), + risk_score: createUpgradeFieldSchema(RiskScore), + risk_score_mapping: createUpgradeFieldSchema(RiskScoreMapping), + references: createUpgradeFieldSchema(RuleReferenceArray), + false_positives: createUpgradeFieldSchema(RuleFalsePositiveArray), + threat: createUpgradeFieldSchema(ThreatArray), + note: createUpgradeFieldSchema(InvestigationGuide), + setup: createUpgradeFieldSchema(SetupGuide), + related_integrations: createUpgradeFieldSchema(RelatedIntegrationArray), + required_fields: createUpgradeFieldSchema(RequiredFieldArray), + max_signals: createUpgradeFieldSchema(MaxSignals), + building_block_type: createUpgradeFieldSchema(BuildingBlockType), + from: createUpgradeFieldSchema(RuleIntervalFrom), + interval: createUpgradeFieldSchema(RuleInterval), + exceptions_list: createUpgradeFieldSchema(RuleExceptionList), + rule_name_override: createUpgradeFieldSchema(RuleNameOverride), + timestamp_override: createUpgradeFieldSchema(TimestampOverride), + timestamp_override_fallback_disabled: createUpgradeFieldSchema( + TimestampOverrideFallbackDisabled + ), + timeline_id: createUpgradeFieldSchema(TimelineTemplateId), + timeline_title: createUpgradeFieldSchema(TimelineTemplateTitle), + index: createUpgradeFieldSchema(IndexPatternArray), + data_view_id: createUpgradeFieldSchema(DataViewId), + query: createUpgradeFieldSchema(RuleQuery), + language: createUpgradeFieldSchema(QueryLanguage), + filters: createUpgradeFieldSchema(RuleFilterArray), + saved_id: createUpgradeFieldSchema(SavedQueryId), + machine_learning_job_id: createUpgradeFieldSchema(MachineLearningJobId), + anomaly_threshold: createUpgradeFieldSchema(AnomalyThreshold), + threat_query: createUpgradeFieldSchema(ThreatQuery), + threat_mapping: createUpgradeFieldSchema(ThreatMapping), + threat_index: createUpgradeFieldSchema(ThreatIndex), + threat_filters: createUpgradeFieldSchema(ThreatFilters), + threat_indicator_path: createUpgradeFieldSchema(ThreatIndicatorPath), + threat_language: createUpgradeFieldSchema(KqlQueryLanguage), + new_terms_fields: createUpgradeFieldSchema(NewTermsFields), + history_window_start: createUpgradeFieldSchema(HistoryWindowStart), + }) + .optional(), +}); -export type UpgradeSpecificRulesRequest = t.TypeOf<typeof UpgradeSpecificRulesRequest>; -export const UpgradeSpecificRulesRequest = t.exact( - t.intersection([ - t.type({ - mode: t.literal(`SPECIFIC_RULES`), - rules: t.array(RuleUpgradeSpecifier), - }), - t.partial({ - pick_version: TPickVersionValues, - }), - ]) -); +export type UpgradeSpecificRulesRequest = z.infer<typeof UpgradeSpecificRulesRequest>; +export const UpgradeSpecificRulesRequest = z.object({ + mode: z.literal('SPECIFIC_RULES'), + rules: z.array(RuleUpgradeSpecifier), + pick_version: PickVersionValues.optional(), +}); -export const UpgradeAllRulesRequest = t.exact( - t.intersection([ - t.type({ - mode: t.literal(`ALL_RULES`), - }), - t.partial({ - pick_version: TPickVersionValues, - }), - ]) -); +export type UpgradeAllRulesRequest = z.infer<typeof UpgradeAllRulesRequest>; +export const UpgradeAllRulesRequest = z.object({ + mode: z.literal('ALL_RULES'), + pick_version: PickVersionValues.optional(), +}); -export const PerformRuleUpgradeRequestBody = t.union([ - UpgradeAllRulesRequest, - UpgradeSpecificRulesRequest, -]); -export type PerformRuleUpgradeRequestBody = t.TypeOf<typeof PerformRuleUpgradeRequestBody>; +export type SkipRuleUpgradeReason = z.infer<typeof SkipRuleUpgradeReason>; +export const SkipRuleUpgradeReason = z.enum(['RULE_UP_TO_DATE']); +export type SkipRuleUpgradeReasonEnum = typeof SkipRuleUpgradeReason.enum; +export const SkipRuleUpgradeReasonEnum = SkipRuleUpgradeReason.enum; -export enum SkipRuleUpgradeReason { - RULE_UP_TO_DATE = 'RULE_UP_TO_DATE', -} +export type SkippedRuleUpgrade = z.infer<typeof SkippedRuleUpgrade>; +export const SkippedRuleUpgrade = z.object({ + rule_id: z.string(), + reason: SkipRuleUpgradeReason, +}); -export interface SkippedRuleUpgrade { - rule_id: string; - reason: SkipRuleUpgradeReason; -} +export type PerformRuleUpgradeResponseBody = z.infer<typeof PerformRuleUpgradeResponseBody>; +export const PerformRuleUpgradeResponseBody = z.object({ + summary: z.object({ + total: z.number(), + succeeded: z.number(), + skipped: z.number(), + failed: z.number(), + }), + results: z.object({ + updated: z.array(RuleResponse), + skipped: z.array(SkippedRuleUpgrade), + }), + errors: z.array(AggregatedPrebuiltRuleError), +}); -export interface PerformRuleUpgradeResponseBody { - summary: { - total: number; - succeeded: number; - skipped: number; - failed: number; - }; - results: { - updated: RuleResponse[]; - skipped: SkippedRuleUpgrade[]; - }; - errors: AggregatedPrebuiltRuleError[]; -} +export type PerformRuleUpgradeRequestBody = z.infer<typeof PerformRuleUpgradeRequestBody>; +export const PerformRuleUpgradeRequestBody = z.discriminatedUnion('mode', [ + UpgradeAllRulesRequest, + UpgradeSpecificRulesRequest, +]); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.gen.ts new file mode 100644 index 0000000000000..74acc33c5332e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Prebuilt Rules Status API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type ReadPrebuiltRulesAndTimelinesStatusResponse = z.infer< + typeof ReadPrebuiltRulesAndTimelinesStatusResponse +>; +export const ReadPrebuiltRulesAndTimelinesStatusResponse = z + .object({ + /** + * The total number of custom rules + */ + rules_custom_installed: z.number().int().min(0), + /** + * The total number of installed prebuilt rules + */ + rules_installed: z.number().int().min(0), + /** + * The total number of available prebuilt rules that are not installed + */ + rules_not_installed: z.number().int().min(0), + /** + * The total number of outdated prebuilt rules + */ + rules_not_updated: z.number().int().min(0), + /** + * The total number of installed prebuilt timelines + */ + timelines_installed: z.number().int().min(0), + /** + * The total number of available prebuilt timelines that are not installed + */ + timelines_not_installed: z.number().int().min(0), + /** + * The total number of outdated prebuilt timelines + */ + timelines_not_updated: z.number().int().min(0), + }) + .strict(); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.schema.yaml new file mode 100644 index 0000000000000..2679518e2ec67 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.schema.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.0 +info: + title: Prebuilt Rules Status API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/rules/prepackaged/_status: + get: + x-labels: [ess] + x-codegen-enabled: true + operationId: ReadPrebuiltRulesAndTimelinesStatus + summary: Retrieve the status of prebuilt detection rules and Timelines + description: Retrieve the status of all Elastic prebuilt detection rules and Timelines. + tags: + - Prebuilt Rules API + responses: + 200: + description: Indicates a successful call + content: + application/json: + schema: + type: object + additionalProperties: false + properties: + rules_custom_installed: + type: integer + description: The total number of custom rules + minimum: 0 + rules_installed: + type: integer + description: The total number of installed prebuilt rules + minimum: 0 + rules_not_installed: + type: integer + description: The total number of available prebuilt rules that are not installed + minimum: 0 + rules_not_updated: + type: integer + description: The total number of outdated prebuilt rules + minimum: 0 + timelines_installed: + type: integer + description: The total number of installed prebuilt timelines + minimum: 0 + timelines_not_installed: + type: integer + description: The total number of available prebuilt timelines that are not installed + minimum: 0 + timelines_not_updated: + type: integer + description: The total number of outdated prebuilt timelines + minimum: 0 + required: + - rules_custom_installed + - rules_installed + - rules_not_installed + - rules_not_updated + - timelines_installed + - timelines_not_installed + - timelines_not_updated diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.test.ts new file mode 100644 index 0000000000000..2d3621a805576 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/read_prebuilt_rules_and_timelines_status/read_prebuilt_rules_and_timelines_status_route.test.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { expectParseError, expectParseSuccess, stringifyZodError } from '@kbn/zod-helpers'; +import { ReadPrebuiltRulesAndTimelinesStatusResponse } from './read_prebuilt_rules_and_timelines_status_route.gen'; + +describe('Get prebuilt rules and timelines status response schema', () => { + test('it should validate an empty prepackaged response with defaults', () => { + const payload: ReadPrebuiltRulesAndTimelinesStatusResponse = { + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 0, + rules_custom_installed: 0, + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }; + const result = ReadPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); + + expectParseSuccess(result); + expect(result.data).toEqual(payload); + }); + + test('it should not validate an extra invalid field added', () => { + const payload: ReadPrebuiltRulesAndTimelinesStatusResponse & { invalid_field: string } = { + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 0, + rules_custom_installed: 0, + invalid_field: 'invalid', + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }; + const result = ReadPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); + + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + "Unrecognized key(s) in object: 'invalid_field'" + ); + }); + + test('it should NOT validate an empty prepackaged response with a negative "rules_installed" number', () => { + const payload: ReadPrebuiltRulesAndTimelinesStatusResponse = { + rules_installed: -1, + rules_not_installed: 0, + rules_not_updated: 0, + rules_custom_installed: 0, + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }; + const result = ReadPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); + + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + 'rules_installed: Number must be greater than or equal to 0' + ); + }); + + test('it should NOT validate an empty prepackaged response with a negative "rules_not_installed"', () => { + const payload: ReadPrebuiltRulesAndTimelinesStatusResponse = { + rules_installed: 0, + rules_not_installed: -1, + rules_not_updated: 0, + rules_custom_installed: 0, + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }; + const result = ReadPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); + + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + 'rules_not_installed: Number must be greater than or equal to 0' + ); + }); + + test('it should NOT validate an empty prepackaged response with a negative "rules_not_updated"', () => { + const payload: ReadPrebuiltRulesAndTimelinesStatusResponse = { + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: -1, + rules_custom_installed: 0, + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }; + const result = ReadPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); + + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + 'rules_not_updated: Number must be greater than or equal to 0' + ); + }); + + test('it should NOT validate an empty prepackaged response with a negative "rules_custom_installed"', () => { + const payload: ReadPrebuiltRulesAndTimelinesStatusResponse = { + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 0, + rules_custom_installed: -1, + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }; + const result = ReadPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); + + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual( + 'rules_custom_installed: Number must be greater than or equal to 0' + ); + }); + + test('it should NOT validate an empty prepackaged response if "rules_installed" is not there', () => { + const payload: ReadPrebuiltRulesAndTimelinesStatusResponse = { + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 0, + rules_custom_installed: 0, + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }; + // @ts-expect-error + delete payload.rules_installed; + const result = ReadPrebuiltRulesAndTimelinesStatusResponse.safeParse(payload); + + expectParseError(result); + expect(stringifyZodError(result.error)).toEqual('rules_installed: Required'); + }); +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts index ae62433d93a35..3f744dffc9447 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/urls.ts @@ -10,14 +10,15 @@ import { INTERNAL_DETECTION_ENGINE_URL as INTERNAL, } from '../../../constants'; -const OLD_BASE_URL = `${RULES}/prepackaged` as const; -const NEW_BASE_URL = `${INTERNAL}/prebuilt_rules` as const; +const LEGACY_BASE_URL = `${RULES}/prepackaged` as const; +const BASE_URL = `${INTERNAL}/prebuilt_rules` as const; -export const PREBUILT_RULES_URL = OLD_BASE_URL; -export const PREBUILT_RULES_STATUS_URL = `${OLD_BASE_URL}/_status` as const; +export const PREBUILT_RULES_URL = LEGACY_BASE_URL; +export const PREBUILT_RULES_STATUS_URL = `${LEGACY_BASE_URL}/_status` as const; -export const GET_PREBUILT_RULES_STATUS_URL = `${NEW_BASE_URL}/status` as const; -export const REVIEW_RULE_UPGRADE_URL = `${NEW_BASE_URL}/upgrade/_review` as const; -export const PERFORM_RULE_UPGRADE_URL = `${NEW_BASE_URL}/upgrade/_perform` as const; -export const REVIEW_RULE_INSTALLATION_URL = `${NEW_BASE_URL}/installation/_review` as const; -export const PERFORM_RULE_INSTALLATION_URL = `${NEW_BASE_URL}/installation/_perform` as const; +export const GET_PREBUILT_RULES_STATUS_URL = `${BASE_URL}/status` as const; +export const BOOTSTRAP_PREBUILT_RULES_URL = `${BASE_URL}/_bootstrap` as const; +export const REVIEW_RULE_UPGRADE_URL = `${BASE_URL}/upgrade/_review` as const; +export const PERFORM_RULE_UPGRADE_URL = `${BASE_URL}/upgrade/_perform` as const; +export const REVIEW_RULE_INSTALLATION_URL = `${BASE_URL}/installation/_review` as const; +export const PERFORM_RULE_INSTALLATION_URL = `${BASE_URL}/installation/_perform` as const; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen.ts deleted file mode 100644 index acf9c9edd389d..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Get alerts migration status API endpoint - * version: 2023-10-31 - */ - -import { z } from 'zod'; -import { isValidDateMath } from '@kbn/zod-helpers'; - -import { NonEmptyString } from '../../../model/primitives.gen'; - -export type AlertVersion = z.infer<typeof AlertVersion>; -export const AlertVersion = z.object({ - version: z.number().int(), - count: z.number().int(), -}); - -export type MigrationStatus = z.infer<typeof MigrationStatus>; -export const MigrationStatus = z.object({ - id: NonEmptyString, - status: z.enum(['success', 'failure', 'pending']), - version: z.number().int(), - updated: z.string().datetime(), -}); - -export type IndexMigrationStatus = z.infer<typeof IndexMigrationStatus>; -export const IndexMigrationStatus = z.object({ - index: NonEmptyString, - version: z.number().int(), - signal_versions: z.array(AlertVersion), - migrations: z.array(MigrationStatus), - is_outdated: z.boolean(), -}); - -export type GetAlertsMigrationStatusRequestQuery = z.infer< - typeof GetAlertsMigrationStatusRequestQuery ->; -export const GetAlertsMigrationStatusRequestQuery = z.object({ - /** - * Maximum age of qualifying detection alerts - */ - from: z.string().superRefine(isValidDateMath), -}); -export type GetAlertsMigrationStatusRequestQueryInput = z.input< - typeof GetAlertsMigrationStatusRequestQuery ->; - -export type GetAlertsMigrationStatusResponse = z.infer<typeof GetAlertsMigrationStatusResponse>; -export const GetAlertsMigrationStatusResponse = z.object({ - indices: z.array(IndexMigrationStatus), -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.schema.yaml deleted file mode 100644 index 64eafd09f65d7..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.schema.yaml +++ /dev/null @@ -1,115 +0,0 @@ -openapi: 3.0.0 -info: - title: Get alerts migration status API endpoint - version: '2023-10-31' -paths: - /api/detection_engine/signals/migration_status: - post: - x-labels: [ess] - operationId: GetAlertsMigrationStatus - x-codegen-enabled: true - summary: Retrieve the status of detection alert migrations - description: Retrieve indices that contain detection alerts of a particular age, along with migration information for each of those indices. - tags: - - Alerts migration API - parameters: - - name: from - in: query - description: Maximum age of qualifying detection alerts - required: true - schema: - type: string - description: | - Time from which data is analyzed. For example, now-4200s means the rule analyzes data from 70 minutes - before its start time. Defaults to now-6m (analyzes data from 6 minutes before the start time). - format: date-math - responses: - 200: - description: Successful response - content: - application/json: - schema: - type: object - properties: - indices: - type: array - items: - $ref: '#/components/schemas/IndexMigrationStatus' - required: [indices] - 400: - description: Invalid input data response - content: - application/json: - schema: - oneOf: - - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - 401: - description: Unsuccessful authentication response - content: - application/json: - schema: - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' - 500: - description: Internal server error response - content: - application/json: - schema: - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' - -components: - schemas: - AlertVersion: - type: object - properties: - version: - type: integer - count: - type: integer - required: [version, count] - - MigrationStatus: - type: object - properties: - id: - $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' - status: - type: string - enum: - - success - - failure - - pending - version: - type: integer - updated: - type: string - format: date-time - required: - - id - - status - - version - - updated - - IndexMigrationStatus: - type: object - properties: - index: - $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' - version: - type: integer - signal_versions: - type: array - items: - $ref: '#/components/schemas/AlertVersion' - migrations: - type: array - items: - $ref: '#/components/schemas/MigrationStatus' - is_outdated: - type: boolean - required: - - index - - version - - signal_versions - - migrations - - is_outdated diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.mock.ts deleted file mode 100644 index 968d72d6f40d0..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.mock.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { GetAlertsMigrationStatusRequestQuery } from './get_signals_migration_status.gen'; - -export const getSignalsMigrationStatusSchemaMock = (): GetAlertsMigrationStatusRequestQuery => ({ - from: 'now-30d', -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts index 84b00fc9f297c..86172d0053732 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts @@ -8,4 +8,4 @@ export * from './create_signals_migration/create_signals_migration.gen'; export * from './delete_signals_migration/delete_signals_migration.gen'; export * from './finalize_signals_migration/finalize_signals_migration.gen'; -export * from './get_signals_migration_status/get_signals_migration_status.gen'; +export * from './read_signals_migration_status/read_signals_migration_status.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/mocks.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/mocks.ts index 90713ec3a1881..cb940906ea848 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/mocks.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/mocks.ts @@ -7,4 +7,4 @@ export * from './create_signals_migration/create_signals_migration_route.mock'; export * from './finalize_signals_migration/finalize_signals_migration_route.mock'; -export * from './get_signals_migration_status/get_signals_migration_status_route.mock'; +export * from './read_signals_migration_status/read_signals_migration_status_route.mock'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.gen.ts new file mode 100644 index 0000000000000..ac6a53f7205c9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.gen.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Read alerts migration status API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; +import { isValidDateMath } from '@kbn/zod-helpers'; + +import { NonEmptyString } from '../../../model/primitives.gen'; + +export type AlertVersion = z.infer<typeof AlertVersion>; +export const AlertVersion = z.object({ + version: z.number().int(), + count: z.number().int(), +}); + +export type MigrationStatus = z.infer<typeof MigrationStatus>; +export const MigrationStatus = z.object({ + id: NonEmptyString, + status: z.enum(['success', 'failure', 'pending']), + version: z.number().int(), + updated: z.string().datetime(), +}); + +export type IndexMigrationStatus = z.infer<typeof IndexMigrationStatus>; +export const IndexMigrationStatus = z.object({ + index: NonEmptyString, + version: z.number().int(), + signal_versions: z.array(AlertVersion), + migrations: z.array(MigrationStatus), + is_outdated: z.boolean(), +}); + +export type ReadAlertsMigrationStatusRequestQuery = z.infer< + typeof ReadAlertsMigrationStatusRequestQuery +>; +export const ReadAlertsMigrationStatusRequestQuery = z.object({ + /** + * Maximum age of qualifying detection alerts + */ + from: z.string().superRefine(isValidDateMath), +}); +export type ReadAlertsMigrationStatusRequestQueryInput = z.input< + typeof ReadAlertsMigrationStatusRequestQuery +>; + +export type ReadAlertsMigrationStatusResponse = z.infer<typeof ReadAlertsMigrationStatusResponse>; +export const ReadAlertsMigrationStatusResponse = z.object({ + indices: z.array(IndexMigrationStatus), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.schema.yaml new file mode 100644 index 0000000000000..6cf11191f5d70 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.schema.yaml @@ -0,0 +1,115 @@ +openapi: 3.0.0 +info: + title: Read alerts migration status API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/migration_status: + post: + x-labels: [ess] + operationId: ReadAlertsMigrationStatus + x-codegen-enabled: true + summary: Retrieve the status of detection alert migrations + description: Retrieve indices that contain detection alerts of a particular age, along with migration information for each of those indices. + tags: + - Alerts migration API + parameters: + - name: from + in: query + description: Maximum age of qualifying detection alerts + required: true + schema: + type: string + description: | + Time from which data is analyzed. For example, now-4200s means the rule analyzes data from 70 minutes + before its start time. Defaults to now-6m (analyzes data from 6 minutes before the start time). + format: date-math + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + indices: + type: array + items: + $ref: '#/components/schemas/IndexMigrationStatus' + required: [indices] + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + AlertVersion: + type: object + properties: + version: + type: integer + count: + type: integer + required: [version, count] + + MigrationStatus: + type: object + properties: + id: + $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' + status: + type: string + enum: + - success + - failure + - pending + version: + type: integer + updated: + type: string + format: date-time + required: + - id + - status + - version + - updated + + IndexMigrationStatus: + type: object + properties: + index: + $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' + version: + type: integer + signal_versions: + type: array + items: + $ref: '#/components/schemas/AlertVersion' + migrations: + type: array + items: + $ref: '#/components/schemas/MigrationStatus' + is_outdated: + type: boolean + required: + - index + - version + - signal_versions + - migrations + - is_outdated diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status_route.mock.ts new file mode 100644 index 0000000000000..bd7cbb675b537 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status_route.mock.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 { ReadAlertsMigrationStatusRequestQuery } from './read_signals_migration_status.gen'; + +export const getSignalsMigrationStatusSchemaMock = (): ReadAlertsMigrationStatusRequestQuery => ({ + from: 'now-30d', +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/action_log.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/action_log.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.ts new file mode 100644 index 0000000000000..e458ae1775479 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Action Log Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { Page, PageSize, StartDate, EndDate } from '../../model/schema/common.gen'; + +export type ActionLogRequestQuery = z.infer<typeof ActionLogRequestQuery>; +export const ActionLogRequestQuery = z.object({ + page: Page.optional(), + page_size: PageSize.optional(), + start_date: StartDate.optional(), + end_date: EndDate.optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml new file mode 100644 index 0000000000000..d46a1cbe7d9ba --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/deprecated_action_log.schema.yaml @@ -0,0 +1,45 @@ +openapi: 3.0.0 +info: + title: Action Log Schema + version: '2023-10-31' +paths: + /api/endpoint/action_log/{agent_id}: + get: + summary: Get action requests log schema + operationId: EndpointGetActionLog + description: Get action requests log + deprecated: true + x-codegen-enabled: false + x-labels: [ess, serverless] + parameters: + - name: agent_id + in: path + required: true + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentId' + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/ActionLogRequestQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + ActionLogRequestQuery: + 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' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/index.ts new file mode 100644 index 0000000000000..61a6f0bcbd5c9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/action_log/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 * from './action_log'; +export * from './deprecated_action_log.gen'; 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 deleted file mode 100644 index a64eb5c5cff3a..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions.schema.yaml +++ /dev/null @@ -1,131 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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 deleted file mode 100644 index 92438f6d1a9fc..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/actions_status.schema.yaml +++ /dev/null @@ -1,30 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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 deleted file mode 100644 index bfbea46ed9c5c..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.gen.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Audit Log Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { Page, PageSize, StartDate, EndDate, AgentId } from '../model/schema/common.gen'; - -export type AuditLogRequestQuery = z.infer<typeof AuditLogRequestQuery>; -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<typeof AuditLogRequestParams>; -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 deleted file mode 100644 index 5d0ed51ca339a..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/audit_log.schema.yaml +++ /dev/null @@ -1,51 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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/common/response_actions.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts index ca6d9d5e91982..56b1fafdb5a71 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/common/response_actions.ts @@ -7,24 +7,27 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; -import { - KillProcessRouteRequestSchema, - SuspendProcessRouteRequestSchema, - UploadActionRequestSchema, -} from '../..'; -import { ExecuteActionRequestSchema } from '../execute_route'; -import { EndpointActionGetFileSchema } from '../get_file_route'; -import { ScanActionRequestSchema } from '../scan_route'; -import { NoParametersRequestSchema } from './base'; + +import { ExecuteActionRequestSchema } from '../response_actions/execute'; +import { EndpointActionGetFileSchema } from '../response_actions/get_file'; +import { ScanActionRequestSchema } from '../response_actions/scan'; +import { IsolateRouteRequestSchema } from '../response_actions/isolate'; +import { UnisolateRouteRequestSchema } from '../response_actions/unisolate'; +import { GetProcessesRouteRequestSchema } from '../response_actions/running_procs'; +import { KillProcessRouteRequestSchema } from '../response_actions/kill_process'; +import { SuspendProcessRouteRequestSchema } from '../response_actions/suspend_process'; +import { UploadActionRequestSchema } from '../response_actions/upload'; export const ResponseActionBodySchema = schema.oneOf([ - NoParametersRequestSchema.body, + IsolateRouteRequestSchema.body, + UnisolateRouteRequestSchema.body, + GetProcessesRouteRequestSchema.body, KillProcessRouteRequestSchema.body, SuspendProcessRouteRequestSchema.body, EndpointActionGetFileSchema.body, ExecuteActionRequestSchema.body, - ScanActionRequestSchema.body, UploadActionRequestSchema.body, + ScanActionRequestSchema.body, ]); export type ResponseActionsRequestBody = TypeOf<typeof ResponseActionBodySchema>; 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 deleted file mode 100644 index dcceb64d44a6b..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Details Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -export type DetailsRequestParams = z.infer<typeof DetailsRequestParams>; -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 deleted file mode 100644 index 18a3ffa2c1fd2..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/details.schema.yaml +++ /dev/null @@ -1,34 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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/details/details.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.ts new file mode 100644 index 0000000000000..3f5305dabd424 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Details Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsDetailsRequestParams = z.infer< + typeof EndpointGetActionsDetailsRequestParams +>; +export const EndpointGetActionsDetailsRequestParams = z.object({ + action_id: z.string(), +}); +export type EndpointGetActionsDetailsRequestParamsInput = z.input< + typeof EndpointGetActionsDetailsRequestParams +>; + +export type EndpointGetActionsDetailsResponse = z.infer<typeof EndpointGetActionsDetailsResponse>; +export const EndpointGetActionsDetailsResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml new file mode 100644 index 0000000000000..ec3a184e6e9a9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.schema.yaml @@ -0,0 +1,27 @@ +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 + description: Get action details + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: action_id + in: path + required: true + schema: + 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/actions/details_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/details_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/details/details.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/index.ts new file mode 100644 index 0000000000000..0154d63be42a2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/details/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 * from './details'; +export * from './details.gen'; 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 deleted file mode 100644 index 8afd62814dfb3..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Execute Action Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema, Command, Timeout } from '../model/schema/common.gen'; - -export type ExecuteActionRequestBody = z.infer<typeof ExecuteActionRequestBody>; -export const ExecuteActionRequestBody = BaseActionSchema.merge( - 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 deleted file mode 100644 index ea9e7d7d98bf9..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute.schema.yaml +++ /dev/null @@ -1,45 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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/execute_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts deleted file mode 100644 index b8de2fc6874a0..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/execute_route.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; - -export const ExecuteActionRequestSchema = { - body: schema.object({ - ...BaseActionRequestSchema, - parameters: schema.object({ - command: schema.string({ - minLength: 1, - validate: (value) => { - if (!value.trim().length) { - return 'command cannot be an empty string'; - } - }, - }), - /** - * The max timeout value before the command is killed. Number represents milliseconds - */ - timeout: schema.maybe(schema.number({ min: 1 })), - }), - }), -}; - -export type ExecuteActionRequestBody = TypeOf<typeof ExecuteActionRequestSchema.body>; 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 deleted file mode 100644 index e670d2070d8ab..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.gen.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: File Download Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -export type FileDownloadRequestParams = z.infer<typeof FileDownloadRequestParams>; -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 deleted file mode 100644 index 8eb02883965b1..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download.schema.yaml +++ /dev/null @@ -1,39 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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_download/file_download.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.ts new file mode 100644 index 0000000000000..a32c1036e3fd9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: File Download Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointFileDownloadRequestParams = z.infer<typeof EndpointFileDownloadRequestParams>; +export const EndpointFileDownloadRequestParams = z.object({ + action_id: z.string(), + file_id: z.string(), +}); +export type EndpointFileDownloadRequestParamsInput = z.input< + typeof EndpointFileDownloadRequestParams +>; + +export type EndpointFileDownloadResponse = z.infer<typeof EndpointFileDownloadResponse>; +export const EndpointFileDownloadResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml new file mode 100644 index 0000000000000..8842d1b6acc61 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.schema.yaml @@ -0,0 +1,30 @@ +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 + description: Download a file from an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: action_id + in: path + required: true + schema: + type: string + - name: file_id + in: path + required: true + schema: + 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/actions/file_download_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_download_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/file_download.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/index.ts new file mode 100644 index 0000000000000..f6b87f11df80e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_download/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 * from './file_download'; +export * from './file_download.gen'; 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 deleted file mode 100644 index d9737560e849c..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.gen.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: File Info Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -export type FileInfoRequestParams = z.infer<typeof FileInfoRequestParams>; -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 deleted file mode 100644 index 5351480738e23..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info.schema.yaml +++ /dev/null @@ -1,40 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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_info/file_info.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.ts new file mode 100644 index 0000000000000..1a706049b067a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: File Info Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointFileInfoRequestParams = z.infer<typeof EndpointFileInfoRequestParams>; +export const EndpointFileInfoRequestParams = z.object({ + action_id: z.string(), + file_id: z.string(), +}); +export type EndpointFileInfoRequestParamsInput = z.input<typeof EndpointFileInfoRequestParams>; + +export type EndpointFileInfoResponse = z.infer<typeof EndpointFileInfoResponse>; +export const EndpointFileInfoResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml new file mode 100644 index 0000000000000..6199dc56ed1b0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.schema.yaml @@ -0,0 +1,30 @@ +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 + description: Get file info + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: action_id + in: path + required: true + schema: + type: string + - name: file_id + in: path + required: true + schema: + 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/actions/file_info_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/file_info_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/file_info.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/index.ts new file mode 100644 index 0000000000000..db1f6c9ef2db3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_info/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 * from './file_info'; +export * from './file_info.gen'; 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 deleted file mode 100644 index f376c6d81fc21..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: File Upload Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type FileUploadActionRequestBody = z.infer<typeof FileUploadActionRequestBody>; -export const FileUploadActionRequestBody = BaseActionSchema.merge( - 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 deleted file mode 100644 index fa9f0da1b1203..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload.schema.yaml +++ /dev/null @@ -1,47 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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/file_upload_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.ts deleted file mode 100644 index b5850a63ca8e0..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/file_upload_route.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 { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; - -export const UploadActionRequestSchema = { - body: schema.object({ - ...BaseActionRequestSchema, - - parameters: schema.object({ - overwrite: schema.maybe(schema.boolean({ defaultValue: false })), - }), - - file: schema.stream(), - }), -}; - -/** Type used by the server's API for `upload` action */ -export type UploadActionApiRequestBody = TypeOf<typeof UploadActionRequestSchema.body>; - -/** - * Type used on the UI side. The `file` definition is different on the UI side, thus the - * need for a separate type. - */ -export type UploadActionUIRequestBody = Omit<UploadActionApiRequestBody, 'file'> & { - file: File; -}; 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 deleted file mode 100644 index 22dc90c6cfd82..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Get File Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type GetFileActionRequestBody = z.infer<typeof GetFileActionRequestBody>; -export const GetFileActionRequestBody = BaseActionSchema.merge( - 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 deleted file mode 100644 index d79c07556da82..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file.schema.yaml +++ /dev/null @@ -1,44 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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 - properties: - parameters: - required: - - path - type: object - properties: - path: - type: string - diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.ts deleted file mode 100644 index 4378042f29403..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_file_route.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 { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; - -export const EndpointActionGetFileSchema = { - body: schema.object({ - ...BaseActionRequestSchema, - - parameters: schema.object({ - path: schema.string({ minLength: 1 }), - }), - }), -}; - -export type ResponseActionGetFileRequestBody = TypeOf<typeof EndpointActionGetFileSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts deleted file mode 100644 index a9e56e52ba292..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/get_processes_route.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { NoParametersRequestSchema } from './common/base'; - -export const GetProcessesRouteRequestSchema = NoParametersRequestSchema; -export type GetProcessesRequestBody = TypeOf<typeof GetProcessesRouteRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.ts deleted file mode 100644 index 1318e25d827f8..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Endpoint Isolate Schema - * version: 2023-10-31 - */ - -import type { z } from 'zod'; - -import { BaseActionSchema, SuccessResponse } from '../model/schema/common.gen'; - -export type EndpointIsolateRedirectRequestBody = z.infer<typeof EndpointIsolateRedirectRequestBody>; -export const EndpointIsolateRedirectRequestBody = BaseActionSchema; -export type EndpointIsolateRedirectRequestBodyInput = z.input< - typeof EndpointIsolateRedirectRequestBody ->; - -export type EndpointIsolateRedirectResponse = z.infer<typeof EndpointIsolateRedirectResponse>; -export const EndpointIsolateRedirectResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml deleted file mode 100644 index 4342dd85c7080..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.schema.yaml +++ /dev/null @@ -1,33 +0,0 @@ -openapi: 3.0.0 -info: - title: Endpoint Isolate Schema - version: '2023-10-31' -paths: - /api/endpoint/isolate: - post: - summary: Permanently redirects to a new location - operationId: EndpointIsolateRedirect - x-codegen-enabled: true - x-labels: - - ess - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '308': - description: Permanent Redirect - headers: - Location: - description: Permanently redirects to "/api/endpoint/action/isolate" - schema: - type: string - example: "/api/endpoint/action/isolate" - '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/isolate_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts deleted file mode 100644 index 0df0d8d913457..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/isolate_route.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { NoParametersRequestSchema } from './common/base'; - -export const IsolateRouteRequestSchema = NoParametersRequestSchema; -export type IsolationRouteRequestBody = TypeOf<typeof IsolateRouteRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.ts deleted file mode 100644 index f3c0d4e8f12be..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/kill_process_route.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; - -// -------------------------------------------------- -// Tests for this module are at: -// x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts:604 -// -------------------------------------------------- - -export const KillProcessRouteRequestSchema = { - body: schema.object( - { - ...BaseActionRequestSchema, - parameters: schema.oneOf([ - schema.object({ pid: schema.number({ min: 1 }) }), - schema.object({ entity_id: schema.string({ minLength: 1 }) }), - - // Process Name currently applies only to SentinelOne (validated below) - schema.object({ process_name: schema.string({ minLength: 1 }) }), - ]), - }, - { - validate(bodyContent) { - if ('process_name' in bodyContent.parameters && bodyContent.agent_type !== 'sentinel_one') { - return `[parameters.process_name]: is not valid with agent type of ${bodyContent.agent_type}`; - } - - if ( - bodyContent.agent_type === 'sentinel_one' && - !('process_name' in bodyContent.parameters) - ) { - return `[parameters.process_name]: missing parameter for agent type of ${bodyContent.agent_type}`; - } - }, - } - ), -}; 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 deleted file mode 100644 index 771af7165f3bd..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Actions List Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { - AgentIds, - Commands, - Page, - StartDate, - EndDate, - UserIds, - Types, - WithOutputs, -} from '../model/schema/common.gen'; - -export type EndpointActionListRequestQuery = z.infer<typeof EndpointActionListRequestQuery>; -export const EndpointActionListRequestQuery = z.object({ - agentIds: AgentIds.optional(), - commands: Commands.optional(), - page: Page.optional(), - /** - * Number of items per page - */ - pageSize: z.number().int().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 deleted file mode 100644 index afa844135ecd5..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list.schema.yaml +++ /dev/null @@ -1,53 +0,0 @@ -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 - x-labels: - - ess - - serverless - parameters: - - name: query - in: query - required: true - schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' -components: - schemas: - EndpointActionListRequestQuery: - 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/actions/list/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/index.ts new file mode 100644 index 0000000000000..9b1e437559465 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/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 * from './list'; +export * from './list.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts new file mode 100644 index 0000000000000..a63e011e1d2f5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.gen.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Actions List Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { + SuccessResponse, + AgentIds, + AgentTypes, + Commands, + Page, + StartDate, + EndDate, + UserIds, + Types, + WithOutputs, +} from '../../model/schema/common.gen'; + +export type GetEndpointActionListRouteQuery = z.infer<typeof GetEndpointActionListRouteQuery>; +export const GetEndpointActionListRouteQuery = z.object({ + agentIds: AgentIds.optional(), + agentTypes: AgentTypes.optional(), + commands: Commands.optional(), + page: Page.optional(), + /** + * Number of items per page + */ + pageSize: z.number().int().min(1).max(10000).optional().default(10), + startDate: StartDate.optional(), + endDate: EndDate.optional(), + userIds: UserIds.optional(), + types: Types.optional(), + withOutputs: WithOutputs.optional(), +}); + +export type EndpointGetActionsListRequestQuery = z.infer<typeof EndpointGetActionsListRequestQuery>; +export const EndpointGetActionsListRequestQuery = z.object({ + query: GetEndpointActionListRouteQuery, +}); +export type EndpointGetActionsListRequestQueryInput = z.input< + typeof EndpointGetActionsListRequestQuery +>; + +export type EndpointGetActionsListResponse = z.infer<typeof EndpointGetActionsListResponse>; +export const EndpointGetActionsListResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml new file mode 100644 index 0000000000000..b91ba03c60b8d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.schema.yaml @@ -0,0 +1,54 @@ +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 + description: Get a list of action requests and their responses + x-codegen-enabled: true + x-labels: [ess, serverless] + parameters: + - name: query + in: query + required: true + schema: + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' +components: + schemas: + GetEndpointActionListRouteQuery: + type: object + properties: + agentIds: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentIds' + agentTypes: + $ref: '../../model/schema/common.schema.yaml#/components/schemas/AgentTypes' + 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/actions/list/list.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.ts new file mode 100644 index 0000000000000..81cd32a045a7d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/list/list.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. + */ + +// TODO: fix the odd TS error +import type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import { + RESPONSE_ACTION_API_COMMANDS_NAMES, + RESPONSE_ACTION_STATUS, + RESPONSE_ACTION_TYPE, +} from '../../../../endpoint/service/response_actions/constants'; +import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../../../../endpoint/constants'; +import { agentTypesSchema } from '../common/base'; + +const commandsSchema = schema.oneOf( + // @ts-expect-error TS2769: No overload matches this call + RESPONSE_ACTION_API_COMMANDS_NAMES.map((command) => schema.literal(command)) +); + +const statusesSchema = { + // @ts-expect-error TS2769: No overload matches this call + schema: schema.oneOf(RESPONSE_ACTION_STATUS.map((status) => schema.literal(status))), + options: { minSize: 1, maxSize: RESPONSE_ACTION_STATUS.length }, +}; + +const actionTypesSchema = { + // @ts-expect-error TS2769: No overload matches this call + schema: schema.oneOf(RESPONSE_ACTION_TYPE.map((type) => schema.literal(type))), + options: { minSize: 1, maxSize: RESPONSE_ACTION_TYPE.length }, +}; + +export const EndpointActionListRequestSchema = { + query: schema.object({ + agentIds: schema.maybe( + schema.oneOf([ + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + schema.string({ minLength: 1 }), + ]) + ), + agentTypes: schema.maybe( + schema.oneOf([ + schema.arrayOf(agentTypesSchema.schema, agentTypesSchema.options), + agentTypesSchema.schema, + ]) + ), + commands: schema.maybe( + schema.oneOf([schema.arrayOf(commandsSchema, { minSize: 1 }), commandsSchema]) + ), + page: schema.maybe(schema.number({ defaultValue: 1, min: 1 })), + pageSize: schema.maybe( + schema.number({ defaultValue: ENDPOINT_DEFAULT_PAGE_SIZE, min: 1, max: 10000 }) + ), + startDate: schema.maybe(schema.string()), // date ISO strings or moment date + endDate: schema.maybe(schema.string()), // date ISO strings or moment date + statuses: schema.maybe( + schema.oneOf([ + schema.arrayOf(statusesSchema.schema, statusesSchema.options), + statusesSchema.schema, + ]) + ), + userIds: schema.maybe( + schema.oneOf([ + schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), + schema.string({ minLength: 1 }), + ]) + ), + withOutputs: schema.maybe( + schema.oneOf([ + schema.arrayOf(schema.string({ minLength: 1 }), { + minSize: 1, + validate: (actionIds) => { + if (actionIds.map((v) => v.trim()).some((v) => !v.length)) { + return 'actionIds cannot contain empty strings'; + } + }, + }), + schema.string({ + minLength: 1, + validate: (actionId) => { + if (!actionId.trim().length) { + return 'actionId cannot be an empty string'; + } + }, + }), + ]) + ), + // action types + types: schema.maybe( + schema.oneOf([ + schema.arrayOf(actionTypesSchema.schema, actionTypesSchema.options), + actionTypesSchema.schema, + ]) + ), + }), +}; + +export type EndpointActionListRequestQuery = TypeOf<typeof EndpointActionListRequestSchema.query>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts deleted file mode 100644 index 720764d9cd03c..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/list_route.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -// TODO: fix the odd TS error -import type { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; -import { - RESPONSE_ACTION_API_COMMANDS_NAMES, - RESPONSE_ACTION_STATUS, - RESPONSE_ACTION_TYPE, -} from '../../../endpoint/service/response_actions/constants'; -import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../../../endpoint/constants'; -import { agentTypesSchema } from './common/base'; - -const commandsSchema = schema.oneOf( - // @ts-expect-error TS2769: No overload matches this call - RESPONSE_ACTION_API_COMMANDS_NAMES.map((command) => schema.literal(command)) -); - -const statusesSchema = { - // @ts-expect-error TS2769: No overload matches this call - schema: schema.oneOf(RESPONSE_ACTION_STATUS.map((status) => schema.literal(status))), - options: { minSize: 1, maxSize: RESPONSE_ACTION_STATUS.length }, -}; - -const actionTypesSchema = { - // @ts-expect-error TS2769: No overload matches this call - schema: schema.oneOf(RESPONSE_ACTION_TYPE.map((type) => schema.literal(type))), - options: { minSize: 1, maxSize: RESPONSE_ACTION_TYPE.length }, -}; - -export const EndpointActionListRequestSchema = { - query: schema.object({ - agentIds: schema.maybe( - schema.oneOf([ - schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), - schema.string({ minLength: 1 }), - ]) - ), - agentTypes: schema.maybe( - schema.oneOf([ - schema.arrayOf(agentTypesSchema.schema, agentTypesSchema.options), - agentTypesSchema.schema, - ]) - ), - commands: schema.maybe( - schema.oneOf([schema.arrayOf(commandsSchema, { minSize: 1 }), commandsSchema]) - ), - page: schema.maybe(schema.number({ defaultValue: 1, min: 1 })), - pageSize: schema.maybe( - schema.number({ defaultValue: ENDPOINT_DEFAULT_PAGE_SIZE, min: 1, max: 10000 }) - ), - startDate: schema.maybe(schema.string()), // date ISO strings or moment date - endDate: schema.maybe(schema.string()), // date ISO strings or moment date - statuses: schema.maybe( - schema.oneOf([ - schema.arrayOf(statusesSchema.schema, statusesSchema.options), - statusesSchema.schema, - ]) - ), - userIds: schema.maybe( - schema.oneOf([ - schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 1 }), - schema.string({ minLength: 1 }), - ]) - ), - withOutputs: schema.maybe( - schema.oneOf([ - schema.arrayOf(schema.string({ minLength: 1 }), { - minSize: 1, - validate: (actionIds) => { - if (actionIds.map((v) => v.trim()).some((v) => !v.length)) { - return 'actionIds cannot contain empty strings'; - } - }, - }), - schema.string({ - minLength: 1, - validate: (actionId) => { - if (!actionId.trim().length) { - return 'actionId cannot be an empty string'; - } - }, - }), - ]) - ), - // action types - types: schema.maybe( - schema.oneOf([ - schema.arrayOf(actionTypesSchema.schema, actionTypesSchema.options), - actionTypesSchema.schema, - ]) - ), - }), -}; - -export type EndpointActionListRequestQuery = TypeOf<typeof EndpointActionListRequestSchema.query>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.ts new file mode 100644 index 0000000000000..29c30de059fc7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Execute Action Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { + SuccessResponse, + BaseActionSchema, + Command, + Timeout, +} from '../../../model/schema/common.gen'; + +export type ExecuteRouteRequestBody = z.infer<typeof ExecuteRouteRequestBody>; +export const ExecuteRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + command: Command, + timeout: Timeout.optional(), + }), + }) +); + +export type EndpointExecuteActionRequestBody = z.infer<typeof EndpointExecuteActionRequestBody>; +export const EndpointExecuteActionRequestBody = ExecuteRouteRequestBody; +export type EndpointExecuteActionRequestBodyInput = z.input< + typeof EndpointExecuteActionRequestBody +>; + +export type EndpointExecuteActionResponse = z.infer<typeof EndpointExecuteActionResponse>; +export const EndpointExecuteActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml new file mode 100644 index 0000000000000..beafae76a4ba6 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.schema.yaml @@ -0,0 +1,44 @@ +openapi: 3.0.0 +info: + title: Execute Action Schema + version: '2023-10-31' +paths: + /api/endpoint/action/execute: + post: + summary: Execute Action + operationId: EndpointExecuteAction + description: Execute a given command on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ExecuteRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + ExecuteRouteRequestBody: + 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/response_actions/execute/execute.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.ts new file mode 100644 index 0000000000000..f7f4394d66089 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/execute.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 type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import { BaseActionRequestSchema } from '../../common/base'; + +export const ExecuteActionRequestSchema = { + body: schema.object({ + ...BaseActionRequestSchema, + parameters: schema.object({ + command: schema.string({ + minLength: 1, + validate: (value) => { + if (!value.trim().length) { + return 'command cannot be an empty string'; + } + }, + }), + /** + * The max timeout value before the command is killed. Number represents milliseconds + */ + timeout: schema.maybe(schema.number({ min: 1 })), + }), + }), +}; + +export type ExecuteActionRequestBody = TypeOf<typeof ExecuteActionRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/index.ts new file mode 100644 index 0000000000000..be6732a4be40d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/execute/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 * from './execute'; +export * from './execute.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.ts new file mode 100644 index 0000000000000..e5c91527f5431 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get File Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type GetFileRouteRequestBody = z.infer<typeof GetFileRouteRequestBody>; +export const GetFileRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + path: z.string(), + }), + }) +); + +export type EndpointGetFileActionRequestBody = z.infer<typeof EndpointGetFileActionRequestBody>; +export const EndpointGetFileActionRequestBody = GetFileRouteRequestBody; +export type EndpointGetFileActionRequestBodyInput = z.input< + typeof EndpointGetFileActionRequestBody +>; + +export type EndpointGetFileActionResponse = z.infer<typeof EndpointGetFileActionResponse>; +export const EndpointGetFileActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml new file mode 100644 index 0000000000000..a5211580d7e42 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.schema.yaml @@ -0,0 +1,44 @@ + +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 + description: Get a file from an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetFileRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + GetFileRouteRequestBody: + allOf: + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - type: object + required: + - parameters + properties: + parameters: + required: + - path + type: object + properties: + path: + type: string + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.ts new file mode 100644 index 0000000000000..0eb11cf538be5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/get_file.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 type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import { BaseActionRequestSchema } from '../../common/base'; + +export const EndpointActionGetFileSchema = { + body: schema.object({ + ...BaseActionRequestSchema, + + parameters: schema.object({ + path: schema.string({ minLength: 1 }), + }), + }), +}; + +export type ResponseActionGetFileRequestBody = TypeOf<typeof EndpointActionGetFileSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/index.ts new file mode 100644 index 0000000000000..b061771b5c3c2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/get_file/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 * from './get_file'; +export * from './get_file.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen.ts new file mode 100644 index 0000000000000..4b179d47bea2a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Isolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { BaseActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointIsolateRedirectRequestBody = z.infer<typeof EndpointIsolateRedirectRequestBody>; +export const EndpointIsolateRedirectRequestBody = BaseActionSchema; +export type EndpointIsolateRedirectRequestBodyInput = z.input< + typeof EndpointIsolateRedirectRequestBody +>; + +export type EndpointIsolateRedirectResponse = z.infer<typeof EndpointIsolateRedirectResponse>; +export const EndpointIsolateRedirectResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml new file mode 100644 index 0000000000000..89d97c948c5d9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.schema.yaml @@ -0,0 +1,33 @@ +openapi: 3.0.0 +info: + title: Endpoint Isolate Schema + version: '2023-10-31' +paths: + /api/endpoint/isolate: + post: + summary: Permanently redirects to a new location + operationId: EndpointIsolateRedirect + deprecated: true + x-codegen-enabled: true + x-labels: [ess] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + responses: + '308': + description: Permanent Redirect + headers: + Location: + description: Permanently redirects to "/api/endpoint/action/isolate" + schema: + type: string + example: "/api/endpoint/action/isolate" + '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/response_actions/isolate/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/index.ts new file mode 100644 index 0000000000000..b4903efed2144 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/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 * from './isolate'; +export * from './isolate.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.ts new file mode 100644 index 0000000000000..d3e9ee2f2588a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Isolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type IsolateRouteRequestBody = z.infer<typeof IsolateRouteRequestBody>; +export const IsolateRouteRequestBody = NoParametersRequestSchema; + +export type EndpointIsolateActionRequestBody = z.infer<typeof EndpointIsolateActionRequestBody>; +export const EndpointIsolateActionRequestBody = IsolateRouteRequestBody; +export type EndpointIsolateActionRequestBodyInput = z.input< + typeof EndpointIsolateActionRequestBody +>; + +export type EndpointIsolateActionResponse = z.infer<typeof EndpointIsolateActionResponse>; +export const EndpointIsolateActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml new file mode 100644 index 0000000000000..f721c69efa570 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: Isolate Schema + version: '2023-10-31' +paths: + /api/endpoint/action/isolate: + post: + summary: Isolate Action + operationId: EndpointIsolateAction + description: Isolate an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/IsolateRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + IsolateRouteRequestBody: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.ts new file mode 100644 index 0000000000000..787b2cb4362ec --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/isolate/isolate.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 { TypeOf } from '@kbn/config-schema'; +import { NoParametersRequestSchema } from '../../common/base'; + +export const IsolateRouteRequestSchema = NoParametersRequestSchema; +export type IsolationRouteRequestBody = TypeOf<typeof IsolateRouteRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/index.ts new file mode 100644 index 0000000000000..cd316877f542d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/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 './kill_process'; +export * from './kill_process.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.ts new file mode 100644 index 0000000000000..dc24dacf3e120 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Kill Process Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { KillOrSuspendActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointKillProcessActionRequestBody = z.infer< + typeof EndpointKillProcessActionRequestBody +>; +export const EndpointKillProcessActionRequestBody = KillOrSuspendActionSchema; +export type EndpointKillProcessActionRequestBodyInput = z.input< + typeof EndpointKillProcessActionRequestBody +>; + +export type EndpointKillProcessActionResponse = z.infer<typeof EndpointKillProcessActionResponse>; +export const EndpointKillProcessActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml new file mode 100644 index 0000000000000..0014026664fe2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.schema.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + title: Kill Process Schema + version: '2023-10-31' +paths: + /api/endpoint/action/kill_process: + post: + summary: Kill process Action + operationId: EndpointKillProcessAction + description: Kill a running process on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/KillOrSuspendActionSchema' + 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/response_actions/kill_process/kill_process.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.ts new file mode 100644 index 0000000000000..0d42cb8badda1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/kill_process/kill_process.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 type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import { BaseActionRequestSchema } from '../../common/base'; + +// -------------------------------------------------- +// Tests for this module are at: +// x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts:604 +// -------------------------------------------------- + +export const KillProcessRouteRequestSchema = { + body: schema.object( + { + ...BaseActionRequestSchema, + parameters: schema.oneOf([ + schema.object({ pid: schema.number({ min: 1 }) }), + schema.object({ entity_id: schema.string({ minLength: 1 }) }), + + // Process Name currently applies only to SentinelOne (validated below) + schema.object({ process_name: schema.string({ minLength: 1 }) }), + ]), + }, + { + validate(bodyContent) { + if ('process_name' in bodyContent.parameters && bodyContent.agent_type !== 'sentinel_one') { + return `[parameters.process_name]: is not valid with agent type of ${bodyContent.agent_type}`; + } + + if ( + bodyContent.agent_type === 'sentinel_one' && + !('process_name' in bodyContent.parameters) + ) { + return `[parameters.process_name]: missing parameter for agent type of ${bodyContent.agent_type}`; + } + }, + } + ), +}; + +export type KillProcessRequestBody = TypeOf<typeof KillProcessRouteRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/index.ts new file mode 100644 index 0000000000000..d44f9455c9807 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/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 * from './running_procs'; +export * from './running_procs.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.ts new file mode 100644 index 0000000000000..8050f78c1f72c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Running Processes Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type GetProcessesRouteRequestBody = z.infer<typeof GetProcessesRouteRequestBody>; +export const GetProcessesRouteRequestBody = NoParametersRequestSchema; + +export type EndpointGetProcessesActionRequestBody = z.infer< + typeof EndpointGetProcessesActionRequestBody +>; +export const EndpointGetProcessesActionRequestBody = GetProcessesRouteRequestBody; +export type EndpointGetProcessesActionRequestBodyInput = z.input< + typeof EndpointGetProcessesActionRequestBody +>; + +export type EndpointGetProcessesActionResponse = z.infer<typeof EndpointGetProcessesActionResponse>; +export const EndpointGetProcessesActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml new file mode 100644 index 0000000000000..0d5ced3b205f4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.schema.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.0 +info: + title: Get Running Processes Schema + version: '2023-10-31' +paths: + /api/endpoint/action/running_procs: + post: + summary: Get Running Processes Action + operationId: EndpointGetProcessesAction + description: Get list of running processes on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetProcessesRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + GetProcessesRouteRequestBody: + allOf: + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.ts new file mode 100644 index 0000000000000..e9caec65e2aa3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/running_procs/running_procs.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 { TypeOf } from '@kbn/config-schema'; +import { NoParametersRequestSchema } from '../../common/base'; + +export const GetProcessesRouteRequestSchema = NoParametersRequestSchema; +export type GetProcessesRequestBody = TypeOf<typeof GetProcessesRouteRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/index.ts new file mode 100644 index 0000000000000..b7170f14f6d48 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/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 * from './scan'; +export * from './scan.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.ts new file mode 100644 index 0000000000000..302f5de95eaa0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Scan Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type ScanRouteRequestBody = z.infer<typeof ScanRouteRequestBody>; +export const ScanRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + path: z.string(), + }), + }) +); + +export type EndpointScanActionRequestBody = z.infer<typeof EndpointScanActionRequestBody>; +export const EndpointScanActionRequestBody = ScanRouteRequestBody; +export type EndpointScanActionRequestBodyInput = z.input<typeof EndpointScanActionRequestBody>; + +export type EndpointScanActionResponse = z.infer<typeof EndpointScanActionResponse>; +export const EndpointScanActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml new file mode 100644 index 0000000000000..beea986e4546c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.schema.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.0 +info: + title: Scan Schema + version: '2023-10-31' +paths: + /api/endpoint/action/scan: + post: + summary: Scan Action + operationId: EndpointScanAction + description: Scan a file or directory + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ScanRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + ScanRouteRequestBody: + allOf: + - $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + - type: object + required: + - parameters + properties: + parameters: + required: + - path + type: object + properties: + path: + type: string + diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.ts new file mode 100644 index 0000000000000..3cbc04fdf4555 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/scan/scan.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 { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import { BaseActionRequestSchema } from '../../common/base'; + +export const ScanActionRequestSchema = { + body: schema.object({ + ...BaseActionRequestSchema, + + parameters: schema.object({ + path: schema.string({ + minLength: 1, + validate: (value) => { + if (!value.trim().length) { + return 'path cannot be an empty string'; + } + }, + }), + }), + }), +}; + +export type ScanActionRequestBody = TypeOf<typeof ScanActionRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/index.ts new file mode 100644 index 0000000000000..ff4d08a07f23f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/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 * from './suspend_process'; +export * from './suspend_process.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.ts new file mode 100644 index 0000000000000..6c5fb52525628 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Suspend Process Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { KillOrSuspendActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointSuspendProcessActionRequestBody = z.infer< + typeof EndpointSuspendProcessActionRequestBody +>; +export const EndpointSuspendProcessActionRequestBody = KillOrSuspendActionSchema; +export type EndpointSuspendProcessActionRequestBodyInput = z.input< + typeof EndpointSuspendProcessActionRequestBody +>; + +export type EndpointSuspendProcessActionResponse = z.infer< + typeof EndpointSuspendProcessActionResponse +>; +export const EndpointSuspendProcessActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml new file mode 100644 index 0000000000000..f5f5b1e46ed2d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.schema.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.0 +info: + title: Suspend Process Schema + version: '2023-10-31' +paths: + /api/endpoint/action/suspend_process: + post: + summary: Suspend process Action + operationId: EndpointSuspendProcessAction + description: Suspend a running process on an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/KillOrSuspendActionSchema' + 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/response_actions/suspend_process/suspend_process.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.ts new file mode 100644 index 0000000000000..d7cbbdc2b21e6 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.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 type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import { BaseActionRequestSchema } from '../../common/base'; + +export const SuspendProcessRouteRequestSchema = { + body: schema.object({ + ...BaseActionRequestSchema, + parameters: schema.oneOf([ + schema.object({ pid: schema.number({ min: 1 }) }), + schema.object({ entity_id: schema.string({ minLength: 1 }) }), + ]), + }), +}; + +export type SuspendProcessRequestBody = TypeOf<typeof SuspendProcessRouteRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.ts new file mode 100644 index 0000000000000..42ed374c24fce --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Unisolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { BaseActionSchema, SuccessResponse } from '../../../model/schema/common.gen'; + +export type EndpointUnisolateRedirectRequestBody = z.infer< + typeof EndpointUnisolateRedirectRequestBody +>; +export const EndpointUnisolateRedirectRequestBody = BaseActionSchema; +export type EndpointUnisolateRedirectRequestBodyInput = z.input< + typeof EndpointUnisolateRedirectRequestBody +>; + +export type EndpointUnisolateRedirectResponse = z.infer<typeof EndpointUnisolateRedirectResponse>; +export const EndpointUnisolateRedirectResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml new file mode 100644 index 0000000000000..1d347f90fed44 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.schema.yaml @@ -0,0 +1,33 @@ +openapi: 3.0.0 +info: + title: Endpoint Unisolate Schema + version: '2023-10-31' +paths: + /api/endpoint/unisolate: + post: + summary: Permanently redirects to a new location + operationId: EndpointUnisolateRedirect + deprecated: true + x-codegen-enabled: true + x-labels: [ess] + requestBody: + required: true + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' + responses: + '308': + description: Permanent Redirect + headers: + Location: + description: Permanently redirects to "/api/endpoint/action/unisolate" + schema: + type: string + example: "/api/endpoint/action/unisolate" + '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/response_actions/unisolate/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/index.ts new file mode 100644 index 0000000000000..46e542a8d1ef1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/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 './unisolate'; +export * from './unisolate.gen'; +export * from './deprecated_unisolate.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.ts new file mode 100644 index 0000000000000..bf0fd2da1a605 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Unisolate Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse, NoParametersRequestSchema } from '../../../model/schema/common.gen'; + +export type UnisolateRouteRequestBody = z.infer<typeof UnisolateRouteRequestBody>; +export const UnisolateRouteRequestBody = NoParametersRequestSchema; + +export type EndpointUnisolateActionRequestBody = z.infer<typeof EndpointUnisolateActionRequestBody>; +export const EndpointUnisolateActionRequestBody = UnisolateRouteRequestBody; +export type EndpointUnisolateActionRequestBodyInput = z.input< + typeof EndpointUnisolateActionRequestBody +>; + +export type EndpointUnisolateActionResponse = z.infer<typeof EndpointUnisolateActionResponse>; +export const EndpointUnisolateActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml new file mode 100644 index 0000000000000..6c12a21f3241a --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.schema.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + title: Unisolate Schema + version: '2023-10-31' +paths: + /api/endpoint/action/unisolate: + post: + summary: Unisolate Action + operationId: EndpointUnisolateAction + description: Release an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UnisolateRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + UnisolateRouteRequestBody: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/NoParametersRequestSchema' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.ts new file mode 100644 index 0000000000000..84d63886bb8fa --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/unisolate/unisolate.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 { TypeOf } from '@kbn/config-schema'; +import { NoParametersRequestSchema } from '../../common/base'; + +export const UnisolateRouteRequestSchema = NoParametersRequestSchema; +export type UnisolationRouteRequestBody = TypeOf<typeof UnisolateRouteRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/index.ts new file mode 100644 index 0000000000000..34071a503e98f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/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 * from './upload'; +export * from './upload.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.ts new file mode 100644 index 0000000000000..81a908d0b8728 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: File Upload Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse, BaseActionSchema } from '../../../model/schema/common.gen'; + +export type UploadRouteRequestBody = z.infer<typeof UploadRouteRequestBody>; +export const UploadRouteRequestBody = BaseActionSchema.merge( + z.object({ + parameters: z.object({ + overwrite: z.boolean().optional().default(false), + }), + file: z.string(), + }) +); + +export type EndpointUploadActionRequestBody = z.infer<typeof EndpointUploadActionRequestBody>; +export const EndpointUploadActionRequestBody = UploadRouteRequestBody; +export type EndpointUploadActionRequestBodyInput = z.input<typeof EndpointUploadActionRequestBody>; + +export type EndpointUploadActionResponse = z.infer<typeof EndpointUploadActionResponse>; +export const EndpointUploadActionResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml new file mode 100644 index 0000000000000..ff62065ae5403 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.schema.yaml @@ -0,0 +1,46 @@ +openapi: 3.0.0 +info: + title: File Upload Schema + version: '2023-10-31' +paths: + /api/endpoint/action/upload: + post: + summary: Upload Action + operationId: EndpointUploadAction + description: Upload a file to an endpoint + x-codegen-enabled: true + x-labels: [ess, serverless] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UploadRouteRequestBody' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' + +components: + schemas: + UploadRouteRequestBody: + 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/response_actions/upload/upload.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.ts new file mode 100644 index 0000000000000..5bebdfa58c0b0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/response_actions/upload/upload.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 { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; +import { BaseActionRequestSchema } from '../../common/base'; + +export const UploadActionRequestSchema = { + body: schema.object({ + ...BaseActionRequestSchema, + + parameters: schema.object({ + overwrite: schema.maybe(schema.boolean({ defaultValue: false })), + }), + + file: schema.stream(), + }), +}; + +/** Type used by the server's API for `upload` action */ +export type UploadActionApiRequestBody = TypeOf<typeof UploadActionRequestSchema.body>; + +/** + * Type used on the UI side. The `file` definition is different on the UI side, thus the + * need for a separate type. + */ +export type UploadActionUIRequestBody = Omit<UploadActionApiRequestBody, 'file'> & { + file: File; +}; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.ts deleted file mode 100644 index 0aa58c33b8ebf..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.gen.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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Scan Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { BaseActionSchema } from '../model/schema/common.gen'; - -export type ScanActionRequestBody = z.infer<typeof ScanActionRequestBody>; -export const ScanActionRequestBody = BaseActionSchema.merge( - z.object({ - parameters: z.object({ - path: z.string(), - }), - }) -); diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml deleted file mode 100644 index ee7e9c1a9a4a7..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan.schema.yaml +++ /dev/null @@ -1,44 +0,0 @@ -openapi: 3.0.0 -info: - title: Scan Schema - version: '2023-10-31' -paths: - /api/endpoint/action/scan: - post: - summary: Scan Action - operationId: EndpointScanAction - x-codegen-enabled: false - x-labels: - - ess - - serverless - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ScanActionRequestBody' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/SuccessResponse' - -components: - schemas: - ScanActionRequestBody: - allOf: - - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - - type: object - required: - - parameters - properties: - parameters: - required: - - path - type: object - properties: - path: - type: string - diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.ts deleted file mode 100644 index 1c9b3c6980d90..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/scan_route.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 { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; - -export const ScanActionRequestSchema = { - body: schema.object({ - ...BaseActionRequestSchema, - - parameters: schema.object({ - path: schema.string({ - minLength: 1, - validate: (value) => { - if (!value.trim().length) { - return 'path cannot be an empty string'; - } - }, - }), - }), - }), -}; - -export type ScanActionRequestBody = TypeOf<typeof ScanActionRequestSchema.body>; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/index.ts new file mode 100644 index 0000000000000..38db865a0e2c7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/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 './state.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.ts new file mode 100644 index 0000000000000..758835f484034 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Action State Schema + * version: 2023-10-31 + */ + +import type { z } from 'zod'; + +import { SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsStateResponse = z.infer<typeof EndpointGetActionsStateResponse>; +export const EndpointGetActionsStateResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml new file mode 100644 index 0000000000000..a8d9187107cbe --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/state/state.schema.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.0 +info: + title: Endpoint Action State Schema + version: '2023-10-31' +paths: + /api/endpoint/action/state: + get: + summary: Get Action State schema + operationId: EndpointGetActionsState + x-codegen-enabled: true + x-labels: [ess, serverless] + 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/status/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/index.ts new file mode 100644 index 0000000000000..bcbee2a39039b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/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 * from './status'; +export * from './status.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.ts new file mode 100644 index 0000000000000..69918d650e4a0 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Action status schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { AgentIds, SuccessResponse } from '../../model/schema/common.gen'; + +export type EndpointGetActionsStatusRequestQuery = z.infer< + typeof EndpointGetActionsStatusRequestQuery +>; +export const EndpointGetActionsStatusRequestQuery = z.object({ + query: z.object({ + agent_ids: AgentIds.optional(), + }), +}); +export type EndpointGetActionsStatusRequestQueryInput = z.input< + typeof EndpointGetActionsStatusRequestQuery +>; + +export type EndpointGetActionsStatusResponse = z.infer<typeof EndpointGetActionsStatusResponse>; +export const EndpointGetActionsStatusResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml new file mode 100644 index 0000000000000..ec8e3d386bcd2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.schema.yaml @@ -0,0 +1,29 @@ +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 + description: Get action status + x-codegen-enabled: true + x-labels: [ess, serverless] + 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/action_status_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/actions/action_status_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/actions/status/status.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.ts deleted file mode 100644 index 81edb01197c69..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/suspend_process_route.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 { schema } from '@kbn/config-schema'; -import { BaseActionRequestSchema } from './common/base'; - -export const SuspendProcessRouteRequestSchema = { - body: schema.object({ - ...BaseActionRequestSchema, - parameters: schema.oneOf([ - schema.object({ pid: schema.number({ min: 1 }) }), - schema.object({ entity_id: schema.string({ minLength: 1 }) }), - ]), - }), -}; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts deleted file mode 100644 index d4c68a439c172..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.gen.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Endpoint Unisolate Schema - * version: 2023-10-31 - */ - -import type { z } from 'zod'; - -import { BaseActionSchema, SuccessResponse } from '../model/schema/common.gen'; - -export type EndpointUnisolateRedirectRequestBody = z.infer< - typeof EndpointUnisolateRedirectRequestBody ->; -export const EndpointUnisolateRedirectRequestBody = BaseActionSchema; -export type EndpointUnisolateRedirectRequestBodyInput = z.input< - typeof EndpointUnisolateRedirectRequestBody ->; - -export type EndpointUnisolateRedirectResponse = z.infer<typeof EndpointUnisolateRedirectResponse>; -export const EndpointUnisolateRedirectResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml deleted file mode 100644 index 570854a054799..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.schema.yaml +++ /dev/null @@ -1,33 +0,0 @@ -openapi: 3.0.0 -info: - title: Endpoint Unisolate Schema - version: '2023-10-31' -paths: - /api/endpoint/unisolate: - post: - summary: Permanently redirects to a new location - operationId: EndpointUnisolateRedirect - x-codegen-enabled: true - x-labels: - - ess - requestBody: - required: true - content: - application/json: - schema: - $ref: '../model/schema/common.schema.yaml#/components/schemas/BaseActionSchema' - responses: - '308': - description: Permanent Redirect - headers: - Location: - description: Permanently redirects to "/api/endpoint/action/unisolate" - schema: - type: string - example: "/api/endpoint/action/unisolate" - '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/unisolate_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.ts deleted file mode 100644 index 2d9f2d2f5725b..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/actions/unisolate_route.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 { NoParametersRequestSchema } from './common/base'; - -export const UnisolateRouteRequestSchema = NoParametersRequestSchema; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/index.ts index 6dfbcd20d12fb..5917101be93a1 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/index.ts +++ b/x-pack/plugins/security_solution/common/api/endpoint/index.ts @@ -5,28 +5,28 @@ * 2.0. */ -export * from './actions/audit_log_route'; -export * from './actions/action_status_route'; -export * from './actions/details_route'; -export * from './actions/file_download_route'; -export * from './actions/file_info_route'; -export * from './actions/file_upload_route'; -export * from './actions/list_route'; -export * from './actions/isolate_route'; -export * from './actions/unisolate_route'; -export * from './actions/kill_process_route'; -export * from './actions/suspend_process_route'; -export * from './actions/get_processes_route'; -export * from './actions/get_file_route'; -export * from './actions/execute_route'; -export * from './actions/scan_route'; export * from './actions/common/base'; export * from './actions/common/response_actions'; -export * from './metadata/list_metadata_route'; -export * from './metadata/get_metadata_route'; +export * from './actions/action_log'; +export * from './actions/status'; +export * from './actions/details'; +export * from './actions/file_download'; +export * from './actions/file_info'; +export * from './actions/list'; -export * from './policy/get_policy_response_route'; -export * from './policy/get_agent_policy_summary_route'; +export * from './actions/response_actions/isolate'; +export * from './actions/response_actions/unisolate'; +export * from './actions/response_actions/kill_process'; +export * from './actions/response_actions/suspend_process'; +export * from './actions/response_actions/running_procs'; +export * from './actions/response_actions/get_file'; +export * from './actions/response_actions/execute'; +export * from './actions/response_actions/upload'; +export * from './actions/response_actions/scan'; -export * from './suggestions/get_suggestions_route'; +export * from './metadata'; + +export * from './policy'; + +export * from './suggestions'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.ts new file mode 100644 index 0000000000000..a520b56c07195 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Metadata Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListRequestQuery } from './list_metadata.gen'; +import { SuccessResponse } from '../model/schema/common.gen'; + +export type GetEndpointMetadataListRequestQuery = z.infer< + typeof GetEndpointMetadataListRequestQuery +>; +export const GetEndpointMetadataListRequestQuery = z.object({ + query: ListRequestQuery, +}); +export type GetEndpointMetadataListRequestQueryInput = z.input< + typeof GetEndpointMetadataListRequestQuery +>; + +export type GetEndpointMetadataListResponse = z.infer<typeof GetEndpointMetadataListResponse>; +export const GetEndpointMetadataListResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.schema.yaml new file mode 100644 index 0000000000000..5ecea044053d2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_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: true + x-labels: [ess, serverless] + 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 + x-labels: [ess, serverless] + 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 + x-labels: [ess, serverless] + parameters: + - name: id + in: path + required: true + schema: + 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/metadata/get_metadata_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/get_metadata.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/index.ts new file mode 100644 index 0000000000000..d95135ef8aa90 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/metadata/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 * from './get_metadata'; +export * from './get_metadata.gen'; + +export * from './list_metadata'; +export * from './list_metadata.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/metadata/list_metadata.ts 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 deleted file mode 100644 index a8af6101472d3..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/metadata/metadata.schema.yaml +++ /dev/null @@ -1,67 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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 index 4e3885391f241..57af5c334f4f0 100644 --- 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 @@ -70,6 +70,7 @@ export const Command = z.enum([ 'get-file', 'execute', 'upload', + 'scan', ]); export type CommandEnum = typeof Command.enum; export const CommandEnum = Command.enum; @@ -98,16 +99,22 @@ export type UserIds = z.infer<typeof UserIds>; export const UserIds = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); /** - * With Outputs + * Shows detailed outputs for an action response */ export type WithOutputs = z.infer<typeof WithOutputs>; export const WithOutputs = z.union([z.array(z.string().min(1)).min(1), z.string().min(1)]); +/** + * Type of response action + */ export type Type = z.infer<typeof Type>; export const Type = z.enum(['automated', 'manual']); export type TypeEnum = typeof Type.enum; export const TypeEnum = Type.enum; +/** + * List of types of response actions + */ export type Types = z.infer<typeof Types>; export const Types = z.array(Type); @@ -135,17 +142,28 @@ export const Comment = z.string(); export type Parameters = z.infer<typeof Parameters>; export const Parameters = z.object({}); +export type AgentTypes = z.infer<typeof AgentTypes>; +export const AgentTypes = z.enum(['endpoint', 'sentinel_one', 'crowdstrike']); +export type AgentTypesEnum = typeof AgentTypes.enum; +export const AgentTypesEnum = AgentTypes.enum; + export type BaseActionSchema = z.infer<typeof BaseActionSchema>; export const BaseActionSchema = z.object({ - endpoint_ids: EndpointIds.optional(), + endpoint_ids: EndpointIds, alert_ids: AlertIds.optional(), case_ids: CaseIds.optional(), comment: Comment.optional(), parameters: Parameters.optional(), + agent_type: AgentTypes.optional(), +}); + +export type NoParametersRequestSchema = z.infer<typeof NoParametersRequestSchema>; +export const NoParametersRequestSchema = z.object({ + body: BaseActionSchema, }); -export type ProcessActionSchemas = z.infer<typeof ProcessActionSchemas>; -export const ProcessActionSchemas = BaseActionSchema.merge( +export type KillOrSuspendActionSchema = z.infer<typeof KillOrSuspendActionSchema>; +export const KillOrSuspendActionSchema = BaseActionSchema.merge( z.object({ parameters: z.union([ 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 index 306bd31f4886b..00cf557b98496 100644 --- 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 @@ -54,6 +54,7 @@ components: - get-file - execute - upload + - scan minLength: 1 description: The command to be executed (cannot be an empty string) @@ -101,16 +102,18 @@ components: minItems: 1 - type: string minLength: 1 - description: With Outputs + description: Shows detailed outputs for an action response Type: type: string + description: Type of response action enum: - automated - manual Types: type: array + description: List of types of response actions items: $ref: '#/components/schemas/Type' minLength: 1 @@ -136,6 +139,12 @@ components: Parameters: type: object description: Optional parameters object + AgentTypes: + type: string + enum: + - endpoint + - sentinel_one + - crowdstrike BaseActionSchema: x-inline: true @@ -151,8 +160,20 @@ components: $ref: '#/components/schemas/Comment' parameters: $ref: '#/components/schemas/Parameters' + agent_type: + $ref: '#/components/schemas/AgentTypes' + required: + - endpoint_ids + + NoParametersRequestSchema: + type: object + required: + - body + properties: + body: + $ref: '#/components/schemas/BaseActionSchema' - ProcessActionSchemas: + KillOrSuspendActionSchema: allOf: - $ref: '#/components/schemas/BaseActionSchema' - type: object diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.ts new file mode 100644 index 0000000000000..9a20dcfc3b310 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Policy Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SuccessResponse } from '../model/schema/common.gen'; + +export type GetAgentPolicySummaryRequestQuery = z.infer<typeof GetAgentPolicySummaryRequestQuery>; +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<typeof GetAgentPolicySummaryResponse>; +export const GetAgentPolicySummaryResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml new file mode 100644 index 0000000000000..803010a4e8268 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.schema.yaml @@ -0,0 +1,32 @@ +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 + deprecated: true + x-codegen-enabled: true + x-labels: [ess, serverless] + 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' diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/get_agent_policy_summary_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/get_agent_policy_summary_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/deprecated_agent_policy_summary.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/index.ts new file mode 100644 index 0000000000000..e27dc83a3b993 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/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. + */ + +export * from './policy_response'; +export * from './deprecated_agent_policy_summary'; +export * from './policy_response.gen'; +export * from './deprecated_agent_policy_summary.gen'; 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 deleted file mode 100644 index 9cc37e874f6a2..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.gen.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Endpoint Policy Schema - * version: 2023-10-31 - */ - -import { z } from 'zod'; - -import { SuccessResponse, AgentId } from '../model/schema/common.gen'; - -export type GetAgentPolicySummaryRequestQuery = z.infer<typeof GetAgentPolicySummaryRequestQuery>; -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<typeof GetAgentPolicySummaryResponse>; -export const GetAgentPolicySummaryResponse = SuccessResponse; -export type GetPolicyResponseRequestQuery = z.infer<typeof GetPolicyResponseRequestQuery>; -export const GetPolicyResponseRequestQuery = z.object({ - query: z.object({ - agentId: AgentId.optional(), - }), -}); -export type GetPolicyResponseRequestQueryInput = z.input<typeof GetPolicyResponseRequestQuery>; - -export type GetPolicyResponseResponse = z.infer<typeof GetPolicyResponseResponse>; -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 deleted file mode 100644 index 6084ae851bc17..0000000000000 --- a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy.schema.yaml +++ /dev/null @@ -1,58 +0,0 @@ -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 - x-labels: - - ess - - serverless - 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 - x-labels: - - ess - - serverless - 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/policy/policy_response.gen.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.ts new file mode 100644 index 0000000000000..e2eb2595be662 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Endpoint Policy Schema + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { AgentId, SuccessResponse } from '../model/schema/common.gen'; + +export type GetPolicyResponseRequestQuery = z.infer<typeof GetPolicyResponseRequestQuery>; +export const GetPolicyResponseRequestQuery = z.object({ + query: z.object({ + agentId: AgentId.optional(), + }), +}); +export type GetPolicyResponseRequestQueryInput = z.input<typeof GetPolicyResponseRequestQuery>; + +export type GetPolicyResponseResponse = z.infer<typeof GetPolicyResponseResponse>; +export const GetPolicyResponseResponse = SuccessResponse; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml new file mode 100644 index 0000000000000..7acc39013da85 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.schema.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.0 +info: + title: Endpoint Policy Schema + version: '2023-10-31' +paths: + /api/endpoint/policy_response: + get: + summary: Get Policy Response schema + operationId: GetPolicyResponse + x-codegen-enabled: true + x-labels: [ess, serverless] + 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/policy/get_policy_response_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/policy/get_policy_response_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/policy/policy_response.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/index.ts new file mode 100644 index 0000000000000..c49eecd853e2c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/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 * from './protection_updates_note'; +export * from './protection_updates_note.gen'; diff --git a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml index ce2760780b627..44c02e417b185 100644 --- a/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.schema.yaml @@ -8,9 +8,7 @@ paths: summary: Get Protection Updates Note schema operationId: GetProtectionUpdatesNote x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] parameters: - name: package_policy_id in: path @@ -28,9 +26,7 @@ paths: summary: Create Update Protection Updates Note schema operationId: CreateUpdateProtectionUpdatesNote x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] requestBody: required: true content: 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.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note_schema.ts rename to x-pack/plugins/security_solution/common/api/endpoint/protection_updates_note/protection_updates_note.ts 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 index b7e1b0f34780e..573f9c0e3992f 100644 --- 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 @@ -8,9 +8,7 @@ paths: summary: Get suggestions operationId: GetEndpointSuggestions x-codegen-enabled: true - x-labels: - - ess - - serverless + x-labels: [ess, serverless] requestBody: required: true content: diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions_route.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.ts similarity index 100% rename from x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions_route.ts rename to x-pack/plugins/security_solution/common/api/endpoint/suggestions/get_suggestions.ts diff --git a/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/index.ts new file mode 100644 index 0000000000000..a4b3e85842ae9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/endpoint/suggestions/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 * from './get_suggestions'; +export * from './get_suggestions.gen'; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts index 22f3f682ae98a..4b747b6a48674 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts @@ -16,7 +16,7 @@ import { z } from 'zod'; -import { IdField } from './common.gen'; +import { IdField, AssetCriticalityRecord } from './common.gen'; export type DeleteAssetCriticalityRecordRequestQuery = z.infer< typeof DeleteAssetCriticalityRecordRequestQuery @@ -38,3 +38,14 @@ export const DeleteAssetCriticalityRecordRequestQuery = z.object({ export type DeleteAssetCriticalityRecordRequestQueryInput = z.input< typeof DeleteAssetCriticalityRecordRequestQuery >; + +export type DeleteAssetCriticalityRecordResponse = z.infer< + typeof DeleteAssetCriticalityRecordResponse +>; +export const DeleteAssetCriticalityRecordResponse = z.object({ + /** + * If the record was deleted. If false the record did not exist. + */ + deleted: z.boolean(), + record: AssetCriticalityRecord.optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml index 2b4f05528da0c..521cacd51406b 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml @@ -33,5 +33,17 @@ paths: responses: '200': description: Successful response + content: + application/json: + schema: + type: object + properties: + deleted: + type: boolean + description: If the record was deleted. If false the record did not exist. + record: + $ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord' + required: + - deleted '400': description: Invalid request diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.gen.ts index 3d828e0e38a7a..8f53ab8c558c3 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.gen.ts @@ -14,30 +14,11 @@ * version: 1 */ -import { z } from 'zod'; +import type { z } from 'zod'; -export type EntityAnalyticsPrivileges = z.infer<typeof EntityAnalyticsPrivileges>; -export const EntityAnalyticsPrivileges = z.object({ - has_all_required: z.boolean(), - has_read_permissions: z.boolean().optional(), - has_write_permissions: z.boolean().optional(), - privileges: z.object({ - elasticsearch: z.object({ - cluster: z - .object({ - manage_index_templates: z.boolean().optional(), - manage_transform: z.boolean().optional(), - }) - .optional(), - index: z - .object({}) - .catchall( - z.object({ - read: z.boolean().optional(), - write: z.boolean().optional(), - }) - ) - .optional(), - }), - }), -}); +import { EntityAnalyticsPrivileges } from '../common/common.gen'; + +export type AssetCriticalityGetPrivilegesResponse = z.infer< + typeof AssetCriticalityGetPrivilegesResponse +>; +export const AssetCriticalityGetPrivilegesResponse = EntityAnalyticsPrivileges; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml index 548237265d0fb..267665613b7c7 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality_privileges.schema.yaml @@ -7,6 +7,7 @@ paths: get: x-labels: [ess, serverless] x-internal: true + x-codegen-enabled: true operationId: AssetCriticalityGetPrivileges summary: Get Asset Criticality Privileges responses: @@ -15,49 +16,11 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/EntityAnalyticsPrivileges' + $ref: '../common/common.schema.yaml#/components/schemas/EntityAnalyticsPrivileges' example: elasticsearch: index: '.asset-criticality.asset-criticality-*': read: true write: false - has_all_required: false -components: - schemas: - EntityAnalyticsPrivileges: - type: object - properties: - has_all_required: - type: boolean - has_read_permissions: - type: boolean - has_write_permissions: - type: boolean - privileges: - type: object - properties: - elasticsearch: - type: object - properties: - cluster: - type: object - properties: - manage_index_templates: - type: boolean - manage_transform: - type: boolean - index: - type: object - additionalProperties: - type: object - properties: - read: - type: boolean - write: - type: boolean - required: - - elasticsearch - required: - - has_all_required - - privileges + has_all_required: false \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts index 4f5590938be97..9574c4b575770 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts @@ -14,6 +14,3 @@ export * from './list_asset_criticality.gen'; export * from './create_asset_criticality.gen'; export * from './get_asset_criticality.gen'; export * from './delete_asset_criticality.gen'; -export * from './internal_create_asset_criticality.gen'; -export * from './internal_get_asset_criticality.gen'; -export * from './internal_delete_asset_criticality.gen'; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_create_asset_criticality.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_create_asset_criticality.gen.ts deleted file mode 100644 index 44ea824720cb6..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_create_asset_criticality.gen.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Internal Asset Criticality Create Record Schema - * version: 1 - */ - -import { z } from 'zod'; - -import { CreateAssetCriticalityRecord, AssetCriticalityRecord } from './common.gen'; - -export type InternalCreateAssetCriticalityRecordRequestBody = z.infer< - typeof InternalCreateAssetCriticalityRecordRequestBody ->; -export const InternalCreateAssetCriticalityRecordRequestBody = CreateAssetCriticalityRecord.merge( - z.object({ - /** - * If 'wait_for' the request will wait for the index refresh. - */ - refresh: z.literal('wait_for').optional(), - }) -); -export type InternalCreateAssetCriticalityRecordRequestBodyInput = z.input< - typeof InternalCreateAssetCriticalityRecordRequestBody ->; - -export type InternalCreateAssetCriticalityRecordResponse = z.infer< - typeof InternalCreateAssetCriticalityRecordResponse ->; -export const InternalCreateAssetCriticalityRecordResponse = AssetCriticalityRecord; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_create_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_create_asset_criticality.schema.yaml deleted file mode 100644 index 89bf56b71056c..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_create_asset_criticality.schema.yaml +++ /dev/null @@ -1,35 +0,0 @@ -openapi: 3.0.0 -info: - version: '1' - title: Internal Asset Criticality Create Record Schema -paths: - /internal/asset_criticality: - post: - x-labels: [ess, serverless] - x-internal: true - x-codegen-enabled: true - operationId: InternalCreateAssetCriticalityRecord - summary: Deprecated Internal Create Criticality Record - deprecated: true - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: './common.schema.yaml#/components/schemas/CreateAssetCriticalityRecord' - - type: object - properties: - refresh: - type: string - enum: [wait_for] - description: If 'wait_for' the request will wait for the index refresh. - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord' - '400': - description: Invalid request diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_delete_asset_criticality.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_delete_asset_criticality.gen.ts deleted file mode 100644 index ca5d2b06251c7..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_delete_asset_criticality.gen.ts +++ /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. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Internal Asset Criticality Delete Record Schema - * version: 1 - */ - -import { z } from 'zod'; - -import { IdField } from './common.gen'; - -export type InternalDeleteAssetCriticalityRecordRequestQuery = z.infer< - typeof InternalDeleteAssetCriticalityRecordRequestQuery ->; -export const InternalDeleteAssetCriticalityRecordRequestQuery = z.object({ - /** - * The ID value of the asset. - */ - id_value: z.string(), - /** - * The field representing the ID. - */ - id_field: IdField, - /** - * If 'wait_for' the request will wait for the index refresh. - */ - refresh: z.literal('wait_for').optional(), -}); -export type InternalDeleteAssetCriticalityRecordRequestQueryInput = z.input< - typeof InternalDeleteAssetCriticalityRecordRequestQuery ->; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_delete_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_delete_asset_criticality.schema.yaml deleted file mode 100644 index 2fa847315cbdc..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_delete_asset_criticality.schema.yaml +++ /dev/null @@ -1,39 +0,0 @@ -openapi: 3.0.0 -info: - version: '1' - title: Internal Asset Criticality Delete Record Schema -paths: - /internal/asset_criticality: - delete: - x-labels: [ess, serverless] - x-internal: true - x-codegen-enabled: true - operationId: InternalDeleteAssetCriticalityRecord - summary: Deprecated Internal Delete Criticality Record - deprecated: true - parameters: - - name: id_value - in: query - required: true - schema: - type: string - description: The ID value of the asset. - - name: id_field - in: query - required: true - schema: - $ref: './common.schema.yaml#/components/schemas/IdField' - example: 'host.name' - description: The field representing the ID. - - name: refresh - in: query - required: false - schema: - type: string - enum: [wait_for] - description: If 'wait_for' the request will wait for the index refresh. - responses: - '200': - description: Successful response - '400': - description: Invalid request diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_get_asset_criticality.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_get_asset_criticality.gen.ts deleted file mode 100644 index 1edf38c55ee0d..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_get_asset_criticality.gen.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Internal Asset Criticality Get Record Schema - * version: 1 - */ - -import { z } from 'zod'; - -import { IdField, AssetCriticalityRecord } from './common.gen'; - -export type InternalGetAssetCriticalityRecordRequestQuery = z.infer< - typeof InternalGetAssetCriticalityRecordRequestQuery ->; -export const InternalGetAssetCriticalityRecordRequestQuery = z.object({ - /** - * The ID value of the asset. - */ - id_value: z.string(), - /** - * The field representing the ID. - */ - id_field: IdField, -}); -export type InternalGetAssetCriticalityRecordRequestQueryInput = z.input< - typeof InternalGetAssetCriticalityRecordRequestQuery ->; - -export type InternalGetAssetCriticalityRecordResponse = z.infer< - typeof InternalGetAssetCriticalityRecordResponse ->; -export const InternalGetAssetCriticalityRecordResponse = AssetCriticalityRecord; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_get_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_get_asset_criticality.schema.yaml deleted file mode 100644 index d528fc391d684..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/internal_get_asset_criticality.schema.yaml +++ /dev/null @@ -1,38 +0,0 @@ -openapi: 3.0.0 -info: - version: '1' - title: Internal Asset Criticality Get Record Schema -paths: - /internal/asset_criticality: - get: - x-labels: [ess, serverless] - x-internal: true - x-codegen-enabled: true - operationId: InternalGetAssetCriticalityRecord - summary: Deprecated Internal Get Criticality Record - deprecated: true - parameters: - - name: id_value - in: query - required: true - schema: - type: string - description: The ID value of the asset. - - name: id_field - in: query - required: true - schema: - $ref: './common.schema.yaml#/components/schemas/IdField' - example: 'host.name' - description: The field representing the ID. - responses: - '200': - description: Successful response - content: - application/json: - schema: - $ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord' - '400': - description: Invalid request - '404': - description: Criticality record not found diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.gen.ts index 5b3538917f78c..8e6f3841b8f6d 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.gen.ts @@ -18,6 +18,32 @@ import { z } from 'zod'; import { AssetCriticalityLevel } from '../asset_criticality/common.gen'; +export type EntityAnalyticsPrivileges = z.infer<typeof EntityAnalyticsPrivileges>; +export const EntityAnalyticsPrivileges = z.object({ + has_all_required: z.boolean(), + has_read_permissions: z.boolean().optional(), + has_write_permissions: z.boolean().optional(), + privileges: z.object({ + elasticsearch: z.object({ + cluster: z + .object({ + manage_index_templates: z.boolean().optional(), + manage_transform: z.boolean().optional(), + }) + .optional(), + index: z + .object({}) + .catchall( + z.object({ + read: z.boolean().optional(), + write: z.boolean().optional(), + }) + ) + .optional(), + }), + }), +}); + export type EntityAfterKey = z.infer<typeof EntityAfterKey>; export const EntityAfterKey = z.object({}).catchall(z.string()); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.schema.yaml index 63aa739d2133d..67428b261a0f9 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/common/common.schema.yaml @@ -6,6 +6,42 @@ info: paths: {} components: schemas: + EntityAnalyticsPrivileges: + type: object + properties: + has_all_required: + type: boolean + has_read_permissions: + type: boolean + has_write_permissions: + type: boolean + privileges: + type: object + properties: + elasticsearch: + type: object + properties: + cluster: + type: object + properties: + manage_index_templates: + type: boolean + manage_transform: + type: boolean + index: + type: object + additionalProperties: + type: object + properties: + read: + type: boolean + write: + type: boolean + required: + - elasticsearch + required: + - has_all_required + - privileges EntityAfterKey: type: object additionalProperties: diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/index.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/index.ts index afb71bbd5bb17..9d3c3a29bdebf 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/index.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/index.ts @@ -8,3 +8,4 @@ export * from './asset_criticality'; export * from './risk_engine'; export * from './risk_score'; +export { EntityAnalyticsPrivileges } from './common'; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/get_risk_engine_privileges.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/get_risk_engine_privileges.gen.ts new file mode 100644 index 0000000000000..db07db331e477 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/get_risk_engine_privileges.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get Risk Engine Privileges Schema + * version: 1 + */ + +import type { z } from 'zod'; + +import { EntityAnalyticsPrivileges } from '../common/common.gen'; + +export type RiskEngineGetPrivilegesResponse = z.infer<typeof RiskEngineGetPrivilegesResponse>; +export const RiskEngineGetPrivilegesResponse = EntityAnalyticsPrivileges; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/get_risk_engine_privileges.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/get_risk_engine_privileges.schema.yaml new file mode 100644 index 0000000000000..0fcaf08f10c16 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/get_risk_engine_privileges.schema.yaml @@ -0,0 +1,26 @@ +openapi: 3.0.0 +info: + title: Get Risk Engine Privileges Schema + version: '1' +paths: + /internal/risk_engine/privileges: + get: + x-labels: [ess, serverless] + x-internal: true + x-codegen-enabled: true + operationId: RiskEngineGetPrivileges + summary: Get Risk Engine Privileges + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '../common/common.schema.yaml#/components/schemas/EntityAnalyticsPrivileges' + example: + elasticsearch: + index: + 'risk-score.risk-score-*': + read: true + write: false + has_all_required: false \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/index.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/index.ts index 97f11da2ef090..94d587cd2bfc7 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/index.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/risk_engine/index.ts @@ -14,3 +14,4 @@ export * from './engine_status_route.gen'; export * from './calculation_route.gen'; export * from './preview_route.gen'; export * from './entity_calculation_route.gen'; +export * from './get_risk_engine_privileges.gen'; diff --git a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.ts new file mode 100644 index 0000000000000..ecdbf03058130 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Draft Timeline API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { TimelineType, TimelineResponse } from '../model/components.gen'; + +export type CleanDraftTimelinesRequestBody = z.infer<typeof CleanDraftTimelinesRequestBody>; +export const CleanDraftTimelinesRequestBody = z.object({ + timelineType: TimelineType, +}); +export type CleanDraftTimelinesRequestBodyInput = z.input<typeof CleanDraftTimelinesRequestBody>; + +export type CleanDraftTimelinesResponse = z.infer<typeof CleanDraftTimelinesResponse>; +export const CleanDraftTimelinesResponse = z.object({ + data: z.object({ + persistTimeline: z.object({ + timeline: TimelineResponse, + }), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml new file mode 100644 index 0000000000000..e5e9f3ed4cfc6 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.schema.yaml @@ -0,0 +1,74 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Draft Timeline API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline/_draft: + post: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: CleanDraftTimelines + summary: Retrieves a draft timeline or timeline template. + description: | + Retrieves a clean draft timeline. If a draft timeline does not exist, it is created and returned. + tags: + - access:securitySolution + requestBody: + description: The type of timeline to create. Valid values are `default` and `template`. + required: true + content: + application/json: + schema: + type: object + required: [timelineType] + properties: + timelineType: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineType' + responses: + '200': + description: Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft timeline is cleared and returned. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [persistTimeline] + properties: + persistTimeline: + type: object + required: [timeline] + properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + '403': + description: Indicates that the user does not have the required permissions to create a draft timeline. + content: + application:json: + schema: + type: object + properties: + message: + type: string + status_code: + type: number + '409': + description: Indicates that there is already a draft timeline with the given timelineId. + content: + application:json: + schema: + type: object + properties: + message: + type: string + status_code: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route_schema.yaml deleted file mode 100644 index 2a5d004507fb8..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route_schema.yaml +++ /dev/null @@ -1,70 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Draft Timeline API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline/_draft: - post: - operationId: cleanDraftTimelines - summary: Retrieves a draft timeline or timeline template. - description: | - Retrieves a clean draft timeline. If a draft timeline does not exist, it is created and returned. - tags: - - access:securitySolution - requestBody: - description: The type of timeline to create. Valid values are `default` and `template`. - required: true - content: - application/json: - schema: - type: object - properties: - timelineType: - $ref: '../model/components.yaml#/components/schemas/TimelineType' - responses: - '200': - description: Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft timeline is cleared and returned. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '../model/components.yaml#/components/schemas/TimelineResponse' - required: - - data - '403': - description: Indicates that the user does not have the required permissions to create a draft timeline. - content: - application:json: - schema: - type: object - properties: - message: - type: string - status_code: - type: number - '409': - description: Indicates that there is already a draft timeline with the given timelineId. - content: - application:json: - schema: - type: object - properties: - message: - type: string - status_code: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.ts new file mode 100644 index 0000000000000..9b5775ca81617 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Create Timelines API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { + TimelineStatus, + TimelineType, + SavedTimeline, + TimelineResponse, +} from '../model/components.gen'; + +export type CreateTimelinesRequestBody = z.infer<typeof CreateTimelinesRequestBody>; +export const CreateTimelinesRequestBody = z.object({ + status: TimelineStatus.nullable().optional(), + timelineId: z.string().nullable().optional(), + templateTimelineId: z.string().nullable().optional(), + templateTimelineVersion: z.number().nullable().optional(), + timelineType: TimelineType.nullable().optional(), + version: z.string().nullable().optional(), + timeline: SavedTimeline, +}); +export type CreateTimelinesRequestBodyInput = z.input<typeof CreateTimelinesRequestBody>; + +export type CreateTimelinesResponse = z.infer<typeof CreateTimelinesResponse>; +export const CreateTimelinesResponse = z.object({ + data: z.object({ + persistTimeline: z.object({ + timeline: TimelineResponse.optional(), + }), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml new file mode 100644 index 0000000000000..561ce75c84fe2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route.schema.yaml @@ -0,0 +1,82 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Create Timelines API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/timeline-api-create.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline: + post: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: CreateTimelines + summary: Creates a new timeline. + tags: + - access:securitySolution + requestBody: + description: The required timeline fields used to create a new timeline along with optional fields that will be created if not provided. + required: true + content: + application/json: + schema: + type: object + required: + - timeline + properties: + status: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineStatus' + nullable: true + timelineId: + type: string + nullable: true + templateTimelineId: + type: string + nullable: true + templateTimelineVersion: + type: number + nullable: true + timelineType: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineType' + nullable: true + version: + type: string + nullable: true + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' + responses: + '200': + description: Indicates the timeline was successfully created. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [persistTimeline] + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + '405': + description: Indicates that there was an error in the timeline creation. + content: + application/json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route_schema.yaml deleted file mode 100644 index d2e2817642943..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/create_timelines/create_timelines_route_schema.yaml +++ /dev/null @@ -1,82 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Create Timelines API - version: 8.9.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/timeline-api-create.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline: - post: - operationId: createTimelines - summary: Creates a new timeline. - tags: - - access:securitySolution - requestBody: - description: The required timeline fields used to create a new timeline along with optional fields that will be created if not provided. - required: true - content: - application/json: - schema: - type: object - required: - - timeline - properties: - status: - allOf: - - $ref: '../model/components.yaml#/components/schemas/TimelineStatus' - - nullable: true - timelineId: - type: string - nullable: true - templateTimelineId: - type: string - nullable: true - templateTimelineVersion: - type: number - nullable: true - timelineType: - allOf: - - $ref: '../model/components.yaml#/components/schemas/TimelineType' - - nullable: true - version: - type: string - nullable: true - timeline: - $ref: '../model/components.yaml#/components/schemas/SavedTimeline' - responses: - '200': - description: Indicates the timeline was successfully created. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '../model/components.yaml#/components/schemas/TimelineResponse' - required: - - data - '405': - description: Indicates that there was an error in the timeline creation. - content: - application/json: - schema: - type: object - properties: - body: - type: string - statusCode: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.gen.ts new file mode 100644 index 0000000000000..eceda37016443 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Notes API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type DeleteNoteRequestBody = z.infer<typeof DeleteNoteRequestBody>; +export const DeleteNoteRequestBody = z.union([ + z + .object({ + noteId: z.string(), + }) + .nullable(), + z.object({ + noteIds: z.array(z.string()).nullable(), + }), +]); +export type DeleteNoteRequestBodyInput = z.input<typeof DeleteNoteRequestBody>; + +export type DeleteNoteResponse = z.infer<typeof DeleteNoteResponse>; +export const DeleteNoteResponse = z.object({ + data: z.object({}).optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml new file mode 100644 index 0000000000000..b330cc82d2fdc --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route.schema.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Notes API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/note: + delete: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: DeleteNote + summary: Deletes a note from a timeline. + tags: + - access:securitySolution + requestBody: + description: The id of the note to delete. + required: true + content: + application/json: + schema: + oneOf: + - type: object + required: [noteId] + nullable: true + properties: + noteId: + type: string + - type: object + required: [noteIds] + properties: + noteIds: + type: array + nullable: true + items: + type: string + responses: + '200': + description: Indicates the note was successfully deleted. + content: + application/json: + schema: + type: object + properties: + data: + type: object diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route_schema.yaml deleted file mode 100644 index 16901b9b0f804..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_note/delete_note_route_schema.yaml +++ /dev/null @@ -1,47 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Notes API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/note: - delete: - operationId: deleteNote - summary: Deletes a note from a timeline. - tags: - - access:securitySolution - requestBody: - description: The id of the note to delete. - required: true - content: - application/json: - schema: - oneOf: - type: object - properties: - noteId: - type: string - nullable: true - type: object - properties: - noteIds: - type: array - items: - type: string - nullable: true - responses: - '200': - description: Indicates the note was successfully deleted. - content: - application/json: - schema: - type: object - properties: - data: - type: object diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.gen.ts new file mode 100644 index 0000000000000..a52a5779770b3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Delete Timelines API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type DeleteTimelinesRequestBody = z.infer<typeof DeleteTimelinesRequestBody>; +export const DeleteTimelinesRequestBody = z.object({ + savedObjectIds: z.array(z.string()), + /** + * Saved search ids that should be deleted alongside the timelines + */ + searchIds: z.array(z.string()).optional(), +}); +export type DeleteTimelinesRequestBodyInput = z.input<typeof DeleteTimelinesRequestBody>; + +export type DeleteTimelinesResponse = z.infer<typeof DeleteTimelinesResponse>; +export const DeleteTimelinesResponse = z.object({ + data: z.object({ + deleteTimeline: z.boolean(), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml new file mode 100644 index 0000000000000..85c1b7c9a6736 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route.schema.yaml @@ -0,0 +1,56 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Delete Timelines API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/timeline-api-delete.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline: + delete: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: DeleteTimelines + summary: Deletes one or more timelines or timeline templates. + tags: + - access:securitySolution + requestBody: + description: The ids of the timelines or timeline templates to delete. + required: true + content: + application/json: + schema: + type: object + required: [savedObjectIds] + properties: + savedObjectIds: + type: array + items: + type: string + searchIds: + type: array + description: Saved search ids that should be deleted alongside the timelines + items: + type: string + responses: + '200': + description: Indicates the timeline was successfully deleted. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [deleteTimeline] + properties: + deleteTimeline: + type: boolean diff --git a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route_schema.yaml deleted file mode 100644 index 5be42a6696d63..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/delete_timelines/delete_timelines_route_schema.yaml +++ /dev/null @@ -1,55 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Delete Timelines API - version: 8.9.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/timeline-api-delete.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline: - delete: - operationId: deleteTimelines - summary: Deletes one or more timelines or timeline templates. - tags: - - access:securitySolution - requestBody: - description: The ids of the timelines or timeline templates to delete. - required: true - content: - application/json: - schema: - type: object - required: - - savedObjectIds - properties: - savedObjectIds: - type: array - items: - type: string - searchIds: - type: array - description: Saved search ids that should be deleted alongside the timelines - items: - type: string - responses: - '200': - description: Indicates the timeline was successfully deleted. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - deleteTimeline: - type: boolean - required: - - data \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.gen.ts new file mode 100644 index 0000000000000..fbe1957522b75 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Import Timelines API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type ExportTimelinesRequestQuery = z.infer<typeof ExportTimelinesRequestQuery>; +export const ExportTimelinesRequestQuery = z.object({ + /** + * The name of the file to export + */ + file_name: z.string(), +}); +export type ExportTimelinesRequestQueryInput = z.input<typeof ExportTimelinesRequestQuery>; + +export type ExportTimelinesRequestBody = z.infer<typeof ExportTimelinesRequestBody>; +export const ExportTimelinesRequestBody = z.object({ + ids: z.array(z.string()).nullable().optional(), +}); +export type ExportTimelinesRequestBodyInput = z.input<typeof ExportTimelinesRequestBody>; diff --git a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml new file mode 100644 index 0000000000000..2c0a367dc5d28 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route.schema.yaml @@ -0,0 +1,62 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Import Timelines API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline/_export: + post: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: ExportTimelines + summary: Exports timelines as an NDJSON file + tags: + - access:securitySolution + parameters: + - in: query + name: file_name + required: true + schema: + type: string + description: The name of the file to export + requestBody: + description: The ids of the timelines to export + required: true + content: + application/json: + schema: + type: object + properties: + ids: + nullable: true + type: array + items: + type: string + responses: + '200': + description: Indicates the timelines were successfully exported + content: + application/ndjson: + schema: + type: string + description: NDJSON of the exported timelines + '400': + description: Indicates that the export size limit was exceeded + content: + application/ndjson: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route_schema.yaml deleted file mode 100644 index 360c53e6d72e8..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/export_timelines/export_timelines_route_schema.yaml +++ /dev/null @@ -1,59 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Import Timelines API - version: 8.9.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline/_export: - post: - operationId: exportTimelines - summary: Exports timelines as an NDJSON file - tags: - - access:securitySolution - parameters: - - in: query - name: file_name - schema: - type: string - description: The name of the file to export - requestBody: - description: The ids of the timelines to export - required: true - content: - application/json: - schema: - type: object - properties: - ids: - nullable: true - type: array - items: - type: string - responses: - '200': - description: Indicates the timelines were successfully exported - content: - application/ndjson: - schema: - type: string - description: NDJSON of the exported timelines - '400': - description: Indicates that the export size limit was exceeded - content: - application/ndjson: - schema: - type: object - properties: - body: - type: string - statusCode: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.ts new file mode 100644 index 0000000000000..db0c0db421df2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Get Draft Timelines API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { TimelineType, TimelineResponse } from '../model/components.gen'; + +export type GetDraftTimelinesRequestQuery = z.infer<typeof GetDraftTimelinesRequestQuery>; +export const GetDraftTimelinesRequestQuery = z.object({ + timelineType: TimelineType, +}); +export type GetDraftTimelinesRequestQueryInput = z.input<typeof GetDraftTimelinesRequestQuery>; + +export type GetDraftTimelinesResponse = z.infer<typeof GetDraftTimelinesResponse>; +export const GetDraftTimelinesResponse = z.object({ + data: z.object({ + persistTimeline: z.object({ + timeline: TimelineResponse, + }), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml new file mode 100644 index 0000000000000..c7a77af98a7f3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route.schema.yaml @@ -0,0 +1,67 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Get Draft Timelines API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline/_draft: + get: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: GetDraftTimelines + summary: Retrieves the draft timeline for the current user. If the user does not have a draft timeline, an empty timeline is returned. + tags: + - access:securitySolution + parameters: + - in: query + name: timelineType + required: true + schema: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineType' + responses: + '200': + description: Indicates that the draft timeline was successfully retrieved. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [persistTimeline] + properties: + persistTimeline: + type: object + required: [timeline] + properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + '403': + description: If a draft timeline was not found and we attempted to create one, it indicates that the user does not have the required permissions to create a draft timeline. + content: + application:json: + schema: + type: object + properties: + message: + type: string + status_code: + type: number + '409': + description: This should never happen, but if a draft timeline was not found and we attempted to create one, it indicates that there is already a draft timeline with the given timelineId. + content: + application:json: + schema: + type: object + properties: + message: + type: string + status_code: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route_schema.yaml deleted file mode 100644 index c0a73bcaefee2..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_draft_timelines/get_draft_timelines_route_schema.yaml +++ /dev/null @@ -1,61 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Get Draft Timelines API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline/_draft: - get: - operationId: getDraftTimelines - summary: Retrieves the draft timeline for the current user. If the user does not have a draft timeline, an empty timeline is returned. - tags: - - access:securitySolution - parameters: - - in: query - name: timelineType - schema: - $ref: '../model/components.yaml#/components/schemas/TimelineType' - responses: - '200': - description: Indicates that the draft timeline was successfully retrieved. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '../model/components.yaml#/components/schemas/TimelineResponse' - '403': - description: If a draft timeline was not found and we attempted to create one, it indicates that the user does not have the required permissions to create a draft timeline. - content: - application:json: - schema: - type: object - properties: - message: - type: string - status_code: - type: number - '409': - description: This should never happen, but if a draft timeline was not found and we attempted to create one, it indicates that there is already a draft timeline with the given timelineId. - content: - application:json: - schema: - type: object - properties: - message: - type: string - status_code: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.ts new file mode 100644 index 0000000000000..f97db62d24797 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Notes API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type DocumentIds = z.infer<typeof DocumentIds>; +export const DocumentIds = z.union([z.array(z.string()), z.string()]); + +export type GetNotesRequestQuery = z.infer<typeof GetNotesRequestQuery>; +export const GetNotesRequestQuery = z.object({ + documentIds: DocumentIds, + page: z.coerce.number().optional(), + perPage: z.coerce.number().optional(), + search: z.string().nullable().optional(), + sortField: z.string().nullable().optional(), + sortOrder: z.string().nullable().optional(), + filter: z.string().nullable().optional(), +}); +export type GetNotesRequestQueryInput = z.input<typeof GetNotesRequestQuery>; diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml new file mode 100644 index 0000000000000..f2e58e2b4e729 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.schema.yaml @@ -0,0 +1,69 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Notes API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/note: + get: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: GetNotes + description: Gets notes + summary: Get all notes for a given document. + tags: + - access:securitySolution + parameters: + - name: documentIds + in: query + required: true + schema: + $ref: '#/components/schemas/DocumentIds' + - name: page + in: query + schema: + type: number + nullable: true + - name: perPage + in: query + schema: + type: number + nullable: true + - name: search + in: query + schema: + type: string + nullable: true + - name: sortField + in: query + schema: + type: string + nullable: true + - name: sortOrder + in: query + schema: + type: string + nullable: true + - name: filter + in: query + schema: + type: string + nullable: true + responses: + '200': + description: Indicates the requested notes were returned. + +components: + schemas: + DocumentIds: + oneOf: + - type: array + items: + type: string + - type: string diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.ts b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.ts index 453f7ff045df2..219632fc522e9 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route.ts @@ -10,8 +10,8 @@ import { unionWithNullType } from '../../../utility_types'; export const getNotesSchema = runtimeTypes.partial({ documentIds: runtimeTypes.union([runtimeTypes.array(runtimeTypes.string), runtimeTypes.string]), - page: unionWithNullType(runtimeTypes.number), - perPage: unionWithNullType(runtimeTypes.number), + page: unionWithNullType(runtimeTypes.string), + perPage: unionWithNullType(runtimeTypes.string), search: unionWithNullType(runtimeTypes.string), sortField: unionWithNullType(runtimeTypes.string), sortOrder: unionWithNullType(runtimeTypes.string), diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route_schema.yaml deleted file mode 100644 index a78b7e6f9cbee..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_notes/get_notes_route_schema.yaml +++ /dev/null @@ -1,60 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Notes API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/note: - get: - operationId: getNotes - description: Gets notes - tags: - - access:securitySolution - parameters: - - name: documentIds - in: query - schema: - oneOf: - - type: array - items: - type: string - - type: string - - name: page - in: query - schema: - type: number - nullable: true - - name: perPage - in: query - schema: - type: number - nullable: true - - name: search - in: query - schema: - type: string - nullable: true - - name: sortField - in: query - schema: - type: string - nullable: true - - name: sortOrder - in: query - schema: - type: string - nullable: true - - name: filter - in: query - schema: - type: string - nullable: true - responses: - 200: - description: Indicates the requested notes were returned. diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.gen.ts new file mode 100644 index 0000000000000..d178b80e35b7f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Get Timeline API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { TimelineResponse } from '../model/components.gen'; + +export type GetTimelineRequestQuery = z.infer<typeof GetTimelineRequestQuery>; +export const GetTimelineRequestQuery = z.object({ + /** + * The ID of the template timeline to retrieve + */ + template_timeline_id: z.string().optional(), + /** + * The ID of the timeline to retrieve + */ + id: z.string().optional(), +}); +export type GetTimelineRequestQueryInput = z.input<typeof GetTimelineRequestQuery>; + +export type GetTimelineResponse = z.infer<typeof GetTimelineResponse>; +export const GetTimelineResponse = z.object({ + data: z.object({ + getOneTimeline: TimelineResponse.nullable(), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml new file mode 100644 index 0000000000000..9a94bafb63a76 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route.schema.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Get Timeline API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/_get_timeline_or_timeline_template_by_savedobjectid.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline: + get: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: GetTimeline + summary: Get an existing saved timeline or timeline template. This API is used to retrieve an existing saved timeline or timeline template. + tags: + - access:securitySolution + parameters: + - in: query + name: template_timeline_id + schema: + type: string + description: The ID of the template timeline to retrieve + - in: query + name: id + schema: + type: string + description: The ID of the timeline to retrieve + responses: + '200': + description: Indicates that the (template) timeline was found and returned. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [getOneTimeline] + properties: + getOneTimeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + nullable: true diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route_schema.yaml deleted file mode 100644 index 0341a26c356cf..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timeline/get_timeline_route_schema.yaml +++ /dev/null @@ -1,49 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Get Timeline API - version: 8.9.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/_get_timeline_or_timeline_template_by_savedobjectid.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline: - get: - operationId: getTimeline - summary: Get an existing saved timeline or timeline template. This API is used to retrieve an existing saved timeline or timeline template. - tags: - - access:securitySolution - parameters: - - in: query - name: template_timeline_id - schema: - type: string - description: The ID of the template timeline to retrieve - - in: query - name: id - schema: - type: string - description: The ID of the timeline to retrieve - responses: - '200': - description: Indicates that the (template) timeline was found and returned. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - getOneTimeline: - oneOf: - - $ref: '../model/components.yaml#/components/schemas/TimelineResponse' - - nullable: true - required: - - data \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.ts new file mode 100644 index 0000000000000..58b5683a89bf3 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Get Timelines API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { + TimelineType, + SortFieldTimeline, + TimelineStatus, + TimelineResponse, +} from '../model/components.gen'; + +export type GetTimelinesRequestQuery = z.infer<typeof GetTimelinesRequestQuery>; +export const GetTimelinesRequestQuery = z.object({ + /** + * If true, only timelines that are marked as favorites by the user are returned. + */ + only_user_favorite: z.enum(['true', 'false']).nullable().optional(), + timeline_type: TimelineType.nullable().optional(), + sort_field: SortFieldTimeline.optional(), + sort_order: z.enum(['asc', 'desc']).optional(), + page_size: z.string().nullable().optional(), + page_index: z.string().nullable().optional(), + search: z.string().nullable().optional(), + status: TimelineStatus.nullable().optional(), +}); +export type GetTimelinesRequestQueryInput = z.input<typeof GetTimelinesRequestQuery>; + +export type GetTimelinesResponse = z.infer<typeof GetTimelinesResponse>; +export const GetTimelinesResponse = z.object({ + data: z.object({ + timelines: z.array(TimelineResponse), + totalCount: z.number(), + defaultTimelineCount: z.number(), + templateTimelineCount: z.number(), + favoriteCount: z.number(), + elasticTemplateTimelineCount: z.number(), + customTemplateTimelineCount: z.number(), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml new file mode 100644 index 0000000000000..189865a9344d4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route.schema.yaml @@ -0,0 +1,118 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Get Timelines API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/timeline-api-get.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timelines: + get: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: GetTimelines + summary: This API is used to retrieve a list of existing saved timelines or timeline templates. + tags: + - access:securitySolution + parameters: + - in: query + name: only_user_favorite + schema: + nullable: true + type: string + enum: + - 'true' + - 'false' + description: If true, only timelines that are marked as favorites by the user are returned. + - in: query + name: timeline_type + schema: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineType' + nullable: true + - in: query + name: sort_field + schema: + $ref: '../model/components.schema.yaml#/components/schemas/SortFieldTimeline' + - in: query + name: sort_order + schema: + type: string + enum: + - asc + - desc + - in: query + name: page_size + schema: + nullable: true + type: string + - in: query + name: page_index + schema: + nullable: true + type: string + - in: query + name: search + schema: + nullable: true + type: string + - in: query + name: status + schema: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineStatus' + nullable: true + responses: + '200': + description: Indicates that the (template) timelines were found and returned. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: + [ + timelines, + totalCount, + defaultTimelineCount, + templateTimelineCount, + favoriteCount, + elasticTemplateTimelineCount, + customTemplateTimelineCount, + ] + properties: + timelines: + type: array + items: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + totalCount: + type: number + defaultTimelineCount: + type: number + templateTimelineCount: + type: number + favoriteCount: + type: number + elasticTemplateTimelineCount: + type: number + customTemplateTimelineCount: + type: number + '400': + description: Bad request. The user supplied invalid data. + content: + application:json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route_schema.yaml deleted file mode 100644 index beb452557cd8e..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/get_timelines/get_timelines_route_schema.yaml +++ /dev/null @@ -1,109 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Get Timelines API - version: 8.9.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/timeline-api-get.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timelines: - get: - operationId: getTimelines - summary: This API is used to retrieve a list of existing saved timelines or timeline templates. - tags: - - access:securitySolution - parameters: - - in: query - name: only_user_favorite - schema: - nullable: true - type: string - enum: - - 'true' - - 'false' - description: If true, only timelines that are marked as favorites by the user are returned. - - in: query - name: timeline_type - schema: - allOf: - - $ref: '../model/components.yaml#/components/schemas/TimelineType' - - nullable: true - - in: query - name: sort_field - schema: - $ref: '../model/components.yaml#/components/schemas/SortFieldTimeline' - - in: query - name: sort_order - schema: - type: string - enum: - - asc - - desc - - in: query - name: page_size - schema: - nullable: true - type: string - - in: query - name: page_index - schema: - nullable: true - type: string - - in: query - name: search - schema: - nullable: true - type: string - - in: query - name: status - schema: - allOf: - - $ref: '../model/components.yaml#/components/schemas/TimelineStatus' - - nullable: true - responses: - '200': - description: Indicates that the (template) timelines were found and returned. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - timelines: - type: array - items: - $ref: '../model/components.yaml#/components/schemas/TimelineResponse' - totalCount: - type: number - defaultTimelineCount: - type: number - templateTimelineCount: - type: number - favoriteCount: - type: number - elasticTemplateTimelineCount: - type: number - customTemplateTimelineCount: - type: number - required: - - data - '400': - description: Bad request. The user supplied invalid data. - content: - application:json: - schema: - type: object - properties: - body: - type: string - statusCode: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.ts new file mode 100644 index 0000000000000..d1bad2e075c86 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Import Timelines API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { Readable, ImportTimelineResult } from '../model/components.gen'; + +export type ImportTimelinesRequestBody = z.infer<typeof ImportTimelinesRequestBody>; +export const ImportTimelinesRequestBody = z.object({ + file: Readable.merge( + z.object({ + hapi: z.object({ + filename: z.string(), + headers: z.object({}), + isImmutable: z.enum(['true', 'false']).optional(), + }), + }) + ), +}); +export type ImportTimelinesRequestBodyInput = z.input<typeof ImportTimelinesRequestBody>; + +export type ImportTimelinesResponse = z.infer<typeof ImportTimelinesResponse>; +export const ImportTimelinesResponse = z.object({ + data: ImportTimelineResult, +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml new file mode 100644 index 0000000000000..9925ae10322e6 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route.schema.yaml @@ -0,0 +1,100 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Import Timelines API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline/_import: + post: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: ImportTimelines + summary: Imports timelines. + tags: + - access:securitySolution + requestBody: + description: The timelines to import as a readable stream. + required: true + content: + application/json: + schema: + type: object + properties: + file: + allOf: + - $ref: '../model/components.schema.yaml#/components/schemas/Readable' + - type: object + required: [hapi] + properties: + hapi: + type: object + required: [filename, headers] + properties: + filename: + type: string + headers: + type: object + isImmutable: + type: string + enum: + - 'true' + - 'false' + responses: + '200': + description: Indicates the import of timelines was successful. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' + + '400': + description: Indicates the import of timelines was unsuccessful because of an invalid file extension. + content: + application/json: + schema: + type: object + properties: + id: + type: string + body: + type: string + statusCode: + type: number + + '404': + description: Indicates that we were unable to locate the saved object client necessary to handle the import. + content: + application/json: + schema: + type: object + properties: + id: + type: string + statusCode: + type: number + '409': + description: Indicates the import of timelines was unsuccessful. + content: + application/json: + schema: + type: object + properties: + id: + type: string + body: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route_schema.yaml deleted file mode 100644 index 7ada9a0e4a148..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/import_timelines/import_timelines_route_schema.yaml +++ /dev/null @@ -1,92 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Import Timelines API - version: 8.9.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/timeline-api-import.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline/_import: - post: - operationId: importTimelines - summary: Imports timelines. - tags: - - access:securitySolution - requestBody: - description: The timelines to import as a readable stream. - required: true - content: - application/json: - schema: - type: object - properties: - file: - type: object - allOf: - - $ref: '../model/components.yaml#/components/schemas/Readable' - - properties: - hapi: - type: object - properties: - filename: - type: string - headers: - type: object - responses: - '200': - description: Indicates the import of timelines was successful. - content: - application/json: - schema: - type: object - properties: - data: - $ref: '../model/components.yaml#/components/schemas/ImportTimelineResult' - required: - - data - - '400': - description: Indicates the import of timelines was unsuccessful because of an invalid file extension. - content: - application/json: - schema: - type: object - properties: - id: - type: string - body: - type: string - statusCode: - type: number - - '404': - description: Indicates that we were unable to locate the saved object client necessary to handle the import. - content: - application/json: - schema: - type: object - properties: - id: - type: string - statusCode: - type: number - '409': - description: Indicates the import of timelines was unsuccessful. - content: - application/json: - schema: - type: object - properties: - id: - type: string - body: - type: string - statusCode: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/index.ts b/x-pack/plugins/security_solution/common/api/timeline/index.ts index 6229b07c53a9f..c2901b96417db 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/index.ts @@ -21,3 +21,4 @@ export * from './persist_note/persist_note_route'; export * from './pinned_events/pinned_events_route'; export * from './install_prepackaged_timelines/install_prepackaged_timelines'; export * from './copy_timeline/copy_timeline_route'; +export * from './get_notes/get_notes_route'; diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.ts new file mode 100644 index 0000000000000..befdf84f496e4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Install Prepackaged Timelines API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ImportTimelines, SavedTimeline, ImportTimelineResult } from '../model/components.gen'; + +export type InstallPrepackedTimelinesRequestBody = z.infer< + typeof InstallPrepackedTimelinesRequestBody +>; +export const InstallPrepackedTimelinesRequestBody = z.object({ + timelinesToInstall: z.array(ImportTimelines.nullable()), + timelinesToUpdate: z.array(ImportTimelines.nullable()), + prepackagedTimelines: z.array(SavedTimeline), +}); +export type InstallPrepackedTimelinesRequestBodyInput = z.input< + typeof InstallPrepackedTimelinesRequestBody +>; + +export type InstallPrepackedTimelinesResponse = z.infer<typeof InstallPrepackedTimelinesResponse>; +export const InstallPrepackedTimelinesResponse = z.object({ + data: ImportTimelineResult, +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml new file mode 100644 index 0000000000000..af96bbeb9c6d2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.schema.yaml @@ -0,0 +1,65 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Install Prepackaged Timelines API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline/_prepackaged: + post: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: InstallPrepackedTimelines + summary: Installs prepackaged timelines. + tags: + - access:securitySolution + requestBody: + description: The timelines to install or update. + required: true + content: + application/json: + schema: + type: object + required: [timelinesToInstall, timelinesToUpdate, prepackagedTimelines] + properties: + timelinesToInstall: + type: array + items: + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelines' + nullable: true + timelinesToUpdate: + type: array + items: + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelines' + nullable: true + prepackagedTimelines: + type: array + items: + $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' + responses: + '200': + description: Indicates the installation of prepackaged timelines was successful. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + $ref: '../model/components.schema.yaml#/components/schemas/ImportTimelineResult' + '500': + description: Indicates the installation of prepackaged timelines was unsuccessful. + content: + application:json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route_schema.yaml deleted file mode 100644 index 247e6aa8e3f68..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route_schema.yaml +++ /dev/null @@ -1,65 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Install Prepackaged Timelines API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline/_prepackaged: - post: - operationId: installPrepackedTimelines - summary: Installs prepackaged timelines. - tags: - - access:securitySolution - requestBody: - description: The timelines to install or update. - required: true - content: - application/json: - schema: - type: object - properties: - timelinesToInstall: - type: array - items: - allOf: - - $ref: '../model/components.yaml#/components/schemas/ImportTimelines' - - nullable: true - timelinesToUpdate: - type: array - items: - allOf: - - $ref: '../model/components.yaml#/components/schemas/ImportTimelines' - - nullable: true - prepackagedTimelines: - type: array - items: - $ref: '../model/components.yaml#/components/schemas/SavedTimeline' - responses: - '200': - description: Indicates the installation of prepackaged timelines was successful. - content: - application/json: - schema: - type: object - properties: - data: - $ref: '../model/components.yaml#/components/schemas/ImportTimelineResult' - required: - - data - '500': - description: Indicates the installation of prepackaged timelines was unsuccessful. - content: - application:json: - schema: - type: object - properties: - body: - type: string - statusCode: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts index 10b12aee32f2f..1e40484505d1b 100644 --- a/x-pack/plugins/security_solution/common/api/timeline/model/api.ts +++ b/x-pack/plugins/security_solution/common/api/timeline/model/api.ts @@ -14,7 +14,36 @@ import type { Maybe } from '../../../search_strategy'; import { Direction } from '../../../search_strategy'; import type { PinnedEvent } from '../pinned_events/pinned_events_route'; import { PinnedEventRuntimeType } from '../pinned_events/pinned_events_route'; -// TODO https://github.com/elastic/security-team/issues/7491 +import { ErrorSchema } from './error_schema'; +import type { DataProviderType } from './components.gen'; +import { + DataProviderTypeEnum, + RowRendererId, + RowRendererIdEnum, + SortFieldTimeline, + SortFieldTimelineEnum, + TemplateTimelineType, + TemplateTimelineTypeEnum, + TimelineStatus, + TimelineStatusEnum, + TimelineType, + TimelineTypeEnum, +} from './components.gen'; + +export { + DataProviderType, + DataProviderTypeEnum, + RowRendererId, + RowRendererIdEnum, + SortFieldTimeline, + SortFieldTimelineEnum, + TemplateTimelineType, + TemplateTimelineTypeEnum, + TimelineStatus, + TimelineStatusEnum, + TimelineType, + TimelineTypeEnum, +}; /** * Outcome is a property of the saved object resolve api @@ -40,8 +69,6 @@ export const SavedObjectResolveAliasPurpose = runtimeTypes.union([ runtimeTypes.literal('savedObjectImport'), ]); -import { ErrorSchema } from './error_schema'; - export const BareNoteSchema = runtimeTypes.intersection([ runtimeTypes.type({ timelineId: runtimeTypes.string, @@ -133,14 +160,9 @@ const SavedDataProviderQueryMatchRuntimeType = runtimeTypes.partial({ queryMatch: unionWithNullType(SavedDataProviderQueryMatchBasicRuntimeType), }); -export enum DataProviderType { - default = 'default', - template = 'template', -} - export const DataProviderTypeLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(DataProviderType.default), - runtimeTypes.literal(DataProviderType.template), + runtimeTypes.literal(DataProviderTypeEnum.default), + runtimeTypes.literal(DataProviderTypeEnum.template), ]); const SavedDataProviderRuntimeType = runtimeTypes.partial({ @@ -251,93 +273,26 @@ export type Sort = runtimeTypes.TypeOf<typeof SavedSortRuntimeType>; * Timeline Statuses */ -export enum TimelineStatus { - active = 'active', - draft = 'draft', - immutable = 'immutable', -} - export const TimelineStatusLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(TimelineStatus.active), - runtimeTypes.literal(TimelineStatus.draft), - runtimeTypes.literal(TimelineStatus.immutable), + runtimeTypes.literal(TimelineStatusEnum.active), + runtimeTypes.literal(TimelineStatusEnum.draft), + runtimeTypes.literal(TimelineStatusEnum.immutable), ]); -const TimelineStatusLiteralWithNullRt = unionWithNullType(TimelineStatusLiteralRt); - -export type TimelineStatusLiteralWithNull = runtimeTypes.TypeOf< - typeof TimelineStatusLiteralWithNullRt ->; - -export enum RowRendererId { - /** event.kind: signal */ - alert = 'alert', - /** endpoint alerts (created on the endpoint) */ - alerts = 'alerts', - auditd = 'auditd', - auditd_file = 'auditd_file', - library = 'library', - netflow = 'netflow', - plain = 'plain', - registry = 'registry', - suricata = 'suricata', - system = 'system', - system_dns = 'system_dns', - system_endgame_process = 'system_endgame_process', - system_file = 'system_file', - system_fim = 'system_fim', - system_security_event = 'system_security_event', - system_socket = 'system_socket', - threat_match = 'threat_match', - zeek = 'zeek', -} +export const RowRendererCount = Object.keys(RowRendererIdEnum).length; +export const RowRendererValues = Object.values(RowRendererId.Values); -export const RowRendererCount = Object.keys(RowRendererId).length; - -const RowRendererIdRuntimeType = stringEnum(RowRendererId, 'RowRendererId'); +const RowRendererIdRuntimeType = stringEnum(RowRendererIdEnum, 'RowRendererId'); /** - * Timeline template type - */ - -export enum TemplateTimelineType { - elastic = 'elastic', - custom = 'custom', -} - -export const TemplateTimelineTypeLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(TemplateTimelineType.elastic), - runtimeTypes.literal(TemplateTimelineType.custom), -]); - -export const TemplateTimelineTypeLiteralWithNullRt = unionWithNullType( - TemplateTimelineTypeLiteralRt -); - -export type TemplateTimelineTypeLiteral = runtimeTypes.TypeOf<typeof TemplateTimelineTypeLiteralRt>; -export type TemplateTimelineTypeLiteralWithNull = runtimeTypes.TypeOf< - typeof TemplateTimelineTypeLiteralWithNullRt ->; - -/* - * Timeline Types + * Timeline types */ -export enum TimelineType { - default = 'default', - template = 'template', -} - export const TimelineTypeLiteralRt = runtimeTypes.union([ - runtimeTypes.literal(TimelineType.template), - runtimeTypes.literal(TimelineType.default), + runtimeTypes.literal(TimelineTypeEnum.template), + runtimeTypes.literal(TimelineTypeEnum.default), ]); -export const TimelineTypeLiteralWithNullRt = unionWithNullType(TimelineTypeLiteralRt); - -export type TimelineTypeLiteral = runtimeTypes.TypeOf<typeof TimelineTypeLiteralRt>; -export type TimelineTypeLiteralWithNull = runtimeTypes.TypeOf<typeof TimelineTypeLiteralWithNullRt>; - /** * This is the response type */ @@ -483,18 +438,11 @@ export const TimelineErrorResponseType = runtimeTypes.union([ export type TimelineErrorResponse = runtimeTypes.TypeOf<typeof TimelineErrorResponseType>; export type TimelineResponse = runtimeTypes.TypeOf<typeof TimelineResponseType>; -export enum SortFieldTimeline { - title = 'title', - description = 'description', - updated = 'updated', - created = 'created', -} - export const sortFieldTimeline = runtimeTypes.union([ - runtimeTypes.literal(SortFieldTimeline.title), - runtimeTypes.literal(SortFieldTimeline.description), - runtimeTypes.literal(SortFieldTimeline.updated), - runtimeTypes.literal(SortFieldTimeline.created), + runtimeTypes.literal(SortFieldTimelineEnum.title), + runtimeTypes.literal(SortFieldTimelineEnum.description), + runtimeTypes.literal(SortFieldTimelineEnum.updated), + runtimeTypes.literal(SortFieldTimelineEnum.created), ]); export const direction = runtimeTypes.union([ @@ -569,7 +517,6 @@ export const pageInfoTimeline = runtimeTypes.type({ export interface PageInfoTimeline { pageIndex: number; - pageSize: number; } @@ -661,16 +608,16 @@ export interface SerializedFilterQueryResult { filterQuery?: Maybe<SerializedKueryQueryResult>; } -export interface SerializedKueryQueryResult { - kuery?: Maybe<KueryFilterQueryResult>; - serializedQuery?: Maybe<string>; -} - export interface KueryFilterQueryResult { kind?: Maybe<string>; expression?: Maybe<string>; } +export interface SerializedKueryQueryResult { + kuery?: Maybe<KueryFilterQueryResult>; + serializedQuery?: Maybe<string>; +} + export interface TimelineResult { columns?: Maybe<ColumnHeaderResult[]>; created?: Maybe<number>; @@ -717,11 +664,6 @@ export interface SortTimeline { sortOrder: Direction; } -export interface ExportTimelineNotFoundError { - statusCode: number; - message: string; -} - export interface GetAllTimelineVariables { pageInfo: PageInfoTimeline; search?: Maybe<string>; diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts new file mode 100644 index 0000000000000..f0c8985241c05 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.gen.ts @@ -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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Shared Timeline Components + * version: not applicable + */ + +import { z } from 'zod'; + +/** + * The type of timeline to create. Valid values are `default` and `template`. + */ +export type TimelineType = z.infer<typeof TimelineType>; +export const TimelineType = z.enum(['default', 'template']); +export type TimelineTypeEnum = typeof TimelineType.enum; +export const TimelineTypeEnum = TimelineType.enum; + +/** + * The type of data provider to create. Valid values are `default` and `template`. + */ +export type DataProviderType = z.infer<typeof DataProviderType>; +export const DataProviderType = z.enum(['default', 'template']); +export type DataProviderTypeEnum = typeof DataProviderType.enum; +export const DataProviderTypeEnum = DataProviderType.enum; + +/** + * The type of the timeline template. + */ +export type TemplateTimelineType = z.infer<typeof TemplateTimelineType>; +export const TemplateTimelineType = z.enum(['elastic', 'custom']); +export type TemplateTimelineTypeEnum = typeof TemplateTimelineType.enum; +export const TemplateTimelineTypeEnum = TemplateTimelineType.enum; + +export type ColumnHeaderResult = z.infer<typeof ColumnHeaderResult>; +export const ColumnHeaderResult = z.object({ + aggregatable: z.boolean().optional(), + category: z.string().optional(), + columnHeaderType: z.string().optional(), + description: z.string().optional(), + example: z.union([z.string(), z.number()]).optional(), + indexes: z.array(z.string()).optional(), + id: z.string().optional(), + name: z.string().optional(), + placeholder: z.string().optional(), + searchable: z.boolean().optional(), + type: z.string().optional(), +}); + +export type QueryMatchResult = z.infer<typeof QueryMatchResult>; +export const QueryMatchResult = z.object({ + field: z.string().nullable().optional(), + displayField: z.string().nullable().optional(), + value: z.string().nullable().optional(), + displayValue: z.string().nullable().optional(), + operator: z.string().nullable().optional(), +}); + +export type DataProviderQueryMatch = z.infer<typeof DataProviderQueryMatch>; +export const DataProviderQueryMatch = z.object({ + enabled: z.boolean().nullable().optional(), + excluded: z.boolean().nullable().optional(), + id: z.string().nullable().optional(), + kqlQuery: z.string().nullable().optional(), + name: z.string().nullable().optional(), + queryMatch: QueryMatchResult.optional(), +}); + +export type DataProviderResult = z.infer<typeof DataProviderResult>; +export const DataProviderResult = z.object({ + and: z.array(DataProviderQueryMatch).nullable().optional(), + enabled: z.boolean().nullable().optional(), + excluded: z.boolean().nullable().optional(), + id: z.string().nullable().optional(), + kqlQuery: z.string().nullable().optional(), + name: z.string().nullable().optional(), + queryMatch: QueryMatchResult.nullable().optional(), + type: DataProviderType.nullable().optional(), +}); + +export type RowRendererId = z.infer<typeof RowRendererId>; +export const RowRendererId = z.enum([ + 'alert', + 'alerts', + 'auditd', + 'auditd_file', + 'library', + 'netflow', + 'plain', + 'registry', + 'suricata', + 'system', + 'system_dns', + 'system_endgame_process', + 'system_file', + 'system_fim', + 'system_security_event', + 'system_socket', + 'threat_match', + 'zeek', +]); +export type RowRendererIdEnum = typeof RowRendererId.enum; +export const RowRendererIdEnum = RowRendererId.enum; + +export type FavoriteTimelineResult = z.infer<typeof FavoriteTimelineResult>; +export const FavoriteTimelineResult = z.object({ + fullName: z.string().nullable().optional(), + userName: z.string().nullable().optional(), + favoriteDate: z.number().nullable().optional(), +}); + +export type FilterTimelineResult = z.infer<typeof FilterTimelineResult>; +export const FilterTimelineResult = z.object({ + exists: z.boolean().optional(), + meta: z + .object({ + alias: z.string().optional(), + controlledBy: z.string().optional(), + disabled: z.boolean().optional(), + field: z.string().optional(), + formattedValue: z.string().optional(), + index: z.string().optional(), + key: z.string().optional(), + negate: z.boolean().optional(), + params: z.string().optional(), + type: z.string().optional(), + value: z.string().optional(), + }) + .optional(), + match_all: z.string().optional(), + missing: z.string().optional(), + query: z.string().optional(), + range: z.string().optional(), + script: z.string().optional(), +}); + +export type SerializedFilterQueryResult = z.infer<typeof SerializedFilterQueryResult>; +export const SerializedFilterQueryResult = z.object({ + filterQuery: z + .object({ + kuery: z + .object({ + kind: z.string().nullable().optional(), + expression: z.string().nullable().optional(), + }) + .nullable() + .optional(), + serializedQuery: z.string().nullable().optional(), + }) + .nullable() + .optional(), +}); + +export type SortObject = z.infer<typeof SortObject>; +export const SortObject = z.object({ + columnId: z.string().nullable().optional(), + columnType: z.string().nullable().optional(), + sortDirection: z.string().nullable().optional(), +}); + +export type Sort = z.infer<typeof Sort>; +export const Sort = z.union([SortObject, z.array(SortObject)]); + +export type SavedTimeline = z.infer<typeof SavedTimeline>; +export const SavedTimeline = z.object({ + columns: z.array(ColumnHeaderResult).nullable().optional(), + created: z.number().nullable().optional(), + createdBy: z.string().nullable().optional(), + dataProviders: z.array(DataProviderResult).nullable().optional(), + dataViewId: z.string().nullable().optional(), + dateRange: z + .object({ + end: z.union([z.string(), z.number()]).optional(), + start: z.union([z.string(), z.number()]).optional(), + }) + .nullable() + .optional(), + description: z.string().nullable().optional(), + eqlOptions: z + .object({ + eventCategoryField: z.string().nullable().optional(), + query: z.string().nullable().optional(), + size: z.union([z.string().nullable(), z.number().nullable()]).optional(), + tiebreakerField: z.string().nullable().optional(), + timestampField: z.string().nullable().optional(), + }) + .nullable() + .optional(), + eventType: z.string().nullable().optional(), + excludedRowRendererIds: z.array(RowRendererId).nullable().optional(), + favorite: z.array(FavoriteTimelineResult).nullable().optional(), + filters: z.array(FilterTimelineResult).nullable().optional(), + kqlMode: z.string().nullable().optional(), + kqlQuery: SerializedFilterQueryResult.nullable().optional(), + indexNames: z.array(z.string()).nullable().optional(), + savedSearchId: z.string().nullable().optional(), + savedQueryId: z.string().nullable().optional(), + sort: Sort.nullable().optional(), + status: z.enum(['active', 'draft', 'immutable']).nullable().optional(), + title: z.string().nullable().optional(), + templateTimelineId: z.string().nullable().optional(), + templateTimelineVersion: z.number().nullable().optional(), + timelineType: TimelineType.nullable().optional(), + updated: z.number().nullable().optional(), + updatedBy: z.string().nullable().optional(), +}); + +export type BareNote = z.infer<typeof BareNote>; +export const BareNote = z.object({ + eventId: z.string().nullable().optional(), + note: z.string().nullable().optional(), + timelineId: z.string().nullable(), + created: z.number().nullable().optional(), + createdBy: z.string().nullable().optional(), + updated: z.number().nullable().optional(), + updatedBy: z.string().nullable().optional(), +}); + +export type Note = z.infer<typeof Note>; +export const Note = BareNote.merge( + z.object({ + noteId: z.string().optional(), + version: z.string().optional(), + }) +); + +export type PinnedEvent = z.infer<typeof PinnedEvent>; +export const PinnedEvent = z.object({ + pinnedEventId: z.string(), + eventId: z.string(), + timelineId: z.string(), + created: z.number().nullable().optional(), + createdBy: z.string().nullable().optional(), + updated: z.number().nullable().optional(), + updatedBy: z.string().nullable().optional(), + version: z.string(), +}); + +export type TimelineResponse = z.infer<typeof TimelineResponse>; +export const TimelineResponse = SavedTimeline.merge( + z.object({ + eventIdToNoteIds: z.array(Note).optional(), + notes: z.array(Note).optional(), + noteIds: z.array(z.string()).optional(), + pinnedEventIds: z.array(z.string()).optional(), + pinnedEventsSaveObject: z.array(PinnedEvent).optional(), + savedObjectId: z.string(), + version: z.string(), + }) +); + +export type FavoriteTimelineResponse = z.infer<typeof FavoriteTimelineResponse>; +export const FavoriteTimelineResponse = z.object({ + savedObjectId: z.string(), + version: z.string(), + code: z.number().nullable().optional(), + message: z.string().nullable().optional(), + templateTimelineId: z.string().nullable().optional(), + templateTimelineVersion: z.number().nullable().optional(), + timelineType: TimelineType.optional(), + favorite: z.array(FavoriteTimelineResult).optional(), +}); + +export type GlobalNote = z.infer<typeof GlobalNote>; +export const GlobalNote = z.object({ + noteId: z.string().optional(), + version: z.string().optional(), + note: z.string().optional(), + timelineId: z.string().optional(), + created: z.number().optional(), + createdBy: z.string().optional(), + updated: z.number().optional(), + updatedBy: z.string().optional(), +}); + +/** + * The field to sort the timelines by. + */ +export type SortFieldTimeline = z.infer<typeof SortFieldTimeline>; +export const SortFieldTimeline = z.enum(['title', 'description', 'updated', 'created']); +export type SortFieldTimelineEnum = typeof SortFieldTimeline.enum; +export const SortFieldTimelineEnum = SortFieldTimeline.enum; + +/** + * The status of the timeline. Valid values are `active`, `draft`, and `immutable`. + */ +export type TimelineStatus = z.infer<typeof TimelineStatus>; +export const TimelineStatus = z.enum(['active', 'draft', 'immutable']); +export type TimelineStatusEnum = typeof TimelineStatus.enum; +export const TimelineStatusEnum = TimelineStatus.enum; + +export type ImportTimelines = z.infer<typeof ImportTimelines>; +export const ImportTimelines = SavedTimeline.merge( + z.object({ + savedObjectId: z.string().nullable().optional(), + version: z.string().nullable().optional(), + globalNotes: z.array(BareNote).nullable().optional(), + eventNotes: z.array(BareNote).nullable().optional(), + pinnedEventIds: z.array(z.string()).nullable().optional(), + }) +); + +export type ImportTimelineResult = z.infer<typeof ImportTimelineResult>; +export const ImportTimelineResult = z.object({ + success: z.boolean().optional(), + success_count: z.number().optional(), + timelines_installed: z.number().optional(), + timelines_updated: z.number().optional(), + errors: z + .array( + z.object({ + id: z.string().optional(), + error: z + .object({ + message: z.string().optional(), + status_code: z.number().optional(), + }) + .optional(), + }) + ) + .optional(), +}); + +export type ExportedTimelines = z.infer<typeof ExportedTimelines>; +export const ExportedTimelines = SavedTimeline.merge( + z.object({ + globalNotes: z.array(Note).optional(), + eventNotes: z.array(Note).optional(), + pinnedEventIds: z.array(z.string()).optional(), + }) +); + +export type Readable = z.infer<typeof Readable>; +export const Readable = z.object({ + _maxListeners: z.object({}).catchall(z.unknown()).optional(), + _readableState: z.object({}).catchall(z.unknown()).optional(), + _read: z.object({}).catchall(z.unknown()).optional(), + readable: z.boolean().optional(), + _events: z.object({}).catchall(z.unknown()).optional(), + _eventsCount: z.number().optional(), + _data: z.object({}).catchall(z.unknown()).optional(), + _position: z.number().optional(), + _encoding: z.string().optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml new file mode 100644 index 0000000000000..d5d4af4cb1e24 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/model/components.schema.yaml @@ -0,0 +1,606 @@ +openapi: 3.0.0 +info: + title: Shared Timeline Components + version: 'not applicable' +paths: {} +components: + schemas: + TimelineType: + type: string + enum: + - default + - template + # enum default value is temporarily unsupported by the code generator + # default: default + description: The type of timeline to create. Valid values are `default` and `template`. + DataProviderType: + type: string + enum: + - default + - template + # enum default value is temporarily unsupported by the code generator + # default: default + description: The type of data provider to create. Valid values are `default` and `template`. + TemplateTimelineType: + type: string + enum: + - elastic + - custom + description: The type of the timeline template. + SavedTimeline: + type: object + properties: + columns: + type: array + nullable: true + items: + $ref: '#/components/schemas/ColumnHeaderResult' + created: + type: number + nullable: true + createdBy: + type: string + nullable: true + dataProviders: + type: array + nullable: true + items: + $ref: '#/components/schemas/DataProviderResult' + dataViewId: + type: string + nullable: true + dateRange: + type: object + nullable: true + properties: + end: + oneOf: + - type: string + - type: number + start: + oneOf: + - type: string + - type: number + description: + type: string + nullable: true + eqlOptions: + type: object + nullable: true + properties: + eventCategoryField: + type: string + nullable: true + query: + type: string + nullable: true + size: + oneOf: + - type: string + nullable: true + - type: number + nullable: true + tiebreakerField: + type: string + nullable: true + timestampField: + type: string + nullable: true + eventType: + type: string + nullable: true + excludedRowRendererIds: + type: array + nullable: true + items: + $ref: '#/components/schemas/RowRendererId' + favorite: + type: array + nullable: true + items: + $ref: '#/components/schemas/FavoriteTimelineResult' + filters: + type: array + nullable: true + items: + $ref: '#/components/schemas/FilterTimelineResult' + kqlMode: + type: string + nullable: true + kqlQuery: + nullable: true + $ref: '#/components/schemas/SerializedFilterQueryResult' + indexNames: + type: array + nullable: true + items: + type: string + savedSearchId: + type: string + nullable: true + savedQueryId: + type: string + nullable: true + sort: + nullable: true + $ref: '#/components/schemas/Sort' + status: + type: string + nullable: true + enum: + - active + - draft + - immutable + title: + type: string + nullable: true + templateTimelineId: + type: string + nullable: true + templateTimelineVersion: + type: number + nullable: true + timelineType: + nullable: true + $ref: '#/components/schemas/TimelineType' + updated: + type: number + nullable: true + updatedBy: + type: string + nullable: true + TimelineResponse: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + required: + - savedObjectId + - version + properties: + eventIdToNoteIds: + type: array + items: + $ref: '#/components/schemas/Note' + notes: + type: array + items: + $ref: '#/components/schemas/Note' + noteIds: + type: array + items: + type: string + pinnedEventIds: + type: array + items: + type: string + pinnedEventsSaveObject: + type: array + items: + $ref: '#/components/schemas/PinnedEvent' + savedObjectId: + type: string + version: + type: string + FavoriteTimelineResponse: + type: object + required: + - savedObjectId + - version + properties: + savedObjectId: + type: string + version: + type: string + code: + type: number + nullable: true + message: + type: string + nullable: true + templateTimelineId: + type: string + nullable: true + templateTimelineVersion: + type: number + nullable: true + timelineType: + $ref: '#/components/schemas/TimelineType' + favorite: + type: array + items: + $ref: '#/components/schemas/FavoriteTimelineResult' + ColumnHeaderResult: + type: object + properties: + aggregatable: + type: boolean + category: + type: string + columnHeaderType: + type: string + description: + type: string + example: + oneOf: + - type: string + - type: number + indexes: + type: array + items: + type: string + id: + type: string + name: + type: string + placeholder: + type: string + searchable: + type: boolean + type: + type: string + QueryMatchResult: + type: object + properties: + field: + type: string + nullable: true + displayField: + type: string + nullable: true + value: + type: string + nullable: true + displayValue: + type: string + nullable: true + operator: + type: string + nullable: true + DataProviderResult: + type: object + properties: + and: + type: array + nullable: true + items: + $ref: '#/components/schemas/DataProviderQueryMatch' + enabled: + type: boolean + nullable: true + excluded: + type: boolean + nullable: true + id: + type: string + nullable: true + kqlQuery: + type: string + nullable: true + name: + type: string + nullable: true + queryMatch: + $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true + DataProviderQueryMatch: + type: object + properties: + enabled: + type: boolean + nullable: true + excluded: + type: boolean + nullable: true + id: + type: string + nullable: true + kqlQuery: + type: string + nullable: true + name: + type: string + nullable: true + queryMatch: + $ref: '#/components/schemas/QueryMatchResult' + BareNote: + type: object + required: [timelineId] + properties: + eventId: + type: string + nullable: true + note: + type: string + nullable: true + timelineId: + type: string + nullable: true + created: + type: number + nullable: true + createdBy: + type: string + nullable: true + updated: + type: number + nullable: true + updatedBy: + type: string + nullable: true + Note: + allOf: + - $ref: '#/components/schemas/BareNote' + - type: object + properties: + noteId: + type: string + version: + type: string + GlobalNote: + type: object + properties: + noteId: + type: string + version: + type: string + note: + type: string + timelineId: + type: string + created: + type: number + createdBy: + type: string + updated: + type: number + updatedBy: + type: string + RowRendererId: + type: string + enum: + - alert + - alerts + - auditd + - auditd_file + - library + - netflow + - plain + - registry + - suricata + - system + - system_dns + - system_endgame_process + - system_file + - system_fim + - system_security_event + - system_socket + - threat_match + - zeek + FavoriteTimelineResult: + type: object + properties: + fullName: + type: string + nullable: true + userName: + type: string + nullable: true + favoriteDate: + type: number + nullable: true + FilterTimelineResult: + type: object + properties: + exists: + type: boolean + meta: + type: object + properties: + alias: + type: string + controlledBy: + type: string + disabled: + type: boolean + field: + type: string + formattedValue: + type: string + index: + type: string + key: + type: string + negate: + type: boolean + params: + type: string + type: + type: string + value: + type: string + match_all: + type: string + missing: + type: string + query: + type: string + range: + type: string + script: + type: string + SerializedFilterQueryResult: + type: object + properties: + filterQuery: + type: object + nullable: true + properties: + kuery: + type: object + nullable: true + properties: + kind: + type: string + nullable: true + expression: + type: string + nullable: true + serializedQuery: + type: string + nullable: true + PinnedEvent: + type: object + required: [eventId, pinnedEventId, timelineId, version] + properties: + pinnedEventId: + type: string + eventId: + type: string + timelineId: + type: string + created: + type: number + nullable: true + createdBy: + type: string + nullable: true + updated: + type: number + nullable: true + updatedBy: + type: string + nullable: true + version: + type: string + Sort: + oneOf: + - $ref: '#/components/schemas/SortObject' + - type: array + items: + $ref: '#/components/schemas/SortObject' + SortObject: + type: object + properties: + columnId: + type: string + nullable: true + columnType: + type: string + nullable: true + sortDirection: + type: string + nullable: true + SortFieldTimeline: + type: string + description: The field to sort the timelines by. + enum: + - title + - description + - updated + - created + TimelineStatus: + type: string + enum: + - active + - draft + - immutable + # enum default value is temporarily unsupported by the code generator + # default: draft + description: The status of the timeline. Valid values are `active`, `draft`, and `immutable`. + ImportTimelines: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + savedObjectId: + type: string + nullable: true + version: + type: string + nullable: true + globalNotes: + nullable: true + type: array + items: + $ref: '#/components/schemas/BareNote' + eventNotes: + nullable: true + type: array + items: + $ref: '#/components/schemas/BareNote' + pinnedEventIds: + nullable: true + type: array + items: + type: string + ImportTimelineResult: + type: object + properties: + success: + type: boolean + success_count: + type: number + timelines_installed: + type: number + timelines_updated: + type: number + errors: + type: array + items: + type: object + properties: + id: + type: string + error: + type: object + properties: + message: + type: string + status_code: + type: number + ExportedTimelines: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + globalNotes: + type: array + items: + $ref: '#/components/schemas/Note' + eventNotes: + type: array + items: + $ref: '#/components/schemas/Note' + pinnedEventIds: + type: array + items: + type: string + Readable: + type: object + properties: + _maxListeners: + type: object + additionalProperties: true + _readableState: + type: object + additionalProperties: true + _read: + type: object + additionalProperties: true + readable: + type: boolean + _events: + type: object + additionalProperties: true + _eventsCount: + type: number + _data: + type: object + additionalProperties: true + _position: + type: number + _encoding: + type: string diff --git a/x-pack/plugins/security_solution/common/api/timeline/model/components.yaml b/x-pack/plugins/security_solution/common/api/timeline/model/components.yaml deleted file mode 100644 index 9c007aa195838..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/model/components.yaml +++ /dev/null @@ -1,494 +0,0 @@ -components: - schemas: - TimelineType: - type: string - enum: - - default - - template - default: default - description: The type of timeline to create. Valid values are `default` and `template`. - DataProviderType: - type: string - enum: - - default - - template - default: default - description: The type of data provider to create. Valid values are `default` and `template`. - SavedTimeline: - type: object - properties: - columns: - $ref: '#/components/schemas/ColumnHeaderResult' - created: - type: number - createdBy: - type: string - dataProviders: - type: array - items: - $ref: '#/components/schemas/DataProviderResult' - dataViewId: - type: string - dateRange: - type: object - properties: - end: - oneOf: - - type: string - - type: number - start: - oneOf: - - type: string - - type: number - description: - type: string - eqlOptions: - type: object - properties: - eventCategoryField: - type: string - tiebreakerField: - type: string - timestampField: - type: string - eventType: - type: string - excludedRowRendererIds: - type: array - items: - $ref: '#/components/schemas/RowRendererId' - favorite: - type: array - items: - $ref: '#/components/schemas/FavoriteTimelineResult' - filters: - type: array - items: - $ref: '#/components/schemas/FilterTimelineResult' - kqlMode: - type: string - kqlQuery: - $ref: '#/components/schemas/SerializedFilterQueryResult' - indexNames: - type: array - items: - type: string - savedQueryId: - type: string - sort: - $ref: '#/components/schemas/Sort' - status: - type: string - enum: - - active - - draft - - immutable - title: - type: string - templateTimelineId: - type: string - templateTimelineVersion: - type: number - timelineType: - $ref: '#/components/schemas/TimelineType' - updated: - type: number - updatedBy: - type: string - TimelineResponse: - allOf: - - $ref: '#/components/schemas/SavedTimeline' - - type: object - required: - - savedObjectId - - version - properties: - eventIdToNoteIds: - type: array - items: - $ref: '#/components/schemas/Note' - notes: - type: array - items: - $ref: '#/components/schemas/Note' - noteIds: - type: array - items: - type: string - pinnedEventIds: - type: array - items: - type: string - pinnedEventsSaveObject: - type: array - items: - $ref: '#/components/schemas/PinnedEvent' - savedObjectId: - type: string - version: - type: string - FavoriteTimelineResponse: - type: object - required: - - savedObjectId - - version - properties: - savedObjectId: - type: string - version: - type: string - code: - type: number - nullable: true - message: - type: string - nullable: true - templateTimelineId: - type: string - nullable: true - templateTimelineVersion: - type: number - nullable: true - timelineType: - $ref: '#/components/schemas/TimelineType' - favorite: - type: array - items: - $ref: '#/components/schemas/FavoriteTimelineResult' - ColumnHeaderResult: - type: object - properties: - aggregatable: - type: boolean - category: - type: string - columnHeaderType: - type: string - description: - type: string - example: - oneOf: - - type: string - - type: number - indexes: - type: array - items: - type: string - id: - type: string - name: - type: string - placeholder: - type: string - searchable: - type: boolean - type: - type: string - QueryMatchResult: - type: object - properties: - field: - type: string - displayField: - type: string - value: - type: string - displayValue: - type: string - operator: - type: string - DataProviderResult: - type: object - properties: - id: - type: string - name: - type: string - enabled: - type: boolean - excluded: - type: boolean - kqlQuery: - type: string - queryMatch: - $ref: '#/components/schemas/QueryMatchResult' - and: - type: array - items: - $ref: '#/components/schemas/DataProviderResult' - type: - $ref: '#/components/schemas/DataProviderType' - BareNote: - type: object - properties: - eventId: - type: string - note: - type: string - timelineId: - type: string - created: - type: number - createdBy: - type: string - updated: - type: number - updatedBy: - type: string - Note: - allOf: - - $ref: '#/components/schemas/BareNote' - - type: object - properties: - noteId: - type: string - version: - type: string - GlobalNote: - type: object - properties: - noteId: - type: string - version: - type: string - note: - type: string - timelineId: - type: string - created: - type: number - createdBy: - type: string - updated: - type: number - updatedBy: - type: string - Note: - allOf: - - $ref: '#/components/schemas/BareNote' - - type: object - properties: - noteId: - type: string - version: - type: string - RowRendererId: - type: string - enum: - - alert - - alerts - - auditd - - auditd_file - - library - - netflow - - plain - - registry - - suricata - - system - - system_dns - - system_endgame_process - - system_file - - system_fim - - system_security_event - - system_socket - - threat_match - - zeek - FavoriteTimelineResult: - type: object - properties: - fullName: - type: string - userName: - type: string - favoriteDate: - type: number - FilterTimelineResult: - type: object - properties: - exists: - type: boolean - meta: - type: object - properties: - alias: - type: string - controlledBy: - type: string - disabled: - type: boolean - field: - type: string - formattedValue: - type: string - index: - type: string - key: - type: string - negate: - type: boolean - params: - type: string - type: - type: string - value: - type: string - match_all: - type: string - missing: - type: string - query: - type: string - range: - type: string - script: - type: string - SerializedFilterQueryResult: - type: object - properties: - filterQuery: - type: object - properties: - kuery: - type: object - properties: - kind: - type: string - expression: - type: string - serializedQuery: - type: string - PinnedEvent: - type: object - properties: - pinnedEventId: - type: string - eventId: - type: string - timelineId: - type: string - created: - type: number - createdBy: - type: string - updated: - type: number - updatedBy: - type: string - version: - type: string - Sort: - type: object - properties: - columnId: - type: string - sortDirection: - type: string - SortFieldTimeline: - type: object - description: The field to sort the timelines by. - properties: - title: - type: string - description: - type: string - updated: - type: string - created: - type: string - TimelineStatus: - type: string - enum: - - active - - draft - - immutable - default: draft - description: The status of the timeline. Valid values are `active`, `draft`, and `immutable`. - ImportTimelines: - allOf: - - $ref: '#/components/schemas/SavedTimeline' - - type: object - properties: - savedObjectId: - type: string - nullable: true - version: - type: string - nullable: true - globalNotes: - nullable: true - type: array - items: - $ref: '#/components/schemas/BareNote' - eventNotes: - nullable: true - type: array - items: - $ref: '#/components/schemas/BareNote' - pinnedEventIds: - nullable: true - type: array - items: - type: string - ImportTimelineResult: - type: object - properties: - success: - type: boolean - success_count: - type: number - timelines_installed: - type: number - timelines_updated: - type: number - errors: - type: array - items: - type: object - properties: - id: - type: string - error: - type: object - properties: - message: - type: string - status_code: - type: number - ExportedTimelines: - allOf: - - $ref: '#/components/schemas/SavedTimeline' - - type: object - properties: - globalNotes: - type: array - items: - $ref: '#/components/schemas/Note' - eventNotes: - type: array - items: - $ref: '#/components/schemas/Note' - pinnedEventIds: - type: array - items: - type: string - Readable: - type: object - properties: - _maxListeners: - type: {} - _readableState: - type: {} - _read: - type: {} - readable: - type: boolean - _events: - type: {} - _eventsCount: - type: number - _data: - type: {} - _position: - type: number - _encoding: - type: string diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.ts new file mode 100644 index 0000000000000..9dd91246a9144 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Patch Timeline API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { SavedTimeline, TimelineResponse } from '../model/components.gen'; + +export type PatchTimelineRequestBody = z.infer<typeof PatchTimelineRequestBody>; +export const PatchTimelineRequestBody = z.object({ + timelineId: z.string().nullable(), + version: z.string().nullable(), + timeline: SavedTimeline, +}); +export type PatchTimelineRequestBodyInput = z.input<typeof PatchTimelineRequestBody>; + +export type PatchTimelineResponse = z.infer<typeof PatchTimelineResponse>; +export const PatchTimelineResponse = z.object({ + data: z.object({ + persistTimeline: z.object({ + timeline: TimelineResponse, + }), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml new file mode 100644 index 0000000000000..04d6de3cc3f87 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route.schema.yaml @@ -0,0 +1,68 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Patch Timeline API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline: + patch: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: PatchTimeline + summary: Updates an existing timeline. + description: Updates an existing timeline. This API is used to update the title, description, date range, pinned events, pinned queries, and/or pinned saved queries of an existing timeline. + tags: + - access:securitySolution + requestBody: + description: The timeline updates along with the timeline ID and version. + required: true + content: + application/json: + schema: + type: object + required: [timelineId, version, timeline] + properties: + timelineId: + type: string + nullable: true + version: + type: string + nullable: true + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/SavedTimeline' + responses: + '200': + description: Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft timeline is cleared and returned. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [persistTimeline] + properties: + persistTimeline: + type: object + required: [timeline] + properties: + timeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + '405': + description: Indicates that the user does not have the required access to create a draft timeline. + content: + application/json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route_schema.yaml deleted file mode 100644 index 2a4f1e1fadfa7..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/patch_timelines/patch_timeline_route_schema.yaml +++ /dev/null @@ -1,62 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Patch Timeline API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline: - patch: - operationId: patchTimeline - summary: Updates an existing timeline. - description: Updates an existing timeline. This API is used to update the title, description, date range, pinned events, pinned queries, and/or pinned saved queries of an existing timeline. - tags: - - access:securitySolution - requestBody: - description: The timeline updates along with the timeline ID and version. - required: true - content: - application/json: - schema: - type: object - properties: - timelineId: - type: string - version: - type: string - timeline: - $ref: '../model/components.yaml#/components/schemas/SavedTimeline' - responses: - '200': - description: Indicates that the draft timeline was successfully created. In the event the user already has a draft timeline, the existing draft timeline is cleared and returned. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - persistTimeline: - type: object - properties: - timeline: - $ref: '../model/components.yaml#/components/schemas/TimelineResponse' - required: - - data - '405': - description: Indicates that the user does not have the required access to create a draft timeline. - content: - application/json: - schema: - type: object - properties: - body: - type: string - statusCode: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.gen.ts new file mode 100644 index 0000000000000..f28483e8f0e66 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Favorite API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { TimelineType, FavoriteTimelineResponse } from '../model/components.gen'; + +export type PersistFavoriteRouteRequestBody = z.infer<typeof PersistFavoriteRouteRequestBody>; +export const PersistFavoriteRouteRequestBody = z.object({ + timelineId: z.string().nullable(), + templateTimelineId: z.string().nullable(), + templateTimelineVersion: z.number().nullable(), + timelineType: TimelineType.nullable(), +}); +export type PersistFavoriteRouteRequestBodyInput = z.input<typeof PersistFavoriteRouteRequestBody>; + +export type PersistFavoriteRouteResponse = z.infer<typeof PersistFavoriteRouteResponse>; +export const PersistFavoriteRouteResponse = z.object({ + data: z.object({ + persistFavorite: FavoriteTimelineResponse, + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml new file mode 100644 index 0000000000000..87a9e4d21ac68 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route.schema.yaml @@ -0,0 +1,67 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Favorite API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline/_favorite: + patch: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: PersistFavoriteRoute + summary: Persists a given users favorite status of a timeline. + tags: + - access:securitySolution + requestBody: + description: The required fields used to favorite a (template) timeline. + required: true + content: + application/json: + schema: + type: object + required: [timelineId, templateTimelineId, templateTimelineVersion, timelineType] + properties: + timelineId: + type: string + nullable: true + templateTimelineId: + type: string + nullable: true + templateTimelineVersion: + type: number + nullable: true + timelineType: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineType' + nullable: true + responses: + '200': + description: Indicates the favorite status was successfully updated. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [persistFavorite] + properties: + persistFavorite: + $ref: '../model/components.schema.yaml#/components/schemas/FavoriteTimelineResponse' + '403': + description: Indicates the user does not have the required permissions to persist the favorite status. + content: + application:json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route_schema.yaml deleted file mode 100644 index 88eced8f9a843..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_favorite/persist_favorite_route_schema.yaml +++ /dev/null @@ -1,65 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Favorite API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline/_favorite: - patch: - operationId: persistFavoriteRoute - summary: Persists a given users favorite status of a timeline. - tags: - - access:securitySolution - requestBody: - description: The required fields used to favorite a (template) timeline. - required: true - content: - application/json: - schema: - type: object - properties: - timelineId: - type: string - nullable: true - templateTimelineId: - type: string - nullable: true - templateTimelineVersion: - type: number - nullable: true - timelineType: - allOf: - - $ref: '../model/components.yaml#/components/schemas/TimelineType' - - nullable: true - responses: - '200': - description: Indicates the favorite status was successfully updated. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - persistFavorite: - $ref: '../model/components.yaml#/components/schemas/FavoriteTimelineResponse' - required: - - data - '403': - description: Indicates the user does not have the required permissions to persist the favorite status. - content: - application:json: - schema: - type: object - properties: - body: - type: string - statusCode: - type: number \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.gen.ts new file mode 100644 index 0000000000000..5cbb0464218fb --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Notes API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { BareNote, Note } from '../model/components.gen'; + +export type PersistNoteRouteRequestBody = z.infer<typeof PersistNoteRouteRequestBody>; +export const PersistNoteRouteRequestBody = z.object({ + note: BareNote, + overrideOwner: z.boolean().nullable().optional(), + noteId: z.string().nullable().optional(), + version: z.string().nullable().optional(), + eventIngested: z.string().nullable().optional(), + eventTimestamp: z.string().nullable().optional(), + eventDataView: z.string().nullable().optional(), +}); +export type PersistNoteRouteRequestBodyInput = z.input<typeof PersistNoteRouteRequestBody>; + +export type PersistNoteRouteResponse = z.infer<typeof PersistNoteRouteResponse>; +export const PersistNoteRouteResponse = z.object({ + data: z.object({ + persistNote: z.object({ + code: z.number(), + message: z.string(), + note: Note, + }), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml new file mode 100644 index 0000000000000..e5de10d97e013 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route.schema.yaml @@ -0,0 +1,75 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Notes API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/timeline-api-update.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/note: + patch: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: PersistNoteRoute + summary: Persists a note to a timeline. + tags: + - access:securitySolution + requestBody: + description: The note to persist or update along with additional metadata. + required: true + content: + application/json: + schema: + type: object + required: [note] + properties: + note: + $ref: '../model/components.schema.yaml#/components/schemas/BareNote' + overrideOwner: + type: boolean + nullable: true + noteId: + type: string + nullable: true + version: + type: string + nullable: true + eventIngested: + type: string + nullable: true + eventTimestamp: + type: string + nullable: true + eventDataView: + type: string + nullable: true + responses: + '200': + description: Indicates the note was successfully created. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [persistNote] + properties: + persistNote: + type: object + required: [code, message, note] + properties: + code: + type: number + message: + type: string + note: + $ref: '../model/components.schema.yaml#/components/schemas/Note' diff --git a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route_schema.yaml deleted file mode 100644 index f0c9e140f241e..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/persist_note/persist_note_route_schema.yaml +++ /dev/null @@ -1,64 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Notes API - version: 8.14.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/timeline-api-update.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/note: - patch: - operationId: persistNoteRoute - summary: Persists a note to a timeline. - tags: - - access:securitySolution - requestBody: - description: The note to persist or update along with additional metadata. - required: true - content: - application/json: - schema: - type: object - required: - - note - properties: - note: - $ref: '../model/components.yaml#/components/schemas/BareNote' - overrideOwner: - type: boolean - nullable: true - noteId: - type: string - nullable: true - version: - type: string - nullable: true - responses: - '200': - description: Indicates the note was successfully created. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - persistNote: - type: object - properties: - code: - type: number - message: - type: string - note: - $ref: '../model/components.yaml#/components/schemas/Note' - required: - - data diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.gen.ts new file mode 100644 index 0000000000000..20556779db7c4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Pinned Event API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { PinnedEvent } from '../model/components.gen'; + +export type PersistPinnedEventRouteRequestBody = z.infer<typeof PersistPinnedEventRouteRequestBody>; +export const PersistPinnedEventRouteRequestBody = z.object({ + eventId: z.string(), + pinnedEventId: z.string().nullable().optional(), + timelineId: z.string(), +}); +export type PersistPinnedEventRouteRequestBodyInput = z.input< + typeof PersistPinnedEventRouteRequestBody +>; + +export type PersistPinnedEventRouteResponse = z.infer<typeof PersistPinnedEventRouteResponse>; +export const PersistPinnedEventRouteResponse = z.object({ + data: z.object({ + persistPinnedEventOnTimeline: PinnedEvent.merge( + z.object({ + code: z.number().optional(), + message: z.string().optional(), + }) + ), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml new file mode 100644 index 0000000000000..dd16dda4f273c --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route.schema.yaml @@ -0,0 +1,61 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Pinned Event API + version: '2023-10-31' +externalDocs: + url: https://www.elastic.co/guide/en/security/current/_pin_an_event_to_an_existing_timeline.html + description: Documentation +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/pinned_event: + patch: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: PersistPinnedEventRoute + summary: Persists a pinned event to a timeline. + tags: + - access:securitySolution + requestBody: + description: The pinned event to persist or update along with additional metadata. + required: true + content: + application/json: + schema: + type: object + required: [eventId, timelineId] + properties: + eventId: + type: string + pinnedEventId: + type: string + nullable: true + timelineId: + type: string + responses: + '200': + description: Indicate the event was successfully pinned in the timeline. + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [persistPinnedEventOnTimeline] + properties: + persistPinnedEventOnTimeline: + allOf: + - $ref: '../model/components.schema.yaml#/components/schemas/PinnedEvent' + - type: object + properties: + code: + type: number + message: + type: string diff --git a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route_schema.yaml deleted file mode 100644 index 506cd0cc15544..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/pinned_events/pinned_events_route_schema.yaml +++ /dev/null @@ -1,60 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Pinned Event API - version: 8.14.0 -externalDocs: - url: https://www.elastic.co/guide/en/security/current/_pin_an_event_to_an_existing_timeline.html - description: Documentation -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/pinned_event: - patch: - operationId: persistPinnedEventRoute - summary: Persists a pinned event to a timeline. - tags: - - access:securitySolution - requestBody: - description: The pinned event to persist or update along with additional metadata. - required: true - content: - application/json: - schema: - type: object - required: - - eventId - properties: - eventId: - type: string - pinnedEventId: - type: string - nullable: true - timelineId: - type: string - responses: - '200': - description: Indicate the event was successfully pinned in the timeline. - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - persistPinnedEventOnTimeline: - allOf: - - $ref: '../model/components.yaml#/components/schemas/PinnedEvent' - - type: object - properties: - code: - type: number - message: - type: string - required: - - data diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.gen.ts new file mode 100644 index 0000000000000..4db6738dc0b87 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Elastic Security - Timeline - Resolve Timeline API + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { TimelineResponse } from '../model/components.gen'; + +export type ResolveTimelineRequestQuery = z.infer<typeof ResolveTimelineRequestQuery>; +export const ResolveTimelineRequestQuery = z.object({ + /** + * The ID of the template timeline to resolve + */ + template_timeline_id: z.string().optional(), + /** + * The ID of the timeline to resolve + */ + id: z.string().optional(), +}); +export type ResolveTimelineRequestQueryInput = z.input<typeof ResolveTimelineRequestQuery>; + +export type ResolveTimelineResponse = z.infer<typeof ResolveTimelineResponse>; +export const ResolveTimelineResponse = z.object({ + data: z.object({ + getOneTimeline: TimelineResponse.nullable(), + }), +}); diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml new file mode 100644 index 0000000000000..6c2b0d309f01f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route.schema.yaml @@ -0,0 +1,51 @@ +openapi: 3.0.0 +info: + title: Elastic Security - Timeline - Resolve Timeline API + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/timeline/resolve: + get: + x-labels: [serverless, ess] + x-codegen-enabled: true + operationId: ResolveTimeline + summary: Get an existing saved timeline or timeline template. + tags: + - access:securitySolution + parameters: + - in: query + name: template_timeline_id + schema: + type: string + description: The ID of the template timeline to resolve + - in: query + name: id + schema: + type: string + description: The ID of the timeline to resolve + responses: + '200': + description: The (template) timeline has been found + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + type: object + required: [getOneTimeline] + properties: + getOneTimeline: + $ref: '../model/components.schema.yaml#/components/schemas/TimelineResponse' + nullable: true + '400': + description: The request is missing parameters + '404': + description: The (template) timeline was not found diff --git a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route_schema.yaml b/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route_schema.yaml deleted file mode 100644 index 5c5a4b228339c..0000000000000 --- a/x-pack/plugins/security_solution/common/api/timeline/resolve_timeline/resolve_timeline_route_schema.yaml +++ /dev/null @@ -1,50 +0,0 @@ -openapi: 3.0.0 -info: - title: Elastic Security - Timeline - Resolve Timeline API - version: 8.9.0 -servers: - - url: 'http://{kibana_host}:{port}' - variables: - kibana_host: - default: localhost - port: - default: '5601' -paths: - /api/timeline: - get: - operationId: resolveTimeline - summary: Get an existing saved timeline or timeline template. - tags: - - access:securitySolution - parameters: - - in: query - name: template_timeline_id - schema: - type: string - description: The ID of the template timeline to resolve - - in: query - name: id - schema: - type: string - description: The ID of the timeline to resolve - responses: - '200': - description: The (template) timeline has been found - content: - application/json: - schema: - type: object - properties: - data: - type: object - properties: - getOneTimeline: - oneOf: - - $ref: '../model/components.yaml#/components/schemas/TimelineResponse' - - nullable: true - required: - - data - '400': - description: The request is missing parameters - '404': - description: The (template) timeline was not found \ No newline at end of file diff --git a/x-pack/plugins/security_solution/common/detection_engine/constants.ts b/x-pack/plugins/security_solution/common/detection_engine/constants.ts index 8e06f46f1f46d..7057e3c8b3091 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/constants.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/constants.ts @@ -29,6 +29,7 @@ export enum RULE_PREVIEW_FROM { } export const PREBUILT_RULES_PACKAGE_NAME = 'security_detection_engine'; +export const ENDPOINT_PACKAGE_NAME = 'endpoint'; /** * Rule signature id (`rule.rule_id`) of the prebuilt "Endpoint Security" rule. diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts index e89cb996f261f..2cf80274ddb14 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_rule_alert_generator.ts @@ -36,7 +36,7 @@ export class EndpointRuleAlertGenerator extends BaseDataGenerator { generate(overrides: DeepPartial<EndpointRuleAlert> = {}): EndpointRuleAlert { const endpointMetadataGenerator = new EndpointMetadataGenerator(); const endpointMetadata = endpointMetadataGenerator.generate({ - agent: { version: kibanaPackageJson.version }, + agent: { version: overrides?.agent?.version ?? kibanaPackageJson.version }, host: { hostname: overrides?.host?.hostname }, Endpoint: { state: { isolation: overrides?.Endpoint?.state?.isolation } }, }); @@ -50,7 +50,7 @@ export class EndpointRuleAlertGenerator extends BaseDataGenerator { agent: { id: endpointAgentId, type: 'endpoint', - version: kibanaPackageJson.version, + version: endpointMetadata.agent.version, }, elastic: endpointMetadata.elastic, host: endpointMetadata.host, 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 3c5a47852b7c7..dfa095609c528 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 @@ -15,6 +15,9 @@ import type { MappingTypeMapping, Name, } from '@elastic/elasticsearch/lib/api/types'; +import type { KbnClient } from '@kbn/test'; +import { isServerlessKibanaFlavor } from '../utils/kibana_status'; +import { fetchFleetLatestAvailableAgentVersion } from '../utils/fetch_fleet_version'; import { createToolingLogger, wrapErrorIfNeeded } from './utils'; import { DEFAULT_ALERTS_INDEX } from '../../constants'; import { EndpointRuleAlertGenerator } from '../data_generators/endpoint_rule_alert_generator'; @@ -26,6 +29,7 @@ export interface IndexEndpointRuleAlertsOptions { endpointIsolated?: boolean; count?: number; log?: ToolingLog; + kbnClient?: KbnClient; } export interface IndexedEndpointRuleAlerts { @@ -41,6 +45,7 @@ export interface DeletedIndexedEndpointRuleAlerts { * Loads alerts for Endpoint directly into the internal index that the Endpoint Rule would have * written them to for a given endpoint * @param esClient + * @param kbnClient * @param endpointAgentId * @param endpointHostname * @param endpointIsolated @@ -49,6 +54,7 @@ export interface DeletedIndexedEndpointRuleAlerts { */ export const indexEndpointRuleAlerts = async ({ esClient, + kbnClient, endpointAgentId, endpointHostname, endpointIsolated, @@ -59,12 +65,20 @@ export const indexEndpointRuleAlerts = async ({ await ensureEndpointRuleAlertsIndexExists(esClient); + let version = kibanaPackageJson.version; + if (kbnClient) { + const isServerless = await isServerlessKibanaFlavor(kbnClient); + if (isServerless) { + version = await fetchFleetLatestAvailableAgentVersion(kbnClient); + } + } + const alertsGenerator = new EndpointRuleAlertGenerator(); const indexedAlerts: estypes.IndexResponse[] = []; for (let n = 0; n < count; n++) { const alert = alertsGenerator.generate({ - agent: { id: endpointAgentId }, + agent: { id: endpointAgentId, version }, host: { hostname: endpointHostname }, ...(endpointIsolated ? { Endpoint: { state: { isolation: endpointIsolated } } } : {}), }); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_server.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_server.ts index 199dd6251dd54..72abfe79b0ae0 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_server.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_server.ts @@ -23,6 +23,7 @@ import { packagePolicyRouteService, } from '@kbn/fleet-plugin/common'; import type { ToolingLog } from '@kbn/tooling-log'; +import { fetchFleetLatestAvailableAgentVersion } from '../utils/fetch_fleet_version'; import { indexFleetServerAgent } from './index_fleet_agent'; import { catchAxiosErrorFormatAndThrow } from '../format_axios_error'; import { usageTracker } from './usage_tracker'; @@ -47,6 +48,12 @@ export const enableFleetServerIfNecessary = usageTracker.track( log: ToolingLog = createToolingLogger(), version: string = kibanaPackageJson.version ) => { + let agentVersion = version; + + if (isServerless) { + agentVersion = await fetchFleetLatestAvailableAgentVersion(kbnClient); + } + const agentPolicy = await getOrCreateFleetServerAgentPolicy(kbnClient, log); if (!isServerless && !(await hasFleetServerAgent(esClient, agentPolicy.id))) { @@ -56,7 +63,7 @@ export const enableFleetServerIfNecessary = usageTracker.track( const indexedAgent = await indexFleetServerAgent(esClient, log, { policy_id: agentPolicy.id, - agent: { version }, + agent: { version: agentVersion }, last_checkin_status: 'online', last_checkin: lastCheckin.toISOString(), }); diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts index 4ea9f59ea179d..c7ce775bb2129 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.test.ts @@ -19,10 +19,10 @@ import { KillProcessRouteRequestSchema, SuspendProcessRouteRequestSchema, UploadActionRequestSchema, + ExecuteActionRequestSchema, + ScanActionRequestSchema, + NoParametersRequestSchema, } from '../../api/endpoint'; -import { NoParametersRequestSchema } from '../../api/endpoint/actions/common/base'; -import { ExecuteActionRequestSchema } from '../../api/endpoint/actions/execute_route'; -import { ScanActionRequestSchema } from '../../api/endpoint/actions/scan_route'; // NOTE: Even though schemas are kept in common/api/endpoint - we keep tests here, because common/api should import from outside describe('actions schemas', () => { diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 47556a966280a..061182d5075ac 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -9,13 +9,12 @@ import type { TypeOf } from '@kbn/config-schema'; import type { EcsError } from '@elastic/ecs'; import type { BaseFileMetadata, FileCompression, FileJSON } from '@kbn/files-plugin/common'; import type { - KillProcessRouteRequestSchema, - ResponseActionBodySchema, - SuspendProcessRouteRequestSchema, UploadActionApiRequestBody, + ActionStatusRequestSchema, + KillProcessRequestBody, + SuspendProcessRequestBody, } from '../../api/endpoint'; -import type { ActionStatusRequestSchema } from '../../api/endpoint/actions/action_status_route'; -import type { NoParametersRequestSchema } from '../../api/endpoint/actions/common/base'; + import type { ResponseActionAgentType, ResponseActionsApiCommandNames, @@ -358,14 +357,6 @@ export interface ActivityLog { data: ActivityLogEntry[]; } -export type HostIsolationRequestBody = TypeOf<typeof NoParametersRequestSchema.body>; - -export type ResponseActionRequestBody = TypeOf<typeof ResponseActionBodySchema>; - -export type KillProcessRequestBody = TypeOf<typeof KillProcessRouteRequestSchema.body>; - -export type SuspendProcessRequestBody = TypeOf<typeof SuspendProcessRouteRequestSchema.body>; - /** Note: this type should almost never be used. Use instead the response action specific types above */ export type KillOrSuspendProcessRequestBody = KillProcessRequestBody & SuspendProcessRequestBody; @@ -373,8 +364,6 @@ export interface HostIsolationResponse { action: string; } -export type ProcessesRequestBody = TypeOf<typeof NoParametersRequestSchema.body>; - export interface ResponseActionApiResponse< TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput > { diff --git a/x-pack/plugins/security_solution/common/endpoint/utils/fetch_fleet_version.ts b/x-pack/plugins/security_solution/common/endpoint/utils/fetch_fleet_version.ts new file mode 100644 index 0000000000000..3b0fe8ad48282 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/utils/fetch_fleet_version.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. + */ + +/** + * Fetches the latest version of the Elastic Agent available for download + * @param kbnClient + */ +import type { KbnClient } from '@kbn/test'; +import { AGENT_API_ROUTES } from '@kbn/fleet-plugin/common'; +import type { GetAvailableVersionsResponse } from '@kbn/fleet-plugin/common/types'; +import { catchAxiosErrorFormatAndThrow } from '../format_axios_error'; + +export const fetchFleetLatestAvailableAgentVersion = async ( + kbnClient: KbnClient +): Promise<string> => { + return kbnClient + .request<GetAvailableVersionsResponse>({ + method: 'GET', + path: AGENT_API_ROUTES.AVAILABLE_VERSIONS_PATTERN, + headers: { + 'elastic-api-version': '2023-10-31', + }, + }) + .then((response) => response.data.items[0]) + .catch(catchAxiosErrorFormatAndThrow); +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/utils/kibana_status.ts b/x-pack/plugins/security_solution/common/endpoint/utils/kibana_status.ts new file mode 100644 index 0000000000000..4e34acd44d74d --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/utils/kibana_status.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 { KbnClient } from '@kbn/test'; +import type { Client } from '@elastic/elasticsearch'; +import type { StatusResponse } from '@kbn/core-status-common-internal'; +import { catchAxiosErrorFormatAndThrow } from '../format_axios_error'; + +export const fetchKibanaStatus = async (kbnClient: KbnClient): Promise<StatusResponse> => { + return (await kbnClient.status.get().catch(catchAxiosErrorFormatAndThrow)) as StatusResponse; +}; +/** + * Checks to see if Kibana/ES is running in serverless mode + * @param client + */ +export const isServerlessKibanaFlavor = async (client: KbnClient | Client): Promise<boolean> => { + if (client instanceof KbnClient) { + const kbnStatus = await fetchKibanaStatus(client); + + // 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'; + } else { + return (await client.info()).version.build_flavor === 'serverless'; + } +}; diff --git a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts index 2abc0120df67c..d089a52f4cc30 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts @@ -5,13 +5,11 @@ * 2.0. */ -export const ASSET_CRITICALITY_INTERNAL_URL = `/internal/asset_criticality` as const; +const ASSET_CRITICALITY_INTERNAL_URL = `/internal/asset_criticality` as const; export const ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL = `${ASSET_CRITICALITY_INTERNAL_URL}/privileges` as const; export const ASSET_CRITICALITY_INTERNAL_STATUS_URL = `${ASSET_CRITICALITY_INTERNAL_URL}/status` as const; -export const ASSET_CRITICALITY_INTERNAL_CSV_UPLOAD_URL = - `${ASSET_CRITICALITY_INTERNAL_URL}/upload_csv` as const; export const ASSET_CRITICALITY_PUBLIC_URL = `/api/asset_criticality` as const; export const ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL = diff --git a/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts b/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts index e0111b3d67871..caf7b640582a6 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EntityAnalyticsPrivileges } from '../../api/entity_analytics/asset_criticality/get_asset_criticality_privileges.gen'; +import type { EntityAnalyticsPrivileges } from '../../api/entity_analytics'; import { getMissingRiskEnginePrivileges } from './privileges'; describe('getMissingRiskEnginePrivileges', () => { diff --git a/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts b/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts index b0bbc39609b3b..b03b9e2921325 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts @@ -6,7 +6,7 @@ */ import type { NonEmptyArray } from 'fp-ts/NonEmptyArray'; -import type { EntityAnalyticsPrivileges } from '../../api/entity_analytics/asset_criticality/get_asset_criticality_privileges.gen'; +import type { EntityAnalyticsPrivileges } from '../../api/entity_analytics'; import type { RiskEngineIndexPrivilege } from './constants'; import { RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 7b8b1a3e47d39..2e8035cef2e0c 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -42,16 +42,6 @@ export const allowedExperimentalValues = Object.freeze({ */ socTrendsEnabled: false, - /** - * Enables the automated response actions in rule + alerts - */ - responseActionsEnabled: true, - - /** - * Enables the automated endpoint response action in rule + alerts - */ - endpointResponseActionsEnabled: true, - /** * Enables the `upload` endpoint response action (v8.9) */ @@ -96,17 +86,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables scan response action on Endpoint */ - responseActionScanEnabled: false, - - /** - * Enables top charts on Alerts Page - */ - alertsPageChartsEnabled: true, - - /** - * Enables the alert type column in KPI visualizations on Alerts Page - */ - alertTypeEnabled: false, + responseActionScanEnabled: true, /** * Enables new notes @@ -131,7 +111,7 @@ export const allowedExperimentalValues = Object.freeze({ /** * Enables the Assistant BedrockChat Langchain model, introduced in `8.15.0`. */ - assistantBedrockChat: false, + assistantBedrockChat: true, /** * Enables the Managed User section inside the new user details flyout. @@ -242,7 +222,12 @@ export const allowedExperimentalValues = Object.freeze({ /** * Adds a new option to filter descendants of a process for Management / Event Filters */ - filterProcessDescendantsForEventFiltersEnabled: false, + filterProcessDescendantsForEventFiltersEnabled: true, + + /** + * Enables the new data ingestion hub + */ + dataIngestionHubEnabled: false, }); type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>; diff --git a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts index 01a1073c0fa71..d64fed95bb18c 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts @@ -11,7 +11,6 @@ export type { BeatFields, IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, - BrowserField, BrowserFields, } from '@kbn/timelines-plugin/common'; -export { EMPTY_BROWSER_FIELDS, EMPTY_INDEX_FIELDS } from '@kbn/timelines-plugin/common'; +export { EMPTY_BROWSER_FIELDS } from '@kbn/timelines-plugin/common'; 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 9efb295c13496..290cd6e57eb92 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 @@ -15,14 +15,6 @@ export { CtiQueries } from '../../../api/search_strategy'; export type CtiEnrichment = Record<string, unknown[]>; export type EventFields = Record<string, unknown>; -export interface CtiEnrichmentIdentifiers { - id: string | undefined; - field: string | undefined; - value: string | undefined; - type: string | undefined; - feedName: string | undefined; -} - export interface CtiEventEnrichmentStrategyResponse extends IEsSearchResponse { enrichments: CtiEnrichment[]; inspect: Inspect; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index 49aea54bd74e6..ff9613503e8e3 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -11,7 +11,7 @@ import type { TimelineType, TimelineStatus, RowRendererId, -} from '../../api/timeline/model/api'; +} from '../../api/timeline'; export * from './events'; diff --git a/x-pack/plugins/security_solution/common/types/timeline/store.ts b/x-pack/plugins/security_solution/common/types/timeline/store.ts index 5beb765f3d623..c65705e0c9a74 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/store.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/store.ts @@ -6,7 +6,7 @@ */ import type { Filter } from '@kbn/es-query'; -import type { RowRendererId, TimelineTypeLiteral } from '../../api/timeline/model/api'; +import type { RowRendererId, TimelineType } from '../../api/timeline/model/api'; import type { Direction } from '../../search_strategy'; import type { ColumnHeaderOptions, ColumnId } from '../header_actions'; @@ -52,7 +52,7 @@ export interface TimelinePersistInput { show?: boolean; sort?: SortColumnTimeline[]; showCheckboxes?: boolean; - timelineType?: TimelineTypeLiteral; + timelineType?: TimelineType; templateTimelineId?: string | null; templateTimelineVersion?: number | null; title?: string; diff --git a/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts b/x-pack/plugins/security_solution/common/utils/data_retrieval.test.ts deleted file mode 100644 index d7ab3986a14f9..0000000000000 --- a/x-pack/plugins/security_solution/common/utils/data_retrieval.test.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getFirstElement } from './data_retrieval'; - -describe('getFirstElement', () => { - it('returns undefined if array is undefined', () => { - expect(getFirstElement(undefined)).toEqual(undefined); - }); - - it('returns undefined if array is empty', () => { - expect(getFirstElement([])).toEqual(undefined); - }); - - it('returns the first element if present', () => { - expect(getFirstElement(['hi mom'])).toEqual('hi mom'); - }); - - it('returns the first element of multiple', () => { - expect(getFirstElement(['hi mom', 'hello world'])).toEqual('hi mom'); - }); -}); diff --git a/x-pack/plugins/security_solution/common/utils/data_retrieval.ts b/x-pack/plugins/security_solution/common/utils/data_retrieval.ts deleted file mode 100644 index 04b6839b854b4..0000000000000 --- a/x-pack/plugins/security_solution/common/utils/data_retrieval.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. - */ - -/** - * Retrieves the first element of the given array. - * - * @param array the array to retrieve a value from - * @returns the first element of the array, or undefined if the array is undefined - */ -export const getFirstElement: <T = unknown>(array: T[] | undefined) => T | undefined = (array) => - array ? array[0] : undefined; diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml index 406cb3ac5c913..c9bb81ede9be3 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -55,9 +55,10 @@ paths: description: Internal server error response summary: Delete an alerts index tags: + - Security Solution Detections API - Alert index API get: - operationId: GetAlertsIndex + operationId: ReadAlertsIndex responses: '200': content: @@ -98,8 +99,9 @@ paths: schema: $ref: '#/components/schemas/SiemErrorResponse' description: Internal server error response - summary: Gets the alert index name if it exists + summary: Reads the alert index name if it exists tags: + - Security Solution Detections API - Alert index API post: operationId: CreateAlertsIndex @@ -141,6 +143,7 @@ paths: description: Internal server error response summary: Create an alerts index tags: + - Security Solution Detections API - Alert index API /api/detection_engine/privileges: get: @@ -153,7 +156,7 @@ paths: index for the Elastic Security alerts generated by detection engine rules. - operationId: GetPrivileges + operationId: ReadPrivileges responses: '200': content: @@ -183,6 +186,7 @@ paths: description: Internal server error response summary: Returns user privileges for the Kibana space tags: + - Security Solution Detections API - Privileges API /api/detection_engine/rules: delete: @@ -210,6 +214,7 @@ paths: description: Indicates a successful call. summary: Delete a detection rule tags: + - Security Solution Detections API - Rules API get: description: Retrieve a detection rule using the `rule_id` or `id` field. @@ -236,6 +241,7 @@ paths: description: Indicates a successful call. summary: Retrieve a detection rule tags: + - Security Solution Detections API - Rules API patch: description: >- @@ -257,6 +263,7 @@ paths: description: Indicates a successful call. summary: Patch a detection rule tags: + - Security Solution Detections API - Rules API post: description: Create a new detection rule. @@ -276,6 +283,7 @@ paths: description: Indicates a successful call. summary: Create a detection rule tags: + - Security Solution Detections API - Rules API put: description: > @@ -301,6 +309,7 @@ paths: description: Indicates a successful call. summary: Update a detection rule tags: + - Security Solution Detections API - Rules API /api/detection_engine/rules/_bulk_action: post: @@ -339,6 +348,7 @@ paths: description: OK summary: Apply a bulk action to detection rules tags: + - Security Solution Detections API - Bulk API /api/detection_engine/rules/_bulk_create: post: @@ -363,6 +373,7 @@ paths: description: Indicates a successful call. summary: Create multiple detection rules tags: + - Security Solution Detections API - Bulk API /api/detection_engine/rules/_bulk_delete: delete: @@ -414,6 +425,7 @@ paths: description: Internal server error response summary: Delete multiple detection rules tags: + - Security Solution Detections API - Bulk API post: deprecated: true @@ -463,6 +475,7 @@ paths: $ref: '#/components/schemas/SiemErrorResponse' description: Internal server error response tags: + - Security Solution Detections API - Bulk API /api/detection_engine/rules/_bulk_update: patch: @@ -489,6 +502,7 @@ paths: description: Indicates a successful call. summary: Patch multiple detection rules tags: + - Security Solution Detections API - Bulk API put: deprecated: true @@ -520,6 +534,7 @@ paths: description: Indicates a successful call. summary: Update multiple detection rules tags: + - Security Solution Detections API - Bulk API /api/detection_engine/rules/_export: post: @@ -583,6 +598,7 @@ paths: description: Indicates a successful call. summary: Export detection rules tags: + - Security Solution Detections API - Import/Export API /api/detection_engine/rules/_find: get: @@ -657,6 +673,7 @@ paths: description: Successful response summary: List all detection rules tags: + - Security Solution Detections API - Rules API /api/detection_engine/rules/_import: post: @@ -771,6 +788,7 @@ paths: description: Indicates a successful call. summary: Import detection rules tags: + - Security Solution Detections API - Import/Export API /api/detection_engine/rules/prepackaged: put: @@ -808,13 +826,14 @@ paths: description: Indicates a successful call summary: Install prebuilt detection rules and Timelines tags: + - Security Solution Detections API - Prebuilt Rules API /api/detection_engine/rules/prepackaged/_status: get: description: >- Retrieve the status of all Elastic prebuilt detection rules and Timelines. - operationId: GetPrebuiltRulesAndTimelinesStatus + operationId: ReadPrebuiltRulesAndTimelinesStatus responses: '200': content: @@ -866,6 +885,7 @@ paths: description: Indicates a successful call summary: Retrieve the status of prebuilt detection rules and Timelines tags: + - Security Solution Detections API - Prebuilt Rules API /api/detection_engine/rules/preview: post: @@ -945,6 +965,7 @@ paths: description: Internal server error response summary: Preview rule alerts generated on specified time range tags: + - Security Solution Detections API - Rule preview API /api/detection_engine/signals/assignees: post: @@ -975,6 +996,8 @@ paths: '400': description: Invalid request. summary: Assign and unassign users from detection alerts + tags: + - Security Solution Detections API /api/detection_engine/signals/finalize_migration: post: description: > @@ -1032,6 +1055,7 @@ paths: description: Internal server error response summary: Finalize detection alert migrations tags: + - Security Solution Detections API - Alerts migration API /api/detection_engine/signals/migration: delete: @@ -1099,6 +1123,7 @@ paths: description: Internal server error response summary: Clean up detection alert migrations tags: + - Security Solution Detections API - Alerts migration API post: description: > @@ -1165,13 +1190,14 @@ paths: description: Internal server error response summary: Initiate a detection alert migration tags: + - Security Solution Detections API - Alerts migration API /api/detection_engine/signals/migration_status: post: description: >- Retrieve indices that contain detection alerts of a particular age, along with migration information for each of those indices. - operationId: GetAlertsMigrationStatus + operationId: ReadAlertsMigrationStatus parameters: - description: Maximum age of qualifying detection alerts in: query @@ -1222,6 +1248,7 @@ paths: description: Internal server error response summary: Retrieve the status of detection alert migrations tags: + - Security Solution Detections API - Alerts migration API /api/detection_engine/signals/search: post: @@ -1294,6 +1321,7 @@ paths: description: Internal server error response summary: Find and/or aggregate detection alerts tags: + - Security Solution Detections API - Alerts API /api/detection_engine/signals/status: post: @@ -1341,6 +1369,7 @@ paths: description: Internal server error response summary: Set a detection alert status tags: + - Security Solution Detections API - Alerts API /api/detection_engine/signals/tags: post: @@ -1348,7 +1377,7 @@ paths: And tags to detection alerts, and remove them from alerts. > info > You cannot add and remove the same alert tag in the same request. - operationId: ManageAlertTags + operationId: SetAlertTags requestBody: content: application/json: @@ -1358,7 +1387,7 @@ paths: ids: $ref: '#/components/schemas/AlertIds' tags: - $ref: '#/components/schemas/ManageAlertTags' + $ref: '#/components/schemas/SetAlertTags' required: - ids - tags @@ -1397,6 +1426,7 @@ paths: description: Internal server error response summary: Add and remove detection alert tags tags: + - Security Solution Detections API - Alerts API /api/detection_engine/tags: get: @@ -1411,6 +1441,7 @@ paths: description: Indicates a successful call summary: List all detection rule tags tags: + - Security Solution Detections API - Tags API components: schemas: @@ -3508,16 +3539,6 @@ components: - risk_score - severity - $ref: '#/components/schemas/MachineLearningRuleCreateFields' - ManageAlertTags: - type: object - properties: - tags_to_add: - $ref: '#/components/schemas/AlertTags' - tags_to_remove: - $ref: '#/components/schemas/AlertTags' - required: - - tags_to_add - - tags_to_remove MaxSignals: minimum: 1 type: integer @@ -5729,6 +5750,16 @@ components: required: - query - status + SetAlertTags: + type: object + properties: + tags_to_add: + $ref: '#/components/schemas/AlertTags' + tags_to_remove: + $ref: '#/components/schemas/AlertTags' + required: + - tags_to_add + - tags_to_remove SetupGuide: type: string Severity: @@ -6926,3 +6957,9 @@ components: type: http security: - BasicAuth: [] +tags: + - description: >- + You can create rules that automatically turn events and external alerts + sent to Elastic Security into detection alerts. These alerts are displayed + on the Detections page. + name: Security Solution Detections API diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml index c23158972fd77..f291b2db216c0 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml @@ -13,13 +13,14 @@ servers: paths: /api/endpoint/action: get: + description: Get a list of action requests and their responses operationId: EndpointGetActionsList parameters: - in: query name: query required: true schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' responses: '200': content: @@ -28,20 +29,24 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Actions List schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action_log/{agent_id}': get: - operationId: EndpointGetActionAuditLog + deprecated: true + description: Get action requests log + operationId: EndpointGetActionLog parameters: - - in: query - name: query + - in: path + name: agent_id required: true schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - in: path + $ref: '#/components/schemas/AgentId' + - in: query name: query required: true schema: - $ref: '#/components/schemas/AuditLogRequestParams' + $ref: '#/components/schemas/ActionLogRequestQuery' responses: '200': content: @@ -49,9 +54,12 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Get action audit log schema + summary: Get action requests log schema + tags: + - Security Solution Endpoint Management API /api/endpoint/action_status: get: + description: Get action status operationId: EndpointGetActionsStatus parameters: - in: query @@ -70,15 +78,18 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Actions status schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}': get: + description: Get action details operationId: EndpointGetActionsDetails parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': content: @@ -87,15 +98,23 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Action details schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}/download`': get: + description: Download a file from an endpoint operationId: EndpointFileDownload parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileDownloadRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -104,15 +123,23 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: File Download schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}`': get: + description: Get file info operationId: EndpointFileInfo parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileInfoRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -121,14 +148,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: File Info schema + tags: + - Security Solution Endpoint Management API /api/endpoint/action/execute: post: + description: Execute a given command on an endpoint operationId: EndpointExecuteAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' required: true responses: '200': @@ -138,14 +168,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Execute Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/get_file: post: + description: Get a file from an endpoint operationId: EndpointGetFileAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' required: true responses: '200': @@ -155,25 +188,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get File Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/isolate: post: - operationId: EndpointIsolateHostAction + description: Isolate an endpoint + operationId: EndpointIsolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/IsolateRouteRequestBody' required: true responses: '200': @@ -182,15 +207,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Isolate host Action + summary: Isolate Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/kill_process: post: + description: Kill a running process on an endpoint operationId: EndpointKillProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -200,25 +228,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Kill process Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/running_procs: post: - operationId: EndpointGetRunningProcessesAction + description: Get list of running processes on an endpoint + operationId: EndpointGetProcessesAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/GetProcessesRouteRequestBody' required: true responses: '200': @@ -228,14 +248,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Running Processes Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/scan: post: + description: Scan a file or directory operationId: EndpointScanAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' required: true responses: '200': @@ -245,6 +268,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Scan Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/state: get: operationId: EndpointGetActionsState @@ -256,14 +281,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Action State schema + tags: + - Security Solution Endpoint Management API /api/endpoint/action/suspend_process: post: + description: Suspend a running process on an endpoint operationId: EndpointSuspendProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -273,25 +301,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Suspend process Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/unisolate: post: - operationId: EndpointUnisolateHostAction + description: Release an endpoint + operationId: EndpointUnisolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/UnisolateRouteRequestBody' required: true responses: '200': @@ -300,15 +320,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Unisolate host Action + summary: Unisolate Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/upload: post: + description: Upload a file to an endpoint operationId: EndpointUploadAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' required: true responses: '200': @@ -318,8 +341,11 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Upload Action + tags: + - Security Solution Endpoint Management API /api/endpoint/isolate: post: + deprecated: true operationId: EndpointIsolateRedirect requestBody: content: @@ -327,6 +353,8 @@ paths: schema: type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -337,6 +365,8 @@ paths: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids required: true responses: '200': @@ -354,6 +384,8 @@ paths: example: /api/endpoint/action/isolate type: string summary: Permanently redirects to a new location + tags: + - Security Solution Endpoint Management API /api/endpoint/metadata: get: operationId: GetEndpointMetadataList @@ -371,18 +403,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Metadata List schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/metadata/{id}': get: operationId: GetEndpointMetadata parameters: - in: path - name: query + name: id required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': content: @@ -391,6 +422,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Metadata schema + tags: + - Security Solution Endpoint Management API /api/endpoint/metadata/transforms: get: operationId: GetEndpointMetadataTransform @@ -402,6 +435,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Metadata Transform schema + tags: + - Security Solution Endpoint Management API /api/endpoint/policy_response: get: operationId: GetPolicyResponse @@ -422,8 +457,11 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Policy Response schema + tags: + - Security Solution Endpoint Management API /api/endpoint/policy/summaries: get: + deprecated: true operationId: GetAgentPolicySummary parameters: - in: query @@ -445,6 +483,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Agent Policy Summary schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/protection_updates_note/{package_policy_id}': get: operationId: GetProtectionUpdatesNote @@ -462,6 +502,8 @@ paths: $ref: '#/components/schemas/ProtectionUpdatesNoteResponse' description: OK summary: Get Protection Updates Note schema + tags: + - Security Solution Endpoint Management API post: operationId: CreateUpdateProtectionUpdatesNote parameters: @@ -487,6 +529,8 @@ paths: $ref: '#/components/schemas/ProtectionUpdatesNoteResponse' description: OK summary: Create Update Protection Updates Note schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/suggestions/{suggestion_type}': post: operationId: GetEndpointSuggestions @@ -521,8 +565,11 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get suggestions + tags: + - Security Solution Endpoint Management API /api/endpoint/unisolate: post: + deprecated: true operationId: EndpointUnisolateRedirect requestBody: content: @@ -530,6 +577,8 @@ paths: schema: type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -540,6 +589,8 @@ paths: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids required: true responses: '200': @@ -557,8 +608,21 @@ paths: example: /api/endpoint/action/unisolate type: string summary: Permanently redirects to a new location + tags: + - Security Solution Endpoint Management API components: schemas: + ActionLogRequestQuery: + type: object + properties: + end_date: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + page_size: + $ref: '#/components/schemas/PageSize' + start_date: + $ref: '#/components/schemas/StartDate' AgentId: description: Agent ID type: string @@ -573,28 +637,18 @@ components: type: array - minLength: 1 type: string + AgentTypes: + enum: + - endpoint + - sentinel_one + - crowdstrike + type: string AlertIds: description: A list of alerts ids. items: $ref: '#/components/schemas/NonEmptyString' minItems: 1 type: array - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '#/components/schemas/AgentId' - AuditLogRequestQuery: - type: object - properties: - end_date: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - page_size: - $ref: '#/components/schemas/PageSize' - start_date: - $ref: '#/components/schemas/StartDate' CaseIds: description: Case IDs to be updated (cannot contain empty strings) items: @@ -613,6 +667,7 @@ components: - get-file - execute - upload + - scan minLength: 1 type: string Commands: @@ -622,39 +677,9 @@ components: Comment: description: Optional comment type: string - DetailsRequestParams: - type: object - properties: - action_id: - type: string EndDate: description: End date type: string - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '#/components/schemas/AgentIds' - commands: - $ref: '#/components/schemas/Commands' - endDate: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - pageSize: - default: 10 - description: Number of items per page - maximum: 10000 - minimum: 1 - type: integer - startDate: - $ref: '#/components/schemas/StartDate' - types: - $ref: '#/components/schemas/Types' - userIds: - $ref: '#/components/schemas/UserIds' - withOutputs: - $ref: '#/components/schemas/WithOutputs' EndpointIds: description: List of endpoint IDs (cannot contain empty strings) items: @@ -662,10 +687,12 @@ components: type: string minItems: 1 type: array - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -676,6 +703,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -689,30 +718,39 @@ components: - command required: - parameters - FileDownloadRequestParams: + GetEndpointActionListRouteQuery: type: object properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileInfoRequestParams: - type: object - properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileUploadActionRequestBody: + agentIds: + $ref: '#/components/schemas/AgentIds' + agentTypes: + $ref: '#/components/schemas/AgentTypes' + commands: + $ref: '#/components/schemas/Commands' + endDate: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + pageSize: + default: 10 + description: Number of items per page + maximum: 10000 + minimum: 1 + type: integer + startDate: + $ref: '#/components/schemas/StartDate' + types: + $ref: '#/components/schemas/Types' + userIds: + $ref: '#/components/schemas/UserIds' + withOutputs: + $ref: '#/components/schemas/WithOutputs' + GetFileRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -723,24 +761,29 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: - file: - format: binary - type: string parameters: type: object properties: - overwrite: - default: false - type: boolean + path: + type: string + required: + - path required: - parameters - - file - GetFileActionRequestBody: + GetProcessesRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + IsolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + KillOrSuspendActionSchema: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -751,15 +794,22 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: - type: object - properties: - path: - type: string - required: - - path + oneOf: + - type: object + properties: + pid: + minimum: 1 + type: integer + - type: object + properties: + entity_id: + minLength: 1 + type: string required: - parameters ListRequestQuery: @@ -814,6 +864,28 @@ components: minLength: 1 pattern: ^(?! *$).+$ type: string + NoParametersRequestSchema: + type: object + properties: + body: + type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + required: + - body Page: default: 1 description: Page number @@ -828,45 +900,17 @@ components: Parameters: description: Optional parameters object type: object - ProcessActionSchemas: - allOf: - - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' - - type: object - properties: - parameters: - oneOf: - - type: object - properties: - pid: - minimum: 1 - type: integer - - type: object - properties: - entity_id: - minLength: 1 - type: string - required: - - parameters ProtectionUpdatesNoteResponse: type: object properties: note: type: string - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -877,6 +921,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -899,16 +945,52 @@ components: minimum: 1 type: integer Type: + description: Type of response action enum: - automated - manual type: string Types: + description: List of types of response actions items: $ref: '#/components/schemas/Type' maxLength: 2 minLength: 1 type: array + UnisolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + UploadRouteRequestBody: + allOf: + - type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + - type: object + properties: + file: + format: binary + type: string + parameters: + type: object + properties: + overwrite: + default: false + type: boolean + required: + - parameters + - file UserIds: description: User IDs oneOf: @@ -920,7 +1002,7 @@ components: - minLength: 1 type: string WithOutputs: - description: With Outputs + description: Shows detailed outputs for an action response oneOf: - items: minLength: 1 @@ -935,3 +1017,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: Interact with and manage endpoints running the Elastic Defend integration. + name: Security Solution Endpoint Management API diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 2b8a646d58f4c..35346afa0f120 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -38,10 +38,26 @@ paths: type: string responses: '200': + content: + application/json: + schema: + type: object + properties: + deleted: + description: >- + If the record was deleted. If false the record did not + exist. + type: boolean + record: + $ref: '#/components/schemas/AssetCriticalityRecord' + required: + - deleted description: Successful response '400': description: Invalid request summary: Delete Criticality Record + tags: + - Security Solution Entity Analytics API get: operationId: GetAssetCriticalityRecord parameters: @@ -70,6 +86,8 @@ paths: '404': description: Criticality record not found summary: Get Criticality Record + tags: + - Security Solution Entity Analytics API post: operationId: CreateAssetCriticalityRecord requestBody: @@ -98,6 +116,8 @@ paths: '400': description: Invalid request summary: Create Criticality Record + tags: + - Security Solution Entity Analytics API /api/asset_criticality/bulk: post: operationId: BulkUpsertAssetCriticalityRecords @@ -153,6 +173,8 @@ paths: summary: >- Bulk upsert asset criticality data, creating or updating records as needed + tags: + - Security Solution Entity Analytics API /api/asset_criticality/list: post: operationId: FindAssetCriticalityRecords @@ -226,6 +248,8 @@ paths: - total description: Bulk upload successful summary: 'List asset criticality data, filtering and sorting as needed' + tags: + - Security Solution Entity Analytics API components: schemas: AssetCriticalityBulkUploadErrorItem: @@ -304,3 +328,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: '' + name: Security Solution Entity Analytics API diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml new file mode 100644 index 0000000000000..e658a2df0284c --- /dev/null +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -0,0 +1,1554 @@ +openapi: 3.0.3 +info: + description: >- + You can create Timelines and Timeline templates via the API, as well as + import new Timelines from an ndjson file. + title: Security Solution Timeline API (Elastic Cloud and self-hosted) + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/note: + delete: + operationId: DeleteNote + requestBody: + content: + application/json: + schema: + oneOf: + - nullable: true + type: object + properties: + noteId: + type: string + required: + - noteId + - type: object + properties: + noteIds: + items: + type: string + nullable: true + type: array + required: + - noteIds + description: The id of the note to delete. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + description: Indicates the note was successfully deleted. + summary: Deletes a note from a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + get: + description: Gets notes + operationId: GetNotes + parameters: + - in: query + name: documentIds + required: true + schema: + $ref: '#/components/schemas/DocumentIds' + - in: query + name: page + schema: + nullable: true + type: number + - in: query + name: perPage + schema: + nullable: true + type: number + - in: query + name: search + schema: + nullable: true + type: string + - in: query + name: sortField + schema: + nullable: true + type: string + - in: query + name: sortOrder + schema: + nullable: true + type: string + - in: query + name: filter + schema: + nullable: true + type: string + responses: + '200': + description: Indicates the requested notes were returned. + summary: Get all notes for a given document. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + patch: + operationId: PersistNoteRoute + requestBody: + content: + application/json: + schema: + type: object + properties: + eventDataView: + nullable: true + type: string + eventIngested: + nullable: true + type: string + eventTimestamp: + nullable: true + type: string + note: + $ref: '#/components/schemas/BareNote' + noteId: + nullable: true + type: string + overrideOwner: + nullable: true + type: boolean + version: + nullable: true + type: string + required: + - note + description: The note to persist or update along with additional metadata. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistNote: + type: object + properties: + code: + type: number + message: + type: string + note: + $ref: '#/components/schemas/Note' + required: + - code + - message + - note + required: + - persistNote + required: + - data + description: Indicates the note was successfully created. + summary: Persists a note to a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/pinned_event: + patch: + operationId: PersistPinnedEventRoute + requestBody: + content: + application/json: + schema: + type: object + properties: + eventId: + type: string + pinnedEventId: + nullable: true + type: string + timelineId: + type: string + required: + - eventId + - timelineId + description: The pinned event to persist or update along with additional metadata. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistPinnedEventOnTimeline: + allOf: + - $ref: '#/components/schemas/PinnedEvent' + - type: object + properties: + code: + type: number + message: + type: string + required: + - persistPinnedEventOnTimeline + required: + - data + description: Indicate the event was successfully pinned in the timeline. + summary: Persists a pinned event to a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline: + delete: + operationId: DeleteTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + savedObjectIds: + items: + type: string + type: array + searchIds: + description: >- + Saved search ids that should be deleted alongside the + timelines + items: + type: string + type: array + required: + - savedObjectIds + description: The ids of the timelines or timeline templates to delete. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + deleteTimeline: + type: boolean + required: + - deleteTimeline + required: + - data + description: Indicates the timeline was successfully deleted. + summary: Deletes one or more timelines or timeline templates. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + get: + operationId: GetTimeline + parameters: + - description: The ID of the template timeline to retrieve + in: query + name: template_timeline_id + schema: + type: string + - description: The ID of the timeline to retrieve + in: query + name: id + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + nullable: true + required: + - getOneTimeline + required: + - data + description: Indicates that the (template) timeline was found and returned. + summary: >- + Get an existing saved timeline or timeline template. This API is used to + retrieve an existing saved timeline or timeline template. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + patch: + description: >- + Updates an existing timeline. This API is used to update the title, + description, date range, pinned events, pinned queries, and/or pinned + saved queries of an existing timeline. + operationId: PatchTimeline + requestBody: + content: + application/json: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineId: + nullable: true + type: string + version: + nullable: true + type: string + required: + - timelineId + - version + - timeline + description: The timeline updates along with the timeline ID and version. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data + description: >- + Indicates that the draft timeline was successfully created. In the + event the user already has a draft timeline, the existing draft + timeline is cleared and returned. + '405': + content: + application/json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: >- + Indicates that the user does not have the required access to create + a draft timeline. + summary: Updates an existing timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + post: + operationId: CreateTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/TimelineStatus' + nullable: true + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineId: + nullable: true + type: string + timelineType: + $ref: '#/components/schemas/TimelineType' + nullable: true + version: + nullable: true + type: string + required: + - timeline + description: >- + The required timeline fields used to create a new timeline along with + optional fields that will be created if not provided. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - persistTimeline + required: + - data + description: Indicates the timeline was successfully created. + '405': + content: + application/json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: Indicates that there was an error in the timeline creation. + summary: Creates a new timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_draft: + get: + operationId: GetDraftTimelines + parameters: + - in: query + name: timelineType + required: true + schema: + $ref: '#/components/schemas/TimelineType' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data + description: Indicates that the draft timeline was successfully retrieved. + '403': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + If a draft timeline was not found and we attempted to create one, it + indicates that the user does not have the required permissions to + create a draft timeline. + '409': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + This should never happen, but if a draft timeline was not found and + we attempted to create one, it indicates that there is already a + draft timeline with the given timelineId. + summary: >- + Retrieves the draft timeline for the current user. If the user does not + have a draft timeline, an empty timeline is returned. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + post: + description: > + Retrieves a clean draft timeline. If a draft timeline does not exist, it + is created and returned. + operationId: CleanDraftTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + timelineType: + $ref: '#/components/schemas/TimelineType' + required: + - timelineType + description: >- + The type of timeline to create. Valid values are `default` and + `template`. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data + description: >- + Indicates that the draft timeline was successfully created. In the + event the user already has a draft timeline, the existing draft + timeline is cleared and returned. + '403': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + Indicates that the user does not have the required permissions to + create a draft timeline. + '409': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + Indicates that there is already a draft timeline with the given + timelineId. + summary: Retrieves a draft timeline or timeline template. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_export: + post: + operationId: ExportTimelines + parameters: + - description: The name of the file to export + in: query + name: file_name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + ids: + items: + type: string + nullable: true + type: array + description: The ids of the timelines to export + required: true + responses: + '200': + content: + application/ndjson: + schema: + description: NDJSON of the exported timelines + type: string + description: Indicates the timelines were successfully exported + '400': + content: + application/ndjson: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: Indicates that the export size limit was exceeded + summary: Exports timelines as an NDJSON file + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_favorite: + patch: + operationId: PersistFavoriteRoute + requestBody: + content: + application/json: + schema: + type: object + properties: + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timelineId: + nullable: true + type: string + timelineType: + $ref: '#/components/schemas/TimelineType' + nullable: true + required: + - timelineId + - templateTimelineId + - templateTimelineVersion + - timelineType + description: The required fields used to favorite a (template) timeline. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistFavorite: + $ref: '#/components/schemas/FavoriteTimelineResponse' + required: + - persistFavorite + required: + - data + description: Indicates the favorite status was successfully updated. + '403': + content: + 'application:json': + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: >- + Indicates the user does not have the required permissions to persist + the favorite status. + summary: Persists a given users favorite status of a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_import: + post: + operationId: ImportTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + file: + allOf: + - $ref: '#/components/schemas/Readable' + - type: object + properties: + hapi: + type: object + properties: + filename: + type: string + headers: + type: object + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - filename + - headers + required: + - hapi + description: The timelines to import as a readable stream. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/ImportTimelineResult' + required: + - data + description: Indicates the import of timelines was successful. + '400': + content: + application/json: + schema: + type: object + properties: + body: + type: string + id: + type: string + statusCode: + type: number + description: >- + Indicates the import of timelines was unsuccessful because of an + invalid file extension. + '404': + content: + application/json: + schema: + type: object + properties: + id: + type: string + statusCode: + type: number + description: >- + Indicates that we were unable to locate the saved object client + necessary to handle the import. + '409': + content: + application/json: + schema: + type: object + properties: + body: + type: string + id: + type: string + statusCode: + type: number + description: Indicates the import of timelines was unsuccessful. + summary: Imports timelines. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_prepackaged: + post: + operationId: InstallPrepackedTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + prepackagedTimelines: + items: + $ref: '#/components/schemas/SavedTimeline' + type: array + timelinesToInstall: + items: + $ref: '#/components/schemas/ImportTimelines' + nullable: true + type: array + timelinesToUpdate: + items: + $ref: '#/components/schemas/ImportTimelines' + nullable: true + type: array + required: + - timelinesToInstall + - timelinesToUpdate + - prepackagedTimelines + description: The timelines to install or update. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/ImportTimelineResult' + required: + - data + description: Indicates the installation of prepackaged timelines was successful. + '500': + content: + 'application:json': + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: >- + Indicates the installation of prepackaged timelines was + unsuccessful. + summary: Installs prepackaged timelines. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/resolve: + get: + operationId: ResolveTimeline + parameters: + - description: The ID of the template timeline to resolve + in: query + name: template_timeline_id + schema: + type: string + - description: The ID of the timeline to resolve + in: query + name: id + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + nullable: true + required: + - getOneTimeline + required: + - data + description: The (template) timeline has been found + '400': + description: The request is missing parameters + '404': + description: The (template) timeline was not found + summary: Get an existing saved timeline or timeline template. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timelines: + get: + operationId: GetTimelines + parameters: + - description: >- + If true, only timelines that are marked as favorites by the user are + returned. + in: query + name: only_user_favorite + schema: + enum: + - 'true' + - 'false' + nullable: true + type: string + - in: query + name: timeline_type + schema: + $ref: '#/components/schemas/TimelineType' + nullable: true + - in: query + name: sort_field + schema: + $ref: '#/components/schemas/SortFieldTimeline' + - in: query + name: sort_order + schema: + enum: + - asc + - desc + type: string + - in: query + name: page_size + schema: + nullable: true + type: string + - in: query + name: page_index + schema: + nullable: true + type: string + - in: query + name: search + schema: + nullable: true + type: string + - in: query + name: status + schema: + $ref: '#/components/schemas/TimelineStatus' + nullable: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timelines: + items: + $ref: '#/components/schemas/TimelineResponse' + type: array + totalCount: + type: number + required: + - timelines + - totalCount + - defaultTimelineCount + - templateTimelineCount + - favoriteCount + - elasticTemplateTimelineCount + - customTemplateTimelineCount + required: + - data + description: Indicates that the (template) timelines were found and returned. + '400': + content: + 'application:json': + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: Bad request. The user supplied invalid data. + summary: >- + This API is used to retrieve a list of existing saved timelines or + timeline templates. + tags: + - Security Solution Timeline API + - 'access:securitySolution' +components: + schemas: + BareNote: + type: object + properties: + created: + nullable: true + type: number + createdBy: + nullable: true + type: string + eventId: + nullable: true + type: string + note: + nullable: true + type: string + timelineId: + nullable: true + type: string + updated: + nullable: true + type: number + updatedBy: + nullable: true + type: string + required: + - timelineId + ColumnHeaderResult: + type: object + properties: + aggregatable: + type: boolean + category: + type: string + columnHeaderType: + type: string + description: + type: string + example: + oneOf: + - type: string + - type: number + id: + type: string + indexes: + items: + type: string + type: array + name: + type: string + placeholder: + type: string + searchable: + type: boolean + type: + type: string + DataProviderQueryMatch: + type: object + properties: + enabled: + nullable: true + type: boolean + excluded: + nullable: true + type: boolean + id: + nullable: true + type: string + kqlQuery: + nullable: true + type: string + name: + nullable: true + type: string + queryMatch: + $ref: '#/components/schemas/QueryMatchResult' + DataProviderResult: + type: object + properties: + and: + items: + $ref: '#/components/schemas/DataProviderQueryMatch' + nullable: true + type: array + enabled: + nullable: true + type: boolean + excluded: + nullable: true + type: boolean + id: + nullable: true + type: string + kqlQuery: + nullable: true + type: string + name: + nullable: true + type: string + queryMatch: + $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true + DataProviderType: + description: >- + The type of data provider to create. Valid values are `default` and + `template`. + enum: + - default + - template + type: string + DocumentIds: + oneOf: + - items: + type: string + type: array + - type: string + FavoriteTimelineResponse: + type: object + properties: + code: + nullable: true + type: number + favorite: + items: + $ref: '#/components/schemas/FavoriteTimelineResult' + type: array + message: + nullable: true + type: string + savedObjectId: + type: string + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timelineType: + $ref: '#/components/schemas/TimelineType' + version: + type: string + required: + - savedObjectId + - version + FavoriteTimelineResult: + type: object + properties: + favoriteDate: + nullable: true + type: number + fullName: + nullable: true + type: string + userName: + nullable: true + type: string + FilterTimelineResult: + type: object + properties: + exists: + type: boolean + match_all: + type: string + meta: + type: object + properties: + alias: + type: string + controlledBy: + type: string + disabled: + type: boolean + field: + type: string + formattedValue: + type: string + index: + type: string + key: + type: string + negate: + type: boolean + params: + type: string + type: + type: string + value: + type: string + missing: + type: string + query: + type: string + range: + type: string + script: + type: string + ImportTimelineResult: + type: object + properties: + errors: + items: + type: object + properties: + error: + type: object + properties: + message: + type: string + status_code: + type: number + id: + type: string + type: array + success: + type: boolean + success_count: + type: number + timelines_installed: + type: number + timelines_updated: + type: number + ImportTimelines: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventNotes: + items: + $ref: '#/components/schemas/BareNote' + nullable: true + type: array + globalNotes: + items: + $ref: '#/components/schemas/BareNote' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + savedObjectId: + nullable: true + type: string + version: + nullable: true + type: string + Note: + allOf: + - $ref: '#/components/schemas/BareNote' + - type: object + properties: + noteId: + type: string + version: + type: string + PinnedEvent: + type: object + properties: + created: + nullable: true + type: number + createdBy: + nullable: true + type: string + eventId: + type: string + pinnedEventId: + type: string + timelineId: + type: string + updated: + nullable: true + type: number + updatedBy: + nullable: true + type: string + version: + type: string + required: + - eventId + - pinnedEventId + - timelineId + - version + QueryMatchResult: + type: object + properties: + displayField: + nullable: true + type: string + displayValue: + nullable: true + type: string + field: + nullable: true + type: string + operator: + nullable: true + type: string + value: + nullable: true + type: string + Readable: + type: object + properties: + _data: + additionalProperties: true + type: object + _encoding: + type: string + _events: + additionalProperties: true + type: object + _eventsCount: + type: number + _maxListeners: + additionalProperties: true + type: object + _position: + type: number + _read: + additionalProperties: true + type: object + _readableState: + additionalProperties: true + type: object + readable: + type: boolean + RowRendererId: + enum: + - alert + - alerts + - auditd + - auditd_file + - library + - netflow + - plain + - registry + - suricata + - system + - system_dns + - system_endgame_process + - system_file + - system_fim + - system_security_event + - system_socket + - threat_match + - zeek + type: string + SavedTimeline: + type: object + properties: + columns: + items: + $ref: '#/components/schemas/ColumnHeaderResult' + nullable: true + type: array + created: + nullable: true + type: number + createdBy: + nullable: true + type: string + dataProviders: + items: + $ref: '#/components/schemas/DataProviderResult' + nullable: true + type: array + dataViewId: + nullable: true + type: string + dateRange: + nullable: true + type: object + properties: + end: + oneOf: + - type: string + - type: number + start: + oneOf: + - type: string + - type: number + description: + nullable: true + type: string + eqlOptions: + nullable: true + type: object + properties: + eventCategoryField: + nullable: true + type: string + query: + nullable: true + type: string + size: + oneOf: + - nullable: true + type: string + - nullable: true + type: number + tiebreakerField: + nullable: true + type: string + timestampField: + nullable: true + type: string + eventType: + nullable: true + type: string + excludedRowRendererIds: + items: + $ref: '#/components/schemas/RowRendererId' + nullable: true + type: array + favorite: + items: + $ref: '#/components/schemas/FavoriteTimelineResult' + nullable: true + type: array + filters: + items: + $ref: '#/components/schemas/FilterTimelineResult' + nullable: true + type: array + indexNames: + items: + type: string + nullable: true + type: array + kqlMode: + nullable: true + type: string + kqlQuery: + $ref: '#/components/schemas/SerializedFilterQueryResult' + nullable: true + savedQueryId: + nullable: true + type: string + savedSearchId: + nullable: true + type: string + sort: + $ref: '#/components/schemas/Sort' + nullable: true + status: + enum: + - active + - draft + - immutable + nullable: true + type: string + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timelineType: + $ref: '#/components/schemas/TimelineType' + nullable: true + title: + nullable: true + type: string + updated: + nullable: true + type: number + updatedBy: + nullable: true + type: string + SerializedFilterQueryResult: + type: object + properties: + filterQuery: + nullable: true + type: object + properties: + kuery: + nullable: true + type: object + properties: + expression: + nullable: true + type: string + kind: + nullable: true + type: string + serializedQuery: + nullable: true + type: string + Sort: + oneOf: + - $ref: '#/components/schemas/SortObject' + - items: + $ref: '#/components/schemas/SortObject' + type: array + SortFieldTimeline: + description: The field to sort the timelines by. + enum: + - title + - description + - updated + - created + type: string + SortObject: + type: object + properties: + columnId: + nullable: true + type: string + columnType: + nullable: true + type: string + sortDirection: + nullable: true + type: string + TimelineResponse: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Note' + type: array + noteIds: + items: + type: string + type: array + notes: + items: + $ref: '#/components/schemas/Note' + type: array + pinnedEventIds: + items: + type: string + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/PinnedEvent' + type: array + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version + TimelineStatus: + description: >- + The status of the timeline. Valid values are `active`, `draft`, and + `immutable`. + enum: + - active + - draft + - immutable + type: string + TimelineType: + description: >- + The type of timeline to create. Valid values are `default` and + `template`. + enum: + - default + - template + type: string + securitySchemes: + BasicAuth: + scheme: basic + type: http +security: + - BasicAuth: [] +tags: + - description: >- + You can create Timelines and Timeline templates via the API, as well as + import new Timelines from an ndjson file. + name: Security Solution Timeline API diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml index 275457b1273c4..6a8f72d45d46c 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -25,7 +25,7 @@ paths: index for the Elastic Security alerts generated by detection engine rules. - operationId: GetPrivileges + operationId: ReadPrivileges responses: '200': content: @@ -55,6 +55,7 @@ paths: description: Internal server error response summary: Returns user privileges for the Kibana space tags: + - Security Solution Detections API - Privileges API /api/detection_engine/rules: delete: @@ -82,6 +83,7 @@ paths: description: Indicates a successful call. summary: Delete a detection rule tags: + - Security Solution Detections API - Rules API get: description: Retrieve a detection rule using the `rule_id` or `id` field. @@ -108,6 +110,7 @@ paths: description: Indicates a successful call. summary: Retrieve a detection rule tags: + - Security Solution Detections API - Rules API patch: description: >- @@ -129,6 +132,7 @@ paths: description: Indicates a successful call. summary: Patch a detection rule tags: + - Security Solution Detections API - Rules API post: description: Create a new detection rule. @@ -148,6 +152,7 @@ paths: description: Indicates a successful call. summary: Create a detection rule tags: + - Security Solution Detections API - Rules API put: description: > @@ -173,6 +178,7 @@ paths: description: Indicates a successful call. summary: Update a detection rule tags: + - Security Solution Detections API - Rules API /api/detection_engine/rules/_bulk_action: post: @@ -211,6 +217,7 @@ paths: description: OK summary: Apply a bulk action to detection rules tags: + - Security Solution Detections API - Bulk API /api/detection_engine/rules/_export: post: @@ -274,6 +281,7 @@ paths: description: Indicates a successful call. summary: Export detection rules tags: + - Security Solution Detections API - Import/Export API /api/detection_engine/rules/_find: get: @@ -348,6 +356,7 @@ paths: description: Successful response summary: List all detection rules tags: + - Security Solution Detections API - Rules API /api/detection_engine/rules/_import: post: @@ -462,6 +471,7 @@ paths: description: Indicates a successful call. summary: Import detection rules tags: + - Security Solution Detections API - Import/Export API /api/detection_engine/rules/preview: post: @@ -541,6 +551,7 @@ paths: description: Internal server error response summary: Preview rule alerts generated on specified time range tags: + - Security Solution Detections API - Rule preview API /api/detection_engine/signals/assignees: post: @@ -571,6 +582,8 @@ paths: '400': description: Invalid request. summary: Assign and unassign users from detection alerts + tags: + - Security Solution Detections API /api/detection_engine/signals/search: post: description: Find and/or aggregate detection alerts that match the given query. @@ -642,6 +655,7 @@ paths: description: Internal server error response summary: Find and/or aggregate detection alerts tags: + - Security Solution Detections API - Alerts API /api/detection_engine/signals/status: post: @@ -689,6 +703,7 @@ paths: description: Internal server error response summary: Set a detection alert status tags: + - Security Solution Detections API - Alerts API /api/detection_engine/signals/tags: post: @@ -696,7 +711,7 @@ paths: And tags to detection alerts, and remove them from alerts. > info > You cannot add and remove the same alert tag in the same request. - operationId: ManageAlertTags + operationId: SetAlertTags requestBody: content: application/json: @@ -706,7 +721,7 @@ paths: ids: $ref: '#/components/schemas/AlertIds' tags: - $ref: '#/components/schemas/ManageAlertTags' + $ref: '#/components/schemas/SetAlertTags' required: - ids - tags @@ -745,6 +760,7 @@ paths: description: Internal server error response summary: Add and remove detection alert tags tags: + - Security Solution Detections API - Alerts API /api/detection_engine/tags: get: @@ -759,6 +775,7 @@ paths: description: Indicates a successful call summary: List all detection rule tags tags: + - Security Solution Detections API - Tags API components: schemas: @@ -2774,16 +2791,6 @@ components: - risk_score - severity - $ref: '#/components/schemas/MachineLearningRuleCreateFields' - ManageAlertTags: - type: object - properties: - tags_to_add: - $ref: '#/components/schemas/AlertTags' - tags_to_remove: - $ref: '#/components/schemas/AlertTags' - required: - - tags_to_add - - tags_to_remove MaxSignals: minimum: 1 type: integer @@ -4897,6 +4904,16 @@ components: required: - query - status + SetAlertTags: + type: object + properties: + tags_to_add: + $ref: '#/components/schemas/AlertTags' + tags_to_remove: + $ref: '#/components/schemas/AlertTags' + required: + - tags_to_add + - tags_to_remove SetupGuide: type: string Severity: @@ -6087,3 +6104,9 @@ components: type: http security: - BasicAuth: [] +tags: + - description: >- + You can create rules that automatically turn events and external alerts + sent to Elastic Security into detection alerts. These alerts are displayed + on the Detections page. + name: Security Solution Detections API diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml index 5b6a8ffc8a189..82af84f6d4253 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_endpoint_management_api_2023_10_31.bundled.schema.yaml @@ -13,13 +13,14 @@ servers: paths: /api/endpoint/action: get: + description: Get a list of action requests and their responses operationId: EndpointGetActionsList parameters: - in: query name: query required: true schema: - $ref: '#/components/schemas/EndpointActionListRequestQuery' + $ref: '#/components/schemas/GetEndpointActionListRouteQuery' responses: '200': content: @@ -28,20 +29,24 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Actions List schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action_log/{agent_id}': get: - operationId: EndpointGetActionAuditLog + deprecated: true + description: Get action requests log + operationId: EndpointGetActionLog parameters: - - in: query - name: query + - in: path + name: agent_id required: true schema: - $ref: '#/components/schemas/AuditLogRequestQuery' - - in: path + $ref: '#/components/schemas/AgentId' + - in: query name: query required: true schema: - $ref: '#/components/schemas/AuditLogRequestParams' + $ref: '#/components/schemas/ActionLogRequestQuery' responses: '200': content: @@ -49,9 +54,12 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Get action audit log schema + summary: Get action requests log schema + tags: + - Security Solution Endpoint Management API /api/endpoint/action_status: get: + description: Get action status operationId: EndpointGetActionsStatus parameters: - in: query @@ -70,15 +78,18 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Actions status schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}': get: + description: Get action details operationId: EndpointGetActionsDetails parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/DetailsRequestParams' + type: string responses: '200': content: @@ -87,15 +98,23 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Action details schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}/download`': get: + description: Download a file from an endpoint operationId: EndpointFileDownload parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileDownloadRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -104,15 +123,23 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: File Download schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/action/{action_id}/file/{file_id}`': get: + description: Get file info operationId: EndpointFileInfo parameters: - in: path - name: query + name: action_id required: true schema: - $ref: '#/components/schemas/FileInfoRequestParams' + type: string + - in: path + name: file_id + required: true + schema: + type: string responses: '200': content: @@ -121,14 +148,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: File Info schema + tags: + - Security Solution Endpoint Management API /api/endpoint/action/execute: post: + description: Execute a given command on an endpoint operationId: EndpointExecuteAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ExecuteActionRequestBody' + $ref: '#/components/schemas/ExecuteRouteRequestBody' required: true responses: '200': @@ -138,14 +168,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Execute Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/get_file: post: + description: Get a file from an endpoint operationId: EndpointGetFileAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/GetFileActionRequestBody' + $ref: '#/components/schemas/GetFileRouteRequestBody' required: true responses: '200': @@ -155,25 +188,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get File Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/isolate: post: - operationId: EndpointIsolateHostAction + description: Isolate an endpoint + operationId: EndpointIsolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/IsolateRouteRequestBody' required: true responses: '200': @@ -182,15 +207,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Isolate host Action + summary: Isolate Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/kill_process: post: + description: Kill a running process on an endpoint operationId: EndpointKillProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -200,25 +228,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Kill process Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/running_procs: post: - operationId: EndpointGetRunningProcessesAction + description: Get list of running processes on an endpoint + operationId: EndpointGetProcessesAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/GetProcessesRouteRequestBody' required: true responses: '200': @@ -228,14 +248,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Running Processes Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/scan: post: + description: Scan a file or directory operationId: EndpointScanAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ScanActionRequestBody' + $ref: '#/components/schemas/ScanRouteRequestBody' required: true responses: '200': @@ -245,6 +268,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Scan Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/state: get: operationId: EndpointGetActionsState @@ -256,14 +281,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Action State schema + tags: + - Security Solution Endpoint Management API /api/endpoint/action/suspend_process: post: + description: Suspend a running process on an endpoint operationId: EndpointSuspendProcessAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/ProcessActionSchemas' + $ref: '#/components/schemas/KillOrSuspendActionSchema' required: true responses: '200': @@ -273,25 +301,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Suspend process Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/unisolate: post: - operationId: EndpointUnisolateHostAction + description: Release an endpoint + operationId: EndpointUnisolateAction requestBody: content: application/json: schema: - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' + $ref: '#/components/schemas/UnisolateRouteRequestBody' required: true responses: '200': @@ -300,15 +320,18 @@ paths: schema: $ref: '#/components/schemas/SuccessResponse' description: OK - summary: Unisolate host Action + summary: Unisolate Action + tags: + - Security Solution Endpoint Management API /api/endpoint/action/upload: post: + description: Upload a file to an endpoint operationId: EndpointUploadAction requestBody: content: application/json: schema: - $ref: '#/components/schemas/FileUploadActionRequestBody' + $ref: '#/components/schemas/UploadRouteRequestBody' required: true responses: '200': @@ -318,6 +341,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Upload Action + tags: + - Security Solution Endpoint Management API /api/endpoint/metadata: get: operationId: GetEndpointMetadataList @@ -335,18 +360,17 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Metadata List schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/metadata/{id}': get: operationId: GetEndpointMetadata parameters: - in: path - name: query + name: id required: true schema: - type: object - properties: - id: - type: string + type: string responses: '200': content: @@ -355,6 +379,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Metadata schema + tags: + - Security Solution Endpoint Management API /api/endpoint/metadata/transforms: get: operationId: GetEndpointMetadataTransform @@ -366,6 +392,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Metadata Transform schema + tags: + - Security Solution Endpoint Management API /api/endpoint/policy_response: get: operationId: GetPolicyResponse @@ -386,8 +414,11 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Policy Response schema + tags: + - Security Solution Endpoint Management API /api/endpoint/policy/summaries: get: + deprecated: true operationId: GetAgentPolicySummary parameters: - in: query @@ -409,6 +440,8 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get Agent Policy Summary schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/protection_updates_note/{package_policy_id}': get: operationId: GetProtectionUpdatesNote @@ -426,6 +459,8 @@ paths: $ref: '#/components/schemas/ProtectionUpdatesNoteResponse' description: OK summary: Get Protection Updates Note schema + tags: + - Security Solution Endpoint Management API post: operationId: CreateUpdateProtectionUpdatesNote parameters: @@ -451,6 +486,8 @@ paths: $ref: '#/components/schemas/ProtectionUpdatesNoteResponse' description: OK summary: Create Update Protection Updates Note schema + tags: + - Security Solution Endpoint Management API '/api/endpoint/suggestions/{suggestion_type}': post: operationId: GetEndpointSuggestions @@ -485,8 +522,21 @@ paths: $ref: '#/components/schemas/SuccessResponse' description: OK summary: Get suggestions + tags: + - Security Solution Endpoint Management API components: schemas: + ActionLogRequestQuery: + type: object + properties: + end_date: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + page_size: + $ref: '#/components/schemas/PageSize' + start_date: + $ref: '#/components/schemas/StartDate' AgentId: description: Agent ID type: string @@ -501,28 +551,18 @@ components: type: array - minLength: 1 type: string + AgentTypes: + enum: + - endpoint + - sentinel_one + - crowdstrike + type: string AlertIds: description: A list of alerts ids. items: $ref: '#/components/schemas/NonEmptyString' minItems: 1 type: array - AuditLogRequestParams: - type: object - properties: - agent_id: - $ref: '#/components/schemas/AgentId' - AuditLogRequestQuery: - type: object - properties: - end_date: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - page_size: - $ref: '#/components/schemas/PageSize' - start_date: - $ref: '#/components/schemas/StartDate' CaseIds: description: Case IDs to be updated (cannot contain empty strings) items: @@ -541,6 +581,7 @@ components: - get-file - execute - upload + - scan minLength: 1 type: string Commands: @@ -550,39 +591,9 @@ components: Comment: description: Optional comment type: string - DetailsRequestParams: - type: object - properties: - action_id: - type: string EndDate: description: End date type: string - EndpointActionListRequestQuery: - type: object - properties: - agentIds: - $ref: '#/components/schemas/AgentIds' - commands: - $ref: '#/components/schemas/Commands' - endDate: - $ref: '#/components/schemas/EndDate' - page: - $ref: '#/components/schemas/Page' - pageSize: - default: 10 - description: Number of items per page - maximum: 10000 - minimum: 1 - type: integer - startDate: - $ref: '#/components/schemas/StartDate' - types: - $ref: '#/components/schemas/Types' - userIds: - $ref: '#/components/schemas/UserIds' - withOutputs: - $ref: '#/components/schemas/WithOutputs' EndpointIds: description: List of endpoint IDs (cannot contain empty strings) items: @@ -590,10 +601,12 @@ components: type: string minItems: 1 type: array - ExecuteActionRequestBody: + ExecuteRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -604,6 +617,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -617,30 +632,39 @@ components: - command required: - parameters - FileDownloadRequestParams: + GetEndpointActionListRouteQuery: type: object properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileInfoRequestParams: - type: object - properties: - action_id: - type: string - file_id: - type: string - required: - - action_id - - file_id - FileUploadActionRequestBody: + agentIds: + $ref: '#/components/schemas/AgentIds' + agentTypes: + $ref: '#/components/schemas/AgentTypes' + commands: + $ref: '#/components/schemas/Commands' + endDate: + $ref: '#/components/schemas/EndDate' + page: + $ref: '#/components/schemas/Page' + pageSize: + default: 10 + description: Number of items per page + maximum: 10000 + minimum: 1 + type: integer + startDate: + $ref: '#/components/schemas/StartDate' + types: + $ref: '#/components/schemas/Types' + userIds: + $ref: '#/components/schemas/UserIds' + withOutputs: + $ref: '#/components/schemas/WithOutputs' + GetFileRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -651,24 +675,29 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: - file: - format: binary - type: string parameters: type: object properties: - overwrite: - default: false - type: boolean + path: + type: string + required: + - path required: - parameters - - file - GetFileActionRequestBody: + GetProcessesRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + IsolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + KillOrSuspendActionSchema: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -679,15 +708,22 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: - type: object - properties: - path: - type: string - required: - - path + oneOf: + - type: object + properties: + pid: + minimum: 1 + type: integer + - type: object + properties: + entity_id: + minLength: 1 + type: string required: - parameters ListRequestQuery: @@ -742,6 +778,28 @@ components: minLength: 1 pattern: ^(?! *$).+$ type: string + NoParametersRequestSchema: + type: object + properties: + body: + type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + required: + - body Page: default: 1 description: Page number @@ -756,45 +814,17 @@ components: Parameters: description: Optional parameters object type: object - ProcessActionSchemas: - allOf: - - type: object - properties: - alert_ids: - $ref: '#/components/schemas/AlertIds' - case_ids: - $ref: '#/components/schemas/CaseIds' - comment: - $ref: '#/components/schemas/Comment' - endpoint_ids: - $ref: '#/components/schemas/EndpointIds' - parameters: - $ref: '#/components/schemas/Parameters' - - type: object - properties: - parameters: - oneOf: - - type: object - properties: - pid: - minimum: 1 - type: integer - - type: object - properties: - entity_id: - minLength: 1 - type: string - required: - - parameters ProtectionUpdatesNoteResponse: type: object properties: note: type: string - ScanActionRequestBody: + ScanRouteRequestBody: allOf: - type: object properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' alert_ids: $ref: '#/components/schemas/AlertIds' case_ids: @@ -805,6 +835,8 @@ components: $ref: '#/components/schemas/EndpointIds' parameters: $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids - type: object properties: parameters: @@ -827,16 +859,52 @@ components: minimum: 1 type: integer Type: + description: Type of response action enum: - automated - manual type: string Types: + description: List of types of response actions items: $ref: '#/components/schemas/Type' maxLength: 2 minLength: 1 type: array + UnisolateRouteRequestBody: + $ref: '#/components/schemas/NoParametersRequestSchema' + UploadRouteRequestBody: + allOf: + - type: object + properties: + agent_type: + $ref: '#/components/schemas/AgentTypes' + alert_ids: + $ref: '#/components/schemas/AlertIds' + case_ids: + $ref: '#/components/schemas/CaseIds' + comment: + $ref: '#/components/schemas/Comment' + endpoint_ids: + $ref: '#/components/schemas/EndpointIds' + parameters: + $ref: '#/components/schemas/Parameters' + required: + - endpoint_ids + - type: object + properties: + file: + format: binary + type: string + parameters: + type: object + properties: + overwrite: + default: false + type: boolean + required: + - parameters + - file UserIds: description: User IDs oneOf: @@ -848,7 +916,7 @@ components: - minLength: 1 type: string WithOutputs: - description: With Outputs + description: Shows detailed outputs for an action response oneOf: - items: minLength: 1 @@ -863,3 +931,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: Interact with and manage endpoints running the Elastic Defend integration. + name: Security Solution Endpoint Management API diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 87188a7434611..79df809b600c2 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -38,10 +38,26 @@ paths: type: string responses: '200': + content: + application/json: + schema: + type: object + properties: + deleted: + description: >- + If the record was deleted. If false the record did not + exist. + type: boolean + record: + $ref: '#/components/schemas/AssetCriticalityRecord' + required: + - deleted description: Successful response '400': description: Invalid request summary: Delete Criticality Record + tags: + - Security Solution Entity Analytics API get: operationId: GetAssetCriticalityRecord parameters: @@ -70,6 +86,8 @@ paths: '404': description: Criticality record not found summary: Get Criticality Record + tags: + - Security Solution Entity Analytics API post: operationId: CreateAssetCriticalityRecord requestBody: @@ -98,6 +116,8 @@ paths: '400': description: Invalid request summary: Create Criticality Record + tags: + - Security Solution Entity Analytics API /api/asset_criticality/bulk: post: operationId: BulkUpsertAssetCriticalityRecords @@ -153,6 +173,8 @@ paths: summary: >- Bulk upsert asset criticality data, creating or updating records as needed + tags: + - Security Solution Entity Analytics API /api/asset_criticality/list: post: operationId: FindAssetCriticalityRecords @@ -226,6 +248,8 @@ paths: - total description: Bulk upload successful summary: 'List asset criticality data, filtering and sorting as needed' + tags: + - Security Solution Entity Analytics API components: schemas: AssetCriticalityBulkUploadErrorItem: @@ -304,3 +328,6 @@ components: type: http security: - BasicAuth: [] +tags: + - description: '' + name: Security Solution Entity Analytics API diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml new file mode 100644 index 0000000000000..d3b079e0551ab --- /dev/null +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_timeline_api_2023_10_31.bundled.schema.yaml @@ -0,0 +1,1554 @@ +openapi: 3.0.3 +info: + description: >- + You can create Timelines and Timeline templates via the API, as well as + import new Timelines from an ndjson file. + title: Security Solution Timeline API (Elastic Cloud Serverless) + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/note: + delete: + operationId: DeleteNote + requestBody: + content: + application/json: + schema: + oneOf: + - nullable: true + type: object + properties: + noteId: + type: string + required: + - noteId + - type: object + properties: + noteIds: + items: + type: string + nullable: true + type: array + required: + - noteIds + description: The id of the note to delete. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + description: Indicates the note was successfully deleted. + summary: Deletes a note from a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + get: + description: Gets notes + operationId: GetNotes + parameters: + - in: query + name: documentIds + required: true + schema: + $ref: '#/components/schemas/DocumentIds' + - in: query + name: page + schema: + nullable: true + type: number + - in: query + name: perPage + schema: + nullable: true + type: number + - in: query + name: search + schema: + nullable: true + type: string + - in: query + name: sortField + schema: + nullable: true + type: string + - in: query + name: sortOrder + schema: + nullable: true + type: string + - in: query + name: filter + schema: + nullable: true + type: string + responses: + '200': + description: Indicates the requested notes were returned. + summary: Get all notes for a given document. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + patch: + operationId: PersistNoteRoute + requestBody: + content: + application/json: + schema: + type: object + properties: + eventDataView: + nullable: true + type: string + eventIngested: + nullable: true + type: string + eventTimestamp: + nullable: true + type: string + note: + $ref: '#/components/schemas/BareNote' + noteId: + nullable: true + type: string + overrideOwner: + nullable: true + type: boolean + version: + nullable: true + type: string + required: + - note + description: The note to persist or update along with additional metadata. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistNote: + type: object + properties: + code: + type: number + message: + type: string + note: + $ref: '#/components/schemas/Note' + required: + - code + - message + - note + required: + - persistNote + required: + - data + description: Indicates the note was successfully created. + summary: Persists a note to a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/pinned_event: + patch: + operationId: PersistPinnedEventRoute + requestBody: + content: + application/json: + schema: + type: object + properties: + eventId: + type: string + pinnedEventId: + nullable: true + type: string + timelineId: + type: string + required: + - eventId + - timelineId + description: The pinned event to persist or update along with additional metadata. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistPinnedEventOnTimeline: + allOf: + - $ref: '#/components/schemas/PinnedEvent' + - type: object + properties: + code: + type: number + message: + type: string + required: + - persistPinnedEventOnTimeline + required: + - data + description: Indicate the event was successfully pinned in the timeline. + summary: Persists a pinned event to a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline: + delete: + operationId: DeleteTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + savedObjectIds: + items: + type: string + type: array + searchIds: + description: >- + Saved search ids that should be deleted alongside the + timelines + items: + type: string + type: array + required: + - savedObjectIds + description: The ids of the timelines or timeline templates to delete. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + deleteTimeline: + type: boolean + required: + - deleteTimeline + required: + - data + description: Indicates the timeline was successfully deleted. + summary: Deletes one or more timelines or timeline templates. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + get: + operationId: GetTimeline + parameters: + - description: The ID of the template timeline to retrieve + in: query + name: template_timeline_id + schema: + type: string + - description: The ID of the timeline to retrieve + in: query + name: id + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + nullable: true + required: + - getOneTimeline + required: + - data + description: Indicates that the (template) timeline was found and returned. + summary: >- + Get an existing saved timeline or timeline template. This API is used to + retrieve an existing saved timeline or timeline template. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + patch: + description: >- + Updates an existing timeline. This API is used to update the title, + description, date range, pinned events, pinned queries, and/or pinned + saved queries of an existing timeline. + operationId: PatchTimeline + requestBody: + content: + application/json: + schema: + type: object + properties: + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineId: + nullable: true + type: string + version: + nullable: true + type: string + required: + - timelineId + - version + - timeline + description: The timeline updates along with the timeline ID and version. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data + description: >- + Indicates that the draft timeline was successfully created. In the + event the user already has a draft timeline, the existing draft + timeline is cleared and returned. + '405': + content: + application/json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: >- + Indicates that the user does not have the required access to create + a draft timeline. + summary: Updates an existing timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + post: + operationId: CreateTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/TimelineStatus' + nullable: true + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timeline: + $ref: '#/components/schemas/SavedTimeline' + timelineId: + nullable: true + type: string + timelineType: + $ref: '#/components/schemas/TimelineType' + nullable: true + version: + nullable: true + type: string + required: + - timeline + description: >- + The required timeline fields used to create a new timeline along with + optional fields that will be created if not provided. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - persistTimeline + required: + - data + description: Indicates the timeline was successfully created. + '405': + content: + application/json: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: Indicates that there was an error in the timeline creation. + summary: Creates a new timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_draft: + get: + operationId: GetDraftTimelines + parameters: + - in: query + name: timelineType + required: true + schema: + $ref: '#/components/schemas/TimelineType' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data + description: Indicates that the draft timeline was successfully retrieved. + '403': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + If a draft timeline was not found and we attempted to create one, it + indicates that the user does not have the required permissions to + create a draft timeline. + '409': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + This should never happen, but if a draft timeline was not found and + we attempted to create one, it indicates that there is already a + draft timeline with the given timelineId. + summary: >- + Retrieves the draft timeline for the current user. If the user does not + have a draft timeline, an empty timeline is returned. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + post: + description: > + Retrieves a clean draft timeline. If a draft timeline does not exist, it + is created and returned. + operationId: CleanDraftTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + timelineType: + $ref: '#/components/schemas/TimelineType' + required: + - timelineType + description: >- + The type of timeline to create. Valid values are `default` and + `template`. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistTimeline: + type: object + properties: + timeline: + $ref: '#/components/schemas/TimelineResponse' + required: + - timeline + required: + - persistTimeline + required: + - data + description: >- + Indicates that the draft timeline was successfully created. In the + event the user already has a draft timeline, the existing draft + timeline is cleared and returned. + '403': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + Indicates that the user does not have the required permissions to + create a draft timeline. + '409': + content: + 'application:json': + schema: + type: object + properties: + message: + type: string + status_code: + type: number + description: >- + Indicates that there is already a draft timeline with the given + timelineId. + summary: Retrieves a draft timeline or timeline template. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_export: + post: + operationId: ExportTimelines + parameters: + - description: The name of the file to export + in: query + name: file_name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + properties: + ids: + items: + type: string + nullable: true + type: array + description: The ids of the timelines to export + required: true + responses: + '200': + content: + application/ndjson: + schema: + description: NDJSON of the exported timelines + type: string + description: Indicates the timelines were successfully exported + '400': + content: + application/ndjson: + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: Indicates that the export size limit was exceeded + summary: Exports timelines as an NDJSON file + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_favorite: + patch: + operationId: PersistFavoriteRoute + requestBody: + content: + application/json: + schema: + type: object + properties: + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timelineId: + nullable: true + type: string + timelineType: + $ref: '#/components/schemas/TimelineType' + nullable: true + required: + - timelineId + - templateTimelineId + - templateTimelineVersion + - timelineType + description: The required fields used to favorite a (template) timeline. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + persistFavorite: + $ref: '#/components/schemas/FavoriteTimelineResponse' + required: + - persistFavorite + required: + - data + description: Indicates the favorite status was successfully updated. + '403': + content: + 'application:json': + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: >- + Indicates the user does not have the required permissions to persist + the favorite status. + summary: Persists a given users favorite status of a timeline. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_import: + post: + operationId: ImportTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + file: + allOf: + - $ref: '#/components/schemas/Readable' + - type: object + properties: + hapi: + type: object + properties: + filename: + type: string + headers: + type: object + isImmutable: + enum: + - 'true' + - 'false' + type: string + required: + - filename + - headers + required: + - hapi + description: The timelines to import as a readable stream. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/ImportTimelineResult' + required: + - data + description: Indicates the import of timelines was successful. + '400': + content: + application/json: + schema: + type: object + properties: + body: + type: string + id: + type: string + statusCode: + type: number + description: >- + Indicates the import of timelines was unsuccessful because of an + invalid file extension. + '404': + content: + application/json: + schema: + type: object + properties: + id: + type: string + statusCode: + type: number + description: >- + Indicates that we were unable to locate the saved object client + necessary to handle the import. + '409': + content: + application/json: + schema: + type: object + properties: + body: + type: string + id: + type: string + statusCode: + type: number + description: Indicates the import of timelines was unsuccessful. + summary: Imports timelines. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/_prepackaged: + post: + operationId: InstallPrepackedTimelines + requestBody: + content: + application/json: + schema: + type: object + properties: + prepackagedTimelines: + items: + $ref: '#/components/schemas/SavedTimeline' + type: array + timelinesToInstall: + items: + $ref: '#/components/schemas/ImportTimelines' + nullable: true + type: array + timelinesToUpdate: + items: + $ref: '#/components/schemas/ImportTimelines' + nullable: true + type: array + required: + - timelinesToInstall + - timelinesToUpdate + - prepackagedTimelines + description: The timelines to install or update. + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/ImportTimelineResult' + required: + - data + description: Indicates the installation of prepackaged timelines was successful. + '500': + content: + 'application:json': + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: >- + Indicates the installation of prepackaged timelines was + unsuccessful. + summary: Installs prepackaged timelines. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timeline/resolve: + get: + operationId: ResolveTimeline + parameters: + - description: The ID of the template timeline to resolve + in: query + name: template_timeline_id + schema: + type: string + - description: The ID of the timeline to resolve + in: query + name: id + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + getOneTimeline: + $ref: '#/components/schemas/TimelineResponse' + nullable: true + required: + - getOneTimeline + required: + - data + description: The (template) timeline has been found + '400': + description: The request is missing parameters + '404': + description: The (template) timeline was not found + summary: Get an existing saved timeline or timeline template. + tags: + - Security Solution Timeline API + - 'access:securitySolution' + /api/timelines: + get: + operationId: GetTimelines + parameters: + - description: >- + If true, only timelines that are marked as favorites by the user are + returned. + in: query + name: only_user_favorite + schema: + enum: + - 'true' + - 'false' + nullable: true + type: string + - in: query + name: timeline_type + schema: + $ref: '#/components/schemas/TimelineType' + nullable: true + - in: query + name: sort_field + schema: + $ref: '#/components/schemas/SortFieldTimeline' + - in: query + name: sort_order + schema: + enum: + - asc + - desc + type: string + - in: query + name: page_size + schema: + nullable: true + type: string + - in: query + name: page_index + schema: + nullable: true + type: string + - in: query + name: search + schema: + nullable: true + type: string + - in: query + name: status + schema: + $ref: '#/components/schemas/TimelineStatus' + nullable: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + customTemplateTimelineCount: + type: number + defaultTimelineCount: + type: number + elasticTemplateTimelineCount: + type: number + favoriteCount: + type: number + templateTimelineCount: + type: number + timelines: + items: + $ref: '#/components/schemas/TimelineResponse' + type: array + totalCount: + type: number + required: + - timelines + - totalCount + - defaultTimelineCount + - templateTimelineCount + - favoriteCount + - elasticTemplateTimelineCount + - customTemplateTimelineCount + required: + - data + description: Indicates that the (template) timelines were found and returned. + '400': + content: + 'application:json': + schema: + type: object + properties: + body: + type: string + statusCode: + type: number + description: Bad request. The user supplied invalid data. + summary: >- + This API is used to retrieve a list of existing saved timelines or + timeline templates. + tags: + - Security Solution Timeline API + - 'access:securitySolution' +components: + schemas: + BareNote: + type: object + properties: + created: + nullable: true + type: number + createdBy: + nullable: true + type: string + eventId: + nullable: true + type: string + note: + nullable: true + type: string + timelineId: + nullable: true + type: string + updated: + nullable: true + type: number + updatedBy: + nullable: true + type: string + required: + - timelineId + ColumnHeaderResult: + type: object + properties: + aggregatable: + type: boolean + category: + type: string + columnHeaderType: + type: string + description: + type: string + example: + oneOf: + - type: string + - type: number + id: + type: string + indexes: + items: + type: string + type: array + name: + type: string + placeholder: + type: string + searchable: + type: boolean + type: + type: string + DataProviderQueryMatch: + type: object + properties: + enabled: + nullable: true + type: boolean + excluded: + nullable: true + type: boolean + id: + nullable: true + type: string + kqlQuery: + nullable: true + type: string + name: + nullable: true + type: string + queryMatch: + $ref: '#/components/schemas/QueryMatchResult' + DataProviderResult: + type: object + properties: + and: + items: + $ref: '#/components/schemas/DataProviderQueryMatch' + nullable: true + type: array + enabled: + nullable: true + type: boolean + excluded: + nullable: true + type: boolean + id: + nullable: true + type: string + kqlQuery: + nullable: true + type: string + name: + nullable: true + type: string + queryMatch: + $ref: '#/components/schemas/QueryMatchResult' + nullable: true + type: + $ref: '#/components/schemas/DataProviderType' + nullable: true + DataProviderType: + description: >- + The type of data provider to create. Valid values are `default` and + `template`. + enum: + - default + - template + type: string + DocumentIds: + oneOf: + - items: + type: string + type: array + - type: string + FavoriteTimelineResponse: + type: object + properties: + code: + nullable: true + type: number + favorite: + items: + $ref: '#/components/schemas/FavoriteTimelineResult' + type: array + message: + nullable: true + type: string + savedObjectId: + type: string + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timelineType: + $ref: '#/components/schemas/TimelineType' + version: + type: string + required: + - savedObjectId + - version + FavoriteTimelineResult: + type: object + properties: + favoriteDate: + nullable: true + type: number + fullName: + nullable: true + type: string + userName: + nullable: true + type: string + FilterTimelineResult: + type: object + properties: + exists: + type: boolean + match_all: + type: string + meta: + type: object + properties: + alias: + type: string + controlledBy: + type: string + disabled: + type: boolean + field: + type: string + formattedValue: + type: string + index: + type: string + key: + type: string + negate: + type: boolean + params: + type: string + type: + type: string + value: + type: string + missing: + type: string + query: + type: string + range: + type: string + script: + type: string + ImportTimelineResult: + type: object + properties: + errors: + items: + type: object + properties: + error: + type: object + properties: + message: + type: string + status_code: + type: number + id: + type: string + type: array + success: + type: boolean + success_count: + type: number + timelines_installed: + type: number + timelines_updated: + type: number + ImportTimelines: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventNotes: + items: + $ref: '#/components/schemas/BareNote' + nullable: true + type: array + globalNotes: + items: + $ref: '#/components/schemas/BareNote' + nullable: true + type: array + pinnedEventIds: + items: + type: string + nullable: true + type: array + savedObjectId: + nullable: true + type: string + version: + nullable: true + type: string + Note: + allOf: + - $ref: '#/components/schemas/BareNote' + - type: object + properties: + noteId: + type: string + version: + type: string + PinnedEvent: + type: object + properties: + created: + nullable: true + type: number + createdBy: + nullable: true + type: string + eventId: + type: string + pinnedEventId: + type: string + timelineId: + type: string + updated: + nullable: true + type: number + updatedBy: + nullable: true + type: string + version: + type: string + required: + - eventId + - pinnedEventId + - timelineId + - version + QueryMatchResult: + type: object + properties: + displayField: + nullable: true + type: string + displayValue: + nullable: true + type: string + field: + nullable: true + type: string + operator: + nullable: true + type: string + value: + nullable: true + type: string + Readable: + type: object + properties: + _data: + additionalProperties: true + type: object + _encoding: + type: string + _events: + additionalProperties: true + type: object + _eventsCount: + type: number + _maxListeners: + additionalProperties: true + type: object + _position: + type: number + _read: + additionalProperties: true + type: object + _readableState: + additionalProperties: true + type: object + readable: + type: boolean + RowRendererId: + enum: + - alert + - alerts + - auditd + - auditd_file + - library + - netflow + - plain + - registry + - suricata + - system + - system_dns + - system_endgame_process + - system_file + - system_fim + - system_security_event + - system_socket + - threat_match + - zeek + type: string + SavedTimeline: + type: object + properties: + columns: + items: + $ref: '#/components/schemas/ColumnHeaderResult' + nullable: true + type: array + created: + nullable: true + type: number + createdBy: + nullable: true + type: string + dataProviders: + items: + $ref: '#/components/schemas/DataProviderResult' + nullable: true + type: array + dataViewId: + nullable: true + type: string + dateRange: + nullable: true + type: object + properties: + end: + oneOf: + - type: string + - type: number + start: + oneOf: + - type: string + - type: number + description: + nullable: true + type: string + eqlOptions: + nullable: true + type: object + properties: + eventCategoryField: + nullable: true + type: string + query: + nullable: true + type: string + size: + oneOf: + - nullable: true + type: string + - nullable: true + type: number + tiebreakerField: + nullable: true + type: string + timestampField: + nullable: true + type: string + eventType: + nullable: true + type: string + excludedRowRendererIds: + items: + $ref: '#/components/schemas/RowRendererId' + nullable: true + type: array + favorite: + items: + $ref: '#/components/schemas/FavoriteTimelineResult' + nullable: true + type: array + filters: + items: + $ref: '#/components/schemas/FilterTimelineResult' + nullable: true + type: array + indexNames: + items: + type: string + nullable: true + type: array + kqlMode: + nullable: true + type: string + kqlQuery: + $ref: '#/components/schemas/SerializedFilterQueryResult' + nullable: true + savedQueryId: + nullable: true + type: string + savedSearchId: + nullable: true + type: string + sort: + $ref: '#/components/schemas/Sort' + nullable: true + status: + enum: + - active + - draft + - immutable + nullable: true + type: string + templateTimelineId: + nullable: true + type: string + templateTimelineVersion: + nullable: true + type: number + timelineType: + $ref: '#/components/schemas/TimelineType' + nullable: true + title: + nullable: true + type: string + updated: + nullable: true + type: number + updatedBy: + nullable: true + type: string + SerializedFilterQueryResult: + type: object + properties: + filterQuery: + nullable: true + type: object + properties: + kuery: + nullable: true + type: object + properties: + expression: + nullable: true + type: string + kind: + nullable: true + type: string + serializedQuery: + nullable: true + type: string + Sort: + oneOf: + - $ref: '#/components/schemas/SortObject' + - items: + $ref: '#/components/schemas/SortObject' + type: array + SortFieldTimeline: + description: The field to sort the timelines by. + enum: + - title + - description + - updated + - created + type: string + SortObject: + type: object + properties: + columnId: + nullable: true + type: string + columnType: + nullable: true + type: string + sortDirection: + nullable: true + type: string + TimelineResponse: + allOf: + - $ref: '#/components/schemas/SavedTimeline' + - type: object + properties: + eventIdToNoteIds: + items: + $ref: '#/components/schemas/Note' + type: array + noteIds: + items: + type: string + type: array + notes: + items: + $ref: '#/components/schemas/Note' + type: array + pinnedEventIds: + items: + type: string + type: array + pinnedEventsSaveObject: + items: + $ref: '#/components/schemas/PinnedEvent' + type: array + savedObjectId: + type: string + version: + type: string + required: + - savedObjectId + - version + TimelineStatus: + description: >- + The status of the timeline. Valid values are `active`, `draft`, and + `immutable`. + enum: + - active + - draft + - immutable + type: string + TimelineType: + description: >- + The type of timeline to create. Valid values are `default` and + `template`. + enum: + - default + - template + type: string + securitySchemes: + BasicAuth: + scheme: basic + type: http +security: + - BasicAuth: [] +tags: + - description: >- + You can create Timelines and Timeline templates via the API, as well as + import new Timelines from an ndjson file. + name: Security Solution Timeline API diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md index e73b976d0b44e..26b01da200903 100644 --- a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md +++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/upgrade_review_algorithms.md @@ -24,14 +24,18 @@ Status: `in progress`. - [**Scenario: `ABB` - Rule field is any type**](#scenario-abb---rule-field-is-any-type) - [Rule field has an update and a custom value that are NOT the same - `ABC`](#rule-field-has-an-update-and-a-custom-value-that-are-not-the-same---abc) - [**Scenario: `ABC` - Rule field is a number or single line string**](#scenario-abc---rule-field-is-a-number-or-single-line-string) - - [**Scenario: `ABC` - Rule field is a mergable multi line string**](#scenario-abc---rule-field-is-a-mergable-multi-line-string) - - [**Scenario: `ABC` - Rule field is a non-mergable multi line string**](#scenario-abc---rule-field-is-a-non-mergable-multi-line-string) + - [**Scenario: `ABC` - Rule field is a mergeable multi line string**](#scenario-abc---rule-field-is-a-mergeable-multi-line-string) + - [**Scenario: `ABC` - Rule field is a non-mergeable multi line string**](#scenario-abc---rule-field-is-a-non-mergeable-multi-line-string) - [**Scenario: `ABC` - Rule field is an array of scalar values**](#scenario-abc---rule-field-is-an-array-of-scalar-values) + - [**Scenario: `ABC` - Rule field is a solvable `data_source` object**](#scenario-abc---rule-field-is-a-solvable-data_source-object) + - [**Scenario: `ABC` - Rule field is a non-solvable `data_source` object**](#scenario-abc---rule-field-is-a-non-solvable-data_source-object) - [Rule field has an update and a custom value that are the same and the rule base version doesn't exist - `-AA`](#rule-field-has-an-update-and-a-custom-value-that-are-the-same-and-the-rule-base-version-doesnt-exist----aa) - [**Scenario: `-AA` - Rule field is any type**](#scenario--aa---rule-field-is-any-type) - - [Rule field has an update and a custom value that are NOT the same and the rule base version doesn't exist - `-BC`](#rule-field-has-an-update-and-a-custom-value-that-are-not-the-same-and-the-rule-base-version-doesnt-exist----bc) - - [**Scenario: `-BC` - Rule field is a number or single line string**](#scenario--bc---rule-field-is-a-number-or-single-line-string) - - [**Scenario: `-BC` - Rule field is an array of scalar values**](#scenario--bc---rule-field-is-an-array-of-scalar-values) + - [Rule field has an update and a custom value that are NOT the same and the rule base version doesn't exist - `-AB`](#rule-field-has-an-update-and-a-custom-value-that-are-not-the-same-and-the-rule-base-version-doesnt-exist----ab) + - [**Scenario: `-AB` - Rule field is a number or single line string**](#scenario--ab---rule-field-is-a-number-or-single-line-string) + - [**Scenario: `-AB` - Rule field is an array of scalar values**](#scenario--ab---rule-field-is-an-array-of-scalar-values) + - [**Scenario: `-AB` - Rule field is a solvable `data_source` object**](#scenario--ab---rule-field-is-a-solvable-data_source-object) + - [**Scenario: `-AB` - Rule field is a non-solvable `data_source` object**](#scenario--ab---rule-field-is-a-non-solvable-data_source-object) ## Useful information @@ -52,6 +56,9 @@ Status: `in progress`. - **Merged version**: Also labeled as `merged_version`. This is the version of the rule that we determine via the various algorithms. It could contain a mix of all the rule versions on a per-field basis to create a singluar version of the rule containing all relevant updates and user changes to display to the user. +- **Grouped fields** + - `data_source`: an object that contains a `type` field with a value of `data_view_id` or `index_patterns` and another field that's either `data_view_id` of type string OR `index_patterns` of type string array + ### Assumptions - All scenarios will contain at least 1 prebuilt rule installed in Kibana. @@ -63,7 +70,7 @@ Status: `in progress`. #### **Scenario: `AAA` - Rule field is any type** -**Automation**: 4 integration tests with mock rules + a set of unit tests for each algorithm +**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm ```Gherkin Given <field_name> field is not customized by the user (current version == base version) @@ -73,18 +80,20 @@ And <field_name> field should not be returned from the `upgrade/_review` API end And <field_name> field should not be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "A" | "A" | "A" | -| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | -| number | risk_score | 1 | 1 | 1 | 1 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "A" | "A" | "A" | +| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | +| number | risk_score | 1 | 1 | 1 | 1 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | +| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | +| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | ``` ### Rule field doesn't have an update but has a custom value - `ABA` #### **Scenario: `ABA` - Rule field is any type** -**Automation**: 3 integration tests with mock rules + a set of unit tests for each algorithm +**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm ```Gherkin Given <field_name> field is customized by the user (current version != base version) @@ -94,18 +103,20 @@ And <field_name> field should be returned from the `upgrade/_review` API endpoin And <field_name> field should be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "B" | "A" | "B" | -| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | -| number | risk_score | 1 | 2 | 1 | 2 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "three"] | ["one", "two", "four"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "B" | "A" | "B" | +| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | +| number | risk_score | 1 | 2 | 1 | 2 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "three"] | ["one", "two", "four"] | +| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | +| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | ``` ### Rule field has an update and doesn't have a custom value - `AAB` #### **Scenario: `AAB` - Rule field is any type** -**Automation**: 3 integration tests with mock rules + a set of unit tests for each algorithm +**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm ```Gherkin Given <field_name> field is not customized by the user (current version == base version) @@ -115,18 +126,20 @@ And <field_name> field should be returned from the `upgrade/_review` API endpoin And <field_name> field should be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "A" | "B" | "B" | -| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | -| number | risk_score | 1 | 1 | 2 | 2 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "A" | "B" | "B" | +| multi line string | description | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | +| number | risk_score | 1 | 1 | 2 | 2 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | +| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | +| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | ``` ### Rule field has an update and a custom value that are the same - `ABB` #### **Scenario: `ABB` - Rule field is any type** -**Automation**: 3 integration tests with mock rules + a set of unit tests for each algorithm +**Automation**: 6 integration tests with mock rules + a set of unit tests for each algorithm ```Gherkin Given <field_name> field is customized by the user (current version != base version) @@ -137,11 +150,13 @@ And <field_name> field should be returned from the `upgrade/_review` API endpoin And <field_name> field should be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | "A" | "B" | "B" | "B" | -| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | -| number | risk_score | 1 | 2 | 2 | 2 | -| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | ["one", "two", "four"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | "A" | "B" | "B" | "B" | +| multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | +| number | risk_score | 1 | 2 | 2 | 2 | +| array of scalars | tags | ["one", "two", "three"] | ["one", "two", "four"] | ["one", "two", "four"] | ["one", "two", "four"] | +| data_source | data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | +| data_source | data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | ``` ### Rule field has an update and a custom value that are NOT the same - `ABC` @@ -164,7 +179,7 @@ Examples: | number | risk_score | 1 | 2 | 3 | 2 | ``` -#### **Scenario: `ABC` - Rule field is a mergable multi line string** +#### **Scenario: `ABC` - Rule field is a mergeable multi line string** **Automation**: 2 integration tests with mock rules + a set of unit tests for the algorithms @@ -172,7 +187,7 @@ Examples: Given <field_name> field is customized by the user (current version != base version) And <field_name> field is updated by Elastic in this upgrade (target version != base version) And customized <field_name> field is different than the Elastic update in this upgrade (current version != target version) -And the 3-way diff of <field_name> fields are determined to be mergable +And the 3-way diff of <field_name> fields are determined to be mergeable Then for <field_name> field the diff algorithm should output a merged version as the merged one with a solvable conflict And <field_name> field should be returned from the `upgrade/_review` API endpoint And <field_name> field should be shown in the upgrade preview UI @@ -182,7 +197,7 @@ Examples: | multi line string | description | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My description.\nThis is a second line, now longer." | "My GREAT description.\nThis is a second line, now longer." | ``` -#### **Scenario: `ABC` - Rule field is a non-mergable multi line string** +#### **Scenario: `ABC` - Rule field is a non-mergeable multi line string** **Automation**: 2 integration tests with mock rules + a set of unit tests for the algorithms @@ -190,7 +205,7 @@ Examples: Given <field_name> field is customized by the user (current version != base version) And <field_name> field is updated by Elastic in this upgrade (target version != base version) And customized <field_name> field is different than the Elastic update in this upgrade (current version != target version) -And the 3-way diff of <field_name> fields are determined to be unmergable +And the 3-way diff of <field_name> fields are determined to be unmergeable Then for <field_name> field the diff algorithm should output the current version as the merged one with a non-solvable conflict And <field_name> field should be returned from the `upgrade/_review` API endpoint And <field_name> field should be shown in the upgrade preview UI @@ -225,11 +240,55 @@ Examples: | array of scalars | index | ["logs-*"] | ["logs-*", "Logs-*"] | ["logs-*", "new-*"] | ["logs-*", "Logs-*", "new-*"] | ``` +#### **Scenario: `ABC` - Rule field is a solvable `data_source` object** + +**Automation**: 2 integration tests with mock rules + a set of unit tests for the algorithm + +```Gherkin +Given data_source field is customized by the user (current version != base version) +And data_source field is updated by Elastic in this upgrade (target version != base version) +And customized data_source field is different than the Elastic update in this upgrade (current version != target version) +And both current version and target version are of type "index_patterns" in data_source +Then for data_source field the diff algorithm should output a custom merged version with a solvable conflict +And arrays should be deduplicated before comparison +And arrays should be compared sensitive of case +And arrays should be compared agnostic of order +And data_source field should be returned from the `upgrade/_review` API endpoint +And data_source field should be shown in the upgrade preview UI + +Examples: +| algorithm | base_version | current_version | target_version | merged_version | +| data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["two", "one", "four"]} | {type: "index_patterns", "index_patterns": ["one", "two", "five"]} | {type: "index_patterns", "index_patterns": ["one", "two", "four", "five"]} | +| data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "five"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three", "five"]} | +``` + +#### **Scenario: `ABC` - Rule field is a non-solvable `data_source` object** + +**Automation**: 6 integration tests with mock rules + a set of unit tests for the algorithm + +```Gherkin +Given data_source field is customized by the user (current version != base version) +And data_source field is updated by Elastic in this upgrade (target version != base version) +And customized data_source field is different than the Elastic update in this upgrade (current version != target version) +Then for data_source field the diff algorithm should output the current version as the merged version with a non-solvable conflict +And data_source field should be returned from the `upgrade/_review` API endpoint +And data_source field should be shown in the upgrade preview UI + +Examples: +| algorithm | base_version | current_version | target_version | merged_version | +| data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "B"} | {type: "data_view", "data_view_id": "C"} | {type: "data_view", "data_view_id": "B"} | +| data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "B"} | {type: "data_view", "data_view_id": "A"} | +| data_source | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "B"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | +| data_source | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "B"} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "B"} | +| data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "four"]} | {type: "data_view", "data_view_id": "C"} | {type: "index_patterns", "index_patterns": ["one", "two", "four"]} | +| data_source | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "index_patterns", "index_patterns": ["one", "two", "five"]} | {type: "data_view", "data_view_id": "A"} | +``` + ### Rule field has an update and a custom value that are the same and the rule base version doesn't exist - `-AA` #### **Scenario: `-AA` - Rule field is any type** -**Automation**: 3 integration tests with mock rules + a set of unit tests for each algorithm +**Automation**: 5 integration tests with mock rules + a set of unit tests for each algorithm ```Gherkin Given at least 1 installed prebuilt rule has a new version available @@ -240,18 +299,20 @@ And <field_name> field should not be returned from the `upgrade/_review` API end And <field_name> field should not be shown in the upgrade preview UI Examples: -| algorithm | field_name | base_version | current_version | target_version | merged_version | -| single line string | name | N/A | "A" | "A" | "A" | -| multi line string | description | N/A | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | -| number | risk_score | N/A | 1 | 1 | 1 | -| array of scalars | tags | N/A | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | +| algorithm | field_name | base_version | current_version | target_version | merged_version | +| single line string | name | N/A | "A" | "A" | "A" | +| multi line string | description | N/A | "My description.\nThis is a second line." | "My description.\nThis is a second line." | "My description.\nThis is a second line." | +| number | risk_score | N/A | 1 | 1 | 1 | +| array of scalars | tags | N/A | ["one", "three", "two"] | ["three", "one", "two"] | ["one", "three", "two"] | +| data_source | data_source | N/A | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | +| data_source | data_source | N/A | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | ``` -### Rule field has an update and a custom value that are NOT the same and the rule base version doesn't exist - `-BC` +### Rule field has an update and a custom value that are NOT the same and the rule base version doesn't exist - `-AB` -#### **Scenario: `-BC` - Rule field is a number or single line string** +#### **Scenario: `-AB` - Rule field is a number or single line string** -**Automation**: 2 integration tests with mock rules + a set of unit tests for the algorithms +**Automation**: 4 integration tests with mock rules + a set of unit tests for the algorithms ```Gherkin Given at least 1 installed prebuilt rule has a new version available @@ -266,15 +327,16 @@ Examples: | single line string | name | N/A | "B" | "C" | "C" | | multi line string | description | N/A | "My description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | "My GREAT description.\nThis is a second line." | | number | risk_score | N/A | 2 | 3 | 3 | +| data_source | data_source | N/A | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "B"} | {type: "data_view", "data_view_id": "B"} | ``` -#### **Scenario: `-BC` - Rule field is an array of scalar values** +#### **Scenario: `-AB` - Rule field is an array of scalar values** **Automation**: 1 integration test with mock rules + a set of unit tests for the algorithm ```Gherkin -Given <field_name> field is customized by the user (current version != base version) -And <field_name> field is updated by Elastic in this upgrade (target version != base version) +Given at least 1 installed prebuilt rule has a new version available +And the base version of the rule cannot be determined And customized <field_name> field is different than the Elastic update in this upgrade (current version != target version) Then for <field_name> field the diff algorithm should output a custom merged version with a solvable conflict And arrays should be deduplicated before comparison @@ -288,3 +350,43 @@ Examples: | algorithm | field_name | base_version | current_version | target_version | merged_version | | array of scalars | tags | N/A | ["one", "two", "four"] | ["one", "two", "five"] | ["one", "two", "four", "five"] | ``` + +#### **Scenario: `-AB` - Rule field is a solvable `data_source` object** + +**Automation**: 1 integration test with mock rules + a set of unit tests for the algorithm + +```Gherkin +Given at least 1 installed prebuilt rule has a new version available +And the base version of the rule cannot be determined +And customized data_source field is different than the Elastic update in this upgrade (current version != target version) +And current version and target version are both array fields in data_source +Then for data_source field the diff algorithm should output a custom merged version with a solvable conflict +And arrays should be deduplicated before comparison +And arrays should be compared sensitive of case +And arrays should be compared agnostic of order +And data_source field should be returned from the `upgrade/_review` API endpoint +And data_source field should be shown in the upgrade preview UI + +Examples: +| algorithm | base_version | current_version | target_version | merged_version | +| data_source | N/A | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "index_patterns", "index_patterns": ["one", "two", "four"]} | {type: "index_patterns", "index_patterns": ["one", "two", "three", "four"]} | + +``` + +#### **Scenario: `-AB` - Rule field is a non-solvable `data_source` object** + +**Automation**: 1 integration test with mock rules + a set of unit tests for the algorithm + +```Gherkin +Given at least 1 installed prebuilt rule has a new version available +And the base version of the rule cannot be determined +And customized data_source field is different than the Elastic update in this upgrade (current version != target version) +And current version and target version are not both array fields in data_source +Then for data_source field the diff algorithm should output the target version as the merged version with a solvable conflict +And data_source field should be returned from the `upgrade/_review` API endpoint +And data_source field should be shown in the upgrade preview UI + +Examples: +| algorithm | base_version | current_version | target_version | merged_version | +| data_source | N/A | {type: "index_patterns", "index_patterns": ["one", "two", "three"]} | {type: "data_view", "data_view_id": "A"} | {type: "data_view", "data_view_id": "A"} | +``` diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index 7df47aba441e3..98575c1f48c26 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -31,7 +31,8 @@ "openapi:generate": "node scripts/openapi/generate", "openapi:generate:debug": "node --inspect-brk scripts/openapi/generate", "openapi:bundle:detections": "node scripts/openapi/bundle_detections", + "openapi:bundle:timeline": "node scripts/openapi/bundle_timeline", "openapi:bundle:entity-analytics": "node scripts/openapi/bundle_entity_analytics", "openapi:bundle:endpoint-management": "node scripts/openapi/bundle_endpoint_management" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx index 5a1b8423572fc..b6492ef97cae7 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -12,7 +12,6 @@ import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout'; -import { DataViewPickerProvider } from '../../../sourcerer/experimental/containers/dataview_picker_provider'; import { AttackDiscoveryTour } from '../../../attack_discovery/tour'; import { URL_PARAM_KEY } from '../../../common/hooks/use_url_state'; import { SecuritySolutionFlyout, TimelineFlyout } from '../../../flyout'; @@ -104,12 +103,10 @@ export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionTemplateW component="div" grow={true} > - <DataViewPickerProvider> - <ExpandableFlyoutProvider urlKey={isPreview ? undefined : URL_PARAM_KEY.flyout}> - {children} - <SecuritySolutionFlyout /> - </ExpandableFlyoutProvider> - </DataViewPickerProvider> + <ExpandableFlyoutProvider urlKey={isPreview ? undefined : URL_PARAM_KEY.flyout}> + {children} + <SecuritySolutionFlyout /> + </ExpandableFlyoutProvider> {didMount && <AttackDiscoveryTour />} </KibanaPageTemplate.Section> diff --git a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.test.tsx new file mode 100644 index 0000000000000..0662ca042a522 --- /dev/null +++ b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.test.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 from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { ManagementSettings } from './management_settings'; +import type { Conversation } from '@kbn/elastic-assistant'; +import { + useAssistantContext, + useFetchCurrentUserConversations, + WELCOME_CONVERSATION_TITLE, +} from '@kbn/elastic-assistant'; +import { useKibana } from '../../common/lib/kibana'; +import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conversation'; + +// Mock the necessary hooks and components +jest.mock('@kbn/elastic-assistant', () => ({ + useAssistantContext: jest.fn(), + useFetchCurrentUserConversations: jest.fn(), + mergeBaseWithPersistedConversations: jest.fn(), + WELCOME_CONVERSATION_TITLE: 'Welcome Conversation', +})); +jest.mock('@kbn/elastic-assistant/impl/assistant/settings/assistant_settings_management', () => ({ + AssistantSettingsManagement: jest.fn(() => <div data-test-subj="AssistantSettingsManagement" />), +})); +jest.mock('@kbn/elastic-assistant/impl/assistant/use_conversation', () => ({ + useConversation: jest.fn(), +})); +jest.mock('../../common/lib/kibana', () => ({ + useKibana: jest.fn(), +})); + +const useAssistantContextMock = useAssistantContext as jest.Mock; +const useFetchCurrentUserConversationsMock = useFetchCurrentUserConversations as jest.Mock; +const useKibanaMock = useKibana as jest.Mock; +const useConversationMock = useConversation as jest.Mock; + +describe('ManagementSettings', () => { + const baseConversations = { base: 'conversation' }; + const http = {}; + const getDefaultConversation = jest.fn(); + const navigateToApp = jest.fn(); + const mockConversations = { + [WELCOME_CONVERSATION_TITLE]: { title: WELCOME_CONVERSATION_TITLE }, + } as Record<string, Conversation>; + + const renderComponent = ({ + isAssistantEnabled = true, + conversations, + }: { + isAssistantEnabled?: boolean; + conversations: Record<string, Conversation>; + }) => { + useAssistantContextMock.mockReturnValue({ + baseConversations, + http, + assistantAvailability: { isAssistantEnabled }, + }); + + useFetchCurrentUserConversationsMock.mockReturnValue({ + data: conversations, + }); + + useKibanaMock.mockReturnValue({ + services: { + application: { + navigateToApp, + capabilities: { + securitySolutionAssistant: { 'ai-assistant': false }, + }, + }, + }, + }); + + useConversationMock.mockReturnValue({ + getDefaultConversation, + }); + + return render(<ManagementSettings />); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('navigates to home if securityAIAssistant is disabled', () => { + renderComponent({ + conversations: mockConversations, + }); + expect(navigateToApp).toHaveBeenCalledWith('home'); + }); + + it('renders AssistantSettingsManagement when conversations are available and securityAIAssistant is enabled', () => { + renderComponent({ + conversations: mockConversations, + }); + expect(screen.getByTestId('AssistantSettingsManagement')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx index 2855c6b6115d3..6a7478eca0df0 100644 --- a/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx +++ b/x-pack/plugins/security_solution/public/assistant/stack_management/management_settings.tsx @@ -16,6 +16,7 @@ import { } from '@kbn/elastic-assistant'; import { useConversation } from '@kbn/elastic-assistant/impl/assistant/use_conversation'; import type { FetchConversationsResponse } from '@kbn/elastic-assistant/impl/assistant/api'; +import { useKibana } from '../../common/lib/kibana'; const defaultSelectedConversationId = WELCOME_CONVERSATION_TITLE; @@ -26,6 +27,15 @@ export const ManagementSettings = React.memo(() => { assistantAvailability: { isAssistantEnabled }, } = useAssistantContext(); + const { + application: { + navigateToApp, + capabilities: { + securitySolutionAssistant: { 'ai-assistant': securityAIAssistantEnabled }, + }, + }, + } = useKibana().services; + const onFetchedConversations = useCallback( (conversationsData: FetchConversationsResponse): Record<string, Conversation> => mergeBaseWithPersistedConversations(baseConversations, conversationsData), @@ -46,6 +56,10 @@ export const ManagementSettings = React.memo(() => { [conversations, getDefaultConversation] ); + if (!securityAIAssistantEnabled) { + navigateToApp('home'); + } + if (conversations) { return <AssistantSettingsManagement selectedConversation={currentConversation} />; } diff --git a/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx b/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx index f317f6ded4b15..e9d06cd157c25 100644 --- a/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/discover_in_timeline/use_discover_in_timeline_actions.tsx @@ -125,7 +125,7 @@ export const useDiscoverInTimelineActions = ( newSavedSearchId ); const savedSearchState = savedSearch ? getAppStateFromSavedSearch(savedSearch) : null; - discoverStateContainer.current?.appState.initAndSync(savedSearch); + discoverStateContainer.current?.appState.initAndSync(); await discoverStateContainer.current?.appState.replaceUrlState( savedSearchState?.appState ?? {} ); diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx index 437309762732d..9c8989abeeab5 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -26,7 +26,7 @@ import { } from '../../hooks/translations'; import { displaySuccessToast, useStateToaster } from '../toasters'; import { TimelineId } from '../../../../common/types/timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { addProviderToTimeline, fieldWasDroppedOnTimelineColumns, @@ -117,7 +117,7 @@ export const DragDropContextWrapperComponent: React.FC<Props> = ({ browserFields const onAddedToTimeline = useCallback( (fieldOrValue: string) => { const message = - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? ADDED_TO_TIMELINE_TEMPLATE_MESSAGE(fieldOrValue) : ADDED_TO_TIMELINE_MESSAGE(fieldOrValue); displaySuccessToast(message, dispatchToaster); diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts index 5b14d7919baf2..cbe8f2a6a7297 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/helpers.ts @@ -8,13 +8,14 @@ import { keyBy } from 'lodash/fp'; import type { DropResult } from '@hello-pangea/dnd'; import type { Dispatch } from 'redux'; import type { ActionCreator } from 'typescript-fsa'; +import type { FieldSpec } from '@kbn/data-plugin/common'; import { getFieldIdFromDraggable, getProviderIdFromDraggable } from '@kbn/securitysolution-t-grid'; import { TableId } from '@kbn/securitysolution-data-table'; import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants'; import { getScopedActions } from '../../../helpers'; import type { ColumnHeaderOptions } from '../../../../common/types'; -import type { BrowserField, BrowserFields } from '../../../../common/search_strategy'; +import type { BrowserFields } from '../../../../common/search_strategy'; import { dragAndDropActions } from '../../store/actions'; import type { IdToDataProvider } from '../../store/drag_and_drop/model'; import { addContentToTimeline } from '../../../timelines/components/timeline/data_providers/helpers'; @@ -184,8 +185,8 @@ export const allowTopN = ({ return isAllowlistedNonBrowserField || (isAggregatable && isAllowedType); }; -const getAllBrowserFields = (browserFields: BrowserFields): Array<Partial<BrowserField>> => - Object.values(browserFields).reduce<Array<Partial<BrowserField>>>( +const getAllBrowserFields = (browserFields: BrowserFields): Array<Partial<FieldSpec>> => + Object.values(browserFields).reduce<Array<Partial<FieldSpec>>>( (acc, namespace) => [ ...acc, ...Object.values(namespace.fields != null ? namespace.fields : {}), @@ -195,8 +196,7 @@ const getAllBrowserFields = (browserFields: BrowserFields): Array<Partial<Browse const getAllFieldsByName = ( browserFields: BrowserFields -): { [fieldName: string]: Partial<BrowserField> } => - keyBy('name', getAllBrowserFields(browserFields)); +): { [fieldName: string]: Partial<FieldSpec> } => keyBy('name', getAllBrowserFields(browserFields)); const linkFields: Record<string, string> = { 'kibana.alert.rule.name': 'kibana.alert.rule.uuid', diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/columns.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/columns.test.tsx deleted file mode 100644 index 52b6493a25c21..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/columns.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 type { ReactWrapper } from 'enzyme'; -import React from 'react'; -import { getColumns } from './columns'; -import { TestProviders } from '../../mock'; -import { useMountAppended } from '../../utils/use_mount_appended'; -import { mockBrowserFields } from '../../containers/source/mock'; -import type { EventFieldsData } from './types'; - -jest.mock('../../lib/kibana'); - -jest.mock('@kbn/cell-actions/src/hooks/use_load_actions', () => { - const actual = jest.requireActual('@kbn/cell-actions/src/hooks/use_load_actions'); - return { - ...actual, - useLoadActions: jest.fn().mockImplementation(() => ({ - value: [], - error: undefined, - loading: false, - })), - }; -}); - -jest.mock('../../hooks/use_get_field_spec'); - -interface Column { - field: string; - name: string | JSX.Element; - sortable: boolean; - render: (field: string, data: EventFieldsData) => JSX.Element; -} - -describe('getColumns', () => { - const mount = useMountAppended(); - const defaultProps = { - browserFields: mockBrowserFields, - columnHeaders: [], - contextId: 'some-context', - eventId: 'some-event', - getLinkValue: jest.fn(), - onUpdateColumns: jest.fn(), - scopeId: 'some-timeline', - toggleColumn: jest.fn(), - }; - - test('should have expected fields', () => { - const columns = getColumns(defaultProps); - columns.forEach((column) => { - expect(column).toHaveProperty('field'); - expect(column).toHaveProperty('name'); - expect(column).toHaveProperty('render'); - expect(column).toHaveProperty('sortable'); - }); - }); - - describe('column actions', () => { - let actionsColumn: Column; - const mockDataToUse = mockBrowserFields.agent.fields; - const testValue = 'testValue'; - const testData = { - type: 'someType', - category: 'agent', - field: 'agent.id', - ...mockDataToUse, - } as EventFieldsData; - - beforeEach(() => { - actionsColumn = getColumns(defaultProps)[0] as Column; - }); - - test('it renders inline actions', () => { - const wrapper = mount( - <TestProviders>{actionsColumn.render(testValue, testData)}</TestProviders> - ) as ReactWrapper; - - expect(wrapper.find('[data-test-subj="inlineActions"]').exists()).toBeTruthy(); - }); - - test('it does not render inline actions when readOnly prop is passed', () => { - actionsColumn = getColumns({ ...defaultProps, isReadOnly: true })[0] as Column; - const wrapper = mount( - <TestProviders>{actionsColumn.render(testValue, testData)}</TestProviders> - ) as ReactWrapper; - - expect(wrapper.find('[data-test-subj="inlineActions"]').exists()).toBeFalsy(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx deleted file mode 100644 index 5ec5cf8ce892c..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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, EuiText } from '@elastic/eui'; -import memoizeOne from 'memoize-one'; -import React from 'react'; -import styled from 'styled-components'; -import { getCategory } from '@kbn/triggers-actions-ui-plugin/public'; -import { SecurityCellActions, CellActionsMode, SecurityCellActionsTrigger } from '../cell_actions'; -import type { BrowserFields } from '../../containers/source'; -import * as i18n from './translations'; -import type { EventFieldsData } from './types'; -import type { BrowserField } from '../../../../common/search_strategy'; -import { FieldValueCell } from './table/field_value_cell'; -import { FieldNameCell } from './table/field_name_cell'; -import { getSourcererScopeId } from '../../../helpers'; -import type { ColumnsProvider } from './event_fields_browser'; - -const HoverActionsContainer = styled(EuiPanel)` - align-items: center; - display: flex; - flex-direction: row; - height: 25px; - justify-content: center; - left: 5px; - position: absolute; - top: -10px; - width: 30px; -`; - -HoverActionsContainer.displayName = 'HoverActionsContainer'; - -export const getFieldFromBrowserField = memoizeOne( - (field: string, browserFields: BrowserFields): BrowserField | undefined => { - const category = getCategory(field); - - return browserFields[category]?.fields?.[field] as BrowserField; - }, - (newArgs, lastArgs) => newArgs[0] === lastArgs[0] -); - -export const getColumns: ColumnsProvider = ({ - browserFields, - eventId, - contextId, - scopeId, - getLinkValue, - isDraggable, - isReadOnly, -}) => [ - ...(!isReadOnly - ? ([ - { - field: 'values', - name: ( - <EuiText size="xs"> - <strong>{i18n.ACTIONS}</strong> - </EuiText> - ), - sortable: false, - truncateText: false, - width: '132px', - render: (values, data) => { - return ( - <SecurityCellActions - data={{ - field: data.field, - value: values, - }} - triggerId={SecurityCellActionsTrigger.DETAILS_FLYOUT} - mode={CellActionsMode.INLINE} - visibleCellActions={3} - sourcererScopeId={getSourcererScopeId(scopeId)} - metadata={{ scopeId, isObjectArray: data.isObjectArray }} - /> - ); - }, - }, - ] as ReturnType<ColumnsProvider>) - : []), - { - field: 'field', - className: 'eventFieldsTable__fieldNameCell', - name: ( - <EuiText size="xs"> - <strong>{i18n.FIELD}</strong> - </EuiText> - ), - sortable: true, - truncateText: false, - render: (field, data) => { - return ( - <FieldNameCell data={data as EventFieldsData} field={field} fieldMapping={undefined} /> - ); - }, - }, - { - field: 'values', - className: 'eventFieldsTable__fieldValueCell', - name: ( - <EuiText size="xs"> - <strong>{i18n.VALUE}</strong> - </EuiText> - ), - sortable: true, - truncateText: false, - render: (values, data) => { - const fieldFromBrowserField = getFieldFromBrowserField(data.field, browserFields); - return ( - <FieldValueCell - contextId={contextId} - data={data as EventFieldsData} - eventId={eventId} - fieldFromBrowserField={fieldFromBrowserField} - getLinkValue={getLinkValue} - isDraggable={isDraggable} - values={values} - /> - ); - }, - }, -]; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.test.tsx deleted file mode 100644 index 3462069e0aa16..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.test.tsx +++ /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 React from 'react'; -import { render } from '@testing-library/react'; -import { EnrichmentAccordionGroup } from './enrichment_accordion_group'; -import { TestProviders } from '../../../mock'; -import { indicatorWithNestedObjects } from '../__mocks__/indicator_with_nested_objects'; -import type { CtiEnrichment } from '../../../../../common/search_strategy'; - -describe('EnrichmentAccordionGroup', () => { - describe('with an indicator with an array of nested objects as a field value', () => { - it('renders the indicator without those fields', () => { - // @ts-expect-error this indicator intentionally does not conform to the CtiEnrichment type - const enrichments = [indicatorWithNestedObjects] as CtiEnrichment[]; - - const { getByTestId } = render( - <TestProviders> - <EnrichmentAccordionGroup enrichments={enrichments} /> - </TestProviders> - ); - - const enrichmentView = getByTestId('threat-details-view-0'); - - expect(enrichmentView).toBeInTheDocument(); - expect(enrichmentView).toHaveTextContent('ipv4-addr'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.tsx deleted file mode 100644 index da9b26ddc4e4a..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_accordion_group.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React from 'react'; -import styled from 'styled-components'; -import type { EuiBasicTableColumn } from '@elastic/eui'; -import { - EuiAccordion, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiToolTip, -} from '@elastic/eui'; - -import type { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti'; -import type { ThreatDetailsRow } from './helpers'; -import { - getEnrichmentIdentifiers, - isInvestigationTimeEnrichment, - getFirstSeen, - buildThreatDetailsItems, -} from './helpers'; -import { EnrichmentButtonContent } from './enrichment_button_content'; -import { ThreatSummaryTitle } from './threat_summary_title'; -import { InspectButton } from '../../inspect'; -import { QUERY_ID } from '../../../containers/cti/event_enrichment'; -import * as i18n from './translations'; -import { ThreatSummaryTable } from './threat_summary_table'; -import { REFERENCE } from '../../../../../common/cti/constants'; - -const StyledEuiAccordion = styled(EuiAccordion)` - .euiAccordion__triggerWrapper { - background: ${({ theme }) => theme.eui.euiColorLightestShade}; - border-radius: ${({ theme }) => theme.eui.euiSizeXS}; - height: ${({ theme }) => theme.eui.euiSizeXL}; - margin-bottom: ${({ theme }) => theme.eui.euiSizeS}; - padding-left: ${({ theme }) => theme.eui.euiSizeS}; - } -`; - -const ThreatDetailsDescription: React.FC<ThreatDetailsRow['description']> = ({ - fieldName, - value, -}) => { - const tooltipChild = fieldName.match(REFERENCE) ? ( - <EuiLink href={value} target="_blank"> - {value} - </EuiLink> - ) : ( - <span>{value}</span> - ); - return ( - <EuiToolTip - data-test-subj="message-tool-tip" - content={ - <EuiFlexGroup direction="column" gutterSize="none"> - <EuiFlexItem grow={false}> - <span>{fieldName}</span> - </EuiFlexItem> - </EuiFlexGroup> - } - > - {tooltipChild} - </EuiToolTip> - ); -}; - -const columns: Array<EuiBasicTableColumn<ThreatDetailsRow>> = [ - { - field: 'title', - truncateText: false, - render: ThreatSummaryTitle, - width: '220px', - name: '', - }, - { - field: 'description', - truncateText: false, - render: ThreatDetailsDescription, - name: '', - }, -]; - -const EnrichmentAccordion: React.FC<{ - enrichment: CtiEnrichment; - index: number; -}> = ({ enrichment, index }) => { - const { - id = `threat-details-item`, - field, - feedName, - type, - value, - } = getEnrichmentIdentifiers(enrichment); - const accordionId = `${id}${field}`; - return ( - <StyledEuiAccordion - id={accordionId} - key={accordionId} - initialIsOpen={true} - arrowDisplay="right" - buttonContent={<EnrichmentButtonContent field={field} feedName={feedName} value={value} />} - extraAction={ - isInvestigationTimeEnrichment(type) && ( - <EuiFlexItem grow={false}> - <InspectButton queryId={QUERY_ID} title={i18n.INVESTIGATION_QUERY_TITLE} /> - </EuiFlexItem> - ) - } - > - <ThreatSummaryTable - columns={columns} - compressed - data-test-subj={`threat-details-view-${index}`} - items={buildThreatDetailsItems(enrichment)} - /> - </StyledEuiAccordion> - ); -}; - -export const EnrichmentAccordionGroup: React.FC<{ enrichments: CtiEnrichment[] }> = ({ - enrichments, -}) => ( - <> - {enrichments - .sort((a, b) => getFirstSeen(b) - getFirstSeen(a)) - .map((enrichment, index) => ( - <React.Fragment key={`${enrichment.id}`}> - <EnrichmentAccordion enrichment={enrichment} index={index} /> - {index < enrichments.length - 1 && <EuiSpacer size="m" />} - </React.Fragment> - ))} - </> -); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_button_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_button_content.test.tsx deleted file mode 100644 index 2be7bdf76fcbf..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_button_content.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { mount } from 'enzyme'; -import { EnrichmentButtonContent } from './enrichment_button_content'; - -describe('EnrichmentButtonContent', () => { - it('renders string with feedName if feedName is present', () => { - const wrapper = mount( - <EnrichmentButtonContent field={'source.ip'} value={'127.0.0.1'} feedName={'eceintel'} /> - ); - expect(wrapper.find('[data-test-subj="enrichment-button-content"]').hostNodes().text()).toEqual( - 'source.ip 127.0.0.1 from eceintel' - ); - }); - - it('renders string without feedName if feedName is not present', () => { - const wrapper = mount(<EnrichmentButtonContent field={'source.ip'} value={'127.0.0.1'} />); - expect(wrapper.find('[data-test-subj="enrichment-button-content"]').hostNodes().text()).toEqual( - 'source.ip 127.0.0.1' - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_button_content.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_button_content.tsx deleted file mode 100644 index b03e2f4a2d21a..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_button_content.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 React from 'react'; -import styled from 'styled-components'; -import { EuiToolTip } from '@elastic/eui'; -import * as i18n from './translations'; - -const OverflowParent = styled.div` - display: inline-grid; -`; - -const OverflowContainer = styled.div` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-weight: bold; -`; - -export const EnrichmentButtonContent: React.FC<{ - field?: string; - feedName?: string; - value?: string; -}> = ({ field = '', feedName = '', value = '' }) => { - const title = `${field} ${value}${feedName ? ` ${i18n.FEED_NAME_PREPOSITION} ${feedName}` : ''}`; - return ( - <EuiToolTip content={value}> - <OverflowParent data-test-subj={'enrichment-button-content'}> - <OverflowContainer>{title}</OverflowContainer> - </OverflowParent> - </EuiToolTip> - ); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx deleted file mode 100644 index 9e6d876b2bcd3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_icon.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { EuiIcon, EuiToolTip } from '@elastic/eui'; - -import * as i18n from './translations'; -import { isInvestigationTimeEnrichment } from './helpers'; - -export const getTooltipTitle = (type: string | undefined) => - isInvestigationTimeEnrichment(type) - ? i18n.INVESTIGATION_ENRICHMENT_TITLE - : i18n.INDICATOR_ENRICHMENT_TITLE; - -export const getTooltipContent = (type: string | undefined) => - isInvestigationTimeEnrichment(type) - ? i18n.INVESTIGATION_TOOLTIP_CONTENT - : i18n.INDICATOR_TOOLTIP_CONTENT; - -export const EnrichmentIcon: React.FC<{ type: string | undefined }> = ({ type }) => { - return ( - <EuiToolTip content={getTooltipContent(type)}> - <EuiIcon type="iInCircle" size="m" /> - </EuiToolTip> - ); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_no_data.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_no_data.tsx deleted file mode 100644 index 8241054ecc940..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_no_data.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 React from 'react'; -import styled from 'styled-components'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiLink } from '@elastic/eui'; -import * as i18n from './translations'; -import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; - -const InlineBlock = styled.div` - display: inline-block; - line-height: 1.7em; -`; - -export const EnrichmentNoData: React.FC<{ type?: ENRICHMENT_TYPES }> = ({ type }) => { - if (!type) return null; - return ( - <InlineBlock data-test-subj="no-enrichments-found"> - {type === ENRICHMENT_TYPES.IndicatorMatchRule ? ( - i18n.NO_ENRICHMENTS_FOUND_DESCRIPTION - ) : ( - <FormattedMessage - id="xpack.securitySolution.enrichment.noInvestigationEnrichment" - defaultMessage="Additional threat intelligence wasn't found within the selected time frame. Try a different time frame, or {link} to collect threat intelligence for threat detection and matching." - values={{ - link: ( - <EuiLink - href="https://www.elastic.co/guide/en/security/current/es-threat-intel-integrations.html" - target="_blank" - > - <FormattedMessage - id="xpack.securitySolution.enrichment.investigationEnrichmentDocumentationLink" - defaultMessage="enable threat intelligence integrations" - /> - </EuiLink> - ), - }} - /> - )} - </InlineBlock> - ); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.test.tsx deleted file mode 100644 index 867eaf73ef85e..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.test.tsx +++ /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 React from 'react'; -import { mount } from 'enzyme'; - -import { TestProviders } from '../../../mock'; -import { EnrichmentRangePicker } from './enrichment_range_picker'; - -describe('EnrichmentRangePicker', () => { - const setRangeSpy = jest.fn(); - - const rangePickerProps = { - loading: false, - setRange: setRangeSpy, - range: { to: 'now', from: 'now-30d' }, - }; - - it('renders a date picker and a button', () => { - const wrapper = mount( - <TestProviders> - <EnrichmentRangePicker {...rangePickerProps} /> - </TestProviders> - ); - - expect(wrapper.exists('[data-test-subj="enrichment-query-range-picker"]')).toEqual(true); - expect(wrapper.exists('[data-test-subj="enrichment-button"]')).toEqual(true); - }); - - it('invokes setRange', () => { - const wrapper = mount( - <TestProviders> - <EnrichmentRangePicker {...rangePickerProps} /> - </TestProviders> - ); - - wrapper - .find('input.start-picker') - .first() - .simulate('change', { target: { value: '08/10/2019 06:29 PM' } }); - wrapper.find('[data-test-subj="enrichment-button"]').hostNodes().simulate('click'); - - expect(setRangeSpy).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.tsx deleted file mode 100644 index 3889823c3fc69..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/enrichment_range_picker.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 moment from 'moment'; -import React, { useMemo, useState, useCallback } from 'react'; -import { - EuiDatePicker, - EuiDatePickerRange, - EuiFlexGroup, - EuiFlexItem, - EuiButton, -} from '@elastic/eui'; - -import * as i18n from './translations'; -import { - DEFAULT_EVENT_ENRICHMENT_FROM, - DEFAULT_EVENT_ENRICHMENT_TO, -} from '../../../../../common/cti/constants'; - -export interface RangePickerProps { - range: { to: string; from: string }; - setRange: ({ to, from }: { to: string; from: string }) => void; - loading: boolean; -} - -export const EnrichmentRangePicker: React.FC<RangePickerProps> = ({ range, setRange, loading }) => { - const [startDate, setStartDate] = useState<moment.Moment | null>( - range.from === DEFAULT_EVENT_ENRICHMENT_FROM ? moment().subtract(30, 'd') : moment(range.from) - ); - const [endDate, setEndDate] = useState<moment.Moment | null>( - range.to === DEFAULT_EVENT_ENRICHMENT_TO ? moment() : moment(range.to) - ); - - const onButtonClick = useCallback(() => { - if (startDate && endDate && startDate.isBefore(endDate)) { - setRange({ - from: startDate.toISOString(), - to: endDate.toISOString(), - }); - } - }, [endDate, setRange, startDate]); - - const isValid = useMemo(() => startDate?.isBefore(endDate), [startDate, endDate]); - - return ( - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiDatePickerRange - data-test-subj="enrichment-query-range-picker" - startDateControl={ - <EuiDatePicker - className="start-picker" - selected={startDate} - onChange={setStartDate} - startDate={startDate} - endDate={endDate} - isInvalid={!isValid} - aria-label={i18n.ENRICHMENT_LOOKBACK_START_DATE} - showTimeSelect - /> - } - endDateControl={ - <EuiDatePicker - className="end-picker" - selected={endDate} - onChange={setEndDate} - startDate={startDate} - endDate={endDate} - isInvalid={!isValid} - aria-label={i18n.ENRICHMENT_LOOKBACK_END_DATE} - showTimeSelect - /> - } - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButton - iconType={'refresh'} - onClick={onButtonClick} - isLoading={loading} - data-test-subj={'enrichment-button'} - isDisabled={!isValid} - > - {i18n.REFRESH} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - ); -}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx deleted file mode 100644 index b1573663313ee..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.test.tsx +++ /dev/null @@ -1,625 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; -import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock'; -import { - filterDuplicateEnrichments, - getEnrichmentFields, - parseExistingEnrichments, - getEnrichmentIdentifiers, - buildThreatDetailsItems, -} from './helpers'; - -describe('parseExistingEnrichments', () => { - it('returns an empty array if data is empty', () => { - expect(parseExistingEnrichments([])).toEqual([]); - }); - - it('returns an empty array if data contains no enrichment field', () => { - const data = [ - { - category: 'host', - field: 'host.os.name.text', - isObjectArray: false, - originalValue: ['Mac OS X'], - values: ['Mac OS X'], - }, - ]; - expect(parseExistingEnrichments(data)).toEqual([]); - }); - - it('returns an empty array if enrichment field contains invalid JSON', () => { - const data = [ - { - category: 'threat', - field: 'threat.enrichments', - isObjectArray: true, - originalValue: ['whoops'], - values: ['whoops'], - }, - ]; - expect(parseExistingEnrichments(data)).toEqual([]); - }); - - it('returns an array if enrichment field contains valid JSON', () => { - const data = [ - { - category: 'threat', - field: 'threat.enrichments', - isObjectArray: true, - originalValue: [ - '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', - ], - values: [ - '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', - ], - }, - ]; - - expect(parseExistingEnrichments(data)).toEqual([ - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['matched_field', 'other_matched_field'], - values: ['matched_field', 'other_matched_field'], - }, - { - category: 'indicator', - field: 'indicator.first_seen', - isObjectArray: false, - originalValue: ['2021-02-22T17:29:25.195Z'], - values: ['2021-02-22T17:29:25.195Z'], - }, - { - category: 'indicator', - field: 'indicator.provider', - isObjectArray: false, - originalValue: ['yourself'], - values: ['yourself'], - }, - { - category: 'indicator', - field: 'indicator.type', - isObjectArray: false, - originalValue: ['custom'], - values: ['custom'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['matched_atomic'], - values: ['matched_atomic'], - }, - { - category: 'lazer', - field: 'lazer', - isObjectArray: true, - originalValue: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], - values: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], - }, - ], - ]); - }); - - it('returns multiple arrays for multiple enrichments', () => { - const data = [ - { - category: 'threat', - field: 'threat.enrichments', - isObjectArray: true, - originalValue: [ - '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', - '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - ], - values: [ - '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', - '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', - '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', - ], - }, - ]; - - expect(parseExistingEnrichments(data)).toEqual([ - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['matched_field', 'other_matched_field'], - values: ['matched_field', 'other_matched_field'], - }, - { - category: 'indicator', - field: 'indicator.first_seen', - isObjectArray: false, - originalValue: ['2021-02-22T17:29:25.195Z'], - values: ['2021-02-22T17:29:25.195Z'], - }, - { - category: 'indicator', - field: 'indicator.provider', - isObjectArray: false, - originalValue: ['yourself'], - values: ['yourself'], - }, - { - category: 'indicator', - field: 'indicator.type', - isObjectArray: false, - originalValue: ['custom'], - values: ['custom'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['matched_atomic'], - values: ['matched_atomic'], - }, - { - category: 'lazer', - field: 'lazer', - isObjectArray: true, - originalValue: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], - values: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], - }, - ], - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['matched_field_2'], - values: ['matched_field_2'], - }, - { - category: 'indicator', - field: 'indicator.first_seen', - isObjectArray: false, - originalValue: ['2021-02-22T17:29:25.195Z'], - values: ['2021-02-22T17:29:25.195Z'], - }, - { - category: 'indicator', - field: 'indicator.provider', - isObjectArray: false, - originalValue: ['other_you'], - values: ['other_you'], - }, - { - category: 'indicator', - field: 'indicator.type', - isObjectArray: false, - originalValue: ['custom'], - values: ['custom'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['matched_atomic_2'], - values: ['matched_atomic_2'], - }, - { - category: 'lazer', - field: 'lazer', - isObjectArray: true, - originalValue: [ - '{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}', - ], - values: [ - '{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}', - ], - }, - ], - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['host.name'], - values: ['host.name'], - }, - { - category: 'matched', - field: 'matched.index', - isObjectArray: false, - originalValue: ['im'], - values: ['im'], - }, - { - category: 'matched', - field: 'matched.type', - isObjectArray: false, - originalValue: ['indicator_match_rule'], - values: ['indicator_match_rule'], - }, - { - category: 'matched', - field: 'matched.id', - isObjectArray: false, - originalValue: ['FFEtSYIBZ61VHL7LvV2j'], - values: ['FFEtSYIBZ61VHL7LvV2j'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['MacBook-Pro-de-Gloria.local'], - values: ['MacBook-Pro-de-Gloria.local'], - }, - ], - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['host.hostname'], - values: ['host.hostname'], - }, - { - category: 'matched', - field: 'matched.index', - isObjectArray: false, - originalValue: ['im'], - values: ['im'], - }, - { - category: 'matched', - field: 'matched.type', - isObjectArray: false, - originalValue: ['indicator_match_rule'], - values: ['indicator_match_rule'], - }, - { - category: 'matched', - field: 'matched.id', - isObjectArray: false, - originalValue: ['E1EtSYIBZ61VHL7Ltl3m'], - values: ['E1EtSYIBZ61VHL7Ltl3m'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['MacBook-Pro-de-Gloria.local'], - values: ['MacBook-Pro-de-Gloria.local'], - }, - ], - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['host.architecture'], - values: ['host.architecture'], - }, - { - category: 'matched', - field: 'matched.index', - isObjectArray: false, - originalValue: ['im'], - values: ['im'], - }, - { - category: 'matched', - field: 'matched.type', - isObjectArray: false, - originalValue: ['indicator_match_rule'], - values: ['indicator_match_rule'], - }, - { - category: 'matched', - field: 'matched.id', - isObjectArray: false, - originalValue: ['E1EtSYIBZ61VHL7Ltl3m'], - values: ['E1EtSYIBZ61VHL7Ltl3m'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['x86_64'], - values: ['x86_64'], - }, - ], - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['host.name'], - values: ['host.name'], - }, - { - category: 'matched', - field: 'matched.index', - isObjectArray: false, - originalValue: ['im'], - values: ['im'], - }, - { - category: 'matched', - field: 'matched.type', - isObjectArray: false, - originalValue: ['indicator_match_rule'], - values: ['indicator_match_rule'], - }, - { - category: 'matched', - field: 'matched.id', - isObjectArray: false, - originalValue: ['E1EtSYIBZ61VHL7Ltl3m'], - values: ['E1EtSYIBZ61VHL7Ltl3m'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['MacBook-Pro-de-Gloria.local'], - values: ['MacBook-Pro-de-Gloria.local'], - }, - ], - [ - { - category: 'matched', - field: 'matched.field', - isObjectArray: false, - originalValue: ['host.hostname'], - values: ['host.hostname'], - }, - { - category: 'matched', - field: 'matched.index', - isObjectArray: false, - originalValue: ['im'], - values: ['im'], - }, - { - category: 'matched', - field: 'matched.type', - isObjectArray: false, - originalValue: ['indicator_match_rule'], - values: ['indicator_match_rule'], - }, - { - category: 'matched', - field: 'matched.id', - isObjectArray: false, - originalValue: ['CFErSYIBZ61VHL7LIV1N'], - values: ['CFErSYIBZ61VHL7LIV1N'], - }, - { - category: 'matched', - field: 'matched.atomic', - isObjectArray: false, - originalValue: ['MacBook-Pro-de-Gloria.local'], - values: ['MacBook-Pro-de-Gloria.local'], - }, - ], - ]); - }); -}); - -describe('filterDuplicateEnrichments', () => { - it('returns an empty array if given one', () => { - expect(filterDuplicateEnrichments([])).toEqual([]); - }); - - it('returns the existing enrichment if given both that and an investigation-time enrichment for the same indicator and field', () => { - const existingEnrichment = buildEventEnrichmentMock({ - 'matched.type': [ENRICHMENT_TYPES.IndicatorMatchRule], - }); - const investigationEnrichment = buildEventEnrichmentMock({ - 'matched.type': [ENRICHMENT_TYPES.InvestigationTime], - }); - expect(filterDuplicateEnrichments([existingEnrichment, investigationEnrichment])).toEqual([ - existingEnrichment, - ]); - }); - - it('includes two enrichments from the same indicator if it matched different fields', () => { - const enrichments = [ - buildEventEnrichmentMock(), - buildEventEnrichmentMock({ - 'matched.field': ['other.field'], - }), - ]; - expect(filterDuplicateEnrichments(enrichments)).toEqual(enrichments); - }); -}); - -describe('getEnrichmentFields', () => { - it('returns an empty object if items is empty', () => { - expect(getEnrichmentFields([])).toEqual({}); - }); - - it('returns an object of event fields and values', () => { - const data = [ - { - category: 'source', - field: 'source.ip', - isObjectArray: false, - originalValue: ['192.168.1.1'], - values: ['192.168.1.1'], - }, - { - category: 'event', - field: 'event.reference', - isObjectArray: false, - originalValue: ['https://urlhaus.abuse.ch/url/1055419/'], - values: ['https://urlhaus.abuse.ch/url/1055419/'], - }, - ]; - expect(getEnrichmentFields(data)).toEqual({ - 'source.ip': '192.168.1.1', - }); - }); -}); - -describe('getEnrichmentIdentifiers', () => { - it(`return feed name as feedName if it's present in enrichment`, () => { - expect( - getEnrichmentIdentifiers({ - 'matched.id': [1], - 'matched.field': ['matched field'], - 'matched.atomic': ['matched atomic'], - 'matched.type': ['matched type'], - 'feed.name': ['feed name'], - }) - ).toEqual({ - id: 1, - field: 'matched field', - value: 'matched atomic', - type: 'matched type', - feedName: 'feed name', - }); - }); -}); - -describe('buildThreatDetailsItems', () => { - it('returns an empty array if given an empty enrichment', () => { - expect(buildThreatDetailsItems({})).toEqual([]); - }); - - it('returns an array of threat details items', () => { - const enrichment = { - 'matched.field': ['matched field'], - 'matched.atomic': ['matched atomic'], - 'matched.type': ['matched type'], - 'feed.name': ['feed name'], - }; - expect(buildThreatDetailsItems(enrichment)).toEqual([ - { - description: { - fieldName: 'feed.name', - value: 'feed name', - }, - title: 'feed.name', - }, - { - description: { - fieldName: 'matched.atomic', - value: 'matched atomic', - }, - title: 'matched.atomic', - }, - { - description: { - fieldName: 'matched.field', - value: 'matched field', - }, - title: 'matched.field', - }, - { - description: { - fieldName: 'matched.type', - value: 'matched type', - }, - title: 'matched.type', - }, - ]); - }); - - it('retrieves the first value of an array field', () => { - const enrichment = { - array_values: ['first value', 'second value'], - }; - - expect(buildThreatDetailsItems(enrichment)).toEqual([ - { - title: 'array_values', - description: { - fieldName: 'array_values', - value: 'first value', - }, - }, - ]); - }); - - it('shortens indicator field names if they contain the default indicator path', () => { - const enrichment = { - 'threat.indicator.ip': ['127.0.0.1'], - }; - expect(buildThreatDetailsItems(enrichment)).toEqual([ - { - title: 'indicator.ip', - description: { - fieldName: 'threat.indicator.ip', - value: '127.0.0.1', - }, - }, - ]); - }); - - it('parses an object field', () => { - const enrichment = { - 'object_field.foo': ['bar'], - }; - - expect(buildThreatDetailsItems(enrichment)).toEqual([ - { - title: 'object_field.foo', - description: { - fieldName: 'object_field.foo', - value: 'bar', - }, - }, - ]); - }); - - describe('edge cases', () => { - describe('field responses for fields of type "flattened"', () => { - it('returns a note for the value of a flattened field containing a single object', () => { - const enrichment = { - flattened_object: [{ foo: 'bar' }], - }; - - expect(buildThreatDetailsItems(enrichment)).toEqual([ - { - title: 'flattened_object', - description: { - fieldName: 'flattened_object', - value: - 'This field contains nested object values, which are not rendered here. See the full document for all fields/values', - }, - }, - ]); - }); - - it('returns a note for the value of a flattened field containing an array of objects', () => { - const enrichment = { - array_field: [{ foo: 'bar' }, { baz: 'qux' }], - }; - - expect(buildThreatDetailsItems(enrichment)).toEqual([ - { - title: 'array_field', - description: { - fieldName: 'array_field', - value: - 'This field contains nested object values, which are not rendered here. See the full document for all fields/values', - }, - }, - ]); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx deleted file mode 100644 index a242fa19d770d..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/helpers.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { groupBy, isObject } from 'lodash'; -import { getDataFromFieldsHits } from '../../../../../common/utils/field_formatters'; -import { - DEFAULT_INDICATOR_SOURCE_PATH, - ENRICHMENT_DESTINATION_PATH, -} from '../../../../../common/constants'; -import { - ENRICHMENT_TYPES, - FIRST_SEEN, - MATCHED_ATOMIC, - MATCHED_FIELD, - MATCHED_ID, - MATCHED_TYPE, - FEED_NAME, -} from '../../../../../common/cti/constants'; -import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy'; -import type { - CtiEnrichment, - CtiEnrichmentIdentifiers, - EventFields, -} from '../../../../../common/search_strategy/security_solution/cti'; -import { isValidEventField } from '../../../../../common/search_strategy/security_solution/cti'; -import { getFirstElement } from '../../../../../common/utils/data_retrieval'; -import * as i18n from './translations'; - -export const isInvestigationTimeEnrichment = (type: string | undefined) => - type === ENRICHMENT_TYPES.InvestigationTime; - -export const parseExistingEnrichments = ( - data: TimelineEventsDetailsItem[] -): TimelineEventsDetailsItem[][] => { - const threatIndicatorField = data.find( - ({ field, originalValue }) => field === ENRICHMENT_DESTINATION_PATH && originalValue - ); - if (!threatIndicatorField) { - return []; - } - - const { originalValue } = threatIndicatorField; - const enrichmentStrings: string[] = Array.isArray(originalValue) - ? originalValue - : [originalValue]; - - return enrichmentStrings.reduce<TimelineEventsDetailsItem[][]>( - (enrichments, enrichmentString) => { - try { - const enrichment = getDataFromFieldsHits(JSON.parse(enrichmentString)); - enrichments.push(enrichment); - } catch (e) { - // omit failed parse - } - return enrichments; - }, - [] - ); -}; - -export const timelineDataToEnrichment = (data: TimelineEventsDetailsItem[]): CtiEnrichment => - data.reduce<CtiEnrichment>((acc, item) => { - acc[item.field] = item.originalValue; - return acc; - }, {}); - -export const getEnrichmentValue = (enrichment: CtiEnrichment, field: string) => - getFirstElement(enrichment[field]) as string | undefined; - -/** - * These fields (e.g. 'indicator.ip') may be in one of three places depending on whether it's: - * * a queried, legacy filebeat indicator ('threatintel.indicator.ip') - * * a queried, ECS 1.11 filebeat indicator ('threat.indicator.ip') - * * an existing indicator from an enriched alert ('indicator.ip') - */ -export const getShimmedIndicatorValue = (enrichment: CtiEnrichment, field: string) => - getEnrichmentValue(enrichment, field) || - getEnrichmentValue(enrichment, `threatintel.${field}`) || - getEnrichmentValue(enrichment, `threat.${field}`); - -export const getEnrichmentIdentifiers = (enrichment: CtiEnrichment): CtiEnrichmentIdentifiers => ({ - id: getEnrichmentValue(enrichment, MATCHED_ID), - field: getEnrichmentValue(enrichment, MATCHED_FIELD), - value: getEnrichmentValue(enrichment, MATCHED_ATOMIC), - type: getEnrichmentValue(enrichment, MATCHED_TYPE), - feedName: getShimmedIndicatorValue(enrichment, FEED_NAME), -}); - -const buildEnrichmentId = (enrichment: CtiEnrichment): string => { - const { id, field } = getEnrichmentIdentifiers(enrichment); - return `${id}${field}`; -}; - -/** - * This function receives an array of enrichments and removes - * investigation-time enrichments if that exact indicator already exists - * elsewhere in the list. - * - * @param enrichments {@type CtiEnrichment[]} - */ -export const filterDuplicateEnrichments = (enrichments: CtiEnrichment[]): CtiEnrichment[] => { - if (enrichments.length < 2) { - return enrichments; - } - const enrichmentsById = groupBy(enrichments, buildEnrichmentId); - - return Object.values(enrichmentsById).map( - (enrichmentGroup) => - enrichmentGroup.find( - (enrichment) => !isInvestigationTimeEnrichment(getEnrichmentValue(enrichment, MATCHED_TYPE)) - ) ?? enrichmentGroup[0] - ); -}; - -export const getEnrichmentFields = (items: TimelineEventsDetailsItem[]): EventFields => - items.reduce<EventFields>((fields, item) => { - if (isValidEventField(item.field)) { - const value = getFirstElement(item.originalValue); - if (value) { - return { ...fields, [item.field]: value }; - } - } - return fields; - }, {}); - -export const getFirstSeen = (enrichment: CtiEnrichment): number => { - const firstSeenValue = getShimmedIndicatorValue(enrichment, FIRST_SEEN); - const firstSeenDate = Date.parse(firstSeenValue ?? 'no date'); - return Number.isInteger(firstSeenDate) ? firstSeenDate : new Date(-1).valueOf(); -}; - -export interface ThreatDetailsRow { - title: string; - description: { - fieldName: string; - value: string; - }; -} - -interface ThreatDetailItem { - title: string; - description: { fieldName: string; value: unknown }; -} - -export const buildThreatDetailsItems = (enrichment: CtiEnrichment): ThreatDetailItem[] => - Object.keys(enrichment) - .sort() - .map((field) => { - const title = field.startsWith(DEFAULT_INDICATOR_SOURCE_PATH) - ? field.replace(`${DEFAULT_INDICATOR_SOURCE_PATH}`, 'indicator') - : field; - - let value = getFirstElement(enrichment[field]); - if (isObject(value)) { - value = i18n.NESTED_OBJECT_VALUES_NOT_RENDERED; - } - - return { title, description: { fieldName: field, value } }; - }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx deleted file mode 100644 index 2b1e73c1141c4..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 '../../../mock'; -import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock'; -import { ThreatDetailsView } from './threat_details_view'; - -describe('ThreatDetailsView', () => { - it('renders a detail view for each enrichment', () => { - const enrichments = [ - buildEventEnrichmentMock(), - buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }), - ]; - - const wrapper = mount( - <TestProviders> - <ThreatDetailsView - enrichments={enrichments} - showInvestigationTimeEnrichments - loading={false} - /> - </TestProviders> - ); - - expect(wrapper.find('[data-test-subj^="threat-details-view"]').hostNodes()).toHaveLength( - enrichments.length - ); - }); - - it('renders an anchor link for indicator.reference', () => { - const enrichments = [ - buildEventEnrichmentMock({ - 'threat.indicator.reference': ['http://foo.baz'], - }), - ]; - const wrapper = mount( - <TestProviders> - <ThreatDetailsView - enrichments={enrichments} - showInvestigationTimeEnrichments - loading={false} - /> - </TestProviders> - ); - expect(wrapper.find('a').length).toEqual(1); - }); - - it('sorts same type of enrichments by first_seen descending', () => { - const mostRecentDate = '2021-04-25T18:17:00.000Z'; - const olderDate = '2021-03-25T18:17:00.000Z'; - // this simulates a legacy enrichment from the old indicator match rule, - // where first_seen is available at the top level - const existingEnrichment = buildEventEnrichmentMock({ - 'indicator.first_seen': [mostRecentDate], - }); - delete existingEnrichment['threat.indicator.first_seen']; - const newEnrichment = buildEventEnrichmentMock({ - 'matched.id': ['other.id'], - 'threat.indicator.first_seen': [olderDate], - }); - const enrichments = [existingEnrichment, newEnrichment]; - - const wrapper = mount( - <TestProviders> - <ThreatDetailsView - enrichments={enrichments} - showInvestigationTimeEnrichments - loading={false} - /> - </TestProviders> - ); - - const firstSeenRows = wrapper - .find('.euiTableRow') - .hostNodes() - .filterWhere((node) => node.text().includes('first_seen')); - expect(firstSeenRows.map((node) => node.text())).toEqual([ - `indicator.first_seen${mostRecentDate}`, - `indicator.first_seen${olderDate}`, - ]); - }); - - it('groups enrichments by matched type', () => { - const indicatorMatch = buildEventEnrichmentMock({ - 'matched.type': ['indicator_match_rule'], - }); - const investigationEnrichment = buildEventEnrichmentMock({ - 'matched.type': ['investigation_time'], - }); - const enrichments = [indicatorMatch, investigationEnrichment]; - - const wrapper = mount( - <TestProviders> - <ThreatDetailsView - enrichments={enrichments} - showInvestigationTimeEnrichments - loading={false} - /> - </TestProviders> - ); - - expect(wrapper.exists('[data-test-subj="threat-match-detected"]')).toEqual(true); - expect(wrapper.exists('[data-test-subj="enriched-with-threat-intel"]')).toEqual(true); - }); - - it('renders no data views', () => { - const wrapper = mount( - <TestProviders> - <ThreatDetailsView enrichments={[]} showInvestigationTimeEnrichments loading={false} /> - </TestProviders> - ); - - expect( - wrapper.exists( - '[data-test-subj="threat-match-detected"] [data-test-subj="no-enrichments-found"]' - ) - ).toEqual(true); - expect( - wrapper.exists( - '[data-test-subj="enriched-with-threat-intel"] [data-test-subj="no-enrichments-found"]' - ) - ).toEqual(true); - }); - - it('renders loading state', () => { - const wrapper = mount( - <TestProviders> - <ThreatDetailsView enrichments={[]} showInvestigationTimeEnrichments loading /> - </TestProviders> - ); - - expect(wrapper.exists('[data-test-subj="loading-enrichments"]')).toEqual(true); - }); - - it('can hide investigation time enrichments', () => { - const investigationEnrichment = buildEventEnrichmentMock({ - 'matched.type': ['investigation_time'], - }); - - const wrapper = mount( - <TestProviders> - <ThreatDetailsView - enrichments={[investigationEnrichment]} - showInvestigationTimeEnrichments={false} - loading={false} - /> - </TestProviders> - ); - - expect(wrapper.exists('[data-test-subj="enriched-with-threat-intel"]')).toEqual(false); - }); - - it('renders children as a part of investigation time enrichment section', () => { - const wrapper = mount( - <TestProviders> - <ThreatDetailsView enrichments={[]} showInvestigationTimeEnrichments loading={false}> - <div className={'test-div'} /> - </ThreatDetailsView> - </TestProviders> - ); - - expect(wrapper.exists('.test-div')).toEqual(true); - }); - - it('does not render children id investigation time enrichment section is not showing', () => { - const wrapper = mount( - <TestProviders> - <ThreatDetailsView - enrichments={[]} - showInvestigationTimeEnrichments={false} - loading={false} - > - <div className={'test-div'} /> - </ThreatDetailsView> - </TestProviders> - ); - - expect(wrapper.exists('.test-div')).toEqual(false); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx deleted file mode 100644 index d3a2785709802..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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, - EuiHorizontalRule, - EuiSkeletonText, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import React from 'react'; -import { groupBy } from 'lodash'; - -import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; -import type { CtiEnrichment } from '../../../../../common/search_strategy/security_solution/cti'; -import * as i18n from './translations'; -import { EnrichmentIcon } from './enrichment_icon'; -import { EnrichmentAccordionGroup } from './enrichment_accordion_group'; -import { EnrichmentNoData } from './enrichment_no_data'; - -const EnrichmentSectionHeader: React.FC<{ type?: ENRICHMENT_TYPES }> = ({ type }) => { - return type ? ( - <> - <EuiFlexGroup direction="row" gutterSize="xs" alignItems="baseline"> - <EuiFlexItem grow={false}> - <EuiTitle size="xxxs"> - <h3> - {type === ENRICHMENT_TYPES.IndicatorMatchRule - ? i18n.INDICATOR_ENRICHMENT_TITLE - : i18n.INVESTIGATION_ENRICHMENT_TITLE} - </h3> - </EuiTitle> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EnrichmentIcon type={type} /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="s" /> - </> - ) : null; -}; - -const EnrichmentSection: React.FC<{ - enrichments: CtiEnrichment[]; - type?: ENRICHMENT_TYPES; - loading?: boolean; - dataTestSubj: string; - children?: React.ReactNode; -}> = ({ enrichments, type, loading, dataTestSubj, children }) => { - return ( - <div data-test-subj={dataTestSubj}> - <EnrichmentSectionHeader type={type} /> - {children} - {Array.isArray(enrichments) ? ( - <EnrichmentAccordionGroup enrichments={enrichments} /> - ) : ( - <> - <EnrichmentNoData type={type} /> - {loading && ( - <> - <EuiSpacer size="m" /> - <EuiSkeletonText data-test-subj="loading-enrichments" lines={4} /> - </> - )} - </> - )} - </div> - ); -}; - -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 -const ThreatDetailsViewComponent: React.FC<{ - enrichments: CtiEnrichment[]; - showInvestigationTimeEnrichments: boolean; - loading: boolean; - /** - * Slot to render something before the beforeHeader. - * NOTE: this was introduced to avoid alterting existing flyout and will be removed after - * new flyout implementation is ready (Expandable Flyout owned by the Investigations Team) - */ - before?: React.ReactNode; - children?: React.ReactNode; -}> = ({ enrichments, before = null, showInvestigationTimeEnrichments, loading, children }) => { - const { - [ENRICHMENT_TYPES.IndicatorMatchRule]: indicatorMatches, - [ENRICHMENT_TYPES.InvestigationTime]: threatIntelEnrichments, - undefined: matchesWithNoType, - } = groupBy(enrichments, 'matched.type'); - - return ( - <> - {before} - <EnrichmentSection - dataTestSubj="threat-match-detected" - enrichments={indicatorMatches} - type={ENRICHMENT_TYPES.IndicatorMatchRule} - /> - {showInvestigationTimeEnrichments && ( - <> - <EuiHorizontalRule /> - <EnrichmentSection - dataTestSubj="enriched-with-threat-intel" - enrichments={threatIntelEnrichments} - type={ENRICHMENT_TYPES.InvestigationTime} - loading={loading} - > - {children} - </EnrichmentSection> - </> - )} - {matchesWithNoType && ( - <> - <EuiHorizontalRule /> - {indicatorMatches && <EuiSpacer size="l" />} - <EnrichmentSection enrichments={matchesWithNoType} dataTestSubj="matches-with-no-type" /> - </> - )} - </> - ); -}; - -export const ThreatDetailsView = React.memo(ThreatDetailsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_table.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_table.tsx deleted file mode 100644 index 91a0a0397602f..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_table.tsx +++ /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 type { AnyStyledComponent } from 'styled-components'; -import styled from 'styled-components'; -import { EuiInMemoryTable } from '@elastic/eui'; - -export const ThreatSummaryTable = styled(EuiInMemoryTable as unknown as AnyStyledComponent)` - .euiTableHeaderCell, - .euiTableRowCell { - border: none; - } - .euiTableHeaderCell .euiTableCellContent { - padding: 0; - } -`; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_title.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_title.tsx deleted file mode 100644 index 71541c9d9ac1e..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_summary_title.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import styled from 'styled-components'; -import React from 'react'; -import { EuiTitle } from '@elastic/eui'; - -const StyledH5 = styled.h5` - line-height: 1.7rem; -`; - -export const ThreatSummaryTitle = (title: string) => ( - <EuiTitle size="xxxs"> - <StyledH5>{title}</StyledH5> - </EuiTitle> -); -ThreatSummaryTitle.displayName = 'ThreatSummaryTitle'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts deleted file mode 100644 index b1d84eadc8c22..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/translations.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -export * from '../../../../entity_analytics/components/risk_score/translations'; - -export const FEED_NAME_PREPOSITION = i18n.translate( - 'xpack.securitySolution.eventDetails.ctiSummary.feedNamePreposition', - { - defaultMessage: 'from', - } -); - -export const INDICATOR_ENRICHMENT_TITLE = i18n.translate( - 'xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTitle', - { - defaultMessage: 'Threat match detected', - } -); - -export const INVESTIGATION_ENRICHMENT_TITLE = i18n.translate( - 'xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTitle', - { - defaultMessage: 'Enriched with threat intelligence', - } -); - -export const INDICATOR_TOOLTIP_CONTENT = i18n.translate( - 'xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipContent', - { - defaultMessage: 'Shows available threat indicator matches.', - } -); - -export const INVESTIGATION_TOOLTIP_CONTENT = i18n.translate( - 'xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipContent', - { - defaultMessage: - 'Shows additional threat intelligence for the alert. The past 30 days were queried by default.', - } -); - -export const NO_ENRICHMENTS_FOUND_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.alertDetails.noEnrichmentsFoundDescription', - { - defaultMessage: 'This alert does not have threat intelligence.', - } -); - -export const INVESTIGATION_QUERY_TITLE = i18n.translate( - 'xpack.securitySolution.alertDetails.investigationTimeQueryTitle', - { - defaultMessage: 'Enrichment with Threat Intelligence', - } -); - -export const ENRICHMENT_LOOKBACK_START_DATE = i18n.translate( - 'xpack.securitySolution.alertDetails.enrichmentQueryStartDate', - { - defaultMessage: 'Start date', - } -); - -export const ENRICHMENT_LOOKBACK_END_DATE = i18n.translate( - 'xpack.securitySolution.alertDetails.enrichmentQueryEndDate', - { - defaultMessage: 'End date', - } -); - -export const REFRESH = i18n.translate('xpack.securitySolution.alertDetails.refresh', { - defaultMessage: 'Refresh', -}); - -export const NESTED_OBJECT_VALUES_NOT_RENDERED = i18n.translate( - 'xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentObjectValuesNotRendered', - { - defaultMessage: - 'This field contains nested object values, which are not rendered here. See the full document for all fields/values', - } -); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx deleted file mode 100644 index 1ddc73207725a..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.test.tsx +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { mockDetailItemData, mockDetailItemDataId } from '../../mock/mock_detail_item'; -import { TestProviders } from '../../mock/test_providers'; -import { EventFieldsBrowser } from './event_fields_browser'; -import { mockBrowserFields } from '../../containers/source/mock'; -import { useMountAppended } from '../../utils/use_mount_appended'; -import { TimelineTabs } from '../../../../common/types/timeline'; - -jest.mock('../../lib/kibana'); - -jest.mock('../../hooks/use_get_field_spec'); - -jest.mock('@kbn/cell-actions/src/hooks/use_load_actions', () => { - const actual = jest.requireActual('@kbn/cell-actions/src/hooks/use_load_actions'); - return { - ...actual, - useLoadActions: jest.fn().mockImplementation(() => ({ - value: [], - error: undefined, - loading: false, - })), - }; -}); - -jest.mock('../link_to'); - -const mockDispatch = jest.fn(); -jest.mock('react-redux', () => { - const original = jest.requireActual('react-redux'); - - return { - ...original, - useDispatch: () => mockDispatch, - }; -}); - -describe('EventFieldsBrowser', () => { - const mount = useMountAppended(); - - describe('column headers', () => { - ['Actions', 'Field', 'Value'].forEach((header) => { - test(`it renders the ${header} column header`, () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={mockDetailItemDataId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - - expect(wrapper.find('thead').contains(header)).toBeTruthy(); - }); - }); - }); - - describe('filter input', () => { - test('it renders a filter input with the expected placeholder', () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={mockDetailItemDataId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - - expect(wrapper.find('input[type="search"]').props().placeholder).toEqual( - 'Filter by Field, Value, or Description...' - ); - }); - }); - - describe('Hover Actions', () => { - const eventId = 'pEMaMmkBUV60JmNWmWVi'; - - test('it renders inline actions', () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={eventId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="inlineActions"]').exists()).toBeTruthy(); - }); - }); - - describe('field type icon', () => { - test('it renders the expected icon type for the data provided', () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={mockDetailItemDataId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - - expect( - wrapper - .find('tr.euiTableRow') - .find('td.euiTableRowCell') - .at(1) - .find('[data-euiicon-type]') - .exists() - ).toEqual(true); - }); - }); - - describe('field', () => { - test('it renders the field name for the data provided', () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={mockDetailItemDataId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="field-name"]').at(0).text()).toEqual('@timestamp'); - }); - - test('it renders the expected icon for description', () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={mockDetailItemDataId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - expect( - wrapper - .find('tr.euiTableRow') - .find('td.euiTableRowCell') - .at(1) - .find('[data-euiicon-type]') - .last() - .prop('data-euiicon-type') - ).toEqual('tokenDate'); - }); - }); - - describe('value', () => { - test('it renders the expected value for the data provided', () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={mockDetailItemDataId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="localized-date-tool-tip"]').at(0).text()).toEqual( - 'Feb 28, 2019 @ 16:50:54.621' - ); - }); - }); - - describe('description', () => { - test('it renders the expected field description the data provided', () => { - const wrapper = mount( - <TestProviders> - <EventFieldsBrowser - browserFields={mockBrowserFields} - data={mockDetailItemData} - eventId={mockDetailItemDataId} - scopeId="timeline-test" - timelineTabType={TimelineTabs.query} - /> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="field-name-cell"]').at(0).find('EuiToolTip').prop('content') - ).toContain('Date/time when the event originated.'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx deleted file mode 100644 index 0a8bef2fb8851..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getOr, noop, sortBy } from 'lodash/fp'; -import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiInMemoryTable } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { rgba } from 'polished'; -import styled from 'styled-components'; -import { - arrayIndexToAriaIndex, - DATA_COLINDEX_ATTRIBUTE, - DATA_ROWINDEX_ATTRIBUTE, - isTab, - onKeyDownFocusHandler, -} from '@kbn/timelines-plugin/public'; -import { dataTableSelectors, tableDefaults } from '@kbn/securitysolution-data-table'; -import { isInTableScope, isTimelineScope } from '../../../helpers'; -import { timelineSelectors } from '../../../timelines/store'; -import type { BrowserFields } from '../../containers/source'; -import { getAllFieldsByName } from '../../containers/source'; -import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; -import { getColumnHeaders } from '../../../timelines/components/timeline/body/column_headers/helpers'; -import { timelineDefaults } from '../../../timelines/store/defaults'; -import { getColumns } from './columns'; -import { EVENT_FIELDS_TABLE_CLASS_NAME, onEventDetailsTabKeyPressed, search } from './helpers'; -import { useDeepEqualSelector } from '../../hooks/use_selector'; -import type { TimelineTabs } from '../../../../common/types/timeline'; - -export type ColumnsProvider = (providerOptions: { - browserFields: BrowserFields; - eventId: string; - contextId: string; - scopeId: string; - getLinkValue: (field: string) => string | null; - isDraggable?: boolean; - isReadOnly?: boolean; -}) => Array<EuiBasicTableColumn<TimelineEventsDetailsItem>>; - -interface Props { - browserFields: BrowserFields; - data: TimelineEventsDetailsItem[]; - eventId: string; - isDraggable?: boolean; - scopeId: string; - timelineTabType: TimelineTabs | 'flyout'; - isReadOnly?: boolean; - columnsProvider?: ColumnsProvider; -} - -const TableWrapper = styled.div` - display: flex; - flex: 1; - overflow: hidden; - > div { - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - > .euiFlexGroup:first-of-type { - flex: 0; - } - } -`; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` - flex: 1; - overflow: auto; - overflow-x: hidden; - &::-webkit-scrollbar { - height: ${({ theme }) => theme.eui.euiScrollBar}; - width: ${({ theme }) => theme.eui.euiScrollBar}; - } - &::-webkit-scrollbar-thumb { - background-clip: content-box; - background-color: ${({ theme }) => rgba(theme.eui.euiColorDarkShade, 0.5)}; - border: ${({ theme }) => theme.eui.euiScrollBarCorner} solid transparent; - } - &::-webkit-scrollbar-corner, - &::-webkit-scrollbar-track { - background-color: transparent; - } - - .eventFieldsTable__fieldIcon { - padding-top: ${({ theme }) => parseFloat(theme.eui.euiSizeXS) * 1.5}px; - } - - .eventFieldsTable__fieldName { - line-height: ${({ theme }) => theme.eui.euiLineHeight}; - padding: ${({ theme }) => theme.eui.euiSizeXS}; - } - - // TODO: Use this logic from discover - /* .eventFieldsTable__multiFieldBadge { - font: ${({ theme }) => theme.eui.euiFont}; - } */ - - .inlineActions { - opacity: 0; - } - - .eventFieldsTable__tableRow { - font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; - font-family: ${({ theme }) => theme.eui.euiCodeFontFamily}; - - .inlineActions-popoverOpen { - opacity: 1; - } - - &:hover { - .inlineActions { - opacity: 1; - } - } - } - - .eventFieldsTable__actionCell, - .eventFieldsTable__fieldNameCell { - align-items: flex-start; - padding: ${({ theme }) => theme.eui.euiSizeXS}; - } - - .eventFieldsTable__fieldValue { - display: inline-block; - word-break: break-all; - word-wrap: break-word; - white-space: pre-wrap; - line-height: ${({ theme }) => theme.eui.euiLineHeight}; - color: ${({ theme }) => theme.eui.euiColorFullShade}; - vertical-align: top; - } -`; - -// Match structure in discover -const COUNT_PER_PAGE_OPTIONS = [25, 50, 100]; - -// Encapsulating the pagination logic for the table. -const useFieldBrowserPagination = () => { - const [pagination, setPagination] = useState<{ pageIndex: number }>({ - pageIndex: 0, - }); - - const onTableChange = useCallback(({ page: { index } }: { page: { index: number } }) => { - setPagination({ pageIndex: index }); - }, []); - const paginationTableProp = useMemo( - () => ({ - ...pagination, - pageSizeOptions: COUNT_PER_PAGE_OPTIONS, - }), - [pagination] - ); - - return { - onTableChange, - paginationTableProp, - }; -}; - -/** - * This callback, invoked via `EuiInMemoryTable`'s `rowProps, assigns - * attributes to every `<tr>`. - */ -/** Renders a table view or JSON view of the `ECS` `data` */ -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 -export const EventFieldsBrowser = React.memo<Props>( - ({ - browserFields, - data, - eventId, - isDraggable, - timelineTabType, - scopeId, - isReadOnly, - columnsProvider = getColumns, - }) => { - const containerElement = useRef<HTMLDivElement | null>(null); - const getScope = useMemo(() => { - if (isTimelineScope(scopeId)) { - return timelineSelectors.getTimelineByIdSelector(); - } else if (isInTableScope(scopeId)) { - return dataTableSelectors.getTableByIdSelector(); - } - }, [scopeId]); - const defaults = isTimelineScope(scopeId) ? timelineDefaults : tableDefaults; - const columnHeaders = useDeepEqualSelector((state) => { - const { columns } = (getScope && getScope(state, scopeId)) ?? defaults; - return getColumnHeaders(columns, browserFields); - }); - - const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); - const items = useMemo( - () => - sortBy(['field'], data).map((item, i) => ({ - ...item, - ...fieldsByName[item.field], - valuesConcatenated: item.values != null ? item.values.join() : '', - ariaRowindex: arrayIndexToAriaIndex(i), - })), - [data, fieldsByName] - ); - - const getLinkValue = useCallback( - (field: string) => { - const linkField = (columnHeaders.find((col) => col.id === field) ?? {}).linkField; - if (!linkField) { - return null; - } - const linkFieldData = (data ?? []).find((d) => d.field === linkField); - const linkFieldValue = getOr(null, 'originalValue', linkFieldData); - return Array.isArray(linkFieldValue) ? linkFieldValue[0] : linkFieldValue; - }, - [data, columnHeaders] - ); - - const onSetRowProps = useCallback(({ ariaRowindex, field }: TimelineEventsDetailsItem) => { - const rowIndex = ariaRowindex != null ? { 'data-rowindex': ariaRowindex } : {}; - return { - ...rowIndex, - className: 'eventFieldsTable__tableRow', - 'data-test-subj': `event-fields-table-row-${field}`, - }; - }, []); - - const columns = useMemo( - () => - columnsProvider({ - browserFields, - eventId, - contextId: `event-fields-browser-for-${scopeId}-${timelineTabType}`, - scopeId, - getLinkValue, - isDraggable, - isReadOnly, - }), - [ - browserFields, - eventId, - scopeId, - columnsProvider, - timelineTabType, - getLinkValue, - isDraggable, - isReadOnly, - ] - ); - - const focusSearchInput = useCallback(() => { - // the selector below is used to focus the input because EuiInMemoryTable does not expose a ref to its built-in search input - containerElement.current?.querySelector<HTMLInputElement>('input[type="search"]')?.focus(); - }, []); - - const onKeyDown = useCallback( - (keyboardEvent: React.KeyboardEvent) => { - if (isTab(keyboardEvent)) { - onEventDetailsTabKeyPressed({ - containerElement: containerElement.current, - keyboardEvent, - onSkipFocusBeforeEventsTable: focusSearchInput, - onSkipFocusAfterEventsTable: noop, - }); - } else { - onKeyDownFocusHandler({ - colindexAttribute: DATA_COLINDEX_ATTRIBUTE, - containerElement: containerElement?.current, - event: keyboardEvent, - maxAriaColindex: 3, - maxAriaRowindex: data.length, - onColumnFocused: noop, - rowindexAttribute: DATA_ROWINDEX_ATTRIBUTE, - }); - } - }, - [data, focusSearchInput] - ); - - useEffect(() => { - focusSearchInput(); - }, [focusSearchInput]); - - // Pagination - const { onTableChange, paginationTableProp } = useFieldBrowserPagination(); - - return ( - <TableWrapper onKeyDown={onKeyDown} ref={containerElement}> - <StyledEuiInMemoryTable - className={EVENT_FIELDS_TABLE_CLASS_NAME} - items={items} - itemId="field" - columns={columns} - onTableChange={onTableChange} - pagination={paginationTableProp} - rowProps={onSetRowProps} - search={search} - sorting={false} - data-test-subj="event-fields-browser" - /> - </TableWrapper> - ); - } -); - -EventFieldsBrowser.displayName = 'EventFieldsBrowser'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx index fca824c2269ae..86bdbb3297530 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -18,25 +18,12 @@ import type { BrowserFields } from '../../containers/source'; import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; import type { EnrichedFieldInfo, EventSummaryField } from './types'; -import * as i18n from './translations'; import { AGENT_STATUS_FIELD_NAME, QUARANTINED_PATH_FIELD_NAME, } from '../../../timelines/components/timeline/body/renderers/constants'; import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../common/endpoint/service/response_actions/constants'; -/** - * Defines the behavior of the search input that appears above the table of data - */ -export const search = { - box: { - incremental: true, - placeholder: i18n.PLACEHOLDER, - schema: true, - 'data-test-subj': 'search-input', - }, -}; - /** * An item rendered in the table */ diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx deleted file mode 100644 index be2bcddfca3e6..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/osquery_tab.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { EuiTabbedContentTab } from '@elastic/eui'; -import { EuiNotificationBadge, EuiSpacer } from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; -import type { Ecs } from '@kbn/cases-plugin/common'; -import type { SearchHit } from '../../../../common/search_strategy'; -import type { - ExpandedEventFieldsObject, - RawEventData, -} from '../../../../common/types/response_actions'; -import { expandDottedObject } from '../../../../common/utils/expand_dotted'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; -import { useKibana } from '../../lib/kibana'; -import { EventsViewType } from './event_details'; -import * as i18n from './translations'; -import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions'; - -const TabContentWrapper = styled.div` - height: 100%; - position: relative; -`; - -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 -export const useOsqueryTab = ({ - rawEventData, - ecsData, -}: { - rawEventData?: SearchHit | undefined; - ecsData?: Ecs | null; -}): EuiTabbedContentTab | undefined => { - const { - services: { osquery }, - } = useKibana(); - const responseActionsEnabled = useIsExperimentalFeatureEnabled('responseActionsEnabled'); - const endpointResponseActionsEnabled = useIsExperimentalFeatureEnabled( - 'endpointResponseActionsEnabled' - ); - - const expandedEventFieldsObject = rawEventData - ? (expandDottedObject((rawEventData as RawEventData).fields) as ExpandedEventFieldsObject) - : undefined; - - const responseActions = - expandedEventFieldsObject?.kibana?.alert?.rule?.parameters?.[0].response_actions; - - const shouldEarlyReturn = - !rawEventData || - !responseActionsEnabled || - endpointResponseActionsEnabled || - !ecsData || - !responseActions?.length; - - const alertId = rawEventData?._id ?? ''; - - const { OsqueryResults, fetchAllLiveQueries } = osquery; - - const { data: actionsData } = fetchAllLiveQueries({ - kuery: `alert_ids: ( ${alertId} )`, - alertId, - skip: shouldEarlyReturn, - }); - - if (shouldEarlyReturn) { - return; - } - - const osqueryResponseActions = responseActions.filter( - (responseAction) => responseAction.action_type_id === ResponseActionTypesEnum['.osquery'] - ); - - if (!osqueryResponseActions?.length) { - return; - } - - const actionItems = actionsData?.data.items || []; - - const ruleName = expandedEventFieldsObject?.kibana?.alert?.rule?.name?.[0]; - - const content = ( - <TabContentWrapper data-test-subj="osqueryViewWrapper"> - <OsqueryResults ruleName={ruleName} actionItems={actionItems} ecsData={ecsData} /> - <EuiSpacer size="s" /> - </TabContentWrapper> - ); - - return { - id: EventsViewType.osqueryView, - 'data-test-subj': 'osqueryViewTab', - name: i18n.OSQUERY_VIEW, - append: ( - <EuiNotificationBadge data-test-subj="osquery-actions-notification"> - {actionItems.length} - </EuiNotificationBadge> - ), - content, - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/response_actions_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/response_actions_view.tsx index 4f6bbac5df419..33760b7ab4242 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/response_actions_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/response_actions_view.tsx @@ -23,8 +23,6 @@ import { useGetAutomatedActionList } from '../../../management/hooks/response_ac import { EventsViewType } from './event_details'; import * as i18n from './translations'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; - const TabContentWrapper = styled.div` height: 100%; position: relative; @@ -75,14 +73,13 @@ export const useResponseActionsView = <T extends object = JSX.Element>({ }), [] ); - const responseActionsEnabled = useIsExperimentalFeatureEnabled('endpointResponseActionsEnabled'); const expandedEventFieldsObject = rawEventData ? (expandDottedObject((rawEventData as RawEventData).fields) as ExpandedEventFieldsObject) : undefined; const responseActions = expandedEventFieldsObject?.kibana?.alert?.rule?.parameters?.[0].response_actions; - const shouldEarlyReturn = !rawEventData || !responseActionsEnabled; + const shouldEarlyReturn = !rawEventData; const alertId = rawEventData?._id ?? ''; const [isLive, setIsLive] = useState(false); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.tsx deleted file mode 100644 index 3ca3ae7a2b0c7..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_name_cell.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 { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip } from '@elastic/eui'; -import { isEmpty } from 'lodash'; -import { FieldIcon } from '@kbn/react-field'; -import type { DataViewField } from '@kbn/data-views-plugin/common'; -import { EcsFlat } from '@elastic/ecs'; -import * as i18n from '../translations'; -import { getExampleText } from '../helpers'; -import type { EventFieldsData } from '../types'; -import { getFieldTypeName } from './get_field_type_name'; - -const getEcsField = (field: string): { example?: string; description?: string } | undefined => { - return EcsFlat[field as keyof typeof EcsFlat] as - | { - example?: string; - description?: string; - } - | undefined; -}; - -export interface FieldNameCellProps { - data: EventFieldsData; - field: string; - fieldMapping?: DataViewField; - scripted?: boolean; -} -export const FieldNameCell = React.memo( - ({ data, field, fieldMapping, scripted }: FieldNameCellProps) => { - const ecsField = getEcsField(field); - const typeName = getFieldTypeName(data.type); - // TODO: We don't have fieldMapping or isMultiField until kibana indexPatterns is implemented. Will default to field for now - const displayName = fieldMapping && fieldMapping.displayName ? fieldMapping.displayName : field; - const defaultTooltip = displayName !== field ? `${field} (${displayName})` : field; - const isMultiField = fieldMapping?.isSubtypeMulti(); - return ( - <> - <EuiFlexItem grow={false} className="eventFieldsTable__fieldIcon"> - <FieldIcon - data-test-subj="field-type-icon" - type={data.type} - label={typeName} - scripted={scripted} // TODO: Will get with kibana indexPatterns; - /> - </EuiFlexItem> - <EuiFlexGroup - wrap={true} - gutterSize="none" - responsive={false} - alignItems="flexStart" - data-test-subj="field-name-cell" - > - <EuiFlexItem className="eventFieldsTable__fieldName eui-textBreakAll" grow={false}> - <EuiToolTip - position="top" - content={ - !isEmpty(ecsField?.description) - ? `${ecsField?.description} ${getExampleText(ecsField?.example)}` - : defaultTooltip - } - delay="long" - anchorClassName="eui-textBreakAll" - > - <EuiText size="xs" data-test-subj="field-name"> - {field} - </EuiText> - </EuiToolTip> - </EuiFlexItem> - {isMultiField && ( - <EuiToolTip position="top" delay="long" content={i18n.MULTI_FIELD_TOOLTIP}> - <EuiBadge - title="" - className="eventFieldsTable__multiFieldBadge" - color="default" - data-test-subj={`eventFieldsTableRow-${field}-multifieldBadge`} - > - {i18n.MULTI_FIELD_BADGE} - </EuiBadge> - </EuiToolTip> - )} - </EuiFlexGroup> - </> - ); - } -); - -FieldNameCell.displayName = 'FieldNameCell'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx deleted file mode 100644 index 139641f3803c7..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.test.tsx +++ /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 { render, screen } from '@testing-library/react'; -import React from 'react'; - -import type { BrowserField } from '../../../containers/source'; -import { FieldValueCell } from './field_value_cell'; -import { TestProviders } from '../../../mock'; -import type { EventFieldsData } from '../types'; - -const contextId = 'test'; - -const eventId = 'TUWyf3wBFCFU0qRJTauW'; - -const hostIpData: EventFieldsData = { - aggregatable: true, - ariaRowindex: 35, - field: 'host.ip', - isObjectArray: false, - name: 'host.ip', - originalValue: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], - readFromDocValues: false, - searchable: true, - type: 'ip', - values: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], -}; -const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', 'fe80::4001:aff:fec8:32']; - -describe('FieldValueCell', () => { - describe('common behavior', () => { - beforeEach(() => { - render( - <TestProviders> - <FieldValueCell - contextId={contextId} - data={hostIpData} - eventId={eventId} - values={hostIpValues} - /> - </TestProviders> - ); - }); - - test('it formats multiple values such that each value is displayed on a single line', () => { - expect(screen.getByTestId(`event-field-${hostIpData.field}`).className).toContain('column'); - }); - }); - - describe('when `BrowserField` metadata is NOT available', () => { - beforeEach(() => { - render( - <TestProviders> - <FieldValueCell - contextId={contextId} - data={hostIpData} - eventId={eventId} - fieldFromBrowserField={undefined} // <-- no metadata - values={hostIpValues} - /> - </TestProviders> - ); - }); - - test('it renders each of the expected values when `fieldFromBrowserField` is undefined', () => { - hostIpValues.forEach((value) => { - expect(screen.getByText(value)).toBeInTheDocument(); - }); - }); - - test('it renders values formatted as plain text (without `eventFieldsTable__fieldValue` formatting)', () => { - expect(screen.getByTestId(`event-field-${hostIpData.field}`).firstChild).not.toHaveClass( - 'eventFieldsTable__fieldValue' - ); - }); - }); - - describe('`message` field formatting', () => { - const messageData: EventFieldsData = { - aggregatable: false, - ariaRowindex: 50, - field: 'message', - isObjectArray: false, - name: 'message', - originalValue: ['Endpoint network event'], - readFromDocValues: false, - searchable: true, - type: 'string', - values: ['Endpoint network event'], - }; - const messageValues = ['Endpoint network event']; - - const messageFieldFromBrowserField: BrowserField = { - aggregatable: false, - name: 'message', - readFromDocValues: false, - searchable: true, - type: 'string', - }; - - beforeEach(() => { - render( - <TestProviders> - <FieldValueCell - contextId={contextId} - data={messageData} - eventId={eventId} - fieldFromBrowserField={messageFieldFromBrowserField} - values={messageValues} - /> - </TestProviders> - ); - }); - - test('it renders special formatting for the `message` field', () => { - expect(screen.getByTestId('event-field-message')).toBeInTheDocument(); - }); - - test('it renders the expected message value', () => { - messageValues.forEach((value) => { - expect(screen.getByText(value)).toBeInTheDocument(); - }); - }); - }); - - describe('when `BrowserField` metadata IS available', () => { - const hostIpFieldFromBrowserField: BrowserField = { - aggregatable: true, - name: 'host.ip', - readFromDocValues: false, - searchable: true, - type: 'ip', - }; - - beforeEach(() => { - render( - <TestProviders> - <FieldValueCell - contextId={contextId} - data={hostIpData} - eventId={eventId} - fieldFromBrowserField={hostIpFieldFromBrowserField} // <-- metadata - values={hostIpValues} - /> - </TestProviders> - ); - }); - - test('it renders values formatted with the expected class', () => { - expect(screen.getByTestId(`event-field-${hostIpData.field}`).firstChild).toHaveClass( - 'eventFieldsTable__fieldValue' - ); - }); - - test('it aligns items at the start of the group to prevent content from stretching (by default)', () => { - expect(screen.getByTestId(`event-field-${hostIpData.field}`).className).toContain( - 'flexStart' - ); - }); - - test('it renders link buttons for each of the host ip addresses', () => { - expect(screen.getAllByRole('button').length).toBe(hostIpValues.length); - }); - - test('it renders each of the expected values when `fieldFromBrowserField` is provided', () => { - hostIpValues.forEach((value) => { - expect(screen.getByText(value)).toBeInTheDocument(); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.tsx deleted file mode 100644 index b62a8a9b8cca5..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/field_value_cell.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 type { CSSProperties } from 'react'; -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import type { BrowserField } from '../../../containers/source'; -import { OverflowField } from '../../tables/helpers'; -import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field'; -import { MESSAGE_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants'; -import type { EventFieldsData, FieldsData } from '../types'; -import { getFieldFormat } from '../get_field_format'; - -export interface FieldValueCellProps { - contextId: string; - data: EventFieldsData | FieldsData; - eventId: string; - fieldFromBrowserField?: Partial<BrowserField>; - getLinkValue?: (field: string) => string | null; - isDraggable?: boolean; - linkValue?: string | null | undefined; - style?: CSSProperties | undefined; - values: string[] | null | undefined; -} - -export const FieldValueCell = React.memo( - ({ - contextId, - data, - eventId, - fieldFromBrowserField, - getLinkValue, - isDraggable = false, - linkValue, - style, - values, - }: FieldValueCellProps) => { - return ( - <EuiFlexGroup - alignItems="flexStart" - data-test-subj={`event-field-${data.field}`} - direction="column" - gutterSize="none" - style={style} - > - {values != null && - values.map((value, i) => { - if (fieldFromBrowserField == null) { - return ( - <EuiFlexItem grow={false} key={`${i}-${value}`}> - <EuiText size="xs" key={`${i}-${value}`}> - {value} - </EuiText> - </EuiFlexItem> - ); - } - return ( - <EuiFlexItem - className="eventFieldsTable__fieldValue" - grow={false} - key={`${i}-${value}`} - > - {data.field === MESSAGE_FIELD_NAME ? ( - <OverflowField value={value} /> - ) : ( - <FormattedFieldValue - contextId={`${contextId}-${eventId}-${data.field}-${i}-${value}`} - eventId={eventId} - fieldFormat={getFieldFormat(data)} - fieldName={data.field} - fieldFromBrowserField={fieldFromBrowserField} - fieldType={data.type} - isAggregatable={fieldFromBrowserField.aggregatable} - isDraggable={isDraggable} - isObjectArray={data.isObjectArray} - value={value} - linkValue={(getLinkValue && getLinkValue(data.field)) ?? linkValue} - truncate={false} - /> - )} - </EuiFlexItem> - ); - })} - </EuiFlexGroup> - ); - } -); - -FieldValueCell.displayName = 'FieldValueCell'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/get_field_type_name.ts b/x-pack/plugins/security_solution/public/common/components/event_details/table/get_field_type_name.ts deleted file mode 100644 index cd753b7e76e95..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/get_field_type_name.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export function getFieldTypeName(type: string) { - switch (type) { - case 'boolean': - return i18n.translate('xpack.securitySolution.fieldNameIcons.booleanAriaLabel', { - defaultMessage: 'Boolean field', - }); - case 'conflict': - return i18n.translate('xpack.securitySolution.fieldNameIcons.conflictFieldAriaLabel', { - defaultMessage: 'Conflicting field', - }); - case 'date': - return i18n.translate('xpack.securitySolution.fieldNameIcons.dateFieldAriaLabel', { - defaultMessage: 'Date field', - }); - case 'geo_point': - return i18n.translate('xpack.securitySolution.fieldNameIcons.geoPointFieldAriaLabel', { - defaultMessage: 'Geo point field', - }); - case 'geo_shape': - return i18n.translate('xpack.securitySolution.fieldNameIcons.geoShapeFieldAriaLabel', { - defaultMessage: 'Geo shape field', - }); - case 'ip': - return i18n.translate('xpack.securitySolution.fieldNameIcons.ipAddressFieldAriaLabel', { - defaultMessage: 'IP address field', - }); - case 'murmur3': - return i18n.translate('xpack.securitySolution.fieldNameIcons.murmur3FieldAriaLabel', { - defaultMessage: 'Murmur3 field', - }); - case 'number': - return i18n.translate('xpack.securitySolution.fieldNameIcons.numberFieldAriaLabel', { - defaultMessage: 'Number field', - }); - case 'source': - // Note that this type is currently not provided, type for _source is undefined - return i18n.translate('xpack.securitySolution.fieldNameIcons.sourceFieldAriaLabel', { - defaultMessage: 'Source field', - }); - case 'string': - return i18n.translate('xpack.securitySolution.fieldNameIcons.stringFieldAriaLabel', { - defaultMessage: 'String field', - }); - case 'nested': - return i18n.translate('xpack.securitySolution.fieldNameIcons.nestedFieldAriaLabel', { - defaultMessage: 'Nested field', - }); - default: - return i18n.translate('xpack.securitySolution.fieldNameIcons.unknownFieldAriaLabel', { - defaultMessage: 'Unknown field', - }); - } -} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx index 2b8581aa22d7e..eb43d61b2f8d4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/investigate_in_timeline_button.tsx @@ -21,7 +21,7 @@ import { sourcererActions } from '../../../store/actions'; import { SourcererScopeName } from '../../../../sourcerer/store/model'; import type { DataProvider } from '../../../../../common/types'; import { TimelineId } from '../../../../../common/types/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline'; import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations'; @@ -58,7 +58,7 @@ export const InvestigateInTimelineButton: FC< const clearTimeline = useCreateTimeline({ timelineId: TimelineId.active, - timelineType: hasTemplateProviders ? TimelineType.template : TimelineType.default, + timelineType: hasTemplateProviders ? TimelineTypeEnum.template : TimelineTypeEnum.default, }); const configureAndOpenTimeline = useCallback(async () => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.test.tsx deleted file mode 100644 index 9fa265a60e16c..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import type { BrowserField } from '../../../containers/source'; -import { PrevalenceCellRenderer } from './prevalence_cell'; -import { TestProviders } from '../../../mock'; -import type { EventFieldsData } from '../types'; -import { TimelineId } from '../../../../../common/types'; -import type { AlertSummaryRow } from '../helpers'; -import { useAlertPrevalence } from '../../../containers/alerts/use_alert_prevalence'; -import { getEmptyValue } from '../../empty_value'; - -jest.mock('../../../lib/kibana'); -jest.mock('../../../containers/alerts/use_alert_prevalence', () => ({ - useAlertPrevalence: jest.fn(), -})); -const mockUseAlertPrevalence = useAlertPrevalence as jest.Mock; - -const eventId = 'TUWyf3wBFCFU0qRJTauW'; -const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::']; -const hostIpFieldFromBrowserField: BrowserField = { - aggregatable: true, - name: 'host.ip', - readFromDocValues: false, - searchable: true, - type: 'ip', -}; -const hostIpData: EventFieldsData = { - ...hostIpFieldFromBrowserField, - ariaRowindex: 35, - field: 'host.ip', - isObjectArray: false, - originalValue: [...hostIpValues], - values: [...hostIpValues], -}; - -const enrichedHostIpData: AlertSummaryRow['description'] = { - data: { ...hostIpData }, - eventId, - fieldFromBrowserField: { ...hostIpFieldFromBrowserField }, - isDraggable: false, - scopeId: TimelineId.test, - values: [...hostIpValues], -}; - -describe('PrevalenceCellRenderer', () => { - describe('When data is loading', () => { - test('it should show the loading spinner', async () => { - mockUseAlertPrevalence.mockImplementation(() => ({ - loading: true, - count: 123, - error: true, - })); - const { container } = render( - <TestProviders> - <PrevalenceCellRenderer {...enrichedHostIpData} /> - </TestProviders> - ); - expect(container.getElementsByClassName('euiLoadingSpinner')).toHaveLength(1); - }); - }); - - describe('When an error was returned', () => { - test('it should return empty value placeholder', async () => { - mockUseAlertPrevalence.mockImplementation(() => ({ - loading: false, - count: undefined, - error: true, - })); - const { container } = render( - <TestProviders> - <PrevalenceCellRenderer {...enrichedHostIpData} /> - </TestProviders> - ); - expect(container.getElementsByClassName('euiLoadingSpinner')).toHaveLength(0); - expect(screen.queryByText('123')).toBeNull(); - expect(screen.queryByText(getEmptyValue())).toBeTruthy(); - }); - }); - - describe('When an actual count is returned', () => { - test('it should show the count', async () => { - mockUseAlertPrevalence.mockImplementation(() => ({ - loading: false, - count: 123, - error: false, - })); - const { container } = render( - <TestProviders> - <PrevalenceCellRenderer {...enrichedHostIpData} /> - </TestProviders> - ); - expect(container.getElementsByClassName('euiLoadingSpinner')).toHaveLength(0); - expect(screen.queryByText('123')).toBeInTheDocument(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx deleted file mode 100644 index 3f2aebb81e9f2..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.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 { EuiLoadingSpinner } from '@elastic/eui'; - -import { TimelineId } from '../../../../../common/types'; -import type { AlertSummaryRow } from '../helpers'; -import { getEmptyTagValue } from '../../empty_value'; -import { InvestigateInTimelineButton } from './investigate_in_timeline_button'; -import { useActionCellDataProvider } from './use_action_cell_data_provider'; -import { useAlertPrevalence } from '../../../containers/alerts/use_alert_prevalence'; -import { getFieldFormat } from '../get_field_format'; - -/** - * Renders a Prevalence cell based on a regular alert prevalence query - */ -const PrevalenceCell: React.FC<AlertSummaryRow['description']> = ({ - data, - eventId, - fieldFromBrowserField, - linkValue, - scopeId, - values, -}) => { - const { loading, count } = useAlertPrevalence({ - field: data.field, - isActiveTimelines: scopeId === TimelineId.active, - value: values, - signalIndexName: null, - }); - - const cellDataProviders = useActionCellDataProvider({ - contextId: scopeId, - eventId, - field: data.field, - fieldFormat: getFieldFormat(data), - fieldFromBrowserField, - fieldType: data.type, - isObjectArray: data.isObjectArray, - linkValue, - values, - }); - - if (loading) { - return <EuiLoadingSpinner />; - } else if ( - typeof count === 'number' && - cellDataProviders?.dataProviders && - cellDataProviders?.dataProviders.length - ) { - return ( - <InvestigateInTimelineButton - asEmptyButton={true} - dataProviders={cellDataProviders.dataProviders} - filters={cellDataProviders.filters} - > - <span data-test-subj="alert-prevalence">{count}</span> - </InvestigateInTimelineButton> - ); - } else { - return getEmptyTagValue(); - } -}; - -PrevalenceCell.displayName = 'PrevalenceCell'; - -export const PrevalenceCellRenderer = (data: AlertSummaryRow['description']) => ( - <PrevalenceCell {...data} /> -); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.test.tsx deleted file mode 100644 index 589384c793884..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { act, render, screen } from '@testing-library/react'; -import React from 'react'; - -import type { BrowserField } from '../../../containers/source'; -import { SummaryValueCell } from './summary_value_cell'; -import { TestProviders } from '../../../mock'; -import type { EventFieldsData } from '../types'; -import type { AlertSummaryRow } from '../helpers'; -import { TimelineId } from '../../../../../common/types'; -import { AGENT_STATUS_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants'; - -jest.mock('../../../lib/kibana'); - -jest.mock('../../../hooks/use_get_field_spec'); - -const eventId = 'TUWyf3wBFCFU0qRJTauW'; -const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::']; -const hostIpFieldFromBrowserField: BrowserField = { - aggregatable: true, - name: 'host.ip', - readFromDocValues: false, - searchable: true, - type: 'ip', -}; -const hostIpData: EventFieldsData = { - ...hostIpFieldFromBrowserField, - ariaRowindex: 35, - field: 'host.ip', - isObjectArray: false, - originalValue: [...hostIpValues], - values: [...hostIpValues], -}; - -const enrichedHostIpData: AlertSummaryRow['description'] = { - data: { ...hostIpData }, - eventId, - fieldFromBrowserField: { ...hostIpFieldFromBrowserField }, - isDraggable: false, - scopeId: TimelineId.test, - values: [...hostIpValues], -}; - -const enrichedAgentStatusData: AlertSummaryRow['description'] = { - data: { - field: AGENT_STATUS_FIELD_NAME, - format: '', - type: '', - aggregatable: false, - name: AGENT_STATUS_FIELD_NAME, - searchable: false, - readFromDocValues: false, - isObjectArray: false, - }, - eventId, - values: [], - scopeId: TimelineId.test, -}; - -describe('SummaryValueCell', () => { - test('it should render', async () => { - await act(async () => { - render( - <TestProviders> - <SummaryValueCell {...enrichedHostIpData} /> - </TestProviders> - ); - }); - - hostIpValues.forEach((ipValue) => expect(screen.getByText(ipValue)).toBeInTheDocument()); - expect(screen.getByTestId('inlineActions')).toBeInTheDocument(); - }); - - describe('Without hover actions', () => { - test('When in the timeline flyout with timelineId active', async () => { - await act(async () => { - render( - <TestProviders> - <SummaryValueCell {...enrichedHostIpData} scopeId={TimelineId.active} /> - </TestProviders> - ); - }); - - hostIpValues.forEach((ipValue) => expect(screen.getByText(ipValue)).toBeInTheDocument()); - expect(screen.queryByTestId('inlineActions')).not.toBeInTheDocument(); - }); - - test('When rendering the host status field', async () => { - await act(async () => { - render( - <TestProviders> - <SummaryValueCell {...enrichedAgentStatusData} /> - </TestProviders> - ); - }); - - expect(screen.getByTestId('event-field-agent.status')).toBeInTheDocument(); - expect(screen.queryByTestId('inlineActions')).not.toBeInTheDocument(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.tsx deleted file mode 100644 index e90367b0f7af1..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/summary_value_cell.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { - SecurityCellActions, - CellActionsMode, - SecurityCellActionsTrigger, -} from '../../cell_actions'; -import { FieldValueCell } from './field_value_cell'; -import type { AlertSummaryRow } from '../helpers'; -import { hasHoverOrRowActions } from '../helpers'; -import { TimelineId } from '../../../../../common/types'; -import { getSourcererScopeId } from '../../../../helpers'; - -const style = { flexGrow: 0 }; - -export const SummaryValueCell: React.FC<AlertSummaryRow['description']> = ({ - data, - eventId, - fieldFromBrowserField, - isDraggable, - linkValue, - scopeId, - values, - isReadOnly, -}) => { - const hoverActionsEnabled = hasHoverOrRowActions(data.field); - - return ( - <> - <FieldValueCell - contextId={scopeId} - data={data} - eventId={eventId} - fieldFromBrowserField={fieldFromBrowserField} - linkValue={linkValue} - isDraggable={isDraggable} - style={style} - values={values} - /> - {scopeId !== TimelineId.active && !isReadOnly && hoverActionsEnabled && ( - <SecurityCellActions - data={{ - field: data.field, - value: values, - }} - triggerId={SecurityCellActionsTrigger.DETAILS_FLYOUT} - mode={CellActionsMode.INLINE} - visibleCellActions={3} - sourcererScopeId={getSourcererScopeId(scopeId)} - metadata={{ scopeId }} - /> - )} - </> - ); -}; - -SummaryValueCell.displayName = 'SummaryValueCell'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts index 40611748c69c8..d9bec6670766b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/use_action_cell_data_provider.ts @@ -11,6 +11,8 @@ import type { Filter } from '@kbn/es-query'; import { escapeDataProviderId } from '@kbn/securitysolution-t-grid'; import { isArray, isEmpty, isString } from 'lodash/fp'; import { useMemo } from 'react'; +import type { FieldSpec } from '@kbn/data-plugin/common'; + import { AGENT_STATUS_FIELD_NAME, EVENT_MODULE_FIELD_NAME, @@ -29,7 +31,6 @@ import { EVENT_DURATION_FIELD_NAME } from '../../../../timelines/components/dura import { getDisplayValue } from '../../../../timelines/components/timeline/data_providers/helpers'; import { PORT_NAMES } from '../../../../explore/network/components/port/helpers'; import { INDICATOR_REFERENCE } from '../../../../../common/cti/constants'; -import type { BrowserField } from '../../../containers/source'; import type { DataProvider, DataProvidersAnd, QueryOperator } from '../../../../../common/types'; import { IS_OPERATOR } from '../../../../../common/types'; @@ -38,7 +39,7 @@ export interface UseActionCellDataProvider { eventId?: string; field: string; fieldFormat?: string; - fieldFromBrowserField?: Partial<BrowserField>; + fieldFromBrowserField?: Partial<FieldSpec>; fieldType?: string; isObjectArray?: boolean; linkValue?: string | null; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index 7ebeee21fd1ed..d4d129f57ae3a 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -29,25 +29,10 @@ export const RESPONSE_ACTIONS_VIEW = i18n.translate( } ); -export const FIELD = i18n.translate('xpack.securitySolution.eventDetails.field', { - defaultMessage: 'Field', -}); - -export const VALUE = i18n.translate('xpack.securitySolution.eventDetails.value', { - defaultMessage: 'Value', -}); - export const DESCRIPTION = i18n.translate('xpack.securitySolution.eventDetails.description', { defaultMessage: 'Description', }); -export const PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.eventDetails.filter.placeholder', - { - defaultMessage: 'Filter by Field, Value, or Description...', - } -); - export const AGENT_STATUS = i18n.translate('xpack.securitySolution.detections.alerts.agentStatus', { defaultMessage: 'Agent status', }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/types.ts b/x-pack/plugins/security_solution/public/common/components/event_details/types.ts index 87f72da37c8b7..7db13725d9eb3 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/types.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { BrowserField } from '../../containers/source'; +import type { FieldSpec } from '@kbn/data-plugin/common'; import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -export type EventFieldsData = BrowserField & TimelineEventsDetailsItem; +export type EventFieldsData = FieldSpec & TimelineEventsDetailsItem; export interface FieldsData { field: string; @@ -20,7 +20,7 @@ export interface FieldsData { export interface EnrichedFieldInfo { data: FieldsData | EventFieldsData; eventId: string; - fieldFromBrowserField?: Partial<BrowserField>; + fieldFromBrowserField?: Partial<FieldSpec>; scopeId: string; values: string[] | null | undefined; linkValue?: string; diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx index 141f04b17e1b2..368fda2500fe1 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx @@ -13,7 +13,7 @@ import type { Filter } from '@kbn/es-query'; import { dataTableActions } from '@kbn/securitysolution-data-table'; import type { TableId } from '@kbn/securitysolution-data-table'; import type { CustomBulkAction } from '../../../../common/types'; -import { RowRendererId } from '../../../../common/api/timeline'; +import { RowRendererValues } from '../../../../common/api/timeline'; import { StatefulEventsViewer } from '../events_viewer'; import { eventsDefaultModel } from '../events_viewer/default_model'; import { MatrixHistogram } from '../matrix_histogram'; @@ -131,7 +131,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = const defaultModel = useMemo( () => ({ ...eventsDefaultModel, - excludedRowRendererIds: showExternalAlerts ? Object.values(RowRendererId) : [], + excludedRowRendererIds: showExternalAlerts ? RowRendererValues : [], }), [showExternalAlerts] ); diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx index d3299d0816594..98dfc83e9d3e8 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { render, screen, waitFor } from '@testing-library/react'; import type { ComponentProps } from 'react'; import React from 'react'; @@ -35,7 +35,7 @@ const toggleShowNotesMock = jest.fn(); const renderTestComponent = (props: Partial<ComponentProps<typeof AddEventNoteAction>> = {}) => { const localProps: ComponentProps<typeof AddEventNoteAction> = { - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, eventId: 'event-1', ariaLabel: 'Add Note', toggleShowNotes: toggleShowNotesMock, @@ -79,7 +79,7 @@ describe('AddEventNoteAction', () => { ariaLabel: 'Add Note', 'data-test-subj': 'add-note', isDisabled: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, toggleShowNotes: expect.any(Function), toolTip: '2 Notes available. Click to view them & add more.', eventId: 'event-1', @@ -101,7 +101,7 @@ describe('AddEventNoteAction', () => { ariaLabel: 'Add Note', 'data-test-subj': 'add-note', isDisabled: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, toggleShowNotes: expect.any(Function), toolTip: '1 Note available. Click to view it & add more.', eventId: 'event-2', @@ -123,7 +123,7 @@ describe('AddEventNoteAction', () => { ariaLabel: 'Add Note', 'data-test-subj': 'add-note', isDisabled: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, toggleShowNotes: expect.any(Function), toolTip: 'Add Note', eventId: 'event-3', diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx index ff9ad479e89c9..f9539b9062331 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/add_note_icon_item.tsx @@ -7,7 +7,7 @@ import React, { useMemo } from 'react'; import { NotesButton } from '../../../timelines/components/timeline/properties/helpers'; -import { TimelineType } from '../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../common/api/timeline'; import { useUserPrivileges } from '../user_privileges'; import * as i18n from './translations'; import { ActionIconItem } from './action_icon_item'; @@ -46,7 +46,7 @@ const AddEventNoteActionComponent: React.FC<AddEventNoteActionProps> = ({ timelineType={timelineType} toggleShowNotes={toggleShowNotes} toolTip={ - timelineType === TimelineType.template ? i18n.NOTES_DISABLE_TOOLTIP : NOTES_TOOLTIP + timelineType === TimelineTypeEnum.template ? i18n.NOTES_DISABLE_TOOLTIP : NOTES_TOOLTIP } eventId={eventId} notesCount={notesCount} diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/pin_event_action.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/pin_event_action.test.tsx index 1e30fdc8868bd..33358264d6417 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/pin_event_action.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/pin_event_action.test.tsx @@ -12,7 +12,7 @@ import { PinEventAction } from './pin_event_action'; import { useUserPrivileges } from '../user_privileges'; import { getEndpointPrivilegesInitialStateMock } from '../user_privileges/endpoint/mocks'; import { TestProviders } from '../../mock'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; jest.mock('../user_privileges'); const useUserPrivilegesMock = useUserPrivileges as jest.Mock; @@ -36,7 +36,7 @@ describe('PinEventAction', () => { noteIds={[]} onPinClicked={jest.fn} eventIsPinned={false} - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </TestProviders> ); @@ -57,7 +57,7 @@ describe('PinEventAction', () => { noteIds={[]} onPinClicked={jest.fn} eventIsPinned={false} - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.test.tsx new file mode 100644 index 0000000000000..e9a9c068b0926 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.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; 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 { DataIngestionHubHeader } from '.'; +import darkRocket from '../images/dark_rocket.png'; +import { useCurrentUser } from '../../../../lib/kibana'; + +jest.mock('../../../../lib/kibana', () => ({ + useCurrentUser: jest.fn(), + useEuiTheme: jest.fn(() => ({ euiTheme: { colorTheme: 'DARK' } })), +})); + +const mockUseCurrentUser = useCurrentUser as jest.Mock; + +describe('WelcomeHeaderComponent', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render fullName when fullName is provided', () => { + const fullName = 'John Doe'; + mockUseCurrentUser.mockReturnValue({ fullName }); + const { getByText } = render(<DataIngestionHubHeader />); + const titleElement = getByText(`Hi ${fullName}!`); + expect(titleElement).toBeInTheDocument(); + }); + + it('should render username when fullName is an empty string', () => { + const fullName = ''; + const username = 'jd'; + mockUseCurrentUser.mockReturnValue({ fullName, username }); + + const { getByText } = render(<DataIngestionHubHeader />); + const titleElement = getByText(`Hi ${username}!`); + expect(titleElement).toBeInTheDocument(); + }); + + it('should render username when fullName is not provided', () => { + const username = 'jd'; + mockUseCurrentUser.mockReturnValue({ username }); + + const { getByText } = render(<DataIngestionHubHeader />); + const titleElement = getByText(`Hi ${username}!`); + expect(titleElement).toBeInTheDocument(); + }); + + it('should not render the greeting message if both fullName and username are not available', () => { + mockUseCurrentUser.mockReturnValue({}); + + const { queryByTestId } = render(<DataIngestionHubHeader />); + const greetings = queryByTestId('data-ingestion-hub-header-greetings'); + expect(greetings).not.toBeInTheDocument(); + }); + + it('should render subtitle', () => { + const { getByText } = render(<DataIngestionHubHeader />); + const subtitleElement = getByText('Welcome to Elastic Security'); + expect(subtitleElement).toBeInTheDocument(); + }); + + it('should render description', () => { + const { getByText } = render(<DataIngestionHubHeader />); + const descriptionElement = getByText('Follow these steps to set up your workspace.'); + expect(descriptionElement).toBeInTheDocument(); + }); + + it('should render the rocket dark image when the theme is DARK', () => { + const { queryByTestId } = render(<DataIngestionHubHeader />); + const image = queryByTestId('data-ingestion-hub-header-image'); + expect(image).toHaveStyle({ backgroundImage: `url(${darkRocket})` }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.tsx new file mode 100644 index 0000000000000..167029c5cc431 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/data_ingestion_hub_header/index.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import classnames from 'classnames'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { + GET_STARTED_PAGE_TITLE, + GET_STARTED_DATA_INGESTION_HUB_DESCRIPTION, + GET_STARTED_DATA_INGESTION_HUB_SUBTITLE, +} from '../translations'; +import { useCurrentUser } from '../../../../lib/kibana'; +import { useDataIngestionHubHeaderStyles } from '../styles/data_ingestion_hub_header.styles'; + +const DataIngestionHubHeaderComponent: React.FC = () => { + const userName = useCurrentUser(); + + const { + headerContentStyles, + headerImageStyles, + headerTitleStyles, + headerSubtitleStyles, + headerDescriptionStyles, + } = useDataIngestionHubHeaderStyles(); + + // Full name could be null, user name should always exist + const name = userName?.fullName || userName?.username; + + const headerSubtitleClassNames = classnames('eui-displayBlock', headerSubtitleStyles); + const headerDescriptionClassNames = classnames('eui-displayBlock', headerDescriptionStyles); + + return ( + <EuiFlexGroup + data-test-subj="data-ingestion-hub-header" + justifyContent="center" + alignItems="center" + > + <EuiFlexItem + data-test-subj="data-ingestion-hub-header-image" + grow={false} + className={headerImageStyles} + /> + <EuiFlexItem grow={false} className={headerContentStyles}> + {name && ( + <EuiTitle + size="l" + className={headerTitleStyles} + data-test-subj="data-ingestion-hub-header-greetings" + > + <span>{GET_STARTED_PAGE_TITLE(name)}</span> + </EuiTitle> + )} + <EuiSpacer size="s" /> + <h1 className={headerSubtitleClassNames}>{GET_STARTED_DATA_INGESTION_HUB_SUBTITLE}</h1> + <EuiSpacer size="s" /> + <span className={headerDescriptionClassNames}> + {GET_STARTED_DATA_INGESTION_HUB_DESCRIPTION} + </span> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +export const DataIngestionHubHeader = React.memo(DataIngestionHubHeaderComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts index 6bf7ff9cced91..a99cdea4faf28 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts @@ -21,6 +21,8 @@ import { CreateProjectSteps, QuickStartSectionCardsId } from './types'; export const CONTENT_WIDTH = 1150; +export const IMG_HEADER_WIDTH = 128; + export const DEFAULT_FINISHED_STEPS: Partial<Record<CardId, StepId[]>> = { [QuickStartSectionCardsId.createFirstProject]: [CreateProjectSteps.createFirstProject], }; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/dark_rocket.png b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/dark_rocket.png new file mode 100644 index 0000000000000..a4d3ba73a8a02 Binary files /dev/null and b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/dark_rocket.png differ diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/rocket.png b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/rocket.png new file mode 100644 index 0000000000000..0d899f1c5e201 Binary files /dev/null and b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/images/rocket.png differ diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx index fb7b6b4c9ec6b..feb77b0e64ab3 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx @@ -17,10 +17,13 @@ import { ProductLine, ProductTier } from './configs'; import { useCurrentUser, useKibana } from '../../../lib/kibana'; import type { AppContextTestRender } from '../../../mock/endpoint'; import { createAppRootMockRenderer } from '../../../mock/endpoint'; +import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; jest.mock('./toggle_panel'); jest.mock('../../../lib/kibana'); - +jest.mock('../../../hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(false), +})); (useCurrentUser as jest.Mock).mockReturnValue({ fullName: 'UserFullName' }); describe('OnboardingComponent', () => { @@ -41,6 +44,7 @@ describe('OnboardingComponent', () => { }; beforeEach(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); mockedContext = createAppRootMockRenderer(); render = () => (renderResult = mockedContext.render(<OnboardingComponent {...props} />)); }); @@ -72,7 +76,25 @@ describe('OnboardingComponent', () => { expect(welcomeHeader).toBeInTheDocument(); expect(togglePanel).toBeInTheDocument(); }); + + it('should render dataIngestionHubHeader if dataIngestionHubEnabled flag is true', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + + render(); + + const dataIngestionHubHeader = renderResult.getByTestId('data-ingestion-hub-header'); + + expect(dataIngestionHubHeader).toBeInTheDocument(); + }); + describe('AVC 2024 Results banner', () => { + beforeEach(() => { + (useKibana().services.storage.get as jest.Mock).mockReturnValue(true); + }); + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); it('should render on the page', () => { render(); expect(renderResult.getByTestId('avcResultsBanner')).toBeTruthy(); @@ -82,7 +104,7 @@ describe('OnboardingComponent', () => { render(); expect(renderResult.getByTestId('avcReadTheBlog')).toHaveAttribute( 'href', - 'https://www.elastic.co/blog/elastic-security-malware-protection-test-av-comparatives' + 'https://www.elastic.co/blog/elastic-av-comparatives-business-security-test' ); }); @@ -95,10 +117,23 @@ describe('OnboardingComponent', () => { false ); }); + it('should stay dismissed if it has been closed once', () => { - (useKibana().services.storage.get as jest.Mock).mockReturnValue(false); + (useKibana().services.storage.get as jest.Mock).mockReturnValueOnce(false); + render(); + expect(renderResult.queryByTestId('avcResultsBanner')).toBeNull(); + }); + + it('should not be shown if the current date is January 1, 2025', () => { + jest.useFakeTimers().setSystemTime(new Date('2025-01-01T05:00:00.000Z')); render(); expect(renderResult.queryByTestId('avcResultsBanner')).toBeNull(); + jest.useRealTimers(); + }); + it('should be shown if the current date is before January 1, 2025', () => { + jest.useFakeTimers().setSystemTime(new Date('2024-12-31T05:00:00.000Z')); + render(); + expect(renderResult.queryByTestId('avcResultsBanner')).toBeTruthy(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx index 571d4e59a89e6..8451cf2178f49 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx @@ -6,7 +6,7 @@ */ import React, { useCallback, useMemo, useState } from 'react'; -import { AVCResultsBanner2024 } from '@kbn/avc-banner'; +import { AVCResultsBanner2024, useIsStillYear2024 } from '@kbn/avc-banner'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { TogglePanel } from './toggle_panel'; @@ -16,6 +16,7 @@ import { Progress } from './progress_bar'; import { StepContextProvider } from './context/step_context'; import { CONTENT_WIDTH } from './helpers'; import { WelcomeHeader } from './welcome_header'; +import { DataIngestionHubHeader } from './data_ingestion_hub_header'; import { Footer } from './footer'; import { useScrollToHash } from './hooks/use_scroll'; import type { SecurityProductTypes } from './configs'; @@ -25,6 +26,7 @@ import type { StepId } from './types'; import { useOnboardingStyles } from './styles/onboarding.styles'; import { useKibana } from '../../../lib/kibana'; import type { OnboardingHubStepLinkClickedParams } from '../../../lib/telemetry/events/onboarding/types'; +import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; interface OnboardingProps { indicesExist?: boolean; @@ -56,7 +58,7 @@ export const OnboardingComponent: React.FC<OnboardingProps> = ({ productTypes?.find((product) => product.product_line === ProductLine.security)?.product_tier, [productTypes] ); - const { wrapperStyles, progressSectionStyles, stepsSectionStyles, bannerStyles } = + const { wrapperStyles, headerSectionStyles, progressSectionStyles, stepsSectionStyles } = useOnboardingStyles(); const { telemetry, storage } = useKibana().services; const onStepLinkClicked = useCallback( @@ -65,6 +67,7 @@ export const OnboardingComponent: React.FC<OnboardingProps> = ({ }, [telemetry] ); + const isDataIngestionHubEnabled = useIsExperimentalFeatureEnabled('dataIngestionHubEnabled'); const [showAVCBanner, setShowAVCBanner] = useState( storage.get('securitySolution.showAvcBanner') ?? true @@ -76,15 +79,34 @@ export const OnboardingComponent: React.FC<OnboardingProps> = ({ useScrollToHash(); + const renderDataIngestionHubHeader = useMemo( + () => + isDataIngestionHubEnabled ? ( + <DataIngestionHubHeader /> + ) : ( + <WelcomeHeader productTier={productTier} /> + ), + [isDataIngestionHubEnabled, productTier] + ); + + const kibanaPageTemplateSectionStyles = useMemo( + () => (isDataIngestionHubEnabled ? headerSectionStyles : ''), + [headerSectionStyles, isDataIngestionHubEnabled] + ); + return ( <div className={wrapperStyles}> - {showAVCBanner && ( - <KibanaPageTemplate.Section paddingSize="none" className={bannerStyles}> + {useIsStillYear2024() && showAVCBanner && ( + <KibanaPageTemplate.Section paddingSize="none"> <AVCResultsBanner2024 onDismiss={onBannerDismiss} /> </KibanaPageTemplate.Section> )} - <KibanaPageTemplate.Section restrictWidth={CONTENT_WIDTH} paddingSize="xl"> - <WelcomeHeader productTier={productTier} /> + <KibanaPageTemplate.Section + className={kibanaPageTemplateSectionStyles} + restrictWidth={CONTENT_WIDTH} + paddingSize="xl" + > + {renderDataIngestionHubHeader} </KibanaPageTemplate.Section> <KibanaPageTemplate.Section restrictWidth={CONTENT_WIDTH} diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/data_ingestion_hub_header.styles.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/data_ingestion_hub_header.styles.ts new file mode 100644 index 0000000000000..807d02ebec2b3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/data_ingestion_hub_header.styles.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 { useEuiTheme, COLOR_MODES_STANDARD } from '@elastic/eui'; +import { css } from '@emotion/css'; +import { useMemo } from 'react'; +import { CONTENT_WIDTH, IMG_HEADER_WIDTH } from '../helpers'; +import rocket from '../images/rocket.png'; +import darkRocket from '../images/dark_rocket.png'; + +export const useDataIngestionHubHeaderStyles = () => { + const { euiTheme, colorMode } = useEuiTheme(); + + const headerBackgroundImage = useMemo( + () => (colorMode === COLOR_MODES_STANDARD.dark ? darkRocket : rocket), + [colorMode] + ); + + const dataIngestionHubHeaderStyles = useMemo(() => { + return { + headerImageStyles: css({ + backgroundImage: `url(${headerBackgroundImage})`, + backgroundSize: 'contain', + backgroundRepeat: 'no-repeat', + backgroundPositionX: 'center', + backgroundPositionY: 'center', + width: `${IMG_HEADER_WIDTH}px`, + height: `${IMG_HEADER_WIDTH}px`, + }), + headerTitleStyles: css({ + fontSize: `${euiTheme.base}px`, + color: euiTheme.colors.darkShade, + fontWeight: euiTheme.font.weight.bold, + lineHeight: euiTheme.size.l, + }), + headerSubtitleStyles: css({ + fontSize: `${euiTheme.base * 2.125}px`, + color: euiTheme.colors.title, + fontWeight: euiTheme.font.weight.bold, + lineHeight: euiTheme.size.xxl, + }), + headerDescriptionStyles: css({ + fontSize: `${euiTheme.base}px`, + color: euiTheme.colors.subduedText, + lineHeight: euiTheme.size.l, + fontWeight: euiTheme.font.weight.regular, + }), + headerContentStyles: css({ + width: `${CONTENT_WIDTH / 2}px`, + }), + }; + }, [ + euiTheme.base, + euiTheme.colors.darkShade, + euiTheme.colors.subduedText, + euiTheme.colors.title, + euiTheme.font.weight.bold, + euiTheme.font.weight.regular, + euiTheme.size.l, + euiTheme.size.xxl, + headerBackgroundImage, + ]); + return dataIngestionHubHeaderStyles; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts index 4f2eb59f06bf2..a16b736dd7efd 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/onboarding.styles.ts @@ -15,7 +15,10 @@ export const useOnboardingStyles = () => { return useMemo( () => ({ wrapperStyles: css({ - margin: `0 -${euiTheme.size.l}`, + margin: `-${euiTheme.size.l} -${euiTheme.size.l}`, + }), + headerSectionStyles: css({ + backgroundColor: euiTheme.colors.lightestShade, }), progressSectionStyles: css({ backgroundColor: euiTheme.colors.lightestShade, @@ -25,9 +28,6 @@ export const useOnboardingStyles = () => { padding: `0 ${euiTheme.size.xxl} ${euiTheme.size.xxxl}`, backgroundColor: euiTheme.colors.lightestShade, }), - bannerStyles: css({ - margin: `-${euiTheme.size.l} 0`, - }), }), [ euiTheme.colors.lightestShade, diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts index bad766fc41a4d..27471327b72fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts @@ -27,6 +27,20 @@ export const GET_STARTED_PAGE_DESCRIPTION = i18n.translate( } ); +export const GET_STARTED_DATA_INGESTION_HUB_SUBTITLE = i18n.translate( + 'xpack.securitySolution.onboarding.subTitle', + { + defaultMessage: `Welcome to Elastic Security`, + } +); + +export const GET_STARTED_DATA_INGESTION_HUB_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.onboarding.description', + { + defaultMessage: `Follow these steps to set up your workspace.`, + } +); + export const CURRENT_PLAN_LABEL = i18n.translate( 'xpack.securitySolution.onboarding.currentPlan.label', { diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx index 98f47334ceca7..3fa8cd2be11bc 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx @@ -6,10 +6,10 @@ */ import { isEmpty } from 'lodash/fp'; -import type { TimelineTypeLiteral } from '../../../../common/api/timeline'; +import type { TimelineType } from '../../../../common/api/timeline'; import { appendSearch } from './helpers'; -export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral | 'notes', search?: string) => +export const getTimelineTabsUrl = (tabName: TimelineType | 'notes', search?: string) => `/${tabName}${appendSearch(search)}`; export const getTimelineUrl = (id: string, graphEventId?: string) => diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.ts index a1976ea75c255..45a4a9f329bc3 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_data_providers.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import type { Filter } from '@kbn/es-query'; import { FILTERS, BooleanRelation, FilterStateStore } from '@kbn/es-query'; import type { QueryOperator, DataProvider } from '@kbn/timelines-plugin/common'; -import { DataProviderType } from '../../../../../../common/api/timeline'; +import { DataProviderTypeEnum } from '../../../../../../common/api/timeline'; import { replaceParamsQuery } from './replace_params_query'; import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; import { @@ -64,7 +64,7 @@ const dataProviderStub: DataProvider = { name: '', excluded: false, kqlQuery: '', - type: DataProviderType.default, + type: DataProviderTypeEnum.default, queryMatch: { field: '', value: '', @@ -88,7 +88,7 @@ const buildDataProviders = ( name: field, excluded, kqlQuery: '', - type: isTemplate ? DataProviderType.template : DataProviderType.default, + type: isTemplate ? DataProviderTypeEnum.template : DataProviderTypeEnum.default, queryMatch: { field, value: result, @@ -102,7 +102,7 @@ const buildDataProviders = ( name: field, excluded, kqlQuery: '', - type: isTemplate ? DataProviderType.template : DataProviderType.default, + type: isTemplate ? DataProviderTypeEnum.template : DataProviderTypeEnum.default, queryMatch: { field, value: result, diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts index 1a7e04ee3b0cc..82474c2d381de 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.test.ts @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; import type { QueryOperator } from '@kbn/timelines-plugin/common'; -import { DataProviderType } from '../../../../../../common/api/timeline'; +import { DataProviderTypeEnum } from '../../../../../../common/api/timeline'; import { useInsightQuery } from './use_insight_query'; import { TestProviders } from '../../../../mock'; import type { UseInsightQuery, UseInsightQueryResult } from './use_insight_query'; @@ -19,7 +19,7 @@ const mockProvider = { name: 'test', excluded: false, kqlQuery: '', - type: DataProviderType.default, + type: DataProviderTypeEnum.default, queryMatch: { field: 'event.id', value: '*', diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.ts index ad0f27aa1568e..43323a6b62f7a 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/insight/use_insight_query.ts @@ -18,6 +18,9 @@ import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; import type { TimeRange } from '../../../../store/inputs/model'; +const fields = ['*']; +const runtimeMappings = {}; + export interface UseInsightQuery { dataProviders: DataProvider[]; filters: Filter[]; @@ -67,16 +70,16 @@ export const useInsightQuery = ({ const [dataLoadingState, { events, totalCount }] = useTimelineEvents({ dataViewId, - fields: ['*'], + fields, filterQuery: combinedQueries?.filterQuery, id: TimelineId.active, indexNames: selectedPatterns, language: 'kuery', limit: 1, - runtimeMappings: {}, - ...(relativeTimerange - ? { startDate: relativeTimerange?.from, endDate: relativeTimerange?.to } - : {}), + runtimeMappings, + startDate: relativeTimerange?.from, + endDate: relativeTimerange?.to, + fetchNotes: false, }); const isQueryLoading = useMemo( diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/plugin.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/plugin.tsx index c11bc6ff26186..8350bf7b3f5d5 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/plugin.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/timeline/plugin.tsx @@ -9,7 +9,7 @@ import React, { useCallback, memo } from 'react'; import type { EuiSelectableOption, EuiMarkdownEditorUiPlugin } from '@elastic/eui'; import { EuiModalBody, EuiModalHeader, EuiCodeBlock } from '@elastic/eui'; -import { TimelineType } from '../../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../../common/api/timeline'; import { SelectableTimeline } from '../../../../../timelines/components/timeline/selectable_timeline'; import type { OpenTimelineResult } from '../../../../../timelines/components/open_timeline/types'; import { getTimelineUrl, useFormatUrl } from '../../../link_to'; @@ -66,7 +66,7 @@ const TimelineEditorComponent: React.FC<TimelineEditorProps> = ({ onClosePopover getSelectableOptions={handleGetSelectableOptions} onTimelineChange={handleTimelineChange} onClosePopover={onClosePopover} - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </EuiModalBody> </> diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts index af64560e58878..139acb0473c8b 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts @@ -7,10 +7,10 @@ import { useMemo } from 'react'; import type { DataViewFieldBase } from '@kbn/es-query'; +import type { FieldSpec } from '@kbn/data-plugin/common'; import { getTermsAggregationFields } from '../../../../detection_engine/rule_creation_ui/components/step_define_rule/utils'; import { useRuleFields } from '../../../../detection_engine/rule_management/logic/use_rule_fields'; -import type { BrowserField } from '../../../containers/source'; import { useMlCapabilities } from './use_ml_capabilities'; import { useMlRuleValidations } from './use_ml_rule_validations'; import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; @@ -21,7 +21,7 @@ export interface UseMlRuleConfigReturn { hasMlLicense: boolean; loading: boolean; mlFields: DataViewFieldBase[]; - mlSuppressionFields: BrowserField[]; + mlSuppressionFields: FieldSpec[]; allJobsStarted: boolean; } @@ -46,7 +46,7 @@ export const useMLRuleConfig = ({ machineLearningJobId, }); const mlSuppressionFields = useMemo( - () => getTermsAggregationFields(mlFields as BrowserField[]), + () => getTermsAggregationFields(mlFields as FieldSpec[]), [mlFields] ); 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 a711e8e8e222d..039860093e423 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 @@ -180,7 +180,7 @@ export const QueryBar = memo<QueryBarComponentProps>( timeHistory={timeHistory} dataTestSubj={dataTestSubj} savedQuery={savedQuery} - displayStyle={displayStyle} + displayStyle={isEsql ? 'withBorders' : displayStyle} isDisabled={isDisabled} /> ); diff --git a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx index a2cbc508da56e..19e783064b763 100644 --- a/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/super_date_picker/index.test.tsx @@ -103,12 +103,15 @@ describe('SIEM Super Date Picker', () => { </ReduxStoreProvider> ); wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); - wrapper.find('button.euiQuickSelect__applyButton').first().simulate('click'); + wrapper + .find('button[data-test-subj="superDatePickerQuickSelectApplyButton"]') + .first() + .simulate('click'); wrapper.update(); }); @@ -123,15 +126,14 @@ describe('SIEM Super Date Picker', () => { test('Make Sure it is Today date is an absolute date', () => { wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); wrapper - .find('[data-test-subj="superDatePickerCommonlyUsed_Today"]') + .find('button[data-test-subj="superDatePickerCommonlyUsed_Today"]') .first() - .find('button') .simulate('click'); wrapper.update(); expect(store.getState().inputs.global.timerange.kind).toBe('absolute'); @@ -139,7 +141,7 @@ describe('SIEM Super Date Picker', () => { test('Make Sure it is This Week date is an absolute date', () => { wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); @@ -173,53 +175,57 @@ describe('SIEM Super Date Picker', () => { </ReduxStoreProvider> ); wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); wrapper - .find('[data-test-subj="superDatePickerCommonlyUsed_Today"]') + .find('button[data-test-subj="superDatePickerCommonlyUsed_Today"]') .first() - .find('button') .simulate('click'); wrapper.update(); }); test('Today is in Recently used date ranges', () => { - expect(wrapper.find('div.euiQuickSelectPopover__section').at(1).text()).toBe('Today'); + expect( + wrapper.find('div[className*="euiQuickSelectPanel__section"]').at(1).text() + ).toContain('Today'); }); test('Today and "Last ${x} hours" where ${x} is in hours are in Recently used date ranges', () => { wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); - wrapper.find('button.euiQuickSelect__applyButton').first().simulate('click'); + wrapper + .find('button[data-test-subj="superDatePickerQuickSelectApplyButton"]') + .first() + .simulate('click'); wrapper.update(); - expect(wrapper.find('div.euiQuickSelectPopover__section').at(1).text()).toMatch( - /^Last\s[0-9]+\s(.)+Today/ - ); + const ranges = wrapper.find('div[className*="euiQuickSelectPanel__section"]').at(1).text(); + expect(ranges).toContain('Today'); + expect(ranges).toContain('Last 24 hours'); }); test('Make sure that it does not add any duplicate if you click again on today', () => { wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); wrapper - .find('[data-test-subj="superDatePickerCommonlyUsed_Today"]') + .find('button[data-test-subj="superDatePickerCommonlyUsed_Today"]') .first() - .find('button') .simulate('click'); wrapper.update(); - expect(wrapper.find('div.euiQuickSelectPopover__section').at(1).text()).toBe('Today'); + const text = wrapper.find('div[className*="euiQuickSelectPanel__section"]').at(1).text(); + expect(text.match(/Today/g)).toHaveLength(1); }); }); @@ -236,15 +242,14 @@ describe('SIEM Super Date Picker', () => { </ReduxStoreProvider> ); wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); wrapper - .find('[data-test-subj="superDatePickerToggleRefreshButton"]') + .find('button[data-test-subj="superDatePickerToggleRefreshButton"]') .first() - .find('button') .simulate('click'); wrapper.update(); @@ -266,15 +271,14 @@ describe('SIEM Super Date Picker', () => { test('Make sure we can stop the stream live', () => { wrapper - .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .find('button[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() .simulate('click'); wrapper.update(); wrapper - .find('[data-test-subj="superDatePickerToggleRefreshButton"]') + .find('button[data-test-subj="superDatePickerToggleRefreshButton"]') .first() - .find('button') .simulate('click'); wrapper.update(); diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.ts deleted file mode 100644 index 736a170382dae..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/api.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 type { Observable } from 'rxjs'; -import { filter } from 'rxjs'; - -import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { isRunningResponse } from '@kbn/data-plugin/common'; -import type { EventEnrichmentRequestOptionsInput } from '../../../../../common/api/search_strategy'; -import type { CtiEventEnrichmentStrategyResponse } from '../../../../../common/search_strategy/security_solution/cti'; -import { CtiQueries } from '../../../../../common/search_strategy/security_solution/cti'; - -type GetEventEnrichmentProps = Omit<EventEnrichmentRequestOptionsInput, 'factoryQueryType'> & { - data: DataPublicPluginStart; - signal: AbortSignal; -}; - -export const getEventEnrichment = ({ - data, - defaultIndex, - eventFields, - filterQuery, - timerange, - signal, -}: GetEventEnrichmentProps): Observable<CtiEventEnrichmentStrategyResponse> => - data.search.search<EventEnrichmentRequestOptionsInput, CtiEventEnrichmentStrategyResponse>( - { - defaultIndex, - eventFields, - factoryQueryType: CtiQueries.eventEnrichment, - filterQuery, - timerange, - }, - { - strategy: 'securitySolutionSearchStrategy', - abortSignal: signal, - } - ); - -export const getEventEnrichmentComplete = ( - props: GetEventEnrichmentProps -): Observable<CtiEventEnrichmentStrategyResponse> => - getEventEnrichment(props).pipe(filter((response) => !isRunningResponse(response))); diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/index.ts deleted file mode 100644 index e8fb1a03045d9..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/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 './use_event_enrichment'; -export * from './use_investigation_enrichment'; diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.ts deleted file mode 100644 index ff9130b288fa8..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/translations.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. - */ - -import { i18n } from '@kbn/i18n'; - -export const INVESTIGATION_ENRICHMENT_REQUEST_ERROR = i18n.translate( - 'xpack.securitySolution.investigationEnrichment.requestError', - { - defaultMessage: `An error occurred while requesting threat intelligence`, - } -); diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.ts deleted file mode 100644 index b8565f9d3e13e..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_event_enrichment.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. - */ - -import { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; - -import { getEventEnrichmentComplete } from './api'; - -const getEventEnrichmentCompleteWithOptionalSignal = withOptionalSignal(getEventEnrichmentComplete); - -export const useEventEnrichmentComplete = () => - useObservable(getEventEnrichmentCompleteWithOptionalSignal); diff --git a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts b/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.ts deleted file mode 100644 index c8a91c41cb847..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/cti/event_enrichment/use_investigation_enrichment.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 { useCallback, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { isEmpty, isEqual } from 'lodash'; -import usePrevious from 'react-use/lib/usePrevious'; - -import { InputsModelId } from '../../../store/inputs/constants'; -import type { EventFields } from '../../../../../common/search_strategy/security_solution/cti'; -import { - DEFAULT_EVENT_ENRICHMENT_FROM, - DEFAULT_EVENT_ENRICHMENT_TO, -} from '../../../../../common/cti/constants'; -import { useAppToasts } from '../../../hooks/use_app_toasts'; -import { useKibana } from '../../../lib/kibana'; -import { inputsActions } from '../../../store/actions'; -import * as i18n from './translations'; -import { useEventEnrichmentComplete } from '.'; -import { DEFAULT_THREAT_INDEX_KEY } from '../../../../../common/constants'; - -export const QUERY_ID = 'investigation_time_enrichment'; -const noop = () => {}; -const noEnrichments = { enrichments: [] }; - -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 -export const useInvestigationTimeEnrichment = (eventFields: EventFields) => { - const { addError } = useAppToasts(); - const { data, uiSettings } = useKibana().services; - const defaultThreatIndices = uiSettings.get<string[]>(DEFAULT_THREAT_INDEX_KEY); - - const dispatch = useDispatch(); - - const [range, setRange] = useState({ - from: DEFAULT_EVENT_ENRICHMENT_FROM, - to: DEFAULT_EVENT_ENRICHMENT_TO, - }); - - const { error, loading, result, start } = useEventEnrichmentComplete(); - - const deleteQuery = useCallback(() => { - dispatch(inputsActions.deleteOneQuery({ inputId: InputsModelId.global, id: QUERY_ID })); - }, [dispatch]); - - useEffect(() => { - if (!loading && result) { - dispatch( - inputsActions.setQuery({ - inputId: InputsModelId.global, - id: QUERY_ID, - inspect: { - dsl: result.inspect.dsl, - response: [JSON.stringify(result.rawResponse, null, 2)], - }, - loading, - refetch: noop, - }) - ); - } - - return deleteQuery; - }, [deleteQuery, dispatch, loading, result]); - - useEffect(() => { - if (error) { - addError(error, { title: i18n.INVESTIGATION_ENRICHMENT_REQUEST_ERROR }); - } - }, [addError, error]); - - const prevEventFields = usePrevious(eventFields); - const prevRange = usePrevious(range); - - useEffect(() => { - if ( - !isEmpty(eventFields) && - (!isEqual(eventFields, prevEventFields) || !isEqual(range, prevRange)) - ) { - start({ - data, - timerange: { ...range, interval: '' }, - defaultIndex: defaultThreatIndices, - eventFields, - filterQuery: '', - }); - } - }, [start, data, eventFields, prevEventFields, range, prevRange, defaultThreatIndices]); - - return { - result: isEmpty(eventFields) ? noEnrichments : result, - range, - setRange, - loading, - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx index c5251dba05744..761dad997c8ce 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx @@ -100,7 +100,6 @@ describe('source/index.tsx', () => { const { type: sourceType, payload } = mockDispatch.mock.calls[1][0]; expect(sourceType).toEqual('x-pack/security_solution/local/sourcerer/SET_DATA_VIEW'); expect(payload.id).toEqual('neato'); - expect(payload.indexFields).toHaveLength(mocksSource.indexFields.length); }); it('should reuse the result for dataView info when cleanCache not passed', async () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index 5e02ca2e11c58..f305088ef3d32 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -9,8 +9,8 @@ import { isEmpty, isEqual, keyBy, pick } from 'lodash/fp'; import memoizeOne from 'memoize-one'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { DataViewBase } from '@kbn/es-query'; -import type { BrowserField, BrowserFields } from '@kbn/timelines-plugin/common'; -import type { IIndexPatternFieldList } from '@kbn/data-views-plugin/common'; +import type { BrowserFields } from '@kbn/timelines-plugin/common'; +import type { FieldSpec, IIndexPatternFieldList } from '@kbn/data-views-plugin/common'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; import { useKibana } from '../../lib/kibana'; @@ -19,10 +19,10 @@ import { getDataViewStateFromIndexFields } from './use_data_view'; import { useAppToasts } from '../../hooks/use_app_toasts'; import type { ENDPOINT_FIELDS_SEARCH_STRATEGY } from '../../../../common/endpoint/constants'; -export type { BrowserField, BrowserFields }; +export type { BrowserFields }; -export function getAllBrowserFields(browserFields: BrowserFields): Array<Partial<BrowserField>> { - const result: Array<Partial<BrowserField>> = []; +export function getAllBrowserFields(browserFields: BrowserFields): Array<Partial<FieldSpec>> { + const result: Array<Partial<FieldSpec>> = []; for (const namespace of Object.values(browserFields)) { if (namespace.fields) { result.push(...Object.values(namespace.fields)); @@ -38,8 +38,7 @@ export function getAllBrowserFields(browserFields: BrowserFields): Array<Partial */ export const getAllFieldsByName = ( browserFields: BrowserFields -): { [fieldName: string]: Partial<BrowserField> } => - keyBy('name', getAllBrowserFields(browserFields)); +): { [fieldName: string]: Partial<FieldSpec> } => keyBy('name', getAllBrowserFields(browserFields)); export const getIndexFields = memoizeOne( (title: string, fields: IIndexPatternFieldList): DataViewBase => diff --git a/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx index ed577d1fd2374..f363a4c079238 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/use_data_view.tsx @@ -9,9 +9,10 @@ import { useCallback, useRef } from 'react'; import type { Subscription } from 'rxjs'; import { useDispatch } from 'react-redux'; import memoizeOne from 'memoize-one'; -import type { BrowserField, BrowserFields } from '@kbn/timelines-plugin/common'; +import type { BrowserFields } from '@kbn/timelines-plugin/common'; import { getCategory } from '@kbn/triggers-actions-ui-plugin/public'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; +import type { FieldCategory } from '@kbn/timelines-plugin/common/search_strategy'; import { useKibana } from '../../lib/kibana'; import { sourcererActions } from '../../../sourcerer/store'; @@ -28,10 +29,7 @@ export type IndexFieldSearch = (param: { skipScopeUpdate?: boolean; }) => Promise<void>; -type DangerCastForBrowserFieldsMutation = Record< - string, - Omit<BrowserField, 'fields'> & { fields: Record<string, BrowserField> } ->; +type DangerCastForBrowserFieldsMutation = Record<string, FieldCategory>; interface DataViewInfo { /** * @deprecated use fields list on dataview / "indexPattern" @@ -62,7 +60,7 @@ export const getDataViewStateFromIndexFields = memoizeOne( } const categoryFields = browserFields[category].fields; if (categoryFields) { - categoryFields[name] = field as BrowserField; + categoryFields[name] = field; } } return { browserFields: browserFields as DangerCastForBrowserFieldsMutation }; diff --git a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts index be0fb1d175118..033c0a8c2a0b7 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts @@ -12,7 +12,7 @@ import { useHistory } from 'react-router-dom'; import { useShowTimelineForGivenPath } from '../../utils/timeline/use_show_timeline_for_path'; import type { TimelineId } from '../../../../common/types'; import { TimelineTabs } from '../../../../common/types'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../common/api/timeline'; import { useKibana } from '../../lib/kibana'; import { useDeepEqualSelector } from '../use_selector'; import { APP_ID, APP_PATH } from '../../../../common/constants'; @@ -82,7 +82,7 @@ export const useTimelineSavePrompt = ( if ( !getIsTimelineVisible(relativePath) && - (changed || (timelineStatus === TimelineStatus.draft && updated != null)) + (changed || (timelineStatus === TimelineStatusEnum.draft && updated != null)) ) { confirmSaveTimeline(); } else { @@ -111,7 +111,7 @@ export const useTimelineSavePrompt = ( // Confirm when the user has made any changes to a timeline if ( !(nextAppId ?? '').includes(APP_ID) && - (changed || (timelineStatus === TimelineStatus.draft && updated != null)) + (changed || (timelineStatus === TimelineStatusEnum.draft && updated != null)) ) { return actions.confirm( UNSAVED_TIMELINE_SAVE_PROMPT, diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts index dc119de848dec..678c0d63e4be6 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts @@ -6,9 +6,10 @@ */ import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../../common/endpoint/types'; + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../../common/endpoint/types'; import { KibanaServices } from '../../kibana'; import { ISOLATE_HOST_ROUTE_V2, @@ -19,7 +20,7 @@ import { /** Isolates a Host running either elastic endpoint or fleet agent */ export const isolateHost = async ( - params: HostIsolationRequestBody + params: IsolationRouteRequestBody ): Promise<ResponseActionApiResponse> => { return KibanaServices.get().http.post<ResponseActionApiResponse>(ISOLATE_HOST_ROUTE_V2, { body: JSON.stringify(params), @@ -29,7 +30,7 @@ export const isolateHost = async ( /** Un-isolates a Host running either elastic endpoint or fleet agent */ export const unIsolateHost = async ( - params: HostIsolationRequestBody + params: UnisolationRouteRequestBody ): Promise<ResponseActionApiResponse> => { return KibanaServices.get().http.post<ResponseActionApiResponse>(UNISOLATE_HOST_ROUTE_V2, { body: JSON.stringify(params), diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts index 4881fc3f1569f..bd65a1de60612 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts @@ -5,10 +5,8 @@ * 2.0. */ -import type { - HostIsolationRequestBody, - HostIsolationResponse, -} from '../../../../../common/endpoint/types'; +import type { IsolationRouteRequestBody } from '../../../../../common/api/endpoint'; +import type { HostIsolationResponse } from '../../../../../common/endpoint/types'; import type { ResponseProvidersInterface } from '../../../mock/endpoint/http_handler_mock_factory'; import { httpHandlerMockFactory } from '../../../mock/endpoint/http_handler_mock_factory'; import { @@ -16,7 +14,7 @@ import { UNISOLATE_HOST_ROUTE_V2, } from '../../../../../common/endpoint/constants'; -export const hostIsolationRequestBodyMock = (): HostIsolationRequestBody => { +export const hostIsolationRequestBodyMock = (): IsolationRouteRequestBody => { return { endpoint_ids: ['88c04a90-b19c-11eb-b838-222'], alert_ids: ['88c04a90-b19c-11eb-b838-333'], diff --git a/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts b/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts index 5018abe6bdf62..b3f98a8483f30 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kuery/index.ts @@ -17,7 +17,7 @@ import memoizeOne from 'memoize-one'; import { prepareKQLParam } from '../../../../common/utils/kql'; import type { BrowserFields } from '../../../../common/search_strategy'; import type { DataProvider, DataProvidersAnd } from '../../../../common/types'; -import { DataProviderType } from '../../../../common/api/timeline'; +import { DataProviderTypeEnum } from '../../../../common/api/timeline'; import { EXISTS_OPERATOR } from '../../../../common/types/timeline'; export type PrimitiveOrArrayOfPrimitives = @@ -125,7 +125,7 @@ const buildQueryMatch = ( ) => `${dataProvider.excluded ? 'NOT ' : ''}${ dataProvider.queryMatch.operator !== EXISTS_OPERATOR && - dataProvider.type !== DataProviderType.template + dataProvider.type !== DataProviderTypeEnum.template ? checkIfFieldTypeIsNested(dataProvider.queryMatch.field, browserFields) ? convertNestedFieldToQuery( dataProvider.queryMatch.field, diff --git a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts index b8cb7c04f469c..960ec21f41ef4 100644 --- a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts @@ -6,10 +6,10 @@ */ import type { - ResponseActionApiResponse, KillProcessRequestBody, SuspendProcessRequestBody, -} from '../../../../common/endpoint/types'; +} from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { KibanaServices } from '../kibana'; import { KILL_PROCESS_ROUTE, SUSPEND_PROCESS_ROUTE } from '../../../../common/endpoint/constants'; 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 5a073684ef23d..7b399e8848c78 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 @@ -6,7 +6,7 @@ */ import { TableId } from '@kbn/securitysolution-data-table'; -import type { DataViewSpec, FieldSpec } from '@kbn/data-views-plugin/public'; +import type { DataViewSpec } from '@kbn/data-views-plugin/public'; import { ReqStatus } from '../../notes/store/notes.slice'; import { HostsFields } from '../../../common/api/search_strategy/hosts/model/sort'; import { InputsModelId } from '../store/inputs/constants'; @@ -34,7 +34,7 @@ import { } from '../../../common/constants'; import { networkModel } from '../../explore/network/store'; import { TimelineTabs, TimelineId } from '../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../common/api/timeline'; import { mockManagementState } from '../../management/store/reducer'; import type { ManagementState } from '../../management/types'; import { initialSourcererState, SourcererScopeName } from '../../sourcerer/store/model'; @@ -47,7 +47,6 @@ import { initialGroupingState } from '../store/grouping/reducer'; import type { SourcererState } from '../../sourcerer/store'; import { EMPTY_RESOLVER } from '../../resolver/store/helpers'; import { getMockDiscoverInTimelineState } from './mock_discover_state'; -import { initialState as dataViewPickerInitialState } from '../../sourcerer/experimental/redux/reducer'; const mockFieldMap: DataViewSpec['fields'] = Object.fromEntries( mockIndexFields.map((field) => [field.name, field]) @@ -60,7 +59,6 @@ export const mockSourcererState: SourcererState = { ...initialSourcererState.defaultDataView, browserFields: mockBrowserFields, id: DEFAULT_DATA_VIEW_ID, - indexFields: mockIndexFields as FieldSpec[], fields: mockFieldMap, loading: false, patternList: [...DEFAULT_INDEX_PATTERN, `${DEFAULT_SIGNALS_INDEX}-spacename`], @@ -365,7 +363,7 @@ export const mockGlobalState: State = { kqlQuery: { filterQuery: null }, loadingEventIds: [], title: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, noteIds: [], @@ -387,7 +385,7 @@ export const mockGlobalState: State = { sortDirection: 'desc', }, ], - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, version: null, selectedEventIds: {}, isSelectAllChecked: false, @@ -513,7 +511,6 @@ export const mockGlobalState: State = { */ management: mockManagementState as ManagementState, discover: getMockDiscoverInTimelineState(), - dataViewPicker: dataViewPickerInitialState, notes: { entities: { '1': { diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts index 2125c234765cb..427285c5f9233 100644 --- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts +++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts @@ -11,7 +11,11 @@ import type { DataTableModel } from '@kbn/securitysolution-data-table'; import { VIEW_SELECTION } from '../../../common/constants'; import type { TimelineResult } from '../../../common/api/timeline'; import { TimelineId, TimelineTabs } from '../../../common/types/timeline'; -import { RowRendererId, TimelineType, TimelineStatus } from '../../../common/api/timeline'; +import { + RowRendererIdEnum, + TimelineTypeEnum, + TimelineStatusEnum, +} from '../../../common/api/timeline'; import type { OpenTimelineResult } from '../../timelines/components/open_timeline/types'; import type { TimelineEventsDetailsItem } from '../../../common/search_strategy'; @@ -128,7 +132,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 1', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -242,7 +246,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 2', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -356,7 +360,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 2', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -470,7 +474,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 3', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -584,7 +588,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 4', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -698,7 +702,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 5', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -812,7 +816,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 6', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -926,7 +930,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 7', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -1040,7 +1044,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 7', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -1154,7 +1158,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 7', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -1268,7 +1272,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 7', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -1382,7 +1386,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 7', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -1496,7 +1500,7 @@ export const mockOpenTimelineQueryResults = { noteIds: ['308783f0-7b6d-11e9-980a-e5349fc014ef', '34ec1690-7b6d-11e9-980a-e5349fc014ef'], pinnedEventIds: ['Wl0W12oB9v5HJNSHb400', '410W12oB9v5HJNSHY4wv', 'ZF0W12oB9v5HJNSHwY6L'], title: 'test 7', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, created: 1558386787614, @@ -1920,9 +1924,9 @@ export const mockTimelineModel: TimelineModel = { sortDirection: Direction.desc, }, ], - status: TimelineStatus.active, + status: TimelineStatusEnum.active, title: 'Test rule', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, version: '1', @@ -2001,7 +2005,7 @@ export const mockGetOneTimelineResult: TimelineResult = { ], kqlMode: 'filter', title: 'Test rule', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, savedQueryId: null, @@ -2051,24 +2055,24 @@ export const defaultTimelineProps: CreateTimelineProps = { eventIdToNoteIds: {}, eventType: 'all', excludedRowRendererIds: [ - RowRendererId.alert, - RowRendererId.alerts, - RowRendererId.auditd, - RowRendererId.auditd_file, - RowRendererId.library, - RowRendererId.netflow, - RowRendererId.plain, - RowRendererId.registry, - RowRendererId.suricata, - RowRendererId.system, - RowRendererId.system_dns, - RowRendererId.system_endgame_process, - RowRendererId.system_file, - RowRendererId.system_fim, - RowRendererId.system_security_event, - RowRendererId.system_socket, - RowRendererId.threat_match, - RowRendererId.zeek, + RowRendererIdEnum.alert, + RowRendererIdEnum.alerts, + RowRendererIdEnum.auditd, + RowRendererIdEnum.auditd_file, + RowRendererIdEnum.library, + RowRendererIdEnum.netflow, + RowRendererIdEnum.plain, + RowRendererIdEnum.registry, + RowRendererIdEnum.suricata, + RowRendererIdEnum.system, + RowRendererIdEnum.system_dns, + RowRendererIdEnum.system_endgame_process, + RowRendererIdEnum.system_file, + RowRendererIdEnum.system_fim, + RowRendererIdEnum.system_security_event, + RowRendererIdEnum.system_socket, + RowRendererIdEnum.threat_match, + RowRendererIdEnum.zeek, ], filters: [], highlightedDropAndProviderId: '', @@ -2104,9 +2108,9 @@ export const defaultTimelineProps: CreateTimelineProps = { sortDirection: Direction.desc, }, ], - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, title: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineVersion: null, templateTimelineId: null, version: null, diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx b/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx index c04474ce5db4a..ec2143ea75854 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx @@ -13,7 +13,6 @@ import { useSourcererDataView } from '../../sourcerer/containers'; import { renderHook } from '@testing-library/react-hooks'; import { initialGroupingState } from './grouping/reducer'; import { initialAnalyzerState } from '../../resolver/store/helpers'; -import { initialState as dataViewPickerInitialState } from '../../sourcerer/experimental/redux/reducer'; import { initialNotesState } from '../../notes/store/notes.slice'; jest.mock('../hooks/use_selector'); @@ -72,7 +71,6 @@ describe('createInitialState', () => { { analyzer: initialAnalyzerState, }, - dataViewPickerInitialState, initialNotesState ); @@ -112,7 +110,6 @@ describe('createInitialState', () => { { analyzer: initialAnalyzerState, }, - dataViewPickerInitialState, initialNotesState ); const { result } = renderHook(() => useSourcererDataView(), { diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts index 3226c508b7078..ea684909a8776 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts @@ -35,10 +35,6 @@ import type { GroupState } from './grouping/types'; import { analyzerReducer } from '../../resolver/store/reducer'; import { securitySolutionDiscoverReducer } from './discover/reducer'; import type { AnalyzerState } from '../../resolver/types'; -import { - type DataviewPickerState, - reducer as dataviewPickerReducer, -} from '../../sourcerer/experimental/redux/reducer'; import type { NotesState } from '../../notes/store/notes.slice'; import { notesReducer } from '../../notes/store/notes.slice'; @@ -73,7 +69,6 @@ export const createInitialState = ( dataTableState: DataTableState, groupsState: GroupState, analyzerState: AnalyzerState, - dataviewPickerState: DataviewPickerState, notesState: NotesState ): State => { const initialPatterns = { @@ -136,7 +131,6 @@ export const createInitialState = ( internal: undefined, savedSearch: undefined, }, - dataViewPicker: dataviewPickerState, notes: notesState, }; @@ -156,7 +150,6 @@ export const createReducer: ( sourcerer: sourcererReducer, globalUrlParam: globalUrlParamReducer, dataTable: dataTableReducer, - dataViewPicker: dataviewPickerReducer, groups: groupsReducer, analyzer: analyzerReducer, discover: securitySolutionDiscoverReducer, 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 ed20e253db538..a73422ef8a4cb 100644 --- a/x-pack/plugins/security_solution/public/common/store/store.ts +++ b/x-pack/plugins/security_solution/public/common/store/store.ts @@ -21,7 +21,7 @@ import type { EnhancerOptions } from 'redux-devtools-extension'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; import type { CoreStart } from '@kbn/core/public'; import reduceReducers from 'reduce-reducers'; -import { TimelineType } from '../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../common/api/timeline'; import { TimelineId } from '../../../common/types'; import { initialGroupingState } from './grouping/reducer'; import type { GroupState } from './grouping/types'; @@ -55,11 +55,6 @@ import { dataAccessLayerFactory } from '../../resolver/data_access_layer/factory import { sourcererActions } from '../../sourcerer/store'; import { createMiddlewares } from './middlewares'; import { addNewTimeline } from '../../timelines/store/helpers'; -import { - reducer as dataViewPickerReducer, - initialState as dataViewPickerState, -} from '../../sourcerer/experimental/redux/reducer'; -import { listenerMiddleware } from '../../sourcerer/experimental/redux/listeners'; import { initialNotesState } from '../../notes/store/notes.slice'; let store: Store<State, Action> | null = null; @@ -122,7 +117,7 @@ export const createStoreFactory = async ( id: TimelineId.active, timelineById: {}, show: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, columns: [], dataViewId: null, indexNames: [], @@ -176,7 +171,6 @@ export const createStoreFactory = async ( dataTableInitialState, groupsInitialState, analyzerInitialState, - dataViewPickerState, initialNotesState ); @@ -184,14 +178,12 @@ export const createStoreFactory = async ( ...subPlugins.explore.store.reducer, timeline: timelineReducer, ...subPlugins.management.store.reducer, - dataViewPicker: dataViewPickerReducer, }; return createStore(initialState, rootReducer, coreStart, storage, [ ...(subPlugins.management.store.middleware ?? []), ...(subPlugins.explore.store.middleware ?? []), ...[resolverMiddlewareFactory(dataAccessLayerFactory(coreStart)) ?? []], - listenerMiddleware.middleware, ]); }; diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts index 8809ccc6ec0fa..bf83f9146bdb2 100644 --- a/x-pack/plugins/security_solution/public/common/store/types.ts +++ b/x-pack/plugins/security_solution/public/common/store/types.ts @@ -25,7 +25,6 @@ import type { GlobalUrlParam } from './global_url_param'; import type { GroupState } from './grouping/types'; import type { SecuritySolutionDiscoverState } from './discover/model'; import type { AnalyzerState } from '../../resolver/types'; -import { type DataviewPickerState } from '../../sourcerer/experimental/redux/reducer'; import type { NotesState } from '../../notes/store/notes.slice'; export type State = HostsPluginState & @@ -39,7 +38,6 @@ export type State = HostsPluginState & sourcerer: SourcererState; globalUrlParam: GlobalUrlParam; discover: SecuritySolutionDiscoverState; - dataViewPicker: DataviewPickerState; } & DataTableState & GroupState & AnalyzerState & { notes: NotesState }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx index ebf6b1bf0930c..b555054a75e0c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/components/step_rule_actions/index.tsx @@ -18,7 +18,6 @@ import { UseArray } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { RuleObjectId } from '../../../../../common/api/detection_engine/model/rule_schema'; import { isQueryRule } from '../../../../../common/detection_engine/utils'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { ResponseActionsForm } from '../../../rule_response_actions/response_actions_form'; import type { RuleStepProps, @@ -85,8 +84,6 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({ const { services: { application }, } = useKibana(); - const responseActionsEnabled = useIsExperimentalFeatureEnabled('responseActionsEnabled'); - const displayActionsOptions = useMemo( () => ( <> @@ -120,7 +117,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({ <DisplayActionsHeader /> {ruleId && <RuleSnoozeSection ruleId={ruleId} />} {displayActionsOptions} - {responseActionsEnabled && displayResponseActionsOptions} + {displayResponseActionsOptions} <UseField path="kibanaSiemAppUrl" component={GhostFormField} /> <UseField path="enabled" component={GhostFormField} /> </> @@ -134,7 +131,6 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({ application.capabilities.actions.show, displayActionsOptions, displayResponseActionsOptions, - responseActionsEnabled, ]); return ( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts index 07f14830d6a71..2fdd8cf8120d7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.test.ts @@ -5,82 +5,117 @@ * 2.0. */ +import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { parseEsqlQuery, computeHasMetadataOperator } from './esql_validator'; -import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; +import { isAggregatingQuery } from '@kbn/securitysolution-utils'; -jest.mock('@kbn/securitysolution-utils', () => ({ computeIsESQLQueryAggregating: jest.fn() })); +jest.mock('@kbn/securitysolution-utils', () => ({ isAggregatingQuery: jest.fn() })); -const computeIsESQLQueryAggregatingMock = computeIsESQLQueryAggregating as jest.Mock; +const isAggregatingQueryMock = isAggregatingQuery as jest.Mock; + +const getQeryAst = (query: string) => { + const { ast } = getAstAndSyntaxErrors(query); + return ast; +}; describe('computeHasMetadataOperator', () => { it('should be false if query does not have operator', () => { - expect(computeHasMetadataOperator('from test*')).toBe(false); - expect(computeHasMetadataOperator('from test* [metadata]')).toBe(false); - expect(computeHasMetadataOperator('from test* [metadata id]')).toBe(false); - expect(computeHasMetadataOperator('from metadata*')).toBe(false); - expect(computeHasMetadataOperator('from test* | keep metadata')).toBe(false); - expect(computeHasMetadataOperator('from test* | eval x="[metadata _id]"')).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test*'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata]'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata id]'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from metadata*'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* | keep metadata'))).toBe(false); + expect(computeHasMetadataOperator(getQeryAst('from test* | eval x="[metadata _id]"'))).toBe( + false + ); }); it('should be true if query has operator', () => { - expect(computeHasMetadataOperator('from test* metadata _id')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _id, _index')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _index, _id')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _id ')).toBe(true); - expect(computeHasMetadataOperator('from test* metadata _id | limit 10')).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id, _index'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _index, _id'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id '))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* metadata _id | limit 10'))).toBe( + true + ); expect( - computeHasMetadataOperator(`from packetbeat* metadata + computeHasMetadataOperator( + getQeryAst(`from packetbeat* metadata _id | limit 100`) + ) ).toBe(true); // still validates deprecated square bracket syntax - expect(computeHasMetadataOperator('from test* [metadata _id]')).toBe(true); - expect(computeHasMetadataOperator('from test* [metadata _id, _index]')).toBe(true); - expect(computeHasMetadataOperator('from test* [metadata _index, _id]')).toBe(true); - expect(computeHasMetadataOperator('from test* [ metadata _id ]')).toBe(true); - expect(computeHasMetadataOperator('from test* [ metadata _id] ')).toBe(true); - expect(computeHasMetadataOperator('from test* [ metadata _id] | limit 10')).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata _id]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata _id, _index]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [metadata _index, _id]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [ metadata _id ]'))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [ metadata _id] '))).toBe(true); + expect(computeHasMetadataOperator(getQeryAst('from test* [ metadata _id] | limit 10'))).toBe( + true + ); expect( - computeHasMetadataOperator(`from packetbeat* [metadata + computeHasMetadataOperator( + getQeryAst(`from packetbeat* [metadata _id ] | limit 100`) + ) ).toBe(true); }); }); describe('parseEsqlQuery', () => { it('returns isMissingMetadataOperator true when query is not aggregating and does not have metadata operator', () => { - computeIsESQLQueryAggregatingMock.mockReturnValueOnce(false); + isAggregatingQueryMock.mockReturnValueOnce(false); expect(parseEsqlQuery('from test*')).toEqual({ + errors: [], isEsqlQueryAggregating: false, isMissingMetadataOperator: true, }); }); it('returns isMissingMetadataOperator false when query is not aggregating and has metadata operator', () => { - computeIsESQLQueryAggregatingMock.mockReturnValueOnce(false); + isAggregatingQueryMock.mockReturnValueOnce(false); expect(parseEsqlQuery('from test* metadata _id')).toEqual({ + errors: [], isEsqlQueryAggregating: false, isMissingMetadataOperator: false, }); }); it('returns isMissingMetadataOperator false when query is aggregating', () => { - computeIsESQLQueryAggregatingMock.mockReturnValue(true); + isAggregatingQueryMock.mockReturnValue(true); expect(parseEsqlQuery('from test*')).toEqual({ + errors: [], isEsqlQueryAggregating: true, isMissingMetadataOperator: false, }); expect(parseEsqlQuery('from test* metadata _id')).toEqual({ + errors: [], isEsqlQueryAggregating: true, isMissingMetadataOperator: false, }); }); + + it('returns error when query is syntactically invalid', () => { + isAggregatingQueryMock.mockReturnValueOnce(false); + + expect(parseEsqlQuery('aaa bbbb ssdasd')).toEqual({ + errors: expect.arrayContaining([ + expect.objectContaining({ + message: + "SyntaxError: mismatched input 'aaa' expecting {'explain', 'from', 'meta', 'metrics', 'row', 'show'}", + }), + ]), + isEsqlQueryAggregating: false, + isMissingMetadataOperator: true, + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts index 484f78c53f0e0..869e379c21aed 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation/logic/esql_validator.ts @@ -7,8 +7,11 @@ import { isEmpty } from 'lodash'; import type { QueryClient } from '@tanstack/react-query'; -import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; +import { isAggregatingQuery } from '@kbn/securitysolution-utils'; +import type { ESQLAst } from '@kbn/esql-ast'; +import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; +import { isColumnItem, isOptionItem } from '@kbn/esql-validation-autocomplete'; import { KibanaServices } from '../../../common/lib/kibana'; import type { ValidationError, ValidationFunc } from '../../../shared_imports'; @@ -21,6 +24,7 @@ export type FieldType = 'string'; export enum ERROR_CODES { INVALID_ESQL = 'ERR_INVALID_ESQL', + INVALID_SYNTAX = 'ERR_INVALID_SYNTAX', ERR_MISSING_ID_FIELD_FROM_RESULT = 'ERR_MISSING_ID_FIELD_FROM_RESULT', } @@ -34,11 +38,52 @@ const constructValidationError = (error: Error) => { }; }; +const constructSyntaxError = (error: Error) => { + return { + code: ERROR_CODES.INVALID_SYNTAX, + message: error?.message + ? i18n.esqlValidationErrorMessage(error.message) + : i18n.ESQL_VALIDATION_UNKNOWN_ERROR, + error, + }; +}; + +const getMetadataOption = (ast: ESQLAst) => { + const fromCommand = ast.find((astItem) => astItem.type === 'command' && astItem.name === 'from'); + + if (!fromCommand?.args) { + return undefined; + } + + // Check whether the `from` command has `metadata` operator + for (const fromArg of fromCommand.args) { + if (isOptionItem(fromArg) && fromArg.name === 'metadata') { + return fromArg; + } + } + + return undefined; +}; + /** * checks whether query has metadata _id operator */ -export const computeHasMetadataOperator = (esqlQuery: string) => { - return /(?<!\|[\s\S.]*)\s*metadata[\s\S.]*_id[\s\S.]*/i.test(esqlQuery?.split('|')?.[0]); +export const computeHasMetadataOperator = (ast: ESQLAst) => { + // Check whether the `from` command has `metadata` operator + const metadataOption = getMetadataOption(ast); + if (!metadataOption) { + return false; + } + + // Check whether the `metadata` operator has `_id` argument + const idColumnItem = metadataOption.args.find( + (fromArg) => isColumnItem(fromArg) && fromArg.name === '_id' + ); + if (!idColumnItem) { + return false; + } + + return true; }; /** @@ -61,7 +106,12 @@ export const esqlValidator = async ( const queryClient = (customData.value as { queryClient: QueryClient | undefined })?.queryClient; const services = KibanaServices.get(); - const { isEsqlQueryAggregating, isMissingMetadataOperator } = parseEsqlQuery(query); + const { isEsqlQueryAggregating, isMissingMetadataOperator, errors } = parseEsqlQuery(query); + + // Check if there are any syntax errors + if (errors.length) { + return constructSyntaxError(new Error(errors[0].message)); + } if (isMissingMetadataOperator) { return { @@ -97,11 +147,14 @@ export const esqlValidator = async ( * - if it's non aggregation query it must have metadata operator */ export const parseEsqlQuery = (query: string) => { - const isEsqlQueryAggregating = computeIsESQLQueryAggregating(query); + const { ast, errors } = getAstAndSyntaxErrors(query); + + const isEsqlQueryAggregating = isAggregatingQuery(ast); return { + errors, isEsqlQueryAggregating, // non-aggregating query which does not have [metadata], is not a valid one - isMissingMetadataOperator: !isEsqlQueryAggregating && !computeHasMetadataOperator(query), + isMissingMetadataOperator: !isEsqlQueryAggregating && !computeHasMetadataOperator(ast), }; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 9797edf38baa1..1ef86c1cab1ed 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -24,7 +24,7 @@ import React, { memo, useCallback, useState, useEffect, useMemo, useRef } from ' import styled from 'styled-components'; import { i18n as i18nCore } from '@kbn/i18n'; import { isEqual, isEmpty } from 'lodash'; -import type { FieldSpec } from '@kbn/data-views-plugin/common'; +import type { FieldSpec } from '@kbn/data-plugin/common'; import usePrevious from 'react-use/lib/usePrevious'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { useQueryClient } from '@tanstack/react-query'; @@ -83,7 +83,6 @@ import { import { EqlQueryBar } from '../eql_query_bar'; import { DataViewSelector } from '../data_view_selector'; import { ThreatMatchInput } from '../threatmatch_input'; -import type { BrowserField } from '../../../../common/containers/source'; import { useFetchIndex } from '../../../../common/containers/source'; import { NewTermsFields } from '../new_terms_fields'; import { ScheduleItem } from '../../../rule_creation/components/schedule_item_form'; @@ -266,12 +265,12 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ [form] ); - const [aggFields, setAggregatableFields] = useState<BrowserField[]>([]); + const [aggFields, setAggregatableFields] = useState<FieldSpec[]>([]); useEffect(() => { const { fields } = indexPattern; /** - * Typecasting to BrowserField because fields is + * Typecasting to FieldSpec because fields is * typed as DataViewFieldBase[] which does not have * the 'aggregatable' property, however the type is incorrect * @@ -279,10 +278,10 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({ * We will need to determine where these types are defined and * figure out where the discrepency is. */ - setAggregatableFields(aggregatableFields(fields as BrowserField[])); + setAggregatableFields(aggregatableFields(fields as FieldSpec[])); }, [indexPattern]); - const termsAggregationFields: BrowserField[] = useMemo( + const termsAggregationFields: FieldSpec[] = useMemo( () => getTermsAggregationFields(aggFields), [aggFields] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts index 0c15a31a6d28d..9ac99b9583ae9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/utils.ts @@ -6,8 +6,7 @@ */ import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; - -import type { BrowserField } from '../../../../common/containers/source'; +import type { FieldSpec } from '@kbn/data-plugin/common'; import { CUSTOM_QUERY_REQUIRED, EQL_QUERY_REQUIRED, ESQL_QUERY_REQUIRED } from './translations'; @@ -19,7 +18,7 @@ import { isEqlRule, isEsqlRule } from '../../../../../common/detection_engine/ut * Keyword, Numeric, ip, boolean, or binary. * https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html */ -export const getTermsAggregationFields = (fields: BrowserField[]): BrowserField[] => { +export const getTermsAggregationFields = (fields: FieldSpec[]): FieldSpec[] => { // binary types is excluded, as binary field has property aggregatable === false const allowedTypesSet = new Set(['string', 'number', 'ip', 'boolean']); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts index 996b3ca044864..1a13f0ff8e3a2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts @@ -12,11 +12,9 @@ import { getESQLQueryColumns } from '@kbn/esql-utils'; import { useAllEsqlRuleFields } from './use_all_esql_rule_fields'; import { createQueryWrapperMock } from '../../../common/__mocks__/query_wrapper'; -import { parseEsqlQuery } from '../../rule_creation/logic/esql_validator'; +import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; -jest.mock('../../rule_creation/logic/esql_validator', () => ({ - parseEsqlQuery: jest.fn(), -})); +jest.mock('@kbn/securitysolution-utils', () => ({ computeIsESQLQueryAggregating: jest.fn() })); jest.mock('@kbn/esql-utils', () => { return { @@ -25,7 +23,7 @@ jest.mock('@kbn/esql-utils', () => { }; }); -const parseEsqlQueryMock = parseEsqlQuery as jest.Mock; +const computeIsESQLQueryAggregatingMock = computeIsESQLQueryAggregating as jest.Mock; const getESQLQueryColumnsMock = getESQLQueryColumns as jest.Mock; const { wrapper } = createQueryWrapperMock(); @@ -47,7 +45,8 @@ const mockEsqlDatatable = { columns: [{ id: '_custom_field', name: '_custom_field', meta: { type: 'string' } }], }; -describe('useAllEsqlRuleFields', () => { +// FLAKY: https://github.com/elastic/kibana/issues/190063 +describe.skip('useAllEsqlRuleFields', () => { beforeEach(() => { jest.clearAllMocks(); getESQLQueryColumnsMock.mockImplementation(({ esqlQuery }) => @@ -60,7 +59,7 @@ describe('useAllEsqlRuleFields', () => { : mockEsqlDatatable.columns ) ); - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: false }); + computeIsESQLQueryAggregatingMock.mockReturnValue(false); }); it('should return loading true when esql fields still loading', () => { @@ -103,7 +102,7 @@ describe('useAllEsqlRuleFields', () => { }); it('should return index pattern fields concatenated with ES|QL fields when ES|QL query is non-aggregating', async () => { - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: false }); + computeIsESQLQueryAggregatingMock.mockReturnValue(false); const { result, waitFor } = renderHook( () => @@ -126,7 +125,7 @@ describe('useAllEsqlRuleFields', () => { }); it('should return only ES|QL fields when ES|QL query is aggregating', async () => { - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: true }); + computeIsESQLQueryAggregatingMock.mockReturnValue(true); const { result, waitFor } = renderHook( () => @@ -148,7 +147,7 @@ describe('useAllEsqlRuleFields', () => { it('should deduplicate index pattern fields and ES|QL fields when fields have same name', async () => { // getESQLQueryColumnsMock.mockClear(); - parseEsqlQueryMock.mockReturnValue({ isEsqlQueryAggregating: false }); + computeIsESQLQueryAggregatingMock.mockReturnValue(false); const { result, waitFor } = renderHook( () => diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts index a67b990c88b80..80bfc364e5b51 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.ts @@ -13,7 +13,7 @@ import useDebounce from 'react-use/lib/useDebounce'; import { useQuery } from '@tanstack/react-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { parseEsqlQuery } from '../../rule_creation/logic/esql_validator'; +import { computeIsESQLQueryAggregating } from '@kbn/securitysolution-utils'; import { getEsqlQueryConfig } from '../../rule_creation/logic/get_esql_query_config'; @@ -89,8 +89,8 @@ export const useAllEsqlRuleFields: UseAllEsqlRuleFields = ({ esqlQuery, indexPat const [debouncedEsqlQuery, setDebouncedEsqlQuery] = useState<string | undefined>(undefined); const { fields: esqlFields, isLoading } = useEsqlFields(debouncedEsqlQuery); - const { isEsqlQueryAggregating } = useMemo( - () => parseEsqlQuery(debouncedEsqlQuery ?? ''), + const isEsqlQueryAggregating = useMemo( + () => computeIsESQLQueryAggregating(debouncedEsqlQuery ?? ''), [debouncedEsqlQuery] ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/helpers.test.ts new file mode 100644 index 0000000000000..d8a1823622bdf --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/helpers.test.ts @@ -0,0 +1,472 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; +import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +import { getRulesSchemaMock } from '../../../../../common/api/detection_engine/model/rule_schema/rule_response_schema.mock'; +import { isSubmitDisabled, prepareNewItemsForSubmission, prepareToCloseAlerts } from './helpers'; +import type { Rule } from '../../../rule_management/logic/types'; +import type { AlertData } from '../../utils/types'; + +const items = [ + { + ...getExceptionListItemSchemaMock(), + }, +]; + +const alertDataMock: AlertData = { + '@timestamp': '1234567890', + _id: 'test-id', + file: { path: 'test/path' }, +}; + +describe('add_exception_flyout#helpers', () => { + describe('isSubmitDisabled', () => { + it('returns true if "isSubmitting" is "true"', () => { + expect( + isSubmitDisabled({ + isSubmitting: true, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.RULE_DEFAULT, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if "isClosingAlerts" is "true"', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: true, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.RULE_DEFAULT, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if "itemConditionValidationErrorExists" is "true"', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: true, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.RULE_DEFAULT, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if "commentErrorExists" is "true"', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: true, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.RULE_DEFAULT, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if "expireErrorExists" is "true"', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: true, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.RULE_DEFAULT, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if item name is empty', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: ' ', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.DETECTION, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if error submitting exists', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: new Error('uh oh'), + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.DETECTION, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if all items do not include any entries', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: [ + { + ...getExceptionListItemSchemaMock(), + entries: [], + }, + ], + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.DETECTION, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeTruthy(); + }); + + it('returns true if exception is to be added to a list, but no list is specified', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.DETECTION, + exceptionListsToAddTo: [], + }) + ).toBeTruthy(); + }); + + it('returns true if exception is to be added to a rule but no rule is specified', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'select_rules_to_add_to', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.RULE_DEFAULT, + exceptionListsToAddTo: [], + }) + ).toBeTruthy(); + }); + + it('returns false if conditions are met for adding exception to a rule', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'select_rules_to_add_to', + selectedRulesToAddTo: [ + { + ...getRulesSchemaMock(), + exceptions_list: [], + } as Rule, + ], + listType: ExceptionListTypeEnum.RULE_DEFAULT, + exceptionListsToAddTo: [], + }) + ).toBeFalsy(); + }); + + it('returns false if conditions are met for adding exception to a list', () => { + expect( + isSubmitDisabled({ + isSubmitting: false, + isClosingAlerts: false, + errorSubmitting: null, + exceptionItemName: 'Item name', + exceptionItems: items, + itemConditionValidationErrorExists: false, + commentErrorExists: false, + expireErrorExists: false, + addExceptionToRadioSelection: 'add_to_lists', + selectedRulesToAddTo: [], + listType: ExceptionListTypeEnum.DETECTION, + exceptionListsToAddTo: [getExceptionListSchemaMock()], + }) + ).toBeFalsy(); + }); + }); + + // Doesn't explicitly test "enrichNewExceptionItems" used within helper as that function + // is covered with unit tests itself. + describe('prepareNewItemsForSubmission', () => { + it('returns "addToLists" true and the sharedListToAddTo lists to add to if correct radio selection and lists are referenced', () => { + const { addToRules, addToLists, listsToAddTo } = prepareNewItemsForSubmission({ + sharedListToAddTo: [getExceptionListSchemaMock()], + addExceptionToRadioSelection: 'add_to_lists', + exceptionListsToAddTo: [], + exceptionItemName: 'Test item', + newComment: '', + listType: ExceptionListTypeEnum.DETECTION, + osTypesSelection: [], + expireTime: undefined, + exceptionItems: [], + }); + + expect(addToLists).toBeTruthy(); + expect(addToRules).toBeFalsy(); + expect(listsToAddTo).toEqual([getExceptionListSchemaMock()]); + }); + + it('returns "addToLists" true and the exceptionListsToAddTo if correct radio selection and lists are referenced', () => { + const { addToRules, addToLists, listsToAddTo } = prepareNewItemsForSubmission({ + sharedListToAddTo: [], + addExceptionToRadioSelection: 'add_to_lists', + exceptionListsToAddTo: [getExceptionListSchemaMock()], + exceptionItemName: 'Test item', + newComment: '', + listType: ExceptionListTypeEnum.DETECTION, + osTypesSelection: [], + expireTime: undefined, + exceptionItems: [], + }); + + expect(addToLists).toBeTruthy(); + expect(addToRules).toBeFalsy(); + expect(listsToAddTo).toEqual([getExceptionListSchemaMock()]); + }); + + it('returns "addToLists" false if no exception lists are specified as the lists to add to', () => { + const { addToRules, addToLists, listsToAddTo } = prepareNewItemsForSubmission({ + sharedListToAddTo: [], + addExceptionToRadioSelection: 'add_to_lists', + exceptionListsToAddTo: [], + exceptionItemName: 'Test item', + newComment: '', + listType: ExceptionListTypeEnum.DETECTION, + osTypesSelection: [], + expireTime: undefined, + exceptionItems: [], + }); + + expect(addToLists).toBeFalsy(); + expect(addToRules).toBeFalsy(); + expect(listsToAddTo).toEqual([]); + }); + + it('returns "addToRules" true if radio selection is "add_to_rule"', () => { + const { addToRules, addToLists, listsToAddTo } = prepareNewItemsForSubmission({ + sharedListToAddTo: [], + addExceptionToRadioSelection: 'add_to_rule', + exceptionListsToAddTo: [], + exceptionItemName: 'Test item', + newComment: '', + listType: ExceptionListTypeEnum.DETECTION, + osTypesSelection: [], + expireTime: undefined, + exceptionItems: [], + }); + + expect(addToLists).toBeFalsy(); + expect(addToRules).toBeTruthy(); + expect(listsToAddTo).toEqual([]); + }); + + it('returns "addToRules" true if radio selection is "add_to_rules"', () => { + const { addToRules, addToLists, listsToAddTo } = prepareNewItemsForSubmission({ + sharedListToAddTo: [], + addExceptionToRadioSelection: 'add_to_rules', + exceptionListsToAddTo: [], + exceptionItemName: 'Test item', + newComment: '', + listType: ExceptionListTypeEnum.DETECTION, + osTypesSelection: [], + expireTime: undefined, + exceptionItems: [], + }); + + expect(addToLists).toBeFalsy(); + expect(addToRules).toBeTruthy(); + expect(listsToAddTo).toEqual([]); + }); + + it('returns "addToRules" true if radio selection is "select_rules_to_add_to"', () => { + const { addToRules, addToLists, listsToAddTo } = prepareNewItemsForSubmission({ + sharedListToAddTo: [], + addExceptionToRadioSelection: 'select_rules_to_add_to', + exceptionListsToAddTo: [], + exceptionItemName: 'Test item', + newComment: '', + listType: ExceptionListTypeEnum.DETECTION, + osTypesSelection: [], + expireTime: undefined, + exceptionItems: [], + }); + + expect(addToLists).toBeFalsy(); + expect(addToRules).toBeTruthy(); + expect(listsToAddTo).toEqual([]); + }); + }); + + describe('prepareToCloseAlerts', () => { + it('returns "shouldCloseAlerts" false if no rule ids defined', () => { + const { shouldCloseAlerts, ruleStaticIds } = prepareToCloseAlerts({ + alertData: undefined, + closeSingleAlert: false, + addToRules: true, + rules: [], + bulkCloseAlerts: true, + selectedRulesToAddTo: [], + }); + + expect(shouldCloseAlerts).toBeFalsy(); + expect(ruleStaticIds).toEqual([]); + }); + + it('returns "shouldCloseAlerts" false if neither closeSingleAlert or bulkCloseAlerts are true', () => { + const { shouldCloseAlerts, ruleStaticIds } = prepareToCloseAlerts({ + alertData: undefined, + closeSingleAlert: false, + addToRules: true, + rules: [], + bulkCloseAlerts: false, + selectedRulesToAddTo: [ + { + ...getRulesSchemaMock(), + exceptions_list: [], + } as Rule, + ], + }); + + expect(shouldCloseAlerts).toBeFalsy(); + expect(ruleStaticIds).toEqual(['query-rule-id']); + }); + + it('returns "alertIdToClose" if "alertData" defined and "closeSingleAlert" selected', () => { + const { alertIdToClose, ruleStaticIds } = prepareToCloseAlerts({ + alertData: alertDataMock, + closeSingleAlert: true, + addToRules: true, + rules: [], + bulkCloseAlerts: false, + selectedRulesToAddTo: [ + { + ...getRulesSchemaMock(), + exceptions_list: [], + } as Rule, + ], + }); + + expect(alertIdToClose).toEqual('test-id'); + expect(ruleStaticIds).toEqual(['query-rule-id']); + }); + + it('returns "alertIdToClose" of undefined if "alertData" defined but "closeSingleAlert" is not selected', () => { + const { alertIdToClose, ruleStaticIds } = prepareToCloseAlerts({ + alertData: alertDataMock, + closeSingleAlert: false, + addToRules: true, + rules: [], + bulkCloseAlerts: false, + selectedRulesToAddTo: [ + { + ...getRulesSchemaMock(), + exceptions_list: [], + } as Rule, + ], + }); + + expect(alertIdToClose).toBeUndefined(); + expect(ruleStaticIds).toEqual(['query-rule-id']); + }); + + it('returns rule ids from "rules" if "addToRules" is false', () => { + const { ruleStaticIds } = prepareToCloseAlerts({ + alertData: alertDataMock, + closeSingleAlert: false, + addToRules: false, + rules: [ + { + ...getRulesSchemaMock(), + exceptions_list: [], + } as Rule, + ], + bulkCloseAlerts: false, + selectedRulesToAddTo: [], + }); + + expect(ruleStaticIds).toEqual(['query-rule-id']); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/helpers.ts new file mode 100644 index 0000000000000..23d20f699780d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/helpers.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 type { Moment } from 'moment'; +import type { ExceptionListSchema, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import { isEmpty } from 'lodash/fp'; +import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution-list-utils'; + +import type { Rule } from '../../../rule_management/logic/types'; +import { enrichNewExceptionItems } from '../flyout_components/utils'; +import type { AlertData } from '../../utils/types'; + +const RULE_DEFAULT_OPTIONS = ['add_to_rule', 'add_to_rules', 'select_rules_to_add_to']; + +/** + * Determines whether add exception flyout submit button + * should be disabled. + * @param isSubmitting Is submition completed + * @param isClosingAlerts Waiting on close alerts actions to complete + * @param errorSubmitting Any submission errors + * @param exceptionItemName Item name + * @param exceptionItems Items to be created + * @param itemConditionValidationErrorExists Item conditions are invalid + * @param commentErrorExists Comment invalid or errors exist + * @param expireErrorExists Expire time invalid or error exists + * @param addExceptionToRadioSelection Radio selection value denoting whether to add item to lists or rules + * @param selectedRulesToAddTo List of rules item/s should be added to + * @param listType list type of the item being added + * @param exceptionListsToAddTo User selected exception lists to add item to + */ +export const isSubmitDisabled = ({ + isSubmitting, + isClosingAlerts, + errorSubmitting, + exceptionItemName, + exceptionItems, + itemConditionValidationErrorExists, + commentErrorExists, + expireErrorExists, + addExceptionToRadioSelection, + selectedRulesToAddTo, + listType, + exceptionListsToAddTo, +}: { + isSubmitting: boolean; + isClosingAlerts: boolean; + errorSubmitting: Error | null; + exceptionItemName: string; + exceptionItems: ExceptionsBuilderReturnExceptionItem[]; + itemConditionValidationErrorExists: boolean; + commentErrorExists: boolean; + expireErrorExists: boolean; + addExceptionToRadioSelection: string; + selectedRulesToAddTo: Rule[]; + listType: ExceptionListTypeEnum; + exceptionListsToAddTo: ExceptionListSchema[]; +}): boolean => { + return ( + isSubmitting || + isClosingAlerts || + errorSubmitting != null || + exceptionItemName.trim() === '' || + exceptionItems.every((item) => item.entries.length === 0) || + itemConditionValidationErrorExists || + commentErrorExists || + expireErrorExists || + (addExceptionToRadioSelection === 'add_to_lists' && isEmpty(exceptionListsToAddTo)) || + (addExceptionToRadioSelection === 'select_rules_to_add_to' && + isEmpty(selectedRulesToAddTo) && + listType === ExceptionListTypeEnum.RULE_DEFAULT) + ); +}; + +/** + * Helper method for determining if user has selected to add exception + * items to specific rules or to exception lists. It also returns the + * exception items enriched with various flyout values. + * @param sharedListToAddTo Exception list passed into add exception item flyout component + * @param addExceptionToRadioSelection Radio selection value denoting whether to add item to lists or rules + * @param exceptionListsToAddTo User selected exception lists to add item to + * @param exceptionItemName Item name + * @param newComment User added comment + * @param listType list type of the item being added + * @param osTypesSelection For endpoint exceptions, OS selected + * @param expireTime User defined item expire time + * @param exceptionItems Items to be added + */ +export const prepareNewItemsForSubmission = ({ + sharedListToAddTo, + addExceptionToRadioSelection, + exceptionListsToAddTo, + exceptionItemName, + newComment, + listType, + osTypesSelection, + expireTime, + exceptionItems, +}: { + sharedListToAddTo: ExceptionListSchema[] | undefined; + addExceptionToRadioSelection: string; + exceptionListsToAddTo: ExceptionListSchema[]; + exceptionItemName: string; + newComment: string; + listType: ExceptionListTypeEnum; + osTypesSelection: OsTypeArray; + expireTime: Moment | undefined; + exceptionItems: ExceptionsBuilderReturnExceptionItem[]; +}): { + listsToAddTo: ExceptionListSchema[]; + addToLists: boolean; + addToRules: boolean; + items: ExceptionsBuilderReturnExceptionItem[]; +} => { + const addToRules = RULE_DEFAULT_OPTIONS.includes(addExceptionToRadioSelection); + const addToLists = + !!sharedListToAddTo?.length || + (addExceptionToRadioSelection === 'add_to_lists' && !isEmpty(exceptionListsToAddTo)); + const listsToAddTo = sharedListToAddTo?.length ? sharedListToAddTo : exceptionListsToAddTo; + + const items = enrichNewExceptionItems({ + itemName: exceptionItemName, + commentToAdd: newComment, + addToRules, + addToSharedLists: addToLists, + sharedLists: listsToAddTo, + listType, + selectedOs: osTypesSelection, + expireTime, + items: exceptionItems, + }); + + return { + listsToAddTo, + addToLists, + addToRules, + items, + }; +}; + +/** + * Determine whether to close a single alert or bulk close + * alerts. Depending on the selection, need to know alert id + * and rule ids. + * @param alertData Alert the item is being added to + * @param closeSingleAlert User selected to close a single alert + * @param addToRules User selected to add item to 'x' rules + * @param rules Rules to determine which alerts to target when bulk closing + * @param bulkCloseAlerts User selected to close all alerts matching new exception + * @param selectedRulesToAddTo User selected rules to add item to + */ +export const prepareToCloseAlerts = ({ + alertData, + closeSingleAlert, + addToRules, + rules, + bulkCloseAlerts, + selectedRulesToAddTo, +}: { + alertData: AlertData | undefined; + closeSingleAlert: boolean; + addToRules: boolean; + rules: Rule[] | null; + bulkCloseAlerts: boolean; + selectedRulesToAddTo: Rule[]; +}): { + shouldCloseAlerts: boolean; + alertIdToClose: string | undefined; + ruleStaticIds: string[]; +} => { + const alertIdToClose = closeSingleAlert && alertData ? alertData._id : undefined; + const ruleStaticIds = addToRules + ? selectedRulesToAddTo.map(({ rule_id: ruleId }) => ruleId) + : (rules ?? []).map(({ rule_id: ruleId }) => ruleId); + + return { + shouldCloseAlerts: !isEmpty(ruleStaticIds) && (bulkCloseAlerts || closeSingleAlert), + alertIdToClose, + ruleStaticIds, + }; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx index 1f8251c510481..0d2e937931e21 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx @@ -6,14 +6,12 @@ */ import React from 'react'; -import type { ReactWrapper } from 'enzyme'; +import type { ReactWrapper, ShallowWrapper } from 'enzyme'; import { mount, shallow } from 'enzyme'; -import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; -import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; import type { EntriesArray, EntryMatch } from '@kbn/securitysolution-io-ts-list-types'; -import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; import { createStubIndexPattern, stubIndexPattern } from '@kbn/data-plugin/common/stubs'; @@ -24,7 +22,6 @@ import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import * as helpers from '../../utils/helpers'; import type { Rule } from '../../../rule_management/logic/types'; -import * as i18n from './translations'; import { TestProviders } from '../../../../common/mock'; @@ -33,23 +30,13 @@ import { getRulesSchemaMock, } from '../../../../../common/api/detection_engine/model/rule_schema/mocks'; import type { AlertData } from '../../utils/types'; -import { useFindRules } from '../../../rule_management/logic/use_find_rules'; -import { useFindExceptionListReferences } from '../../logic/use_find_references'; -import { MAX_COMMENT_LENGTH } from '../../../../../common/constants'; jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/containers/source'); jest.mock('../../logic/use_create_update_exception'); jest.mock('../../logic/use_exception_flyout_data'); -jest.mock('../../logic/use_find_references'); -jest.mock('@kbn/securitysolution-hook-utils', () => ({ - ...jest.requireActual('@kbn/securitysolution-hook-utils'), - useAsync: jest.fn(), -})); -jest.mock('../../../rule_management/logic/use_rule'); jest.mock('@kbn/lists-plugin/public'); -jest.mock('../../../rule_management/logic/use_find_rules'); jest.mock('../../../rule_management/api/hooks/use_fetch_rule_by_id_query'); const mockGetExceptionBuilderComponentLazy = getExceptionBuilderComponentLazy as jest.Mock< @@ -63,8 +50,6 @@ const mockFetchIndexPatterns = useFetchIndexPatterns as jest.Mock< >; const mockUseSignalIndex = useSignalIndex as jest.Mock<Partial<ReturnType<typeof useSignalIndex>>>; const mockUseFetchIndex = useFetchIndex as jest.Mock; -const mockUseFindRules = useFindRules as jest.Mock; -const mockUseFindExceptionListReferences = useFindExceptionListReferences as jest.Mock; const alertDataMock: AlertData = { '@timestamp': '1234567890', @@ -83,64 +68,11 @@ describe('When the add exception modal is opened', () => { defaultEndpointItems = jest.spyOn(helpers, 'defaultEndpointExceptionItems'); mockUseAddOrUpdateException.mockImplementation(() => [false, jest.fn()]); - mockFetchIndexPatterns.mockImplementation(() => ({ - isLoading: false, - indexPatterns: stubIndexPattern, - getExtendedFields: () => Promise.resolve([]), - })); mockUseSignalIndex.mockImplementation(() => ({ loading: false, signalIndexName: 'mock-siem-signals-index', })); - mockUseFetchIndex.mockImplementation(() => [ - false, - { - indexPatterns: stubIndexPattern, - }, - ]); - mockUseFindRules.mockImplementation(() => ({ - data: { - rules: [ - { - ...getRulesSchemaMock(), - exceptions_list: [], - } as Rule, - ], - total: 1, - }, - isFetched: true, - })); - mockUseFindExceptionListReferences.mockImplementation(() => [ - false, - false, - { - my_list_id: { - ...getExceptionListSchemaMock(), - id: '123', - list_id: 'my_list_id', - namespace_type: 'single', - type: ExceptionListTypeEnum.DETECTION, - name: 'My exception list', - referenced_rules: [ - { - id: '345', - name: 'My rule', - rule_id: 'my_rule_id', - exception_lists: [ - { - id: '123', - list_id: 'my_list_id', - namespace_type: 'single', - type: ExceptionListTypeEnum.DETECTION, - }, - ], - }, - ], - }, - }, - jest.fn(), - ]); }); afterEach(() => { @@ -148,7 +80,7 @@ describe('When the add exception modal is opened', () => { }); describe('when the modal is loading', () => { - let wrapper: ReactWrapper; + let wrapper: ShallowWrapper; beforeEach(() => { // Mocks one of the hooks as loading mockFetchIndexPatterns.mockImplementation(() => ({ @@ -157,33 +89,79 @@ describe('When the add exception modal is opened', () => { getExtendedFields: () => Promise.resolve([]), })); - wrapper = mount( - <TestProviders> + wrapper = shallow( + <AddExceptionFlyout + rules={[{ ...getRulesSchemaMock() } as Rule]} + isBulkAction={false} + alertData={undefined} + isAlertDataLoading={undefined} + alertStatus={undefined} + isEndpointItem={false} + showAlertCloseOptions + onCancel={jest.fn()} + onConfirm={jest.fn()} + /> + ); + }); + + it('should show the loading spinner', () => { + expect(wrapper.find('EuiSkeletonText').exists()).toBeTruthy(); + }); + }); + + describe('exception list type of "endpoint"', () => { + describe('common functionality to test regardless of alert input', () => { + let wrapper: ShallowWrapper; + beforeEach(async () => { + wrapper = shallow( <AddExceptionFlyout - rules={[{ ...getRulesSchemaMock() } as Rule]} + rules={[ + { + ...getRulesSchemaMock(), + index: ['filebeat-*'], + exceptions_list: [ + { + id: 'endpoint_list', + list_id: 'endpoint_list', + namespace_type: 'agnostic', + type: 'endpoint', + }, + ], + } as Rule, + ]} isBulkAction={false} alertData={undefined} isAlertDataLoading={undefined} alertStatus={undefined} - isEndpointItem={false} + isEndpointItem showAlertCloseOptions onCancel={jest.fn()} onConfirm={jest.fn()} /> - </TestProviders> - ); - }); + ); + }); - it('should show the loading spinner', () => { - expect(wrapper.find('[data-test-subj="loadingAddExceptionFlyout"]').exists()).toBeTruthy(); - }); - }); + it('should render item name input', () => { + expect(wrapper.find('ExceptionsFlyoutMeta').exists()).toBeTruthy(); + }); - describe('exception list type of "endpoint"', () => { - describe('common functionality to test regardless of alert input', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - wrapper = mount( + it('should render the exception builder', () => { + expect(wrapper.find('ExceptionsConditions').exists()).toBeTruthy(); + }); + + it('does NOT render options to add exception to a rule or shared list', () => { + expect(wrapper.find('ExceptionsAddToRulesOrLists').exists()).toBeFalsy(); + }); + + it('should show a warning callout if wildcard is used', async () => { + mockUseFetchIndex.mockImplementation(() => [ + false, + { + indexPatterns: stubIndexPattern, + }, + ]); + + const mountWrapper = mount( <TestProviders> <AddExceptionFlyout rules={[ @@ -211,44 +189,6 @@ describe('When the add exception modal is opened', () => { /> </TestProviders> ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) - ); - }); - - it('displays proper flyout and button text', () => { - expect(wrapper.find('[data-test-subj="exceptionFlyoutTitle"]').at(1).text()).toEqual( - i18n.ADD_ENDPOINT_EXCEPTION - ); - expect(wrapper.find('[data-test-subj="addExceptionConfirmButton"]').at(1).text()).toEqual( - i18n.ADD_ENDPOINT_EXCEPTION - ); - }); - - it('should render item name input', () => { - expect(wrapper.find('[data-test-subj="exceptionFlyoutNameInput"]').exists()).toBeTruthy(); - }); - - it('should render the exception builder', () => { - expect(wrapper.find('[data-test-subj="alertExceptionBuilder"]').exists()).toBeTruthy(); - }); - - it('does NOT render options to add exception to a rule or shared list', () => { - expect( - wrapper.find('[data-test-subj="exceptionItemAddToRuleOrListSection"]').exists() - ).toBeFalsy(); - }); - - it('should contain the endpoint specific documentation text', () => { - expect(wrapper.find('[data-test-subj="addExceptionEndpointText"]').exists()).toBeTruthy(); - }); - - it('should NOT display the eql sequence callout', () => { - expect(wrapper.find('[data-test-subj="eqlSequenceCallout"]').exists()).not.toBeTruthy(); - }); - - it('should show a warning callout if wildcard is used', async () => { const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; await waitFor(() => callProps.onChange({ @@ -268,16 +208,23 @@ describe('When the add exception modal is opened', () => { }) ); - wrapper.update(); + mountWrapper.update(); expect( - wrapper.find('[data-test-subj="wildcardWithWrongOperatorCallout"]').exists() + mountWrapper.find('[data-test-subj="wildcardWithWrongOperatorCallout"]').exists() ).toBeTruthy(); }); }); describe('alert data is passed in', () => { let wrapper: ReactWrapper; - beforeEach(async () => { + beforeAll(async () => { + mockUseFetchIndex.mockImplementation(() => [ + false, + { + indexPatterns: stubIndexPattern, + }, + ]); + wrapper = mount( <TestProviders> <AddExceptionFlyout @@ -471,50 +418,38 @@ describe('When the add exception modal is opened', () => { }); describe('alert data NOT passed in', () => { - let wrapper: ReactWrapper; + let wrapper: ShallowWrapper; beforeEach(async () => { - wrapper = mount( - <TestProviders> - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - index: ['filebeat-*'], - exceptions_list: [ - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - } as Rule, - ]} - isBulkAction={false} - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} - isEndpointItem - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) + wrapper = shallow( + <AddExceptionFlyout + rules={[ + { + ...getRulesSchemaMock(), + index: ['filebeat-*'], + exceptions_list: [ + { + id: 'endpoint_list', + list_id: 'endpoint_list', + namespace_type: 'agnostic', + type: 'endpoint', + }, + ], + } as Rule, + ]} + isBulkAction={false} + alertData={undefined} + isAlertDataLoading={undefined} + alertStatus={undefined} + isEndpointItem + showAlertCloseOptions + onCancel={jest.fn()} + onConfirm={jest.fn()} + /> ); }); - it('should NOT render the close single alert checkbox', () => { - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeFalsy(); - }); - it('should render the os selection dropdown', () => { - expect(wrapper.find('[data-test-subj="osSelectionDropdown"]').exists()).toBeTruthy(); + expect(wrapper.find('ExceptionsConditions').prop('showOsTypeOptions')).toBeTruthy(); }); }); }); @@ -522,7 +457,7 @@ describe('When the add exception modal is opened', () => { describe('exception list type is NOT "endpoint" ("rule_default" or "detection")', () => { describe('common features to test regardless of alert input', () => { let wrapper: ReactWrapper; - beforeEach(async () => { + beforeAll(async () => { wrapper = mount( <TestProviders> <AddExceptionFlyout @@ -549,27 +484,10 @@ describe('When the add exception modal is opened', () => { ); }); - it('displays proper flyout and button text', () => { - expect(wrapper.find('[data-test-subj="exceptionFlyoutTitle"]').at(1).text()).toEqual( - i18n.CREATE_RULE_EXCEPTION - ); - expect(wrapper.find('[data-test-subj="addExceptionConfirmButton"]').at(1).text()).toEqual( - i18n.CREATE_RULE_EXCEPTION - ); - }); - it('should NOT prepopulate items', () => { expect(defaultEndpointItems).not.toHaveBeenCalled(); }); - // button is disabled until there are exceptions, a name, and selection made on - // add to rule or lists section - it('has the add exception button disabled', () => { - expect( - wrapper.find('button[data-test-subj="addExceptionConfirmButton"]').getDOMNode() - ).toBeDisabled(); - }); - it('should render item name input', () => { expect(wrapper.find('[data-test-subj="exceptionFlyoutNameInput"]').exists()).toBeTruthy(); }); @@ -600,51 +518,6 @@ describe('When the add exception modal is opened', () => { }); }); - describe('alert data is passed in', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - wrapper = mount( - <TestProviders> - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - exceptions_list: [], - } as Rule, - ]} - isBulkAction={false} - alertData={alertDataMock} - isAlertDataLoading={false} - alertStatus="open" - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [getExceptionListItemSchemaMock()] }) - ); - }); - - it('should render the close single alert checkbox', () => { - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeTruthy(); - expect( - wrapper.find('input[data-test-subj="closeAlertOnAddExceptionCheckbox"]').getDOMNode() - ).not.toBeDisabled(); - }); - - it('should have the bulk close checkbox disabled', () => { - expect( - wrapper.find('input[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').getDOMNode() - ).toBeDisabled(); - }); - }); - describe('Auto populate rule exception', () => { beforeEach(() => { mockGetExceptionBuilderComponentLazy.mockImplementation((props) => { @@ -733,548 +606,123 @@ describe('When the add exception modal is opened', () => { expect(getAllByTestId('entryValue')[1]).toHaveTextContent('test/path'); }); }); - describe('bulk closeable alert data is passed in', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - mockUseFetchIndex.mockImplementation(() => [ - false, - { - indexPatterns: createStubIndexPattern({ - spec: { - id: '1234', - title: 'filebeat-*', - fields: { - 'event.code': { - name: 'event.code', - type: 'string', - aggregatable: true, - searchable: true, - }, - 'file.path.caseless': { - name: 'file.path.caseless', - type: 'string', - aggregatable: true, - searchable: true, - }, - subject_name: { - name: 'subject_name', - type: 'string', - aggregatable: true, - searchable: true, - }, - trusted: { - name: 'trusted', - type: 'string', - aggregatable: true, - searchable: true, - }, - 'file.hash.sha256': { - name: 'file.hash.sha256', - type: 'string', - aggregatable: true, - searchable: true, - }, - }, - }, - }), - }, - ]); - wrapper = mount( - <TestProviders> - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - index: ['filebeat-*'], - exceptions_list: [ - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - } as Rule, - ]} - isBulkAction={false} - alertData={alertDataMock} - isAlertDataLoading={false} - alertStatus="open" - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - </TestProviders> - ); - - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ - exceptionItems: [ - { - ...getExceptionListItemSchemaMock(), - entries: [ - { - field: 'file.hash.sha256', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - }, - ], - }) - ); - }); - - it('should render the close single alert checkbox', () => { - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeTruthy(); - expect( - wrapper.find('input[data-test-subj="closeAlertOnAddExceptionCheckbox"]').getDOMNode() - ).not.toBeDisabled(); - }); - - it('should have the bulk close checkbox enabled', () => { - expect( - wrapper.find('input[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').getDOMNode() - ).not.toBeDisabled(); - }); - - describe('when a "is in list" entry is added', () => { - it('should have the bulk close checkbox disabled', async () => { - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - - await waitFor(() => - callProps.onChange({ - exceptionItems: [ - ...callProps.exceptionListItems, - { - ...getExceptionListItemSchemaMock(), - entries: [ - { field: 'event.code', operator: 'included', type: 'list' }, - ] as EntriesArray, - }, - ], - }) - ); - - expect( - wrapper - .find('input[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]') - .getDOMNode() - ).toBeDisabled(); - }); - }); - }); - - describe('alert data NOT passed in', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - wrapper = mount( - <TestProviders> - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - index: ['filebeat-*'], - exceptions_list: [ - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - } as Rule, - ]} - isBulkAction={false} - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) - ); - }); - - it('should NOT render the close single alert checkbox', () => { - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeFalsy(); - }); - - it('should have the bulk close checkbox disabled', () => { - expect( - wrapper.find('input[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').getDOMNode() - ).toBeDisabled(); - }); - }); }); /* Say for example, from the lists management or lists details page */ describe('when no rules are passed in', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - wrapper = mount( - <TestProviders> - <AddExceptionFlyout - rules={null} - isBulkAction={false} - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [getExceptionListItemSchemaMock()] }) - ); - }); - - it('allows large value lists', () => { - expect(wrapper.find('ExceptionsConditions').prop('allowLargeValueLists')).toBeTruthy(); - }); - - it('defaults to selecting add to rule option, displaying rules selection table', () => { - expect(wrapper.find('[data-test-subj="addExceptionToRulesTable"]').exists()).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="selectRulesToAddToOptionRadio"] input').getDOMNode() - ).toHaveAttribute('checked'); - }); - }); - - /* Say for example, from the rule details page, exceptions tab, or from an alert */ - describe('when a single rule is passed in', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - wrapper = mount( - <TestProviders> - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - exceptions_list: [], - } as Rule, - ]} - isBulkAction={false} - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) - ); - }); - it('does not allow large value list selection for query rule', () => { - const shallowWrapper = shallow( - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - exceptions_list: [], - } as Rule, - ]} - isBulkAction={false} - alertData={alertDataMock} - isAlertDataLoading={false} - alertStatus="open" - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - ); - - expect(shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists')).toBeTruthy(); - }); - - it('does not allow large value list selection if EQL rule', () => { - const shallowWrapper = shallow( + let wrapper: ShallowWrapper; + beforeAll(async () => { + wrapper = shallow( <AddExceptionFlyout - rules={[ - { - ...getRulesEqlSchemaMock(), - exceptions_list: [], - } as Rule, - ]} + rules={null} isBulkAction={false} - alertData={alertDataMock} - isAlertDataLoading={false} - alertStatus="open" + alertData={undefined} + isAlertDataLoading={undefined} + alertStatus={undefined} isEndpointItem={false} showAlertCloseOptions onCancel={jest.fn()} onConfirm={jest.fn()} /> ); - - expect(shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists')).toBeFalsy(); }); - it('does not allow large value list selection if threshold rule', () => { - const shallowWrapper = shallow( - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - type: 'threshold', - } as Rule, - ]} - isBulkAction={false} - alertData={alertDataMock} - isAlertDataLoading={false} - alertStatus="open" - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - ); - - expect(shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists')).toBeFalsy(); + it('allows large value lists', () => { + expect(wrapper.find('ExceptionsConditions').prop('allowLargeValueLists')).toBeTruthy(); }); - it('does not allow large value list selection if new trems rule', () => { - const shallowWrapper = shallow( - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - type: 'new_terms', - } as Rule, - ]} - isBulkAction={false} - alertData={alertDataMock} - isAlertDataLoading={false} - alertStatus="open" - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> + it('defaults to selecting add to rule option, displaying rules selection table', () => { + expect(wrapper.find('ExceptionsAddToRulesOrLists').prop('selectedRadioOption')).toEqual( + 'select_rules_to_add_to' ); - - expect(shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists')).toBeFalsy(); - }); - - it('defaults to selecting add to rule radio option', () => { - expect( - wrapper.find('[data-test-subj="exceptionItemAddToRuleOrListSection"]').exists() - ).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="addToRuleOptionsRadio"] input').getDOMNode() - ).toBeChecked(); - }); - - it('disables add to shared lists option if rule has no shared exception lists attached already', () => { - expect( - wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() - ).toBeDisabled(); }); + }); - it('enables add to shared lists option if rule has shared list', () => { - wrapper = mount( - <TestProviders> + /* Say for example, from the rule details page, exceptions tab, or from an alert */ + describe('when a single rule is passed in', () => { + describe('large value list selection', () => { + it('does not allow large value list selection for query rule', () => { + const shallowWrapper = shallow( <AddExceptionFlyout rules={[ { ...getRulesSchemaMock(), - exceptions_list: [ - { - id: 'test', - list_id: 'test', - namespace_type: 'single', - type: 'detection', - }, - ], + exceptions_list: [], } as Rule, ]} isBulkAction={false} - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} + alertData={alertDataMock} + isAlertDataLoading={false} + alertStatus="open" isEndpointItem={false} showAlertCloseOptions onCancel={jest.fn()} onConfirm={jest.fn()} /> - </TestProviders> - ); + ); - expect( - wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() - ).toBeEnabled(); - }); - }); + expect( + shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists') + ).toBeTruthy(); + }); - /* Say for example, add exception item from rules bulk action */ - describe('when multiple rules are passed in - bulk action', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - wrapper = mount( - <TestProviders> + it('does not allow large value list selection if EQL rule', () => { + const shallowWrapper = shallow( <AddExceptionFlyout rules={[ { - ...getRulesSchemaMock(), + ...getRulesEqlSchemaMock(), exceptions_list: [], } as Rule, - { - ...getRulesSchemaMock(), - id: 'foo', - rule_id: 'foo', - exceptions_list: [ - { - id: 'bar', - list_id: 'bar', - namespace_type: 'single', - type: 'detection', - }, - ], - } as Rule, ]} - isBulkAction - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} + isBulkAction={false} + alertData={alertDataMock} + isAlertDataLoading={false} + alertStatus="open" isEndpointItem={false} showAlertCloseOptions onCancel={jest.fn()} onConfirm={jest.fn()} /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) - ); - }); - - it('allows large value lists', () => { - const shallowWrapper = shallow( - <AddExceptionFlyout - rules={[ - { - ...getRulesSchemaMock(), - exceptions_list: [], - } as Rule, - { - ...getRulesSchemaMock(), - id: 'foo', - rule_id: 'foo', - exceptions_list: [], - } as Rule, - ]} - isBulkAction - alertData={alertDataMock} - isAlertDataLoading={false} - alertStatus="open" - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - ); - - expect(shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists')).toBeTruthy(); - }); - - it('defaults to selecting add to rules radio option', () => { - expect( - wrapper.find('[data-test-subj="exceptionItemAddToRuleOrListSection"]').exists() - ).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="addToRulesOptionsRadio"] input').getDOMNode() - ).toBeChecked(); - }); + ); - it('disables add to shared lists option if rules have no shared lists in common', () => { - expect( - wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() - ).toBeDisabled(); - }); + expect( + shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists') + ).toBeFalsy(); + }); - it('enables add to shared lists option if rules have at least one shared list in common', () => { - wrapper = mount( - <TestProviders> + it('does not allow large value list selection if threshold rule', () => { + const shallowWrapper = shallow( <AddExceptionFlyout rules={[ { ...getRulesSchemaMock(), - exceptions_list: [ - { - id: 'bar', - list_id: 'bar', - namespace_type: 'single', - type: 'detection', - }, - ], - } as Rule, - { - ...getRulesSchemaMock(), - id: 'foo', - rule_id: 'foo', - exceptions_list: [ - { - id: 'bar', - list_id: 'bar', - namespace_type: 'single', - type: 'detection', - }, - ], + type: 'threshold', } as Rule, ]} - isBulkAction - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} + isBulkAction={false} + alertData={alertDataMock} + isAlertDataLoading={false} + alertStatus="open" isEndpointItem={false} showAlertCloseOptions onCancel={jest.fn()} onConfirm={jest.fn()} /> - </TestProviders> - ); + ); - expect( - wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() - ).toBeEnabled(); - }); - }); + expect( + shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists') + ).toBeFalsy(); + }); - describe('when there is an exception being created on a sequence eql rule type', () => { - let wrapper: ReactWrapper; - beforeEach(async () => { - wrapper = mount( - <TestProviders> + it('does not allow large value list selection if new trems rule', () => { + const shallowWrapper = shallow( <AddExceptionFlyout rules={[ { - ...getRulesEqlSchemaMock(), - query: - 'sequence [process where process.name = "test.exe"] [process where process.name = "explorer.exe"]', + ...getRulesSchemaMock(), + type: 'new_terms', } as Rule, ]} isBulkAction={false} @@ -1286,79 +734,113 @@ describe('When the add exception modal is opened', () => { onCancel={jest.fn()} onConfirm={jest.fn()} /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => - callProps.onChange({ exceptionItems: [getExceptionListItemSchemaMock()] }) - ); - }); + ); - it('should render the exception builder', () => { - expect(wrapper.find('[data-test-subj="alertExceptionBuilder"]').exists()).toBeTruthy(); + expect( + shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists') + ).toBeFalsy(); + }); }); - it('should not prepopulate endpoint items', () => { - expect(defaultEndpointItems).not.toHaveBeenCalled(); - }); + describe('add to rule/shared list selection', () => { + let wrapper: ReactWrapper; + beforeAll(async () => { + wrapper = mount( + <TestProviders> + <AddExceptionFlyout + rules={[ + { + ...getRulesSchemaMock(), + exceptions_list: [], + } as Rule, + ]} + isBulkAction={false} + alertData={undefined} + isAlertDataLoading={undefined} + alertStatus={undefined} + isEndpointItem={false} + showAlertCloseOptions + onCancel={jest.fn()} + onConfirm={jest.fn()} + /> + </TestProviders> + ); + const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; + await waitFor(() => + callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) + ); + }); - it('should render the close single alert checkbox', () => { - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeTruthy(); - }); + it('defaults to selecting add to rule radio option', () => { + expect( + wrapper.find('[data-test-subj="exceptionItemAddToRuleOrListSection"]').exists() + ).toBeTruthy(); + expect( + wrapper.find('[data-test-subj="addToRuleOptionsRadio"] input').getDOMNode() + ).toBeChecked(); + }); - it('should have the bulk close checkbox disabled', () => { - expect( - wrapper.find('input[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').getDOMNode() - ).toBeDisabled(); - }); + it('disables add to shared lists option if rule has no shared exception lists attached already', () => { + expect( + wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() + ).toBeDisabled(); + }); - it('should display the eql sequence callout', () => { - expect(wrapper.find('[data-test-subj="eqlSequenceCallout"]').exists()).toBeTruthy(); + it('enables add to shared lists option if rule has shared list', () => { + wrapper = mount( + <TestProviders> + <AddExceptionFlyout + rules={[ + { + ...getRulesSchemaMock(), + exceptions_list: [ + { + id: 'test', + list_id: 'test', + namespace_type: 'single', + type: 'detection', + }, + ], + } as Rule, + ]} + isBulkAction={false} + alertData={undefined} + isAlertDataLoading={undefined} + alertStatus={undefined} + isEndpointItem={false} + showAlertCloseOptions + onCancel={jest.fn()} + onConfirm={jest.fn()} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() + ).toBeEnabled(); + }); }); }); - describe('error states', () => { - test('when there are exception builder errors submit button is disabled', async () => { - const wrapper = mount( - <TestProviders> + /* Say for example, add exception item from rules bulk action */ + describe('when multiple rules are passed in - bulk action', () => { + describe('large value lists selection', () => { + it('allows large value lists', () => { + const shallowWrapper = shallow( <AddExceptionFlyout rules={[ { ...getRulesSchemaMock(), + exceptions_list: [], } as Rule, - ]} - isBulkAction={false} - alertData={undefined} - isAlertDataLoading={undefined} - alertStatus={undefined} - isEndpointItem={false} - showAlertCloseOptions - onCancel={jest.fn()} - onConfirm={jest.fn()} - /> - </TestProviders> - ); - const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; - await waitFor(() => callProps.onChange({ exceptionItems: [], errorExists: true })); - expect( - wrapper.find('button[data-test-subj="addExceptionConfirmButton"]').getDOMNode() - ).toBeDisabled(); - }); - - test('when there is a comment error has submit button disabled', async () => { - const { getByLabelText, queryByText, getByTestId } = render( - <TestProviders> - <AddExceptionFlyout - rules={[ { - ...getRulesEqlSchemaMock(), - query: - 'sequence [process where process.name = "test.exe"] [process where process.name = "explorer.exe"]', + ...getRulesSchemaMock(), + id: 'foo', + rule_id: 'foo', + exceptions_list: [], } as Rule, ]} - isBulkAction={false} + isBulkAction alertData={alertDataMock} isAlertDataLoading={false} alertStatus="open" @@ -1367,25 +849,116 @@ describe('When the add exception modal is opened', () => { onCancel={jest.fn()} onConfirm={jest.fn()} /> - </TestProviders> - ); + ); + + expect( + shallowWrapper.find('ExceptionsConditions').prop('allowLargeValueLists') + ).toBeTruthy(); + }); + }); + + describe('add to rules/lists selection', () => { + let wrapper: ReactWrapper; + beforeAll(async () => { + wrapper = mount( + <TestProviders> + <AddExceptionFlyout + rules={[ + { + ...getRulesSchemaMock(), + exceptions_list: [], + } as Rule, + { + ...getRulesSchemaMock(), + id: 'foo', + rule_id: 'foo', + exceptions_list: [ + { + id: 'bar', + list_id: 'bar', + namespace_type: 'single', + type: 'detection', + }, + ], + } as Rule, + ]} + isBulkAction + alertData={undefined} + isAlertDataLoading={undefined} + alertStatus={undefined} + isEndpointItem={false} + showAlertCloseOptions + onCancel={jest.fn()} + onConfirm={jest.fn()} + /> + </TestProviders> + ); + const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0]; + await waitFor(() => + callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }) + ); + }); + it('defaults to selecting add to rules radio option', () => { + expect( + wrapper.find('[data-test-subj="exceptionItemAddToRuleOrListSection"]').exists() + ).toBeTruthy(); + expect( + wrapper.find('[data-test-subj="addToRulesOptionsRadio"] input').getDOMNode() + ).toBeChecked(); + }); - const commentInput = getByLabelText('Comment Input'); + it('disables add to shared lists option if rules have no shared lists in common', () => { + expect( + wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() + ).toBeDisabled(); + }); - const commentErrorMessage = `The length of the comment is too long. The maximum length is ${MAX_COMMENT_LENGTH} characters.`; - expect(queryByText(commentErrorMessage)).toBeNull(); + it('enables add to shared lists option if rules have at least one shared list in common', () => { + wrapper = mount( + <TestProviders> + <AddExceptionFlyout + rules={[ + { + ...getRulesSchemaMock(), + exceptions_list: [ + { + id: 'bar', + list_id: 'bar', + namespace_type: 'single', + type: 'detection', + }, + ], + } as Rule, + { + ...getRulesSchemaMock(), + id: 'foo', + rule_id: 'foo', + exceptions_list: [ + { + id: 'bar', + list_id: 'bar', + namespace_type: 'single', + type: 'detection', + }, + ], + } as Rule, + ]} + isBulkAction + alertData={undefined} + isAlertDataLoading={undefined} + alertStatus={undefined} + isEndpointItem={false} + showAlertCloseOptions + onCancel={jest.fn()} + onConfirm={jest.fn()} + /> + </TestProviders> + ); - // Put comment with the length above maximum allowed - act(() => { - fireEvent.change(commentInput, { - target: { - value: [...new Array(MAX_COMMENT_LENGTH + 1).keys()].map((_) => 'a').join(''), - }, - }); - fireEvent.blur(commentInput); + expect( + wrapper.find('[data-test-subj="addToListsRadioOption"] input').getDOMNode() + ).toBeEnabled(); }); - expect(queryByText(commentErrorMessage)).not.toBeNull(); - expect(getByTestId('addExceptionConfirmButton')).toBeDisabled(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx index a16fa6677b57b..85790713cfa3f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx @@ -11,15 +11,11 @@ import { isEmpty } from 'lodash/fp'; import { EuiFlyout, - EuiFlyoutHeader, EuiTitle, - EuiFlyoutFooter, EuiFlyoutBody, EuiButton, - EuiButtonEmpty, EuiHorizontalRule, EuiSpacer, - EuiFlexGroup, EuiSkeletonText, EuiCallOut, EuiText, @@ -56,13 +52,15 @@ import type { Rule } from '../../../rule_management/logic/types'; import { ExceptionItemsFlyoutAlertsActions } from '../flyout_components/alerts_actions'; import { ExceptionsAddToRulesOrLists } from '../flyout_components/add_exception_to_rule_or_list'; import { useAddNewExceptionItems } from './use_add_new_exceptions'; -import { enrichNewExceptionItems } from '../flyout_components/utils'; import { useCloseAlertsFromExceptions } from '../../logic/use_close_alerts'; import { ruleTypesThatAllowLargeValueLists } from '../../utils/constants'; import { useInvalidateFetchRuleByIdQuery } from '../../../rule_management/api/hooks/use_fetch_rule_by_id_query'; import { ExceptionsExpireTime } from '../flyout_components/expire_time'; import { CONFIRM_WARNING_MODAL_LABELS } from '../../../../management/common/translations'; import { ArtifactConfirmModal } from '../../../../management/components/artifact_list_page/components/artifact_confirm_modal'; +import { ExceptionFlyoutFooter } from '../flyout_components/footer'; +import { ExceptionFlyoutHeader } from '../flyout_components/header'; +import { isSubmitDisabled, prepareNewItemsForSubmission, prepareToCloseAlerts } from './helpers'; const SectionHeader = styled(EuiTitle)` ${() => css` @@ -97,18 +95,6 @@ const FlyoutBodySection = styled(EuiFlyoutBody)` `} `; -const FlyoutHeader = styled(EuiFlyoutHeader)` - ${({ theme }) => css` - border-bottom: 1px solid ${theme.eui.euiColorLightShade}; - `} -`; - -const FlyoutFooterGroup = styled(EuiFlexGroup)` - ${({ theme }) => css` - padding: ${theme.eui.euiSizeS}; - `} -`; - export const AddExceptionFlyout = memo(function AddExceptionFlyout({ rules, isBulkAction, @@ -395,23 +381,16 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ if (submitNewExceptionItems == null) return; try { - const ruleDefaultOptions = ['add_to_rule', 'add_to_rules', 'select_rules_to_add_to']; - const addToRules = ruleDefaultOptions.includes(addExceptionToRadioSelection); - const addToSharedLists = - !!sharedListToAddTo?.length || - (addExceptionToRadioSelection === 'add_to_lists' && !isEmpty(exceptionListsToAddTo)); - const sharedLists = sharedListToAddTo?.length ? sharedListToAddTo : exceptionListsToAddTo; - - const items = enrichNewExceptionItems({ - itemName: exceptionItemName, - commentToAdd: newComment, - addToRules, - addToSharedLists, - sharedLists, + const { listsToAddTo, addToLists, addToRules, items } = prepareNewItemsForSubmission({ + sharedListToAddTo, + addExceptionToRadioSelection, + exceptionListsToAddTo, + exceptionItemName, + newComment, listType, - selectedOs: osTypesSelection, + osTypesSelection, expireTime, - items: exceptionItems, + exceptionItems, }); const addedItems = await submitNewExceptionItems({ @@ -419,16 +398,20 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ selectedRulesToAddTo, listType, addToRules: addToRules && !isEmpty(selectedRulesToAddTo), - addToSharedLists, - sharedLists, + addToSharedLists: addToLists, + sharedLists: listsToAddTo, }); - const alertIdToClose = closeSingleAlert && alertData ? alertData._id : undefined; - const ruleStaticIds = addToRules - ? selectedRulesToAddTo.map(({ rule_id: ruleId }) => ruleId) - : (rules ?? []).map(({ rule_id: ruleId }) => ruleId); + const { shouldCloseAlerts, alertIdToClose, ruleStaticIds } = prepareToCloseAlerts({ + alertData, + closeSingleAlert, + addToRules, + rules, + bulkCloseAlerts, + selectedRulesToAddTo, + }); - if (closeAlerts != null && !isEmpty(ruleStaticIds) && (bulkCloseAlerts || closeSingleAlert)) { + if (closeAlerts != null && shouldCloseAlerts) { await closeAlerts(ruleStaticIds, addedItems, alertIdToClose, bulkCloseIndex); } @@ -470,35 +453,20 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ } }, [wildcardWarningExists, submitException]); - const isSubmitButtonDisabled = useMemo( - (): boolean => - isSubmitting || - isClosingAlerts || - errorSubmitting != null || - exceptionItemName.trim() === '' || - exceptionItems.every((item) => item.entries.length === 0) || - itemConditionValidationErrorExists || - commentErrorExists || - expireErrorExists || - (addExceptionToRadioSelection === 'add_to_lists' && isEmpty(exceptionListsToAddTo)) || - (addExceptionToRadioSelection === 'select_rules_to_add_to' && - isEmpty(selectedRulesToAddTo) && - listType === ExceptionListTypeEnum.RULE_DEFAULT), - [ - isSubmitting, - isClosingAlerts, - errorSubmitting, - exceptionItemName, - exceptionItems, - itemConditionValidationErrorExists, - addExceptionToRadioSelection, - exceptionListsToAddTo, - expireErrorExists, - selectedRulesToAddTo, - listType, - commentErrorExists, - ] - ); + const isSubmitButtonDisabled = isSubmitDisabled({ + isSubmitting, + isClosingAlerts, + errorSubmitting, + exceptionItemName, + exceptionItems, + itemConditionValidationErrorExists, + commentErrorExists, + expireErrorExists, + addExceptionToRadioSelection, + selectedRulesToAddTo, + listType, + exceptionListsToAddTo, + }); const handleDismissError = useCallback((): void => { setErrorSubmitting(null); @@ -508,12 +476,6 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ onCancel(false); }, [onCancel]); - const addExceptionMessage = useMemo(() => { - return listType === ExceptionListTypeEnum.ENDPOINT - ? i18n.ADD_ENDPOINT_EXCEPTION - : i18n.CREATE_RULE_EXCEPTION; - }, [listType]); - const exceptionFlyoutTitleId = useGeneratedHtmlId({ prefix: 'exceptionFlyoutTitle', }); @@ -545,14 +507,11 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ // EUI TODO: This z-index override of EuiOverlayMask is a workaround, and ideally should be resolved with a cleaner UI/UX flow long-term maskProps={{ style: `z-index: ${(euiTheme.levels.flyout as number) + 3}` }} // we need this flyout to be above the timeline flyout (which has a z-index of 1002) > - <FlyoutHeader> - <EuiTitle> - <h2 id={exceptionFlyoutTitleId} data-test-subj="exceptionFlyoutTitle"> - {addExceptionMessage} - </h2> - </EuiTitle> - <EuiSpacer size="m" /> - </FlyoutHeader> + <ExceptionFlyoutHeader + listType={listType} + titleId={exceptionFlyoutTitleId} + dataTestSubjId={'exceptionFlyoutTitle'} + /> <FlyoutBodySection className="builder-section"> { // TODO: This is a quick fix to make sure that we do not lose conditions state on refetching index patterns via `useFetchIndexPatterns` @@ -660,22 +619,14 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ </> )} </FlyoutBodySection> - <EuiFlyoutFooter> - <FlyoutFooterGroup justifyContent="spaceBetween"> - <EuiButtonEmpty data-test-subj="cancelExceptionAddButton" onClick={handleCloseFlyout}> - {i18n.CANCEL} - </EuiButtonEmpty> - - <EuiButton - data-test-subj="addExceptionConfirmButton" - onClick={handleOnSubmit} - isDisabled={isSubmitButtonDisabled} - fill - > - {addExceptionMessage} - </EuiButton> - </FlyoutFooterGroup> - </EuiFlyoutFooter> + <ExceptionFlyoutFooter + listType={listType} + isSubmitButtonDisabled={isSubmitButtonDisabled} + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={handleOnSubmit} + handleCloseFlyout={handleCloseFlyout} + /> {showConfirmModal && confirmModal} </EuiFlyout> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.test.tsx index 5e64eca8df8a2..5c9f363bf1804 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/utility_bar.test.tsx @@ -11,8 +11,21 @@ import { mount } from 'enzyme'; import { ExceptionsViewerUtility } from './utility_bar'; import { TestProviders } from '../../../../common/mock'; -// FLAKY: https://github.com/elastic/kibana/issues/185023 -describe.skip('ExceptionsViewerUtility', () => { +jest.mock('@kbn/i18n-react', () => { + const { i18n } = jest.requireActual('@kbn/i18n'); + i18n.init({ locale: 'en' }); + + const originalModule = jest.requireActual('@kbn/i18n-react'); + const FormattedRelative = jest.fn(); + FormattedRelative.mockImplementation(() => '20 hours ago'); + + return { + ...originalModule, + FormattedRelative, + }; +}); + +describe('ExceptionsViewerUtility', () => { it('it renders correct item counts', () => { const wrapper = mount( <TestProviders> @@ -55,7 +68,7 @@ describe.skip('ExceptionsViewerUtility', () => { ); expect(wrapper.find('[data-test-subj="exceptionsViewerLastUpdated"]').at(0).text()).toEqual( - 'Updated now' + 'Updated 20 hours ago' ); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx index 8fc6ddaa7d9b7..07723db415daf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx @@ -33,7 +33,6 @@ import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/res import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data'; import { useCreateOrUpdateException } from '../../logic/use_create_update_exception'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; -import * as i18n from './translations'; import { MAX_COMMENT_LENGTH } from '../../../../../common/constants'; const mockTheme = getMockTheme({ @@ -260,15 +259,6 @@ describe('When the edit exception modal is opened', () => { ); }); - it('displays proper flyout and button text', () => { - expect(wrapper.find('[data-test-subj="exceptionFlyoutTitle"]').at(1).text()).toEqual( - i18n.EDIT_ENDPOINT_EXCEPTION_TITLE - ); - expect(wrapper.find('[data-test-subj="editExceptionConfirmButton"]').at(1).text()).toEqual( - i18n.EDIT_ENDPOINT_EXCEPTION_TITLE - ); - }); - it('should render item name input', () => { expect(wrapper.find('[data-test-subj="exceptionFlyoutNameInput"]').exists()).toBeTruthy(); }); @@ -466,15 +456,6 @@ describe('When the edit exception modal is opened', () => { ); }); - it('displays proper flyout and button text', () => { - expect(wrapper.find('[data-test-subj="exceptionFlyoutTitle"]').at(1).text()).toEqual( - i18n.EDIT_EXCEPTION_TITLE - ); - expect(wrapper.find('[data-test-subj="editExceptionConfirmButton"]').at(1).text()).toEqual( - i18n.EDIT_EXCEPTION_TITLE - ); - }); - it('should render item name input', () => { expect(wrapper.find('[data-test-subj="exceptionFlyoutNameInput"]').exists()).toBeTruthy(); }); @@ -581,15 +562,6 @@ describe('When the edit exception modal is opened', () => { ); }); - it('displays proper flyout and button text', () => { - expect(wrapper.find('[data-test-subj="exceptionFlyoutTitle"]').at(1).text()).toEqual( - i18n.EDIT_EXCEPTION_TITLE - ); - expect(wrapper.find('[data-test-subj="editExceptionConfirmButton"]').at(1).text()).toEqual( - i18n.EDIT_EXCEPTION_TITLE - ); - }); - it('should render item name input', () => { expect(wrapper.find('[data-test-subj="exceptionFlyoutNameInput"]').exists()).toBeTruthy(); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx index b620814ee3fe6..b6336e3326e25 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx @@ -9,16 +9,10 @@ import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import styled, { css } from 'styled-components'; import { - EuiButton, - EuiButtonEmpty, EuiHorizontalRule, - EuiSpacer, - EuiFlyoutHeader, EuiFlyoutBody, - EuiFlexGroup, EuiTitle, EuiFlyout, - EuiFlyoutFooter, EuiSkeletonText, useGeneratedHtmlId, } from '@elastic/eui'; @@ -65,6 +59,8 @@ import { RULE_EXCEPTION, ENDPOINT_EXCEPTION } from '../../utils/translations'; import { ExceptionsExpireTime } from '../flyout_components/expire_time'; import { CONFIRM_WARNING_MODAL_LABELS } from '../../../../management/common/translations'; import { ArtifactConfirmModal } from '../../../../management/components/artifact_list_page/components/artifact_confirm_modal'; +import { ExceptionFlyoutFooter } from '../flyout_components/footer'; +import { ExceptionFlyoutHeader } from '../flyout_components/header'; interface EditExceptionFlyoutProps { list: ExceptionListSchema; @@ -76,12 +72,6 @@ interface EditExceptionFlyoutProps { onConfirm: (arg: boolean) => void; } -const FlyoutHeader = styled(EuiFlyoutHeader)` - ${({ theme }) => css` - border-bottom: 1px solid ${theme.eui.euiColorLightShade}; - `} -`; - const FlyoutBodySection = styled(EuiFlyoutBody)` ${() => css` &.builder-section { @@ -90,12 +80,6 @@ const FlyoutBodySection = styled(EuiFlyoutBody)` `} `; -const FlyoutFooterGroup = styled(EuiFlexGroup)` - ${({ theme }) => css` - padding: ${theme.eui.euiSizeS}; - `} -`; - const SectionHeader = styled(EuiTitle)` ${() => css` font-weight: ${({ theme }) => theme.eui.euiFontWeightSemiBold}; @@ -357,14 +341,6 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({ } }, [wildcardWarningExists, handleSubmitException]); - const editExceptionMessage = useMemo( - () => - listType === ExceptionListTypeEnum.ENDPOINT - ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE - : i18n.EDIT_EXCEPTION_TITLE, - [listType] - ); - const isSubmitButtonDisabled = useMemo( () => isSubmitting || @@ -414,14 +390,11 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({ data-test-subj="editExceptionFlyout" aria-labelledby={exceptionFlyoutTitleId} > - <FlyoutHeader> - <EuiTitle> - <h2 id={exceptionFlyoutTitleId} data-test-subj="exceptionFlyoutTitle"> - {editExceptionMessage} - </h2> - </EuiTitle> - <EuiSpacer size="m" /> - </FlyoutHeader> + <ExceptionFlyoutHeader + listType={listType} + titleId={exceptionFlyoutTitleId} + dataTestSubjId={'exceptionFlyoutTitle'} + /> <FlyoutBodySection className="builder-section"> {isLoading && <EuiSkeletonText data-test-subj="loadingEditExceptionFlyout" lines={4} />} <ExceptionsFlyoutMeta @@ -499,22 +472,14 @@ const EditExceptionFlyoutComponent: React.FC<EditExceptionFlyoutProps> = ({ </> )} </FlyoutBodySection> - <EuiFlyoutFooter> - <FlyoutFooterGroup justifyContent="spaceBetween"> - <EuiButtonEmpty data-test-subj="cancelExceptionEditButton" onClick={handleCloseFlyout}> - {i18n.CANCEL} - </EuiButtonEmpty> - - <EuiButton - data-test-subj="editExceptionConfirmButton" - onClick={handleOnSubmit} - isDisabled={isSubmitButtonDisabled} - fill - > - {editExceptionMessage} - </EuiButton> - </FlyoutFooterGroup> - </EuiFlyoutFooter> + <ExceptionFlyoutFooter + listType={listType} + isSubmitButtonDisabled={isSubmitButtonDisabled} + cancelButtonDataTestSubjId={'cancelExceptionEditButton'} + submitButtonDataTestSubjId={'editExceptionConfirmButton'} + handleOnSubmit={handleOnSubmit} + handleCloseFlyout={handleCloseFlyout} + /> {showConfirmModal && confirmModal} </EuiFlyout> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/translations.ts index 9839fa5ddc9de..6386cebf5dd48 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/translations.ts @@ -7,24 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const CANCEL = i18n.translate('xpack.securitySolution.ruleExceptions.editException.cancel', { - defaultMessage: 'Cancel', -}); - -export const EDIT_EXCEPTION_TITLE = i18n.translate( - 'xpack.securitySolution.ruleExceptions.editException.editExceptionTitle', - { - defaultMessage: 'Edit rule exception', - } -); - -export const EDIT_ENDPOINT_EXCEPTION_TITLE = i18n.translate( - 'xpack.securitySolution.ruleExceptions.editException.editEndpointExceptionTitle', - { - defaultMessage: 'Edit endpoint exception', - } -); - export const EDIT_RULE_EXCEPTION_SUCCESS_TITLE = i18n.translate( 'xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastSuccessTitle', { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.test.tsx index e7d56028a62a1..91a56fb1b61b4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.test.tsx @@ -13,8 +13,13 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionItemsFlyoutAlertsActions } from '.'; import { TestProviders } from '../../../../../common/mock'; import type { AlertData } from '../../../utils/types'; +import { useFetchIndex } from '../../../../../common/containers/source'; +import { useSignalIndex } from '../../../../../detections/containers/detection_engine/alerts/use_signal_index'; +import { stubIndexPattern } from '@kbn/data-plugin/common/stubs'; jest.mock('../../../../../common/lib/kibana'); +jest.mock('../../../../../common/containers/source'); +jest.mock('../../../../../detections/containers/detection_engine/alerts/use_signal_index'); const alertDataMock: AlertData = { '@timestamp': '1234567890', @@ -22,181 +27,364 @@ const alertDataMock: AlertData = { file: { path: 'test/path' }, }; +const mockUseSignalIndex = useSignalIndex as jest.Mock<Partial<ReturnType<typeof useSignalIndex>>>; +const mockUseFetchIndex = useFetchIndex as jest.Mock; + describe('ExceptionItemsFlyoutAlertsActions', () => { - it('it displays single alert close checkbox if alert status is not "closed" and "alertData" exists', () => { - const wrapper = mountWithIntl( - <TestProviders> - <ExceptionItemsFlyoutAlertsActions - exceptionListItems={[getExceptionListItemSchemaMock()]} - exceptionListType={ExceptionListTypeEnum.DETECTION} - shouldCloseSingleAlert={false} - shouldBulkCloseAlert={false} - disableBulkClose={false} - alertData={alertDataMock} - alertStatus="open" - onDisableBulkClose={jest.fn()} - onUpdateBulkCloseIndex={jest.fn()} - onBulkCloseCheckboxChange={jest.fn()} - onSingleAlertCloseCheckboxChange={jest.fn()} - isAlertDataLoading={false} - /> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeTruthy(); - }); + beforeEach(() => { + mockUseSignalIndex.mockImplementation(() => ({ + loading: false, + signalIndexName: 'mock-siem-signals-index', + })); - it('it does not display single alert close checkbox if alert status is "closed"', () => { - const wrapper = mountWithIntl( - <TestProviders> - <ExceptionItemsFlyoutAlertsActions - exceptionListItems={[getExceptionListItemSchemaMock()]} - exceptionListType={ExceptionListTypeEnum.DETECTION} - shouldCloseSingleAlert={false} - shouldBulkCloseAlert={false} - disableBulkClose={false} - alertData={alertDataMock} - alertStatus="closed" - onDisableBulkClose={jest.fn()} - onUpdateBulkCloseIndex={jest.fn()} - onBulkCloseCheckboxChange={jest.fn()} - onSingleAlertCloseCheckboxChange={jest.fn()} - isAlertDataLoading={false} - /> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeFalsy(); + mockUseFetchIndex.mockImplementation(() => [ + false, + { + indexPatterns: stubIndexPattern, + }, + ]); }); - it('it does not display single alert close checkbox if "alertData" does not exist', () => { - const wrapper = mountWithIntl( - <TestProviders> - <ExceptionItemsFlyoutAlertsActions - exceptionListItems={[getExceptionListItemSchemaMock()]} - exceptionListType={ExceptionListTypeEnum.DETECTION} - shouldCloseSingleAlert={false} - shouldBulkCloseAlert={false} - disableBulkClose={false} - alertData={undefined} - alertStatus="open" - onDisableBulkClose={jest.fn()} - onUpdateBulkCloseIndex={jest.fn()} - onBulkCloseCheckboxChange={jest.fn()} - onSingleAlertCloseCheckboxChange={jest.fn()} - isAlertDataLoading={false} - /> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() - ).toBeFalsy(); + afterEach(() => { + jest.clearAllMocks(); }); - it('it displays bulk close checkbox', () => { - const wrapper = mountWithIntl( - <TestProviders> - <ExceptionItemsFlyoutAlertsActions - exceptionListItems={[getExceptionListItemSchemaMock()]} - exceptionListType={ExceptionListTypeEnum.DETECTION} - shouldCloseSingleAlert={false} - shouldBulkCloseAlert={false} - disableBulkClose={false} - alertData={alertDataMock} - alertStatus="open" - onDisableBulkClose={jest.fn()} - onUpdateBulkCloseIndex={jest.fn()} - onBulkCloseCheckboxChange={jest.fn()} - onSingleAlertCloseCheckboxChange={jest.fn()} - isAlertDataLoading={false} - /> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').exists() - ).toBeTruthy(); - }); + describe('Endpoint specific logic', () => { + it('it displays endpoint quarantine text if exception list type is "endpoint"', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.ENDPOINT} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); - it('it displays checkboxes disabled if "isAlertDataLoading" is "true"', () => { - const wrapper = mountWithIntl( - <TestProviders> - <ExceptionItemsFlyoutAlertsActions - exceptionListItems={[getExceptionListItemSchemaMock()]} - exceptionListType={ExceptionListTypeEnum.DETECTION} - shouldCloseSingleAlert={false} - shouldBulkCloseAlert={false} - disableBulkClose={false} - alertData={alertDataMock} - alertStatus="open" - onDisableBulkClose={jest.fn()} - onUpdateBulkCloseIndex={jest.fn()} - onBulkCloseCheckboxChange={jest.fn()} - onSingleAlertCloseCheckboxChange={jest.fn()} - isAlertDataLoading - /> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').at(0).props().disabled - ).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').at(0).props().disabled - ).toBeTruthy(); + expect(wrapper.find('[data-test-subj="addExceptionEndpointText"]').exists()).toBeTruthy(); + }); }); - it('it displays bulk close checkbox disabled if "disableBulkCloseAlert" is "true"', () => { - const wrapper = mountWithIntl( - <TestProviders> - <ExceptionItemsFlyoutAlertsActions - exceptionListItems={[getExceptionListItemSchemaMock()]} - exceptionListType={ExceptionListTypeEnum.DETECTION} - shouldCloseSingleAlert={false} - shouldBulkCloseAlert={false} - disableBulkClose={true} - alertData={alertDataMock} - alertStatus="open" - onDisableBulkClose={jest.fn()} - onUpdateBulkCloseIndex={jest.fn()} - onBulkCloseCheckboxChange={jest.fn()} - onSingleAlertCloseCheckboxChange={jest.fn()} - isAlertDataLoading={false} - /> - </TestProviders> - ); - - expect( - wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').at(0).props().disabled - ).toBeTruthy(); - expect(wrapper.find('[data-test-subj="addExceptionEndpointText"]').exists()).toBeFalsy(); + describe('alert data exists', () => { + it('it displays single alert close checkbox if alert status is not "closed" and "alertData" exists', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() + ).toBeTruthy(); + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"] input').prop('disabled') + ).toBeFalsy(); + }); + + it('it displays single alert close checkbox disabled if "isAlertDataLoading" is true', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"] input').prop('disabled') + ).toBeTruthy(); + }); + + it('it displays single alert close checkbox disabled if "isSignalIndexLoading" is true', () => { + mockUseSignalIndex.mockImplementation(() => ({ + loading: true, + signalIndexName: 'mock-siem-signals-index', + })); + + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"] input').prop('disabled') + ).toBeTruthy(); + }); + + it('it displays single alert close checkbox disabled if "isSignalIndexPatternLoading" is true', () => { + mockUseFetchIndex.mockImplementation(() => [ + true, + { + indexPatterns: stubIndexPattern, + }, + ]); + + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"] input').prop('disabled') + ).toBeTruthy(); + }); + + it('it does not display single alert close checkbox if alert status is "closed"', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="closed" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() + ).toBeFalsy(); + }); }); - it('it displays endpoint quarantine text if exception list type is "endpoint"', () => { - const wrapper = mountWithIntl( - <TestProviders> - <ExceptionItemsFlyoutAlertsActions - exceptionListItems={[getExceptionListItemSchemaMock()]} - exceptionListType={ExceptionListTypeEnum.ENDPOINT} - shouldCloseSingleAlert={false} - shouldBulkCloseAlert={false} - disableBulkClose={false} - alertData={alertDataMock} - alertStatus="open" - onDisableBulkClose={jest.fn()} - onUpdateBulkCloseIndex={jest.fn()} - onBulkCloseCheckboxChange={jest.fn()} - onSingleAlertCloseCheckboxChange={jest.fn()} - isAlertDataLoading={false} - /> - </TestProviders> - ); - - expect(wrapper.find('[data-test-subj="addExceptionEndpointText"]').exists()).toBeTruthy(); + describe('bulk close alert', () => { + it('it does not display single alert close checkbox if "alertData" does not exist', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={undefined} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').exists() + ).toBeFalsy(); + }); + + it('it displays bulk close checkbox', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').exists() + ).toBeTruthy(); + }); + + it('it displays checkboxes disabled if "isAlertDataLoading" is "true"', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').at(0).props() + .disabled + ).toBeTruthy(); + expect( + wrapper.find('[data-test-subj="closeAlertOnAddExceptionCheckbox"]').at(0).props().disabled + ).toBeTruthy(); + }); + + it('it displays bulk close checkbox disabled if "disableBulkCloseAlert" is "true"', () => { + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={true} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').at(0).props() + .disabled + ).toBeTruthy(); + expect(wrapper.find('[data-test-subj="addExceptionEndpointText"]').exists()).toBeFalsy(); + }); + + it('it displays bulk close checkbox disabled if "isSignalIndexLoading" is "true"', () => { + mockUseSignalIndex.mockImplementation(() => ({ + loading: true, + signalIndexName: 'mock-siem-signals-index', + })); + + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').at(0).props() + .disabled + ).toBeTruthy(); + }); + + it('it displays bulk close checkbox disabled if "isSignalIndexPatternLoading" is "true"', () => { + mockUseFetchIndex.mockImplementation(() => [ + true, + { + indexPatterns: stubIndexPattern, + }, + ]); + + const wrapper = mountWithIntl( + <TestProviders> + <ExceptionItemsFlyoutAlertsActions + exceptionListItems={[getExceptionListItemSchemaMock()]} + exceptionListType={ExceptionListTypeEnum.DETECTION} + shouldCloseSingleAlert={false} + shouldBulkCloseAlert={false} + disableBulkClose={false} + alertData={alertDataMock} + alertStatus="open" + onDisableBulkClose={jest.fn()} + onUpdateBulkCloseIndex={jest.fn()} + onBulkCloseCheckboxChange={jest.fn()} + onSingleAlertCloseCheckboxChange={jest.fn()} + isAlertDataLoading={false} + /> + </TestProviders> + ); + + expect( + wrapper.find('[data-test-subj="bulkCloseAlertOnAddExceptionCheckbox"]').at(0).props() + .disabled + ).toBeTruthy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.tsx index 5543a48a72858..0b9ebc9cd85d5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/index.tsx @@ -15,7 +15,7 @@ import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution import { useSignalIndex } from '../../../../../detections/containers/detection_engine/alerts/use_signal_index'; import type { Status } from '../../../../../../common/api/detection_engine'; import { useFetchIndex } from '../../../../../common/containers/source'; -import { entryHasListType, entryHasNonEcsType } from './utils'; +import { shouldDisableBulkClose } from './utils'; import * as i18n from './translations'; import type { AlertData } from '../../../utils/types'; @@ -103,9 +103,7 @@ const ExceptionItemsFlyoutAlertsActionsComponent: React.FC< useEffect((): void => { if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) { onDisableBulkClose( - entryHasListType(exceptionListItems) || - entryHasNonEcsType(exceptionListItems, signalIndexPatterns) || - exceptionListItems.every((item) => item.entries.length === 0) + shouldDisableBulkClose({ items: exceptionListItems, signalIndexPatterns }) ); } }, [ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts index 777fec2fd135c..55171d830c4ae 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts @@ -8,9 +8,24 @@ import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; import type { EntriesArray, ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { entryHasNonEcsType, entryHasListType } from './utils'; +import { entryHasNonEcsType, entryHasListType, shouldDisableBulkClose } from './utils'; import type { DataViewBase } from '@kbn/es-query'; +const mockEcsIndexPattern = { + title: 'testIndex', + fields: [ + { + name: 'some.parentField', + }, + { + name: 'some.not.nested.field', + }, + { + name: 'nested.field', + }, + ], +} as DataViewBase; + describe('alerts_actions#utils', () => { describe('#entryHasListType', () => { test('it should return false with an empty array', () => { @@ -39,21 +54,6 @@ describe('alerts_actions#utils', () => { }); describe('#entryHasNonEcsType', () => { - const mockEcsIndexPattern = { - title: 'testIndex', - fields: [ - { - name: 'some.parentField', - }, - { - name: 'some.not.nested.field', - }, - { - name: 'nested.field', - }, - ], - } as DataViewBase; - test('it should return false with an empty array', () => { const payload: ExceptionListItemSchema[] = []; const result = entryHasNonEcsType(payload, mockEcsIndexPattern); @@ -78,4 +78,73 @@ describe('alerts_actions#utils', () => { expect(result).toEqual(true); }); }); + + describe('#shouldDisableBulkClose', () => { + it('returns true if items include large value lists', () => { + expect( + shouldDisableBulkClose({ + items: [ + { + ...getExceptionListItemSchemaMock(), + entries: [ + { + field: 'host.name', + list: { type: 'text', id: 'blob' }, + operator: 'included', + type: 'list', + }, + ], + }, + ], + signalIndexPatterns: mockEcsIndexPattern, + }) + ).toBeTruthy(); + }); + + it('returns true if items include non ECS types', () => { + expect( + shouldDisableBulkClose({ + items: [ + { + ...getExceptionListItemSchemaMock(), + entries: [{ field: 'some.nonEcsField' }] as EntriesArray, + }, + ], + signalIndexPatterns: mockEcsIndexPattern, + }) + ).toBeTruthy(); + }); + + it('returns true if all items have no entries', () => { + expect( + shouldDisableBulkClose({ + items: [ + { + ...getExceptionListItemSchemaMock(), + entries: [] as EntriesArray, + }, + ], + signalIndexPatterns: mockEcsIndexPattern, + }) + ).toBeTruthy(); + }); + + it('returns true if no items exist', () => { + expect( + shouldDisableBulkClose({ + items: [], + signalIndexPatterns: mockEcsIndexPattern, + }) + ).toBeTruthy(); + }); + + it('returns false if no large value list entries exist and all are ECS compliant', () => { + expect( + shouldDisableBulkClose({ + items: [getExceptionListItemSchemaMock(), getExceptionListItemSchemaMock()], + signalIndexPatterns: mockEcsIndexPattern, + }) + ).toBeFalsy(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.ts index 03dc4e168cf0b..da393cae3bef9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.ts @@ -58,3 +58,20 @@ export const entryHasNonEcsType = ( } return false; }; + +/** + * Determines whether the bulk close alerts option should be disabled. + */ +export const shouldDisableBulkClose = ({ + items, + signalIndexPatterns, +}: { + items: ExceptionsBuilderReturnExceptionItem[]; + signalIndexPatterns: DataViewBase; +}): boolean => { + return ( + entryHasListType(items) || + entryHasNonEcsType(items, signalIndexPatterns) || + items.every((item) => item.entries.length === 0) + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/index.test.tsx new file mode 100644 index 0000000000000..bb24ba61d1ab7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/index.test.tsx @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, screen } from '@testing-library/react'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +import { TestProviders } from '../../../../../common/mock'; +import { ExceptionFlyoutFooter } from '.'; +import * as i18n from './translations'; + +describe('Exception flyout footer', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render button disabled if "isSubmitButtonDisabled" is "true"', () => { + render( + <TestProviders> + <ExceptionFlyoutFooter + listType={ExceptionListTypeEnum.ENDPOINT} + isSubmitButtonDisabled + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={jest.fn()} + handleCloseFlyout={jest.fn()} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toBeDisabled(); + }); + + describe('"isEdit" is "false"', () => { + it('should render proper text when endpoint exception', () => { + render( + <TestProviders> + <ExceptionFlyoutFooter + listType={ExceptionListTypeEnum.ENDPOINT} + isSubmitButtonDisabled={false} + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={jest.fn()} + handleCloseFlyout={jest.fn()} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.ADD_ENDPOINT_EXCEPTION + ); + }); + + it('should render proper text when list type of "rule_default"', () => { + render( + <TestProviders> + <ExceptionFlyoutFooter + listType={ExceptionListTypeEnum.RULE_DEFAULT} + isSubmitButtonDisabled={false} + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={jest.fn()} + handleCloseFlyout={jest.fn()} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.CREATE_RULE_EXCEPTION + ); + }); + + it('should render proper text when list type of "detection"', () => { + render( + <TestProviders> + <ExceptionFlyoutFooter + listType={ExceptionListTypeEnum.DETECTION} + isSubmitButtonDisabled={false} + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={jest.fn()} + handleCloseFlyout={jest.fn()} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.CREATE_RULE_EXCEPTION + ); + }); + }); + + describe('"isEdit" is "true"', () => { + it('should render proper text when endpoint exception', () => { + render( + <TestProviders> + <ExceptionFlyoutFooter + isEdit + listType={ExceptionListTypeEnum.ENDPOINT} + isSubmitButtonDisabled={false} + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={jest.fn()} + handleCloseFlyout={jest.fn()} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.EDIT_ENDPOINT_EXCEPTION_TITLE + ); + }); + + it('should render proper text when list type of "rule_default"', () => { + render( + <TestProviders> + <ExceptionFlyoutFooter + isEdit + listType={ExceptionListTypeEnum.RULE_DEFAULT} + isSubmitButtonDisabled={false} + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={jest.fn()} + handleCloseFlyout={jest.fn()} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.EDIT_EXCEPTION_TITLE + ); + }); + + it('should render proper text when list type of "detection"', () => { + render( + <TestProviders> + <ExceptionFlyoutFooter + isEdit + listType={ExceptionListTypeEnum.DETECTION} + isSubmitButtonDisabled={false} + cancelButtonDataTestSubjId={'cancelExceptionAddButton'} + submitButtonDataTestSubjId={'addExceptionConfirmButton'} + handleOnSubmit={jest.fn()} + handleCloseFlyout={jest.fn()} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.EDIT_EXCEPTION_TITLE + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/index.tsx new file mode 100644 index 0000000000000..7e85f611d04e3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useMemo } from 'react'; +import styled, { css } from 'styled-components'; + +import { EuiFlyoutFooter, EuiButton, EuiButtonEmpty, EuiFlexGroup } from '@elastic/eui'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import * as i18n from './translations'; + +const FlyoutFooterGroup = styled(EuiFlexGroup)` + ${({ theme }) => css` + padding: ${theme.eui.euiSizeS}; + `} +`; + +export interface ExceptionFlyoutFooterProps { + isEdit?: boolean; + listType: ExceptionListTypeEnum; + isSubmitButtonDisabled: boolean; + cancelButtonDataTestSubjId: string; + submitButtonDataTestSubjId: string; + handleOnSubmit: () => Promise<void> | undefined; + handleCloseFlyout: () => void; +} + +export const ExceptionFlyoutFooter = memo(function ExceptionFlyoutFooter({ + isEdit = false, + listType, + isSubmitButtonDisabled, + cancelButtonDataTestSubjId, + submitButtonDataTestSubjId, + handleOnSubmit, + handleCloseFlyout, +}: ExceptionFlyoutFooterProps) { + const addButtonMessage = useMemo(() => { + return listType === ExceptionListTypeEnum.ENDPOINT + ? i18n.ADD_ENDPOINT_EXCEPTION + : i18n.CREATE_RULE_EXCEPTION; + }, [listType]); + + const editButtonMessage = useMemo(() => { + return listType === ExceptionListTypeEnum.ENDPOINT + ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE + : i18n.EDIT_EXCEPTION_TITLE; + }, [listType]); + + const submitButtonMessage = isEdit ? editButtonMessage : addButtonMessage; + + return ( + <EuiFlyoutFooter> + <FlyoutFooterGroup justifyContent="spaceBetween"> + <EuiButtonEmpty data-test-subj={cancelButtonDataTestSubjId} onClick={handleCloseFlyout}> + {i18n.CANCEL} + </EuiButtonEmpty> + + <EuiButton + data-test-subj={submitButtonDataTestSubjId} + onClick={handleOnSubmit} + isDisabled={isSubmitButtonDisabled} + fill + > + {submitButtonMessage} + </EuiButton> + </FlyoutFooterGroup> + </EuiFlyoutFooter> + ); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/translations.ts new file mode 100644 index 0000000000000..7dbb3be7eb622 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/footer/translations.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 { i18n } from '@kbn/i18n'; + +export const CREATE_RULE_EXCEPTION = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.addException', + { + defaultMessage: 'Add rule exception', + } +); + +export const ADD_ENDPOINT_EXCEPTION = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.addEndpointException', + { + defaultMessage: 'Add Endpoint Exception', + } +); + +export const EDIT_ENDPOINT_EXCEPTION_TITLE = i18n.translate( + 'xpack.securitySolution.ruleExceptions..flyoutComponents.footer.editEndpointExceptionTitle', + { + defaultMessage: 'Edit endpoint exception', + } +); + +export const EDIT_EXCEPTION_TITLE = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.editExceptionTitle', + { + defaultMessage: 'Edit rule exception', + } +); + +export const CANCEL = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.cancel', + { + defaultMessage: 'Cancel', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/index.test.tsx new file mode 100644 index 0000000000000..f9f08231b2155 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/index.test.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, screen } from '@testing-library/react'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; + +import { TestProviders } from '../../../../../common/mock'; +import { ExceptionFlyoutHeader } from '.'; +import * as i18n from './translations'; + +describe('Exception flyout header', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('"isEdit" is "false"', () => { + it('should render proper text when endpoint exception', () => { + render( + <TestProviders> + <ExceptionFlyoutHeader + listType={ExceptionListTypeEnum.ENDPOINT} + titleId={'someId'} + dataTestSubjId={'addExceptionConfirmButton'} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.ADD_ENDPOINT_EXCEPTION + ); + }); + + it('should render proper text when list type of "rule_default"', () => { + render( + <TestProviders> + <ExceptionFlyoutHeader + listType={ExceptionListTypeEnum.RULE_DEFAULT} + titleId={'someId'} + dataTestSubjId={'addExceptionConfirmButton'} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.CREATE_RULE_EXCEPTION + ); + }); + + it('should render proper text when list type of "detection"', () => { + render( + <TestProviders> + <ExceptionFlyoutHeader + listType={ExceptionListTypeEnum.DETECTION} + titleId={'someId'} + dataTestSubjId={'addExceptionConfirmButton'} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.CREATE_RULE_EXCEPTION + ); + }); + }); + + describe('"isEdit" is "true"', () => { + it('should render proper text when endpoint exception', () => { + render( + <TestProviders> + <ExceptionFlyoutHeader + isEdit + listType={ExceptionListTypeEnum.ENDPOINT} + titleId={'someId'} + dataTestSubjId={'addExceptionConfirmButton'} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.EDIT_ENDPOINT_EXCEPTION_TITLE + ); + }); + + it('should render proper text when list type of "rule_default"', () => { + render( + <TestProviders> + <ExceptionFlyoutHeader + isEdit + listType={ExceptionListTypeEnum.RULE_DEFAULT} + titleId={'someId'} + dataTestSubjId={'addExceptionConfirmButton'} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.EDIT_EXCEPTION_TITLE + ); + }); + + it('should render proper text when list type of "detection"', () => { + render( + <TestProviders> + <ExceptionFlyoutHeader + isEdit + listType={ExceptionListTypeEnum.DETECTION} + titleId={'someId'} + dataTestSubjId={'addExceptionConfirmButton'} + /> + </TestProviders> + ); + + expect(screen.getByTestId('addExceptionConfirmButton')).toHaveTextContent( + i18n.EDIT_EXCEPTION_TITLE + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/index.tsx new file mode 100644 index 0000000000000..185a4f4ec0148 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/index.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, { memo, useMemo } from 'react'; +import styled, { css } from 'styled-components'; + +import { EuiTitle, EuiSpacer, EuiFlyoutHeader } from '@elastic/eui'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import * as i18n from './translations'; + +const FlyoutHeader = styled(EuiFlyoutHeader)` + ${({ theme }) => css` + border-bottom: 1px solid ${theme.eui.euiColorLightShade}; + `} +`; + +export interface ExceptionFlyoutHeaderProps { + isEdit?: boolean; + listType: ExceptionListTypeEnum; + titleId: string; + dataTestSubjId: string; +} + +export const ExceptionFlyoutHeader = memo(function ExceptionFlyoutHeader({ + isEdit = false, + listType, + titleId, + dataTestSubjId, +}: ExceptionFlyoutHeaderProps) { + const addTitle = useMemo(() => { + return listType === ExceptionListTypeEnum.ENDPOINT + ? i18n.ADD_ENDPOINT_EXCEPTION + : i18n.CREATE_RULE_EXCEPTION; + }, [listType]); + + const editTitle = useMemo(() => { + return listType === ExceptionListTypeEnum.ENDPOINT + ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE + : i18n.EDIT_EXCEPTION_TITLE; + }, [listType]); + + const title = isEdit ? editTitle : addTitle; + + return ( + <FlyoutHeader> + <EuiTitle> + <h2 id={titleId} data-test-subj={dataTestSubjId}> + {title} + </h2> + </EuiTitle> + <EuiSpacer size="m" /> + </FlyoutHeader> + ); +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/translations.ts new file mode 100644 index 0000000000000..7dbb3be7eb622 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/header/translations.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 { i18n } from '@kbn/i18n'; + +export const CREATE_RULE_EXCEPTION = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.addException', + { + defaultMessage: 'Add rule exception', + } +); + +export const ADD_ENDPOINT_EXCEPTION = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.addEndpointException', + { + defaultMessage: 'Add Endpoint Exception', + } +); + +export const EDIT_ENDPOINT_EXCEPTION_TITLE = i18n.translate( + 'xpack.securitySolution.ruleExceptions..flyoutComponents.footer.editEndpointExceptionTitle', + { + defaultMessage: 'Edit endpoint exception', + } +); + +export const EDIT_EXCEPTION_TITLE = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.editExceptionTitle', + { + defaultMessage: 'Edit rule exception', + } +); + +export const CANCEL = i18n.translate( + 'xpack.securitySolution.ruleExceptions.flyoutComponents.footer.cancel', + { + defaultMessage: 'Cancel', + } +); 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 57f6c5f4e8305..3513c9a21aa7b 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 @@ -13,9 +13,6 @@ import { INTERNAL_ALERTING_API_FIND_RULES_PATH } from '@kbn/alerting-plugin/comm import { BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; import type { ActionType, AsApiContract } from '@kbn/actions-plugin/common'; import type { ActionResult } from '@kbn/actions-plugin/server'; -import type { BulkInstallPackagesResponse } from '@kbn/fleet-plugin/common'; -import { epmRouteService } from '@kbn/fleet-plugin/common'; -import type { InstallPackageResponse } from '@kbn/fleet-plugin/common/types'; import { convertRulesFilterToKQL } from '../../../../common/detection_engine/rule_management/rule_filtering'; import type { UpgradeSpecificRulesRequest, @@ -48,6 +45,7 @@ import { } from '../../../../common/constants'; import { + BOOTSTRAP_PREBUILT_RULES_URL, GET_PREBUILT_RULES_STATUS_URL, PERFORM_RULE_INSTALLATION_URL, PERFORM_RULE_UPGRADE_URL, @@ -81,6 +79,7 @@ import type { RulesSnoozeSettingsMap, UpdateRulesProps, } from '../logic/types'; +import type { BootstrapPrebuiltRulesResponse } from '../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; /** * Create provided Rule @@ -594,66 +593,6 @@ export const addRuleExceptions = async ({ } ); -export interface InstallFleetPackageProps { - packageName: string; - packageVersion: string; - prerelease?: boolean; - force?: boolean; -} - -/** - * Install a Fleet package from the registry - * - * @param packageName Name of the package to install - * @param packageVersion Version of the package to install - * @param prerelease Whether to install a prerelease version of the package - * @param force Whether to force install the package. If false, the package will only be installed if it is not already installed - * - * @returns The response from the Fleet API - */ -export const installFleetPackage = ({ - packageName, - packageVersion, - prerelease = false, - force = true, -}: InstallFleetPackageProps): Promise<InstallPackageResponse> => { - return KibanaServices.get().http.post<InstallPackageResponse>( - epmRouteService.getInstallPath(packageName, packageVersion), - { - query: { prerelease }, - version: '2023-10-31', - body: JSON.stringify({ force }), - } - ); -}; - -export interface BulkInstallFleetPackagesProps { - packages: string[]; - prerelease?: boolean; -} - -/** - * Install multiple Fleet packages from the registry - * - * @param packages Array of package names to install - * @param prerelease Whether to install prerelease versions of the packages - * - * @returns The response from the Fleet API - */ -export const bulkInstallFleetPackages = ({ - packages, - prerelease = false, -}: BulkInstallFleetPackagesProps): Promise<BulkInstallPackagesResponse> => { - return KibanaServices.get().http.post<BulkInstallPackagesResponse>( - epmRouteService.getBulkInstallPath(), - { - query: { prerelease }, - version: '2023-10-31', - body: JSON.stringify({ packages }), - } - ); -}; - /** * NEW PREBUILT RULES ROUTES START HERE! 👋 * USE THESE ONES! THEY'RE THE NICE ONES, PROMISE! @@ -759,3 +698,9 @@ export const performUpgradeSpecificRules = async ( pick_version: 'TARGET', // Setting fixed 'TARGET' temporarily for Milestone 2 }), }); + +export const bootstrapPrebuiltRules = async (): Promise<BootstrapPrebuiltRulesResponse> => + KibanaServices.get().http.fetch(BOOTSTRAP_PREBUILT_RULES_URL, { + method: 'POST', + version: '1', + }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bootstrap_prebuilt_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bootstrap_prebuilt_rules.ts new file mode 100644 index 0000000000000..9f2b810138a6c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bootstrap_prebuilt_rules.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 { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import { BOOTSTRAP_PREBUILT_RULES_URL } from '../../../../../common/api/detection_engine'; +import type { BootstrapPrebuiltRulesResponse } from '../../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; +import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants'; +import { bootstrapPrebuiltRules } from '../api'; +import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query'; +import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query'; +import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query'; + +export const BOOTSTRAP_PREBUILT_RULES_KEY = ['POST', BOOTSTRAP_PREBUILT_RULES_URL]; + +export const useBootstrapPrebuiltRulesMutation = ( + options?: UseMutationOptions<BootstrapPrebuiltRulesResponse, Error> +) => { + const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); + const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery(); + const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery(); + + return useMutation(() => bootstrapPrebuiltRules(), { + ...options, + mutationKey: BOOTSTRAP_PREBUILT_RULES_KEY, + onSettled: (...args) => { + const response = args[0]; + if ( + response?.packages.find((pkg) => pkg.name === PREBUILT_RULES_PACKAGE_NAME)?.status === + 'installed' + ) { + // Invalidate other pre-packaged rules related queries. We need to do + // that only in case the prebuilt rules package was installed indicating + // that there might be new rules to install. + invalidatePrePackagedRulesStatus(); + invalidatePrebuiltRulesInstallReview(); + invalidatePrebuiltRulesUpdateReview(); + } + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts deleted file mode 100644 index d47f4799fcc30..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.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 { EPM_API_ROUTES } from '@kbn/fleet-plugin/common'; -import type { BulkInstallPackagesResponse } from '@kbn/fleet-plugin/common/types'; -import type { UseMutationOptions } from '@tanstack/react-query'; -import { useMutation } from '@tanstack/react-query'; -import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants'; -import type { BulkInstallFleetPackagesProps } from '../api'; -import { bulkInstallFleetPackages } from '../api'; -import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query'; -import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query'; -import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query'; - -export const BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY = [ - 'POST', - EPM_API_ROUTES.BULK_INSTALL_PATTERN, -]; - -export const useBulkInstallFleetPackagesMutation = ( - options?: UseMutationOptions<BulkInstallPackagesResponse, Error, BulkInstallFleetPackagesProps> -) => { - const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); - const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery(); - const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery(); - - return useMutation((props: BulkInstallFleetPackagesProps) => bulkInstallFleetPackages(props), { - ...options, - mutationKey: BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY, - onSettled: (...args) => { - const response = args[0]; - const rulesPackage = response?.items.find( - (item) => item.name === PREBUILT_RULES_PACKAGE_NAME - ); - if (rulesPackage && 'result' in rulesPackage && rulesPackage.result.status === 'installed') { - // The rules package was installed/updated, so invalidate the pre-packaged rules status query - invalidatePrePackagedRulesStatus(); - invalidatePrebuiltRulesInstallReview(); - invalidatePrebuiltRulesUpdateReview(); - } - - if (options?.onSettled) { - options.onSettled(...args); - } - }, - }); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts deleted file mode 100644 index e4f88d8b9c3ef..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.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 { EPM_API_ROUTES } from '@kbn/fleet-plugin/common'; -import type { InstallPackageResponse } from '@kbn/fleet-plugin/common/types'; -import type { UseMutationOptions } from '@tanstack/react-query'; -import { useMutation } from '@tanstack/react-query'; -import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants'; -import type { InstallFleetPackageProps } from '../api'; -import { installFleetPackage } from '../api'; -import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query'; -import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query'; -import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query'; - -export const INSTALL_FLEET_PACKAGE_MUTATION_KEY = [ - 'POST', - EPM_API_ROUTES.INSTALL_FROM_REGISTRY_PATTERN, -]; - -export const useInstallFleetPackageMutation = ( - options?: UseMutationOptions<InstallPackageResponse, Error, InstallFleetPackageProps> -) => { - const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); - const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery(); - const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery(); - - return useMutation((props: InstallFleetPackageProps) => installFleetPackage(props), { - ...options, - mutationKey: INSTALL_FLEET_PACKAGE_MUTATION_KEY, - onSettled: (...args) => { - const { packageName } = args[2]; - if (packageName === PREBUILT_RULES_PACKAGE_NAME) { - // Invalidate the pre-packaged rules status query as there might be new rules to install - invalidatePrePackagedRulesStatus(); - invalidatePrebuiltRulesInstallReview(); - invalidatePrebuiltRulesUpdateReview(); - } - - if (options?.onSettled) { - options.onSettled(...args); - } - }, - }); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/diff_view.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/diff_view.tsx index 69503c7062372..ba3195edab73a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/diff_view.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/diff_view.tsx @@ -25,6 +25,7 @@ import type { HunkTokens, } from 'react-diff-view'; import unidiff from 'unidiff'; +import type { Change } from 'diff'; import { useEuiTheme, COLOR_MODES_STANDARD } from '@elastic/eui'; import { Hunks } from './hunks'; import { markEdits, DiffMethod } from './mark_edits'; @@ -111,15 +112,32 @@ const renderGutter: RenderGutter = ({ change }) => { return null; }; +/** + * Converts an array of Change objects into a "unified diff" string. + * + * Takes an array of changes (as provided by the jsdiff library) and converts it into a "unified diff" string. + * + * @param {Change[]} changes - An array of changes between two strings. + * @returns {string} A unified diff string representing the changes. + */ +const convertChangesToUnifiedDiffString = (changes: Change[]): string => { + const unifiedDiff: string = unidiff.formatLines(changes, { + context: 3, + }); + + return unifiedDiff; +}; + const convertToDiffFile = (oldSource: string, newSource: string) => { /* "diffLines" call converts two strings of text into an array of Change objects. */ - const changes = unidiff.diffLines(oldSource, newSource); + const changes: Change[] = unidiff.diffLines(oldSource, newSource); /* - Then "formatLines" takes an array of Change objects and turns it into a single "unified diff" string. - More info about the "unified diff" format: https://en.wikipedia.org/wiki/Diff_utility#Unified_format + "convertChangesToUnifiedDiffString" converts an array of Change objects into a single "unified diff" string. + More info about the "unified diff" format: https://en.wikipedia.org/wiki/Diff#Unified_format + Unified diff is a string with change markers added. Looks something like: ` @@ -3,16 +3,15 @@ @@ -129,9 +147,7 @@ const convertToDiffFile = (oldSource: string, newSource: string) => { "history_window_start": "now-14d", ` */ - const unifiedDiff: string = unidiff.formatLines(changes, { - context: 3, - }); + const unifiedDiff: string = convertChangesToUnifiedDiffString(changes); /* "parseDiff" converts a unified diff string into a gitdiff-parser File object. @@ -154,18 +170,34 @@ const CustomStyles: FC<PropsWithChildren<unknown>> = ({ children }) => { padding: 0 ${euiTheme.size.l} 0 ${euiTheme.size.m}; } + /* Gutter - a narrow column on the left-hand side that displays either a "+" or a "-" */ .${TABLE_CLASS_NAME} .diff-gutter-col { width: ${euiTheme.size.xl}; } + /* + Hide the redundant second gutter column in "unified" view. + Hiding it with "display: none" would break the layout, so we set its width to 0 and make its content invisible. + */ + .${TABLE_CLASS_NAME}.diff-unified .diff-gutter-col + .diff-gutter-col { + width: 0; + } + .${TABLE_CLASS_NAME}.diff-unified .diff-gutter + .diff-gutter { + visibility: hidden; + } + /* Vertical line separating two sides of the diff view */ .${GUTTER_CLASS_NAME}:nth-child(3) { border-left: 1px solid ${euiTheme.colors.mediumShade}; } + .${GUTTER_CLASS_NAME}.diff-gutter-delete, .${GUTTER_CLASS_NAME}.diff-gutter-insert { + font-weight: bold; + text-align: center; + } + /* Gutter of a line with deletions */ .${GUTTER_CLASS_NAME}.diff-gutter-delete { - font-weight: bold; background: ${COLORS.light.gutterBackground.deletion}; } .${DARK_THEME_CLASS_NAME} .${GUTTER_CLASS_NAME}.diff-gutter-delete { @@ -174,7 +206,6 @@ const CustomStyles: FC<PropsWithChildren<unknown>> = ({ children }) => { /* Gutter of a line with insertions */ .${GUTTER_CLASS_NAME}.diff-gutter-insert { - font-weight: bold; background: ${COLORS.light.gutterBackground.insertion}; } .${DARK_THEME_CLASS_NAME} .${GUTTER_CLASS_NAME}.diff-gutter-insert { @@ -228,12 +259,14 @@ interface DiffViewProps extends Partial<DiffProps> { oldSource: string; newSource: string; diffMethod?: DiffMethod; + viewType?: 'split' | 'unified'; } export const DiffView = ({ oldSource, newSource, diffMethod = DiffMethod.WORDS, + viewType = 'split', }: DiffViewProps) => { /* "react-diff-view" components consume diffs not as a strings, but as something they call "hunks". @@ -272,6 +305,7 @@ export const DiffView = ({ Passing 'add' or 'delete' would skip rendering one of the sides in split view. */ diffType={diffFile.type} + viewType={viewType} hunks={hunks} renderGutter={renderGutter} tokens={tokens} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/unidiff.d.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/unidiff.d.ts index d7fa438700e97..0f57677af362c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/unidiff.d.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/json_diff/unidiff.d.ts @@ -5,14 +5,20 @@ * 2.0. */ -declare module 'unidiff' { - interface Change { - count?: number | undefined; - value: string; - added?: boolean | undefined; - removed?: boolean | undefined; - } +interface Change { + count?: number | undefined; + value: string; + added?: boolean | undefined; + removed?: boolean | undefined; +} + +type ADDED = 'ADDED'; +type REMOVED = 'REMOVED'; +type UNMODIFIED = 'UNMODIFIED'; +type LineChangeType = ADDED | REMOVED | UNMODIFIED; + +declare module 'unidiff' { export interface FormatOptions { context?: number; } @@ -21,3 +27,28 @@ declare module 'unidiff' { export function formatLines(line: Change[], options?: FormatOptions): string; } + +declare module 'unidiff/hunk' { + export const ADDED: ADDED; + export const REMOVED: REMOVED; + export const UNMODIFIED: UNMODIFIED; + + export type ChangeWithType = Change & { type: LineChangeType }; + + export interface LineChange { + type: LineChangeType; + text: string; + unified(): string; + } + + export interface UniDiffHunk { + aoff: number; + boff: number; + changes: LineChange[]; + unified(): string; + } + + export function lineChanges(change: ChangeWithType): LineChange[]; + + export function hunk(aOffset: number, bOffset: number, lchanges: LineChange[]): UniDiffHunk; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.stories.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.stories.tsx new file mode 100644 index 0000000000000..37ddb71dce9f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.stories.tsx @@ -0,0 +1,315 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { Story } from '@storybook/react'; +import { ComparisonSide } from './comparison_side'; +import type { + ThreeWayDiff, + DiffableAllFields, + RuleKqlQuery, +} from '../../../../../../../common/api/detection_engine'; +import { + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, + ThreeWayDiffConflict, + KqlQueryType, +} from '../../../../../../../common/api/detection_engine'; +export default { + component: ComparisonSide, + title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/ComparisonSide', + argTypes: { + fieldName: { + control: 'text', + description: 'Field name to compare', + }, + fieldThreeWayDiff: { + control: 'object', + description: 'Field ThreeWayDiff', + }, + resolvedValue: { + control: 'text', + description: 'Resolved value', + }, + }, +}; + +interface TemplateProps<FieldName extends keyof DiffableAllFields> { + fieldName: FieldName; + fieldThreeWayDiff: ThreeWayDiff<DiffableAllFields[FieldName]>; + resolvedValue?: DiffableAllFields[FieldName]; +} + +const Template: Story<TemplateProps<keyof DiffableAllFields>> = (args) => { + return ( + <ComparisonSide + fieldName={args.fieldName} + fieldThreeWayDiff={args.fieldThreeWayDiff} + resolvedValue={args.resolvedValue} + /> + ); +}; + +export const NoBaseVersion = Template.bind({}); +NoBaseVersion.args = { + fieldName: 'rule_name_override', + fieldThreeWayDiff: { + has_base_version: false, + base_version: undefined, + current_version: { + field_name: 'rule.name', + }, + target_version: undefined, + merged_version: undefined, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_update: true, + conflict: ThreeWayDiffConflict.SOLVABLE, + }, +}; + +export const WithResolvedValue = Template.bind({}); +WithResolvedValue.args = { + fieldName: 'risk_score', + fieldThreeWayDiff: { + has_base_version: true, + base_version: 10, + current_version: 40, + target_version: 20, + merged_version: 40, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_update: true, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }, + resolvedValue: 35, +}; +WithResolvedValue.argTypes = { + resolvedValue: { + control: 'number', + }, +}; + +/* Optional field becomes defined - was undefined in base version, but was then defined by user in current version */ +export const OptionalFieldBecomesDefined = Template.bind({}); +OptionalFieldBecomesDefined.args = { + fieldName: 'timestamp_override', + fieldThreeWayDiff: { + has_base_version: true, + base_version: undefined, + current_version: { + field_name: 'event.ingested', + fallback_disabled: true, + }, + target_version: undefined, + merged_version: { + field_name: 'event.ingested', + fallback_disabled: true, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_update: false, + conflict: ThreeWayDiffConflict.NONE, + }, +}; + +export const SubfieldsWithLabels = Template.bind({}); + +const subfieldsWithLabelsThreeWayDiff: ThreeWayDiff<RuleKqlQuery> = { + has_base_version: true, + base_version: { + type: KqlQueryType.inline_query, + query: 'event.agent_id_status: *', + language: 'kuery', + filters: [], + }, + current_version: { + type: KqlQueryType.inline_query, + query: 'event.agent_id_status: *', + language: 'kuery', + filters: [], + }, + target_version: { + type: KqlQueryType.saved_query, + saved_query_id: 'e355ef26-45f5-40f1-bbb7-5176ecf07d5c', + }, + merged_version: { + type: KqlQueryType.saved_query, + saved_query_id: 'e355ef26-45f5-40f1-bbb7-5176ecf07d5c', + }, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_update: true, + conflict: ThreeWayDiffConflict.NONE, +}; + +SubfieldsWithLabels.args = { + fieldName: 'kql_query', + fieldThreeWayDiff: subfieldsWithLabelsThreeWayDiff, +}; + +/* Field type changes - in this example "kql_query" field was "inline" in base version, but became "saved" in the current version */ +export const FieldTypeChanges = Template.bind({}); + +const fieldTypeChangesThreeWayDiff: ThreeWayDiff<RuleKqlQuery> = { + has_base_version: true, + base_version: { + type: KqlQueryType.inline_query, + query: 'event.agent_id_status: *', + language: 'kuery', + filters: [], + }, + current_version: { + type: KqlQueryType.saved_query, + saved_query_id: 'e355ef26-45f5-40f1-bbb7-5176ecf07d5c', + }, + target_version: { + type: KqlQueryType.inline_query, + query: 'event.agent_id_status: *', + language: 'kuery', + filters: [], + }, + merged_version: { + type: KqlQueryType.saved_query, + saved_query_id: 'e355ef26-45f5-40f1-bbb7-5176ecf07d5c', + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_update: false, + conflict: ThreeWayDiffConflict.NONE, +}; + +FieldTypeChanges.args = { + fieldName: 'kql_query', + fieldThreeWayDiff: fieldTypeChangesThreeWayDiff, +}; + +export const SingleLineStringSubfieldChanges = Template.bind({}); +SingleLineStringSubfieldChanges.args = { + fieldName: 'name', + fieldThreeWayDiff: { + has_base_version: true, + base_version: 'Prebuilt rule', + current_version: 'Customized prebuilt rule', + target_version: 'Prebuilt rule with new changes', + merged_version: 'Customized prebuilt rule', + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_update: true, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }, +}; + +export const MultiLineStringSubfieldChanges = Template.bind({}); +MultiLineStringSubfieldChanges.args = { + fieldName: 'note', + fieldThreeWayDiff: { + has_base_version: true, + base_version: 'My description.\f\nThis is a second\u2001 line.\f\nThis is a third line.', + current_version: + 'My GREAT description.\f\nThis is a second\u2001 line.\f\nThis is a third line.', + target_version: 'My description.\f\nThis is a second\u2001 line.\f\nThis is a GREAT line.', + merged_version: + 'My GREAT description.\f\nThis is a second\u2001 line.\f\nThis is a GREAT line.', + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + has_update: true, + conflict: ThreeWayDiffConflict.SOLVABLE, + }, +}; + +export const NumberSubfieldChanges = Template.bind({}); +NumberSubfieldChanges.args = { + fieldName: 'risk_score', + fieldThreeWayDiff: { + has_base_version: true, + base_version: 33, + current_version: 43, + target_version: 53, + merged_version: 43, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_update: true, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }, +}; + +export const ArraySubfieldChanges = Template.bind({}); +ArraySubfieldChanges.args = { + fieldName: 'tags', + fieldThreeWayDiff: { + has_base_version: true, + base_version: ['one', 'two', 'three'], + current_version: ['two', 'three', 'four', 'five'], + target_version: ['one', 'three', 'four', 'six'], + merged_version: ['three', 'four', 'five', 'six'], + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_update: true, + conflict: ThreeWayDiffConflict.SOLVABLE, + }, +}; + +export const QuerySubfieldChanges = Template.bind({}); +QuerySubfieldChanges.args = { + fieldName: 'kql_query', + fieldThreeWayDiff: { + has_base_version: true, + base_version: { + type: KqlQueryType.inline_query, + query: + 'event.action:("Directory Service Changes" or "directory-service-object-modified") and event.code:5136 and\n winlog.event_data.OperationType:"%%14674" and\n winlog.event_data.ObjectClass:"user" and\n winlog.event_data.AttributeLDAPDisplayName:"servicePrincipalName"\n', + language: 'kuery', + filters: [], + }, + current_version: { + type: KqlQueryType.inline_query, + query: + 'event.action:("Directory Service Changes" or "directory-service-object-modified") and event.code:5136 and\n winlog.event_data.OperationType:"%%14674" or winlog.event_data.OperationType:"%%14675" and\n winlog.event_data.ObjectClass:"user" and\n winlog.event_data.AttributeLDAPDisplayName:"serviceSecondaryName"\n', + language: 'kuery', + filters: [], + }, + target_version: { + type: KqlQueryType.inline_query, + query: + 'event.action:("Directory Service Changes" or "Directory Service Modifications" or "directory-service-object-modified") and event.code:5136 and\n winlog.event_data.OperationType:"%%14674" and\n winlog.event_data.ObjectClass:"user" and\n winlog.event_data.AttributeLDAPDisplayName:"servicePrincipalName"\n', + language: 'kuery', + filters: [], + }, + merged_version: { + type: KqlQueryType.inline_query, + query: + 'event.action:("Directory Service Changes" or "directory-service-object-modified") and event.code:5136 and\n winlog.event_data.OperationType:"%%14674" or winlog.event_data.OperationType:"%%14675" and\n winlog.event_data.ObjectClass:"user" and\n winlog.event_data.AttributeLDAPDisplayName:"serviceSecondaryName"\n', + language: 'kuery', + filters: [], + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_update: true, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }, +}; + +export const SetupGuideSubfieldChanges = Template.bind({}); +SetupGuideSubfieldChanges.args = { + fieldName: 'setup', + fieldThreeWayDiff: { + has_base_version: true, + base_version: + '## Setup\n\nThis rule requires data coming in from one of the following integrations:\n- Elastic Defend\n- Auditbeat\n\n### Elastic Defend Integration Setup\nElastic Defend is integrated into the Elastic Agent using Fleet. Upon configuration, the integration allows the Elastic Agent to monitor events on your host and send data to the Elastic Security app.\n\n#### Prerequisite Requirements:\n- Fleet is required for Elastic Defend.\n- To configure Fleet Server refer to the [documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html).\n\n#### The following steps should be executed in order to add the Elastic Defend integration on a Linux System:\n- Go to the Kibana home page and click "Add integrations".\n- In the query bar, search for "Elastic Defend" and select the integration to see more details about it.\n- Click "Add Elastic Defend".\n- Configure the integration name and optionally add a description.\n- Select the type of environment you want to protect, either "Traditional Endpoints" or "Cloud Workloads".\n- Select a configuration preset. Each preset comes with different default settings for Elastic Agent, you can further customize these later by configuring the Elastic Defend integration policy. [Helper guide](https://www.elastic.co/guide/en/security/current/configure-endpoint-integration-policy.html).\n- We suggest selecting "Complete EDR (Endpoint Detection and Response)" as a configuration setting, that provides "All events; all preventions"\n- Enter a name for the agent policy in "New agent policy name". If other agent policies already exist, you can click the "Existing hosts" tab and select an existing policy instead.\nFor more details on Elastic Agent configuration settings, refer to the [helper guide](https://www.elastic.co/guide/en/fleet/8.10/agent-policy.html).\n- Click "Save and Continue".\n- To complete the integration, select "Add Elastic Agent to your hosts" and continue to the next section to install the Elastic Agent on your hosts.\nFor more details on Elastic Defend refer to the [helper guide](https://www.elastic.co/guide/en/security/current/install-endpoint.html).\n\n### Auditbeat Setup\nAuditbeat is a lightweight shipper that you can install on your servers to audit the activities of users and processes on your systems. For example, you can use Auditbeat to collect and centralize audit events from the Linux Audit Framework. You can also use Auditbeat to detect changes to critical files, like binaries and configuration files, and identify potential security policy violations.\n\n#### The following steps should be executed in order to add the Auditbeat on a Linux System:\n- Elastic provides repositories available for APT and YUM-based distributions. Note that we provide binary packages, but no source packages.\n- To install the APT and YUM repositories follow the setup instructions in this [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setup-repositories.html).\n- To run Auditbeat on Docker follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-docker.html).\n- To run Auditbeat on Kubernetes follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-kubernetes.html).\n- For complete “Setup and Run Auditbeat” information refer to the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setting-up-and-running.html).\n', + current_version: + '## Setup\n\nThis rule requires data coming in from one of the following integrations:\n- Elastic Defend\n- Auditbeat\n- Custom Windows Event Logs\n\n### Elastic Defend Integration Setup\nElastic Defend is integrated into the Elastic Agent using Fleet. Upon configuration, the integration allows the Elastic Agent to monitor events on your host and send data to the Elastic Security app.\n\n#### Prerequisite Requirements:\n- Fleet is required for Elastic Defend.\n- To configure Fleet Server refer to the [documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html).\n\n#### The following steps should be executed in order to add the Elastic Defend integration on a Linux System:\n- Go to the Kibana home page and click "Add integrations".\n- In the query bar, search for "Elastic Defend" and select the integration to see more details about it.\n- Click "Add Elastic Defend".\n- Configure the integration name and optionally add a description.\n- Select the type of environment you want to protect.\n- Select a configuration preset. Each preset comes with different default settings for Elastic Agent, you can further customize these later by configuring the Elastic Defend integration policy. [Helper guide](https://www.elastic.co/guide/en/security/current/configure-endpoint-integration-policy.html).\n- Enter a name for the agent policy in "New agent policy name". If other agent policies already exist, you can click the "Existing hosts" tab and select an existing policy instead.\nFor more details on Elastic Agent configuration settings, refer to the [helper guide](https://www.elastic.co/guide/en/fleet/8.10/agent-policy.html).\n- Click "Save and Continue".\n- The rule is now ready to run.\n- To complete the integration, select "Add Elastic Agent to your hosts" and continue to the next section to install the Elastic Agent on your hosts.\nFor more details on Elastic Defend refer to the [helper guide](https://www.elastic.co/guide/en/security/current/install-endpoint.html).\n\n### Auditbeat Setup\nAuditbeat is a lightweight shipper that you can install on your servers to audit the activities of users and processes on your systems. For example, you can use Auditbeat to collect and centralize audit events from the Linux Audit Framework. You can also use Auditbeat to detect changes to critical files, like binaries and configuration files, and identify potential security policy violations.\n\n#### The following steps should be executed in order to add the Auditbeat on a Linux System:\n- Elastic provides repositories available for AXT and YUM-based distributions. Note that we provide binary packages, but no source packages.\n- To install the AXT and YUM repositories follow the setup instructions in this [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setup-repositories.html).\n- To run Auditbeat on Docker follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-docker.html).\n- To run Auditbeat on Kubernetes follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-kubernetes.html).\n- For complete “Setup and Run Auditbeat” information refer to the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setting-up-and-running.html).\n', + target_version: + '## Setup\n\nThis rule requires data coming in from one of the following integrations:\n- Elastic Defend\n- Auditbeat\n\n### Elastic Defend Integration Setup\nElastic Defend is integrated into the Elastic Agent using Fleet. Upon configuration, the integration allows the Elastic Agent to monitor events on your host and send data to the Elastic Security app.\n\n#### Prerequisite Requirements:\n- Fleet is required for Elastic Defend.\n- To configure Fleet Server refer to the [documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html).\n\n#### The following steps should be executed in order to add the Elastic Defend integration on a Linux System:\n- Go to the Kibana home page and click "Add integrations".\n- In the query bar, search for "Elastic Defend" and select the integration to see more details about it.\n- Click "Add Elastic Defend".\n- Configure the integration name and optionally add a description.\n- Select the type of environment you want to protect, either "Traditional Endpoints" or "Cloud Workloads".\n- Carefully select a configuration preset. Each preset comes with different default settings for Elastic Agent, you can further customize these later by configuring the Elastic Defend integration policy. [Helper guide](https://www.elastic.co/guide/en/security/current/configure-endpoint-integration-policy.html).\n- We suggest selecting "Complete EDR (Endpoint Detection and Response)" as a configuration setting, that provides "All events; all preventions"\n- Enter a title for the agent policy in "New agent policy title". If other agent policies already exist, you can click the "Existing hosts" tab and select an existing policy instead.\nFor more details on Elastic Agent configuration settings, refer to the [helper guide](https://www.elastic.co/guide/en/fleet/8.10/agent-policy.html).\n- Click "Save and Continue".\n- To complete the integration, select "Add Elastic Agent to your hosts" and continue to the next section to install the Elastic Agent on your hosts.\nFor more details on Elastic Defend refer to the [helper guide](https://www.elastic.co/guide/en/security/current/install-endpoint.html).\n\n### Auditbeat Setup\nAuditbeat is a lightweight shipper that you can install on your servers to audit the activities of users and processes on your systems. You can use Auditbeat to detect changes to critical files, like binaries and configuration files, and identify potential security policy violations.\n\n#### The following steps should be executed in order to add the Auditbeat on a Linux System:\n- Elastic provides repositories available for APT and YUM-based distributions. Note that we provide binary packages, but no source packages.\n- To install the APT and YUM repositories follow the setup instructions in this [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setup-repositories.html).\n- To run Auditbeat on Docker follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-docker.html).\n- To run Auditbeat on Kubernetes follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-kubernetes.html).\n- For complete “Setup and Run Auditbeat” information refer to the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setting-up-and-running.html).\n- Good luck!\n', + merged_version: + '## Setup\n\nThis rule requires data coming in from one of the following integrations:\n- Elastic Defend\n- Auditbeat\n- Custom Windows Event Logs\n\n### Elastic Defend Integration Setup\nElastic Defend is integrated into the Elastic Agent using Fleet. Upon configuration, the integration allows the Elastic Agent to monitor events on your host and send data to the Elastic Security app.\n\n#### Prerequisite Requirements:\n- Fleet is required for Elastic Defend.\n- To configure Fleet Server refer to the [documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html).\n\n#### The following steps should be executed in order to add the Elastic Defend integration on a Linux System:\n- Go to the Kibana home page and click "Add integrations".\n- In the query bar, search for "Elastic Defend" and select the integration to see more details about it.\n- Click "Add Elastic Defend".\n- Configure the integration name and optionally add a description.\n- Select the type of environment you want to protect.\n- Carefully select a configuration preset. Each preset comes with different default settings for Elastic Agent, you can further customize these later by configuring the Elastic Defend integration policy. [Helper guide](https://www.elastic.co/guide/en/security/current/configure-endpoint-integration-policy.html).\n- Enter a title for the agent policy in "New agent policy title". If other agent policies already exist, you can click the "Existing hosts" tab and select an existing policy instead.\nFor more details on Elastic Agent configuration settings, refer to the [helper guide](https://www.elastic.co/guide/en/fleet/8.10/agent-policy.html).\n- Click "Save and Continue".\n- The rule is now ready to run.\n- To complete the integration, select "Add Elastic Agent to your hosts" and continue to the next section to install the Elastic Agent on your hosts.\nFor more details on Elastic Defend refer to the [helper guide](https://www.elastic.co/guide/en/security/current/install-endpoint.html).\n\n### Auditbeat Setup\nAuditbeat is a lightweight shipper that you can install on your servers to audit the activities of users and processes on your systems. You can use Auditbeat to detect changes to critical files, like binaries and configuration files, and identify potential security policy violations.\n\n#### The following steps should be executed in order to add the Auditbeat on a Linux System:\n- Elastic provides repositories available for AXT and YUM-based distributions. Note that we provide binary packages, but no source packages.\n- To install the AXT and YUM repositories follow the setup instructions in this [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setup-repositories.html).\n- To run Auditbeat on Docker follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-docker.html).\n- To run Auditbeat on Kubernetes follow the setup instructions in the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/running-on-kubernetes.html).\n- For complete “Setup and Run Auditbeat” information refer to the [helper guide](https://www.elastic.co/guide/en/beats/auditbeat/current/setting-up-and-running.html).\n- Good luck!\n', + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + has_update: true, + conflict: ThreeWayDiffConflict.SOLVABLE, + }, +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.tsx new file mode 100644 index 0000000000000..3a6f3e366d848 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/comparison_side.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, { useState } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { VersionsPicker } from '../versions_picker/versions_picker'; +import type { Version } from '../versions_picker/constants'; +import { SelectedVersions } from '../versions_picker/constants'; +import { pickFieldValueForVersion } from './utils'; +import type { + DiffableAllFields, + ThreeWayDiff, +} from '../../../../../../../common/api/detection_engine'; +import { getSubfieldChanges } from './get_subfield_changes'; +import { SubfieldChanges } from './subfield_changes'; + +interface ComparisonSideProps<FieldName extends keyof DiffableAllFields> { + fieldName: FieldName; + fieldThreeWayDiff: ThreeWayDiff<DiffableAllFields[FieldName]>; + resolvedValue?: DiffableAllFields[FieldName]; +} + +export function ComparisonSide<FieldName extends keyof DiffableAllFields>({ + fieldName, + fieldThreeWayDiff, + resolvedValue, +}: ComparisonSideProps<FieldName>) { + const [selectedVersions, setSelectedVersions] = useState<SelectedVersions>( + SelectedVersions.CurrentFinal + ); + + const [oldVersionType, newVersionType] = selectedVersions.split('_') as [Version, Version]; + + const oldFieldValue = pickFieldValueForVersion(oldVersionType, fieldThreeWayDiff, resolvedValue); + const newFieldValue = pickFieldValueForVersion(newVersionType, fieldThreeWayDiff, resolvedValue); + + const subfieldChanges = getSubfieldChanges(fieldName, oldFieldValue, newFieldValue); + + return ( + <> + <VersionsPicker + hasBaseVersion={fieldThreeWayDiff.has_base_version} + selectedVersions={selectedVersions} + onChange={setSelectedVersions} + /> + <EuiSpacer size="m" /> + <SubfieldChanges fieldName={fieldName} subfieldChanges={subfieldChanges} /> + </> + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/constants.ts new file mode 100644 index 0000000000000..d848fc7bb1dfa --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/constants.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 type { DiffableAllFields } from '../../../../../../../common/api/detection_engine'; + +export const FIELDS_WITH_SUBFIELDS: Array<keyof DiffableAllFields> = [ + 'data_source', + 'kql_query', + 'eql_query', + 'esql_query', + 'threat_query', + 'rule_schedule', + 'rule_name_override', + 'timestamp_override', + 'timeline_template', + 'building_block', + 'threshold', +]; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/building_block.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/building_block.ts new file mode 100644 index 0000000000000..4cb1ad66f8a90 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/building_block.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForBuildingBlock = ( + oldFieldValue?: DiffableAllFields['building_block'], + newFieldValue?: DiffableAllFields['building_block'] +): SubfieldChange[] => { + const oldType = stringifyToSortedJson(oldFieldValue?.type); + const newType = stringifyToSortedJson(newFieldValue?.type); + + if (oldType !== newType) { + return [ + { + subfieldName: 'type', + oldSubfieldValue: oldType, + newSubfieldValue: newType, + }, + ]; + } + + return []; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/data_source.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/data_source.ts new file mode 100644 index 0000000000000..f887f623362a4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/data_source.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForDataSource = ( + oldFieldValue?: DiffableAllFields['data_source'], + newFieldValue?: DiffableAllFields['data_source'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldType = stringifyToSortedJson(oldFieldValue?.type); + const newType = stringifyToSortedJson(newFieldValue?.type); + + if (oldType !== newType) { + changes.push({ + subfieldName: 'type', + oldSubfieldValue: oldType, + newSubfieldValue: newType, + }); + } + + const oldIndexPatterns = stringifyToSortedJson( + oldFieldValue?.type === 'index_patterns' ? oldFieldValue?.index_patterns : '' + ); + const newIndexPatterns = stringifyToSortedJson( + newFieldValue?.type === 'index_patterns' ? newFieldValue?.index_patterns : '' + ); + + if (oldIndexPatterns !== newIndexPatterns) { + changes.push({ + subfieldName: 'index_patterns', + oldSubfieldValue: oldIndexPatterns, + newSubfieldValue: newIndexPatterns, + }); + } + + const oldDataViewId = stringifyToSortedJson( + oldFieldValue?.type === 'data_view' ? oldFieldValue?.data_view_id : '' + ); + const newDataViewId = stringifyToSortedJson( + newFieldValue?.type === 'data_view' ? newFieldValue?.data_view_id : '' + ); + + if (oldDataViewId !== newDataViewId) { + changes.push({ + subfieldName: 'data_view_id', + oldSubfieldValue: oldDataViewId, + newSubfieldValue: newDataViewId, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.ts new file mode 100644 index 0000000000000..25a4dff97dd21 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/eql_query.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForEqlQuery = ( + oldFieldValue?: DiffableAllFields['eql_query'], + newFieldValue?: DiffableAllFields['eql_query'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldQuery = stringifyToSortedJson(oldFieldValue?.query); + const newQuery = stringifyToSortedJson(newFieldValue?.query); + + if (oldQuery !== newQuery) { + changes.push({ + subfieldName: 'query', + oldSubfieldValue: oldQuery, + newSubfieldValue: newQuery, + }); + } + + const oldFilters = stringifyToSortedJson(oldFieldValue?.filters); + const newFilters = stringifyToSortedJson(newFieldValue?.filters); + + if (oldFilters !== newFilters) { + changes.push({ + subfieldName: 'filters', + oldSubfieldValue: oldFilters, + newSubfieldValue: newFilters, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/esql_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/esql_query.ts new file mode 100644 index 0000000000000..dd99f8de8579e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/esql_query.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForEsqlQuery = ( + oldFieldValue?: DiffableAllFields['esql_query'], + newFieldValue?: DiffableAllFields['esql_query'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldQuery = stringifyToSortedJson(oldFieldValue?.query); + const newQuery = stringifyToSortedJson(newFieldValue?.query); + + if (oldQuery !== newQuery) { + changes.push({ + subfieldName: 'query', + oldSubfieldValue: oldQuery, + newSubfieldValue: newQuery, + }); + } + + const oldLanguage = stringifyToSortedJson(oldFieldValue?.language); + const newLanguage = stringifyToSortedJson(oldFieldValue?.language); + + if (oldLanguage !== newLanguage) { + changes.push({ + subfieldName: 'language', + oldSubfieldValue: oldLanguage, + newSubfieldValue: newLanguage, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/index.ts new file mode 100644 index 0000000000000..c3628d74176c3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import { stringifyToSortedJson } from '../utils'; +import { getSubfieldChangesForDataSource } from './data_source'; +import { getSubfieldChangesForKqlQuery } from './kql_query'; +import { getSubfieldChangesForEqlQuery } from './eql_query'; +import { getSubfieldChangesForEsqlQuery } from './esql_query'; +import { getSubfieldChangesForThreatQuery } from './threat_query'; +import { getSubfieldChangesForRuleSchedule } from './rule_schedule'; +import { getSubfieldChangesForRuleNameOverride } from './rule_name_override'; +import { getSubfieldChangesForTimestampOverride } from './timestamp_override'; +import { getSubfieldChangesForTimelineTemplate } from './timeline_template'; +import { getSubfieldChangesForBuildingBlock } from './building_block'; +import { getSubfieldChangesForThreshold } from './threshold'; +import type { SubfieldChanges } from '../types'; + +/** + * Splits a field into subfields and returns the changes between the old and new subfield values. + * + * @param fieldName - The name of the field for which subfield changes are to be computed. + * @param oldFieldValue - The old value of the field. + * @param newFieldValue - The new value of the field. + * @returns - An array of subfield changes. + */ +export const getSubfieldChanges = <FieldName extends keyof DiffableAllFields>( + fieldName: FieldName, + oldFieldValue?: DiffableAllFields[FieldName], + newFieldValue?: DiffableAllFields[FieldName] +): SubfieldChanges => { + switch (fieldName) { + /* + Typecasting `oldFieldValue` and `newFieldValue` to corresponding field + type `DiffableAllFields[*]` is required here since `oldFieldValue` and + `newFieldValue` concrete types depend on `fieldName` but TS doesn't track that. + */ + case 'data_source': + return getSubfieldChangesForDataSource( + oldFieldValue as DiffableAllFields['data_source'], + newFieldValue as DiffableAllFields['data_source'] + ); + case 'kql_query': + return getSubfieldChangesForKqlQuery( + oldFieldValue as DiffableAllFields['kql_query'], + newFieldValue as DiffableAllFields['kql_query'] + ); + case 'eql_query': + return getSubfieldChangesForEqlQuery( + oldFieldValue as DiffableAllFields['eql_query'], + newFieldValue as DiffableAllFields['eql_query'] + ); + case 'esql_query': + return getSubfieldChangesForEsqlQuery( + oldFieldValue as DiffableAllFields['esql_query'], + newFieldValue as DiffableAllFields['esql_query'] + ); + case 'threat_query': + return getSubfieldChangesForThreatQuery( + oldFieldValue as DiffableAllFields['threat_query'], + newFieldValue as DiffableAllFields['threat_query'] + ); + case 'rule_schedule': + return getSubfieldChangesForRuleSchedule( + oldFieldValue as DiffableAllFields['rule_schedule'], + newFieldValue as DiffableAllFields['rule_schedule'] + ); + case 'rule_name_override': + return getSubfieldChangesForRuleNameOverride( + oldFieldValue as DiffableAllFields['rule_name_override'], + newFieldValue as DiffableAllFields['rule_name_override'] + ); + case 'timestamp_override': + return getSubfieldChangesForTimestampOverride( + oldFieldValue as DiffableAllFields['timestamp_override'], + newFieldValue as DiffableAllFields['timestamp_override'] + ); + case 'timeline_template': + return getSubfieldChangesForTimelineTemplate( + oldFieldValue as DiffableAllFields['timeline_template'], + newFieldValue as DiffableAllFields['timeline_template'] + ); + case 'building_block': + return getSubfieldChangesForBuildingBlock( + oldFieldValue as DiffableAllFields['building_block'], + newFieldValue as DiffableAllFields['building_block'] + ); + case 'threshold': + return getSubfieldChangesForThreshold( + oldFieldValue as DiffableAllFields['threshold'], + newFieldValue as DiffableAllFields['threshold'] + ); + default: + const oldFieldValueStringified = stringifyToSortedJson(oldFieldValue); + const newFieldValueStringified = stringifyToSortedJson(newFieldValue); + + if (oldFieldValueStringified === newFieldValueStringified) { + return []; + } + + return [ + { + subfieldName: fieldName, + oldSubfieldValue: oldFieldValueStringified, + newSubfieldValue: newFieldValueStringified, + }, + ]; + } +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/kql_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/kql_query.ts new file mode 100644 index 0000000000000..4bd96d212ba0f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/kql_query.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { stringifyToSortedJson } from '../utils'; +import type { RuleKqlQuery } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForKqlQuery = ( + oldFieldValue?: RuleKqlQuery, + newFieldValue?: RuleKqlQuery +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldType = stringifyToSortedJson(oldFieldValue?.type); + const newType = stringifyToSortedJson(newFieldValue?.type); + + if (oldType !== newType) { + changes.push({ + subfieldName: 'type', + oldSubfieldValue: oldType, + newSubfieldValue: newType, + }); + } + + const oldQuery = stringifyToSortedJson( + oldFieldValue?.type === 'inline_query' ? oldFieldValue?.query : '' + ); + const newQuery = stringifyToSortedJson( + newFieldValue?.type === 'inline_query' ? newFieldValue?.query : '' + ); + + if (oldQuery !== newQuery) { + changes.push({ + subfieldName: 'query', + oldSubfieldValue: oldQuery, + newSubfieldValue: newQuery, + }); + } + + const oldLanguage = stringifyToSortedJson( + oldFieldValue?.type === 'inline_query' ? oldFieldValue?.language : '' + ); + const newLanguage = stringifyToSortedJson( + newFieldValue?.type === 'inline_query' ? newFieldValue?.language : '' + ); + + if (oldLanguage !== newLanguage) { + changes.push({ + subfieldName: 'language', + oldSubfieldValue: oldLanguage, + newSubfieldValue: newLanguage, + }); + } + + const oldFilters = stringifyToSortedJson( + oldFieldValue?.type === 'inline_query' ? oldFieldValue?.filters : '' + ); + const newFilters = stringifyToSortedJson( + newFieldValue?.type === 'inline_query' ? newFieldValue?.filters : '' + ); + + if (oldFilters !== newFilters) { + changes.push({ + subfieldName: 'filters', + oldSubfieldValue: oldFilters, + newSubfieldValue: newFilters, + }); + } + + const oldSavedQueryId = stringifyToSortedJson( + oldFieldValue?.type === 'saved_query' ? oldFieldValue?.saved_query_id : '' + ); + const newSavedQueryId = stringifyToSortedJson( + newFieldValue?.type === 'saved_query' ? newFieldValue?.saved_query_id : '' + ); + + if (oldSavedQueryId !== newSavedQueryId) { + changes.push({ + subfieldName: 'saved_query_id', + oldSubfieldValue: oldSavedQueryId, + newSubfieldValue: newSavedQueryId, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/rule_name_override.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/rule_name_override.ts new file mode 100644 index 0000000000000..805b0dffeb8b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/rule_name_override.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForRuleNameOverride = ( + oldFieldValue?: DiffableAllFields['rule_name_override'], + newFieldValue?: DiffableAllFields['rule_name_override'] +): SubfieldChange[] => { + const oldFieldName = stringifyToSortedJson(oldFieldValue?.field_name); + const newFieldName = stringifyToSortedJson(newFieldValue?.field_name); + + if (oldFieldName !== newFieldName) { + return [ + { + subfieldName: 'field_name', + oldSubfieldValue: oldFieldName, + newSubfieldValue: newFieldName, + }, + ]; + } + + return []; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/rule_schedule.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/rule_schedule.ts new file mode 100644 index 0000000000000..8bbf0c9235257 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/rule_schedule.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForRuleSchedule = ( + oldFieldValue?: DiffableAllFields['rule_schedule'], + newFieldValue?: DiffableAllFields['rule_schedule'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + if (oldFieldValue?.interval !== newFieldValue?.interval) { + changes.push({ + subfieldName: 'interval', + oldSubfieldValue: stringifyToSortedJson(oldFieldValue?.interval), + newSubfieldValue: stringifyToSortedJson(newFieldValue?.interval), + }); + } + + if (oldFieldValue?.lookback !== newFieldValue?.lookback) { + changes.push({ + subfieldName: 'lookback', + oldSubfieldValue: stringifyToSortedJson(oldFieldValue?.lookback), + newSubfieldValue: stringifyToSortedJson(newFieldValue?.lookback), + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/threat_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/threat_query.ts new file mode 100644 index 0000000000000..a9264d1624f5a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/threat_query.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForThreatQuery = ( + oldFieldValue?: DiffableAllFields['threat_query'], + newFieldValue?: DiffableAllFields['threat_query'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldQuery = stringifyToSortedJson(oldFieldValue?.query); + const newQuery = stringifyToSortedJson(newFieldValue?.query); + + if (oldQuery !== newQuery) { + changes.push({ + subfieldName: 'query', + oldSubfieldValue: oldQuery, + newSubfieldValue: newQuery, + }); + } + + const oldLanguage = stringifyToSortedJson(oldFieldValue?.language); + const newLanguage = stringifyToSortedJson(newFieldValue?.language); + + if (oldLanguage !== newLanguage) { + changes.push({ + subfieldName: 'language', + oldSubfieldValue: oldLanguage, + newSubfieldValue: newLanguage, + }); + } + + const oldFilters = stringifyToSortedJson(oldFieldValue?.filters); + const newFilters = stringifyToSortedJson(newFieldValue?.filters); + + if (oldFilters !== newFilters) { + changes.push({ + subfieldName: 'filters', + oldSubfieldValue: oldFilters, + newSubfieldValue: newFilters, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/threshold.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/threshold.ts new file mode 100644 index 0000000000000..5dd07096c0633 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/threshold.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForThreshold = ( + oldFieldValue?: DiffableAllFields['threshold'], + newFieldValue?: DiffableAllFields['threshold'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldField = stringifyToSortedJson(oldFieldValue?.field); + const newField = stringifyToSortedJson(newFieldValue?.field); + + if (oldField !== newField) { + changes.push({ + subfieldName: 'field', + oldSubfieldValue: oldField, + newSubfieldValue: newField, + }); + } + + const oldValue = stringifyToSortedJson(oldFieldValue?.value); + const newValue = stringifyToSortedJson(newFieldValue?.value); + + if (oldValue !== newValue) { + changes.push({ + subfieldName: 'value', + oldSubfieldValue: oldValue, + newSubfieldValue: newValue, + }); + } + + const oldCardinality = stringifyToSortedJson(oldFieldValue?.cardinality); + const newCardinality = stringifyToSortedJson(newFieldValue?.cardinality); + + if (oldCardinality !== newCardinality) { + changes.push({ + subfieldName: 'cardinality', + oldSubfieldValue: oldCardinality, + newSubfieldValue: newCardinality, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/timeline_template.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/timeline_template.ts new file mode 100644 index 0000000000000..502d093374b8d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/timeline_template.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForTimelineTemplate = ( + oldFieldValue?: DiffableAllFields['timeline_template'], + newFieldValue?: DiffableAllFields['timeline_template'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldTimelineId = stringifyToSortedJson(oldFieldValue?.timeline_id); + const newTimelineId = stringifyToSortedJson(newFieldValue?.timeline_id); + + if (oldTimelineId !== newTimelineId) { + changes.push({ + subfieldName: 'timeline_id', + oldSubfieldValue: oldTimelineId, + newSubfieldValue: newTimelineId, + }); + } + + const oldTimelineTitle = stringifyToSortedJson(oldFieldValue?.timeline_title); + const newTimelineTitle = stringifyToSortedJson(newFieldValue?.timeline_title); + + if (oldTimelineTitle !== newTimelineTitle) { + changes.push({ + subfieldName: 'timeline_title', + oldSubfieldValue: oldTimelineTitle, + newSubfieldValue: newTimelineTitle, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/timestamp_override.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/timestamp_override.ts new file mode 100644 index 0000000000000..51d7d0caad64f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/get_subfield_changes/timestamp_override.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 { stringifyToSortedJson } from '../utils'; +import type { DiffableAllFields } from '../../../../../../../../common/api/detection_engine'; +import type { SubfieldChange } from '../types'; + +export const getSubfieldChangesForTimestampOverride = ( + oldFieldValue?: DiffableAllFields['timestamp_override'], + newFieldValue?: DiffableAllFields['timestamp_override'] +): SubfieldChange[] => { + const changes: SubfieldChange[] = []; + + const oldFieldName = stringifyToSortedJson(oldFieldValue?.field_name); + const newFieldName = stringifyToSortedJson(newFieldValue?.field_name); + + if (oldFieldName !== newFieldName) { + changes.push({ + subfieldName: 'field_name', + oldSubfieldValue: oldFieldName, + newSubfieldValue: newFieldName, + }); + } + + const oldVersionFallbackDisabled = stringifyToSortedJson(oldFieldValue?.fallback_disabled); + const newVersionFallbackDisabled = stringifyToSortedJson(newFieldValue?.fallback_disabled); + + if (oldVersionFallbackDisabled !== newVersionFallbackDisabled) { + changes.push({ + subfieldName: 'fallback_disabled', + oldSubfieldValue: oldVersionFallbackDisabled, + newSubfieldValue: newVersionFallbackDisabled, + }); + } + + return changes; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/no_changes.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/no_changes.tsx new file mode 100644 index 0000000000000..4cd86c69a9ca4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/no_changes.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 { EuiText } from '@elastic/eui'; +import * as i18n from './translations'; + +export function NoChanges() { + return ( + <EuiText size="s" color="subdued"> + {i18n.NO_CHANGES} + </EuiText> + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield.tsx new file mode 100644 index 0000000000000..d1df52f93c7c7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield.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 type { DiffableAllFields } from '../../../../../../../common/api/detection_engine'; +import { DiffView } from '../../json_diff/diff_view'; +import { SubfieldHeader } from './subfield_header'; +import { FIELDS_WITH_SUBFIELDS } from './constants'; + +function shouldDisplaySubfieldLabelForField(fieldName: keyof DiffableAllFields): boolean { + return FIELDS_WITH_SUBFIELDS.includes(fieldName); +} + +interface SubfieldProps { + fieldName: keyof DiffableAllFields; + subfieldName: string; + oldSubfieldValue: string; + newSubfieldValue: string; +} + +export const Subfield = ({ + fieldName, + subfieldName, + oldSubfieldValue, + newSubfieldValue, +}: SubfieldProps) => ( + <> + {shouldDisplaySubfieldLabelForField(fieldName) && ( + <SubfieldHeader subfieldName={subfieldName} /> + )} + <DiffView oldSource={oldSubfieldValue} newSource={newSubfieldValue} viewType="unified" /> + </> +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield_changes.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield_changes.tsx new file mode 100644 index 0000000000000..af10edbe1c599 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield_changes.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 { EuiHorizontalRule } from '@elastic/eui'; +import type { SubfieldChanges } from './types'; +import { Subfield } from './subfield'; +import type { DiffableAllFields } from '../../../../../../../common/api/detection_engine'; +import { NoChanges } from './no_changes'; + +interface SubfieldChangesProps { + fieldName: keyof DiffableAllFields; + subfieldChanges: SubfieldChanges; +} + +export function SubfieldChanges({ fieldName, subfieldChanges }: SubfieldChangesProps) { + if (subfieldChanges.length === 0) { + return <NoChanges />; + } + + return ( + <> + {subfieldChanges.map((change, index) => { + const shouldShowSeparator = index !== subfieldChanges.length - 1; + + return ( + <> + <Subfield + key={change.subfieldName} + fieldName={fieldName} + subfieldName={change.subfieldName} + oldSubfieldValue={change.oldSubfieldValue} + newSubfieldValue={change.newSubfieldValue} + /> + {shouldShowSeparator ? <EuiHorizontalRule margin="s" size="full" /> : null} + </> + ); + })} + </> + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield_header.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield_header.tsx new file mode 100644 index 0000000000000..9eb14188364b2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/subfield_header.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 { camelCase, startCase } from 'lodash'; +import { EuiTitle, EuiSpacer } from '@elastic/eui'; +import { fieldToDisplayNameMap } from '../../diff_components/translations'; + +interface SubfieldHeaderProps { + subfieldName: string; +} + +export function SubfieldHeader({ subfieldName }: SubfieldHeaderProps) { + const subfieldLabel = fieldToDisplayNameMap[subfieldName] ?? startCase(camelCase(subfieldName)); + + return ( + <> + <EuiTitle size="xxxs"> + <h4>{subfieldLabel}</h4> + </EuiTitle> + <EuiSpacer size="s" /> + </> + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/translations.ts new file mode 100644 index 0000000000000..d60c78646b5ad --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/translations.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 { i18n } from '@kbn/i18n'; + +export const NO_CHANGES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.upgradeRules.comparisonSide.noChangesLabel', + { + defaultMessage: 'No changes', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/types.ts new file mode 100644 index 0000000000000..f6e4b7f5d1664 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface SubfieldChange { + readonly subfieldName: string; + readonly oldSubfieldValue: string; + readonly newSubfieldValue: string; +} + +export type SubfieldChanges = Readonly<SubfieldChange[]>; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/utils.ts new file mode 100644 index 0000000000000..fd16366f1a76e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/comparison_side/utils.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 stringify from 'json-stable-stringify'; +import { Version } from '../versions_picker/constants'; +import type { + DiffableAllFields, + ThreeWayDiff, +} from '../../../../../../../common/api/detection_engine'; + +/** + * Picks the field value for a given version either from a three-way diff object or from a user-set resolved value. + * + * @param version - The version for which the field value is to be picked. + * @param fieldThreeWayDiff - The three-way diff object containing the field values for different versions. + * @param resolvedValue - The user-set resolved value resolved value. Used if it is set and the version is final. + * @returns - The field value for the specified version + */ +export function pickFieldValueForVersion<FieldName extends keyof DiffableAllFields>( + version: Version, + fieldThreeWayDiff: ThreeWayDiff<DiffableAllFields[FieldName]>, + resolvedValue?: DiffableAllFields[FieldName] +): DiffableAllFields[FieldName] | undefined { + if (version === Version.Final) { + return resolvedValue ?? fieldThreeWayDiff.merged_version; + } + + const versionFieldToPick = `${version}_version` as const; + return fieldThreeWayDiff[versionFieldToPick]; +} + +/** + * Stringifies a field value to an alphabetically sorted JSON string. + */ +export const stringifyToSortedJson = (fieldValue: unknown): string => { + if (fieldValue === undefined) { + return ''; + } + + if (typeof fieldValue === 'string') { + return fieldValue; + } + + return stringify(fieldValue, { space: 2 }); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/versions_picker/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/versions_picker/constants.ts index ca8b0c75e1727..73a7e89a200a3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/versions_picker/constants.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/versions_picker/constants.ts @@ -8,6 +8,13 @@ import type { EuiSelectOption } from '@elastic/eui'; import * as i18n from './translations'; +export enum Version { + Base = 'base', + Current = 'current', + Target = 'target', + Final = 'final', +} + export enum SelectedVersions { BaseTarget = 'base_target', BaseCurrent = 'base_current', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_secuirty_packages.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_secuirty_packages.test.tsx deleted file mode 100644 index 9454a1c4dfb16..0000000000000 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_secuirty_packages.test.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 { epmRouteService } from '@kbn/fleet-plugin/common'; -import { renderHook } from '@testing-library/react-hooks'; -import { useKibana, KibanaServices } from '../../../common/lib/kibana'; -import { TestProviders } from '../../../common/mock'; -import { useUpgradeSecurityPackages } from './use_upgrade_security_packages'; - -jest.mock('../../../common/components/user_privileges', () => { - return { - useUserPrivileges: jest.fn().mockReturnValue({ - endpointPrivileges: { - canAccessFleet: true, - }, - }), - }; -}); -jest.mock('../../../common/lib/kibana'); - -const mockGetPrebuiltRulesPackageVersion = - KibanaServices.getPrebuiltRulesPackageVersion as jest.Mock; -const mockGetKibanaVersion = KibanaServices.getKibanaVersion as jest.Mock; -const mockGetKibanaBranch = KibanaServices.getKibanaBranch as jest.Mock; -const mockBuildFlavor = KibanaServices.getBuildFlavor as jest.Mock; -const useKibanaMock = useKibana as jest.MockedFunction<typeof useKibana>; - -describe('When using the `useUpgradeSecurityPackages()` hook', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should call fleet setup first via `isInitialized()` and then send upgrade request', async () => { - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - expect(useKibanaMock().services.fleet?.isInitialized).toHaveBeenCalled(); - expect(useKibanaMock().services.http.post).not.toHaveBeenCalled(); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - }) - ); - }); - - it('should send upgrade request with prerelease:false if in serverless', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('serverless'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: false }), - }) - ); - }); - - it('should send upgrade request with prerelease:false if in serverless SNAPSHOT', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0-SNAPSHOT'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('serverless'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: false }), - }) - ); - }); - - it('should send upgrade request with prerelease:false if build does not include `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0'); - mockGetKibanaBranch.mockReturnValue('release'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: false }), - }) - ); - }); - - it('should send upgrade request with prerelease:true if not serverless and branch is `main` AND build includes `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0-SNAPSHOT'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); - - it('should send upgrade request with prerelease:true if branch is `release` and build includes `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0-SNAPSHOT'); - mockGetKibanaBranch.mockReturnValue('release'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); - - it('should send upgrade request with prerelease:true if branch is `main` and build does not include `-SNAPSHOT`', async () => { - mockGetKibanaVersion.mockReturnValue('8.0.0'); - mockGetKibanaBranch.mockReturnValue('main'); - mockBuildFlavor.mockReturnValue('traditional'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenCalledWith( - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: '{"packages":["endpoint","security_detection_engine"]}', - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); - - it('should send separate upgrade requests if prebuiltRulesPackageVersion is provided', async () => { - mockGetPrebuiltRulesPackageVersion.mockReturnValue('8.2.1'); - - const { waitFor } = renderHook(() => useUpgradeSecurityPackages(), { - wrapper: TestProviders, - }); - - await waitFor(() => (useKibanaMock().services.http.post as jest.Mock).mock.calls.length > 0); - - expect(useKibanaMock().services.http.post).toHaveBeenNthCalledWith( - 1, - `${epmRouteService.getInstallPath('security_detection_engine', '8.2.1')}`, - expect.objectContaining({ query: { prerelease: true } }) - ); - expect(useKibanaMock().services.http.post).toHaveBeenNthCalledWith( - 2, - `${epmRouteService.getBulkInstallPath()}`, - expect.objectContaining({ - body: expect.stringContaining('endpoint'), - query: expect.objectContaining({ prerelease: true }), - }) - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts index 8ad266169231d..e19cf2bcacf94 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_upgrade_security_packages.ts @@ -7,88 +7,29 @@ import { useIsMutating } from '@tanstack/react-query'; import { useEffect } from 'react'; -import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../common/detection_engine/constants'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; -import { KibanaServices, useKibana } from '../../../common/lib/kibana'; -import type { BulkInstallFleetPackagesProps, InstallFleetPackageProps } from '../api/api'; import { - BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY, - useBulkInstallFleetPackagesMutation, -} from '../api/hooks/use_bulk_install_fleet_packages_mutation'; -import { - INSTALL_FLEET_PACKAGE_MUTATION_KEY, - useInstallFleetPackageMutation, -} from '../api/hooks/use_install_fleet_package_mutation'; + BOOTSTRAP_PREBUILT_RULES_KEY, + useBootstrapPrebuiltRulesMutation, +} from '../api/hooks/use_bootstrap_prebuilt_rules'; /** * Install or upgrade the security packages (endpoint and prebuilt rules) */ export const useUpgradeSecurityPackages = () => { - const context = useKibana(); - const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet; - const { mutate: bulkInstallFleetPackages } = useBulkInstallFleetPackagesMutation(); - const { mutate: installFleetPackage } = useInstallFleetPackageMutation(); + const { mutate: bootstrapPrebuiltRules } = useBootstrapPrebuiltRulesMutation(); useEffect(() => { - if (!canAccessFleet) { - return; - } - - (async () => { - // Make sure fleet is initialized first - await context.services.fleet?.isInitialized(); - - // Install the latest prerelease if in non-production non-serverless environments - const prerelease = - KibanaServices.getBuildFlavor() === 'traditional' && - (KibanaServices.getKibanaVersion().includes('-SNAPSHOT') || - KibanaServices.getKibanaBranch() === 'main'); - - const prebuiltRulesPackageVersion = KibanaServices.getPrebuiltRulesPackageVersion(); - // ignore the response for now since we aren't notifying the user - const packages = ['endpoint', PREBUILT_RULES_PACKAGE_NAME]; - - // If `prebuiltRulesPackageVersion` is provided, try to install that version - // Must be done as two separate requests as bulk API doesn't support versions - if (prebuiltRulesPackageVersion != null) { - installFleetPackage({ - packageName: PREBUILT_RULES_PACKAGE_NAME, - packageVersion: prebuiltRulesPackageVersion, - prerelease, - force: true, - }); - packages.splice(packages.indexOf(PREBUILT_RULES_PACKAGE_NAME), 1); - } - - // Note: if `prerelease:true` option is provided, endpoint package will also be installed as prerelease - bulkInstallFleetPackages({ - packages, - prerelease, - }); - })(); - }, [bulkInstallFleetPackages, canAccessFleet, context.services.fleet, installFleetPackage]); + bootstrapPrebuiltRules(); + }, [bootstrapPrebuiltRules]); }; /** * @returns true if the security packages are being installed or upgraded */ export const useIsUpgradingSecurityPackages = () => { - const isInstallingPackages = useIsMutating({ - predicate: ({ options: { mutationKey }, state: { variables } }) => { - // The mutation is bulk Fleet packages installation. Check if the packages include the prebuilt rules package - if (mutationKey === BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY) { - return (variables as BulkInstallFleetPackagesProps).packages.includes( - PREBUILT_RULES_PACKAGE_NAME - ); - } - - // The mutation is single Fleet package installation. Check if the package is the prebuilt rules package - if (mutationKey === INSTALL_FLEET_PACKAGE_MUTATION_KEY) { - return (variables as InstallFleetPackageProps).packageName === PREBUILT_RULES_PACKAGE_NAME; - } - return false; - }, + const bootstrappingRules = useIsMutating({ + mutationKey: BOOTSTRAP_PREBUILT_RULES_KEY, }); - return isInstallingPackages > 0; + return bootstrappingRules > 0; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts index 51b599028156d..95fc300a3fe57 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/get_supported_response_actions.ts @@ -24,12 +24,10 @@ interface EnabledFeatures { export const getSupportedResponseActions = ( actionTypes: ResponseActionType[], - enabledFeatures: EnabledFeatures, userPermissions: EnabledFeatures ): ResponseActionType[] => actionTypes.reduce((acc: ResponseActionType[], actionType) => { const isEndpointAction = actionType.id === ResponseActionTypesEnum['.endpoint']; - if (!enabledFeatures.endpoint && isEndpointAction) return acc; if (ResponseActionTypes.options.includes(actionType.id)) return [ ...acc, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/use_supported_response_action_types.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/use_supported_response_action_types.tsx index aed3d0302f05c..5d6f7a01bf076 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/use_supported_response_action_types.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/use_supported_response_action_types.tsx @@ -7,7 +7,6 @@ import { useEffect, useMemo, useState } from 'react'; import { useUserPrivileges } from '../../common/components/user_privileges'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import type { ResponseActionType } from './get_supported_response_actions'; import { getSupportedResponseActions, responseActionTypes } from './get_supported_response_actions'; @@ -16,15 +15,8 @@ export const useSupportedResponseActionTypes = () => { ResponseActionType[] | undefined >(); - const isEndpointEnabled = useIsExperimentalFeatureEnabled('endpointResponseActionsEnabled'); const { canIsolateHost, canKillProcess, canSuspendProcess } = useUserPrivileges().endpointPrivileges; - const enabledFeatures = useMemo( - () => ({ - endpoint: isEndpointEnabled, - }), - [isEndpointEnabled] - ); const userHasPermissionsToExecute = useMemo( () => ({ @@ -36,11 +28,10 @@ export const useSupportedResponseActionTypes = () => { useEffect(() => { const supportedTypes = getSupportedResponseActions( responseActionTypes, - enabledFeatures, userHasPermissionsToExecute ); setSupportedResponseActionTypes(supportedTypes); - }, [isEndpointEnabled, enabledFeatures, userHasPermissionsToExecute]); + }, [userHasPermissionsToExecute]); return supportedResponseActionTypes; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/alerts_by_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/alerts_by_rule.test.tsx new file mode 100644 index 0000000000000..a07ba04cefcbd --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/alerts_by_rule.test.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 { act, render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { AlertsByRule } from './alerts_by_rule'; +import { parsedAlerts } from './mock_rule_data'; + +jest.mock('../../../../common/lib/kibana'); + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); + +describe('Alert by rule chart', () => { + const defaultProps = { + data: [], + isLoading: false, + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('renders table correctly without data', () => { + act(() => { + const { container } = render( + <TestProviders> + <AlertsByRule {...defaultProps} /> + </TestProviders> + ); + expect( + container.querySelector('[data-test-subj="alerts-by-rule-table"]') + ).toBeInTheDocument(); + expect( + container.querySelector('[data-test-subj="alerts-by-rule-table"] tbody')?.textContent + ).toEqual('No items found'); + }); + }); + + test('renders table correctly with data', () => { + act(() => { + const { queryAllByRole } = render( + <TestProviders> + <AlertsByRule data={parsedAlerts} isLoading={false} /> + </TestProviders> + ); + + parsedAlerts.forEach((_, i) => { + expect(queryAllByRole('row')[i + 1].textContent).toContain(parsedAlerts[i].rule); + expect(queryAllByRole('row')[i + 1].textContent).toContain( + parsedAlerts[i].value.toString() + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/alerts_by_rule.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/alerts_by_rule.tsx new file mode 100644 index 0000000000000..d213f3026f4e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/alerts_by_rule.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 type { EuiBasicTableColumn } from '@elastic/eui'; +import { EuiInMemoryTable, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; +import { TableId } from '@kbn/securitysolution-data-table'; +import type { AlertsByRuleData } from './types'; +import { FormattedCount } from '../../../../common/components/formatted_number'; +import { DefaultDraggable } from '../../../../common/components/draggables'; +import { ALERTS_HEADERS_RULE_NAME } from '../../alerts_table/translations'; +import { COUNT_TABLE_TITLE } from '../alerts_count_panel/translations'; + +const Wrapper = styled.div` + margin-top: -${({ theme }) => theme.eui.euiSizeM}; +`; +const TableWrapper = styled.div` + height: 178px; +`; + +export interface AlertsByRuleProps { + data: AlertsByRuleData[]; + isLoading: boolean; +} + +const COLUMNS: Array<EuiBasicTableColumn<AlertsByRuleData>> = [ + { + field: 'rule', + name: ALERTS_HEADERS_RULE_NAME, + 'data-test-subj': 'alert-by-rule-table-rule-name', + truncateText: true, + render: (rule: string) => ( + <EuiText size="xs" className="eui-textTruncate"> + <DefaultDraggable + isDraggable={false} + field={ALERT_RULE_NAME} + hideTopN={true} + id={`alert-detection-draggable-${rule}`} + value={rule} + queryValue={rule} + tooltipContent={null} + truncate={true} + scopeId={TableId.alertsOnAlertsPage} + /> + </EuiText> + ), + }, + { + field: 'value', + name: COUNT_TABLE_TITLE, + dataType: 'number', + sortable: true, + 'data-test-subj': 'alert-by-rule-table-count', + render: (count: number) => ( + <EuiText grow={false} size="xs"> + <FormattedCount count={count} /> + </EuiText> + ), + width: '22%', + }, +]; + +const SORTING: { sort: { field: keyof AlertsByRuleData; direction: SortOrder } } = { + sort: { + field: 'value', + direction: 'desc', + }, +}; + +const PAGINATION: {} = { + pageSize: 25, + showPerPageOptions: false, +}; + +export const AlertsByRule: React.FC<AlertsByRuleProps> = ({ data, isLoading }) => { + return ( + <Wrapper data-test-subj="alerts-by-rule"> + <EuiSpacer size="xs" /> + <TableWrapper className="eui-yScroll"> + <EuiInMemoryTable + data-test-subj="alerts-by-rule-table" + columns={COLUMNS} + items={data} + loading={isLoading} + sorting={SORTING} + pagination={PAGINATION} + /> + </TableWrapper> + </Wrapper> + ); +}; + +AlertsByRule.displayName = 'AlertsByRule'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/helpers.test.tsx new file mode 100644 index 0000000000000..00ba5b3edb360 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/helpers.test.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 { parseAlertsRuleData, getIsAlertsByRuleData, getIsAlertsByRuleAgg } from './helpers'; +import * as mockRule from './mock_rule_data'; +import type { AlertsByRuleAgg } from './types'; +import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; + +describe('parse alerts by rule data', () => { + test('parse alerts with data', () => { + const res = parseAlertsRuleData( + mockRule.mockAlertsData as AlertSearchResponse<{}, AlertsByRuleAgg> + ); + expect(res).toEqual(mockRule.parsedAlerts); + }); + + test('parse alerts without data', () => { + const res = parseAlertsRuleData( + mockRule.mockAlertsEmptyData as AlertSearchResponse<{}, AlertsByRuleAgg> + ); + expect(res).toEqual([]); + }); +}); + +describe('get is alerts by rule data', () => { + test('should return true for rule data', () => { + expect(getIsAlertsByRuleData(mockRule.parsedAlerts)).toBe(true); + }); + + test('should return false for non rule data', () => { + expect(getIsAlertsByRuleData([{ key: 'low', value: 1 }])).toBe(false); + }); + + test('should return false for empty array', () => { + expect(getIsAlertsByRuleData([])).toBe(false); + }); +}); + +describe('get is alerts by rule agg', () => { + test('return true for rule aggregation query', () => { + expect(getIsAlertsByRuleAgg(mockRule.mockAlertsData)).toBe(true); + }); + + test('return false for queries without alertByRule key', () => { + expect(getIsAlertsByRuleAgg({ ...mockRule.mockAlertsEmptyData, aggregations: {} })).toBe(false); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/helpers.tsx new file mode 100644 index 0000000000000..c2c6d8e95d49f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/helpers.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 { has } from 'lodash'; +import type { AlertsByRuleData, AlertsByRuleAgg } from './types'; +import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; +import type { SummaryChartsData, SummaryChartsAgg } from '../alerts_summary_charts_panel/types'; + +export const parseAlertsRuleData = ( + response: AlertSearchResponse<{}, AlertsByRuleAgg> +): AlertsByRuleData[] => { + const rulesBuckets = response?.aggregations?.alertsByRule?.buckets ?? []; + + return rulesBuckets.length === 0 + ? [] + : rulesBuckets.map((rule) => { + return { + rule: rule.key, + value: rule.doc_count, + }; + }); +}; + +export const getIsAlertsByRuleData = (data: SummaryChartsData[]): data is AlertsByRuleData[] => { + return data.length > 0 && data.every((x) => has(x, 'rule')); +}; + +export const getIsAlertsByRuleAgg = ( + data: AlertSearchResponse<{}, SummaryChartsAgg> +): data is AlertSearchResponse<{}, AlertsByRuleAgg> => { + return has(data, 'aggregations.alertsByRule'); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/index.test.tsx new file mode 100644 index 0000000000000..387df74772f02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/index.test.tsx @@ -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 { act, render } from '@testing-library/react'; +import React from 'react'; +import { TestProviders } from '../../../../common/mock'; +import { AlertsByRulePanel } from '.'; + +jest.mock('../../../../common/lib/kibana'); + +jest.mock('react-router-dom', () => { + const actual = jest.requireActual('react-router-dom'); + return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; +}); + +describe('Alert by rule panel', () => { + const defaultProps = { + signalIndexName: 'signalIndexName', + skip: false, + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('renders correctly', async () => { + await act(async () => { + const { container } = render( + <TestProviders> + <AlertsByRulePanel {...defaultProps} /> + </TestProviders> + ); + expect( + container.querySelector('[data-test-subj="alerts-by-rule-panel"]') + ).toBeInTheDocument(); + }); + }); + + test('renders HeaderSection', async () => { + await act(async () => { + const { container } = render( + <TestProviders> + <AlertsByRulePanel {...defaultProps} /> + </TestProviders> + ); + expect(container.querySelector(`[data-test-subj="header-section"]`)).toBeInTheDocument(); + }); + }); + + test('renders inspect button', async () => { + await act(async () => { + const { container } = render( + <TestProviders> + <AlertsByRulePanel {...defaultProps} /> + </TestProviders> + ); + expect(container.querySelector('[data-test-subj="inspect-icon-button"]')).toBeInTheDocument(); + }); + }); + + test('renders alert by rule chart', async () => { + await act(async () => { + const { container } = render( + <TestProviders> + <AlertsByRulePanel {...defaultProps} /> + </TestProviders> + ); + expect(container.querySelector('[data-test-subj="alerts-by-rule"]')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/index.tsx new file mode 100644 index 0000000000000..98e98698f6083 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/index.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 { EuiPanel } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { v4 as uuid } from 'uuid'; +import type { ChartsPanelProps } from '../alerts_summary_charts_panel/types'; +import { AlertsByRule } from './alerts_by_rule'; +import { HeaderSection } from '../../../../common/components/header_section'; +import { InspectButtonContainer } from '../../../../common/components/inspect'; +import { useSummaryChartData } from '../alerts_summary_charts_panel/use_summary_chart_data'; +import { alertRuleAggregations } from '../alerts_summary_charts_panel/aggregations'; +import { getIsAlertsByRuleData } from './helpers'; +import * as i18n from './translations'; + +const ALERTS_BY_TYPE_CHART_ID = 'alerts-summary-alert_by_type'; + +export const AlertsByRulePanel: React.FC<ChartsPanelProps> = ({ + filters, + query, + signalIndexName, + runtimeMappings, + skip, +}) => { + const uniqueQueryId = useMemo(() => `${ALERTS_BY_TYPE_CHART_ID}-${uuid()}`, []); + + const { items, isLoading } = useSummaryChartData({ + aggregations: alertRuleAggregations, + filters, + query, + signalIndexName, + runtimeMappings, + skip, + uniqueQueryId, + }); + const data = useMemo(() => (getIsAlertsByRuleData(items) ? items : []), [items]); + + return ( + <InspectButtonContainer> + <EuiPanel hasBorder hasShadow={false} data-test-subj="alerts-by-rule-panel"> + <HeaderSection + id={uniqueQueryId} + inspectTitle={i18n.ALERTS_RULE_TITLE} + outerDirection="row" + title={i18n.ALERTS_RULE_TITLE} + titleSize="xs" + hideSubtitle + /> + <AlertsByRule data={data} isLoading={isLoading} /> + </EuiPanel> + </InspectButtonContainer> + ); +}; + +AlertsByRulePanel.displayName = 'AlertsByRulePanel'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/mock_rule_data.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/mock_rule_data.ts similarity index 85% rename from x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/mock_rule_data.ts rename to x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/mock_rule_data.ts index 62dc3f3f885f1..42d743004bec9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/mock_rule_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/mock_rule_data.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { AlertsTypeData } from './types'; +import type { AlertsByRuleData } from './types'; const from = '2022-04-05T12:00:00.000Z'; const to = '2022-04-08T12:00:00.000Z'; @@ -102,8 +102,8 @@ export const query = { runtime_mappings: undefined, }; -export const parsedAlerts: AlertsTypeData[] = [ - { rule: 'Test rule 1', type: 'Detection', value: 537, color: '#D36086' }, - { rule: 'Test rule 2', type: 'Detection', value: 27, color: '#D36086' }, - { rule: 'Test rule 3', type: 'Detection', value: 25, color: '#D36086' }, +export const parsedAlerts: AlertsByRuleData[] = [ + { rule: 'Test rule 1', value: 537 }, + { rule: 'Test rule 2', value: 27 }, + { rule: 'Test rule 3', value: 25 }, ]; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/translations.ts new file mode 100644 index 0000000000000..5664f01691821 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/translations.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 { i18n } from '@kbn/i18n'; + +export const ALERTS_RULE_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.alertsByType.alertRuleChartTitle', + { + defaultMessage: 'Alerts by name', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/types.ts new file mode 100644 index 0000000000000..7cda2fa27df26 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_rule_panel/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 type { BucketItem } from '../../../../../common/search_strategy/security_solution/cti'; + +export interface AlertsByRuleAgg { + alertsByRule: { + doc_count_error_upper_bound: number; + sum_other_doc_count: number; + buckets: BucketItem[]; + }; +} + +export interface AlertsByRuleData { + rule: string; + value: number; +} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/alerts_by_type.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/alerts_by_type.test.tsx deleted file mode 100644 index 59ceb50ec227e..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/alerts_by_type.test.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 { act, render } from '@testing-library/react'; -import React from 'react'; -import { TestProviders } from '../../../../common/mock'; -import { AlertsByType } from './alerts_by_type'; -import { parsedAlerts } from './mock_type_data'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; - -const display = 'alerts-by-type-palette-display'; - -jest.mock('../../../../common/lib/kibana'); - -jest.mock('react-router-dom', () => { - const actual = jest.requireActual('react-router-dom'); - return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; -}); - -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -jest.mock('../../../../common/hooks/use_experimental_features'); - -describe('Alert by type chart', () => { - const defaultProps = { - data: [], - isLoading: false, - }; - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('isAlertTypeEnabled flag is true', () => { - beforeEach(() => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - }); - - test('renders health and pallette display correctly without data', () => { - act(() => { - const { container } = render( - <TestProviders> - <AlertsByType {...defaultProps} /> - </TestProviders> - ); - expect(container.querySelector(`[data-test-subj="${display}"]`)).toBeInTheDocument(); - expect(container.querySelector(`[data-test-subj="${display}"]`)?.textContent).toContain( - 'Detection:0' - ); - expect(container.querySelector(`[data-test-subj="${display}"]`)?.textContent).toContain( - 'Prevention:0' - ); - }); - }); - - test('renders table correctly without data', () => { - act(() => { - const { container } = render( - <TestProviders> - <AlertsByType {...defaultProps} /> - </TestProviders> - ); - expect( - container.querySelector('[data-test-subj="alerts-by-type-table"]') - ).toBeInTheDocument(); - expect( - container.querySelector('[data-test-subj="alerts-by-type-table"] tbody')?.textContent - ).toEqual('No items found'); - }); - }); - - test('renders health and pallette display correctly with data', () => { - act(() => { - const { container } = render( - <TestProviders> - <AlertsByType data={parsedAlerts} isLoading={false} /> - </TestProviders> - ); - expect(container.querySelector(`[data-test-subj="${display}"]`)).toBeInTheDocument(); - expect(container.querySelector(`[data-test-subj="${display}"]`)?.textContent).toContain( - 'Detection:583' - ); - expect(container.querySelector(`[data-test-subj="${display}"]`)?.textContent).toContain( - 'Prevention:6' - ); - }); - }); - - test('renders table correctly with data', () => { - act(() => { - const { queryAllByRole } = render( - <TestProviders> - <AlertsByType data={parsedAlerts} isLoading={false} /> - </TestProviders> - ); - - parsedAlerts.forEach((_, i) => { - expect(queryAllByRole('row')[i + 1].textContent).toContain(parsedAlerts[i].rule); - expect(queryAllByRole('row')[i + 1].textContent).toContain(parsedAlerts[i].type); - expect(queryAllByRole('row')[i + 1].textContent).toContain( - parsedAlerts[i].value.toString() - ); - }); - }); - }); - }); - - describe('isAlertTypeEnabled flag is false', () => { - beforeEach(() => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - }); - - test('do not renders health and pallette display correctly without data', () => { - act(() => { - const { container } = render( - <TestProviders> - <AlertsByType {...defaultProps} /> - </TestProviders> - ); - expect(container.querySelector(`[data-test-subj="${display}"]`)).not.toBeInTheDocument(); - }); - }); - - test('renders table correctly without data', () => { - act(() => { - const { container } = render( - <TestProviders> - <AlertsByType {...defaultProps} /> - </TestProviders> - ); - expect( - container.querySelector('[data-test-subj="alerts-by-type-table"]') - ).toBeInTheDocument(); - expect( - container.querySelector('[data-test-subj="alerts-by-type-table"] tbody')?.textContent - ).toEqual('No items found'); - }); - }); - - test('do not renders health and pallette display correctly with data', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - act(() => { - const { container } = render( - <TestProviders> - <AlertsByType data={parsedAlerts} isLoading={false} /> - </TestProviders> - ); - expect(container.querySelector(`[data-test-subj="${display}"]`)).not.toBeInTheDocument(); - }); - }); - - test('renders table correctly with data', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - act(() => { - const { queryAllByRole } = render( - <TestProviders> - <AlertsByType data={parsedAlerts} isLoading={false} /> - </TestProviders> - ); - - parsedAlerts.forEach((_, i) => { - expect(queryAllByRole('row')[i + 1].textContent).toContain(parsedAlerts[i].rule); - expect(queryAllByRole('row')[i + 1].textContent).toContain( - parsedAlerts[i].value.toString() - ); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/alerts_by_type.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/alerts_by_type.tsx deleted file mode 100644 index 5208e74470fa8..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/alerts_by_type.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - EuiFlexItem, - EuiInMemoryTable, - EuiColorPaletteDisplay, - EuiSpacer, - EuiFlexGroup, - EuiHealth, - EuiText, -} from '@elastic/eui'; -import React, { useMemo } from 'react'; -import styled from 'styled-components'; -import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { AlertsTypeData, AlertType } from './types'; -import { FormattedCount } from '../../../../common/components/formatted_number'; -import { getAlertsTypeTableColumns } from './columns'; -import { ALERT_TYPE_COLOR } from './helpers'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; - -const Wrapper = styled.div` - margin-top: -${({ theme }) => theme.eui.euiSizeM}; -`; -const TableWrapper = styled.div` - height: 178px; -`; -const StyledEuiColorPaletteDisplay = styled(EuiColorPaletteDisplay)` - border: none; - border-radius: 0; -`; -interface PalletteObject { - stop: number; - color: string; -} - -export interface AlertsByTypeProps { - data: AlertsTypeData[]; - isLoading: boolean; -} - -export const AlertsByType: React.FC<AlertsByTypeProps> = ({ data, isLoading }) => { - const isAlertTypeEnabled = useIsExperimentalFeatureEnabled('alertTypeEnabled'); - const columns = useMemo( - () => getAlertsTypeTableColumns(isAlertTypeEnabled), - [isAlertTypeEnabled] - ); - - const subtotals = useMemo( - () => - data.reduce( - (acc: { Detection: number; Prevention: number }, item: AlertsTypeData) => { - if (item.type === 'Detection') { - acc.Detection += item.value; - } - if (item.type === 'Prevention') { - acc.Prevention += item.value; - } - return acc; - }, - { Detection: 0, Prevention: 0 } - ), - [data] - ); - - const palette: PalletteObject[] = useMemo( - () => - (Object.keys(subtotals) as AlertType[]).reduce((acc: PalletteObject[], type: AlertType) => { - const previousStop = acc.length > 0 ? acc[acc.length - 1].stop : 0; - if (subtotals[type]) { - const newEntry: PalletteObject = { - stop: previousStop + (subtotals[type] || 0), - color: ALERT_TYPE_COLOR[type], - }; - acc.push(newEntry); - } - return acc; - }, [] as PalletteObject[]), - [subtotals] - ); - - const sorting: { sort: { field: keyof AlertsTypeData; direction: SortOrder } } = { - sort: { - field: 'value', - direction: 'desc', - }, - }; - - const pagination: {} = { - pageSize: 25, - showPerPageOptions: false, - }; - - return ( - <Wrapper data-test-subj="alerts-by-type"> - {isAlertTypeEnabled && ( - <> - <EuiFlexGroup gutterSize="xs" data-test-subj="alerts-by-type-palette-display"> - {(Object.keys(subtotals) as AlertType[]).map((type) => ( - <EuiFlexItem key={type} grow={false}> - <EuiFlexGroup alignItems="center" gutterSize="xs"> - <EuiFlexItem grow={false}> - <EuiHealth className="eui-alignMiddle" color={ALERT_TYPE_COLOR[type]}> - <EuiText size="xs"> - <h4>{`${type}:`}</h4> - </EuiText> - </EuiHealth> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiText size="xs"> - <FormattedCount count={subtotals[type] || 0} /> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - ))} - <EuiSpacer size="xs" /> - </EuiFlexGroup> - <EuiSpacer size="xs" /> - <StyledEuiColorPaletteDisplay size="xs" palette={palette} /> - </> - )} - <EuiSpacer size="xs" /> - <TableWrapper className="eui-yScroll"> - <EuiInMemoryTable - data-test-subj="alerts-by-type-table" - columns={columns} - items={data} - loading={isLoading} - sorting={sorting} - pagination={pagination} - /> - </TableWrapper> - </Wrapper> - ); -}; - -AlertsByType.displayName = 'AlertsByType'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/columns.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/columns.tsx deleted file mode 100644 index 24dc0cc6690aa..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/columns.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { EuiHealth, EuiText } from '@elastic/eui'; -import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; -import type { EuiBasicTableColumn } from '@elastic/eui'; -import { TableId } from '@kbn/securitysolution-data-table'; -import { - SecurityCellActions, - CellActionsMode, - SecurityCellActionsTrigger, -} from '../../../../common/components/cell_actions'; -import type { AlertsTypeData, AlertType } from './types'; -import { DefaultDraggable } from '../../../../common/components/draggables'; -import { FormattedCount } from '../../../../common/components/formatted_number'; -import { ALERTS_HEADERS_RULE_NAME } from '../../alerts_table/translations'; -import { ALERT_TYPE_COLOR, ALERT_TYPE_LABEL } from './helpers'; -import { COUNT_TABLE_TITLE } from '../alerts_count_panel/translations'; -import * as i18n from './translations'; -import { SourcererScopeName } from '../../../../sourcerer/store/model'; - -export const getAlertsTypeTableColumns = ( - isAlertTypeEnabled: boolean -): Array<EuiBasicTableColumn<AlertsTypeData>> => [ - { - field: 'rule', - name: ALERTS_HEADERS_RULE_NAME, - 'data-test-subj': 'detectionsTable-rule', - truncateText: true, - render: (rule: string) => ( - <EuiText size="xs" className="eui-textTruncate"> - <DefaultDraggable - isDraggable={false} - field={ALERT_RULE_NAME} - hideTopN={true} - id={`alert-detection-draggable-${rule}`} - value={rule} - queryValue={rule} - tooltipContent={null} - truncate={true} - scopeId={TableId.alertsOnAlertsPage} - /> - </EuiText> - ), - }, - ...(isAlertTypeEnabled - ? [ - { - field: 'type', - name: i18n.ALERTS_TYPE_COLUMN_TITLE, - 'data-test-subj': 'detectionsTable-type', - truncateText: true, - render: (type: string) => { - return ( - <EuiHealth color={ALERT_TYPE_COLOR[type as AlertType]}> - <EuiText grow={false} size="xs"> - <SecurityCellActions - mode={CellActionsMode.HOVER_DOWN} - visibleCellActions={4} - showActionTooltips - triggerId={SecurityCellActionsTrigger.DEFAULT} - data={{ - value: 'denied', - field: 'event.type', - }} - sourcererScopeId={SourcererScopeName.detections} - metadata={{ - negateFilters: type === 'Detection', // Detection: event.type != denied - scopeId: TableId.alertsOnAlertsPage, - }} - > - {ALERT_TYPE_LABEL[type as AlertType]} - </SecurityCellActions> - </EuiText> - </EuiHealth> - ); - }, - width: '30%', - }, - ] - : []), - { - field: 'value', - name: COUNT_TABLE_TITLE, - dataType: 'number', - sortable: true, - 'data-test-subj': 'detectionsTable-count', - render: (count: number) => ( - <EuiText grow={false} size="xs"> - <FormattedCount count={count} /> - </EuiText> - ), - width: '22%', - }, -]; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/helpers.test.tsx deleted file mode 100644 index e0118b349e6b1..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/helpers.test.tsx +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { parseAlertsTypeData, parseAlertsRuleData } from './helpers'; -import * as mockType from './mock_type_data'; -import * as mockRule from './mock_rule_data'; -import type { AlertsByTypeAgg, AlertsByRuleAgg } from './types'; -import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; - -describe('parse alerts by type data', () => { - test('parse alerts with data', () => { - const res = parseAlertsTypeData( - mockType.mockAlertsData as AlertSearchResponse<{}, AlertsByTypeAgg> - ); - expect(res).toEqual(mockType.parsedAlerts); - }); - - test('parse alerts without data', () => { - const res = parseAlertsTypeData( - mockType.mockAlertsEmptyData as AlertSearchResponse<{}, AlertsByTypeAgg> - ); - expect(res).toEqual([]); - }); -}); - -describe('parse alerts by rule data', () => { - test('parse alerts with data', () => { - const res = parseAlertsRuleData( - mockRule.mockAlertsData as AlertSearchResponse<{}, AlertsByRuleAgg> - ); - expect(res).toEqual(mockRule.parsedAlerts); - }); - - test('parse alerts without data', () => { - const res = parseAlertsRuleData( - mockRule.mockAlertsEmptyData as AlertSearchResponse<{}, AlertsByRuleAgg> - ); - expect(res).toEqual([]); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/helpers.tsx deleted file mode 100644 index 5ad677bea154a..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/helpers.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { has } from 'lodash'; -import type { AlertType, AlertsByTypeAgg, AlertsTypeData, AlertsByRuleAgg } from './types'; -import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; -import type { SummaryChartsData, SummaryChartsAgg } from '../alerts_summary_charts_panel/types'; -import { DETECTION, PREVENTION } from './translations'; - -export const ALERT_TYPE_COLOR = { - Detection: '#D36086', - Prevention: '#54B399', -}; -export const ALERT_TYPE_LABEL = { - Detection: DETECTION, - Prevention: PREVENTION, -}; - -export const parseAlertsRuleData = ( - response: AlertSearchResponse<{}, AlertsByRuleAgg> -): AlertsTypeData[] => { - const rulesBuckets = response?.aggregations?.alertsByRule?.buckets ?? []; - - return rulesBuckets.length === 0 - ? [] - : rulesBuckets.map((rule) => { - return { - rule: rule.key, - type: 'Detection' as AlertType, - value: rule.doc_count, - color: ALERT_TYPE_COLOR.Detection, - }; - }); -}; - -export const parseAlertsTypeData = ( - response: AlertSearchResponse<{}, AlertsByTypeAgg> -): AlertsTypeData[] => { - const rulesBuckets = response?.aggregations?.alertsByType?.buckets ?? []; - return rulesBuckets.length === 0 - ? [] - : rulesBuckets.flatMap((rule) => { - const events = rule.ruleByEventType?.buckets ?? []; - return getAlertType(rule.key, rule.doc_count, events); - }); -}; - -const getAlertType = ( - ruleName: string, - ruleCount: number, - ruleEvents: Array<{ key: string; doc_count: number }> -): AlertsTypeData[] => { - const preventions = ruleEvents.find((bucket) => bucket.key === 'denied'); - if (!preventions) { - return [ - { - rule: ruleName, - type: 'Detection' as AlertType, - value: ruleCount, - color: ALERT_TYPE_COLOR.Detection, - }, - ]; - } - - const ret = []; - if (preventions.doc_count < ruleCount) { - ret.push({ - rule: ruleName, - type: 'Detection' as AlertType, - value: ruleCount - preventions.doc_count, - color: ALERT_TYPE_COLOR.Detection, - }); - } - - ret.push({ - rule: ruleName, - type: 'Prevention' as AlertType, - value: preventions.doc_count, - color: ALERT_TYPE_COLOR.Prevention, - }); - - return ret; -}; - -export const getIsAlertsTypeData = (data: SummaryChartsData[]): data is AlertsTypeData[] => { - return data?.every((x) => has(x, 'type')); -}; - -export const getIsAlertsByTypeAgg = ( - data: AlertSearchResponse<{}, SummaryChartsAgg> -): data is AlertSearchResponse<{}, AlertsByTypeAgg> => { - return has(data, 'aggregations.alertsByType'); -}; - -export const getIsAlertsByRuleAgg = ( - data: AlertSearchResponse<{}, SummaryChartsAgg> -): data is AlertSearchResponse<{}, AlertsByRuleAgg> => { - return has(data, 'aggregations.alertsByRule'); -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/index.test.tsx deleted file mode 100644 index 9dbfcfa23af03..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/index.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 { act, render } from '@testing-library/react'; -import React from 'react'; -import { TestProviders } from '../../../../common/mock'; -import { AlertsByTypePanel } from '.'; - -jest.mock('../../../../common/lib/kibana'); - -jest.mock('react-router-dom', () => { - const actual = jest.requireActual('react-router-dom'); - return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; -}); - -describe('Alert by type panel', () => { - const defaultProps = { - signalIndexName: 'signalIndexName', - skip: false, - }; - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('renders correctly', async () => { - await act(async () => { - const { container } = render( - <TestProviders> - <AlertsByTypePanel {...defaultProps} /> - </TestProviders> - ); - expect( - container.querySelector('[data-test-subj="alerts-by-type-panel"]') - ).toBeInTheDocument(); - }); - }); - - test('renders HeaderSection', async () => { - await act(async () => { - const { container } = render( - <TestProviders> - <AlertsByTypePanel {...defaultProps} /> - </TestProviders> - ); - expect(container.querySelector(`[data-test-subj="header-section"]`)).toBeInTheDocument(); - }); - }); - - test('renders inspect button', async () => { - await act(async () => { - const { container } = render( - <TestProviders> - <AlertsByTypePanel {...defaultProps} /> - </TestProviders> - ); - expect(container.querySelector('[data-test-subj="inspect-icon-button"]')).toBeInTheDocument(); - }); - }); - - test('renders alert by type chart', async () => { - await act(async () => { - const { container } = render( - <TestProviders> - <AlertsByTypePanel {...defaultProps} /> - </TestProviders> - ); - expect(container.querySelector('[data-test-subj="alerts-by-type"]')).toBeInTheDocument(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/index.tsx deleted file mode 100644 index 92d88d28ec419..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/index.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiPanel } from '@elastic/eui'; -import React, { useMemo } from 'react'; -import { v4 as uuid } from 'uuid'; -import type { ChartsPanelProps } from '../alerts_summary_charts_panel/types'; -import { AlertsByType } from './alerts_by_type'; -import { HeaderSection } from '../../../../common/components/header_section'; -import { InspectButtonContainer } from '../../../../common/components/inspect'; -import { useSummaryChartData } from '../alerts_summary_charts_panel/use_summary_chart_data'; -import { - alertTypeAggregations, - alertRuleAggregations, -} from '../alerts_summary_charts_panel/aggregations'; -import { getIsAlertsTypeData } from './helpers'; -import * as i18n from './translations'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; - -const ALERTS_BY_TYPE_CHART_ID = 'alerts-summary-alert_by_type'; - -export const AlertsByTypePanel: React.FC<ChartsPanelProps> = ({ - filters, - query, - signalIndexName, - runtimeMappings, - skip, -}) => { - const isAlertTypeEnabled = useIsExperimentalFeatureEnabled('alertTypeEnabled'); - const uniqueQueryId = useMemo(() => `${ALERTS_BY_TYPE_CHART_ID}-${uuid()}`, []); - - const { items, isLoading } = useSummaryChartData({ - aggregations: isAlertTypeEnabled ? alertTypeAggregations : alertRuleAggregations, - filters, - query, - signalIndexName, - runtimeMappings, - skip, - uniqueQueryId, - }); - const data = useMemo(() => (getIsAlertsTypeData(items) ? items : []), [items]); - - return ( - <InspectButtonContainer> - <EuiPanel hasBorder hasShadow={false} data-test-subj="alerts-by-type-panel"> - <HeaderSection - id={uniqueQueryId} - inspectTitle={isAlertTypeEnabled ? i18n.ALERTS_TYPE_TITLE : i18n.ALERTS_RULE_TITLE} - outerDirection="row" - title={isAlertTypeEnabled ? i18n.ALERTS_TYPE_TITLE : i18n.ALERTS_RULE_TITLE} - titleSize="xs" - hideSubtitle - /> - <AlertsByType data={data} isLoading={isLoading} /> - </EuiPanel> - </InspectButtonContainer> - ); -}; - -AlertsByTypePanel.displayName = 'AlertsByTypePanel'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/mock_type_data.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/mock_type_data.ts deleted file mode 100644 index 4c9cea8e63206..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/mock_type_data.ts +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { AlertsTypeData } from './types'; - -const from = '2022-04-05T12:00:00.000Z'; -const to = '2022-04-08T12:00:00.000Z'; - -export const mockAlertsData = { - took: 0, - timeout: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 589, - relation: 'eq', - }, - max_score: null, - hits: [], - }, - aggregations: { - alertsByType: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 'Test rule 1', - doc_count: 537, - ruleByEventType: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 'info', - doc_count: 406, - }, - { - key: 'creation', - doc_count: 131, - }, - ], - }, - }, - { - key: 'Test rule 2', - doc_count: 27, - ruleByEventType: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 'info', - doc_count: 19, - }, - { - key: 'creation', - doc_count: 8, - }, - ], - }, - }, - { - key: 'Test rule 3', - doc_count: 25, - ruleByEventType: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 'info', - doc_count: 19, - }, - { - key: 'denied', - doc_count: 6, - }, - ], - }, - }, - ], - }, - }, -}; - -export const mockAlertsEmptyData = { - took: 0, - timeout: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 0, - relation: 'eq', - }, - max_score: null, - hits: [], - }, - aggregations: { - alertsByType: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [], - }, - }, -}; - -export const query = { - size: 0, - query: { - bool: { - filter: [ - { - bool: { - filter: [], - must: [], - must_not: [], - should: [], - }, - }, - { range: { '@timestamp': { gte: from, lte: to } } }, - ], - }, - }, - aggs: { - alertsByType: { - terms: { - field: 'kibana.alert.rule.name', - size: 1000, - }, - aggs: { - ruleByEventType: { - terms: { - field: 'event.type', - size: 1000, - }, - }, - }, - }, - }, - runtime_mappings: undefined, -}; - -export const parsedAlerts: AlertsTypeData[] = [ - { rule: 'Test rule 1', type: 'Detection', value: 537, color: '#D36086' }, - { rule: 'Test rule 2', type: 'Detection', value: 27, color: '#D36086' }, - { rule: 'Test rule 3', type: 'Detection', value: 19, color: '#D36086' }, - { rule: 'Test rule 3', type: 'Prevention', value: 6, color: '#54B399' }, -]; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/translations.ts deleted file mode 100644 index 66fa31c29a448..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/translations.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 { i18n } from '@kbn/i18n'; - -export const ALERTS_TYPE_TITLE = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.alertsByType.alertTypeChartTitle', - { - defaultMessage: 'Alerts by type', - } -); - -export const ALERTS_RULE_TITLE = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.alertsByType.alertRuleChartTitle', - { - defaultMessage: 'Alerts by name', - } -); - -export const ALERTS_TYPE_COLUMN_TITLE = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.alertsByType.typeColumn', - { - defaultMessage: 'Type', - } -); - -export const PREVENTIONS = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.alertsByType.preventions', - { - defaultMessage: 'Preventions', - } -); - -export const DETECTIONS = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.alertsByType.detections', - { - defaultMessage: 'Detections', - } -); - -export const PREVENTION = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.alertsByType.prevention', - { - defaultMessage: 'Prevention', - } -); - -export const DETECTION = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.alertsByType.detection', - { - defaultMessage: 'Detection', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/types.ts deleted file mode 100644 index 3f1a97096cca7..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_by_type_panel/types.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 type { BucketItem } from '../../../../../common/search_strategy/security_solution/cti'; - -export type AlertType = 'Detection' | 'Prevention'; - -export interface AlertsByTypeAgg { - alertsByType: { - doc_count_error_upper_bound: number; - sum_other_doc_count: number; - buckets: RuleBucket[]; - }; -} - -export interface AlertsByRuleAgg { - alertsByRule: { - doc_count_error_upper_bound: number; - sum_other_doc_count: number; - buckets: BucketItem[]; - }; -} - -interface RuleBucket { - key: string; - doc_count: number; - ruleByEventType?: RuleByEventType; -} - -interface RuleByEventType { - doc_count_error_upper_bound: number; - sum_other_doc_count: number; - buckets: BucketItem[]; -} - -export interface AlertsTypeData { - rule: string; - type: AlertType; - value: number; - color: string; -} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.test.tsx deleted file mode 100644 index 3740c7b2ef7df..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.test.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { shallow, mount } from 'enzyme'; - -import { AlertsCount } from './alerts_count'; -import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; -import { TestProviders } from '../../../../common/mock'; -import { DragDropContextWrapper } from '../../../../common/components/drag_and_drop/drag_drop_context_wrapper'; -import { mockBrowserFields } from '../../../../common/containers/source/mock'; -import type { AlertsCountAggregation } from './types'; -import { emptyStackByField0Response } from './mocks/mock_response_empty_field0'; -import { - buckets as oneGroupByResponseBuckets, - mockMultiGroupResponse, -} from './mocks/mock_response_multi_group'; -import { - buckets as twoGroupByResponseBuckets, - singleGroupResponse, -} from './mocks/mock_response_single_group'; - -jest.mock('../../../../common/lib/kibana'); -const mockDispatch = jest.fn(); - -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: () => mockDispatch, - }; -}); - -describe('AlertsCount', () => { - it('renders correctly', () => { - const wrapper = shallow( - <AlertsCount - data={{} as AlertSearchResponse<{}, AlertsCountAggregation>} - loading={false} - stackByField0={'test_selected_field'} - stackByField1={undefined} - /> - ); - - expect(wrapper.find('[data-test-subj="alertsCountTable"]').exists()).toBeTruthy(); - }); - - it('renders the expected table body message when stackByField0 is an empty string', () => { - const wrapper = mount( - <TestProviders> - <AlertsCount - data={emptyStackByField0Response} - loading={false} - stackByField0={''} - stackByField1={undefined} - /> - </TestProviders> - ); - - expect(wrapper.find('[data-test-subj="alertsCountTable"] tbody').text()).toEqual( - 'No items found' - ); - }); - - describe('one group by field', () => { - oneGroupByResponseBuckets.forEach((bucket, i) => { - it(`renders the expected stackByField0 column text for bucket '${bucket.key}'`, () => { - const wrapper = mount( - <TestProviders> - <DragDropContextWrapper browserFields={mockBrowserFields}> - <AlertsCount - data={mockMultiGroupResponse} - loading={false} - stackByField0={'kibana.alert.rule.name'} - stackByField1={undefined} - /> - </DragDropContextWrapper> - </TestProviders> - ); - - expect( - wrapper - .find(`[data-test-subj="stackByField0Key"] div.euiTableCellContent`) - .hostNodes() - .at(i) - .text() - ).toEqual(bucket.key); - }); - }); - - oneGroupByResponseBuckets.forEach((bucket, i) => { - it(`renders the expected doc_count column value for bucket '${bucket.key}'`, () => { - const wrapper = mount( - <TestProviders> - <DragDropContextWrapper browserFields={mockBrowserFields}> - <AlertsCount - data={mockMultiGroupResponse} - loading={false} - stackByField0={'test_selected_field'} - stackByField1={undefined} - /> - </DragDropContextWrapper> - </TestProviders> - ); - - expect( - wrapper - .find(`[data-test-subj="doc_count"] div.euiTableCellContent`) - .hostNodes() - .at(i) - .text() - ).toEqual(`${bucket.doc_count}`); - }); - }); - }); - - describe('two group by fields: stackByField0 column', () => { - let resultRow = 0; - - twoGroupByResponseBuckets.forEach((bucket) => { - bucket.stackByField1.buckets.forEach((b) => { - it(`renders the expected stackByField0 column text for stackByField0: '${bucket.key}', stackByField1 '${b.key}'`, () => { - const wrapper = mount( - <TestProviders> - <DragDropContextWrapper browserFields={mockBrowserFields}> - <AlertsCount - data={singleGroupResponse} - loading={false} - stackByField0={'kibana.alert.rule.name'} - stackByField1={'host.name'} - /> - </DragDropContextWrapper> - </TestProviders> - ); - - expect( - wrapper - .find(`[data-test-subj="stackByField0Key"] div.euiTableCellContent`) - .hostNodes() - .at(resultRow++) - .text() - ).toEqual(bucket.key); - }); - }); - }); - }); - - describe('two group by fields: stackByField1 column', () => { - let resultRow = 0; - - twoGroupByResponseBuckets.forEach((bucket) => { - bucket.stackByField1.buckets.forEach((b, i) => { - it(`renders the expected stackByField1 column text for stackByField0: '${bucket.key}', stackByField1 '${b.key}'`, () => { - const wrapper = mount( - <TestProviders> - <DragDropContextWrapper browserFields={mockBrowserFields}> - <AlertsCount - data={singleGroupResponse} - loading={false} - stackByField0={'kibana.alert.rule.name'} - stackByField1={'host.name'} - /> - </DragDropContextWrapper> - </TestProviders> - ); - - expect( - wrapper - .find(`[data-test-subj="stackByField1Key"] div.euiTableCellContent`) - .hostNodes() - .at(resultRow++) - .text() - ).toEqual(b.key); - }); - }); - }); - }); - - describe('two group by fields: stackByField1DocCount column', () => { - let resultRow = 0; - - twoGroupByResponseBuckets.forEach((bucket) => { - bucket.stackByField1.buckets.forEach((b, i) => { - it(`renders the expected doc_count column value for stackByField0: '${bucket.key}', stackByField1 '${b.key}'`, () => { - const wrapper = mount( - <TestProviders> - <DragDropContextWrapper browserFields={mockBrowserFields}> - <AlertsCount - data={singleGroupResponse} - loading={false} - stackByField0={'kibana.alert.rule.name'} - stackByField1={'host.name'} - /> - </DragDropContextWrapper> - </TestProviders> - ); - - expect( - wrapper - .find(`[data-test-subj="stackByField1DocCount"] div.euiTableCellContent`) - .hostNodes() - .at(resultRow++) - .text() - ).toEqual(`${b.doc_count}`); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.tsx deleted file mode 100644 index c83650b8e15d8..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/alerts_count.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 { EuiInMemoryTable } from '@elastic/eui'; -import { isEmpty } from 'lodash/fp'; -import React, { useMemo } from 'react'; -import styled from 'styled-components'; - -import { useUiSetting$ } from '../../../../common/lib/kibana'; -import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants'; -import type { AlertsCountAggregation } from './types'; -import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; -import { - getMaxRiskSubAggregations, - getUpToMaxBuckets, -} from '../alerts_treemap_panel/alerts_treemap/lib/helpers'; -import { getFlattenedBuckets } from '../alerts_treemap_panel/alerts_treemap/lib/flatten/get_flattened_buckets'; -import type { FlattenedBucket, RawBucket } from '../alerts_treemap_panel/alerts_treemap/types'; -import { - getMultiGroupAlertsCountTableColumns, - getSingleGroupByAlertsCountTableColumns, -} from './columns'; -import { DEFAULT_STACK_BY_FIELD0_SIZE } from './helpers'; - -interface AlertsCountProps { - loading: boolean; - data: AlertSearchResponse<unknown, AlertsCountAggregation>; - stackByField0: string; - stackByField1: string | undefined; -} - -const Wrapper = styled.div` - margin-top: -${({ theme }) => theme.eui.euiSizeS}; -`; - -export const AlertsCountComponent: React.FC<AlertsCountProps> = ({ - data, - loading, - stackByField0, - stackByField1, -}) => { - const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT); - - const tableColumns = useMemo( - () => - isEmpty(stackByField1?.trim()) - ? getSingleGroupByAlertsCountTableColumns({ - defaultNumberFormat, - stackByField0, - }) - : getMultiGroupAlertsCountTableColumns({ - defaultNumberFormat, - stackByField0, - stackByField1, - }), - [defaultNumberFormat, stackByField0, stackByField1] - ); - - const buckets: RawBucket[] = useMemo( - () => - getUpToMaxBuckets({ - buckets: data.aggregations?.stackByField0?.buckets, - maxItems: DEFAULT_STACK_BY_FIELD0_SIZE, - }), - [data.aggregations?.stackByField0?.buckets] - ); - - const maxRiskSubAggregations = useMemo(() => getMaxRiskSubAggregations(buckets), [buckets]); - - const items: FlattenedBucket[] = useMemo( - () => - isEmpty(stackByField1?.trim()) - ? buckets - : getFlattenedBuckets({ - buckets, - maxRiskSubAggregations, - stackByField0, - }), - [buckets, maxRiskSubAggregations, stackByField0, stackByField1] - ); - - return ( - <Wrapper data-test-subj="alertsCountTable" className="eui-yScroll"> - <EuiInMemoryTable columns={tableColumns} items={items} loading={loading} sorting={true} /> - </Wrapper> - ); -}; - -AlertsCountComponent.displayName = 'AlertsCountComponent'; - -export const AlertsCount = React.memo(AlertsCountComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/columns.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/columns.test.tsx deleted file mode 100644 index c5600fe7eda94..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/columns.test.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { omit } from 'lodash/fp'; -import { - getMultiGroupAlertsCountTableColumns, - getSingleGroupByAlertsCountTableColumns, -} from './columns'; - -describe('columns', () => { - const defaultNumberFormat = '0,0.[000]'; - const stackByField0 = 'kibana.alert.rule.name'; - - describe('getMultiGroupAlertsCountTableColumns', () => { - const stackByField1 = 'host.name'; - - test('it returns the expected columns', () => { - expect( - getMultiGroupAlertsCountTableColumns({ - defaultNumberFormat, - stackByField0, - stackByField1, - }).map((x) => omit('render', x)) - ).toEqual([ - { - 'data-test-subj': 'stackByField0Key', - field: 'key', - name: 'Top 1000 values of kibana.alert.rule.name', - truncateText: false, - }, - { - 'data-test-subj': 'stackByField1Key', - field: 'stackByField1Key', - name: 'Top 1000 values of host.name', - truncateText: false, - }, - { - 'data-test-subj': 'stackByField1DocCount', - dataType: 'number', - field: 'stackByField1DocCount', - name: 'Count of records', - sortable: true, - textOnly: true, - }, - ]); - }); - }); - - describe('getSingleGroupByAlertsCountTableColumns', () => { - test('it returns the expected columns', () => { - expect( - getSingleGroupByAlertsCountTableColumns({ defaultNumberFormat, stackByField0 }).map((x) => - omit('render', x) - ) - ).toEqual([ - { - 'data-test-subj': 'stackByField0Key', - field: 'key', - name: 'kibana.alert.rule.name', - truncateText: false, - }, - { - 'data-test-subj': 'doc_count', - dataType: 'number', - field: 'doc_count', - name: 'Count of records', - sortable: true, - textOnly: true, - }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/columns.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/columns.tsx deleted file mode 100644 index 9b9e3081d2fe5..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/columns.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 React from 'react'; -import type { EuiBasicTableColumn } from '@elastic/eui'; -import numeral from '@elastic/numeral'; - -import { TableId } from '@kbn/securitysolution-data-table'; -import type { FlattenedBucket } from '../alerts_treemap_panel/alerts_treemap/types'; -import { DefaultDraggable } from '../../../../common/components/draggables'; -import type { GenericBuckets } from '../../../../../common/search_strategy/common'; -import * as i18n from './translations'; -import { DEFAULT_STACK_BY_FIELD0_SIZE, DEFAULT_STACK_BY_FIELD1_SIZE } from './helpers'; - -export const getSingleGroupByAlertsCountTableColumns = ({ - defaultNumberFormat, - stackByField0, -}: { - defaultNumberFormat: string; - stackByField0: string; -}): Array<EuiBasicTableColumn<GenericBuckets>> => [ - { - 'data-test-subj': 'stackByField0Key', - field: 'key', - name: stackByField0, - render: function DraggableStackOptionField(value: string) { - return ( - <DefaultDraggable - isDraggable={false} - field={stackByField0} - hideTopN={true} - id={`alert-count-draggable-stackByField0-${stackByField0}-${value}`} - value={value} - tooltipContent={null} - scopeId={TableId.alertsOnAlertsPage} - /> - ); - }, - truncateText: false, - }, - { - 'data-test-subj': 'doc_count', - dataType: 'number', - field: 'doc_count', - name: i18n.COUNT_TABLE_COLUMN_TITLE, - render: (item: string) => numeral(item).format(defaultNumberFormat), - sortable: true, - textOnly: true, - }, -]; - -export const getMultiGroupAlertsCountTableColumns = ({ - defaultNumberFormat, - stackByField0, - stackByField1, -}: { - defaultNumberFormat: string; - stackByField0: string; - stackByField1: string | undefined; -}): Array<EuiBasicTableColumn<FlattenedBucket>> => [ - { - 'data-test-subj': 'stackByField0Key', - field: 'key', - name: i18n.COLUMN_LABEL({ fieldName: stackByField0, topN: DEFAULT_STACK_BY_FIELD0_SIZE }), - render: function DraggableStackOptionField(value: string) { - return ( - <DefaultDraggable - isDraggable={false} - field={stackByField0} - hideTopN={true} - id={`alert-count-draggable-stackByField0-${stackByField0}-${stackByField1}-${value}`} - value={value} - tooltipContent={null} - scopeId={TableId.alertsOnAlertsPage} - /> - ); - }, - truncateText: false, - }, - { - 'data-test-subj': 'stackByField1Key', - field: 'stackByField1Key', - name: i18n.COLUMN_LABEL({ fieldName: stackByField1 ?? '', topN: DEFAULT_STACK_BY_FIELD1_SIZE }), - render: function DraggableStackOptionField(value: string) { - return ( - <DefaultDraggable - isDraggable={false} - field={stackByField1 ?? ''} - hideTopN={true} - id={`alert-count-draggable-stackByField1-${stackByField0}-${stackByField1}-${value}`} - value={value} - tooltipContent={null} - scopeId={TableId.alertsOnAlertsPage} - /> - ); - }, - truncateText: false, - }, - { - 'data-test-subj': 'stackByField1DocCount', - dataType: 'number', - field: 'stackByField1DocCount', - name: i18n.COUNT_TABLE_COLUMN_TITLE, - render: (item: string) => numeral(item).format(defaultNumberFormat), - sortable: true, - textOnly: true, - }, -]; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.test.tsx deleted file mode 100644 index e651f17d59157..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.test.tsx +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getAlertsCountQuery } from './helpers'; - -const stackByField0 = 'kibana.alert.rule.name'; -const stackByField1 = 'host.name'; -const from = '2022-07-08T06:00:00.000Z'; -const to = '2022-07-09T05:59:59.999Z'; -const additionalFilters = [ - { - bool: { - must: [], - filter: [ - { - term: { - 'kibana.alert.workflow_status': 'open', - }, - }, - ], - should: [], - must_not: [ - { - exists: { - field: 'kibana.alert.building_block_type', - }, - }, - ], - }, - }, -]; -const runtimeMappings = {}; - -describe('helpers', () => { - describe('getAlertsCountQuery', () => { - test('it returns the expected query when stackByField1 is specified', () => { - expect( - getAlertsCountQuery({ - additionalFilters, - from, - runtimeMappings, - stackByField0, - stackByField1, - to, - }) - ).toEqual({ - size: 0, - aggs: { - stackByField0: { - terms: { field: 'kibana.alert.rule.name', order: { _count: 'desc' }, size: 1000 }, - aggs: { - stackByField1: { - terms: { field: 'host.name', order: { _count: 'desc' }, size: 1000 }, - }, - }, - }, - }, - query: { - bool: { - filter: [ - { - bool: { - must: [], - filter: [{ term: { 'kibana.alert.workflow_status': 'open' } }], - should: [], - must_not: [{ exists: { field: 'kibana.alert.building_block_type' } }], - }, - }, - { - range: { - '@timestamp': { - gte: '2022-07-08T06:00:00.000Z', - lte: '2022-07-09T05:59:59.999Z', - }, - }, - }, - ], - }, - }, - runtime_mappings: {}, - }); - }); - - test('it returns the expected query when stackByField1 is `undefined`', () => { - expect( - getAlertsCountQuery({ - additionalFilters, - from, - runtimeMappings, - stackByField0, - stackByField1: undefined, - to, - }) - ).toEqual({ - size: 0, - aggs: { - stackByField0: { - terms: { field: 'kibana.alert.rule.name', order: { _count: 'desc' }, size: 1000 }, - aggs: {}, - }, - }, - query: { - bool: { - filter: [ - { - bool: { - must: [], - filter: [{ term: { 'kibana.alert.workflow_status': 'open' } }], - should: [], - must_not: [{ exists: { field: 'kibana.alert.building_block_type' } }], - }, - }, - { - range: { - '@timestamp': { - gte: '2022-07-08T06:00:00.000Z', - lte: '2022-07-09T05:59:59.999Z', - }, - }, - }, - ], - }, - }, - runtime_mappings: {}, - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.tsx deleted file mode 100644 index 1537d1f1fd212..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/helpers.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { getOptionalSubAggregation } from '../alerts_treemap_panel/alerts_treemap/query'; - -export const DEFAULT_STACK_BY_FIELD0_SIZE = 1000; -export const DEFAULT_STACK_BY_FIELD1_SIZE = 1000; - -export const getAlertsCountQuery = ({ - additionalFilters = [], - from, - runtimeMappings, - stackByField0, - stackByField1, - to, -}: { - additionalFilters: Array<{ - bool: { filter: unknown[]; should: unknown[]; must_not: unknown[]; must: unknown[] }; - }>; - from: string; - runtimeMappings?: MappingRuntimeFields; - stackByField0: string; - stackByField1: string | undefined; - to: string; -}) => { - return { - size: 0, - aggs: { - stackByField0: { - terms: { - field: stackByField0, - order: { - _count: 'desc', - }, - size: DEFAULT_STACK_BY_FIELD0_SIZE, - }, - aggs: { - ...getOptionalSubAggregation({ - stackByField1, - stackByField1Size: DEFAULT_STACK_BY_FIELD1_SIZE, - }), - }, - }, - }, - query: { - bool: { - filter: [ - ...additionalFilters, - { - range: { - '@timestamp': { - gte: from, - lte: to, - }, - }, - }, - ], - }, - }, - runtime_mappings: runtimeMappings, - }; -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx index 18d44abca441a..806be83a738d3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.test.tsx @@ -12,15 +12,11 @@ import type { Action } from '@kbn/ui-actions-plugin/public'; import { AlertsCountPanel } from '.'; import type { Status } from '../../../../../common/api/detection_engine'; -import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { DEFAULT_STACK_BY_FIELD, DEFAULT_STACK_BY_FIELD1 } from '../common/config'; import { TestProviders } from '../../../../common/mock'; import { ChartContextMenu } from '../chart_panels/chart_context_menu'; -import { TABLE } from '../chart_panels/chart_select/translations'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { COUNTS } from '../chart_panels/chart_select/translations'; import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; -import type { ExperimentalFeatures } from '../../../../../common/experimental_features'; -import { allowedExperimentalValues } from '../../../../../common/experimental_features'; const from = '2022-07-28T08:20:18.966Z'; const to = '2022-07-28T08:20:18.966Z'; @@ -40,7 +36,6 @@ jest.mock('react-router-dom', () => { return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; }); -jest.mock('../../../../common/hooks/use_experimental_features'); jest.mock('../../../../common/components/page/use_refetch_by_session'); jest.mock('../../../../common/components/visualization_actions/visualization_embeddable'); jest.mock('../../../../common/components/page/use_refetch_by_session'); @@ -48,38 +43,26 @@ jest.mock('../common/hooks', () => ({ useInspectButton: jest.fn(), useStackByFields: jest.fn().mockReturnValue(() => []), })); -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -const getMockUseIsExperimentalFeatureEnabled = - (mockMapping?: Partial<ExperimentalFeatures>) => (flag: keyof typeof allowedExperimentalValues) => - mockMapping ? mockMapping?.[flag] : allowedExperimentalValues?.[flag]; -jest.mock('../../../../common/hooks/use_experimental_features'); +const mockSetIsExpanded = jest.fn(); const defaultProps = { - inspectTitle: TABLE, + inspectTitle: COUNTS, signalIndexName: 'signalIndexName', stackByField0: DEFAULT_STACK_BY_FIELD, stackByField1: DEFAULT_STACK_BY_FIELD1, setStackByField0: jest.fn(), setStackByField1: jest.fn(), isExpanded: true, - setIsExpanded: jest.fn(), + setIsExpanded: mockSetIsExpanded, showBuildingBlockAlerts: false, showOnlyThreatIndicatorAlerts: false, status: 'open' as Status, extraActions: [{ id: 'resetGroupByFields' }] as Action[], }; -const mockSetToggle = jest.fn(); -const mockUseQueryToggle = useQueryToggle as jest.Mock; describe('AlertsCountPanel', () => { beforeEach(() => { jest.clearAllMocks(); - mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); - mockUseIsExperimentalFeatureEnabled.mockImplementation( - getMockUseIsExperimentalFeatureEnabled({ - alertsPageChartsEnabled: false, - }) - ); }); it('renders correctly', async () => { @@ -155,37 +138,11 @@ describe('AlertsCountPanel', () => { </TestProviders> ); wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click'); - expect(mockSetToggle).toBeCalledWith(false); - }); - }); - it('alertsPageChartsEnabled is false and toggleStatus=true, render', async () => { - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsCountPanel {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="visualization-embeddable"]').exists()).toEqual(true); - }); - }); - it('alertsPageChartsEnabled is false and toggleStatus=false, hide', async () => { - mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle }); - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsCountPanel {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="visualization-embeddable"]').exists()).toEqual(false); + expect(mockSetIsExpanded).toBeCalledWith(false); }); }); - it('alertsPageChartsEnabled is true and isExpanded=true, render', async () => { - mockUseIsExperimentalFeatureEnabled.mockImplementation( - getMockUseIsExperimentalFeatureEnabled({ - alertsPageChartsEnabled: true, - }) - ); + it('when isExpanded is true, render counts panel', async () => { await act(async () => { const wrapper = mount( <TestProviders> @@ -195,12 +152,7 @@ describe('AlertsCountPanel', () => { expect(wrapper.find('[data-test-subj="visualization-embeddable"]').exists()).toEqual(true); }); }); - it('alertsPageChartsEnabled is true and isExpanded=false, hide', async () => { - mockUseIsExperimentalFeatureEnabled.mockImplementation( - getMockUseIsExperimentalFeatureEnabled({ - alertsPageChartsEnabled: true, - }) - ); + it('when isExpanded is false, hide counts panel', async () => { await act(async () => { const wrapper = mount( <TestProviders> diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 2ebbf29692f3d..a9d9821601d25 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -7,20 +7,15 @@ import type { EuiComboBox } from '@elastic/eui'; import type { Action } from '@kbn/ui-actions-plugin/public'; -import React, { memo, useMemo, useCallback } from 'react'; +import React, { memo, useMemo } from 'react'; import { v4 as uuidv4 } from 'uuid'; - import type { Filter } from '@kbn/es-query'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { HeaderSection } from '../../../../common/components/header_section'; - import { InspectButtonContainer } from '../../../../common/components/inspect'; - import * as i18n from './translations'; import { KpiPanel } from '../common/components'; -import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { FieldSelection } from '../../../../common/components/field_selection'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { getAlertsTableLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_table'; import { SourcererScopeName } from '../../../../sourcerer/store/model'; import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; @@ -44,8 +39,8 @@ interface AlertsCountPanelProps { stackByField1ComboboxRef?: React.RefObject<EuiComboBox<string | number | string[] | undefined>>; stackByWidth?: number; title?: React.ReactNode; - isExpanded?: boolean; - setIsExpanded?: (status: boolean) => void; + isExpanded: boolean; + setIsExpanded: (status: boolean) => void; } const CHART_HEIGHT = 218; // px @@ -71,22 +66,8 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>( setIsExpanded, }) => { const { to, from } = useGlobalTime(); - const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); // create a unique, but stable (across re-renders) query id const uniqueQueryId = useMemo(() => `${DETECTIONS_ALERTS_COUNT_ID}-${uuidv4()}`, []); - - const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTIONS_ALERTS_COUNT_ID); - const toggleQuery = useCallback( - (newToggleStatus: boolean) => { - if (isAlertsPageChartsEnabled && setIsExpanded) { - setIsExpanded(newToggleStatus); - } else { - setToggleStatus(newToggleStatus); - } - }, - [setToggleStatus, setIsExpanded, isAlertsPageChartsEnabled] - ); - const timerange = useMemo(() => ({ from, to }), [from, to]); const extraVisualizationOptions = useMemo( @@ -97,19 +78,10 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>( [filters, stackByField1] ); - const showCount = useMemo(() => { - if (isAlertsPageChartsEnabled) { - return isExpanded; - } - return toggleStatus; - }, [isAlertsPageChartsEnabled, toggleStatus, isExpanded]); - return ( - <InspectButtonContainer show={isAlertsPageChartsEnabled ? isExpanded : toggleStatus}> + <InspectButtonContainer show={isExpanded}> <KpiPanel - $toggleStatus={ - isAlertsPageChartsEnabled && isExpanded !== undefined ? isExpanded : toggleStatus - } + $toggleStatus={Boolean(isExpanded)} data-test-subj="alertsCountPanel" hasBorder height={panelHeight} @@ -123,8 +95,8 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>( titleSize="s" hideSubtitle showInspectButton={chartOptionsContextMenu == null} - toggleStatus={isAlertsPageChartsEnabled ? isExpanded : toggleStatus} - toggleQuery={toggleQuery} + toggleStatus={isExpanded} + toggleQuery={setIsExpanded} > <FieldSelection setStackByField0={setStackByField0} @@ -140,7 +112,7 @@ export const AlertsCountPanel = memo<AlertsCountPanelProps>( useLensCompatibleFields={true} /> </HeaderSection> - {showCount && ( + {isExpanded && ( <VisualizationEmbeddable data-test-subj="embeddable-alerts-count" extraActions={extraActions} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_empty_field0.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_empty_field0.ts deleted file mode 100644 index a2d9d92cd75a6..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_empty_field0.ts +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { AlertSearchResponse } from '../../../../containers/detection_engine/alerts/types'; -import type { AlertsCountAggregation } from '../types'; - -export const emptyStackByField0Response: AlertSearchResponse<unknown, AlertsCountAggregation> = { - took: 0, - timeout: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 87, - relation: 'eq', - }, - max_score: null, - hits: [], - }, - aggregations: { - stackByField0: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [], - }, - }, -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_multi_group.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_multi_group.ts deleted file mode 100644 index 730fded03f88b..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_multi_group.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { AlertSearchResponse } from '../../../../containers/detection_engine/alerts/types'; -import type { AlertsCountAggregation } from '../types'; - -export const buckets = [ - { - key: 'matches everything', - doc_count: 34, - }, - { - key: 'EQL process sequence', - doc_count: 28, - }, - { - key: 'Endpoint Security', - doc_count: 19, - }, - { - key: 'mimikatz process started', - doc_count: 5, - }, - { - key: 'Threshold rule', - doc_count: 1, - }, -]; - -/** - * A mock response to a request containing multiple group by fields - */ -export const mockMultiGroupResponse: AlertSearchResponse<unknown, AlertsCountAggregation> = { - took: 0, - timeout: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 87, - relation: 'eq', - }, - max_score: null, - hits: [], - }, - aggregations: { - stackByField0: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets, - }, - }, -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_single_group.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_single_group.ts deleted file mode 100644 index e7c0f982be03b..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/mocks/mock_response_single_group.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { AlertSearchResponse } from '../../../../containers/detection_engine/alerts/types'; -import type { AlertsCountAggregation } from '../types'; - -export const buckets = [ - { - key: 'matches everything', - doc_count: 34, - stackByField1: { - buckets: [ - { - key: 'Host-k8iyfzraq9', - doc_count: 12, - }, - { - key: 'Host-ao1a4wu7vn', - doc_count: 10, - }, - { - key: 'Host-3fbljiq8rj', - doc_count: 7, - }, - { - key: 'Host-r4y6xi92ob', - doc_count: 5, - }, - ], - }, - }, - { - key: 'EQL process sequence', - doc_count: 28, - stackByField1: { - sum_other_doc_count: 0, - buckets: [ - { - key: 'Host-k8iyfzraq9', - doc_count: 10, - }, - { - key: 'Host-ao1a4wu7vn', - doc_count: 7, - }, - { - key: 'Host-3fbljiq8rj', - doc_count: 5, - }, - { - key: 'Host-r4y6xi92ob', - doc_count: 3, - }, - ], - }, - }, - { - key: 'Endpoint Security', - doc_count: 19, - stackByField1: { - sum_other_doc_count: 0, - buckets: [ - { - key: 'Host-ao1a4wu7vn', - doc_count: 11, - }, - { - key: 'Host-3fbljiq8rj', - doc_count: 6, - }, - { - key: 'Host-k8iyfzraq9', - doc_count: 1, - }, - { - key: 'Host-r4y6xi92ob', - doc_count: 1, - }, - ], - }, - }, - { - key: 'mimikatz process started', - doc_count: 5, - stackByField1: { - sum_other_doc_count: 0, - buckets: [ - { - key: 'Host-k8iyfzraq9', - doc_count: 3, - }, - { - key: 'Host-3fbljiq8rj', - doc_count: 1, - }, - { - key: 'Host-r4y6xi92ob', - doc_count: 1, - }, - ], - }, - }, - { - key: 'Threshold rule', - doc_count: 1, - stackByField1: { - sum_other_doc_count: 0, - buckets: [ - { - key: 'Host-r4y6xi92ob', - doc_count: 1, - }, - ], - }, - }, -]; - -export const singleGroupResponse: AlertSearchResponse<unknown, AlertsCountAggregation> = { - took: 0, - timeout: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 87, - relation: 'eq', - }, - max_score: null, - hits: [], - }, - aggregations: { - stackByField0: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets, - }, - }, -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/alerts_histogram.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/alerts_histogram.test.tsx deleted file mode 100644 index 4b64a214bd02b..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/alerts_histogram.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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, shallow } from 'enzyme'; - -import { AlertsHistogram } from './alerts_histogram'; -import { TestProviders } from '../../../../common/mock'; - -jest.mock('../../../../common/lib/kibana'); - -const legendItems = [ - { - color: '#1EA593', - count: 77, - dataProviderId: - 'draggable-legend-item-2f890398-548e-4604-b2de-525f0eecd124-kibana_alert_rule_name-matches everything', - field: 'kibana.alert.rule.name', - value: 'matches everything', - }, - { - color: '#2B70F7', - count: 56, - dataProviderId: - 'draggable-legend-item-07aca01b-d334-424d-98c0-6d6bc9f8a886-kibana_alert_rule_name-Endpoint Security', - field: 'kibana.alert.rule.name', - value: 'Endpoint Security', - }, -]; - -const defaultProps = { - legendItems, - loading: false, - data: [], - from: '2020-07-07T08:20:18.966Z', - to: '2020-07-08T08:20:18.966Z', - updateDateRange: jest.fn(), -}; - -describe('AlertsHistogram', () => { - it('renders correctly', () => { - const wrapper = shallow(<AlertsHistogram {...defaultProps} />); - - expect(wrapper.find('Chart').exists()).toBeTruthy(); - }); - - it('renders a legend with the default width', () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogram {...defaultProps} /> - </TestProviders> - ); - - expect(wrapper.find('[data-test-subj="draggable-legend"]').first()).toHaveStyleRule( - 'min-width', - '165px' - ); - }); - - it('renders a legend with the specified `legendWidth`', () => { - const legendMinWidth = 1234; - - const wrapper = mount( - <TestProviders> - <AlertsHistogram {...defaultProps} legendMinWidth={legendMinWidth} /> - </TestProviders> - ); - - expect(wrapper.find('[data-test-subj="draggable-legend"]').first()).toHaveStyleRule( - 'min-width', - `${legendMinWidth}px` - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/alerts_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/alerts_histogram.tsx deleted file mode 100644 index 83052e4e5a2f1..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/alerts_histogram.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 type { ChartSizeArray } from '@elastic/charts'; -import { i18n } from '@kbn/i18n'; -import { - Axis, - Chart, - HistogramBarSeries, - Position, - Settings, - ScaleType, - LegendValue, -} from '@elastic/charts'; -import { EuiFlexGroup, EuiFlexItem, EuiProgress } from '@elastic/eui'; -import React, { useMemo } from 'react'; - -import type { UpdateDateRange, ChartData } from '../../../../common/components/charts/common'; -import { useThemes } from '../../../../common/components/charts/common'; -import { histogramDateTimeFormatter } from '../../../../common/components/utils'; -import { hasValueToDisplay } from '../../../../common/utils/validators'; -import { DraggableLegend } from '../../../../common/components/charts/draggable_legend'; -import type { LegendItem } from '../../../../common/components/charts/draggable_legend_item'; -import { EMPTY_VALUE_LABEL } from '../../../../common/components/charts/translation'; - -import type { HistogramData } from './types'; - -const DEFAULT_CHART_HEIGHT = 174; - -interface AlertsHistogramProps { - chartHeight?: number; - from: string; - legendItems: LegendItem[]; - legendPosition?: Position; - legendMinWidth?: number; - loading: boolean; - showLegend?: boolean; - to: string; - data: HistogramData[]; - updateDateRange: UpdateDateRange; -} -export const AlertsHistogram = React.memo<AlertsHistogramProps>( - ({ - chartHeight = DEFAULT_CHART_HEIGHT, - data, - from, - legendItems, - legendPosition = Position.Right, - legendMinWidth, - loading, - showLegend, - to, - updateDateRange, - }) => { - const { baseTheme, theme } = useThemes(); - const chartSize: ChartSizeArray = useMemo(() => ['100%', chartHeight], [chartHeight]); - const xAxisId = 'alertsHistogramAxisX'; - const yAxisId = 'alertsHistogramAxisY'; - const id = 'alertsHistogram'; - const yAccessors = useMemo(() => ['y'], []); - const splitSeriesAccessors = useMemo( - () => [(datum: ChartData) => (hasValueToDisplay(datum.g) ? datum.g : EMPTY_VALUE_LABEL)], - [] - ); - const tickFormat = useMemo(() => histogramDateTimeFormatter([from, to]), [from, to]); - - return ( - <> - {loading && ( - <EuiProgress - data-test-subj="loadingPanelAlertsHistogram" - size="xs" - position="absolute" - color="accent" - /> - )} - - <EuiFlexGroup gutterSize="none"> - <EuiFlexItem grow={true}> - <Chart size={chartSize}> - <Settings - legendPosition={legendPosition} - onBrushEnd={updateDateRange} - // showLegend controls the default legend coming from @elastic/charts, we show them when our customized legend items don't exist (but we still want to show legend). - showLegend={showLegend && legendItems.length === 0} - legendValues={showLegend ? [LegendValue.CurrentAndLastValue] : []} - theme={theme} - baseTheme={baseTheme} - locale={i18n.getLocale()} - /> - - <Axis id={xAxisId} position={Position.Bottom} tickFormat={tickFormat} /> - - <Axis id={yAxisId} position={Position.Left} /> - - <HistogramBarSeries - id={id} - xScaleType={ScaleType.Time} - yScaleType={ScaleType.Linear} - xAccessor="x" - yAccessors={yAccessors} - stackAccessors={['true']} - splitSeriesAccessors={splitSeriesAccessors} - data={data} - /> - </Chart> - </EuiFlexItem> - <EuiFlexItem grow={false}> - {legendItems.length > 0 && ( - <DraggableLegend - legendItems={legendItems} - height={chartHeight} - minWidth={legendMinWidth} - /> - )} - </EuiFlexItem> - </EuiFlexGroup> - </> - ); - } -); - -AlertsHistogram.displayName = 'AlertsHistogram'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx index 519a7975c5891..2599b10b93a48 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.test.tsx @@ -7,12 +7,7 @@ import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import type { Embeddable } from '@kbn/embeddable-plugin/public'; -import { - createResetGroupByFieldAction, - formatAlertsData, - showInitialLoadingSpinner, -} from './helpers'; -import { result, textResult, stackedByBooleanField, stackedByTextField } from './mock_data'; +import { createResetGroupByFieldAction, showInitialLoadingSpinner } from './helpers'; import type { LensDataTableEmbeddable } from '../../../../common/components/visualization_actions/types'; describe('helpers', () => { @@ -43,18 +38,6 @@ describe('helpers', () => { }); }); -describe('formatAlertsData', () => { - test('stack by a boolean field', () => { - const res = formatAlertsData(stackedByBooleanField); - expect(res).toEqual(result); - }); - - test('stack by a text field', () => { - const res = formatAlertsData(stackedByTextField); - expect(res).toEqual(textResult); - }); -}); - describe('createResetGroupByFieldAction', () => { let action: Action; const embeddable = { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx index cbeb7e9c3d0a5..8757316038b4a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/helpers.tsx @@ -5,95 +5,11 @@ * 2.0. */ -import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { isEmpty } from 'lodash/fp'; -import moment from 'moment'; - import type { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import type { Embeddable } from '@kbn/embeddable-plugin/public'; -import type { HistogramData, AlertsAggregation, AlertsBucket, AlertsGroupBucket } from './types'; -import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; import { RESET_GROUP_BY_FIELDS } from '../../../../common/components/chart_settings_popover/configurations/default/translations'; import type { LensDataTableEmbeddable } from '../../../../common/components/visualization_actions/types'; -const EMPTY_ALERTS_DATA: HistogramData[] = []; - -export const formatAlertsData = (alertsData: AlertSearchResponse<{}, AlertsAggregation> | null) => { - const groupBuckets: AlertsGroupBucket[] = - alertsData?.aggregations?.alertsByGrouping?.buckets ?? []; - return groupBuckets.reduce<HistogramData[]>( - (acc, { key_as_string: keyAsString, key: group, alerts }) => { - const alertsBucket: AlertsBucket[] = alerts.buckets ?? []; - - return [ - ...acc, - // eslint-disable-next-line @typescript-eslint/naming-convention - ...alertsBucket.map(({ key, doc_count }: AlertsBucket) => ({ - x: key, - y: doc_count, - g: keyAsString ?? group.toString(), - })), - ]; - }, - EMPTY_ALERTS_DATA - ); -}; - -export const getAlertsHistogramQuery = ( - stackByField: string, - from: string, - to: string, - additionalFilters: Array<{ - bool: { filter: unknown[]; should: unknown[]; must_not: unknown[]; must: unknown[] }; - }>, - runtimeMappings?: MappingRuntimeFields -) => { - return { - aggs: { - alertsByGrouping: { - terms: { - field: stackByField, - order: { - _count: 'desc', - }, - size: 10, - }, - aggs: { - alerts: { - date_histogram: { - field: '@timestamp', - fixed_interval: `${Math.floor(moment(to).diff(moment(from)) / 32)}ms`, - min_doc_count: 0, - extended_bounds: { - min: from, - max: to, - }, - }, - }, - }, - }, - }, - query: { - bool: { - filter: [ - ...additionalFilters, - { - range: { - '@timestamp': { - gte: from, - lte: to, - }, - }, - }, - ], - }, - }, - runtime_mappings: runtimeMappings, - _source: false, - size: 0, - }; -}; - /** * Returns `true` when the alerts histogram initial loading spinner should be shown * @@ -108,22 +24,6 @@ export const showInitialLoadingSpinner = ({ isLoadingAlerts: boolean; }): boolean => isInitialLoading && isLoadingAlerts; -export const parseFilterQuery = (query?: string) => { - try { - return query != null && !isEmpty(query) ? JSON.parse(query) : {}; - } catch { - return {}; - } -}; - -export const buildFilterQuery = (query?: string) => { - try { - return isEmpty(query) ? [] : [parseFilterQuery(query)]; - } catch { - return []; - } -}; - interface CreateResetGroupByFieldActionProps { callback?: () => void; order?: number; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx index 6b92dd328f625..8a14499740444 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; import { mount } from 'enzyme'; import type { Filter } from '@kbn/es-query'; @@ -17,9 +17,6 @@ import { mockAlertSearchResponse } from './mock_data'; import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; import { AlertsHistogramPanel } from '.'; -import type { ExperimentalFeatures } from '../../../../../common'; -import { allowedExperimentalValues } from '../../../../../common'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useVisualizationResponse } from '../../../../common/components/visualization_actions/use_visualization_response'; jest.mock('../../../../common/containers/query_toggle'); @@ -78,8 +75,6 @@ jest.mock('../../../../common/lib/kibana', () => { jest.mock('../../../../common/components/visualization_actions/visualization_embeddable'); -jest.mock('../../../../common/hooks/use_experimental_features'); - jest.mock('../../../../common/components/visualization_actions/use_visualization_response', () => { const original = jest.requireActual( '../../../../common/components/visualization_actions/use_visualization_response' @@ -98,12 +93,6 @@ jest.mock('../common/hooks', () => { }; }); -const mockUseIsExperimentalFeatureEnabled = jest.fn((feature: keyof ExperimentalFeatures) => { - if (feature === 'alertsPageChartsEnabled') return false; - return allowedExperimentalValues[feature]; -}); - -jest.mock('../../../../common/hooks/use_experimental_features'); jest.mock('../../../hooks/alerts_visualization/use_alert_histogram_count', () => ({ useAlertHistogramCount: jest.fn().mockReturnValue(999), })); @@ -120,6 +109,7 @@ jest.mock('../../../../common/components/visualization_actions/use_visualization }), })); +const mockSetIsExpanded = jest.fn(); const defaultProps = { setQuery: jest.fn(), showBuildingBlockAlerts: false, @@ -127,6 +117,8 @@ const defaultProps = { showTotalAlertsCount: true, signalIndexName: 'signalIndexName', updateDateRange: jest.fn(), + isExpanded: true, + setIsExpanded: mockSetIsExpanded, }; const mockSetToggle = jest.fn(); const mockUseQueryToggle = useQueryToggle as jest.Mock; @@ -136,10 +128,6 @@ describe('AlertsHistogramPanel', () => { beforeEach(() => { jest.clearAllMocks(); mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); - - (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( - mockUseIsExperimentalFeatureEnabled - ); }); test('renders correctly', () => { @@ -381,30 +369,26 @@ describe('AlertsHistogramPanel', () => { }); describe('Query', () => { - it('it render with a illegal KQL', async () => { - await act(async () => { - jest.mock('@kbn/es-query', () => ({ - buildEsQuery: jest.fn().mockImplementation(() => { - throw new Error('Something went wrong'); - }), - })); - const props = { ...defaultProps, query: { query: 'host.name: "', language: 'kql' } }; - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...props} /> - </TestProviders> - ); + it('it render with a illegal KQL', () => { + jest.mock('@kbn/es-query', () => ({ + buildEsQuery: jest.fn().mockImplementation(() => { + throw new Error('Something went wrong'); + }), + })); + const props = { ...defaultProps, query: { query: 'host.name: "', language: 'kql' } }; + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...props} /> + </TestProviders> + ); - await waitFor(() => { - expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBeTruthy(); - }); - wrapper.unmount(); - }); + expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBeTruthy(); + wrapper.unmount(); }); }); describe('Filters', () => { - it('filters props is valid, alerts query include filter', async () => { + it('filters props is valid, alerts query include filter', () => { const statusFilter: Filter = { meta: { alias: null, @@ -433,139 +417,103 @@ describe('AlertsHistogramPanel', () => { </TestProviders> ); - await waitFor(() => { - expect( - (VisualizationEmbeddable as unknown as jest.Mock).mock.calls[0][0].timerange - ).toEqual({ - from: '2020-07-07T08:20:18.966Z', - to: '2020-07-08T08:20:18.966Z', - }); - expect( - (VisualizationEmbeddable as unknown as jest.Mock).mock.calls[0][0].extraOptions.filters - ).toEqual(props.filters); + expect((VisualizationEmbeddable as unknown as jest.Mock).mock.calls[0][0].timerange).toEqual({ + from: '2020-07-07T08:20:18.966Z', + to: '2020-07-08T08:20:18.966Z', }); + expect( + (VisualizationEmbeddable as unknown as jest.Mock).mock.calls[0][0].extraOptions.filters + ).toEqual(props.filters); wrapper.unmount(); }); }); - describe('toggleQuery', () => { - it('toggles', async () => { - await act(async () => { + describe('toggle button', () => { + describe('When setIsExpanded is available', () => { + it('toggles', () => { const wrapper = mount( <TestProviders> <AlertsHistogramPanel {...defaultProps} /> </TestProviders> ); + wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click'); - expect(mockSetToggle).toBeCalledWith(false); + expect(mockSetIsExpanded).toBeCalledWith(false); + expect(mockSetToggle).not.toBeCalled(); + wrapper.unmount(); }); - }); - describe('when alertsPageChartsEnabled = false', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUseIsExperimentalFeatureEnabled.mockReturnValueOnce(false); // for alertsPageChartsEnabled flag + it('when isExpanded is true, render histogram panel', async () => { + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...defaultProps} isExpanded={true} /> + </TestProviders> + ); + expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( + true + ); + wrapper.unmount(); }); - it('toggleStatus=true, render', async () => { - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - - expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(true); - expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( - true - ); - }); - }); - it('toggleStatus=false, hide', async () => { - mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle }); - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(false); - expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( - false - ); - }); + it('when isExpanded is false, hide histogram panel', async () => { + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...defaultProps} isExpanded={false} /> + </TestProviders> + ); + expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(false); + expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( + false + ); + wrapper.unmount(); }); }); - describe('when alertsPageChartsEnabled = true', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUseIsExperimentalFeatureEnabled.mockReturnValueOnce(true); // for alertsPageChartsEnabled flag - }); + describe('When setIsExpanded is not available, use toggleQuery', () => { + const props = { ...defaultProps, setIsExpanded: undefined }; - it('isExpanded=true, render', async () => { - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} isExpanded={true} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(true); - expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( - true - ); - }); - }); - it('isExpanded=false, hide', async () => { - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} isExpanded={false} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(false); - expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( - false - ); - }); + it('toggles', async () => { + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...props} /> + </TestProviders> + ); + wrapper.find('[data-test-subj="query-toggle-header"]').first().simulate('click'); + expect(mockSetToggle).toBeCalledWith(false); + wrapper.unmount(); }); - it('isExpanded is not passed in and toggleStatus =true, render', async () => { - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(true); - expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( - true - ); - }); + + it('when toggleStatus is true, render', () => { + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...props} /> + </TestProviders> + ); + expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( + true + ); + wrapper.unmount(); }); - it('isExpanded is not passed in and toggleStatus =false, hide', async () => { + + it('when toggleStatus is false, hide', async () => { mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle }); - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(false); - expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( - false - ); - }); + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...props} /> + </TestProviders> + ); + expect(wrapper.find('[data-test-subj="panelFlexGroup"]').exists()).toEqual(false); + expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toEqual( + false + ); + wrapper.unmount(); }); }); }); describe('VisualizationEmbeddable', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); - mockUseIsExperimentalFeatureEnabled.mockReturnValueOnce(false); // for alertsPageChartsEnabled flag - }); - test('it renders the header with alerts count', () => { const wrapper = mount( <TestProviders> @@ -582,50 +530,49 @@ describe('AlertsHistogramPanel', () => { }, ], }); + wrapper.setProps({ filters: [] }); wrapper.update(); expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).text()).toContain('999'); + wrapper.unmount(); }); - it('renders LensEmbeddable', async () => { - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - expect( - wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists() - ).toBeTruthy(); - }); + it('renders LensEmbeddable', () => { + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...defaultProps} /> + </TestProviders> + ); + expect(wrapper.find('[data-test-subj="embeddable-matrix-histogram"]').exists()).toBeTruthy(); + wrapper.unmount(); }); - it('renders LensEmbeddable with provided height', async () => { - await act(async () => { - mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - expect((VisualizationEmbeddable as unknown as jest.Mock).mock.calls[0][0].height).toEqual( - 155 - ); - }); + it('renders LensEmbeddable with provided height', () => { + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...defaultProps} /> + </TestProviders> + ); + + expect((VisualizationEmbeddable as unknown as jest.Mock).mock.calls[0][0].height).toEqual( + 155 + ); + wrapper.unmount(); }); - it('should render correct subtitle with alert count', async () => { - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).text()).toContain('999'); - }); + it('should render correct subtitle with alert count', () => { + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...defaultProps} /> + </TestProviders> + ); + + expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).text()).toContain('999'); + wrapper.unmount(); }); - it('should render correct subtitle with empty string', async () => { + it('should render correct subtitle with empty string', () => { (useVisualizationResponse as jest.Mock).mockReturnValue({ responses: [ { @@ -635,15 +582,14 @@ describe('AlertsHistogramPanel', () => { ], loading: false, }); + const wrapper = mount( + <TestProviders> + <AlertsHistogramPanel {...defaultProps} /> + </TestProviders> + ); - await act(async () => { - const wrapper = mount( - <TestProviders> - <AlertsHistogramPanel {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).text()).toEqual(''); - }); + expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).text()).toEqual(''); + wrapper.unmount(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index c61e3b51521d6..c2f25b4b5c80c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -36,7 +36,6 @@ import { KpiPanel, StackByComboBox } from '../common/components'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { GROUP_BY_TOP_LABEL } from '../common/translations'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { getAlertsHistogramLensAttributes as getLensAttributes } from '../../../../common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram'; import { SourcererScopeName } from '../../../../sourcerer/store/model'; import { VisualizationEmbeddable } from '../../../../common/components/visualization_actions/visualization_embeddable'; @@ -118,7 +117,6 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>( const [selectedStackByOption, setSelectedStackByOption] = useState<string>( onlyField == null ? defaultStackByOption : onlyField ); - const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); const onSelect = useCallback( (field: string) => { @@ -136,15 +134,17 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>( const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTIONS_HISTOGRAM_ID); + // alerts page uses isExpanded from kpi panel + // rules detail page and overview uses the toggle query const toggleQuery = useCallback( - (newToggleStatus: boolean) => { - if (isAlertsPageChartsEnabled && setIsExpanded !== undefined) { - setIsExpanded(newToggleStatus); - } else { - setToggleStatus(newToggleStatus); - } - }, - [setToggleStatus, setIsExpanded, isAlertsPageChartsEnabled] + (newToggleStatus: boolean) => + setIsExpanded ? setIsExpanded(newToggleStatus) : setToggleStatus(newToggleStatus), + [setToggleStatus, setIsExpanded] + ); + + const showHistogram = useMemo( + () => (setIsExpanded ? Boolean(isExpanded) : toggleStatus), + [setIsExpanded, isExpanded, toggleStatus] ); const timerange = useMemo(() => ({ from, to }), [from, to]); @@ -202,20 +202,6 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>( [onlyField, title] ); - const showHistogram = useMemo(() => { - if (isAlertsPageChartsEnabled) { - if (isExpanded !== undefined) { - // alerts page - return isExpanded; - } else { - // rule details page and overview page - return toggleStatus; - } - } else { - return toggleStatus; - } - }, [isAlertsPageChartsEnabled, isExpanded, toggleStatus]); - const { responses, loading } = useVisualizationResponse({ visualizationId, }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/mock_data.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/mock_data.ts index e2ab1a3ae9f84..df6f4e1573554 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/mock_data.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/mock_data.ts @@ -4,84 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export const stackedByBooleanField = { - took: 1, - timed_out: false, - _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, - hits: { - total: { - value: 3, - relation: 'eq', - }, - hits: [], - }, - timeout: false, - aggregations: { - alertsByGrouping: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 1, - key_as_string: 'true', - doc_count: 2683, - alerts: { - buckets: [ - { key_as_string: '2022-05-10T15:34:48.075Z', key: 1652196888075, doc_count: 0 }, - { key_as_string: '2022-05-10T16:19:48.074Z', key: 1652199588074, doc_count: 0 }, - { key_as_string: '2022-05-10T17:04:48.073Z', key: 1652202288073, doc_count: 0 }, - ], - }, - }, - ], - }, - }, -}; - -export const result = [ - { x: 1652196888075, y: 0, g: 'true' }, - { x: 1652199588074, y: 0, g: 'true' }, - { x: 1652202288073, y: 0, g: 'true' }, -]; - -export const stackedByTextField = { - took: 1, - timeout: false, - _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, - hits: { - total: { - value: 3, - relation: 'eq', - }, - hits: [], - }, - aggregations: { - alertsByGrouping: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 'MacBook-Pro.local', - doc_count: 2706, - alerts: { - buckets: [ - { key_as_string: '2022-05-10T15:34:48.075Z', key: 1652196888075, doc_count: 0 }, - { key_as_string: '2022-05-10T16:19:48.074Z', key: 1652199588074, doc_count: 0 }, - { key_as_string: '2022-05-10T17:04:48.073Z', key: 1652202288073, doc_count: 0 }, - ], - }, - }, - ], - }, - }, -}; - -export const textResult = [ - { x: 1652196888075, y: 0, g: 'MacBook-Pro.local' }, - { x: 1652199588074, y: 0, g: 'MacBook-Pro.local' }, - { x: 1652202288073, y: 0, g: 'MacBook-Pro.local' }, -]; - export const mockAlertSearchResponse = { took: 1, timed_out: false, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/types.ts deleted file mode 100644 index ba0205577aa3a..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/types.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. - */ - -export interface HistogramData { - x: number; - y: number; - g: string; -} - -export interface AlertsAggregation { - alertsByGrouping: { - buckets: AlertsGroupBucket[]; - }; -} - -export interface AlertsBucket { - key_as_string: string; - key: number; - doc_count: number; -} - -export interface AlertsGroupBucket { - key: string | number; - key_as_string?: string; - alerts: { - buckets: AlertsBucket[]; - }; - doc_count: number; -} - -export interface AlertsTotal { - value: number; - relation: string; -} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/aggregations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/aggregations.ts index dd646a20931f6..2cf83768e510d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/aggregations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/aggregations.ts @@ -17,23 +17,6 @@ export const severityAggregations = { }, }; -export const alertTypeAggregations = { - alertsByType: { - terms: { - field: ALERT_RULE_NAME, - size: DEFAULT_QUERY_SIZE, - }, - aggs: { - ruleByEventType: { - terms: { - field: 'event.type', - size: DEFAULT_QUERY_SIZE, - }, - }, - }, - }, -}; - export const alertRuleAggregations = { alertsByRule: { terms: { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.test.tsx index 7ff8c949e0624..3f8df968204e4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.test.tsx @@ -6,8 +6,7 @@ */ import { parseData } from './helpers'; import * as severityMock from '../severity_level_panel/mock_data'; -import * as alertsTypeMock from '../alerts_by_type_panel/mock_type_data'; -import * as alertsRuleMock from '../alerts_by_type_panel/mock_rule_data'; +import * as alertsRuleMock from '../alerts_by_rule_panel/mock_rule_data'; import * as alertsGroupingMock from '../alerts_progress_bar_panel/mock_data'; import type { SummaryChartsAgg } from './types'; import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; @@ -18,12 +17,7 @@ describe('parse data by aggregation type', () => { expect(res).toEqual(severityMock.parsedAlerts); }); - test('parse alert type data', () => { - const resType = parseData( - alertsTypeMock.mockAlertsData as AlertSearchResponse<{}, SummaryChartsAgg> - ); - expect(resType).toEqual(alertsTypeMock.parsedAlerts); - + test('parse alert by rule data', () => { const resRule = parseData( alertsRuleMock.mockAlertsData as AlertSearchResponse<{}, SummaryChartsAgg> ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.tsx index 7e530ba26011f..1f43e1cb7367a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/helpers.tsx @@ -7,12 +7,7 @@ import type { SummaryChartsAgg } from './types'; import type { AlertSearchResponse } from '../../../containers/detection_engine/alerts/types'; import { parseSeverityData, getIsAlertsBySeverityAgg } from '../severity_level_panel/helpers'; -import { - parseAlertsTypeData, - getIsAlertsByTypeAgg, - parseAlertsRuleData, - getIsAlertsByRuleAgg, -} from '../alerts_by_type_panel/helpers'; +import { parseAlertsRuleData, getIsAlertsByRuleAgg } from '../alerts_by_rule_panel/helpers'; import { parseAlertsGroupingData, getIsAlertsByGroupingAgg, @@ -26,9 +21,6 @@ export const parseData = (data: AlertSearchResponse<{}, SummaryChartsAgg>) => { if (getIsAlertsBySeverityAgg(data)) { return parseSeverityData(data); } - if (getIsAlertsByTypeAgg(data)) { - return parseAlertsTypeData(data); - } if (getIsAlertsByRuleAgg(data)) { return parseAlertsRuleData(data); } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.test.tsx index f02043f3d7c55..4513a0591803a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.test.tsx @@ -6,10 +6,8 @@ */ import { act, render, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; -import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { TestProviders } from '../../../../common/mock'; import { AlertsSummaryChartsPanel } from '.'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import type { GroupBySelection } from '../alerts_progress_bar_panel/types'; jest.mock('../../../../common/lib/kibana'); @@ -20,23 +18,15 @@ jest.mock('react-router-dom', () => { return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) }; }); -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -jest.mock('../../../../common/hooks/use_experimental_features'); - describe('AlertsSummaryChartsPanel', () => { + const mockSetIsExpanded = jest.fn(); const defaultProps = { signalIndexName: 'signalIndexName', isExpanded: true, - setIsExpanded: jest.fn(), + setIsExpanded: mockSetIsExpanded, groupBySelection: 'host.name' as GroupBySelection, setGroupBySelection: jest.fn(), }; - const mockSetToggle = jest.fn(); - const mockUseQueryToggle = useQueryToggle as jest.Mock; - beforeEach(() => { - mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - }); test('renders correctly', async () => { await act(async () => { @@ -95,11 +85,11 @@ describe('AlertsSummaryChartsPanel', () => { if (element) { fireEvent.click(element); } - expect(mockSetToggle).toBeCalledWith(false); + expect(mockSetIsExpanded).toBeCalledWith(false); }); }); - it('alertsPageChartsEnabled is false and toggleStatus=true, render', async () => { + it('when isExpanded is true, render summary chart', async () => { await act(async () => { const { container } = render( <TestProviders> @@ -112,35 +102,7 @@ describe('AlertsSummaryChartsPanel', () => { }); }); - it('alertsPageChartsEnabled is false and toggleStatus=false, hide', async () => { - mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle }); - await act(async () => { - const { container } = render( - <TestProviders> - <AlertsSummaryChartsPanel {...defaultProps} /> - </TestProviders> - ); - expect( - container.querySelector('[data-test-subj="alerts-charts-container"]') - ).not.toBeInTheDocument(); - }); - }); - - it('alertsPageChartsEnabled is true and isExpanded=true, render', async () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - await act(async () => { - const { container } = render( - <TestProviders> - <AlertsSummaryChartsPanel {...defaultProps} /> - </TestProviders> - ); - expect( - container.querySelector('[data-test-subj="alerts-charts-container"]') - ).toBeInTheDocument(); - }); - }); - it('alertsPageChartsEnabled is true and isExpanded=false, hide', async () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + it('when isExpanded is false, hide summary chart', async () => { await act(async () => { const { container } = render( <TestProviders> diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx index ff1597a0246af..791f8eb9dc1ce 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; import type { Filter, Query } from '@kbn/es-query'; import styled from 'styled-components'; @@ -13,23 +13,20 @@ import * as i18n from './translations'; import { KpiPanel } from '../common/components'; import { HeaderSection } from '../../../../common/components/header_section'; import { SeverityLevelPanel } from '../severity_level_panel'; -import { AlertsByTypePanel } from '../alerts_by_type_panel'; +import { AlertsByRulePanel } from '../alerts_by_rule_panel'; import { AlertsProgressBarPanel } from '../alerts_progress_bar_panel'; -import { useQueryToggle } from '../../../../common/containers/query_toggle'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import type { GroupBySelection } from '../alerts_progress_bar_panel/types'; import type { AddFilterProps } from '../common/types'; const StyledFlexGroup = styled(EuiFlexGroup)` - @media only screen and (min-width: ${({ theme }) => theme.eui.euiBreakpoints.l}); + @media only screen and (min-width: ${({ theme }) => theme.eui.euiBreakpoints.l}) { + } `; const StyledFlexItem = styled(EuiFlexItem)` min-width: 355px; `; -const DETECTIONS_ALERTS_CHARTS_ID = 'detections-alerts-charts'; - interface Props { alignHeader?: 'center' | 'baseline' | 'stretch' | 'flexStart' | 'flexEnd'; filters?: Filter[]; @@ -39,8 +36,8 @@ interface Props { signalIndexName: string | null; title?: React.ReactNode; runtimeMappings?: MappingRuntimeFields; - isExpanded?: boolean; - setIsExpanded?: (status: boolean) => void; + isExpanded: boolean; + setIsExpanded: (status: boolean) => void; groupBySelection: GroupBySelection; setGroupBySelection: (groupBySelection: GroupBySelection) => void; } @@ -59,40 +56,16 @@ export const AlertsSummaryChartsPanel: React.FC<Props> = ({ groupBySelection, setGroupBySelection, }: Props) => { - const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); - - const { toggleStatus, setToggleStatus } = useQueryToggle(DETECTIONS_ALERTS_CHARTS_ID); const toggleQuery = useCallback( (status: boolean) => { - if (isAlertsPageChartsEnabled && setIsExpanded) { - setIsExpanded(status); - } else { - setToggleStatus(status); - } + setIsExpanded(status); }, - [setToggleStatus, setIsExpanded, isAlertsPageChartsEnabled] + [setIsExpanded] ); - const querySkip = useMemo( - () => (isAlertsPageChartsEnabled ? !isExpanded : !toggleStatus), - [isAlertsPageChartsEnabled, isExpanded, toggleStatus] - ); - - const status: boolean = useMemo(() => { - if (isAlertsPageChartsEnabled && isExpanded) { - return true; - } - if (!isAlertsPageChartsEnabled && toggleStatus) { - return true; - } - return false; - }, [isAlertsPageChartsEnabled, isExpanded, toggleStatus]); - return ( <KpiPanel - $toggleStatus={ - isAlertsPageChartsEnabled && isExpanded !== undefined ? isExpanded : toggleStatus - } + $toggleStatus={isExpanded} data-test-subj="alerts-charts-panel" hasBorder height={panelHeight} @@ -104,10 +77,10 @@ export const AlertsSummaryChartsPanel: React.FC<Props> = ({ titleSize="s" hideSubtitle showInspectButton={false} - toggleStatus={isAlertsPageChartsEnabled ? isExpanded : toggleStatus} + toggleStatus={isExpanded} toggleQuery={toggleQuery} /> - {status && ( + {isExpanded && ( <StyledFlexGroup data-test-subj="alerts-charts-container" className="eui-yScroll" @@ -120,17 +93,17 @@ export const AlertsSummaryChartsPanel: React.FC<Props> = ({ query={query} signalIndexName={signalIndexName} runtimeMappings={runtimeMappings} - skip={querySkip} + skip={!isExpanded} addFilter={addFilter} /> </StyledFlexItem> <StyledFlexItem> - <AlertsByTypePanel + <AlertsByRulePanel filters={filters} query={query} signalIndexName={signalIndexName} runtimeMappings={runtimeMappings} - skip={querySkip} + skip={!isExpanded} /> </StyledFlexItem> <StyledFlexItem> @@ -139,7 +112,7 @@ export const AlertsSummaryChartsPanel: React.FC<Props> = ({ query={query} signalIndexName={signalIndexName} runtimeMappings={runtimeMappings} - skip={querySkip} + skip={!isExpanded} groupBySelection={groupBySelection} setGroupBySelection={setGroupBySelection} addFilter={addFilter} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/types.ts index 9ed251fc5f713..6067bad447623 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/types.ts @@ -8,11 +8,7 @@ import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types' import type { Filter, Query } from '@kbn/es-query'; import type { SeverityBuckets as SeverityData } from '../../../../overview/components/detection_response/alerts_by_status/types'; import type { AlertsBySeverityAgg } from '../severity_level_panel/types'; -import type { - AlertsByTypeAgg, - AlertsTypeData, - AlertsByRuleAgg, -} from '../alerts_by_type_panel/types'; +import type { AlertsByRuleAgg, AlertsByRuleData } from '../alerts_by_rule_panel/types'; import type { AlertsByGroupingAgg, AlertsProgressBarData, @@ -20,12 +16,12 @@ import type { import type { ChartCollapseAgg, ChartCollapseData } from '../chart_panels/chart_collapse/types'; export type SummaryChartsAgg = Partial< - AlertsBySeverityAgg | AlertsByTypeAgg | AlertsByGroupingAgg | ChartCollapseAgg | AlertsByRuleAgg + AlertsBySeverityAgg | AlertsByRuleAgg | AlertsByGroupingAgg | ChartCollapseAgg >; export type SummaryChartsData = | SeverityData - | AlertsTypeData + | AlertsByRuleData | AlertsProgressBarData | ChartCollapseData; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx index 7719fd47ea606..22dd8da0e1101 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_summary_charts_panel/use_summary_chart_data.test.tsx @@ -12,10 +12,8 @@ import type { UseAlerts, UseAlertsQueryProps } from './use_summary_chart_data'; import { useSummaryChartData, getAlertsQuery } from './use_summary_chart_data'; import * as aggregations from './aggregations'; import * as severityMock from '../severity_level_panel/mock_data'; -import * as alertTypeMock from '../alerts_by_type_panel/mock_type_data'; -import * as alertRuleMock from '../alerts_by_type_panel/mock_rule_data'; +import * as alertRuleMock from '../alerts_by_rule_panel/mock_rule_data'; import * as alertsGroupingMock from '../alerts_progress_bar_panel/mock_data'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; const from = '2022-04-05T12:00:00.000Z'; const to = '2022-04-08T12:00:00.000Z'; @@ -51,9 +49,6 @@ jest.mock('../../../../common/containers/use_global_time', () => { }; }); -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -jest.mock('../../../../common/hooks/use_experimental_features'); - describe('getAlertsQuery', () => { test('it returns the expected severity query', () => { expect( @@ -66,25 +61,6 @@ describe('getAlertsQuery', () => { ).toEqual(severityMock.query); }); - test('it returns the expected alerts by type query', () => { - expect( - getAlertsQuery({ - from, - to, - additionalFilters, - aggregations: aggregations.alertTypeAggregations, - }) - ).toEqual(alertTypeMock.query); - expect( - getAlertsQuery({ - from, - to, - additionalFilters, - aggregations: aggregations.alertRuleAggregations, - }) - ).toEqual(alertRuleMock.query); - }); - test('it returns the expected alerts by grouping query', () => { expect( getAlertsQuery({ @@ -187,34 +163,14 @@ describe('get summary charts data', () => { }); }); - describe('get alerts by type data', () => { + describe('get alerts by rule data', () => { beforeEach(() => { jest.clearAllMocks(); mockDateNow.mockReturnValue(dateNow); mockUseQueryAlerts.mockReturnValue(defaultUseQueryAlertsReturn); - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); }); - it('should return correct default values when alertsTypeChartsEnabled is true', () => { - const { result } = renderUseSummaryChartData({ - aggregations: aggregations.alertTypeAggregations, - }); - expect(result.current).toEqual({ - items: [], - isLoading: false, - updatedAt: dateNow, - }); - - expect(mockUseQueryAlerts).toBeCalledWith({ - query: alertTypeMock.query, - indexName: 'signal-alerts', - skip: false, - queryName: ALERTS_QUERY_NAMES.COUNT, - }); - }); - - it('should return correct default values when alertsTypeChartsEnabled is false', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + it('should return correct default values', () => { const { result } = renderUseSummaryChartData({ aggregations: aggregations.alertRuleAggregations, }); @@ -233,24 +189,7 @@ describe('get summary charts data', () => { }); }); - it('should return parsed alerts by type items when alertsTypeChartsEnabled is true', () => { - mockUseQueryAlerts.mockReturnValue({ - ...defaultUseQueryAlertsReturn, - data: alertTypeMock.mockAlertsData, - }); - - const { result } = renderUseSummaryChartData({ - aggregations: aggregations.alertTypeAggregations, - }); - expect(result.current).toEqual({ - items: alertTypeMock.parsedAlerts, - isLoading: false, - updatedAt: dateNow, - }); - }); - - it('should return parsed alerts by type items when alertsTypeChartsEnabled is false', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); + it('should return parsed alerts by type items', () => { mockUseQueryAlerts.mockReturnValue({ ...defaultUseQueryAlertsReturn, data: alertRuleMock.mockAlertsData, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx index 8145fcf698704..303b85a40e6ee 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.test.tsx @@ -10,38 +10,13 @@ import React from 'react'; import { useAlertsLocalStorage } from '.'; import { TestProviders } from '../../../../../common/mock'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; - -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -jest.mock('../../../../../common/hooks/use_experimental_features'); describe('useAlertsLocalStorage', () => { const wrapper = ({ children }: { children: React.ReactNode }) => ( <TestProviders>{children}</TestProviders> ); - test('it returns the expected defaults when isAlertsPageCharts is disabled', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - const { result } = renderHook(() => useAlertsLocalStorage(), { wrapper }); - - const defaults = Object.fromEntries( - Object.entries(result.current).filter((x) => typeof x[1] !== 'function') - ); - - expect(defaults).toEqual({ - alertViewSelection: 'trend', // default to the trend chart - countTableStackBy0: 'kibana.alert.rule.name', - countTableStackBy1: 'host.name', - groupBySelection: 'host.name', - isTreemapPanelExpanded: true, - riskChartStackBy0: 'kibana.alert.rule.name', - riskChartStackBy1: 'host.name', - trendChartStackBy: 'kibana.alert.rule.name', - }); - }); - - test('it returns the expected defaults when isAlertsPageCharts is enaabled', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + test('it returns the expected defaults', () => { const { result } = renderHook(() => useAlertsLocalStorage(), { wrapper }); const defaults = Object.fromEntries( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.tsx index a7c795037df86..f4aac7a42cedd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/alerts_local_storage/index.tsx @@ -27,14 +27,12 @@ import { import { DEFAULT_STACK_BY_FIELD, DEFAULT_STACK_BY_FIELD1 } from '../../common/config'; import type { AlertsSettings } from './types'; import type { AlertViewSelection } from '../chart_select/helpers'; -import { CHARTS_ID, TREND_ID } from '../chart_select/helpers'; +import { CHARTS_ID } from '../chart_select/helpers'; import type { GroupBySelection } from '../../alerts_progress_bar_panel/types'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; export const useAlertsLocalStorage = (): AlertsSettings => { - const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); const [alertViewSelection, setAlertViewSelection] = useLocalStorage<AlertViewSelection>({ - defaultValue: isAlertsPageChartsEnabled ? CHARTS_ID : TREND_ID, + defaultValue: CHARTS_ID, key: getSettingKey({ category: VIEW_CATEGORY, page: ALERTS_PAGE, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_collapse/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_collapse/index.tsx index dbeea09315331..0539b7f7615d6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_collapse/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_collapse/index.tsx @@ -50,7 +50,8 @@ const combinedAggregations = (groupBySelection: GroupBySelection) => { const StyledEuiFlexGroup = styled(EuiFlexGroup)` margin-top: ${({ theme }) => theme.eui.euiSizeXS}; - @media only screen and (min-width: ${({ theme }) => theme.eui.euiBreakpoints.l}); + @media only screen and (min-width: ${({ theme }) => theme.eui.euiBreakpoints.l}) { + } `; const SeverityWrapper = styled(EuiFlexItem)` @@ -105,7 +106,7 @@ export const ChartCollapse: React.FC<Props> = ({ }); }, [data]); const groupBy = useMemo(() => getGroupByLabel(groupBySelection), [groupBySelection]); - // className="eui-alignMiddle" + return ( <InspectButtonContainer> {!isLoading && ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.test.ts index e842f998bd5c1..11e58253c94f0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.test.ts @@ -5,102 +5,12 @@ * 2.0. */ -import type { AlertViewSelection } from './helpers'; -import { - getButtonProperties, - getContextMenuPanels, - getOptionProperties, - TABLE_ID, - TREEMAP_ID, - TREND_ID, - CHARTS_ID, -} from './helpers'; +import { getOptionProperties, TABLE_ID, TREEMAP_ID, TREND_ID, CHARTS_ID } from './helpers'; import * as i18n from './translations'; describe('helpers', () => { beforeEach(() => jest.clearAllMocks()); - describe('getButtonProperties', () => { - test('it returns the expected properties when alertViewSelection is Trend', () => { - expect(getButtonProperties(TREND_ID)).toEqual({ - 'data-test-subj': TREND_ID, - icon: 'visBarVerticalStacked', - name: i18n.TREND, - }); - }); - - test('it returns the expected properties when alertViewSelection is Table', () => { - expect(getButtonProperties(TABLE_ID)).toEqual({ - 'data-test-subj': TABLE_ID, - icon: 'visTable', - name: i18n.TABLE, - }); - }); - - test('it returns the expected properties when alertViewSelection is Treemap', () => { - expect(getButtonProperties(TREEMAP_ID)).toEqual({ - 'data-test-subj': TREEMAP_ID, - icon: 'grid', - name: i18n.TREEMAP, - }); - }); - - test('it returns the expected properties when alertViewSelection is charts', () => { - expect(getButtonProperties(CHARTS_ID)).toEqual({ - 'data-test-subj': CHARTS_ID, - icon: 'visPie', - name: i18n.CHARTS, - }); - }); - }); - - describe('getContextMenuPanels', () => { - const alertViewSelections: AlertViewSelection[] = ['trend', 'table', 'treemap', 'charts']; - const closePopover = jest.fn(); - const setAlertViewSelection = jest.fn(); - - alertViewSelections.forEach((alertViewSelection) => { - test(`it returns the expected panel id when alertViewSelection is '${alertViewSelection}'`, () => { - const panels = getContextMenuPanels({ - alertViewSelection, - closePopover, - setAlertViewSelection, - isAlertsPageChartsEnabled: true, // remove after charts is implemented - }); - - expect(panels[0].id).toEqual(0); - }); - - test(`onClick invokes setAlertViewSelection with '${alertViewSelection}' item when alertViewSelection is '${alertViewSelection}'`, () => { - const panels = getContextMenuPanels({ - alertViewSelection, - closePopover, - setAlertViewSelection, - isAlertsPageChartsEnabled: true, // remove after charts is implemented - }); - - const item = panels[0].items?.find((x) => x['data-test-subj'] === alertViewSelection); - (item?.onClick as () => void)(); - - expect(setAlertViewSelection).toBeCalledWith(alertViewSelection); - }); - - test(`onClick invokes closePopover when alertViewSelection is '${alertViewSelection}'`, () => { - const panels = getContextMenuPanels({ - alertViewSelection, - closePopover, - setAlertViewSelection, - isAlertsPageChartsEnabled: true, // remove after charts is implemented - }); - - const item = panels[0].items?.find((x) => x['data-test-subj'] === alertViewSelection); - (item?.onClick as () => void)(); - - expect(closePopover).toBeCalled(); - }); - }); - }); - describe('getOptionProperties', () => { test('it returns the expected properties when alertViewSelection is Trend', () => { expect(getOptionProperties(TREND_ID)).toEqual({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.ts index bc0deb426fe59..b47b8ea63bc82 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EuiContextMenuPanelDescriptor, EuiButtonGroupOptionProps } from '@elastic/eui'; +import type { EuiButtonGroupOptionProps } from '@elastic/eui'; import * as i18n from './translations'; @@ -22,77 +22,6 @@ export interface ButtonProperties { name: string; } -export const getButtonProperties = (alertViewSelection: AlertViewSelection): ButtonProperties => { - const table = { 'data-test-subj': alertViewSelection, icon: 'visTable', name: i18n.TABLE }; - - switch (alertViewSelection) { - case TABLE_ID: - return table; - case TREND_ID: - return { - 'data-test-subj': alertViewSelection, - icon: 'visBarVerticalStacked', - name: i18n.TREND, - }; - case TREEMAP_ID: - return { 'data-test-subj': alertViewSelection, icon: 'grid', name: i18n.TREEMAP }; - case CHARTS_ID: - return { 'data-test-subj': alertViewSelection, icon: 'visPie', name: i18n.CHARTS }; - default: - return table; - } -}; - -export const getContextMenuPanels = ({ - alertViewSelection, - closePopover, - setAlertViewSelection, - isAlertsPageChartsEnabled, -}: { - alertViewSelection: AlertViewSelection; - closePopover: () => void; - setAlertViewSelection: (alertViewSelection: AlertViewSelection) => void; - isAlertsPageChartsEnabled: boolean; -}): EuiContextMenuPanelDescriptor[] => [ - { - id: 0, - items: [ - { - ...getButtonProperties('table'), - onClick: () => { - closePopover(); - setAlertViewSelection('table'); - }, - }, - { - ...getButtonProperties('trend'), - onClick: () => { - closePopover(); - setAlertViewSelection('trend'); - }, - }, - { - ...getButtonProperties('treemap'), - onClick: () => { - closePopover(); - setAlertViewSelection('treemap'); - }, - }, - ...(isAlertsPageChartsEnabled - ? [ - { - ...getButtonProperties('charts'), - onClick: () => { - closePopover(); - setAlertViewSelection('charts'); - }, - }, - ] - : []), - ], - }, -]; - export const getOptionProperties = ( alertViewSelection: AlertViewSelection ): EuiButtonGroupOptionProps => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.test.tsx index 4d7798a4fcbe6..6801bae62f3fe 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.test.tsx @@ -5,56 +5,16 @@ * 2.0. */ import { render, screen, fireEvent } from '@testing-library/react'; -import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; import React from 'react'; - import { TestProviders } from '../../../../../common/mock'; -import * as i18n from './translations'; import { ChartSelect } from '.'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; - -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -jest.mock('../../../../../common/hooks/use_experimental_features'); describe('ChartSelect', () => { beforeEach(() => { jest.clearAllMocks(); }); - test('it renders the chart select button when alertsPageChartsEnabled is false', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - render( - <TestProviders> - <ChartSelect alertViewSelection="trend" setAlertViewSelection={jest.fn()} /> - </TestProviders> - ); - - expect( - screen.getByRole('button', { name: i18n.SELECT_A_CHART_ARIA_LABEL }) - ).toBeInTheDocument(); - }); - - test('it invokes `setAlertViewSelection` with the expected value when a chart is selected and alertsPageChartsEnabled is false', async () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - const setAlertViewSelection = jest.fn(); - render( - <TestProviders> - <ChartSelect alertViewSelection="trend" setAlertViewSelection={setAlertViewSelection} /> - </TestProviders> - ); - - const selectButton = screen.getByRole('button', { name: i18n.SELECT_A_CHART_ARIA_LABEL }); - selectButton.click(); - await waitForEuiPopoverOpen(); - - const treemapMenuItem = screen.getByRole('button', { name: i18n.TREEMAP }); - treemapMenuItem.click(); - - expect(setAlertViewSelection).toBeCalledWith('treemap'); - }); - - test('it renders the chart select tabs when alertsPageChartsEnabled is true', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + test('it renders the chart select tabs', () => { render( <TestProviders> <ChartSelect alertViewSelection="trend" setAlertViewSelection={jest.fn()} /> @@ -65,8 +25,7 @@ describe('ChartSelect', () => { expect(screen.getByTitle('Trend')).toHaveAttribute('aria-pressed', 'true'); }); - test('changing selection render correctly when alertsPageChartsEnabled is true', () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + test('changing selection render correctly', () => { const setAlertViewSelection = jest.fn(); const { rerender } = render( <TestProviders> diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.tsx index c31e04afeb301..58f3c4f6fe284 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/index.tsx @@ -5,95 +5,39 @@ * 2.0. */ -import type { EuiContextMenuPanelDescriptor } from '@elastic/eui'; -import { EuiButton, EuiContextMenu, EuiIcon, EuiPopover, EuiButtonGroup } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import styled from 'styled-components'; - +import { EuiButtonGroup } from '@elastic/eui'; +import React, { useMemo } from 'react'; import type { AlertViewSelection } from './helpers'; -import { getButtonProperties, getContextMenuPanels, getOptionProperties } from './helpers'; +import { getOptionProperties } from './helpers'; import * as i18n from './translations'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; + interface Props { alertViewSelection: AlertViewSelection; setAlertViewSelection: (alertViewSelection: AlertViewSelection) => void; } -const ChartTypeIcon = styled(EuiIcon)` - margin-right: ${({ theme }) => theme.eui.euiSizeS}; -`; const AlertViewOptions: AlertViewSelection[] = ['charts', 'trend', 'table', 'treemap']; -const ChartSelectComponent: React.FC<Props> = ({ +export const ChartSelect: React.FC<Props> = ({ alertViewSelection, setAlertViewSelection, }: Props) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const closePopover = useCallback(() => setIsPopoverOpen(false), []); - const onButtonClick = useCallback(() => setIsPopoverOpen((currentVal) => !currentVal), []); - - const button = useMemo(() => { - const buttonProperties = getButtonProperties(alertViewSelection); - - return ( - <EuiButton - aria-label={i18n.SELECT_A_CHART_ARIA_LABEL} - className="kbnToolbarButton" - color="text" - data-test-subj="chartSelect" - iconSide="right" - iconType="arrowDown" - onClick={onButtonClick} - > - <ChartTypeIcon type={buttonProperties.icon} /> - <span>{buttonProperties.name}</span> - </EuiButton> - ); - }, [alertViewSelection, onButtonClick]); - const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); const options = useMemo(() => { return AlertViewOptions.map((option: AlertViewSelection) => getOptionProperties(option)); }, []); - const panels: EuiContextMenuPanelDescriptor[] = useMemo( - () => - getContextMenuPanels({ - alertViewSelection, - closePopover, - setAlertViewSelection, - isAlertsPageChartsEnabled, - }), - [alertViewSelection, closePopover, setAlertViewSelection, isAlertsPageChartsEnabled] - ); - return ( - <> - {isAlertsPageChartsEnabled ? ( - <EuiButtonGroup - name="chart-select" - legend={i18n.LEGEND_TITLE} - options={options} - idSelected={alertViewSelection} - onChange={(id) => setAlertViewSelection(id as AlertViewSelection)} - buttonSize="compressed" - color="primary" - data-test-subj="chart-select-tabs" - /> - ) : ( - <EuiPopover - anchorPosition="downLeft" - button={button} - closePopover={closePopover} - isOpen={isPopoverOpen} - panelPaddingSize="none" - > - <EuiContextMenu initialPanelId={0} panels={panels} /> - </EuiPopover> - )} - </> + <EuiButtonGroup + name="chart-select" + legend={i18n.LEGEND_TITLE} + options={options} + idSelected={alertViewSelection} + onChange={(id) => setAlertViewSelection(id as AlertViewSelection)} + buttonSize="compressed" + color="primary" + data-test-subj="chart-select-tabs" + /> ); }; -ChartSelectComponent.displayName = 'ChartSelectComponent'; - -export const ChartSelect = React.memo(ChartSelectComponent); +ChartSelect.displayName = 'ChartSelect'; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/translations.ts index a18c1b25dc417..2da0abc04ec42 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/chart_select/translations.ts @@ -7,17 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const SELECT_A_CHART_ARIA_LABEL = i18n.translate( - 'xpack.securitySolution.components.chartSelect.selectAChartAriaLabel', - { - defaultMessage: 'Select a chart', - } -); - -export const TABLE = i18n.translate('xpack.securitySolution.components.chartSelect.tableOption', { - defaultMessage: 'Table', -}); - export const TREND = i18n.translate('xpack.securitySolution.components.chartSelect.trendOption', { defaultMessage: 'Trend', }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.test.tsx index dea6278dc5db9..668f323d147cb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.test.tsx @@ -16,7 +16,6 @@ import { mockBrowserFields } from '../../../../common/containers/source/mock'; import { useSourcererDataView } from '../../../../sourcerer/containers'; import { TestProviders } from '../../../../common/mock'; import { ChartPanels } from '.'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { LensEmbeddable } from '../../../../common/components/visualization_actions/lens_embeddable'; import { createResetGroupByFieldAction } from '../alerts_histogram_panel/helpers'; @@ -27,6 +26,11 @@ jest.mock('../../../../sourcerer/containers'); jest.mock('../../../../common/components/visualization_actions/lens_embeddable'); jest.mock('../../../../common/components/page/use_refetch_by_session', () => ({ useRefetchByRestartingSession: jest.fn().mockReturnValue({ + session: { + current: { + start: jest.fn(), + }, + }, searchSessionId: 'mockSearchSessionId', refetchByRestartingSession: jest.fn(), }), @@ -63,9 +67,6 @@ jest.mock('../../../../common/lib/kibana', () => { }; }); -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -jest.mock('../../../../common/hooks/use_experimental_features'); - const mockSetToggle = jest.fn(); const mockUseQueryToggle = useQueryToggle as jest.Mock; jest.mock('../../../../common/containers/query_toggle'); @@ -149,7 +150,6 @@ const resetGroupByFields = () => { describe('ChartPanels', () => { beforeEach(() => { jest.clearAllMocks(); - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); (useSourcererDataView as jest.Mock).mockReturnValue({ @@ -163,20 +163,7 @@ describe('ChartPanels', () => { }); }); - test('it renders the chart selector when alertsPageChartsEnabled is false', async () => { - render( - <TestProviders> - <ChartPanels {...defaultProps} /> - </TestProviders> - ); - - await waitFor(() => { - expect(screen.getByTestId('chartSelect')).toBeInTheDocument(); - }); - }); - - test('it renders the chart selector tabs when alertsPageChartsEnabled is true and toggle is true', async () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + test('when toggle is true, renders the chart selector tabs', async () => { mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: mockSetToggle }); render( <TestProviders> @@ -189,8 +176,7 @@ describe('ChartPanels', () => { }); }); - test('it renders the chart collapse when alertsPageChartsEnabled is true and toggle is false', async () => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); + test('when toggle is false, renders the chart collapse', async () => { mockUseQueryToggle.mockReturnValue({ toggleStatus: false, setToggleStatus: mockSetToggle }); render( <TestProviders> @@ -465,7 +451,6 @@ describe('ChartPanels', () => { ...defaultAlertSettings, alertViewSelection: 'charts', }); - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); render( <TestProviders> <ChartPanels {...defaultProps} isLoadingIndexPattern={true} /> diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.tsx index edbfe756acfd6..c37c7b0513a51 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/chart_panels/index.tsx @@ -9,8 +9,6 @@ import type { Filter, Query } from '@kbn/es-query'; import { EuiFlexItem, EuiSkeletonText } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; - -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useAlertsLocalStorage } from './alerts_local_storage'; import type { AlertsSettings } from './alerts_local_storage/types'; import { ChartContextMenu } from './chart_context_menu'; @@ -65,21 +63,18 @@ const ChartPanelsComponent: React.FC<Props> = ({ const { toggleStatus: isExpanded, setToggleStatus: setIsExpanded } = useQueryToggle( DETECTIONS_ALERTS_CHARTS_PANEL_ID ); - const isAlertsPageChartsEnabled = useIsExperimentalFeatureEnabled('alertsPageChartsEnabled'); const { alertViewSelection, countTableStackBy0, countTableStackBy1, groupBySelection, - isTreemapPanelExpanded, riskChartStackBy0, riskChartStackBy1, setAlertViewSelection, setCountTableStackBy0, setCountTableStackBy1, setGroupBySelection, - setIsTreemapPanelExpanded, setRiskChartStackBy0, setRiskChartStackBy1, setTrendChartStackBy, @@ -149,37 +144,25 @@ const ChartPanelsComponent: React.FC<Props> = ({ ); const title = useMemo(() => { - if (isAlertsPageChartsEnabled) { - return isExpanded ? ( - <ChartSelectContainer> - <ChartSelect - alertViewSelection={alertViewSelection} - setAlertViewSelection={setAlertViewSelection} - /> - </ChartSelectContainer> - ) : ( - <ChartCollapse - groupBySelection={groupBySelection} - filters={alertsDefaultFilters} - query={query} - signalIndexName={signalIndexName} - runtimeMappings={runtimeMappings} + return isExpanded ? ( + <ChartSelectContainer> + <ChartSelect + alertViewSelection={alertViewSelection} + setAlertViewSelection={setAlertViewSelection} /> - ); - } else { - return ( - <ChartSelectContainer> - <ChartSelect - alertViewSelection={alertViewSelection} - setAlertViewSelection={setAlertViewSelection} - /> - </ChartSelectContainer> - ); - } + </ChartSelectContainer> + ) : ( + <ChartCollapse + groupBySelection={groupBySelection} + filters={alertsDefaultFilters} + query={query} + signalIndexName={signalIndexName} + runtimeMappings={runtimeMappings} + /> + ); }, [ alertViewSelection, setAlertViewSelection, - isAlertsPageChartsEnabled, isExpanded, groupBySelection, alertsDefaultFilters, @@ -230,7 +213,7 @@ const ChartPanelsComponent: React.FC<Props> = ({ chartOptionsContextMenu={chartOptionsContextMenu} extraActions={resetGroupByFieldAction} filters={alertsDefaultFilters} - inspectTitle={isAlertsPageChartsEnabled ? i18n.COUNTS : i18n.TABLE} + inspectTitle={i18n.COUNTS} panelHeight={CHART_PANEL_HEIGHT} setStackByField0={updateCommonStackBy0} setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} @@ -259,13 +242,11 @@ const ChartPanelsComponent: React.FC<Props> = ({ chartOptionsContextMenu={chartOptionsContextMenu} height={CHART_PANEL_HEIGHT} inspectTitle={i18n.TREEMAP} - isPanelExpanded={isAlertsPageChartsEnabled ? isExpanded : isTreemapPanelExpanded} + isPanelExpanded={isExpanded} filters={alertsDefaultFilters} query={query} riskSubAggregationField="kibana.alert.risk_score" - setIsPanelExpanded={ - isAlertsPageChartsEnabled ? setIsExpanded : setIsTreemapPanelExpanded - } + setIsPanelExpanded={setIsExpanded} setStackByField0={updateCommonStackBy0} setStackByField0ComboboxInputRef={setStackByField0ComboboxInputRef} setStackByField1={updateCommonStackBy1} @@ -281,7 +262,7 @@ const ChartPanelsComponent: React.FC<Props> = ({ </FullHeightFlexItem> )} - {isAlertsPageChartsEnabled && alertViewSelection === 'charts' && ( + {alertViewSelection === 'charts' && ( <FullHeightFlexItem grow={1}> {isLoadingIndexPattern ? ( <EuiSkeletonText lines={10} data-test-subj="chartsLoadingSpinner" /> diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx index 8700e0e22de73..c89626b9edcec 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; -import type { BrowserField } from '@kbn/timelines-plugin/common'; +import type { FieldSpec } from '@kbn/data-plugin/common'; import type { GetAggregatableFields, UseInspectButtonParams } from './hooks'; import { getAggregatableFields, useInspectButton, useStackByFields } from './hooks'; @@ -57,7 +57,7 @@ describe('getAggregatableFields', () => { expect( getAggregatableFields( - { [field]: mockBrowserFields?.destination?.fields?.[field] as Partial<BrowserField> }, + { [field]: mockBrowserFields?.destination?.fields?.[field] as Partial<FieldSpec> }, useLensCompatibleFields ) ).toHaveLength(1); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts index 3c383cbd7c2fa..75d86873c52bc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/common/hooks.ts @@ -9,8 +9,8 @@ import { useCallback, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import type { IFieldSubTypeNested } from '@kbn/es-query'; +import type { FieldSpec } from '@kbn/data-plugin/common'; -import type { BrowserField } from '@kbn/timelines-plugin/common'; import { i18n } from '@kbn/i18n'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import type { GlobalTimeArgs } from '../../../../common/containers/use_global_time'; @@ -64,13 +64,13 @@ export const useInspectButton = ({ }, [setQuery, loading, response, request, refetch, uniqueQueryId, deleteQuery, searchSessionId]); }; -export function isDataViewFieldSubtypeNested(field: Partial<BrowserField>) { +export function isDataViewFieldSubtypeNested(field: Partial<FieldSpec>) { const subTypeNested = field?.subType as IFieldSubTypeNested; return !!subTypeNested?.nested?.path; } export interface GetAggregatableFields { - [fieldName: string]: Partial<BrowserField>; + [fieldName: string]: Partial<FieldSpec>; } export function getAggregatableFields( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 0908083a30a18..3d284b89f0745 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -33,7 +33,7 @@ import { import type { CreateTimeline, UpdateTimelineLoading } from './types'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { DataProvider } from '../../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import { TimelineId, TimelineTabs } from '../../../../common/types/timeline'; import type { ISearchStart } from '@kbn/data-plugin/public'; import { searchServiceMock } from '@kbn/data-plugin/public/search/mocks'; @@ -440,9 +440,9 @@ describe('alert actions', () => { sortDirection: 'desc', }, ], - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, title: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, version: null, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 8e5a25638788e..fdfe314094be6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -52,7 +52,7 @@ import { } from '../../../../common/detection_engine/utils'; import type { TimelineResult } from '../../../../common/api/timeline'; import { TimelineId } from '../../../../common/types/timeline'; -import { TimelineStatus, TimelineType } from '../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import type { SendAlertToTimelineActionProps, ThresholdAggregationData, @@ -990,7 +990,7 @@ export const sendAlertToTimelineAction = async ({ const { timeline, notes } = formatTimelineResultToModel( timelineTemplate, true, - timelineTemplate.timelineType ?? TimelineType.default + timelineTemplate.timelineType ?? TimelineTypeEnum.default ); const query = replaceTemplateFieldFromQuery( timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', @@ -1056,9 +1056,9 @@ export const sendAlertToTimelineAction = async ({ ...timeline, excludedRowRendererIds: [], title: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, dataProviders, eventType: 'all', filters, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/translations.ts index eb421c67ff39a..18a9c495e408a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/translations.ts @@ -27,10 +27,3 @@ export const ADDITIONAL_FILTERS_ACTIONS_SHOW_ONLY_THREAT_INDICATOR_ALERTS = i18n defaultMessage: 'Show only threat indicator alerts', } ); - -export const TAKE_ACTION = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.utilityBar.takeActionTitle', - { - defaultMessage: 'Take action', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts index 79c93d0d32ba3..41ebf87834bf8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.test.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { TimelineType } from '../../../../common/api/timeline'; +import { DataProviderTypeEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import type { Filter } from '@kbn/es-query'; import { FilterStateStore } from '@kbn/es-query'; import type { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider'; -import { DataProviderType } from '../../../timelines/components/timeline/data_providers/data_provider'; import { mockDataProviders } from '../../../timelines/components/timeline/data_providers/mock/mock_data_providers'; - import { getStringArray, replaceTemplateFieldFromQuery, @@ -143,7 +141,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( '', mockTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual(''); }); @@ -152,7 +150,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( ' ', mockTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual(''); }); @@ -161,7 +159,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( 'host.name: placeholdertext', mockTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual('host.name: apache'); }); @@ -175,7 +173,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( 'host.name: *', dupTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual('host.name: *'); }); @@ -184,7 +182,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( 'user.id: placeholdertext', mockTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual('user.id: placeholdertext'); }); @@ -195,7 +193,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( '', mockTimelineDetails, - TimelineType.template + TimelineTypeEnum.template ); expect(replacement).toEqual(''); }); @@ -204,7 +202,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( ' ', mockTimelineDetails, - TimelineType.template + TimelineTypeEnum.template ); expect(replacement).toEqual(''); }); @@ -213,7 +211,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( 'host.name: placeholdertext', mockTimelineDetails, - TimelineType.template + TimelineTypeEnum.template ); expect(replacement).toEqual('host.name: placeholdertext'); }); @@ -227,7 +225,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( 'host.name: *', dupTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual('host.name: *'); }); @@ -236,7 +234,7 @@ describe('helpers', () => { const replacement = replaceTemplateFieldFromQuery( 'user.id: placeholdertext', mockTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual('user.id: placeholdertext'); }); @@ -323,7 +321,7 @@ describe('helpers', () => { const replacement = reformatDataProviderWithNewValue( mockDataProvider, mockTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual({ id: 'apache', @@ -339,7 +337,7 @@ describe('helpers', () => { displayValue: undefined, }, and: [], - type: TimelineType.default, + type: TimelineTypeEnum.default, }); }); @@ -357,7 +355,7 @@ describe('helpers', () => { const replacement = reformatDataProviderWithNewValue( mockDataProvider, dupTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual({ id: 'apache', @@ -373,7 +371,7 @@ describe('helpers', () => { displayValue: undefined, }, and: [], - type: TimelineType.default, + type: TimelineTypeEnum.default, }); }); @@ -386,7 +384,7 @@ describe('helpers', () => { const replacement = reformatDataProviderWithNewValue( mockDataProvider, mockTimelineDetails, - TimelineType.default + TimelineTypeEnum.default ); expect(replacement).toEqual({ id: 'my-id', @@ -402,7 +400,7 @@ describe('helpers', () => { displayValue: undefined, }, and: [], - type: TimelineType.default, + type: TimelineTypeEnum.default, }); }); }); @@ -414,11 +412,11 @@ describe('helpers', () => { mockDataProvider.id = 'Braden'; mockDataProvider.name = 'Braden'; mockDataProvider.queryMatch.value = '{host.name}'; - mockDataProvider.type = DataProviderType.template; + mockDataProvider.type = DataProviderTypeEnum.template; const replacement = reformatDataProviderWithNewValue( mockDataProvider, mockTimelineDetails, - TimelineType.template + TimelineTypeEnum.template ); expect(replacement).toEqual({ id: 'apache', @@ -434,7 +432,7 @@ describe('helpers', () => { displayValue: undefined, }, and: [], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }); }); @@ -444,11 +442,11 @@ describe('helpers', () => { mockDataProvider.id = 'Braden'; mockDataProvider.name = 'Braden'; mockDataProvider.queryMatch.value = '{host.name}'; - mockDataProvider.type = DataProviderType.default; + mockDataProvider.type = DataProviderTypeEnum.default; const replacement = reformatDataProviderWithNewValue( mockDataProvider, mockTimelineDetails, - TimelineType.template + TimelineTypeEnum.template ); expect(replacement).toEqual({ id: 'Braden', @@ -464,7 +462,7 @@ describe('helpers', () => { displayValue: undefined, }, and: [], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }); }); @@ -479,11 +477,11 @@ describe('helpers', () => { mockDataProvider.id = 'Braden'; mockDataProvider.name = 'Braden'; mockDataProvider.queryMatch.value = '{host.name}'; - mockDataProvider.type = DataProviderType.template; + mockDataProvider.type = DataProviderTypeEnum.template; const replacement = reformatDataProviderWithNewValue( mockDataProvider, dupTimelineDetails, - TimelineType.template + TimelineTypeEnum.template ); expect(replacement).toEqual({ id: 'apache', @@ -499,7 +497,7 @@ describe('helpers', () => { displayValue: undefined, }, and: [], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }); }); @@ -509,11 +507,11 @@ describe('helpers', () => { mockDataProvider.id = 'my-id'; mockDataProvider.name = 'Rebecca'; mockDataProvider.queryMatch.value = 'Rebecca'; - mockDataProvider.type = DataProviderType.default; + mockDataProvider.type = DataProviderTypeEnum.default; const replacement = reformatDataProviderWithNewValue( mockDataProvider, mockTimelineDetails, - TimelineType.template + TimelineTypeEnum.template ); expect(replacement).toEqual({ id: 'my-id', @@ -529,7 +527,7 @@ describe('helpers', () => { displayValue: undefined, }, and: [], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts index 7221a9ede2785..6d97a32b5fc38 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/helpers.ts @@ -11,12 +11,15 @@ import type { Filter, KueryNode } from '@kbn/es-query'; import { FilterStateStore, fromKueryExpression } from '@kbn/es-query'; import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { TimelineType } from '../../../../common/api/timeline'; +import { + DataProviderTypeEnum, + type TimelineType, + TimelineTypeEnum, +} from '../../../../common/api/timeline'; import type { DataProvider, DataProvidersAnd, } from '../../../timelines/components/timeline/data_providers/data_provider'; -import { DataProviderType } from '../../../timelines/components/timeline/data_providers/data_provider'; interface FindValueToChangeInQuery { field: string; @@ -114,9 +117,9 @@ export const findValueToChangeInQuery = ( export const replaceTemplateFieldFromQuery = ( query: string, eventData: TimelineEventsDetailsItem[], - timelineType: TimelineType = TimelineType.default + timelineType: TimelineType = TimelineTypeEnum.default ): string => { - if (timelineType === TimelineType.default) { + if (timelineType === TimelineTypeEnum.default) { if (query.trim() !== '') { const valueToChange = findValueToChangeInQuery(fromKueryExpression(query)); return valueToChange.reduce((newQuery, vtc) => { @@ -157,10 +160,10 @@ export const replaceTemplateFieldFromMatchFilters = ( export const reformatDataProviderWithNewValue = <T extends DataProvider | DataProvidersAnd>( dataProvider: T, eventData: TimelineEventsDetailsItem[], - timelineType: TimelineType = TimelineType.default + timelineType: TimelineType = TimelineTypeEnum.default ): T => { // Support for legacy "template-like" timeline behavior that is using hardcoded list of templateFields - if (timelineType !== TimelineType.template) { + if (timelineType !== TimelineTypeEnum.template) { if (templateFields.includes(dataProvider.queryMatch.field)) { const newValue = getStringArray(dataProvider.queryMatch.field, eventData); if (newValue.length) { @@ -171,13 +174,13 @@ export const reformatDataProviderWithNewValue = <T extends DataProvider | DataPr dataProvider.queryMatch.displayValue = undefined; } } - dataProvider.type = DataProviderType.default; + dataProvider.type = DataProviderTypeEnum.default; return dataProvider; } - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { if ( - dataProvider.type === DataProviderType.template && + dataProvider.type === DataProviderTypeEnum.template && dataProvider.queryMatch.operator === ':' ) { const newValue = getStringArray(dataProvider.queryMatch.field, eventData); @@ -191,12 +194,12 @@ export const reformatDataProviderWithNewValue = <T extends DataProvider | DataPr dataProvider.queryMatch.value = newValue[0]; dataProvider.queryMatch.displayField = undefined; dataProvider.queryMatch.displayValue = undefined; - dataProvider.type = DataProviderType.default; + dataProvider.type = DataProviderTypeEnum.default; return dataProvider; } - dataProvider.type = dataProvider.type ?? DataProviderType.default; + dataProvider.type = dataProvider.type ?? DataProviderTypeEnum.default; return dataProvider; } @@ -207,7 +210,7 @@ export const reformatDataProviderWithNewValue = <T extends DataProvider | DataPr export const replaceTemplateFieldFromDataProviders = ( dataProviders: DataProvider[], eventData: TimelineEventsDetailsItem[], - timelineType: TimelineType = TimelineType.default + timelineType: TimelineType = TimelineTypeEnum.default ): DataProvider[] => dataProviders.map((dataProvider) => { const newDataProvider = reformatDataProviderWithNewValue(dataProvider, eventData, timelineType); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx index dee7d665295b8..8cc2c89b76659 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_bulk_to_timeline.tsx @@ -26,7 +26,7 @@ import { timelineActions } from '../../../../timelines/store'; import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline'; import { INVESTIGATE_BULK_IN_TIMELINE } from '../translations'; import { TimelineId } from '../../../../../common/types/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { sendBulkEventsToTimelineAction } from '../actions'; import type { CreateTimelineProps } from '../types'; import type { SourcererScopeName } from '../../../../sourcerer/store/model'; @@ -138,7 +138,7 @@ export const useAddBulkToTimelineAction = ({ const clearActiveTimeline = useCreateTimeline({ timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); const updateTimelineIsLoading = useCallback( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx index 786708263fa74..2056de93a63be 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx @@ -22,7 +22,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_ex import { createHistoryEntry } from '../../../../common/utils/global_query_string/helpers'; import { useKibana } from '../../../../common/lib/kibana'; import { TimelineId } from '../../../../../common/types/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { timelineActions } from '../../../../timelines/store'; import { sendAlertToTimelineAction } from '../actions'; import { useUpdateTimeline } from '../../../../timelines/components/open_timeline/use_update_timeline'; @@ -141,7 +141,7 @@ export const useInvestigateInTimeline = ({ const clearActiveTimeline = useCreateTimeline({ timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); const unifiedComponentsInTimelineDisabled = useIsExperimentalFeatureEnabled( @@ -164,7 +164,8 @@ export const useInvestigateInTimeline = ({ indexNames: timeline.indexNames ?? [], show: true, excludedRowRendererIds: - !unifiedComponentsInTimelineDisabled && timeline.timelineType !== TimelineType.template + !unifiedComponentsInTimelineDisabled && + timeline.timelineType !== TimelineTypeEnum.template ? timeline.excludedRowRendererIds : [], }, diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx deleted file mode 100644 index d8150057dd44b..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { ReactWrapper } from 'enzyme'; -import { mount } from 'enzyme'; -import { waitFor } from '@testing-library/react'; - -import type { TakeActionDropdownProps } from '.'; -import { TakeActionDropdown } from '.'; -import { generateAlertDetailsDataMock } from '../../../common/components/event_details/__mocks__'; -import { getDetectionAlertMock } from '../../../common/mock/mock_detection_alerts'; -import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { TimelineId } from '../../../../common/types/timeline'; -import { TestProviders } from '../../../common/mock'; -import { mockTimelines } from '../../../common/mock/mock_timelines_plugin'; -import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock'; -import { useHttp, useKibana } from '../../../common/lib/kibana'; -import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; -import { initialUserPrivilegesState as mockInitialUserPrivilegesState } from '../../../common/components/user_privileges/user_privileges_context'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; -import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__'; -import { allCasesPermissions } from '../../../cases_test_utils'; -import { - ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE, - ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE, -} from '../../../common/components/toolbar/bulk_actions/translations'; - -jest.mock('../../../common/components/endpoint/host_isolation'); -jest.mock('../../../common/components/endpoint/responder'); -jest.mock('../../../common/components/user_privileges'); - -jest.mock('../user_info', () => ({ - useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), -})); - -jest.mock('../../../common/lib/kibana'); -jest.mock('../../../common/components/guided_onboarding_tour/tour_step'); - -jest.mock('../../containers/detection_engine/alerts/use_alerts_privileges', () => ({ - useAlertsPrivileges: jest.fn().mockReturnValue({ hasIndexWrite: true, hasKibanaCRUD: true }), -})); -jest.mock('../../../cases/components/use_insert_timeline'); - -jest.mock('../../../common/hooks/use_app_toasts', () => ({ - useAppToasts: jest.fn().mockReturnValue({ - addError: jest.fn(), - }), -})); - -jest.mock('../../../common/hooks/use_license', () => ({ - useLicense: jest.fn().mockReturnValue({ isPlatinumPlus: () => true, isEnterprise: () => false }), -})); - -jest.mock( - '../../../common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status', - () => { - return { - useEndpointHostIsolationStatus: jest.fn().mockReturnValue({ - loading: false, - isIsolated: false, - agentStatus: 'healthy', - }), - }; - } -); - -describe('take action dropdown', () => { - let defaultProps: TakeActionDropdownProps; - let mockStartServicesMock: ReturnType<typeof createStartServicesMock>; - - beforeEach(() => { - defaultProps = { - detailsData: generateAlertDetailsDataMock() as TimelineEventsDetailsItem[], - ecsData: getDetectionAlertMock(), - handleOnEventClosed: jest.fn(), - isHostIsolationPanelOpen: false, - loadingEventDetails: false, - onAddEventFilterClick: jest.fn(), - onAddExceptionTypeClick: jest.fn(), - onAddIsolationStatusClick: jest.fn(), - refetch: jest.fn(), - refetchFlyoutData: jest.fn(), - scopeId: TimelineId.active, - onOsqueryClick: jest.fn(), - }; - - mockStartServicesMock = createStartServicesMock(); - - (useKibana as jest.Mock).mockImplementation(() => { - return { - services: { - ...mockStartServicesMock, - timelines: { ...mockTimelines }, - cases: { - ...mockCasesContract(), - helpers: { - canUseCases: jest.fn().mockReturnValue(allCasesPermissions()), - getRuleIdFromEvent: () => null, - }, - }, - osquery: { - isOsqueryAvailable: jest.fn().mockReturnValue(true), - }, - application: { - capabilities: { siem: { crud_alerts: true, read_alerts: true }, osquery: true }, - }, - }, - }; - }); - - (useHttp as jest.Mock).mockReturnValue(mockStartServicesMock.http); - }); - - afterEach(() => { - (useUserPrivileges as jest.Mock).mockReturnValue(getUserPrivilegesMockDefaultValue()); - }); - - test('should render takeActionButton', () => { - const wrapper = mount( - <TestProviders> - <TakeActionDropdown {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="take-action-dropdown-btn"]').exists()).toBeTruthy(); - }); - - test('should render takeActionButton with correct text', () => { - const wrapper = mount( - <TestProviders> - <TakeActionDropdown {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="take-action-dropdown-btn"]').first().text()).toEqual( - 'Take action' - ); - }); - - describe('should render take action items', () => { - let wrapper: ReactWrapper; - - beforeAll(() => { - wrapper = mount( - <TestProviders> - <TakeActionDropdown {...defaultProps} /> - </TestProviders> - ); - wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); - }); - test('should render "Add to existing case"', async () => { - await waitFor(() => { - expect( - wrapper.find('[data-test-subj="add-to-existing-case-action"]').first().text() - ).toEqual('Add to existing case'); - }); - }); - test('should render "Add to new case"', async () => { - await waitFor(() => { - expect(wrapper.find('[data-test-subj="add-to-new-case-action"]').first().text()).toEqual( - 'Add to new case' - ); - }); - }); - - test('should render "mark as acknowledge"', async () => { - await waitFor(() => { - expect(wrapper.find('[data-test-subj="acknowledged-alert-status"]').first().text()).toEqual( - 'Mark as acknowledged' - ); - }); - }); - - test('should render "mark as close"', async () => { - await waitFor(() => { - expect(wrapper.find('[data-test-subj="close-alert-status"]').first().text()).toEqual( - 'Mark as closed' - ); - }); - }); - - test('should render "Add Endpoint exception"', async () => { - await waitFor(() => { - expect( - wrapper.find('[data-test-subj="add-endpoint-exception-menu-item"]').first().text() - ).toEqual('Add Endpoint exception'); - }); - }); - test('should render "Add rule exception"', async () => { - await waitFor(() => { - expect(wrapper.find('[data-test-subj="add-exception-menu-item"]').first().text()).toEqual( - 'Add rule exception' - ); - }); - }); - - test('should render "Isolate host"', async () => { - await waitFor(() => { - expect(wrapper.find('[data-test-subj="isolate-host-action-item"]').first().text()).toEqual( - 'Isolate host' - ); - }); - }); - test('should render "Investigate in timeline"', async () => { - await waitFor(() => { - expect( - wrapper.find('[data-test-subj="investigate-in-timeline-action-item"]').first().text() - ).toEqual('Investigate in timeline'); - }); - }); - test('should render "Run Osquery"', async () => { - await waitFor(() => { - expect(wrapper.find('[data-test-subj="osquery-action-item"]').first().text()).toEqual( - 'Run Osquery' - ); - }); - }); - test('should render "Respond"', async () => { - await waitFor(() => { - expect( - wrapper.find('[data-test-subj="endpointResponseActions-action-item"]').first().text() - ).toEqual('Respond'); - }); - }); - test('should render "Apply alert tags"', async () => { - await waitFor(() => { - expect( - wrapper.find('[data-test-subj="alert-tags-context-menu-item"]').first().text() - ).toEqual(ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE); - }); - }); - test('should render "Assign alert"', async () => { - await waitFor(() => { - expect( - wrapper.find('[data-test-subj="alert-assignees-context-menu-item"]').first().text() - ).toEqual(ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE); - }); - }); - }); - - describe('for Endpoint related actions', () => { - /** Removes the detail data that is used to determine if data is for an Alert */ - const setAlertDetailsDataMockToEvent = () => { - if (defaultProps.detailsData) { - defaultProps.detailsData = defaultProps.detailsData - .map((obj) => { - if (obj.field === 'kibana.alert.rule.uuid') { - return null; - } - if (obj.field === 'event.kind') { - return { - category: 'event', - field: 'event.kind', - values: ['event'], - originalValue: 'event', - }; - } - return obj; - }) - .filter((obj) => obj) as TimelineEventsDetailsItem[]; - } else { - expect(defaultProps.detailsData).toBeInstanceOf(Object); - } - }; - - const setAgentTypeOnAlertDetailsDataMock = (agentType: string = 'endpoint') => { - if (defaultProps.detailsData) { - defaultProps.detailsData = defaultProps.detailsData.map((obj) => { - if (obj.field === 'agent.type') { - return { - category: 'agent', - field: 'agent.type', - values: [agentType], - originalValue: [agentType], - }; - } - if (obj.field === 'agent.id') { - return { - category: 'agent', - field: 'agent.id', - values: ['123'], - originalValue: ['123'], - }; - } - - return obj; - }) as TimelineEventsDetailsItem[]; - } else { - expect(defaultProps.detailsData).toBeInstanceOf(Object); - } - }; - - /** Set the `agent.type` and `agent.id` on the EcsData */ - const setTypeOnEcsDataWithAgentType = ( - agentType: string = 'endpoint', - agentId: string = '123' - ) => { - if (defaultProps.ecsData) { - defaultProps.ecsData.agent = { - // @ts-expect-error Ecs definition for agent seems to be missing properties - id: agentId, - type: [agentType], - }; - } else { - expect(defaultProps.ecsData).toBeInstanceOf(Object); - } - }; - - let wrapper: ReactWrapper; - - const render = (): ReactWrapper => { - wrapper = mount( - <TestProviders> - <TakeActionDropdown {...defaultProps} /> - </TestProviders> - ); - wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); - - return wrapper; - }; - - it('should include the Isolate/Release action', () => { - render(); - - expect(wrapper.exists('[data-test-subj="isolate-host-action-item"]')).toBe(true); - }); - - it('should include the Responder action', () => { - render(); - - expect(wrapper.exists('[data-test-subj="endpointResponseActions-action-item"]')).toBe(true); - }); - - describe('should correctly enable/disable the "Add Endpoint event filter" button', () => { - beforeEach(() => { - setTypeOnEcsDataWithAgentType(); - setAlertDetailsDataMockToEvent(); - }); - - test('should enable the "Add Endpoint event filter" button if provided endpoint event and has right privileges', async () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - ...mockInitialUserPrivilegesState(), - endpointPrivileges: { loading: false, canWriteEventFilters: true }, - }); - render(); - await waitFor(() => { - expect( - wrapper.find('[data-test-subj="add-event-filter-menu-item"]').last().getDOMNode() - ).toBeEnabled(); - }); - }); - - test('should hide the "Add Endpoint event filter" button if no write event filters privileges', async () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - ...mockInitialUserPrivilegesState(), - endpointPrivileges: { loading: false, canWriteEventFilters: false }, - }); - render(); - await waitFor(() => { - expect(wrapper.exists('[data-test-subj="add-event-filter-menu-item"]')).toBeFalsy(); - }); - }); - - test('should hide the "Add Endpoint event filter" button if provided no event from endpoint', async () => { - setAgentTypeOnAlertDetailsDataMock('filebeat'); - setTypeOnEcsDataWithAgentType('filebeat'); - render(); - await waitFor(() => { - expect(wrapper.exists('[data-test-subj="add-event-filter-menu-item"]')).toBeFalsy(); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx deleted file mode 100644 index 1f4baaeae8df5..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useCallback, useMemo, useState } from 'react'; -import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui'; -import type { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { TableId } from '@kbn/securitysolution-data-table'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { getAlertDetailsFieldValue } from '../../../common/lib/endpoint/utils/get_event_details_field_values'; -import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; -import { - AlertsCasesTourSteps, - SecurityStepId, -} from '../../../common/components/guided_onboarding_tour/tour_config'; -import { isActiveTimeline } from '../../../helpers'; -import { TAKE_ACTION } from '../alerts_table/additional_filters_action/translations'; -import { useAlertExceptionActions } from '../alerts_table/timeline_actions/use_add_exception_actions'; -import { useAlertsActions } from '../alerts_table/timeline_actions/use_alerts_actions'; -import { useInvestigateInTimeline } from '../alerts_table/timeline_actions/use_investigate_in_timeline'; -import { useEventFilterAction } from '../alerts_table/timeline_actions/use_event_filter_action'; -import { useResponderActionItem } from '../../../common/components/endpoint/responder'; -import { useHostIsolationAction } from '../../../common/components/endpoint/host_isolation'; -import type { Status } from '../../../../common/api/detection_engine'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; -import { useAddToCaseActions } from '../alerts_table/timeline_actions/use_add_to_case_actions'; -import { useKibana } from '../../../common/lib/kibana'; -import { getOsqueryActionItem } from '../osquery/osquery_action_item'; -import type { AlertTableContextMenuItem } from '../alerts_table/types'; -import { useAlertTagsActions } from '../alerts_table/timeline_actions/use_alert_tags_actions'; -import { useAlertAssigneesActions } from '../alerts_table/timeline_actions/use_alert_assignees_actions'; - -interface ActionsData { - alertStatus: Status; - eventId: string; - eventKind: string; - ruleId: string; - ruleName: string; -} - -export interface TakeActionDropdownProps { - detailsData: TimelineEventsDetailsItem[] | null; - ecsData?: Ecs; - handleOnEventClosed: () => void; - isHostIsolationPanelOpen: boolean; - loadingEventDetails: boolean; - onAddEventFilterClick: () => void; - onAddExceptionTypeClick: (type?: ExceptionListTypeEnum) => void; - onAddIsolationStatusClick: (action: 'isolateHost' | 'unisolateHost') => void; - refetch: (() => void) | undefined; - refetchFlyoutData: () => Promise<void>; - onOsqueryClick: (id: string) => void; - scopeId: string; -} - -// eslint-disable-next-line react/display-name -export const TakeActionDropdown = React.memo( - ({ - detailsData, - ecsData, - handleOnEventClosed, - isHostIsolationPanelOpen, - loadingEventDetails, - onAddEventFilterClick, - onAddExceptionTypeClick, - onAddIsolationStatusClick, - refetch, - refetchFlyoutData, - onOsqueryClick, - scopeId, - }: TakeActionDropdownProps) => { - const { loading: endpointPrivilegesLoading, canWriteEventFilters } = - useUserPrivileges().endpointPrivileges; - - const canCreateEndpointEventFilters = useMemo( - () => !endpointPrivilegesLoading && canWriteEventFilters, - [canWriteEventFilters, endpointPrivilegesLoading] - ); - const { osquery } = useKibana().services; - - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - const actionsData = useMemo( - () => - [ - { category: 'kibana', field: 'kibana.alert.rule.uuid', name: 'ruleId' }, - { category: 'kibana', field: 'kibana.alert.rule.name', name: 'ruleName' }, - { category: 'kibana', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, - { category: 'event', field: 'event.kind', name: 'eventKind' }, - { category: '_id', field: '_id', name: 'eventId' }, - ].reduce<ActionsData>( - (acc, curr) => ({ - ...acc, - [curr.name]: getAlertDetailsFieldValue( - { category: curr.category, field: curr.field }, - detailsData - ), - }), - {} as ActionsData - ), - [detailsData] - ); - - const isEvent = actionsData.eventKind === 'event'; - - const isAgentEndpoint = useMemo(() => ecsData?.agent?.type?.includes('endpoint'), [ecsData]); - const isEndpointEvent = useMemo(() => isEvent && isAgentEndpoint, [isEvent, isAgentEndpoint]); - - const osqueryAgentId = useMemo( - () => getAlertDetailsFieldValue({ category: 'agent', field: 'agent.id' }, detailsData), - [detailsData] - ); - - const togglePopoverHandler = useCallback(() => { - setIsPopoverOpen(!isPopoverOpen); - }, [isPopoverOpen]); - - const closePopoverHandler = useCallback(() => { - setIsPopoverOpen(false); - }, []); - - const closePopoverAndFlyout = useCallback(() => { - handleOnEventClosed(); - setIsPopoverOpen(false); - }, [handleOnEventClosed]); - - const handleOnAddIsolationStatusClick = useCallback( - (action: 'isolateHost' | 'unisolateHost') => { - onAddIsolationStatusClick(action); - setIsPopoverOpen(false); - }, - [onAddIsolationStatusClick] - ); - - const hostIsolationActionItems = useHostIsolationAction({ - closePopover: closePopoverHandler, - detailsData, - onAddIsolationStatusClick: handleOnAddIsolationStatusClick, - isHostIsolationPanelOpen, - }); - - const endpointResponseActionsConsoleItems = useResponderActionItem( - detailsData, - closePopoverHandler - ); - - const handleOnAddExceptionTypeClick = useCallback( - (type?: ExceptionListTypeEnum) => { - onAddExceptionTypeClick(type); - setIsPopoverOpen(false); - }, - [onAddExceptionTypeClick] - ); - - const { exceptionActionItems } = useAlertExceptionActions({ - isEndpointAlert: Boolean(isAgentEndpoint), - onAddExceptionTypeClick: handleOnAddExceptionTypeClick, - }); - - const handleOnAddEventFilterClick = useCallback(() => { - onAddEventFilterClick(); - setIsPopoverOpen(false); - }, [onAddEventFilterClick]); - - const { eventFilterActionItems } = useEventFilterAction({ - onAddEventFilterClick: handleOnAddEventFilterClick, - }); - - const onMenuItemClick = useCallback(() => { - closePopoverHandler(); - }, [closePopoverHandler]); - - const { actionItems: statusActionItems } = useAlertsActions({ - alertStatus: actionsData.alertStatus, - closePopover: closePopoverAndFlyout, - eventId: actionsData.eventId, - refetch, - scopeId, - }); - - const { alertTagsItems, alertTagsPanels } = useAlertTagsActions({ - closePopover: closePopoverHandler, - ecsRowData: ecsData ?? { _id: actionsData.eventId }, - refetch, - }); - - const onAssigneesUpdate = useCallback(() => { - if (refetch) { - refetch(); - } - if (refetchFlyoutData) { - refetchFlyoutData(); - } - }, [refetch, refetchFlyoutData]); - const { alertAssigneesItems, alertAssigneesPanels } = useAlertAssigneesActions({ - closePopover: closePopoverHandler, - ecsRowData: ecsData ?? { _id: actionsData.eventId }, - refetch: onAssigneesUpdate, - }); - - const { investigateInTimelineActionItems } = useInvestigateInTimeline({ - ecsRowData: ecsData, - onInvestigateInTimelineAlertClick: closePopoverHandler, - }); - - const osqueryAvailable = osquery?.isOsqueryAvailable({ - agentId: osqueryAgentId, - }); - - const handleOnOsqueryClick = useCallback(() => { - onOsqueryClick(osqueryAgentId); - setIsPopoverOpen(false); - }, [onOsqueryClick, setIsPopoverOpen, osqueryAgentId]); - - const osqueryActionItem = useMemo( - () => - getOsqueryActionItem({ - handleClick: handleOnOsqueryClick, - }), - [handleOnOsqueryClick] - ); - - const alertsActionItems = useMemo( - () => - !isEvent && actionsData.ruleId - ? [ - ...statusActionItems, - ...alertTagsItems, - ...alertAssigneesItems, - ...exceptionActionItems, - ] - : isEndpointEvent && canCreateEndpointEventFilters - ? eventFilterActionItems - : [], - [ - eventFilterActionItems, - isEndpointEvent, - canCreateEndpointEventFilters, - exceptionActionItems, - statusActionItems, - isEvent, - actionsData.ruleId, - alertTagsItems, - alertAssigneesItems, - ] - ); - - const isInDetections = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage].includes( - scopeId as TableId - ); - - const { addToCaseActionItems, handleAddToNewCaseClick } = useAddToCaseActions({ - ecsData, - nonEcsData: detailsData?.map((d) => ({ field: d.field, value: d.values })) ?? [], - onMenuItemClick, - onSuccess: refetchFlyoutData, - isActiveTimelines: isActiveTimeline(scopeId), - isInDetections, - refetch, - }); - - const items: AlertTableContextMenuItem[] = useMemo( - () => [ - ...addToCaseActionItems, - ...alertsActionItems, - ...hostIsolationActionItems, - ...endpointResponseActionsConsoleItems, - ...(osqueryAvailable ? [osqueryActionItem] : []), - ...investigateInTimelineActionItems, - ], - [ - addToCaseActionItems, - alertsActionItems, - hostIsolationActionItems, - endpointResponseActionsConsoleItems, - osqueryAvailable, - osqueryActionItem, - investigateInTimelineActionItems, - ] - ); - - const panels = [ - { - id: 0, - items, - }, - ...alertTagsPanels, - ...alertAssigneesPanels, - ]; - - const takeActionButton = useMemo( - () => ( - <GuidedOnboardingTourStep - onClick={handleAddToNewCaseClick} - step={AlertsCasesTourSteps.addAlertToCase} - tourId={SecurityStepId.alertsCases} - > - <EuiButton - data-test-subj="take-action-dropdown-btn" - fill - iconSide="right" - iconType="arrowDown" - onClick={togglePopoverHandler} - > - {TAKE_ACTION} - </EuiButton> - </GuidedOnboardingTourStep> - ), - - [handleAddToNewCaseClick, togglePopoverHandler] - ); - - return items.length && !loadingEventDetails && ecsData ? ( - <EuiPopover - id="AlertTakeActionPanel" - button={takeActionButton} - isOpen={isPopoverOpen} - closePopover={closePopoverHandler} - panelPaddingSize="none" - anchorPosition="downLeft" - repositionOnScroll - > - <EuiContextMenu - size="s" - initialPanelId={0} - panels={panels} - data-test-subj="takeActionPanelMenu" - /> - </EuiPopover> - ) : null; - } -); diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts index 500c327d86b0c..9351e34ab4b5b 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts @@ -22,7 +22,7 @@ import type { import type { AssetCriticalityRecord, EntityAnalyticsPrivileges, -} from '../../../common/api/entity_analytics/asset_criticality'; +} from '../../../common/api/entity_analytics'; import type { RiskScoreEntity } from '../../../common/search_strategy'; import { RISK_ENGINE_STATUS_URL, diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/shared_lists.test.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/shared_lists.test.tsx index c1167f0575fad..8d9100e0da94f 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/shared_lists.test.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/shared_lists.test.tsx @@ -30,8 +30,13 @@ jest.mock('react-router-dom', () => { }; }); jest.mock('@kbn/i18n-react', () => { + const { i18n } = jest.requireActual('@kbn/i18n'); + i18n.init({ locale: 'en' }); + const originalModule = jest.requireActual('@kbn/i18n-react'); - const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); + const FormattedRelative = jest.fn(); + FormattedRelative.mockImplementationOnce(() => '2 days ago'); + FormattedRelative.mockImplementation(() => '20 hours ago'); return { ...originalModule, @@ -43,10 +48,7 @@ jest.mock('../../../detections/containers/detection_engine/lists/use_lists_confi useListsConfig: jest.fn().mockReturnValue({ loading: false }), })); -// FLAKY: https://github.com/elastic/kibana/issues/177670 -// FLAKY: https://github.com/elastic/kibana/issues/177671 -// FLAKY: https://github.com/elastic/kibana/issues/177672 -describe.skip('SharedLists', () => { +describe('SharedLists', () => { const mockHistory = generateHistoryMock(); const exceptionList1 = getExceptionListSchemaMock(); const exceptionList2 = { ...getExceptionListSchemaMock(), list_id: 'not_endpoint_list', id: '2' }; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx index 6c4aafa0f7bd4..658624dfdbbf4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx @@ -14,7 +14,7 @@ import { EndpointIsolateSuccess, HostIsolationPanel, } from '../../../common/components/endpoint/host_isolation'; -import { useHostIsolationTools } from '../../../timelines/components/side_panel/event_details/use_host_isolation_tools'; +import { useHostIsolation } from '../shared/hooks/use_host_isolation'; import { useIsolateHostPanelContext } from './context'; import { FlyoutBody } from '../../shared/components/flyout_body'; @@ -26,8 +26,7 @@ export const PanelContent: FC = () => { const { dataFormattedForFieldBrowser, eventId, scopeId, indexName, isolateAction } = useIsolateHostPanelContext(); - const { isIsolateActionSuccessBannerVisible, handleIsolationActionSuccess } = - useHostIsolationTools(); + const { isIsolateActionSuccessBannerVisible, handleIsolationActionSuccess } = useHostIsolation(); const { alertId, hostName } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.test.tsx index e8be41c601844..9b11ccbb516ba 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.test.tsx @@ -12,7 +12,6 @@ import { DocumentDetailsContext } from '../../shared/context'; import { rawEventData, TestProviders } from '../../../../common/mock'; import { RESPONSE_DETAILS_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', () => { @@ -98,19 +97,6 @@ const renderResponseDetails = (contextValue: DocumentDetailsContext) => ); describe('<ResponseDetails />', () => { - let featureFlags: { endpointResponseActionsEnabled: boolean; responseActionsEnabled: boolean }; - - beforeEach(() => { - featureFlags = { endpointResponseActionsEnabled: true, responseActionsEnabled: true }; - - const useIsExperimentalFeatureEnabledMock = (feature: keyof typeof featureFlags) => - featureFlags[feature]; - - (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( - useIsExperimentalFeatureEnabledMock - ); - }); - it('should render the view with response actions', () => { const wrapper = renderResponseDetails(contextWithResponseActions); @@ -120,17 +106,6 @@ describe('<ResponseDetails />', () => { // TODO mock osquery results }); - it('should render the view with osquery only', () => { - featureFlags.responseActionsEnabled = true; - featureFlags.endpointResponseActionsEnabled = false; - - 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 = renderResponseDetails(defaultContextValue); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.tsx index c240799639166..5081bdad9c17f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/response_details.tsx @@ -11,8 +11,6 @@ import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n-react'; import { RESPONSE_DETAILS_TEST_ID } from './test_ids'; import { useDocumentDetailsContext } from '../../shared/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'; const ExtendedFlyoutWrapper = styled.div` @@ -25,18 +23,11 @@ const ExtendedFlyoutWrapper = styled.div` */ export const ResponseDetails: React.FC = () => { const { searchHit, dataAsNestedObject, isPreview } = useDocumentDetailsContext(); - const endpointResponseActionsEnabled = useIsExperimentalFeatureEnabled( - 'endpointResponseActionsEnabled' - ); const responseActionsView = useResponseActionsView({ rawEventData: searchHit, ecsData: dataAsNestedObject, }); - const osqueryView = useOsqueryTab({ - rawEventData: searchHit, - ecsData: dataAsNestedObject, - }); return ( <div data-test-subj={RESPONSE_DETAILS_TEST_ID}> @@ -57,9 +48,7 @@ export const ResponseDetails: React.FC = () => { </EuiTitle> <EuiSpacer size="s" /> - <ExtendedFlyoutWrapper> - {endpointResponseActionsEnabled ? responseActionsView?.content : osqueryView?.content} - </ExtendedFlyoutWrapper> + <ExtendedFlyoutWrapper>{responseActionsView?.content}</ExtendedFlyoutWrapper> </> )} </div> diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts index 95ec61d66fff3..fc940979ae05f 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/test_ids.ts @@ -61,9 +61,29 @@ export const HOST_DETAILS_INFO_TEST_ID = 'host-overview' as const; /* Threat Intelligence */ -export const THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID = `threat-match-detected` as const; +const THREAT_INTELLIGENCE_DETAILS_TEST_ID = `${PREFIX}ThreatIntelligenceDetails` as const; +export const THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}ThreatMatchDetected` as const; export const THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID = - `${PREFIX}ThreatIntelligenceDetailsLoading` as const; + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}Loading` as const; +export const THREAT_INTELLIGENCE_ENRICHMENTS_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}EnrichedWithThreatIntel` as const; +export const THREAT_INTELLIGENCE_MATCHES_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}MatchesWithNoType` as const; +export const THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}NoEnrichmentFound` as const; +export const THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}EnrichmentTitle` as const; +export const THREAT_INTELLIGENCE_LOADING_ENRICHMENTS_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}LoadingEnrichment` as const; +export const THREAT_INTELLIGENCE_ENRICHMENTS_BUTTON_CONTENT_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}EnrichmentButtonContent` as const; +export const THREAT_INTELLIGENCE_ENRICHMENTS_ACCORDION_TABLE_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}EnrichmentAccordionTable` as const; +export const THREAT_INTELLIGENCE_ENRICHMENTS_RANGE_PICKER_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}EnrichmentRangePicker` as const; +export const THREAT_INTELLIGENCE_ENRICHMENTS_REFRESH_BUTTON_TEST_ID = + `${THREAT_INTELLIGENCE_DETAILS_TEST_ID}EnrichmentRefreshButton` as const; /* Correlations */ diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.test.tsx new file mode 100644 index 0000000000000..81d971fa98af8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.test.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 { render } from '@testing-library/react'; +import { TestProviders } from '../../../../common/mock'; +import React from 'react'; +import { EnrichmentAccordion } from './threat_details_view_enrichment_accordion'; +import { indicatorWithNestedObjects } from '../mocks/indicator_with_nested_objects'; +import type { CtiEnrichment } from '../../../../../common/search_strategy'; + +describe('EnrichmentAccordion', () => { + it('should render', () => { + render( + <TestProviders> + <EnrichmentAccordion + enrichment={indicatorWithNestedObjects as unknown as CtiEnrichment} + index={0} + /> + </TestProviders> + ); + + expect(true).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.tsx new file mode 100644 index 0000000000000..85b443682d34e --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion.tsx @@ -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 React, { memo, useMemo } from 'react'; +import styled from '@emotion/styled'; +import type { EuiBasicTableColumn } from '@elastic/eui'; +import { + EuiAccordion, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiInMemoryTable, + EuiTitle, + EuiToolTip, + useEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { THREAT_INTELLIGENCE_ENRICHMENTS_ACCORDION_TABLE_TEST_ID } from './test_ids'; +import type { CtiEnrichment } from '../../../../../common/search_strategy'; +import { QUERY_ID } from '../../shared/hooks/use_investigation_enrichment'; +import { InspectButton } from '../../../../common/components/inspect'; +import { + getEnrichmentIdentifiers, + buildThreatDetailsItems, + isInvestigationTimeEnrichment, +} from '../../shared/utils/threat_intelligence'; +import { EnrichmentButtonContent } from './threat_details_view_enrichment_button_content'; +import { REFERENCE } from '../../../../../common/cti/constants'; + +const StyledH5 = styled.h5` + line-height: 1.7rem; +`; + +const INVESTIGATION_QUERY_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.investigationTimeQueryTitle', + { + defaultMessage: 'Enrichment with Threat Intelligence', + } +); + +/** + * Defines the fields displayed on each row of the table + */ +export interface ThreatDetailsRow { + /** + * Field column showing a field of the enrichment + */ + title: string; + /** + * Description column + */ + description: { + /** + * Field of the enrichment + */ + fieldName: string; + /** + * Value of the enrichment + */ + value: string; + }; +} + +const columns: Array<EuiBasicTableColumn<ThreatDetailsRow>> = [ + { + field: 'title', + truncateText: false, + render: (title: string) => ( + <EuiTitle size="xxxs"> + <StyledH5>{title}</StyledH5> + </EuiTitle> + ), + width: '220px', + name: '', + }, + { + field: 'description', + truncateText: false, + render: (description: ThreatDetailsRow['description']) => { + const { fieldName, value } = description; + const tooltipChild = fieldName.match(REFERENCE) ? ( + <EuiLink href={value} target="_blank"> + {value} + </EuiLink> + ) : ( + <span>{value}</span> + ); + return ( + <EuiToolTip + data-test-subj="message-tool-tip" + content={ + <EuiFlexGroup direction="column" gutterSize="none"> + <EuiFlexItem grow={false}> + <span>{fieldName}</span> + </EuiFlexItem> + </EuiFlexGroup> + } + > + {tooltipChild} + </EuiToolTip> + ); + }, + name: '', + }, +]; + +export interface EnrichmentAccordionProps { + /** + * Enrichment data + */ + enrichment: CtiEnrichment; + /** + * Index used to pass to the table data-test-subj + */ + index: number; +} + +/** + * Displays the enrichment data in an accordion + */ +export const EnrichmentAccordion = memo(({ enrichment, index }: EnrichmentAccordionProps) => { + const { euiTheme } = useEuiTheme(); + const { + id = `threat-details-item`, + field, + feedName, + type, + value, + } = getEnrichmentIdentifiers(enrichment); + const accordionId = `${id}${field}`; + const showInspectButton = useMemo(() => isInvestigationTimeEnrichment(type), [type]); + const items = useMemo(() => buildThreatDetailsItems(enrichment), [enrichment]); + + return ( + <EuiAccordion + id={accordionId} + key={accordionId} + initialIsOpen={true} + arrowDisplay="right" + buttonContent={<EnrichmentButtonContent field={field} feedName={feedName} value={value} />} + extraAction={ + showInspectButton && ( + <EuiFlexItem grow={false}> + <InspectButton queryId={QUERY_ID} title={INVESTIGATION_QUERY_TITLE} /> + </EuiFlexItem> + ) + } + css={css` + .euiAccordion__triggerWrapper { + background: ${euiTheme.colors.lightestShade}; + border-radius: ${euiTheme.size.xs}; + height: ${euiTheme.size.xl}; + margin-bottom: ${euiTheme.size.s}; + padding-left: ${euiTheme.size.s}; + `} + > + <EuiInMemoryTable + columns={columns} + compressed + data-test-subj={`${THREAT_INTELLIGENCE_ENRICHMENTS_ACCORDION_TABLE_TEST_ID}-${index}`} + items={items} + css={css` + .euiTableHeaderCell, + .euiTableRowCell { + border: none; + } + .euiTableHeaderCell .euiTableCellContent { + padding: 0; + } + `} + /> + </EuiAccordion> + ); +}); + +EnrichmentAccordion.displayName = 'EnrichmentAccordion'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion_group.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion_group.test.tsx new file mode 100644 index 0000000000000..3022c6aac8a44 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion_group.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { EnrichmentAccordionGroup } from './threat_details_view_enrichment_accordion_group'; +import type { CtiEnrichment } from '../../../../../common/search_strategy'; +import { TestProviders } from '../../../../common/mock'; +import { indicatorWithNestedObjects } from '../mocks/indicator_with_nested_objects'; +import { THREAT_INTELLIGENCE_ENRICHMENTS_ACCORDION_TABLE_TEST_ID } from './test_ids'; + +describe('EnrichmentAccordionGroup', () => { + describe('with an indicator with an array of nested objects as a field value', () => { + it('should render the indicator without those fields', () => { + // @ts-expect-error this indicator intentionally does not conform to the CtiEnrichment type + const enrichments = [indicatorWithNestedObjects] as CtiEnrichment[]; + + const { getByTestId } = render( + <TestProviders> + <EnrichmentAccordionGroup enrichments={enrichments} /> + </TestProviders> + ); + + const enrichmentView = getByTestId( + `${THREAT_INTELLIGENCE_ENRICHMENTS_ACCORDION_TABLE_TEST_ID}-0` + ); + expect(enrichmentView).toBeInTheDocument(); + expect(enrichmentView).toHaveTextContent('ipv4-addr'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion_group.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion_group.tsx new file mode 100644 index 0000000000000..60f7ad8d739cd --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_accordion_group.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, { memo } from 'react'; +import { EuiSpacer } from '@elastic/eui'; + +import { EnrichmentAccordion } from './threat_details_view_enrichment_accordion'; +import type { CtiEnrichment } from '../../../../../common/search_strategy'; +import { getFirstSeen } from '../../shared/utils/threat_intelligence'; + +export interface EnrichmentAccordionGroupProps { + /** + * Enrichment data + */ + enrichments: CtiEnrichment[]; +} + +/** + * Displays multiple accordions that each show the enrichment data + */ +export const EnrichmentAccordionGroup = memo(({ enrichments }: EnrichmentAccordionGroupProps) => ( + <> + {enrichments + .sort((a, b) => getFirstSeen(b) - getFirstSeen(a)) + .map((enrichment, index) => ( + <React.Fragment key={`${enrichment.id}`}> + <EnrichmentAccordion enrichment={enrichment} index={index} /> + {index < enrichments.length - 1 && <EuiSpacer size="m" />} + </React.Fragment> + ))} + </> +)); + +EnrichmentAccordionGroup.displayName = 'EnrichmentAccordionGroup'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_button_content.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_button_content.test.tsx new file mode 100644 index 0000000000000..5d9d39f266988 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_button_content.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import { EnrichmentButtonContent } from './threat_details_view_enrichment_button_content'; +import { THREAT_INTELLIGENCE_ENRICHMENTS_BUTTON_CONTENT_TEST_ID } from './test_ids'; + +describe('EnrichmentButtonContent', () => { + it('should render string with feedName if feedName is present', () => { + const wrapper = mount( + <EnrichmentButtonContent field={'source.ip'} value={'127.0.0.1'} feedName={'eceintel'} /> + ); + expect( + wrapper + .find(`[data-test-subj="${THREAT_INTELLIGENCE_ENRICHMENTS_BUTTON_CONTENT_TEST_ID}"]`) + .hostNodes() + .text() + ).toEqual('source.ip 127.0.0.1 from eceintel'); + }); + + it('should enders string without feedName if feedName is not present', () => { + const wrapper = mount(<EnrichmentButtonContent field={'source.ip'} value={'127.0.0.1'} />); + expect( + wrapper + .find(`[data-test-subj="${THREAT_INTELLIGENCE_ENRICHMENTS_BUTTON_CONTENT_TEST_ID}"]`) + .hostNodes() + .text() + ).toEqual('source.ip 127.0.0.1'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_button_content.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_button_content.tsx new file mode 100644 index 0000000000000..87fb0babbfad4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_button_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 React, { memo } from 'react'; +import styled from 'styled-components'; +import { EuiToolTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { THREAT_INTELLIGENCE_ENRICHMENTS_BUTTON_CONTENT_TEST_ID } from './test_ids'; + +const FEED_NAME_PREPOSITION = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.feedNamePreposition', + { + defaultMessage: 'from', + } +); + +const OverflowParent = styled.div` + display: inline-grid; +`; + +const OverflowContainer = styled.div` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-weight: bold; +`; + +export interface EnrichmentButtonContentProps { + /** + * The field name of the enrichment (default to an empty string) + */ + field?: string; + /** + * The feed name of the enrichment (default to an empty string) + */ + feedName?: string; + /** + * The value of the enrichment (default to an empty string) + */ + value?: string; +} + +/** + * Displays the content of the button rendered in the accordion + */ +export const EnrichmentButtonContent = memo( + ({ field = '', feedName = '', value = '' }: EnrichmentButtonContentProps) => { + const title = `${field} ${value}${feedName ? ` ${FEED_NAME_PREPOSITION} ${feedName}` : ''}`; + return ( + <EuiToolTip content={value}> + <OverflowParent data-test-subj={THREAT_INTELLIGENCE_ENRICHMENTS_BUTTON_CONTENT_TEST_ID}> + <OverflowContainer>{title}</OverflowContainer> + </OverflowParent> + </EuiToolTip> + ); + } +); + +EnrichmentButtonContent.displayName = 'EnrichmentButtonContent'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_section.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_section.test.tsx new file mode 100644 index 0000000000000..9670118785bd5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_section.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 } from '@testing-library/react'; +import { EnrichmentSection } from './threat_details_view_enrichment_section'; +import { TestProviders } from '../../../../common/mock'; +import { + THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID, + THREAT_INTELLIGENCE_LOADING_ENRICHMENTS_TEST_ID, + THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID, +} from './test_ids'; +import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; + +describe('EnrichmentSection', () => { + it('should render no data views for indicator match rule type', () => { + const { getByTestId } = render( + <TestProviders> + <EnrichmentSection enrichments={undefined} type={ENRICHMENT_TYPES.IndicatorMatchRule} /> + </TestProviders> + ); + + expect(getByTestId(THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID)).toHaveTextContent( + 'This alert does not have threat intelligence.' + ); + }); + + it('should render no data views for other types', () => { + const { getByTestId } = render( + <TestProviders> + <EnrichmentSection enrichments={undefined} type={ENRICHMENT_TYPES.InvestigationTime} /> + </TestProviders> + ); + + expect(getByTestId(THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID)).toHaveTextContent( + "Additional threat intelligence wasn't found within the selected time frame" + ); + }); + + it('should render title for indicator match rule type', () => { + const { getByTestId } = render( + <TestProviders> + <EnrichmentSection enrichments={[]} type={ENRICHMENT_TYPES.IndicatorMatchRule} /> + </TestProviders> + ); + + expect(getByTestId(THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID)).toHaveTextContent( + 'Threat match detected' + ); + }); + + it('should render title for other types', () => { + const { getByTestId } = render( + <TestProviders> + <EnrichmentSection enrichments={[]} type={ENRICHMENT_TYPES.InvestigationTime} /> + </TestProviders> + ); + + expect(getByTestId(THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID)).toHaveTextContent( + 'Enriched with threat intelligence' + ); + }); + + it('should render children props', () => { + const { getByTestId } = render( + <TestProviders> + <EnrichmentSection enrichments={undefined}> + <div data-test-subj="test-child" /> + </EnrichmentSection> + </TestProviders> + ); + + expect(getByTestId('test-child')).toBeInTheDocument(); + }); + + it('should render loading state', () => { + const { getByTestId } = render( + <TestProviders> + <EnrichmentSection enrichments={undefined} loading={true} /> + </TestProviders> + ); + + expect(getByTestId(THREAT_INTELLIGENCE_LOADING_ENRICHMENTS_TEST_ID)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_section.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_section.tsx new file mode 100644 index 0000000000000..b615ff8fa86d4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_details_view_enrichment_section.tsx @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useMemo } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSkeletonText, + EuiSpacer, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID, + THREAT_INTELLIGENCE_LOADING_ENRICHMENTS_TEST_ID, + THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID, +} from './test_ids'; +import { isInvestigationTimeEnrichment } from '../../shared/utils/threat_intelligence'; +import type { CtiEnrichment } from '../../../../../common/search_strategy'; +import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; +import { EnrichmentAccordionGroup } from './threat_details_view_enrichment_accordion_group'; + +const INDICATOR_ENRICHMENT_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.indicatorEnrichmentTitle', + { + defaultMessage: 'Threat match detected', + } +); + +const INVESTIGATION_ENRICHMENT_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.investigationEnrichmentTitle', + { + defaultMessage: 'Enriched with threat intelligence', + } +); + +const INDICATOR_TOOLTIP_CONTENT = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.indicatorEnrichmentTooltipContent', + { + defaultMessage: 'Shows available threat indicator matches.', + } +); + +const INVESTIGATION_TOOLTIP_CONTENT = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.investigationEnrichmentTooltipContent', + { + defaultMessage: + 'Shows additional threat intelligence for the alert. The past 30 days were queried by default.', + } +); + +const NO_ENRICHMENTS_FOUND_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.noEnrichmentsFoundDescription', + { + defaultMessage: 'This alert does not have threat intelligence.', + } +); + +const InlineBlock = styled.div` + display: inline-block; + line-height: 1.7em; +`; + +export interface EnrichmentSectionProps { + /** + * The enrichments to display + */ + enrichments: CtiEnrichment[] | undefined; + /** + * The type of enrichment (InvestigationTime or IndicatorMatchRule) + */ + type?: ENRICHMENT_TYPES; + /** + * Whether the enrichments are loading + */ + loading?: boolean; + /** + * The data-test-subj to apply to the component + */ + dataTestSubj?: string; + /** + * The children to render + */ + children?: React.ReactNode; +} + +/** + * Displays the enrichments in multiple accordions when data has loaded. + * While data is loading, it renders a skeleton. + * If no data is found, it displays a message. + * Also allows to render a component passed from the parent (currently used to render a range picker). + */ +export const EnrichmentSection = memo( + ({ enrichments, type, loading, dataTestSubj, children }: EnrichmentSectionProps) => { + const tooltip = useMemo( + () => + isInvestigationTimeEnrichment(type) + ? INVESTIGATION_TOOLTIP_CONTENT + : INDICATOR_TOOLTIP_CONTENT, + [type] + ); + + const noEnrichmentDataMessage = useMemo( + () => ( + <InlineBlock data-test-subj={THREAT_INTELLIGENCE_NO_ENRICHMENTS_FOUND_TEST_ID}> + {type === ENRICHMENT_TYPES.IndicatorMatchRule ? ( + NO_ENRICHMENTS_FOUND_DESCRIPTION + ) : ( + <FormattedMessage + id="xpack.securitySolution.enrichment.noInvestigationEnrichment" + defaultMessage="Additional threat intelligence wasn't found within the selected time frame. Try a different time frame, or {link} to collect threat intelligence for threat detection and matching." + values={{ + link: ( + <EuiLink + href="https://www.elastic.co/guide/en/security/current/es-threat-intel-integrations.html" + target="_blank" + > + <FormattedMessage + id="xpack.securitySolution.enrichment.investigationEnrichmentDocumentationLink" + defaultMessage="enable threat intelligence integrations" + /> + </EuiLink> + ), + }} + /> + )} + </InlineBlock> + ), + [type] + ); + + return ( + <div data-test-subj={dataTestSubj}> + {type ? ( + <> + <EuiFlexGroup + direction="row" + gutterSize="xs" + alignItems="baseline" + data-test-subj={THREAT_INTELLIGENCE_ENRICHMENTS_TITLE_TEST_ID} + > + <EuiFlexItem grow={false}> + <EuiTitle size="xxxs"> + <h3> + {type === ENRICHMENT_TYPES.IndicatorMatchRule + ? INDICATOR_ENRICHMENT_TITLE + : INVESTIGATION_ENRICHMENT_TITLE} + </h3> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiToolTip content={tooltip}> + <EuiIcon type="iInCircle" size="m" /> + </EuiToolTip> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="s" /> + </> + ) : null} + + {children} + + {Array.isArray(enrichments) ? ( + <EnrichmentAccordionGroup enrichments={enrichments} /> + ) : ( + <> + {type ? noEnrichmentDataMessage : null} + {loading && ( + <> + <EuiSpacer size="m" /> + <EuiSkeletonText + data-test-subj={THREAT_INTELLIGENCE_LOADING_ENRICHMENTS_TEST_ID} + lines={4} + /> + </> + )} + </> + )} + </div> + ); + } +); + +EnrichmentSection.displayName = 'EnrichmentSection'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.test.tsx index c028b9194748f..a4e2ec73018a3 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.test.tsx @@ -16,6 +16,7 @@ import { } from './test_ids'; import { ThreatIntelligenceDetails } from './threat_intelligence_details'; import { useThreatIntelligenceDetails } from '../hooks/use_threat_intelligence_details'; +import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock'; jest.mock('../../../../common/lib/kibana', () => { const originalModule = jest.requireActual('../../../../common/lib/kibana'); @@ -37,7 +38,6 @@ const defaultContextValue = { getFieldsData: () => 'id', } as unknown as DocumentDetailsContext; -// Renders System Under Test const renderThreatIntelligenceDetails = (contextValue: DocumentDetailsContext) => render( <TestProviders> @@ -59,12 +59,9 @@ describe('<ThreatIntelligenceDetails />', () => { eventFields: {}, }); - const wrapper = renderThreatIntelligenceDetails(defaultContextValue); - - expect( - wrapper.getByTestId(THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID) - ).toBeInTheDocument(); + const { getByTestId } = renderThreatIntelligenceDetails(defaultContextValue); + expect(getByTestId(THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID)).toBeInTheDocument(); expect(useThreatIntelligenceDetails).toHaveBeenCalled(); }); @@ -79,10 +76,32 @@ describe('<ThreatIntelligenceDetails />', () => { eventFields: {}, }); - const wrapper = renderThreatIntelligenceDetails(defaultContextValue); - - expect(wrapper.getByTestId(THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID)).toBeInTheDocument(); + const { getByTestId } = renderThreatIntelligenceDetails(defaultContextValue); + expect(getByTestId(THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID)).toBeInTheDocument(); expect(useThreatIntelligenceDetails).toHaveBeenCalled(); }); + + it('should render enrichments section', () => { + const enrichments = [ + buildEventEnrichmentMock(), + buildEventEnrichmentMock({ 'matched.id': ['other.id'], 'matched.field': ['other.field'] }), + ]; + + jest.mocked(useThreatIntelligenceDetails).mockReturnValue({ + isLoading: true, + enrichments, + isEventDataLoading: false, + isEnrichmentsLoading: false, + range: { from: '', to: '' }, + setRange: () => {}, + eventFields: { + test: 'test', + }, + }); + + const { getByTestId } = renderThreatIntelligenceDetails(defaultContextValue); + + expect(getByTestId(THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID)).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.tsx index 5a8b6984fb3be..f473ae2c3262b 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_details.tsx @@ -5,13 +5,20 @@ * 2.0. */ -import React from 'react'; -import { EuiSpacer } from '@elastic/eui'; +import React, { memo } from 'react'; +import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; 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 { groupBy } from 'lodash'; +import { EnrichmentSection } from './threat_details_view_enrichment_section'; +import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; +import { EnrichmentRangePicker } from './threat_intelligence_view_enrichment_range_picker'; import { useThreatIntelligenceDetails } from '../hooks/use_threat_intelligence_details'; -import { THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID } from './test_ids'; +import { + THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID, + THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID, + THREAT_INTELLIGENCE_ENRICHMENTS_TEST_ID, + THREAT_INTELLIGENCE_MATCHES_TEST_ID, +} from './test_ids'; import { FlyoutLoading } from '../../../shared/components/flyout_loading'; export const THREAT_INTELLIGENCE_TAB_ID = 'threatIntelligence'; @@ -19,7 +26,7 @@ export const THREAT_INTELLIGENCE_TAB_ID = 'threatIntelligence'; /** * Threat intelligence displayed in the document details expandable flyout left section under the Insights tab */ -export const ThreatIntelligenceDetails: React.FC = () => { +export const ThreatIntelligenceDetails = memo(() => { const { enrichments, eventFields, @@ -30,21 +37,54 @@ export const ThreatIntelligenceDetails: React.FC = () => { setRange, } = useThreatIntelligenceDetails(); + const showInvestigationTimeEnrichments = !isEmpty(eventFields); + const { + [ENRICHMENT_TYPES.IndicatorMatchRule]: indicatorMatches, + [ENRICHMENT_TYPES.InvestigationTime]: threatIntelEnrichments, + undefined: matchesWithNoType, + } = groupBy(enrichments, 'matched.type'); + return isEventDataLoading ? ( <FlyoutLoading data-test-subj={THREAT_INTELLIGENCE_DETAILS_LOADING_TEST_ID} /> ) : ( - <ThreatDetailsView - before={null} - loading={isLoading} - enrichments={enrichments} - showInvestigationTimeEnrichments={!isEmpty(eventFields)} - > - <> - <EnrichmentRangePicker setRange={setRange} loading={isEnrichmentsLoading} range={range} /> - <EuiSpacer size="m" /> - </> - </ThreatDetailsView> + <> + <EnrichmentSection + dataTestSubj={THREAT_INTELLIGENCE_DETAILS_ENRICHMENTS_TEST_ID} + enrichments={indicatorMatches} + type={ENRICHMENT_TYPES.IndicatorMatchRule} + /> + + {showInvestigationTimeEnrichments ? ( + <> + <EuiHorizontalRule /> + <EnrichmentSection + dataTestSubj={THREAT_INTELLIGENCE_ENRICHMENTS_TEST_ID} + enrichments={threatIntelEnrichments} + type={ENRICHMENT_TYPES.InvestigationTime} + loading={isLoading} + > + <EnrichmentRangePicker + setRange={setRange} + loading={isEnrichmentsLoading} + range={range} + /> + <EuiSpacer size="m" /> + </EnrichmentSection> + </> + ) : null} + + {matchesWithNoType ? ( + <> + <EuiHorizontalRule /> + {indicatorMatches && <EuiSpacer size="l" />} + <EnrichmentSection + enrichments={matchesWithNoType} + dataTestSubj={THREAT_INTELLIGENCE_MATCHES_TEST_ID} + /> + </> + ) : null} + </> ); -}; +}); ThreatIntelligenceDetails.displayName = 'ThreatIntelligenceDetails'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_view_enrichment_range_picker.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_view_enrichment_range_picker.test.tsx new file mode 100644 index 0000000000000..483ef9e4fc597 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_view_enrichment_range_picker.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; 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 { EnrichmentRangePicker } from './threat_intelligence_view_enrichment_range_picker'; +import { + THREAT_INTELLIGENCE_ENRICHMENTS_RANGE_PICKER_TEST_ID, + THREAT_INTELLIGENCE_ENRICHMENTS_REFRESH_BUTTON_TEST_ID, +} from './test_ids'; + +describe('EnrichmentRangePicker', () => { + const setRangeSpy = jest.fn(); + + const rangePickerProps = { + loading: false, + setRange: setRangeSpy, + range: { to: 'now', from: 'now-30d' }, + }; + + it('renders a date picker and a button', () => { + const wrapper = mount( + <TestProviders> + <EnrichmentRangePicker {...rangePickerProps} /> + </TestProviders> + ); + + expect( + wrapper.exists(`[data-test-subj="${THREAT_INTELLIGENCE_ENRICHMENTS_RANGE_PICKER_TEST_ID}"]`) + ).toEqual(true); + expect( + wrapper.exists(`[data-test-subj="${THREAT_INTELLIGENCE_ENRICHMENTS_REFRESH_BUTTON_TEST_ID}"]`) + ).toEqual(true); + }); + + it('invokes setRange', () => { + const wrapper = mount( + <TestProviders> + <EnrichmentRangePicker {...rangePickerProps} /> + </TestProviders> + ); + + wrapper + .find('input.start-picker') + .first() + .simulate('change', { target: { value: '08/10/2019 06:29 PM' } }); + wrapper + .find(`[data-test-subj="${THREAT_INTELLIGENCE_ENRICHMENTS_REFRESH_BUTTON_TEST_ID}"]`) + .hostNodes() + .simulate('click'); + + expect(setRangeSpy).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_view_enrichment_range_picker.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_view_enrichment_range_picker.tsx new file mode 100644 index 0000000000000..54401fdad1e11 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/threat_intelligence_view_enrichment_range_picker.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import React, { memo, useMemo, useState, useCallback } from 'react'; +import { + EuiDatePicker, + EuiDatePickerRange, + EuiFlexGroup, + EuiFlexItem, + EuiButton, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { + THREAT_INTELLIGENCE_ENRICHMENTS_RANGE_PICKER_TEST_ID, + THREAT_INTELLIGENCE_ENRICHMENTS_REFRESH_BUTTON_TEST_ID, +} from './test_ids'; +import { + DEFAULT_EVENT_ENRICHMENT_FROM, + DEFAULT_EVENT_ENRICHMENT_TO, +} from '../../../../../common/cti/constants'; + +const ENRICHMENT_LOOKBACK_START_DATE = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.enrichmentQueryStartDate', + { + defaultMessage: 'Start date', + } +); + +const ENRICHMENT_LOOKBACK_END_DATE = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.enrichmentQueryEndDate', + { + defaultMessage: 'End date', + } +); + +const REFRESH = i18n.translate('xpack.securitySolution.flyout.threatIntelligence.refresh', { + defaultMessage: 'Refresh', +}); + +export interface RangePickerProps { + /** + * The range of the picker + */ + range: { to: string; from: string }; + /** + * Set the range of the picker + */ + setRange: ({ to, from }: { to: string; from: string }) => void; + /** + * Whether the picker is loading + */ + loading: boolean; +} + +/** + * A component that allows the user to select a range of dates for enrichment + */ +export const EnrichmentRangePicker = memo(({ range, setRange, loading }: RangePickerProps) => { + const [startDate, setStartDate] = useState<moment.Moment | null>( + range.from === DEFAULT_EVENT_ENRICHMENT_FROM ? moment().subtract(30, 'd') : moment(range.from) + ); + const [endDate, setEndDate] = useState<moment.Moment | null>( + range.to === DEFAULT_EVENT_ENRICHMENT_TO ? moment() : moment(range.to) + ); + + const onButtonClick = useCallback(() => { + if (startDate && endDate && startDate.isBefore(endDate)) { + setRange({ + from: startDate.toISOString(), + to: endDate.toISOString(), + }); + } + }, [endDate, setRange, startDate]); + + const isValid = useMemo(() => startDate?.isBefore(endDate), [startDate, endDate]); + + return ( + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiDatePickerRange + data-test-subj={THREAT_INTELLIGENCE_ENRICHMENTS_RANGE_PICKER_TEST_ID} + startDateControl={ + <EuiDatePicker + className="start-picker" + selected={startDate} + onChange={setStartDate} + startDate={startDate} + endDate={endDate} + isInvalid={!isValid} + aria-label={ENRICHMENT_LOOKBACK_START_DATE} + showTimeSelect + /> + } + endDateControl={ + <EuiDatePicker + className="end-picker" + selected={endDate} + onChange={setEndDate} + startDate={startDate} + endDate={endDate} + isInvalid={!isValid} + aria-label={ENRICHMENT_LOOKBACK_END_DATE} + showTimeSelect + /> + } + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton + iconType={'refresh'} + onClick={onButtonClick} + isLoading={loading} + data-test-subj={THREAT_INTELLIGENCE_ENRICHMENTS_REFRESH_BUTTON_TEST_ID} + isDisabled={!isValid} + > + {REFRESH} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + ); +}); + +EnrichmentRangePicker.displayName = 'EnrichmentRangePicker'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts index ae71c7f74c8d6..e4b067afb0921 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.test.ts @@ -7,13 +7,12 @@ import { useThreatIntelligenceDetails } from './use_threat_intelligence_details'; import { renderHook } from '@testing-library/react-hooks'; - +import { SecurityPageName } from '@kbn/deeplinks-security'; import { useTimelineEventsDetails } from '../../../../timelines/containers/details'; import { useSourcererDataView } from '../../../../sourcerer/containers'; import { useRouteSpy } from '../../../../common/utils/route/use_route_spy'; import { useDocumentDetailsContext } from '../../shared/context'; -import { useInvestigationTimeEnrichment } from '../../../../common/containers/cti/event_enrichment'; -import { SecurityPageName } from '../../../../../common/constants'; +import { useInvestigationTimeEnrichment } from '../../shared/hooks/use_investigation_enrichment'; import type { RouteSpyState } from '../../../../common/utils/route/types'; import { type GetBasicDataFromDetailsData, @@ -25,7 +24,7 @@ jest.mock('../../../../timelines/containers/details'); jest.mock('../../../../sourcerer/containers'); jest.mock('../../../../common/utils/route/use_route_spy'); jest.mock('../../shared/context'); -jest.mock('../../../../common/containers/cti/event_enrichment'); +jest.mock('../../shared/hooks/use_investigation_enrichment'); jest.mock('../../../../timelines/components/side_panel/event_details/helpers'); describe('useThreatIntelligenceDetails', () => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.ts index 04a2bd0ddee47..cba0c25cb25ad 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/hooks/use_threat_intelligence_details.ts @@ -6,6 +6,7 @@ */ import { useMemo } from 'react'; +import { SecurityPageName } from '@kbn/deeplinks-security'; 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'; @@ -14,26 +15,42 @@ import { getEnrichmentFields, parseExistingEnrichments, timelineDataToEnrichment, -} from '../../../../common/components/event_details/cti_details/helpers'; -import { SecurityPageName } from '../../../../../common/constants'; +} from '../../shared/utils/threat_intelligence'; import { SourcererScopeName } from '../../../../sourcerer/store/model'; - -import { useInvestigationTimeEnrichment } from '../../../../common/containers/cti/event_enrichment'; +import { useInvestigationTimeEnrichment } from '../../shared/hooks/use_investigation_enrichment'; import { useTimelineEventsDetails } from '../../../../timelines/containers/details'; import { useSourcererDataView } from '../../../../sourcerer/containers'; import { useRouteSpy } from '../../../../common/utils/route/use_route_spy'; import { useDocumentDetailsContext } from '../../shared/context'; -export interface ThreatIntelligenceDetailsValue { +export interface ThreatIntelligenceDetailsResult { + /** + * Enrichments extracted from the event data + */ enrichments: CtiEnrichment[]; + /** + * Fields extracted from the event data + */ eventFields: EventFields; + /** + * Whether enrichments are loading + */ isEnrichmentsLoading: boolean; + /** + * Whether event data is loading + */ isEventDataLoading: boolean; + /** + * Whether event or enrichment data is loading + */ isLoading: boolean; - range: { - from: string; - to: string; - }; + /** + * Range on the range picker to fetch enrichments + */ + range: { from: string; to: string }; + /** + * Set the range on the range picker to fetch enrichments + */ setRange: (range: { from: string; to: string }) => void; } @@ -42,7 +59,7 @@ export interface ThreatIntelligenceDetailsValue { * Reusing a bunch of hooks scattered across kibana, it makes it easier to mock the data layer * for component testing. */ -export const useThreatIntelligenceDetails = (): ThreatIntelligenceDetailsValue => { +export const useThreatIntelligenceDetails = (): ThreatIntelligenceDetailsResult => { const { indexName, eventId } = useDocumentDetailsContext(); const [{ pageName }] = useRouteSpy(); const sourcererScope = @@ -68,7 +85,7 @@ export const useThreatIntelligenceDetails = (): ThreatIntelligenceDetailsValue = loading: isEnrichmentsLoading, setRange, range, - } = useInvestigationTimeEnrichment(eventFields); + } = useInvestigationTimeEnrichment({ eventFields }); const existingEnrichments = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/indicator_with_nested_objects.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/mocks/indicator_with_nested_objects.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/indicator_with_nested_objects.ts rename to x-pack/plugins/security_solution/public/flyout/document_details/left/mocks/indicator_with_nested_objects.ts diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_title.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_title.tsx index a9da1ae146394..b7625075b98da 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_title.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/alert_header_title.tsx @@ -14,7 +14,7 @@ import { useRuleDetailsLink } from '../../shared/hooks/use_rule_details_link'; import { DocumentStatus } from './status'; import { DocumentSeverity } from './severity'; import { RiskScore } from './risk_score'; -import { useRefetchByScope } from '../../../../timelines/components/side_panel/event_details/flyout/use_refetch_by_scope'; +import { useRefetchByScope } from '../hooks/use_refetch_by_scope'; import { useBasicDataFromDetailsData } from '../../../../timelines/components/side_panel/event_details/helpers'; import { useDocumentDetailsContext } from '../../shared/context'; import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_name_cell.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_name_cell.test.tsx new file mode 100644 index 0000000000000..31a605ef9f2f8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_name_cell.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 { render } from '@testing-library/react'; +import React from 'react'; + +import { TableFieldNameCell } from './table_field_name_cell'; +import { + FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID, + FLYOUT_TABLE_FIELD_NAME_CELL_TEXT_TEST_ID, +} from './test_ids'; + +const mockDataType = 'date'; +const mockField = '@timestamp'; + +describe('TableFieldNameCell', () => { + it('should render icon and text', () => { + const { getByTestId } = render( + <TableFieldNameCell dataType={mockDataType} field={mockField} /> + ); + + expect(getByTestId(FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID)).toBeInTheDocument(); + expect( + getByTestId(FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID).querySelector('span') + ).toHaveAttribute('data-euiicon-type', 'tokenDate'); + expect(getByTestId(FLYOUT_TABLE_FIELD_NAME_CELL_TEXT_TEST_ID)).toHaveTextContent(mockField); + }); + + it('should render default icon', () => { + const { getByTestId } = render( + <TableFieldNameCell dataType={'wrong_type'} field={mockField} /> + ); + + expect( + getByTestId(FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID).querySelector('span') + ).toHaveAttribute('data-euiicon-type', 'questionInCircle'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_name_cell.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_name_cell.tsx new file mode 100644 index 0000000000000..81657e3280ec8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_name_cell.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import { FieldIcon } from '@kbn/react-field'; +import { EcsFlat } from '@elastic/ecs'; +import { getFieldTypeName } from '@kbn/field-utils'; +import { + FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID, + FLYOUT_TABLE_FIELD_NAME_CELL_TEXT_TEST_ID, +} from './test_ids'; +import { getExampleText } from '../../../../common/components/event_details/helpers'; + +const getEcsField = (field: string): { example?: string; description?: string } | undefined => { + return EcsFlat[field as keyof typeof EcsFlat] as + | { + example?: string; + description?: string; + } + | undefined; +}; + +export interface TableFieldNameCellProps { + /** + * Type used to pick the correct icon + */ + dataType: string; + /** + * Field name + */ + field: string; +} + +/** + * Renders an icon/text couple in the first column of the table + */ +export const TableFieldNameCell = memo(({ dataType, field }: TableFieldNameCellProps) => { + const ecsField = getEcsField(field); + const typeName = getFieldTypeName(dataType); + + return ( + <EuiFlexGroup gutterSize="xs" alignItems="center"> + <EuiFlexItem grow={false}> + <FieldIcon + data-test-subj={FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID} + type={dataType} + label={typeName} + scripted={undefined} + /> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup wrap={true} gutterSize="none" responsive={false}> + <EuiFlexItem className="eui-textBreakAll" grow={false}> + <EuiToolTip + position="top" + content={ + !isEmpty(ecsField?.description) + ? `${ecsField?.description} ${getExampleText(ecsField?.example)}` + : field + } + delay="long" + anchorClassName="eui-textBreakAll" + > + <EuiText size="xs" data-test-subj={FLYOUT_TABLE_FIELD_NAME_CELL_TEXT_TEST_ID}> + {field} + </EuiText> + </EuiToolTip> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + ); +}); + +TableFieldNameCell.displayName = 'TableFieldNameCell'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_value_cell.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_value_cell.test.tsx new file mode 100644 index 0000000000000..f4c0f54a11f02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_value_cell.test.tsx @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import type { FieldSpec } from '@kbn/data-plugin/common'; +import type { EventFieldsData } from '../../../../common/components/event_details/types'; +import { TableFieldValueCell } from './table_field_value_cell'; +import { TestProviders } from '../../../../common/mock'; + +const contextId = 'test'; + +const eventId = 'TUWyf3wBFCFU0qRJTauW'; + +const hostIpData: EventFieldsData = { + aggregatable: true, + ariaRowindex: 35, + field: 'host.ip', + isObjectArray: false, + name: 'host.ip', + originalValue: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], + readFromDocValues: false, + searchable: true, + type: 'ip', + values: ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'], +}; +const hostIpValues = ['127.0.0.1', '::1', '10.1.2.3', 'fe80::4001:aff:fec8:32']; + +describe('TableFieldValueCell', () => { + describe('common behavior', () => { + beforeEach(() => { + render( + <TestProviders> + <TableFieldValueCell + contextId={contextId} + data={hostIpData} + eventId={eventId} + values={hostIpValues} + /> + </TestProviders> + ); + }); + + it('should format multiple values such that each value is displayed on a single line', () => { + expect(screen.getByTestId(`event-field-${hostIpData.field}`).className).toContain('column'); + }); + }); + + describe('when `BrowserField` metadata is NOT available', () => { + beforeEach(() => { + render( + <TestProviders> + <TableFieldValueCell + contextId={contextId} + data={hostIpData} + eventId={eventId} + fieldFromBrowserField={undefined} // <-- no metadata + values={hostIpValues} + /> + </TestProviders> + ); + }); + + it('should render each of the expected values when `fieldFromBrowserField` is undefined', () => { + hostIpValues.forEach((value) => { + expect(screen.getByText(value)).toBeInTheDocument(); + }); + }); + }); + + describe('`message` field formatting', () => { + const messageData: EventFieldsData = { + aggregatable: false, + ariaRowindex: 50, + field: 'message', + isObjectArray: false, + name: 'message', + originalValue: ['Endpoint network event'], + readFromDocValues: false, + searchable: true, + type: 'string', + values: ['Endpoint network event'], + }; + const messageValues = ['Endpoint network event']; + + const messageFieldFromBrowserField: FieldSpec = { + aggregatable: false, + name: 'message', + readFromDocValues: false, + searchable: true, + type: 'string', + }; + + beforeEach(() => { + render( + <TestProviders> + <TableFieldValueCell + contextId={contextId} + data={messageData} + eventId={eventId} + fieldFromBrowserField={messageFieldFromBrowserField} + values={messageValues} + /> + </TestProviders> + ); + }); + + it('should render special formatting for the `message` field', () => { + expect(screen.getByTestId('event-field-message')).toBeInTheDocument(); + }); + + it('should render the expected message value', () => { + messageValues.forEach((value) => { + expect(screen.getByText(value)).toBeInTheDocument(); + }); + }); + }); + + describe('when `FieldSpec` metadata IS available', () => { + const hostIpFieldFromBrowserField: FieldSpec = { + aggregatable: true, + name: 'host.ip', + readFromDocValues: false, + searchable: true, + type: 'ip', + }; + + beforeEach(() => { + render( + <TestProviders> + <TableFieldValueCell + contextId={contextId} + data={hostIpData} + eventId={eventId} + fieldFromBrowserField={hostIpFieldFromBrowserField} // <-- metadata + values={hostIpValues} + /> + </TestProviders> + ); + }); + + it('should align items at the start of the group to prevent content from stretching (by default)', () => { + expect(screen.getByTestId(`event-field-${hostIpData.field}`).className).toContain( + 'flexStart' + ); + }); + + it('should render link buttons for each of the host ip addresses', () => { + expect(screen.getAllByRole('button').length).toBe(hostIpValues.length); + }); + + it('should render each of the expected values when `fieldFromBrowserField` is provided', () => { + hostIpValues.forEach((value) => { + expect(screen.getByText(value)).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_value_cell.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_value_cell.tsx new file mode 100644 index 0000000000000..8212ddc70c420 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/table_field_value_cell.tsx @@ -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 React, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import type { FieldSpec } from '@kbn/data-plugin/common'; +import { getFieldFormat } from '../../../../common/components/event_details/get_field_format'; +import type { EventFieldsData } from '../../../../common/components/event_details/types'; +import { OverflowField } from '../../../../common/components/tables/helpers'; +import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field'; +import { MESSAGE_FIELD_NAME } from '../../../../timelines/components/timeline/body/renderers/constants'; + +export interface FieldValueCellProps { + /** + * Value used to create a unique identifier in children components + */ + contextId: string; + /** + * Datq retrieved from the row + */ + data: EventFieldsData; + /** + * Id of the document + */ + eventId: string; + /** + * Field retrieved from the BrowserField + */ + fieldFromBrowserField?: Partial<FieldSpec>; + /** + * Value of the link field if it exists. Allows to navigate to other pages like host, user, network... + */ + getLinkValue?: (field: string) => string | null; + /** + * Values for the field, to render in the second column of the table + */ + values: string[] | null | undefined; +} + +/** + * Renders the value of a field in the second column of the table + */ +export const TableFieldValueCell = memo( + ({ + contextId, + data, + eventId, + fieldFromBrowserField, + getLinkValue, + values, + }: FieldValueCellProps) => { + if (values == null) { + return null; + } + + return ( + <EuiFlexGroup + data-test-subj={`event-field-${data.field}`} + direction="column" + gutterSize="none" + > + {values.map((value, i) => { + if (fieldFromBrowserField == null) { + return ( + <EuiFlexItem grow={false} key={`${i}-${value}`}> + <EuiText size="xs" key={`${i}-${value}`}> + {value} + </EuiText> + </EuiFlexItem> + ); + } + + return ( + <EuiFlexItem grow={false} key={`${i}-${value}`}> + {data.field === MESSAGE_FIELD_NAME ? ( + <OverflowField value={value} /> + ) : ( + <FormattedFieldValue + contextId={`${contextId}-${eventId}-${data.field}-${i}-${value}`} + eventId={eventId} + fieldFormat={getFieldFormat(data)} + fieldName={data.field} + fieldFromBrowserField={fieldFromBrowserField} + fieldType={data.type} + isAggregatable={fieldFromBrowserField.aggregatable} + isDraggable={false} + isObjectArray={data.isObjectArray} + value={value} + linkValue={getLinkValue && getLinkValue(data.field)} + truncate={false} + /> + )} + </EuiFlexItem> + ); + })} + </EuiFlexGroup> + ); + } +); + +TableFieldValueCell.displayName = 'TableFieldValueCell'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.test.tsx new file mode 100644 index 0000000000000..6a902ef2b5ce6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.test.tsx @@ -0,0 +1,386 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { ReactWrapper } from 'enzyme'; +import { mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { TakeActionDropdownProps } from './take_action_dropdown'; +import { TakeActionDropdown } from './take_action_dropdown'; +import { generateAlertDetailsDataMock } from '../../../../common/components/event_details/__mocks__'; +import { getDetectionAlertMock } from '../../../../common/mock/mock_detection_alerts'; +import { TimelineId } from '../../../../../common/types/timeline'; +import { TestProviders } from '../../../../common/mock'; +import { mockTimelines } from '../../../../common/mock/mock_timelines_plugin'; +import { createStartServicesMock } from '../../../../common/lib/kibana/kibana_react.mock'; +import { useHttp, useKibana } from '../../../../common/lib/kibana'; +import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; +import { initialUserPrivilegesState as mockInitialUserPrivilegesState } from '../../../../common/components/user_privileges/user_privileges_context'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; +import { getUserPrivilegesMockDefaultValue } from '../../../../common/components/user_privileges/__mocks__'; +import { allCasesPermissions } from '../../../../cases_test_utils'; +import { + ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE, + ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE, +} from '../../../../common/components/toolbar/bulk_actions/translations'; +import { FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID } from '../test_ids'; + +jest.mock('../../../../common/components/endpoint/host_isolation'); +jest.mock('../../../../common/components/endpoint/responder'); +jest.mock('../../../../common/components/user_privileges'); + +jest.mock('../../../../detections/components/user_info', () => ({ + useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), +})); + +jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../../common/components/guided_onboarding_tour/tour_step'); + +jest.mock( + '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges', + () => ({ + useAlertsPrivileges: jest.fn().mockReturnValue({ hasIndexWrite: true, hasKibanaCRUD: true }), + }) +); +jest.mock('../../../../cases/components/use_insert_timeline'); + +jest.mock('../../../../common/hooks/use_app_toasts', () => ({ + useAppToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + }), +})); + +jest.mock('../../../../common/hooks/use_license', () => ({ + useLicense: jest.fn().mockReturnValue({ isPlatinumPlus: () => true, isEnterprise: () => false }), +})); + +jest.mock( + '../../../../common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status', + () => { + return { + useEndpointHostIsolationStatus: jest.fn().mockReturnValue({ + loading: false, + isIsolated: false, + agentStatus: 'healthy', + }), + }; + } +); + +describe('take action dropdown', () => { + let defaultProps: TakeActionDropdownProps; + let mockStartServicesMock: ReturnType<typeof createStartServicesMock>; + + beforeEach(() => { + defaultProps = { + dataFormattedForFieldBrowser: generateAlertDetailsDataMock() as TimelineEventsDetailsItem[], + dataAsNestedObject: getDetectionAlertMock(), + handleOnEventClosed: jest.fn(), + isHostIsolationPanelOpen: false, + onAddEventFilterClick: jest.fn(), + onAddExceptionTypeClick: jest.fn(), + onAddIsolationStatusClick: jest.fn(), + refetch: jest.fn(), + refetchFlyoutData: jest.fn(), + scopeId: TimelineId.active, + onOsqueryClick: jest.fn(), + }; + + mockStartServicesMock = createStartServicesMock(); + + (useKibana as jest.Mock).mockImplementation(() => { + return { + services: { + ...mockStartServicesMock, + timelines: { ...mockTimelines }, + cases: { + ...mockCasesContract(), + helpers: { + canUseCases: jest.fn().mockReturnValue(allCasesPermissions()), + getRuleIdFromEvent: () => null, + }, + }, + osquery: { + isOsqueryAvailable: jest.fn().mockReturnValue(true), + }, + application: { + capabilities: { siem: { crud_alerts: true, read_alerts: true }, osquery: true }, + }, + }, + }; + }); + + (useHttp as jest.Mock).mockReturnValue(mockStartServicesMock.http); + }); + + afterEach(() => { + (useUserPrivileges as jest.Mock).mockReturnValue(getUserPrivilegesMockDefaultValue()); + }); + + test('should render takeActionButton', () => { + const wrapper = mount( + <TestProviders> + <TakeActionDropdown {...defaultProps} /> + </TestProviders> + ); + expect( + wrapper.find(`[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`).exists() + ).toBeTruthy(); + }); + + test('should render takeActionButton with correct text', () => { + const wrapper = mount( + <TestProviders> + <TakeActionDropdown {...defaultProps} /> + </TestProviders> + ); + expect( + wrapper.find(`[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`).first().text() + ).toEqual('Take action'); + }); + + describe('should render take action items', () => { + let wrapper: ReactWrapper; + + beforeAll(() => { + wrapper = mount( + <TestProviders> + <TakeActionDropdown {...defaultProps} /> + </TestProviders> + ); + wrapper + .find(`button[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`) + .simulate('click'); + }); + test('should render "Add to existing case"', async () => { + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="add-to-existing-case-action"]').first().text() + ).toEqual('Add to existing case'); + }); + }); + test('should render "Add to new case"', async () => { + await waitFor(() => { + expect(wrapper.find('[data-test-subj="add-to-new-case-action"]').first().text()).toEqual( + 'Add to new case' + ); + }); + }); + + test('should render "mark as acknowledge"', async () => { + await waitFor(() => { + expect(wrapper.find('[data-test-subj="acknowledged-alert-status"]').first().text()).toEqual( + 'Mark as acknowledged' + ); + }); + }); + + test('should render "mark as close"', async () => { + await waitFor(() => { + expect(wrapper.find('[data-test-subj="close-alert-status"]').first().text()).toEqual( + 'Mark as closed' + ); + }); + }); + + test('should render "Add Endpoint exception"', async () => { + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="add-endpoint-exception-menu-item"]').first().text() + ).toEqual('Add Endpoint exception'); + }); + }); + test('should render "Add rule exception"', async () => { + await waitFor(() => { + expect(wrapper.find('[data-test-subj="add-exception-menu-item"]').first().text()).toEqual( + 'Add rule exception' + ); + }); + }); + + test('should render "Isolate host"', async () => { + await waitFor(() => { + expect(wrapper.find('[data-test-subj="isolate-host-action-item"]').first().text()).toEqual( + 'Isolate host' + ); + }); + }); + test('should render "Investigate in timeline"', async () => { + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="investigate-in-timeline-action-item"]').first().text() + ).toEqual('Investigate in timeline'); + }); + }); + test('should render "Run Osquery"', async () => { + await waitFor(() => { + expect(wrapper.find('[data-test-subj="osquery-action-item"]').first().text()).toEqual( + 'Run Osquery' + ); + }); + }); + test('should render "Respond"', async () => { + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="endpointResponseActions-action-item"]').first().text() + ).toEqual('Respond'); + }); + }); + test('should render "Apply alert tags"', async () => { + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="alert-tags-context-menu-item"]').first().text() + ).toEqual(ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE); + }); + }); + test('should render "Assign alert"', async () => { + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="alert-assignees-context-menu-item"]').first().text() + ).toEqual(ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE); + }); + }); + }); + + describe('for Endpoint related actions', () => { + /** Removes the detail data that is used to determine if data is for an Alert */ + const setAlertDetailsDataMockToEvent = () => { + if (defaultProps.dataFormattedForFieldBrowser) { + defaultProps.dataFormattedForFieldBrowser = defaultProps.dataFormattedForFieldBrowser + .map((obj) => { + if (obj.field === 'kibana.alert.rule.uuid') { + return null; + } + if (obj.field === 'event.kind') { + return { + category: 'event', + field: 'event.kind', + values: ['event'], + originalValue: 'event', + }; + } + return obj; + }) + .filter((obj) => obj) as TimelineEventsDetailsItem[]; + } else { + expect(defaultProps.dataFormattedForFieldBrowser).toBeInstanceOf(Object); + } + }; + + const setAgentTypeOnAlertDetailsDataMock = (agentType: string = 'endpoint') => { + if (defaultProps.dataFormattedForFieldBrowser) { + defaultProps.dataFormattedForFieldBrowser = defaultProps.dataFormattedForFieldBrowser.map( + (obj) => { + if (obj.field === 'agent.type') { + return { + category: 'agent', + field: 'agent.type', + values: [agentType], + originalValue: [agentType], + }; + } + if (obj.field === 'agent.id') { + return { + category: 'agent', + field: 'agent.id', + values: ['123'], + originalValue: ['123'], + }; + } + + return obj; + } + ) as TimelineEventsDetailsItem[]; + } else { + expect(defaultProps.dataFormattedForFieldBrowser).toBeInstanceOf(Object); + } + }; + + /** Set the `agent.type` and `agent.id` on the EcsData */ + const setTypeOnEcsDataWithAgentType = ( + agentType: string = 'endpoint', + agentId: string = '123' + ) => { + if (defaultProps.dataAsNestedObject) { + defaultProps.dataAsNestedObject.agent = { + // @ts-expect-error Ecs definition for agent seems to be missing properties + id: agentId, + type: [agentType], + }; + } else { + expect(defaultProps.dataAsNestedObject).toBeInstanceOf(Object); + } + }; + + let wrapper: ReactWrapper; + + const render = (): ReactWrapper => { + wrapper = mount( + <TestProviders> + <TakeActionDropdown {...defaultProps} /> + </TestProviders> + ); + wrapper + .find(`button[data-test-subj="${FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID}"]`) + .simulate('click'); + + return wrapper; + }; + + it('should include the Isolate/Release action', () => { + render(); + + expect(wrapper.exists('[data-test-subj="isolate-host-action-item"]')).toBe(true); + }); + + it('should include the Responder action', () => { + render(); + + expect(wrapper.exists('[data-test-subj="endpointResponseActions-action-item"]')).toBe(true); + }); + + describe('should correctly enable/disable the "Add Endpoint event filter" button', () => { + beforeEach(() => { + setTypeOnEcsDataWithAgentType(); + setAlertDetailsDataMockToEvent(); + }); + + test('should enable the "Add Endpoint event filter" button if provided endpoint event and has right privileges', async () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...mockInitialUserPrivilegesState(), + endpointPrivileges: { loading: false, canWriteEventFilters: true }, + }); + render(); + await waitFor(() => { + expect( + wrapper.find('[data-test-subj="add-event-filter-menu-item"]').last().getDOMNode() + ).toBeEnabled(); + }); + }); + + test('should hide the "Add Endpoint event filter" button if no write event filters privileges', async () => { + (useUserPrivileges as jest.Mock).mockReturnValue({ + ...mockInitialUserPrivilegesState(), + endpointPrivileges: { loading: false, canWriteEventFilters: false }, + }); + render(); + await waitFor(() => { + expect(wrapper.exists('[data-test-subj="add-event-filter-menu-item"]')).toBeFalsy(); + }); + }); + + test('should hide the "Add Endpoint event filter" button if provided no event from endpoint', async () => { + setAgentTypeOnAlertDetailsDataMock('filebeat'); + setTypeOnEcsDataWithAgentType('filebeat'); + render(); + await waitFor(() => { + expect(wrapper.exists('[data-test-subj="add-event-filter-menu-item"]')).toBeFalsy(); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.tsx new file mode 100644 index 0000000000000..dbc21e82220de --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/take_action_dropdown.tsx @@ -0,0 +1,400 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, useCallback, useMemo, useState } from 'react'; +import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui'; +import type { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { TableId } from '@kbn/securitysolution-data-table'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID } from '../test_ids'; +import { getAlertDetailsFieldValue } from '../../../../common/lib/endpoint/utils/get_event_details_field_values'; +import { GuidedOnboardingTourStep } from '../../../../common/components/guided_onboarding_tour/tour_step'; +import { + AlertsCasesTourSteps, + SecurityStepId, +} from '../../../../common/components/guided_onboarding_tour/tour_config'; +import { isActiveTimeline } from '../../../../helpers'; +import { useAlertExceptionActions } from '../../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'; +import { useAlertsActions } from '../../../../detections/components/alerts_table/timeline_actions/use_alerts_actions'; +import { useInvestigateInTimeline } from '../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'; +import { useEventFilterAction } from '../../../../detections/components/alerts_table/timeline_actions/use_event_filter_action'; +import { useResponderActionItem } from '../../../../common/components/endpoint/responder'; +import { useHostIsolationAction } from '../../../../common/components/endpoint/host_isolation'; +import type { Status } from '../../../../../common/api/detection_engine'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; +import { useAddToCaseActions } from '../../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions'; +import { useKibana } from '../../../../common/lib/kibana'; +import { getOsqueryActionItem } from '../../../../detections/components/osquery/osquery_action_item'; +import type { AlertTableContextMenuItem } from '../../../../detections/components/alerts_table/types'; +import { useAlertTagsActions } from '../../../../detections/components/alerts_table/timeline_actions/use_alert_tags_actions'; +import { useAlertAssigneesActions } from '../../../../detections/components/alerts_table/timeline_actions/use_alert_assignees_actions'; + +const TAKE_ACTION = i18n.translate('xpack.securitySolution.flyout.footer.takeActionButtonLabel', { + defaultMessage: 'Take action', +}); + +export interface AlertSummaryData { + /** + * Status of the alert (open, closed...) + */ + alertStatus: Status; + /** + * Id of the document + */ + eventId: string; + /** + * Type of document (event, signal, alert...) + */ + eventKind: string; + /** + * Id of the rule + */ + ruleId: string; + /** + * Name of the rule + */ + ruleName: string; +} + +export interface TakeActionDropdownProps { + /** + * An array of field objects with category and value + */ + dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + /** + * The actual raw document object + */ + dataAsNestedObject?: Ecs; + /** + * Callback called when the popover closes + */ + handleOnEventClosed: () => void; + /** + * Callback to let the parent know if the isolation panel is opened or closed + */ + isHostIsolationPanelOpen: boolean; + /** + * Callback to let parent know when the user interacts with the exception panel + */ + onAddIsolationStatusClick: (action: 'isolateHost' | 'unisolateHost') => void; + /** + * Callback to let parent know when the user interacts with event filter + */ + onAddEventFilterClick: () => void; + /** + * Callback to let parent know when the user interacts with the exception panel + */ + onAddExceptionTypeClick: (type?: ExceptionListTypeEnum) => void; + /** + * Callback to let parent know when the user interacts with the osquery panel + */ + onOsqueryClick: (id: string) => void; + /** + * Callback to refetch the data (from timeline query or global query) + */ + refetch: (() => void) | undefined; + /** + * Callback to refetch the document + */ + refetchFlyoutData: () => Promise<void>; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; +} + +/** + * Take action button with dropdown used to show all the options available to the user on a document rendered in the expandable flyout + */ +export const TakeActionDropdown = memo( + ({ + dataFormattedForFieldBrowser, + dataAsNestedObject, + handleOnEventClosed, + isHostIsolationPanelOpen, + onAddEventFilterClick, + onAddExceptionTypeClick, + onAddIsolationStatusClick, + refetch, + refetchFlyoutData, + onOsqueryClick, + scopeId, + }: TakeActionDropdownProps) => { + // popover interaction + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopoverHandler = useCallback(() => { + setIsPopoverOpen(!isPopoverOpen); + }, [isPopoverOpen]); + const closePopoverHandler = useCallback(() => { + setIsPopoverOpen(false); + }, []); + const onMenuItemClick = useCallback(() => { + closePopoverHandler(); + }, [closePopoverHandler]); + const closePopoverAndFlyout = useCallback(() => { + handleOnEventClosed(); + setIsPopoverOpen(false); + }, [handleOnEventClosed]); + + const alertSummaryData = useMemo( + () => + [ + { category: 'kibana', field: 'kibana.alert.rule.uuid', name: 'ruleId' }, + { category: 'kibana', field: 'kibana.alert.rule.name', name: 'ruleName' }, + { category: 'kibana', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, + { category: 'event', field: 'event.kind', name: 'eventKind' }, + { category: '_id', field: '_id', name: 'eventId' }, + ].reduce<AlertSummaryData>( + (acc, curr) => ({ + ...acc, + [curr.name]: getAlertDetailsFieldValue( + { category: curr.category, field: curr.field }, + dataFormattedForFieldBrowser + ), + }), + {} as AlertSummaryData + ), + [dataFormattedForFieldBrowser] + ); + + const isEvent = alertSummaryData.eventKind === 'event'; + + const isAgentEndpoint = useMemo( + () => dataAsNestedObject?.agent?.type?.includes('endpoint'), + [dataAsNestedObject] + ); + + // host isolation interaction + const handleOnAddIsolationStatusClick = useCallback( + (action: 'isolateHost' | 'unisolateHost') => { + onAddIsolationStatusClick(action); + setIsPopoverOpen(false); + }, + [onAddIsolationStatusClick] + ); + const hostIsolationActionItems = useHostIsolationAction({ + closePopover: closePopoverHandler, + detailsData: dataFormattedForFieldBrowser, + onAddIsolationStatusClick: handleOnAddIsolationStatusClick, + isHostIsolationPanelOpen, + }); + + // exception interaction + const handleOnAddExceptionTypeClick = useCallback( + (type?: ExceptionListTypeEnum) => { + onAddExceptionTypeClick(type); + setIsPopoverOpen(false); + }, + [onAddExceptionTypeClick] + ); + const { exceptionActionItems } = useAlertExceptionActions({ + isEndpointAlert: Boolean(isAgentEndpoint), + onAddExceptionTypeClick: handleOnAddExceptionTypeClick, + }); + + // event filter interaction + const handleOnAddEventFilterClick = useCallback(() => { + onAddEventFilterClick(); + setIsPopoverOpen(false); + }, [onAddEventFilterClick]); + const { eventFilterActionItems } = useEventFilterAction({ + onAddEventFilterClick: handleOnAddEventFilterClick, + }); + const { loading: endpointPrivilegesLoading, canWriteEventFilters } = + useUserPrivileges().endpointPrivileges; + const isEndpointEvent = useMemo(() => isEvent && isAgentEndpoint, [isEvent, isAgentEndpoint]); + const canCreateEndpointEventFilters = useMemo( + () => !endpointPrivilegesLoading && canWriteEventFilters, + [canWriteEventFilters, endpointPrivilegesLoading] + ); + + // alert status interaction + const { actionItems: statusActionItems } = useAlertsActions({ + alertStatus: alertSummaryData.alertStatus, + closePopover: closePopoverAndFlyout, + eventId: alertSummaryData.eventId, + refetch, + scopeId, + }); + + // alert tagging interation + const { alertTagsItems, alertTagsPanels } = useAlertTagsActions({ + closePopover: closePopoverHandler, + ecsRowData: dataAsNestedObject ?? { _id: alertSummaryData.eventId }, + refetch, + }); + + // assignee interaction + const onAssigneesUpdate = useCallback(() => { + if (refetch) { + refetch(); + } + if (refetchFlyoutData) { + refetchFlyoutData(); + } + }, [refetch, refetchFlyoutData]); + const { alertAssigneesItems, alertAssigneesPanels } = useAlertAssigneesActions({ + closePopover: closePopoverHandler, + ecsRowData: dataAsNestedObject ?? { _id: alertSummaryData.eventId }, + refetch: onAssigneesUpdate, + }); + + // timeline interaction + const { investigateInTimelineActionItems } = useInvestigateInTimeline({ + ecsRowData: dataAsNestedObject, + onInvestigateInTimelineAlertClick: closePopoverHandler, + }); + + // osquery interaction + const osqueryAgentId = useMemo( + () => + getAlertDetailsFieldValue( + { category: 'agent', field: 'agent.id' }, + dataFormattedForFieldBrowser + ), + [dataFormattedForFieldBrowser] + ); + const handleOnOsqueryClick = useCallback(() => { + onOsqueryClick(osqueryAgentId); + setIsPopoverOpen(false); + }, [onOsqueryClick, setIsPopoverOpen, osqueryAgentId]); + const osqueryActionItem = useMemo( + () => + getOsqueryActionItem({ + handleClick: handleOnOsqueryClick, + }), + [handleOnOsqueryClick] + ); + const { osquery } = useKibana().services; + const osqueryAvailable = osquery?.isOsqueryAvailable({ + agentId: osqueryAgentId, + }); + + // alert action items + const alertsActionItems = useMemo( + () => + !isEvent && alertSummaryData.ruleId + ? [ + ...statusActionItems, + ...alertTagsItems, + ...alertAssigneesItems, + ...exceptionActionItems, + ] + : isEndpointEvent && canCreateEndpointEventFilters + ? eventFilterActionItems + : [], + [ + eventFilterActionItems, + isEndpointEvent, + canCreateEndpointEventFilters, + exceptionActionItems, + statusActionItems, + isEvent, + alertSummaryData.ruleId, + alertTagsItems, + alertAssigneesItems, + ] + ); + + // cases interaction + const isInDetections = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage].includes( + scopeId as TableId + ); + const { addToCaseActionItems, handleAddToNewCaseClick } = useAddToCaseActions({ + ecsData: dataAsNestedObject, + nonEcsData: + dataFormattedForFieldBrowser?.map((d) => ({ field: d.field, value: d.values })) ?? [], + onMenuItemClick, + onSuccess: refetchFlyoutData, + isActiveTimelines: isActiveTimeline(scopeId), + isInDetections, + refetch, + }); + + // responder action items + const endpointResponseActionsConsoleItems = useResponderActionItem( + dataFormattedForFieldBrowser, + closePopoverHandler + ); + + // items to render in the dropdown + const items: AlertTableContextMenuItem[] = useMemo( + () => [ + ...addToCaseActionItems, + ...alertsActionItems, + ...hostIsolationActionItems, + ...endpointResponseActionsConsoleItems, + ...(osqueryAvailable ? [osqueryActionItem] : []), + ...investigateInTimelineActionItems, + ], + [ + addToCaseActionItems, + alertsActionItems, + hostIsolationActionItems, + endpointResponseActionsConsoleItems, + osqueryAvailable, + osqueryActionItem, + investigateInTimelineActionItems, + ] + ); + + // panels rendered in the context menu + const panels = [ + { + id: 0, + items, + }, + ...alertTagsPanels, + ...alertAssigneesPanels, + ]; + + const takeActionButton = useMemo( + () => ( + <GuidedOnboardingTourStep + onClick={handleAddToNewCaseClick} + step={AlertsCasesTourSteps.addAlertToCase} + tourId={SecurityStepId.alertsCases} + > + <EuiButton + data-test-subj={FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID} + fill + iconSide="right" + iconType="arrowDown" + onClick={togglePopoverHandler} + > + {TAKE_ACTION} + </EuiButton> + </GuidedOnboardingTourStep> + ), + + [handleAddToNewCaseClick, togglePopoverHandler] + ); + + return items.length && dataAsNestedObject ? ( + <EuiPopover + id="AlertTakeActionPanel" + button={takeActionButton} + isOpen={isPopoverOpen} + closePopover={closePopoverHandler} + panelPaddingSize="none" + anchorPosition="downLeft" + repositionOnScroll + > + <EuiContextMenu + size="s" + initialPanelId={0} + panels={panels} + data-test-subj="takeActionPanelMenu" + /> + </EuiPopover> + ) : null; + } +); + +TakeActionDropdown.displayName = 'TakeActionDropdown'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts index c68718fcd73dd..f2e4b529da8f1 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/test_ids.ts @@ -8,6 +8,14 @@ import { PREFIX } from '../../../shared/test_ids'; import { CONTENT_TEST_ID, HEADER_TEST_ID } from './expandable_section'; +/* Table */ + +const FLYOUT_TABLE_TEST_ID = `${PREFIX}Table` as const; +export const FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID = + `${FLYOUT_TABLE_TEST_ID}FieldNameCellIcon` as const; +export const FLYOUT_TABLE_FIELD_NAME_CELL_TEXT_TEST_ID = + `${FLYOUT_TABLE_TEST_ID}FieldNameCellText` as const; + /* Header */ const FLYOUT_HEADER_TEST_ID = `${PREFIX}Header` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.test.tsx new file mode 100644 index 0000000000000..9ece9b0e52495 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.test.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 from 'react'; +import { render } from '@testing-library/react'; +import { PanelFooter } from './footer'; +import { TestProviders } from '../../../common/mock'; +import { mockContextValue } from '../shared/mocks/mock_context'; +import { DocumentDetailsContext } from '../shared/context'; +import { FLYOUT_FOOTER_TEST_ID } from './test_ids'; +import { useKibana } from '../../../common/lib/kibana'; +import { useAlertExceptionActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'; +import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'; +import { useAddToCaseActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions'; + +jest.mock('../../../common/lib/kibana'); +jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions'); +jest.mock( + '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline' +); +jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions'); + +describe('PanelFooter', () => { + it('should not render the take action dropdown if preview mode', () => { + const { queryByTestId } = render( + <TestProviders> + <DocumentDetailsContext.Provider value={mockContextValue}> + <PanelFooter isPreview={true} /> + </DocumentDetailsContext.Provider> + </TestProviders> + ); + + expect(queryByTestId(FLYOUT_FOOTER_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render the take action dropdown', () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + osquery: { + isOsqueryAvailable: jest.fn(), + }, + }, + }); + (useAlertExceptionActions as jest.Mock).mockReturnValue({ exceptionActionItems: [] }); + (useInvestigateInTimeline as jest.Mock).mockReturnValue({ + investigateInTimelineActionItems: [], + }); + (useAddToCaseActions as jest.Mock).mockReturnValue({ addToCaseActionItems: [] }); + + const wrapper = render( + <TestProviders> + <DocumentDetailsContext.Provider value={mockContextValue}> + <PanelFooter isPreview={false} /> + </DocumentDetailsContext.Provider> + </TestProviders> + ); + expect(wrapper.getByTestId(FLYOUT_FOOTER_TEST_ID)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx index 100365b0bc6f9..be162a24dde20 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/footer.tsx @@ -6,21 +6,54 @@ */ import type { FC } from 'react'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import styled from 'styled-components'; import { euiThemeVars } from '@kbn/ui-theme'; -import { DocumentDetailsIsolateHostPanelKey } from '../shared/constants/panel_keys'; -import { FlyoutFooter } from '../../../timelines/components/side_panel/event_details/flyout'; +import { EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { find } from 'lodash/fp'; +import { FLYOUT_FOOTER_TEST_ID } from './test_ids'; +import type { Status } from '../../../../common/api/detection_engine'; +import { getAlertDetailsFieldValue } from '../../../common/lib/endpoint/utils/get_event_details_field_values'; +import { TakeActionDropdown } from './components/take_action_dropdown'; +import { AddExceptionFlyoutWrapper } from '../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; +import { EventFiltersFlyout } from '../../../management/pages/event_filters/view/components/event_filters_flyout'; +import { OsqueryFlyout } from '../../../detections/components/osquery/osquery_flyout'; import { useDocumentDetailsContext } from '../shared/context'; -import { useHostIsolationTools } from '../../../timelines/components/side_panel/event_details/use_host_isolation_tools'; +import { useHostIsolation } from '../shared/hooks/use_host_isolation'; +import { DocumentDetailsIsolateHostPanelKey } from '../shared/constants/panel_keys'; +import { useRefetchByScope } from './hooks/use_refetch_by_scope'; +import { useExceptionFlyout } from '../../../detections/components/alerts_table/timeline_actions/use_add_exception_flyout'; +import { isActiveTimeline } from '../../../helpers'; +import { useEventFilterModal } from '../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; -const ContainerDiv = styled('div')` - .side-panel-flyout-footer { - padding: ${euiThemeVars.euiPanelPaddingModifiers.paddingMedium}; - } +const StyledEuiFlyoutFooter = styled('div')` + padding: ${euiThemeVars.euiPanelPaddingModifiers.paddingMedium}; `; +interface AlertSummaryData { + /** + * Status of the alert (open, closed...) + */ + alertStatus: Status; + /** + * Id of the document + */ + eventId: string; + /** + * Id of the rule + */ + ruleId: string; + /** + * Property ruleId on the rule + */ + ruleRuleId: string; + /** + * Name of the rule + */ + ruleName: string; +} + interface PanelFooterProps { /** * Boolean that indicates whether flyout is in preview and action should be hidden @@ -29,9 +62,16 @@ interface PanelFooterProps { } /** - * + * Bottom section of the flyout that contains the take action button */ export const PanelFooter: FC<PanelFooterProps> = ({ isPreview }) => { + const { euiTheme } = useEuiTheme(); + // we need this flyout to be above the timeline flyout (which has a z-index of 1002) + const flyoutZIndex = useMemo( + () => ({ style: `z-index: ${(euiTheme.levels.flyout as number) + 3}` }), + [euiTheme] + ); + const { closeFlyout, openRightPanel } = useExpandableFlyoutApi(); const { eventId, @@ -41,8 +81,9 @@ export const PanelFooter: FC<PanelFooterProps> = ({ isPreview }) => { refetchFlyoutData, scopeId, } = useDocumentDetailsContext(); - const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolationTools(); + // host isolation interaction + const { isHostIsolationPanelOpen, showHostIsolationPanel } = useHostIsolation(); const showHostIsolationPanelCallback = useCallback( (action: 'isolateHost' | 'unisolateHost' | undefined) => { showHostIsolationPanel(action); @@ -59,19 +100,143 @@ export const PanelFooter: FC<PanelFooterProps> = ({ isPreview }) => { [eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel] ); - return !isPreview ? ( - <ContainerDiv> - <FlyoutFooter - detailsData={dataFormattedForFieldBrowser} - detailsEcsData={dataAsNestedObject} - handleOnEventClosed={closeFlyout} - isHostIsolationPanelOpen={isHostIsolationPanelOpen} - isReadOnly={false} - loadingEventDetails={false} - onAddIsolationStatusClick={showHostIsolationPanelCallback} - scopeId={scopeId} - refetchFlyoutData={refetchFlyoutData} - /> - </ContainerDiv> - ) : null; + const { refetch: refetchAll } = useRefetchByScope({ scopeId }); + + // exception interaction + const ruleIndexRaw = useMemo( + () => + find({ category: 'signal', field: 'signal.rule.index' }, dataFormattedForFieldBrowser) + ?.values ?? + find( + { category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, + dataFormattedForFieldBrowser + )?.values, + [dataFormattedForFieldBrowser] + ); + const ruleIndex = useMemo( + (): string[] | undefined => (Array.isArray(ruleIndexRaw) ? ruleIndexRaw : undefined), + [ruleIndexRaw] + ); + const ruleDataViewIdRaw = useMemo( + () => + find({ category: 'signal', field: 'signal.rule.data_view_id' }, dataFormattedForFieldBrowser) + ?.values ?? + find( + { category: 'kibana', field: 'kibana.alert.rule.parameters.data_view_id' }, + dataFormattedForFieldBrowser + )?.values, + [dataFormattedForFieldBrowser] + ); + const ruleDataViewId = useMemo( + (): string | undefined => (Array.isArray(ruleDataViewIdRaw) ? ruleDataViewIdRaw[0] : undefined), + [ruleDataViewIdRaw] + ); + const alertSummaryData = useMemo( + () => + [ + { category: 'signal', field: 'signal.rule.id', name: 'ruleId' }, + { category: 'signal', field: 'signal.rule.rule_id', name: 'ruleRuleId' }, + { category: 'signal', field: 'signal.rule.name', name: 'ruleName' }, + { category: 'signal', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, + { category: '_id', field: '_id', name: 'eventId' }, + ].reduce<AlertSummaryData>( + (acc, curr) => ({ + ...acc, + [curr.name]: getAlertDetailsFieldValue( + { category: curr.category, field: curr.field }, + dataFormattedForFieldBrowser + ), + }), + {} as AlertSummaryData + ), + [dataFormattedForFieldBrowser] + ); + const { + exceptionFlyoutType, + openAddExceptionFlyout, + onAddExceptionTypeClick, + onAddExceptionCancel, + onAddExceptionConfirm, + } = useExceptionFlyout({ + refetch: refetchAll, + isActiveTimelines: isActiveTimeline(scopeId), + }); + + // event filter interaction + const { closeAddEventFilterModal, isAddEventFilterModalOpen, onAddEventFilterClick } = + useEventFilterModal(); + + // osquery interaction + const [isOsqueryFlyoutOpenWithAgentId, setOsqueryFlyoutOpenWithAgentId] = useState<null | string>( + null + ); + const closeOsqueryFlyout = useCallback(() => { + setOsqueryFlyoutOpenWithAgentId(null); + }, [setOsqueryFlyoutOpenWithAgentId]); + const alertId = useMemo( + () => (dataAsNestedObject?.kibana?.alert ? dataAsNestedObject?._id : null), + [dataAsNestedObject?._id, dataAsNestedObject?.kibana?.alert] + ); + + if (isPreview) return null; + + return ( + <> + <StyledEuiFlyoutFooter data-test-subj={FLYOUT_FOOTER_TEST_ID}> + <EuiFlexGroup justifyContent="flexEnd"> + <EuiFlexItem grow={false}> + {dataAsNestedObject && ( + <TakeActionDropdown + dataFormattedForFieldBrowser={dataFormattedForFieldBrowser} + dataAsNestedObject={dataAsNestedObject} + handleOnEventClosed={closeFlyout} + isHostIsolationPanelOpen={isHostIsolationPanelOpen} + onAddEventFilterClick={onAddEventFilterClick} + onAddExceptionTypeClick={onAddExceptionTypeClick} + onAddIsolationStatusClick={showHostIsolationPanelCallback} + refetchFlyoutData={refetchFlyoutData} + refetch={refetchAll} + scopeId={scopeId} + onOsqueryClick={setOsqueryFlyoutOpenWithAgentId} + /> + )} + </EuiFlexItem> + </EuiFlexGroup> + </StyledEuiFlyoutFooter> + + {openAddExceptionFlyout && + alertSummaryData.ruleId != null && + alertSummaryData.ruleRuleId != null && + alertSummaryData.eventId != null && ( + <AddExceptionFlyoutWrapper + {...alertSummaryData} + ruleIndices={ruleIndex} + ruleDataViewId={ruleDataViewId} + exceptionListType={exceptionFlyoutType} + onCancel={onAddExceptionCancel} + onConfirm={onAddExceptionConfirm} + /> + )} + + {isAddEventFilterModalOpen && dataAsNestedObject != null && ( + <EventFiltersFlyout + data={dataAsNestedObject} + onCancel={closeAddEventFilterModal} + // EUI TODO: This z-index override of EuiOverlayMask is a workaround, and ideally should be resolved with a cleaner UI/UX flow long-term + maskProps={flyoutZIndex} // we need this flyout to be above the timeline flyout (which has a z-index of 1002) + /> + )} + + {isOsqueryFlyoutOpenWithAgentId && dataAsNestedObject != null && ( + <OsqueryFlyout + agentId={isOsqueryFlyoutOpenWithAgentId} + defaultValues={alertId ? { alertIds: [alertId] } : undefined} + onClose={closeOsqueryFlyout} + ecsData={dataAsNestedObject} + /> + )} + </> + ); }; + +PanelFooter.displayName = 'PanelFooter'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx index 8c7dac4829d1a..e778552dff613 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.test.tsx @@ -12,9 +12,9 @@ import type { UseThreatIntelligenceResult, } from './use_fetch_threat_intelligence'; import { useFetchThreatIntelligence } from './use_fetch_threat_intelligence'; -import { useInvestigationTimeEnrichment } from '../../../../common/containers/cti/event_enrichment'; +import { useInvestigationTimeEnrichment } from '../../shared/hooks/use_investigation_enrichment'; -jest.mock('../../../../common/containers/cti/event_enrichment'); +jest.mock('../../shared/hooks/use_investigation_enrichment'); const dataFormattedForFieldBrowser = [ { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.ts index 133fd43392c8a..ac59f6c802a87 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_fetch_threat_intelligence.ts @@ -15,8 +15,8 @@ import { getEnrichmentFields, parseExistingEnrichments, timelineDataToEnrichment, -} from '../../../../common/components/event_details/cti_details/helpers'; -import { useInvestigationTimeEnrichment } from '../../../../common/containers/cti/event_enrichment'; +} from '../../shared/utils/threat_intelligence'; +import { useInvestigationTimeEnrichment } from '../../shared/hooks/use_investigation_enrichment'; import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; export interface UseThreatIntelligenceParams { @@ -76,7 +76,7 @@ export const useFetchThreatIntelligence = ({ ); // api call to retrieve all documents that match the eventFields - const { result: response, loading } = useInvestigationTimeEnrichment(eventFields); + const { result: response, loading } = useInvestigationTimeEnrichment({ eventFields }); // combine existing enrichment and enrichment from the api response // also removes the investigation-time enrichments if the exact indicator already exists diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_refetch_by_scope.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_refetch_by_scope.tsx similarity index 78% rename from x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_refetch_by_scope.tsx rename to x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_refetch_by_scope.tsx index 1c348173a93df..fe5be6569f875 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_refetch_by_scope.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/hooks/use_refetch_by_scope.tsx @@ -6,10 +6,10 @@ */ import { useCallback, useMemo } from 'react'; -import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; -import { isActiveTimeline } from '../../../../../helpers'; -import type { inputsModel } from '../../../../../common/store'; -import { inputsSelectors } from '../../../../../common/store'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import type { inputsModel } from '../../../../common/store'; +import { inputsSelectors } from '../../../../common/store'; +import { isActiveTimeline } from '../../../../helpers'; export interface UseRefetchScopeQueryParams { /** @@ -21,7 +21,6 @@ export interface UseRefetchScopeQueryParams { /** * Hook to refetch data within specified scope */ -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 export const useRefetchByScope = ({ scopeId }: UseRefetchScopeQueryParams) => { const getGlobalQueries = useMemo(() => inputsSelectors.globalQuery(), []); const getTimelineQuery = useMemo(() => inputsSelectors.timelineQueryByIdSelector(), []); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.test.tsx index 0f6f3046ee3af..090b4838daa4e 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.test.tsx @@ -7,10 +7,13 @@ import React from 'react'; import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { DocumentDetailsContext } from '../../shared/context'; -import { TABLE_TAB_CONTENT_TEST_ID } from './test_ids'; +import { TABLE_TAB_CONTENT_TEST_ID, TABLE_TAB_SEARCH_INPUT_TEST_ID } from './test_ids'; import { TableTab } from './table_tab'; import { TestProviders } from '../../../../common/mock'; +import { mockContextValue } from '../../shared/mocks/mock_context'; +import { FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID } from '../components/test_ids'; const mockDispatch = jest.fn(); jest.mock('react-redux', () => { @@ -40,4 +43,35 @@ describe('<TableTab />', () => { expect(getByTestId(TABLE_TAB_CONTENT_TEST_ID)).toBeInTheDocument(); }); + + it('should renders the column headers and a field/value pair', () => { + const { getAllByTestId, getByText } = render( + <TestProviders> + <DocumentDetailsContext.Provider value={mockContextValue}> + <TableTab /> + </DocumentDetailsContext.Provider> + </TestProviders> + ); + + expect(getByText('Field')).toBeInTheDocument(); + expect(getByText('Value')).toBeInTheDocument(); + expect(getByText('kibana.alert.workflow_status')).toBeInTheDocument(); + expect(getByText('open')).toBeInTheDocument(); + expect(getAllByTestId(FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID).length).toBeGreaterThan(0); + }); + + it('should filter the table correctly', () => { + const { getByTestId, queryByTestId, queryByText } = render( + <TestProviders> + <DocumentDetailsContext.Provider value={mockContextValue}> + <TableTab /> + </DocumentDetailsContext.Provider> + </TestProviders> + ); + + userEvent.type(getByTestId(TABLE_TAB_SEARCH_INPUT_TEST_ID), 'test'); + expect(queryByText('kibana.alert.workflow_status')).not.toBeInTheDocument(); + expect(queryByText('open')).not.toBeInTheDocument(); + expect(queryByTestId(FLYOUT_TABLE_FIELD_NAME_CELL_ICON_TEST_ID)).not.toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.tsx index e47d46fcff1e6..181ba46a67e2a 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/table_tab.tsx @@ -5,46 +5,101 @@ * 2.0. */ -import React, { memo } from 'react'; -import { EuiText } from '@elastic/eui'; -import { getFieldFromBrowserField } from '../../../../common/components/event_details/columns'; +import React, { memo, useCallback, useMemo, useState } from 'react'; +import { getOr, sortBy } from 'lodash/fp'; +import memoizeOne from 'memoize-one'; +import { css } from '@emotion/react'; +import { type EuiBasicTableColumn, EuiText, EuiInMemoryTable, useEuiFontSize } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { dataTableSelectors, tableDefaults } from '@kbn/securitysolution-data-table'; +import { getCategory } from '@kbn/triggers-actions-ui-plugin/public'; +import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { FieldSpec } from '@kbn/data-plugin/common'; +import { TableFieldNameCell } from '../components/table_field_name_cell'; +import { TableFieldValueCell } from '../components/table_field_value_cell'; +import { TABLE_TAB_CONTENT_TEST_ID, TABLE_TAB_SEARCH_INPUT_TEST_ID } from './test_ids'; +import { getAllFieldsByName } from '../../../../common/containers/source'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { timelineDefaults } from '../../../../timelines/store/defaults'; +import { timelineSelectors } from '../../../../timelines/store'; import type { EventFieldsData } from '../../../../common/components/event_details/types'; -import { FieldValueCell } from '../../../../common/components/event_details/table/field_value_cell'; -import { FieldNameCell } from '../../../../common/components/event_details/table/field_name_cell'; import { CellActions } from '../components/cell_actions'; -import * as i18n from '../../../../common/components/event_details/translations'; import { useDocumentDetailsContext } from '../../shared/context'; -import type { ColumnsProvider } from '../../../../common/components/event_details/event_fields_browser'; -import { EventFieldsBrowser } from '../../../../common/components/event_details/event_fields_browser'; -import { TimelineTabs } from '../../../../../common/types'; - -export const getColumns: ColumnsProvider = ({ - browserFields, - eventId, - contextId, - scopeId, - getLinkValue, - isDraggable, -}) => [ +import { isInTableScope, isTimelineScope } from '../../../../helpers'; + +const COUNT_PER_PAGE_OPTIONS = [25, 50, 100]; + +const PLACEHOLDER = i18n.translate('xpack.securitySolution.flyout.table.filterPlaceholderLabel', { + defaultMessage: 'Filter by field or value...', +}); +export const FIELD = i18n.translate('xpack.securitySolution.flyout.table.fieldCellLabel', { + defaultMessage: 'Field', +}); +const VALUE = i18n.translate('xpack.securitySolution.flyout.table.valueCellLabel', { + defaultMessage: 'Value', +}); + +/** + * Defines the behavior of the search input that appears above the table of data + */ +const search = { + box: { + incremental: true, + placeholder: PLACEHOLDER, + schema: true, + 'data-test-subj': TABLE_TAB_SEARCH_INPUT_TEST_ID, + }, +}; + +/** + * Retrieve the correct field from the BrowserField + */ +export const getFieldFromBrowserField = memoizeOne( + (field: string, browserFields: BrowserFields): FieldSpec | undefined => { + const category = getCategory(field); + + return browserFields[category]?.fields?.[field] as FieldSpec; + }, + (newArgs, lastArgs) => newArgs[0] === lastArgs[0] +); + +export type ColumnsProvider = (providerOptions: { + /** + * An object containing fields by type + */ + browserFields: BrowserFields; + /** + * Id of the document + */ + eventId: string; + /** + * Maintain backwards compatibility // TODO remove when possible + */ + scopeId: string; + /** + * Value of the link field if it exists. Allows to navigate to other pages like host, user, network... + */ + getLinkValue: (field: string) => string | null; +}) => Array<EuiBasicTableColumn<TimelineEventsDetailsItem>>; + +export const getColumns: ColumnsProvider = ({ browserFields, eventId, scopeId, getLinkValue }) => [ { field: 'field', name: ( <EuiText size="xs"> - <strong>{i18n.FIELD}</strong> + <strong>{FIELD}</strong> </EuiText> ), width: '30%', render: (field, data) => { - return ( - <FieldNameCell data={data as EventFieldsData} field={field} fieldMapping={undefined} /> - ); + return <TableFieldNameCell dataType={(data as EventFieldsData).type} field={field} />; }, }, { field: 'values', name: ( <EuiText size="xs"> - <strong>{i18n.VALUE}</strong> + <strong>{VALUE}</strong> </EuiText> ), width: '70%', @@ -52,13 +107,12 @@ export const getColumns: ColumnsProvider = ({ const fieldFromBrowserField = getFieldFromBrowserField(data.field, browserFields); return ( <CellActions field={data.field} value={values} isObjectArray={data.isObjectArray}> - <FieldValueCell - contextId={contextId} + <TableFieldValueCell + contextId={scopeId} data={data as EventFieldsData} eventId={eventId} fieldFromBrowserField={fieldFromBrowserField} getLinkValue={getLinkValue} - isDraggable={isDraggable} values={values} /> </CellActions> @@ -68,23 +122,106 @@ export const getColumns: ColumnsProvider = ({ ]; /** - * Table view displayed in the document details expandable flyout right section + * Table view displayed in the document details expandable flyout right section Table tab */ -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 export const TableTab = memo(() => { + const smallFontSize = useEuiFontSize('xs').fontSize; + const { browserFields, dataFormattedForFieldBrowser, eventId, scopeId } = useDocumentDetailsContext(); + const [pagination, setPagination] = useState<{ pageIndex: number }>({ + pageIndex: 0, + }); + const onTableChange = useCallback(({ page: { index } }: { page: { index: number } }) => { + setPagination({ pageIndex: index }); + }, []); + + const getScope = useMemo(() => { + if (isTimelineScope(scopeId)) { + return timelineSelectors.getTimelineByIdSelector(); + } else if (isInTableScope(scopeId)) { + return dataTableSelectors.getTableByIdSelector(); + } + }, [scopeId]); + + const defaults = useMemo( + () => (isTimelineScope(scopeId) ? timelineDefaults : tableDefaults), + [scopeId] + ); + + const columnHeaders = useDeepEqualSelector((state) => { + const { columns } = (getScope && getScope(state, scopeId)) ?? defaults; + return columns; + }); + + const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); + + const items = useMemo( + () => + sortBy(['field'], dataFormattedForFieldBrowser).map((item, i) => ({ + ...item, + ...fieldsByName[item.field], + valuesConcatenated: item.values != null ? item.values.join() : '', + ariaRowindex: i + 1, + })), + [dataFormattedForFieldBrowser, fieldsByName] + ); + + const getLinkValue = useCallback( + (field: string) => { + const columnHeader = columnHeaders.find((col) => col.id === field); + if (!columnHeader || !columnHeader.linkField) { + return null; + } + const linkFieldData = (dataFormattedForFieldBrowser ?? []).find( + (d) => d.field === columnHeader.linkField + ); + const linkFieldValue = getOr(null, 'originalValue', linkFieldData); + return Array.isArray(linkFieldValue) ? linkFieldValue[0] : linkFieldValue; + }, + [dataFormattedForFieldBrowser, columnHeaders] + ); + + // forces the rows of the table to render smaller fonts + const onSetRowProps = useCallback( + ({ field }: TimelineEventsDetailsItem) => ({ + className: 'flyout-table-row-small-font', + 'data-test-subj': `flyout-table-row-${field}`, + }), + [] + ); + + const columns = useMemo( + () => + getColumns({ + browserFields, + eventId, + scopeId, + getLinkValue, + }), + [browserFields, eventId, scopeId, getLinkValue] + ); + return ( - <EventFieldsBrowser - browserFields={browserFields} - data={dataFormattedForFieldBrowser} - eventId={eventId} - isDraggable={false} - timelineTabType={TimelineTabs.query} - scopeId={scopeId} - isReadOnly={false} - columnsProvider={getColumns} + <EuiInMemoryTable + items={items} + itemId="field" + columns={columns} + onTableChange={onTableChange} + pagination={{ + ...pagination, + pageSizeOptions: COUNT_PER_PAGE_OPTIONS, + }} + rowProps={onSetRowProps} + search={search} + sorting={false} + data-test-subj={TABLE_TAB_CONTENT_TEST_ID} + css={css` + .euiTableRow { + font-size: ${smallFontSize}; + } + `} /> ); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/test_ids.ts index 6fa34f66265a9..ca894c99b5ffe 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/tabs/test_ids.ts @@ -7,6 +7,7 @@ import { PREFIX } from '../../../shared/test_ids'; -export const TABLE_TAB_CONTENT_TEST_ID = 'event-fields-browser' as const; +export const TABLE_TAB_CONTENT_TEST_ID = `${PREFIX}DocumentTable` as const; +export const TABLE_TAB_SEARCH_INPUT_TEST_ID = `${PREFIX}DocumentTableSearchInput` as const; export const JSON_TAB_CONTENT_TEST_ID = 'jsonView' as const; export const JSON_TAB_COPY_TO_CLIPBOARD_BUTTON_TEST_ID = `${PREFIX}JsonTabCopyToClipboard` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts index b0c823542f5c5..54199abf55de8 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/test_ids.ts @@ -8,6 +8,9 @@ import { PREFIX } from '../../shared/test_ids'; export const FLYOUT_BODY_TEST_ID = `${PREFIX}Body` as const; +export const FLYOUT_FOOTER_TEST_ID = `${PREFIX}Footer` as const; +export const FLYOUT_FOOTER_DEOPDOEN_BUTTON_TEST_ID = + `${FLYOUT_FOOTER_TEST_ID}DropdownButton` as const; export const OVERVIEW_TAB_TEST_ID = `${PREFIX}OverviewTab` as const; export const OVERVIEW_TAB_LABEL_TEST_ID = `${PREFIX}OverviewTabLabel` as const; export const TABLE_TAB_TEST_ID = `${PREFIX}TableTab` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_host_isolation.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_host_isolation.tsx new file mode 100644 index 0000000000000..d4882572157b5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_host_isolation.tsx @@ -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 { useCallback, useMemo, useReducer } from 'react'; +import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint'; + +interface State { + isHostIsolationPanelOpen: boolean; + isIsolateActionSuccessBannerVisible: boolean; +} + +const initialState: State = { + isHostIsolationPanelOpen: false, + isIsolateActionSuccessBannerVisible: false, +}; + +type HostIsolationActions = + | { + type: 'setIsHostIsolationPanel'; + isHostIsolationPanelOpen: boolean; + } + | { + type: 'setIsIsolateActionSuccessBannerVisible'; + isIsolateActionSuccessBannerVisible: boolean; + }; + +function reducer(state: State, action: HostIsolationActions) { + switch (action.type) { + case 'setIsHostIsolationPanel': + return { ...state, isHostIsolationPanelOpen: action.isHostIsolationPanelOpen }; + case 'setIsIsolateActionSuccessBannerVisible': + return { + ...state, + isIsolateActionSuccessBannerVisible: action.isIsolateActionSuccessBannerVisible, + }; + default: + throw new Error(); + } +} + +export interface UseHostIsolationResult { + /** + * True if the host isolation panel is open in the flyout + */ + isHostIsolationPanelOpen: boolean; + /** + * True if the isolate action was successful and the banner should be displayed + */ + isIsolateActionSuccessBannerVisible: boolean; + /** + * Callback to handle the success of the isolation action + */ + handleIsolationActionSuccess: () => void; + /** + * Callback to show the host isolation panel in the flyout + */ + showHostIsolationPanel: (action: 'isolateHost' | 'unisolateHost' | undefined) => void; +} + +/** + * Hook that returns the information for a parent to render the host isolation panel in the flyout + */ +export const useHostIsolation = (): UseHostIsolationResult => { + const [{ isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible }, dispatch] = useReducer( + reducer, + initialState + ); + + const showHostIsolationPanel = useCallback( + (action: 'isolateHost' | 'unisolateHost' | undefined) => { + if (action === 'isolateHost' || action === 'unisolateHost') { + dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: true }); + } + }, + [] + ); + + const caseDetailsRefresh = useWithCaseDetailsRefresh(); + + const handleIsolationActionSuccess = useCallback(() => { + dispatch({ + type: 'setIsIsolateActionSuccessBannerVisible', + isIsolateActionSuccessBannerVisible: true, + }); + + // If a case details refresh ref is defined, then refresh actions and comments + if (caseDetailsRefresh) { + caseDetailsRefresh.refreshCase(); + } + }, [caseDetailsRefresh]); + + return useMemo( + () => ({ + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + handleIsolationActionSuccess, + showHostIsolationPanel, + }), + [ + isHostIsolationPanelOpen, + isIsolateActionSuccessBannerVisible, + handleIsolationActionSuccess, + showHostIsolationPanel, + ] + ); +}; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.test.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.test.ts new file mode 100644 index 0000000000000..0e1cdbc845b38 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useInvestigationTimeEnrichment } from './use_investigation_enrichment'; +import { + DEFAULT_EVENT_ENRICHMENT_FROM, + DEFAULT_EVENT_ENRICHMENT_TO, +} from '../../../../../common/cti/constants'; +import { useEventEnrichmentComplete } from '../services/threat_intelligence'; + +jest.mock('../services/threat_intelligence'); +jest.mock('react-redux', () => { + const original = jest.requireActual('react-redux'); + return { + ...original, + useDispatch: () => jest.fn(), + }; +}); +jest.mock('../../../../common/hooks/use_app_toasts', () => ({ + useAppToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + }), +})); +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(), + }), + }), + }, + }, + uiSettings: { + get: jest.fn().mockReturnValue(''), + }, + }, + }), + }; +}); + +describe('useInvestigationTimeEnrichment', () => { + it('should return default range', () => { + (useEventEnrichmentComplete as jest.Mock).mockReturnValue({}); + + const { result } = renderHook(() => + useInvestigationTimeEnrichment({ + eventFields: {}, + }) + ); + + expect(result.current.range).toEqual({ + from: DEFAULT_EVENT_ENRICHMENT_FROM, + to: DEFAULT_EVENT_ENRICHMENT_TO, + }); + expect(typeof result.current.setRange).toBe('function'); + }); + + it('should return loading', () => { + (useEventEnrichmentComplete as jest.Mock).mockReturnValue({ + error: null, + result: undefined, + loading: true, + }); + + const { result } = renderHook(() => + useInvestigationTimeEnrichment({ + eventFields: {}, + }) + ); + + expect(result.current.loading).toEqual(true); + }); + + it('should return no enrichments', () => { + (useEventEnrichmentComplete as jest.Mock).mockReturnValue({}); + + const { result } = renderHook(() => + useInvestigationTimeEnrichment({ + eventFields: {}, + }) + ); + + expect(result.current.result).toEqual({ enrichments: [] }); + }); + + it('should return enrichments and loading false', () => { + (useEventEnrichmentComplete as jest.Mock).mockReturnValue({ + error: null, + result: { + enrichments: [{}], + inspect: { dsl: [] }, + totalCount: 0, + }, + loading: false, + start: jest.fn(), + }); + + const { result } = renderHook(() => + useInvestigationTimeEnrichment({ + eventFields: { test: 'test' }, + }) + ); + + expect(result.current.result).toEqual({ + enrichments: [{}], + inspect: { dsl: [] }, + totalCount: 0, + }); + expect(result.current.loading).toEqual(false); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.ts new file mode 100644 index 0000000000000..34cabc044f469 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_investigation_enrichment.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 { useCallback, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { isEmpty, isEqual } from 'lodash'; +import usePrevious from 'react-use/lib/usePrevious'; +import { i18n } from '@kbn/i18n'; +import { useEventEnrichmentComplete } from '../services/threat_intelligence'; +import type { + CtiEventEnrichmentStrategyResponse, + EventFields, +} from '../../../../../common/search_strategy'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; +import { + DEFAULT_EVENT_ENRICHMENT_FROM, + DEFAULT_EVENT_ENRICHMENT_TO, +} from '../../../../../common/cti/constants'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useKibana } from '../../../../common/lib/kibana'; +import { inputsActions } from '../../../../common/store/actions'; +import { DEFAULT_THREAT_INDEX_KEY } from '../../../../../common/constants'; + +const INVESTIGATION_ENRICHMENT_REQUEST_ERROR = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.requestError', + { + defaultMessage: `An error occurred while requesting threat intelligence`, + } +); + +export const QUERY_ID = 'investigation_time_enrichment'; +const noop = () => {}; +const noEnrichments = { enrichments: [] }; + +export interface UseInvestigationTimeEnrichmentProps { + /** + * The event fields to fetch enrichment for + */ + eventFields: EventFields; +} + +export interface UseInvestigationTimeEnrichmentResult { + /** + * The result of the enrichment + */ + result: CtiEventEnrichmentStrategyResponse | undefined | typeof noEnrichments; + /** + * The range of the query + */ + range: { from: string; to: string }; + /** + * Function to set the range of the query + */ + setRange: (range: { from: string; to: string }) => void; + /** + * Whether the enrichment is loading + */ + loading: boolean; +} + +/** + * Hook to fetch the enrichment for a set of event fields. + * Holds the range of the query. + * Returns the result of the enrichment, the range of the query, the function to set it and the loading state. + */ +export const useInvestigationTimeEnrichment = ({ + eventFields, +}: UseInvestigationTimeEnrichmentProps): UseInvestigationTimeEnrichmentResult => { + const { addError } = useAppToasts(); + const { data, uiSettings } = useKibana().services; + const defaultThreatIndices = uiSettings.get<string[]>(DEFAULT_THREAT_INDEX_KEY); + + const dispatch = useDispatch(); + + const [range, setRange] = useState({ + from: DEFAULT_EVENT_ENRICHMENT_FROM, + to: DEFAULT_EVENT_ENRICHMENT_TO, + }); + + const { error, loading, result, start } = useEventEnrichmentComplete(); + + const deleteQuery = useCallback(() => { + dispatch(inputsActions.deleteOneQuery({ inputId: InputsModelId.global, id: QUERY_ID })); + }, [dispatch]); + + useEffect(() => { + if (!loading && result) { + dispatch( + inputsActions.setQuery({ + inputId: InputsModelId.global, + id: QUERY_ID, + inspect: { + dsl: result.inspect.dsl, + response: [JSON.stringify(result.rawResponse, null, 2)], + }, + loading, + refetch: noop, + }) + ); + } + + return deleteQuery; + }, [deleteQuery, dispatch, loading, result]); + + useEffect(() => { + if (error) { + addError(error, { title: INVESTIGATION_ENRICHMENT_REQUEST_ERROR }); + } + }, [addError, error]); + + const prevEventFields = usePrevious(eventFields); + const prevRange = usePrevious(range); + + useEffect(() => { + if ( + !isEmpty(eventFields) && + (!isEqual(eventFields, prevEventFields) || !isEqual(range, prevRange)) + ) { + start({ + data, + timerange: { ...range, interval: '' }, + defaultIndex: defaultThreatIndices, + eventFields, + filterQuery: '', + }); + } + }, [start, data, eventFields, prevEventFields, range, prevRange, defaultThreatIndices]); + + return { + result: isEmpty(eventFields) ? noEnrichments : result, + range, + setRange, + loading, + }; +}; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/services/threat_intelligence.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/services/threat_intelligence.ts new file mode 100644 index 0000000000000..db51e0505917b --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/services/threat_intelligence.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 type { Observable } from 'rxjs'; +import { filter } from 'rxjs'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { isRunningResponse } from '@kbn/data-plugin/common'; +import { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; +import { CtiQueries } from '../../../../../common/api/search_strategy'; +import type { CtiEventEnrichmentStrategyResponse } from '../../../../../common/search_strategy'; +import type { EventEnrichmentRequestOptionsInput } from '../../../../../common/api/search_strategy'; + +type GetEventEnrichmentProps = Omit<EventEnrichmentRequestOptionsInput, 'factoryQueryType'> & { + /** + * The data plugin start + */ + data: DataPublicPluginStart; + /** + * An `AbortSignal` that allows the caller of `search` to abort a search request. + */ + signal: AbortSignal; +}; + +/** + * API call to retrieve the enrichments for a set of fields + */ +const getEventEnrichment = ({ + data, + defaultIndex, + eventFields, + filterQuery, + timerange, + signal, +}: GetEventEnrichmentProps): Observable<CtiEventEnrichmentStrategyResponse> => + data.search.search<EventEnrichmentRequestOptionsInput, CtiEventEnrichmentStrategyResponse>( + { + defaultIndex, + eventFields, + factoryQueryType: CtiQueries.eventEnrichment, + filterQuery, + timerange, + }, + { + strategy: 'securitySolutionSearchStrategy', + abortSignal: signal, + } + ); + +/** + * Returns the enrichments for a set of fields, excluding the running response + */ +const getEventEnrichmentComplete = ( + props: GetEventEnrichmentProps +): Observable<CtiEventEnrichmentStrategyResponse> => + getEventEnrichment(props).pipe(filter((response) => !isRunningResponse(response))); + +export const useEventEnrichmentComplete = () => + useObservable(withOptionalSignal(getEventEnrichmentComplete)); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.test.tsx new file mode 100644 index 0000000000000..fba86d76cedd7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.test.tsx @@ -0,0 +1,625 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ENRICHMENT_TYPES } from '../../../../../common/cti/constants'; +import { buildEventEnrichmentMock } from '../../../../../common/search_strategy/security_solution/cti/index.mock'; +import { + filterDuplicateEnrichments, + getEnrichmentFields, + parseExistingEnrichments, + getEnrichmentIdentifiers, + buildThreatDetailsItems, +} from './threat_intelligence'; + +describe('parseExistingEnrichments', () => { + it('returns an empty array if data is empty', () => { + expect(parseExistingEnrichments([])).toEqual([]); + }); + + it('returns an empty array if data contains no enrichment field', () => { + const data = [ + { + category: 'host', + field: 'host.os.name.text', + isObjectArray: false, + originalValue: ['Mac OS X'], + values: ['Mac OS X'], + }, + ]; + expect(parseExistingEnrichments(data)).toEqual([]); + }); + + it('returns an empty array if enrichment field contains invalid JSON', () => { + const data = [ + { + category: 'threat', + field: 'threat.enrichments', + isObjectArray: true, + originalValue: ['whoops'], + values: ['whoops'], + }, + ]; + expect(parseExistingEnrichments(data)).toEqual([]); + }); + + it('returns an array if enrichment field contains valid JSON', () => { + const data = [ + { + category: 'threat', + field: 'threat.enrichments', + isObjectArray: true, + originalValue: [ + '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', + ], + values: [ + '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', + ], + }, + ]; + + expect(parseExistingEnrichments(data)).toEqual([ + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['matched_field', 'other_matched_field'], + values: ['matched_field', 'other_matched_field'], + }, + { + category: 'indicator', + field: 'indicator.first_seen', + isObjectArray: false, + originalValue: ['2021-02-22T17:29:25.195Z'], + values: ['2021-02-22T17:29:25.195Z'], + }, + { + category: 'indicator', + field: 'indicator.provider', + isObjectArray: false, + originalValue: ['yourself'], + values: ['yourself'], + }, + { + category: 'indicator', + field: 'indicator.type', + isObjectArray: false, + originalValue: ['custom'], + values: ['custom'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['matched_atomic'], + values: ['matched_atomic'], + }, + { + category: 'lazer', + field: 'lazer', + isObjectArray: true, + originalValue: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], + values: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], + }, + ], + ]); + }); + + it('returns multiple arrays for multiple enrichments', () => { + const data = [ + { + category: 'threat', + field: 'threat.enrichments', + isObjectArray: true, + originalValue: [ + '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', + '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + ], + values: [ + '{"matched.field":["matched_field","other_matched_field"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["yourself"],"indicator.type":["custom"],"matched.atomic":["matched_atomic"],"lazer":[{"great.field":["grrrrr"]},{"great.field":["grrrrr_2"]}]}', + '{"matched.field":["matched_field_2"],"indicator.first_seen":["2021-02-22T17:29:25.195Z"],"indicator.provider":["other_you"],"indicator.type":["custom"],"matched.atomic":["matched_atomic_2"],"lazer":[{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["FFEtSYIBZ61VHL7LvV2j"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.architecture"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["x86_64"]}', + '{"matched.field":["host.name"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["E1EtSYIBZ61VHL7Ltl3m"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + '{"matched.field":["host.hostname"],"matched.index":["im"],"matched.type":["indicator_match_rule"],"matched.id":["CFErSYIBZ61VHL7LIV1N"],"matched.atomic":["MacBook-Pro-de-Gloria.local"]}', + ], + }, + ]; + + expect(parseExistingEnrichments(data)).toEqual([ + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['matched_field', 'other_matched_field'], + values: ['matched_field', 'other_matched_field'], + }, + { + category: 'indicator', + field: 'indicator.first_seen', + isObjectArray: false, + originalValue: ['2021-02-22T17:29:25.195Z'], + values: ['2021-02-22T17:29:25.195Z'], + }, + { + category: 'indicator', + field: 'indicator.provider', + isObjectArray: false, + originalValue: ['yourself'], + values: ['yourself'], + }, + { + category: 'indicator', + field: 'indicator.type', + isObjectArray: false, + originalValue: ['custom'], + values: ['custom'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['matched_atomic'], + values: ['matched_atomic'], + }, + { + category: 'lazer', + field: 'lazer', + isObjectArray: true, + originalValue: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], + values: ['{"great.field":["grrrrr"]}', '{"great.field":["grrrrr_2"]}'], + }, + ], + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['matched_field_2'], + values: ['matched_field_2'], + }, + { + category: 'indicator', + field: 'indicator.first_seen', + isObjectArray: false, + originalValue: ['2021-02-22T17:29:25.195Z'], + values: ['2021-02-22T17:29:25.195Z'], + }, + { + category: 'indicator', + field: 'indicator.provider', + isObjectArray: false, + originalValue: ['other_you'], + values: ['other_you'], + }, + { + category: 'indicator', + field: 'indicator.type', + isObjectArray: false, + originalValue: ['custom'], + values: ['custom'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['matched_atomic_2'], + values: ['matched_atomic_2'], + }, + { + category: 'lazer', + field: 'lazer', + isObjectArray: true, + originalValue: [ + '{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}', + ], + values: [ + '{"great.field":[{"wowoe":[{"fooooo":["grrrrr"]}],"astring":"cool","aNumber":1,"neat":true}]}', + ], + }, + ], + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.name'], + values: ['host.name'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['im'], + values: ['im'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['indicator_match_rule'], + values: ['indicator_match_rule'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['FFEtSYIBZ61VHL7LvV2j'], + values: ['FFEtSYIBZ61VHL7LvV2j'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['MacBook-Pro-de-Gloria.local'], + values: ['MacBook-Pro-de-Gloria.local'], + }, + ], + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.hostname'], + values: ['host.hostname'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['im'], + values: ['im'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['indicator_match_rule'], + values: ['indicator_match_rule'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['E1EtSYIBZ61VHL7Ltl3m'], + values: ['E1EtSYIBZ61VHL7Ltl3m'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['MacBook-Pro-de-Gloria.local'], + values: ['MacBook-Pro-de-Gloria.local'], + }, + ], + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.architecture'], + values: ['host.architecture'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['im'], + values: ['im'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['indicator_match_rule'], + values: ['indicator_match_rule'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['E1EtSYIBZ61VHL7Ltl3m'], + values: ['E1EtSYIBZ61VHL7Ltl3m'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['x86_64'], + values: ['x86_64'], + }, + ], + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.name'], + values: ['host.name'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['im'], + values: ['im'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['indicator_match_rule'], + values: ['indicator_match_rule'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['E1EtSYIBZ61VHL7Ltl3m'], + values: ['E1EtSYIBZ61VHL7Ltl3m'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['MacBook-Pro-de-Gloria.local'], + values: ['MacBook-Pro-de-Gloria.local'], + }, + ], + [ + { + category: 'matched', + field: 'matched.field', + isObjectArray: false, + originalValue: ['host.hostname'], + values: ['host.hostname'], + }, + { + category: 'matched', + field: 'matched.index', + isObjectArray: false, + originalValue: ['im'], + values: ['im'], + }, + { + category: 'matched', + field: 'matched.type', + isObjectArray: false, + originalValue: ['indicator_match_rule'], + values: ['indicator_match_rule'], + }, + { + category: 'matched', + field: 'matched.id', + isObjectArray: false, + originalValue: ['CFErSYIBZ61VHL7LIV1N'], + values: ['CFErSYIBZ61VHL7LIV1N'], + }, + { + category: 'matched', + field: 'matched.atomic', + isObjectArray: false, + originalValue: ['MacBook-Pro-de-Gloria.local'], + values: ['MacBook-Pro-de-Gloria.local'], + }, + ], + ]); + }); +}); + +describe('filterDuplicateEnrichments', () => { + it('returns an empty array if given one', () => { + expect(filterDuplicateEnrichments([])).toEqual([]); + }); + + it('returns the existing enrichment if given both that and an investigation-time enrichment for the same indicator and field', () => { + const existingEnrichment = buildEventEnrichmentMock({ + 'matched.type': [ENRICHMENT_TYPES.IndicatorMatchRule], + }); + const investigationEnrichment = buildEventEnrichmentMock({ + 'matched.type': [ENRICHMENT_TYPES.InvestigationTime], + }); + expect(filterDuplicateEnrichments([existingEnrichment, investigationEnrichment])).toEqual([ + existingEnrichment, + ]); + }); + + it('includes two enrichments from the same indicator if it matched different fields', () => { + const enrichments = [ + buildEventEnrichmentMock(), + buildEventEnrichmentMock({ + 'matched.field': ['other.field'], + }), + ]; + expect(filterDuplicateEnrichments(enrichments)).toEqual(enrichments); + }); +}); + +describe('getEnrichmentFields', () => { + it('returns an empty object if items is empty', () => { + expect(getEnrichmentFields([])).toEqual({}); + }); + + it('returns an object of event fields and values', () => { + const data = [ + { + category: 'source', + field: 'source.ip', + isObjectArray: false, + originalValue: ['192.168.1.1'], + values: ['192.168.1.1'], + }, + { + category: 'event', + field: 'event.reference', + isObjectArray: false, + originalValue: ['https://urlhaus.abuse.ch/url/1055419/'], + values: ['https://urlhaus.abuse.ch/url/1055419/'], + }, + ]; + expect(getEnrichmentFields(data)).toEqual({ + 'source.ip': '192.168.1.1', + }); + }); +}); + +describe('getEnrichmentIdentifiers', () => { + it(`return feed name as feedName if it's present in enrichment`, () => { + expect( + getEnrichmentIdentifiers({ + 'matched.id': [1], + 'matched.field': ['matched field'], + 'matched.atomic': ['matched atomic'], + 'matched.type': ['matched type'], + 'feed.name': ['feed name'], + }) + ).toEqual({ + id: 1, + field: 'matched field', + value: 'matched atomic', + type: 'matched type', + feedName: 'feed name', + }); + }); +}); + +describe('buildThreatDetailsItems', () => { + it('returns an empty array if given an empty enrichment', () => { + expect(buildThreatDetailsItems({})).toEqual([]); + }); + + it('returns an array of threat details items', () => { + const enrichment = { + 'matched.field': ['matched field'], + 'matched.atomic': ['matched atomic'], + 'matched.type': ['matched type'], + 'feed.name': ['feed name'], + }; + expect(buildThreatDetailsItems(enrichment)).toEqual([ + { + description: { + fieldName: 'feed.name', + value: 'feed name', + }, + title: 'feed.name', + }, + { + description: { + fieldName: 'matched.atomic', + value: 'matched atomic', + }, + title: 'matched.atomic', + }, + { + description: { + fieldName: 'matched.field', + value: 'matched field', + }, + title: 'matched.field', + }, + { + description: { + fieldName: 'matched.type', + value: 'matched type', + }, + title: 'matched.type', + }, + ]); + }); + + it('retrieves the first value of an array field', () => { + const enrichment = { + array_values: ['first value', 'second value'], + }; + + expect(buildThreatDetailsItems(enrichment)).toEqual([ + { + title: 'array_values', + description: { + fieldName: 'array_values', + value: 'first value', + }, + }, + ]); + }); + + it('shortens indicator field names if they contain the default indicator path', () => { + const enrichment = { + 'threat.indicator.ip': ['127.0.0.1'], + }; + expect(buildThreatDetailsItems(enrichment)).toEqual([ + { + title: 'indicator.ip', + description: { + fieldName: 'threat.indicator.ip', + value: '127.0.0.1', + }, + }, + ]); + }); + + it('parses an object field', () => { + const enrichment = { + 'object_field.foo': ['bar'], + }; + + expect(buildThreatDetailsItems(enrichment)).toEqual([ + { + title: 'object_field.foo', + description: { + fieldName: 'object_field.foo', + value: 'bar', + }, + }, + ]); + }); + + describe('edge cases', () => { + describe('field responses for fields of type "flattened"', () => { + it('returns a note for the value of a flattened field containing a single object', () => { + const enrichment = { + flattened_object: [{ foo: 'bar' }], + }; + + expect(buildThreatDetailsItems(enrichment)).toEqual([ + { + title: 'flattened_object', + description: { + fieldName: 'flattened_object', + value: + 'This field contains nested object values, which are not rendered here. See the full document for all fields/values', + }, + }, + ]); + }); + + it('returns a note for the value of a flattened field containing an array of objects', () => { + const enrichment = { + array_field: [{ foo: 'bar' }, { baz: 'qux' }], + }; + + expect(buildThreatDetailsItems(enrichment)).toEqual([ + { + title: 'array_field', + description: { + fieldName: 'array_field', + value: + 'This field contains nested object values, which are not rendered here. See the full document for all fields/values', + }, + }, + ]); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.tsx new file mode 100644 index 0000000000000..2f5bd6510430b --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/utils/threat_intelligence.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 { groupBy, isObject } from 'lodash'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { i18n } from '@kbn/i18n'; +import type { ThreatDetailsRow } from '../../left/components/threat_details_view_enrichment_accordion'; +import type { CtiEnrichment, EventFields } from '../../../../../common/search_strategy'; +import { isValidEventField } from '../../../../../common/search_strategy'; +import { getDataFromFieldsHits } from '../../../../../common/utils/field_formatters'; +import { + DEFAULT_INDICATOR_SOURCE_PATH, + ENRICHMENT_DESTINATION_PATH, +} from '../../../../../common/constants'; +import { + ENRICHMENT_TYPES, + FIRST_SEEN, + MATCHED_ATOMIC, + MATCHED_FIELD, + MATCHED_ID, + MATCHED_TYPE, + FEED_NAME, +} from '../../../../../common/cti/constants'; + +const NESTED_OBJECT_VALUES_NOT_RENDERED = i18n.translate( + 'xpack.securitySolution.flyout.threatIntelligence.investigationEnrichmentObjectValuesNotRendered', + { + defaultMessage: + 'This field contains nested object values, which are not rendered here. See the full document for all fields/values', + } +); + +/** + * Retrieves the first element of the given array. + * + * @param array the array to retrieve a value from + * @returns the first element of the array, or undefined if the array is undefined + */ +const getFirstElement: <T = unknown>(array: T[] | undefined) => T | undefined = (array) => + array ? array[0] : undefined; + +/** + * Returns true if the enrichment type is 'investigation_time' + */ +export const isInvestigationTimeEnrichment = (type: string | undefined): boolean => + type === ENRICHMENT_TYPES.InvestigationTime; + +/** + * Parses existing enrichments from the timeline data + */ +export const parseExistingEnrichments = ( + data: TimelineEventsDetailsItem[] +): TimelineEventsDetailsItem[][] => { + const threatIndicatorField = data.find( + ({ field, originalValue }) => field === ENRICHMENT_DESTINATION_PATH && originalValue + ); + if (!threatIndicatorField) { + return []; + } + + const { originalValue } = threatIndicatorField; + const enrichmentStrings: string[] = Array.isArray(originalValue) + ? originalValue + : [originalValue]; + + return enrichmentStrings.reduce<TimelineEventsDetailsItem[][]>( + (enrichments, enrichmentString) => { + try { + const enrichment = getDataFromFieldsHits(JSON.parse(enrichmentString)); + enrichments.push(enrichment); + } catch (e) { + // omit failed parse + } + return enrichments; + }, + [] + ); +}; + +/** + * Converts timeline data to a CtiEnrichment object + */ +export const timelineDataToEnrichment = (data: TimelineEventsDetailsItem[]): CtiEnrichment => + data.reduce<CtiEnrichment>((acc, item) => { + acc[item.field] = item.originalValue; + return acc; + }, {}); + +/** + * Extracts the first value from an enrichment field + */ +export const getEnrichmentValue = (enrichment: CtiEnrichment, field: string) => + getFirstElement(enrichment[field]) as string | undefined; + +/** + * These fields (e.g. 'indicator.ip') may be in one of three places depending on whether it's: + * * a queried, legacy filebeat indicator ('threatintel.indicator.ip') + * * a queried, ECS 1.11 filebeat indicator ('threat.indicator.ip') + * * an existing indicator from an enriched alert ('indicator.ip') + */ +export const getShimmedIndicatorValue = (enrichment: CtiEnrichment, field: string) => + getEnrichmentValue(enrichment, field) || + getEnrichmentValue(enrichment, `threatintel.${field}`) || + getEnrichmentValue(enrichment, `threat.${field}`); + +/** + * Extracts the identifiers from an enrichment + */ +export const getEnrichmentIdentifiers = ( + enrichment: CtiEnrichment +): { + id: string | undefined; + field: string | undefined; + value: string | undefined; + type: string | undefined; + feedName: string | undefined; +} => ({ + id: getEnrichmentValue(enrichment, MATCHED_ID), + field: getEnrichmentValue(enrichment, MATCHED_FIELD), + value: getEnrichmentValue(enrichment, MATCHED_ATOMIC), + type: getEnrichmentValue(enrichment, MATCHED_TYPE), + feedName: getShimmedIndicatorValue(enrichment, FEED_NAME), +}); + +/** + * Returns a string composed of the id and the field for the enrichment + */ +const buildEnrichmentId = (enrichment: CtiEnrichment): string => { + const { id, field } = getEnrichmentIdentifiers(enrichment); + return `${id}${field}`; +}; + +/** + * This function receives an array of enrichments and removes + * investigation-time enrichments if that exact indicator already exists + * elsewhere in the list. + * + * @param enrichments {@type CtiEnrichment[]} + */ +export const filterDuplicateEnrichments = (enrichments: CtiEnrichment[]): CtiEnrichment[] => { + if (enrichments.length < 2) { + return enrichments; + } + const enrichmentsById = groupBy(enrichments, buildEnrichmentId); + + return Object.values(enrichmentsById).map( + (enrichmentGroup) => + enrichmentGroup.find( + (enrichment) => !isInvestigationTimeEnrichment(getEnrichmentValue(enrichment, MATCHED_TYPE)) + ) ?? enrichmentGroup[0] + ); +}; + +/** + * Returns the fields from the enrichments + */ +export const getEnrichmentFields = (items: TimelineEventsDetailsItem[]): EventFields => + items.reduce<EventFields>((fields, item) => { + if (isValidEventField(item.field)) { + const value = getFirstElement(item.originalValue); + if (value) { + return { ...fields, [item.field]: value }; + } + } + return fields; + }, {}); + +/** + * Returns the first seen date from the enrichment + */ +export const getFirstSeen = (enrichment: CtiEnrichment): number => { + const firstSeenValue = getShimmedIndicatorValue(enrichment, FIRST_SEEN); + const firstSeenDate = Date.parse(firstSeenValue ?? 'no date'); + return Number.isInteger(firstSeenDate) ? firstSeenDate : new Date(-1).valueOf(); +}; + +/** + * Builds the threat details items for the summary table + */ +export const buildThreatDetailsItems = (enrichment: CtiEnrichment): ThreatDetailsRow[] => + Object.keys(enrichment) + .sort() + .map((field) => { + const title = field.startsWith(DEFAULT_INDICATOR_SOURCE_PATH) + ? field.replace(`${DEFAULT_INDICATOR_SOURCE_PATH}`, 'indicator') + : field; + + let value = getFirstElement(enrichment[field]); + if (isObject(value)) { + value = NESTED_OBJECT_VALUES_NOT_RENDERED; + } + + return { title, description: { fieldName: field, value: value as string } }; + }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_action_failure_message/endpoint_action_failure_message.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_action_failure_message/endpoint_action_failure_message.test.tsx index 747548fc2dac6..7826b207e7002 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_action_failure_message/endpoint_action_failure_message.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_action_failure_message/endpoint_action_failure_message.test.tsx @@ -102,7 +102,7 @@ describe('EndpointActionFailureMessage', () => { 'agent-fails-a-lot': { type: 'json', content: { - code: 'ra_scan_error_scan-queue-quota', + code: 'ra_scan_error_queue-quota', }, }, }, @@ -233,7 +233,7 @@ describe('EndpointActionFailureMessage', () => { 'agent-fails-a-lot': { type: 'json', content: { - code: 'ra_scan_error_scan-queue-quota', + code: 'ra_scan_error_queue-quota', }, }, 'agent-errs-a-lot': { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx index 54dce8a6e4add..5086e1ac1fada 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx @@ -6,12 +6,10 @@ */ import React, { memo, useMemo } from 'react'; +import type { GetProcessesRequestBody } from '../../../../../common/api/endpoint'; import { RunningProcessesActionResults } from '../../running_processes_action_results'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; -import type { - GetProcessesActionOutputContent, - ProcessesRequestBody, -} from '../../../../../common/endpoint/types'; +import type { GetProcessesActionOutputContent } from '../../../../../common/endpoint/types'; import { useSendGetEndpointProcessesRequest } from '../../../hooks/response_actions/use_send_get_endpoint_processes_request'; import type { ActionRequestComponentProps } from '../types'; @@ -32,7 +30,7 @@ export const GetProcessesActionResult = memo<ActionRequestComponentProps>( }, [endpointId, comment, agentType]); const { result, actionDetails: completedActionDetails } = useConsoleActionSubmitter< - ProcessesRequestBody, + GetProcessesRequestBody, GetProcessesActionOutputContent >({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/scan_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/scan_action.test.tsx index ed60cc734994c..4457119128e46 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/scan_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/scan_action.test.tsx @@ -223,17 +223,16 @@ describe('When using scan action from response actions console', () => { }); }); - it.each([ - 'ra_scan_error_not-found', - 'ra_scan_error_scan_invalid-input', - 'ra_scan_error_scan-queue-quota', - ])('should show detailed error if `scan` failure returned code: %s', async (outputCode) => { - const mockData = apiMocks.responseProvider.actionDetails({ - path: '/api/endpoint/action/agent-a', - }).data; - - const actionDetailsApiResponseMock: ReturnType<typeof apiMocks.responseProvider.actionDetails> = - { + it.each(['ra_scan_error_not-found', 'ra_scan_error_invalid-input', 'ra_scan_error_queue-quota'])( + 'should show detailed error if `scan` failure returned code: %s', + async (outputCode) => { + const mockData = apiMocks.responseProvider.actionDetails({ + path: '/api/endpoint/action/agent-a', + }).data; + + const actionDetailsApiResponseMock: ReturnType< + typeof apiMocks.responseProvider.actionDetails + > = { data: { ...mockData, id: '123', @@ -263,15 +262,16 @@ describe('When using scan action from response actions console', () => { }, }; - apiMocks.responseProvider.actionDetails.mockReturnValue(actionDetailsApiResponseMock); - await render(); - enterConsoleCommand(renderResult, 'scan --path="/error/path"'); + apiMocks.responseProvider.actionDetails.mockReturnValue(actionDetailsApiResponseMock); + await render(); + enterConsoleCommand(renderResult, 'scan --path="/error/path"'); - await waitFor(() => { - expect(renderResult.getByTestId('scan-actionFailure').textContent).toMatch( - // RegExp below taken from: https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js - new RegExp(endpointActionResponseCodes[outputCode].replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')) - ); - }); - }); + await waitFor(() => { + expect(renderResult.getByTestId('scan-actionFailure').textContent).toMatch( + // RegExp below taken from: https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js + new RegExp(endpointActionResponseCodes[outputCode].replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')) + ); + }); + } + ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx index abfc48b78c791..e96f1f0028b7d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx @@ -6,11 +6,11 @@ */ import { memo, useMemo } from 'react'; +import type { KillProcessRequestBody } from '../../../../../common/api/endpoint'; import { parsedKillOrSuspendParameter } from '../lib/utils'; import { useSendKillProcessRequest } from '../../../hooks/response_actions/use_send_kill_process_endpoint_request'; import type { ActionRequestComponentProps } from '../types'; import { useConsoleActionSubmitter } from '../hooks/use_console_action_submitter'; -import type { KillProcessRequestBody } from '../../../../../common/endpoint/types'; export const KillProcessActionResult = memo< ActionRequestComponentProps<{ pid?: string[]; entityId?: string[]; processName?: string[] }> diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx index 344ebe5da626e..b2a73b426aa27 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx @@ -6,10 +6,10 @@ */ import { memo, useMemo } from 'react'; +import type { SuspendProcessRequestBody } from '../../../../../common/api/endpoint'; import { parsedKillOrSuspendParameter } from '../lib/utils'; import type { SuspendProcessActionOutputContent, - SuspendProcessRequestBody, ResponseActionParametersWithEntityId, ResponseActionParametersWithPid, } from '../../../../../common/endpoint/types'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts index 4d769e68b98b1..da407b589a84d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/endpoint_action_response_codes.ts @@ -274,7 +274,7 @@ const CODES = Object.freeze({ // SCAN CODES // ----------------------------------------------------------------- - 'ra_scan_error_scan_invalid-input': i18n.translate( + 'ra_scan_error_invalid-input': i18n.translate( 'xpack.securitySolution.endpointActionResponseCodes.scan.invalidInput', { defaultMessage: 'Invalid absolute file path provided' } ), @@ -288,7 +288,7 @@ const CODES = Object.freeze({ // Dev: // scan quota exceeded failure - 'ra_scan_error_scan-queue-quota': i18n.translate( + 'ra_scan_error_queue-quota': i18n.translate( 'xpack.securitySolution.endpointActionResponseCodes.scan.queueQuota', { defaultMessage: 'Too many scans are queued' } ), diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx index f403ba6f6aacd..b950c3f343e18 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_filters.tsx @@ -55,9 +55,6 @@ export const ActionsLogFilters = memo( 'data-test-subj'?: string; }) => { const getTestId = useTestIdGenerator(dataTestSubj); - const responseActionsEnabled = useIsExperimentalFeatureEnabled( - 'endpointResponseActionsEnabled' - ); const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( 'responseActionsSentinelOneV1Enabled' @@ -86,26 +83,24 @@ export const ActionsLogFilters = memo( onChangeFilterOptions={onChangeStatusesFilter} data-test-subj={dataTestSubj} /> - {isSentinelOneV1Enabled - ? responseActionsEnabled && ( - <ActionsLogFilter - filterName={'types'} - typesFilters={{ - agentTypes: { onChangeFilterOptions: onChangeAgentTypesFilter }, - actionTypes: { onChangeFilterOptions: onChangeTypeFilter }, - }} - isFlyout={isFlyout} - data-test-subj={dataTestSubj} - /> - ) - : responseActionsEnabled && ( - <ActionsLogFilter - filterName={'types'} - onChangeFilterOptions={onChangeTypeFilter} - isFlyout={isFlyout} - data-test-subj={dataTestSubj} - /> - )} + {isSentinelOneV1Enabled ? ( + <ActionsLogFilter + filterName={'types'} + typesFilters={{ + agentTypes: { onChangeFilterOptions: onChangeAgentTypesFilter }, + actionTypes: { onChangeFilterOptions: onChangeTypeFilter }, + }} + isFlyout={isFlyout} + data-test-subj={dataTestSubj} + /> + ) : ( + <ActionsLogFilter + filterName={'types'} + onChangeFilterOptions={onChangeTypeFilter} + isFlyout={isFlyout} + data-test-subj={dataTestSubj} + /> + )} </> ); }, [ @@ -116,7 +111,6 @@ export const ActionsLogFilters = memo( dataTestSubj, onChangeCommandsFilter, onChangeStatusesFilter, - responseActionsEnabled, onChangeAgentTypesFilter, onChangeTypeFilter, ]); 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 b764880257b08..c15e8d1752852 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 @@ -1229,7 +1229,7 @@ describe('Response actions history', () => { command === 'get-file' ? 'ra_get-file_error_not-found' : command === 'scan' - ? 'ra_scan_error_scan_invalid-input' + ? 'ra_scan_error_invalid-input' : 'non_existing_code_for_test', }, }, @@ -1384,7 +1384,7 @@ describe('Response actions history', () => { command === 'get-file' ? 'ra_get-file_error_not-found' : command === 'scan' - ? 'ra_scan_error_scan_invalid-input' + ? 'ra_scan_error_invalid-input' : 'non_existing_code_for_test', content: undefined, }, @@ -1396,7 +1396,7 @@ describe('Response actions history', () => { command === 'get-file' ? 'ra_get-file_error_invalid-input' : command === 'scan' - ? 'ra_scan_error_scan_invalid-input' + ? 'ra_scan_error_invalid-input' : 'non_existing_code_for_test', content: undefined, }, 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 index 22a796767a530..b5c41d1e66faf 100644 --- 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 @@ -10,7 +10,11 @@ import { login, ROLE } from '../../tasks/login'; import { loadPage } from '../../tasks/common'; import { getArtifactsListTestsData } from '../../fixtures/artifacts_page'; -import { removeAllArtifacts } from '../../tasks/artifacts'; +import { + createArtifactList, + createPerPolicyArtifact, + removeAllArtifacts, +} from '../../tasks/artifacts'; import { performUserActions } from '../../tasks/perform_user_actions'; import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import type { ReturnTypeFromChainable } from '../../types'; @@ -38,7 +42,9 @@ describe('Artifacts pages', { tags: ['@ess', '@serverless', '@skipInServerlessMK indexEndpointHosts().then((indexEndpoints) => { endpointData = indexEndpoints; }); + }); + beforeEach(() => { removeAllArtifacts(); }); @@ -50,116 +56,124 @@ describe('Artifacts pages', { tags: ['@ess', '@serverless', '@skipInServerlessMK }); for (const testData of getArtifactsListTestsData()) { - // FLAKY: https://github.com/elastic/kibana/issues/183718 - // FLAKY: https://github.com/elastic/kibana/issues/183719 - // FLAKY: https://github.com/elastic/kibana/issues/183720 - describe.skip(`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`, - // there is no such role in Serverless environment that only reads artifacts - { tags: ['@skipInServerless'] }, - () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); - cy.getByTestSubj(testData.emptyState).should('exist'); + describe(`When on the ${testData.title} entries list`, () => { + describe('given there are no artifacts yet', () => { + 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(`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(); + }); + + it( + `read - should show empty state page if there is no ${testData.title} entry and the add button does not exist`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + 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'); + }); - performUserActions(testData.create.formActions); + it(`write - should create new ${testData.title} entry`, () => { + loginWithWriteAccess(`/app/security/administration/${testData.urlPath}`); + // Opens add flyout + cy.getByTestSubj(`${testData.pagePrefix}-emptyState-addButton`).click(); - // Submit create artifact form - cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); + performUserActions(testData.create.formActions); - // Check new artifact is in the list - for (const checkResult of testData.create.checkResults) { - cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); - } + // Submit create artifact form + cy.getByTestSubj(`${testData.pagePrefix}-flyout-submitButton`).click(); - // Title is shown after adding an item - cy.getByTestSubj('header-page-title').contains(testData.title); - }); + // Check new artifact is in the list + for (const checkResult of testData.create.checkResults) { + cy.getByTestSubj(checkResult.selector).should('have.text', checkResult.value); + } - it( - `read - should not be able to update/delete an existing ${testData.title} entry`, - // there is no such role in Serverless environment that only reads artifacts - { tags: ['@skipInServerless'] }, - () => { - loginWithReadAccess( - testData.privilegePrefix, - `/app/security/administration/${testData.urlPath}` - ); + // Title is shown after adding an item 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`, - // there is no such role in Serverless environment that only reads artifacts - { tags: ['@skipInServerless'] }, - () => { - 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'); + describe('given there is an existing artifact', () => { + beforeEach(() => { + createArtifactList(testData.createRequestBody.list_id); + createPerPolicyArtifact(testData.artifactName, testData.createRequestBody); + }); + + it( + `read - should not be able to update/delete an existing ${testData.title} entry`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + 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`, + // there is no such role in Serverless environment that only reads artifacts + { tags: ['@skipInServerless'] }, + () => { + 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.update.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/endpoint_role_rbac.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.cy.ts index 6a3e7d16040c4..f0dc0c1a5891d 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.cy.ts @@ -48,6 +48,7 @@ describe('When defining a kibana role for Endpoint security access', { tags: '@e 'Process Operations Perform process-related response actions in the response console.Process Operations sub-feature privilegeAllNone', 'File Operations Perform file-related response actions in the response console.File Operations sub-feature privilegeAllNone', 'Execute Operations Perform script execution response actions in the response console.Execute Operations sub-feature privilegeAllNone', + 'Scan Operations Perform folder scan response actions in the response console.Scan Operations sub-feature privilegeAllNone', ]); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.feature_flag.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.feature_flag.cy.ts deleted file mode 100644 index e7c98df4e735f..0000000000000 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_role_rbac.feature_flag.cy.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { closeAllToasts } from '../tasks/toasts'; -import { login, ROLE } from '../tasks/login'; -import { loadPage } from '../tasks/common'; - -describe( - 'When defining a kibana role for Endpoint security access', - { - env: { - ftrConfig: { - kbnServerArgs: [ - `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'responseActionScanEnabled', - ])}`, - ], - }, - }, - tags: '@ess', - }, - () => { - const getAllSubFeatureRows = (): Cypress.Chainable<JQuery<HTMLElement>> => { - return cy - .get('#featurePrivilegeControls_siem') - .findByTestSubj('mutexSubFeaturePrivilegeControl') - .closest('.euiFlexGroup'); - }; - - beforeEach(() => { - login(ROLE.system_indices_superuser); - loadPage('/app/management/security/roles/edit'); - closeAllToasts(); - cy.getByTestSubj('addSpacePrivilegeButton').click(); - cy.getByTestSubj('featureCategoryButton_securitySolution').closest('button').click(); - cy.get('.featurePrivilegeName:contains("Security")').closest('button').click(); - }); - - it('should display RBAC entries with expected controls', () => { - getAllSubFeatureRows() - .then(($subFeatures) => { - const featureRows: string[] = []; - $subFeatures.each((_, $subFeature) => { - featureRows.push($subFeature.textContent ?? ''); - }); - - return featureRows; - }) - .should('deep.equal', [ - 'Endpoint List Displays all hosts running Elastic Defend and their relevant integration details.Endpoint List sub-feature privilegeAllReadNone', - 'Trusted Applications Helps mitigate conflicts with other software, usually other antivirus or endpoint security applications.Trusted Applications sub-feature privilegeAllReadNone', - 'Host Isolation Exceptions Add specific IP addresses that isolated hosts are still allowed to communicate with, even when isolated from the rest of the network.Host Isolation Exceptions sub-feature privilegeAllReadNone', - 'Blocklist Extend Elastic Defend’s protection against malicious processes and protect against potentially harmful applications.Blocklist sub-feature privilegeAllReadNone', - 'Event Filters Filter out endpoint events that you do not need or want stored in Elasticsearch.Event Filters sub-feature privilegeAllReadNone', - 'Elastic Defend Policy Management Access the Elastic Defend integration policy to configure protections, event collection, and advanced policy features.Elastic Defend Policy Management sub-feature privilegeAllReadNone', - 'Response Actions History Access the history of response actions performed on endpoints.Response Actions History sub-feature privilegeAllReadNone', - 'Host Isolation Perform the "isolate" and "release" response actions.Host Isolation sub-feature privilegeAllNone', - 'Process Operations Perform process-related response actions in the response console.Process Operations sub-feature privilegeAllNone', - 'File Operations Perform file-related response actions in the response console.File Operations sub-feature privilegeAllNone', - 'Execute Operations Perform script execution response actions in the response console.Execute Operations sub-feature privilegeAllNone', - 'Scan Operations Perform folder scan response actions in the response console.Scan Operations sub-feature privilegeAllNone', - ]); - }); - } -); 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 index ec7914d76f791..dc7c37b3c521c 100644 --- 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 @@ -255,7 +255,7 @@ describe.skip( 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.getByTestSubj('securitySolutionFlyoutFooterDropdownButton').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 index 9af118eb1de47..c5d88ad5420b1 100644 --- 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 @@ -320,7 +320,7 @@ describe('Isolate command', { tags: ['@ess', '@serverless', '@brokenInServerless 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.getByTestSubj('securitySolutionFlyoutFooterDropdownButton').click(); } cy.get('[title="Isolated"]').should('not.exist'); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts index 3a763fc0e8e23..02eb16c5d0c53 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts @@ -54,7 +54,7 @@ describe( const openCaseAlertDetails = () => { cy.getByTestSubj(`comment-action-show-alert-${caseAlertActions.comments[alertId]}`).click(); - return cy.getByTestSubj('take-action-dropdown-btn').click(); + return cy.getByTestSubj('securitySolutionFlyoutFooterDropdownButton').click(); }; before(() => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts index ba105aa8cbc0a..a786a0d682096 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts @@ -56,8 +56,8 @@ describe( policy = indexedPolicy.integrationPolicies[0]; return enableAllPolicyProtections(policy.id).then(() => { - // Create and enroll a new Endpoint host - return createEndpointHost(policy.policy_ids[0]).then((host) => { + // At this point 8.14.2 is GA and this functionality is not available until 8.15.0 + return createEndpointHost(policy.policy_ids[0], '8.15.0').then((host) => { createdHost = host as CreateAndEnrollEndpointHostResponse; }); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts index 8662294ced6d2..47f09f65dd094 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts @@ -126,7 +126,7 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [ { selector: 'trustedAppsListPage-card-criteriaConditions', value: - 'OSIS WindowsAND file.pathis one of\nc:\\randomFolder\\randomFile.exe\nc:\\randomFolder\\randomFile2.exe', + ' OSIS WindowsAND process.executable.caselessIS c:\\randomFolder\\randomFile.exe, c:\\randomFolder\\randomFile2.exe', }, { selector: 'trustedAppsListPage-card-header-title', @@ -281,10 +281,10 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [ list_id: ENDPOINT_ARTIFACT_LISTS.eventFilters.id, entries: [ { - field: 'destination.ip', + field: 'agent.id', operator: 'included', type: 'match', - value: '1.2.3.4', + value: 'mr agent', }, ], os_types: ['windows'], @@ -382,7 +382,7 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [ { selector: 'blocklistPage-card-criteriaConditions', value: - 'OSIS WindowsAND file.path.caselessis one of\nc:\\randomFolder\\randomFile.exe\nc:\\randomFolder\\randomFile2.exe', + ' OSIS WindowsAND file.path.caselessis one of c:\\randomFolder\\randomFile.exe c:\\randomFolder\\randomFile2.exe', }, { selector: 'blocklistPage-card-header-title', @@ -404,16 +404,10 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [ list_id: ENDPOINT_ARTIFACT_LISTS.blocklists.id, entries: [ { - field: 'file.Ext.code_signature', - entries: [ - { - field: 'subject_name', - value: ['wegwergwegw'], - type: 'match_any', - operator: 'included', - }, - ], - type: 'nested', + field: 'file.hash.sha256', + value: ['a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476'], + type: 'match_any', + operator: 'included', }, ], os_types: ['windows'], @@ -484,7 +478,7 @@ export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [ checkResults: [ { selector: 'hostIsolationExceptionsListPage-card-criteriaConditions', - value: 'OSIS Windows, Linux, Mac\nAND destination.ipIS 2.2.2.2/24', + value: ' OSIS Windows, Linux, MacAND destination.ipIS 2.2.2.2/24', }, { selector: 'hostIsolationExceptionsListPage-card-header-title', 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 713c260f095ba..3ce5846a5b152 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 @@ -44,14 +44,14 @@ export const getAlertsTableRows = (timeout?: number): Cypress.Chainable<JQuery<H export const openAlertDetailsView = (rowIndex: number = 0): void => { cy.getByTestSubj('expand-event').eq(rowIndex).click(); - cy.getByTestSubj('take-action-dropdown-btn').click(); + cy.getByTestSubj('securitySolutionFlyoutFooterDropdownButton').click(); }; export const openAlertDetailsViewFromTimeline = (rowIndex: number = 0): void => { cy.getByTestSubj('timeline-container').within(() => { cy.getByTestSubj('docTableExpandToggleColumn').eq(rowIndex).click(); }); - cy.getByTestSubj('take-action-dropdown-btn').click(); + cy.getByTestSubj('securitySolutionFlyoutFooterDropdownButton').click(); }; export const openInvestigateInTimelineView = (): void => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/create_and_enroll_endpoint_host_ci.ts b/x-pack/plugins/security_solution/public/management/cypress/support/create_and_enroll_endpoint_host_ci.ts index 85b95c0d5f4a6..c4110ad2b3e0e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/create_and_enroll_endpoint_host_ci.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/create_and_enroll_endpoint_host_ci.ts @@ -10,13 +10,14 @@ import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import type { KbnClient } from '@kbn/test/src/kbn_client'; import { kibanaPackageJson } from '@kbn/repo-info'; +import { isServerlessKibanaFlavor } from '../../../../common/endpoint/utils/kibana_status'; +import { fetchFleetLatestAvailableAgentVersion } from '../../../../common/endpoint/utils/fetch_fleet_version'; import { isFleetServerRunning } from '../../../../scripts/endpoint/common/fleet_server/fleet_server_services'; import type { HostVm } from '../../../../scripts/endpoint/common/types'; import type { BaseVmCreateOptions } from '../../../../scripts/endpoint/common/vm_services'; import { createVm } from '../../../../scripts/endpoint/common/vm_services'; import { fetchAgentPolicyEnrollmentKey, - fetchFleetAvailableVersions, fetchFleetServerUrl, getAgentDownloadUrl, getAgentFileName, @@ -38,12 +39,12 @@ export interface CreateAndEnrollEndpointHostCIOptions agentPolicyId: string; /** version of the Agent to install. Defaults to stack version */ version?: string; + /** skip all checks and use provided version */ + forceVersion?: boolean; /** The name for the host. Will also be the name of the VM */ hostname?: string; /** If `version` should be exact, or if this is `true`, then the closest version will be used. Defaults to `false` */ useClosestVersionMatch?: boolean; - /** If the environment is MKI */ - isMkiEnvironment?: boolean; } export interface CreateAndEnrollEndpointHostCIResponse { @@ -66,14 +67,16 @@ export const createAndEnrollEndpointHostCI = async ({ hostname, version = kibanaPackageJson.version, useClosestVersionMatch = true, - isMkiEnvironment = false, + forceVersion = false, }: CreateAndEnrollEndpointHostCIOptions): Promise<CreateAndEnrollEndpointHostCIResponse> => { - let agentVersion = version; const vmName = hostname ?? `test-host-${Math.random().toString().substring(2, 6)}`; + let agentVersion = version; - if (isMkiEnvironment) { - // MKI env provides own fleet server. We must be sure that currently deployed FS is compatible with agent version we want to deploy. - agentVersion = await fetchFleetAvailableVersions(kbnClient); + if (!forceVersion) { + const isServerless = await isServerlessKibanaFlavor(kbnClient); + if (isServerless) { + agentVersion = await fetchFleetLatestAvailableAgentVersion(kbnClient); + } } const fileNameNoExtension = getAgentFileName(agentVersion); diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts index 2ed45297fc531..3abf04765ca5e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts @@ -246,11 +246,12 @@ export const dataLoaders = ( }, indexEndpointRuleAlerts: async (options: { endpointAgentId: string; count?: number }) => { - const { esClient, log } = await stackServicesPromise; + const { esClient, log, kbnClient } = await stackServicesPromise; return ( await indexEndpointRuleAlerts({ ...options, esClient, + kbnClient, log, }) ).alerts; @@ -326,8 +327,6 @@ export const dataLoadersForRealEndpoints = ( config: Cypress.PluginConfigOptions ): void => { const stackServicesPromise = setupStackServicesUsingCypressConfig(config); - const isServerless = Boolean(config.env.IS_SERVERLESS); - const isCloudServerless = Boolean(config.env.CLOUD_SERVERLESS); on('task', { createSentinelOneHost: async () => { @@ -415,7 +414,6 @@ ${s1Info.status} options: Omit<CreateAndEnrollEndpointHostCIOptions, 'log' | 'kbnClient'> ): Promise<CreateAndEnrollEndpointHostCIResponse> => { const { kbnClient, log, esClient } = await stackServicesPromise; - const isMkiEnvironment = isServerless && isCloudServerless; let retryAttempt = 0; const attemptCreateEndpointHost = async (): Promise<CreateAndEnrollEndpointHostCIResponse> => { @@ -424,7 +422,6 @@ ${s1Info.status} const newHost = process.env.CI ? await createAndEnrollEndpointHostCI({ useClosestVersionMatch: true, - isMkiEnvironment, ...options, log, kbnClient, diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts b/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts index 841a857846a2a..76004f91ccb48 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/plugin_handlers/endpoint_data_loader.ts @@ -10,6 +10,7 @@ import type { KbnClient } from '@kbn/test'; import pRetry from 'p-retry'; import { kibanaPackageJson } from '@kbn/repo-info'; import type { ToolingLog } from '@kbn/tooling-log'; +import { fetchFleetLatestAvailableAgentVersion } from '../../../../../common/endpoint/utils/fetch_fleet_version'; import { dump } from '../../../../../scripts/endpoint/common/utils'; import { STARTED_TRANSFORM_STATES } from '../../../../../common/constants'; import { @@ -77,8 +78,18 @@ export const cyLoadEndpointDataHandler = async ( isServerless = false, } = options; + let agentVersion = version; + + if (isServerless) { + agentVersion = await fetchFleetLatestAvailableAgentVersion(kbnClient); + } + const DocGenerator = EndpointDocGenerator.custom({ - CustomMetadataGenerator: EndpointMetadataGenerator.custom({ version, os, isolation }), + CustomMetadataGenerator: EndpointMetadataGenerator.custom({ + version: agentVersion, + os, + isolation, + }), }); if (waitUntilTransformed) { @@ -192,6 +203,7 @@ const startTransform = async ( * the united metadata index * * @param esClient + * @param log * @param location * @param ids */ diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts b/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts index a7085494ab6e8..526c430920ea6 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/saml_authentication.ts @@ -10,6 +10,7 @@ import { ToolingLog } from '@kbn/tooling-log'; import type { HostOptions } from '@kbn/test'; import { SamlSessionManager } from '@kbn/test'; import type { SecurityRoleName } from '../../../../common/test'; +import { resolveCloudUsersFilePath } from '../../../../scripts/endpoint/common/roles_users/serverless'; export const samlAuthentication = async ( on: Cypress.PluginEvents, @@ -34,15 +35,15 @@ export const samlAuthentication = async ( role: string | SecurityRoleName ): Promise<{ cookie: string; username: string; password: string }> => { // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. - const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const rolesFilename = config.env.PROXY_ORG + ? `${config.env.PROXY_ORG}.json` + : 'role_users.json'; + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath: resolveCloudUsersFilePath(rolesFilename), + }); return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role).then((cookie) => { return { cookie, diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts index 8b75c7e1b2af5..6ca939851e6fc 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts @@ -10,12 +10,14 @@ import type { CreateAndEnrollEndpointHostResponse } from '../../../../scripts/en // only used in "real" endpoint tests not in mocked ones export const createEndpointHost = ( agentPolicyId: string, + version?: string, timeout?: number ): Cypress.Chainable<CreateAndEnrollEndpointHostResponse> => { return cy.task( 'createEndpointHost', { agentPolicyId, + ...(version ? { version, forceVersion: true } : {}), }, { timeout: timeout ?? 30 * 60 * 1000 } ); 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 a285cc67dcc36..204fac9d9ac64 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 @@ -51,7 +51,7 @@ export const releaseHostWithComment = (comment: string, hostname: string): void export const openCaseAlertDetails = (alertId: string): void => { cy.getByTestSubj(`comment-action-show-alert-${alertId}`).click(); - cy.getByTestSubj('take-action-dropdown-btn').click(); + cy.getByTestSubj('securitySolutionFlyoutFooterDropdownButton').click(); }; export const waitForReleaseOption = (alertId: string): void => { diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts index cf946db04ca37..849380714cd94 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_get_endpoint_processes_request.ts @@ -8,8 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { GetProcessesRequestBody } from '../../../../common/api/endpoint'; import type { - ProcessesRequestBody, ResponseActionApiResponse, GetProcessesActionOutputContent, } from '../../../../common/endpoint/types/actions'; @@ -24,18 +24,18 @@ export const useSendGetEndpointProcessesRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse<GetProcessesActionOutputContent>, IHttpFetchError, - ProcessesRequestBody + GetProcessesRequestBody > ): UseMutationResult< ResponseActionApiResponse<GetProcessesActionOutputContent>, IHttpFetchError, - ProcessesRequestBody + GetProcessesRequestBody > => { return useMutation< ResponseActionApiResponse<GetProcessesActionOutputContent>, IHttpFetchError, - ProcessesRequestBody - >((getRunningProcessesData: ProcessesRequestBody) => { + GetProcessesRequestBody + >((getRunningProcessesData: GetProcessesRequestBody) => { return KibanaServices.get().http.post< ResponseActionApiResponse<GetProcessesActionOutputContent> >(GET_PROCESSES_ROUTE, { diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts index b4cb131f6ae9c..a9efa834e97e5 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts @@ -8,11 +8,9 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { IsolationRouteRequestBody } from '../../../../common/api/endpoint'; import { isolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; -import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../common/endpoint/types'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; /** * Create host isolation requests @@ -22,11 +20,11 @@ export const useSendIsolateEndpointRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - HostIsolationRequestBody + IsolationRouteRequestBody > -): UseMutationResult<ResponseActionApiResponse, IHttpFetchError, HostIsolationRequestBody> => { - return useMutation<ResponseActionApiResponse, IHttpFetchError, HostIsolationRequestBody>( - (isolateData: HostIsolationRequestBody) => { +): UseMutationResult<ResponseActionApiResponse, IHttpFetchError, IsolationRouteRequestBody> => { + return useMutation<ResponseActionApiResponse, IHttpFetchError, IsolationRouteRequestBody>( + (isolateData: IsolationRouteRequestBody) => { return isolateHost(isolateData); }, customOptions diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts index b17d34ab4e463..1dd2b1ae2a8c7 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_kill_process_endpoint_request.ts @@ -8,10 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - ResponseActionApiResponse, - KillProcessRequestBody, -} from '../../../../common/endpoint/types'; +import type { KillProcessRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { killProcess } from '../../../common/lib/process_actions'; /** diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts index b9e140dc3ba9d..ed0eaf68f90d4 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts @@ -8,10 +8,8 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - HostIsolationRequestBody, - ResponseActionApiResponse, -} from '../../../../common/endpoint/types'; +import type { UnisolationRouteRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { unIsolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; /** @@ -22,11 +20,11 @@ export const useSendReleaseEndpointRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, IHttpFetchError, - HostIsolationRequestBody + UnisolationRouteRequestBody > -): UseMutationResult<ResponseActionApiResponse, IHttpFetchError, HostIsolationRequestBody> => { - return useMutation<ResponseActionApiResponse, IHttpFetchError, HostIsolationRequestBody>( - (releaseData: HostIsolationRequestBody) => { +): UseMutationResult<ResponseActionApiResponse, IHttpFetchError, UnisolationRouteRequestBody> => { + return useMutation<ResponseActionApiResponse, IHttpFetchError, UnisolationRouteRequestBody>( + (releaseData: UnisolationRouteRequestBody) => { return unIsolateHost(releaseData); }, customOptions diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts index 787344ffd54dc..f2e467e36073e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_suspend_process_endpoint_request.ts @@ -8,10 +8,8 @@ import { useMutation } from '@tanstack/react-query'; import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import type { - ResponseActionApiResponse, - SuspendProcessRequestBody, -} from '../../../../common/endpoint/types'; +import type { SuspendProcessRequestBody } from '../../../../common/api/endpoint'; +import type { ResponseActionApiResponse } from '../../../../common/endpoint/types'; import { suspendProcess } from '../../../common/lib/process_actions'; /** diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 01b9b3455dea9..43db8f92929e8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -7,9 +7,9 @@ import type { Action } from 'redux'; import type { DataViewBase } from '@kbn/es-query'; +import type { IsolationRouteRequestBody } from '../../../../../common/api/endpoint'; import type { GetHostPolicyResponse, - HostIsolationRequestBody, ISOLATION_ACTIONS, MetadataListResponse, } from '../../../../../common/endpoint/types'; @@ -133,7 +133,7 @@ export interface ServerFailedToReturnEndpointsTotal { export type EndpointIsolationRequest = Action<'endpointIsolationRequest'> & { payload: { type: ISOLATION_ACTIONS; - data: HostIsolationRequestBody; + data: IsolationRouteRequestBody; }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index fce262052220e..ec27500a45e12 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -14,6 +14,10 @@ import type { IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, } from '@kbn/timelines-plugin/common'; +import type { + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../common/api/endpoint'; import { ENDPOINT_FIELDS_SEARCH_STRATEGY, HOST_METADATA_LIST_ROUTE, @@ -22,7 +26,6 @@ import { metadataCurrentIndexPattern, } from '../../../../../common/endpoint/constants'; import type { - HostIsolationRequestBody, HostResultList, Immutable, ImmutableObject, @@ -246,9 +249,9 @@ const handleIsolateEndpointHost = async ( let response: ResponseActionApiResponse; if (action.payload.type === 'unisolate') { - response = await unIsolateHost(action.payload.data as HostIsolationRequestBody); + response = await unIsolateHost(action.payload.data as UnisolationRouteRequestBody); } else { - response = await isolateHost(action.payload.data as HostIsolationRequestBody); + response = await isolateHost(action.payload.data as IsolationRouteRequestBody); } dispatch({ diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx index 7dc11f828187a..47aca9f9ab597 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.test.tsx @@ -550,7 +550,7 @@ describe('Event filter form', () => { it('should not show warning text when unique fields are added', async () => { formProps.item.entries = [ { - field: 'event.category', + field: 'event.action', operator: 'included', type: 'match', value: 'some value', @@ -573,13 +573,13 @@ describe('Event filter form', () => { it('should not show warning text when field values are not added', async () => { formProps.item.entries = [ { - field: 'event.category', + field: 'event.action', operator: 'included', type: 'match', value: '', }, { - field: 'event.category', + field: 'event.action', operator: 'excluded', type: 'match', value: '', @@ -596,13 +596,13 @@ describe('Event filter form', () => { it('should show warning text when duplicate fields are added with values', async () => { formProps.item.entries = [ { - field: 'event.category', + field: 'event.action', operator: 'included', type: 'match', value: 'some value', }, { - field: 'event.category', + field: 'event.action', operator: 'excluded', type: 'match', value: 'some other value', diff --git a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx index b5b6503262217..ee44b775766cc 100644 --- a/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/detection_response/hooks/use_navigate_to_timeline.tsx @@ -16,7 +16,7 @@ import { } from '../../../../common/components/event_details/table/use_action_cell_data_provider'; import type { DataProvider, QueryOperator } from '../../../../../common/types/timeline'; import { TimelineId } from '../../../../../common/types/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useCreateTimeline } from '../../../../timelines/hooks/use_create_timeline'; import { updateProviders } from '../../../../timelines/store/actions'; import { sourcererSelectors } from '../../../../common/store'; @@ -36,7 +36,7 @@ export const useNavigateToTimeline = () => { const clearTimeline = useCreateTimeline({ timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); const navigateToTimeline = useCallback( diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx index 7d86ee6a2e5c4..8e6ccb158bfa7 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx @@ -9,7 +9,7 @@ import { EuiHorizontalRule, EuiText } from '@elastic/eui'; import React, { useCallback, useMemo, useEffect } from 'react'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import { SortFieldTimeline, TimelineType } from '../../../../common/api/timeline'; +import { SortFieldTimelineEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import { useGetAllTimeline } from '../../../timelines/containers/all'; import { useQueryTimelineById } from '../../../timelines/components/open_timeline/helpers'; import type { OnOpenTimeline } from '../../../timelines/components/open_timeline/types'; @@ -80,7 +80,7 @@ const StatefulRecentTimelinesComponent: React.FC<Props> = ({ filterBy }) => { ); const { fetchAllTimeline, timelines, loading } = useGetAllTimeline(); - const timelineType = TimelineType.default; + const timelineType = TimelineTypeEnum.default; const { timelineStatus } = useTimelineStatus({ timelineType }); useEffect(() => { @@ -91,7 +91,7 @@ const StatefulRecentTimelinesComponent: React.FC<Props> = ({ filterBy }) => { }, search: '', sort: { - sortField: SortFieldTimeline.updated, + sortField: SortFieldTimelineEnum.updated, sortOrder: Direction.desc, }, onlyUserFavorite: filterBy === 'favorites', diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/recent_timelines.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/recent_timelines.tsx index 92ae01123dab9..5f73a38f86d92 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/recent_timelines.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/recent_timelines.tsx @@ -15,7 +15,7 @@ import type { OpenTimelineResult, } from '../../../timelines/components/open_timeline/types'; import { HoverPopover } from '../../../common/components/hover_popover'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { RecentTimelineCounts } from './counts'; import * as i18n from './translations'; @@ -53,14 +53,14 @@ const RecentTimelinesItem = React.memo<RecentTimelinesItemProps>( hoverContent={ <EuiToolTip content={ - timeline.timelineType === TimelineType.default + timeline.timelineType === TimelineTypeEnum.default ? i18n.OPEN_AS_DUPLICATE : i18n.OPEN_AS_DUPLICATE_TEMPLATE } > <EuiButtonIcon aria-label={ - timeline.timelineType === TimelineType.default + timeline.timelineType === TimelineTypeEnum.default ? i18n.OPEN_AS_DUPLICATE : i18n.OPEN_AS_DUPLICATE_TEMPLATE } diff --git a/x-pack/plugins/security_solution/public/sourcerer/components/misc.test.tsx b/x-pack/plugins/security_solution/public/sourcerer/components/misc.test.tsx index 4ad87e78f227b..8a1d333355e43 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/components/misc.test.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/components/misc.test.tsx @@ -17,7 +17,7 @@ import { createMockStore, mockGlobalState, TestProviders } from '../../common/mo import { useSourcererDataView } from '../containers'; import { useSignalHelpers } from '../containers/use_signal_helpers'; import { TimelineId } from '../../../common/types/timeline'; -import { TimelineType } from '../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../common/api/timeline'; import { sortWithExcludesAtEnd } from '../../../common/utils/sourcerer'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; @@ -263,7 +263,7 @@ describe('Update available for timeline template', () => { ...mockGlobalState.timeline.timelineById, [TimelineId.active]: { ...mockGlobalState.timeline.timelineById.test, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }, }, }, @@ -339,7 +339,7 @@ describe('Missing index patterns', () => { ...mockGlobalState.timeline.timelineById, [TimelineId.active]: { ...mockGlobalState.timeline.timelineById.test, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template as TimelineType, }, }, }, @@ -392,7 +392,7 @@ describe('Missing index patterns', () => { activePatterns: ['myFakebeat-*'], }); const state3 = cloneDeep(state2); - state3.timeline.timelineById[TimelineId.active].timelineType = TimelineType.default; + state3.timeline.timelineById[TimelineId.active].timelineType = TimelineTypeEnum.default; store = createMockStore(state3); render( diff --git a/x-pack/plugins/security_solution/public/sourcerer/components/temporary.tsx b/x-pack/plugins/security_solution/public/sourcerer/components/temporary.tsx index 4d8849f9b1b6c..66899affeb4aa 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/components/temporary.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/components/temporary.tsx @@ -21,7 +21,7 @@ import * as i18n from './translations'; import { Blockquote, ResetButton } from './helpers'; import { UpdateDefaultDataViewModal } from './update_default_data_view_modal'; import { TimelineId } from '../../../common/types'; -import { TimelineType } from '../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../common/api/timeline'; import { timelineSelectors } from '../../timelines/store'; import { useDeepEqualSelector } from '../../common/hooks/use_selector'; import { timelineDefaults } from '../../timelines/store/defaults'; @@ -46,15 +46,15 @@ interface Props { const translations = { deprecated: { title: { - [TimelineType.default]: i18n.CALL_OUT_DEPRECATED_TITLE, - [TimelineType.template]: i18n.CALL_OUT_DEPRECATED_TEMPLATE_TITLE, + [TimelineTypeEnum.default]: i18n.CALL_OUT_DEPRECATED_TITLE, + [TimelineTypeEnum.template]: i18n.CALL_OUT_DEPRECATED_TEMPLATE_TITLE, }, update: i18n.UPDATE_INDEX_PATTERNS, }, missingPatterns: { title: { - [TimelineType.default]: i18n.CALL_OUT_MISSING_PATTERNS_TITLE, - [TimelineType.template]: i18n.CALL_OUT_MISSING_PATTERNS_TEMPLATE_TITLE, + [TimelineTypeEnum.default]: i18n.CALL_OUT_MISSING_PATTERNS_TITLE, + [TimelineTypeEnum.template]: i18n.CALL_OUT_MISSING_PATTERNS_TEMPLATE_TITLE, }, update: i18n.ADD_INDEX_PATTERN, }, diff --git a/x-pack/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx b/x-pack/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx index e7126722e244e..397732ed4ec9f 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/components/use_pick_index_patterns.tsx @@ -192,7 +192,7 @@ export const usePickIndexPatterns = ({ async (newSelectedDataViewId: string, isAlerts?: boolean) => { if ( kibanaDataViews.some( - (kdv) => kdv.id === newSelectedDataViewId && kdv.indexFields.length === 0 + (kdv) => kdv.id === newSelectedDataViewId && Object.keys(kdv?.fields || {}).length === 0 ) ) { try { diff --git a/x-pack/plugins/security_solution/public/sourcerer/components/utils.tsx b/x-pack/plugins/security_solution/public/sourcerer/components/utils.tsx index a77655bffcf94..1aa2181f050b7 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/components/utils.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/components/utils.tsx @@ -8,7 +8,7 @@ import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useMemo } from 'react'; -import { TimelineType } from '../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../common/api/timeline'; import { Blockquote } from './helpers'; import * as i18n from './translations'; @@ -41,7 +41,7 @@ export const CurrentPatternsMessage = ({ [activePatterns, deadPatterns.length, selectedPatterns, timelineType] ); - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return ( <span data-test-subj="sourcerer-current-patterns-message"> <FormattedMessage @@ -83,7 +83,7 @@ export const NoMatchDataMessage = ({ () => selectedPatterns.filter((p) => !activePatterns.includes(p)).join(', '), [activePatterns, selectedPatterns] ); - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return ( <FormattedMessage id="xpack.securitySolution.indexPatterns.timelineTemplate.noMatchData" @@ -118,7 +118,7 @@ export const BadCurrentPatternsMessage = ({ [selectedPatterns] ); - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return ( <FormattedMessage id="xpack.securitySolution.indexPatterns.timelineTemplate.currentPatternsBad" @@ -147,7 +147,7 @@ export const DeprecatedMessage = ({ onReset: () => void; timelineType: TimelineType; }) => { - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return ( <span data-test-subj="sourcerer-deprecated-message"> <FormattedMessage @@ -180,7 +180,7 @@ export const MissingPatternsMessage = ({ timelineType: TimelineType; onReset: () => void; }) => { - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return ( <span data-test-subj="sourcerer-missing-patterns-message"> <FormattedMessage diff --git a/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.test.ts b/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.test.ts index 082fbf8d1765f..fcf2e86462193 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.test.ts +++ b/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.test.ts @@ -32,7 +32,6 @@ describe('getSourcererDataView', () => { loading: false, id: 'test-id', title: 'test-pattern', - indexFields: {}, fields: {}, patternList: ['test-pattern'], dataView: { diff --git a/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.ts b/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.ts index 31b8484610952..a0c6a82e66c48 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.ts +++ b/x-pack/plugins/security_solution/public/sourcerer/containers/get_sourcerer_data_view.ts @@ -24,7 +24,6 @@ export const getSourcererDataView = async ( loading: false, id: dataViewData.id ?? '', title: dataView.getIndexPattern(), - indexFields: dataView.fields, fields: dataViewData.fields, patternList, dataView: dataViewData, diff --git a/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx b/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx index 01dff26d7fdfc..a4ee0815aac97 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx @@ -15,7 +15,6 @@ import { getDataViewStateFromIndexFields } from '../../common/containers/source/ import { useFetchIndex } from '../../common/containers/source'; import type { State } from '../../common/store/types'; import { sortWithExcludesAtEnd } from '../../../common/utils/sourcerer'; -import { useUnstableSecuritySolutionDataView } from '../experimental/use_unstable_security_solution_data_view'; export const useSourcererDataView = ( scopeId: SourcererScopeName = SourcererScopeName.default @@ -116,12 +115,12 @@ export const useSourcererDataView = ( return dataViewBrowserFields; }, [sourcererDataView.fields, sourcererDataView.patternList]); - const stableSourcererValues = useMemo( + return useMemo( () => ({ browserFields: browserFields(), dataViewId: sourcererDataView.id, indexPattern: { - fields: sourcererDataView.indexFields, + fields: Object.values(sourcererDataView.fields || {}), title: selectedPatterns.join(','), getName: () => selectedPatterns.join(','), }, @@ -145,10 +144,4 @@ export const useSourcererDataView = ( legacyPatterns.length, ] ); - - return useUnstableSecuritySolutionDataView( - scopeId, - // NOTE: data view derived from current implementation is used as a fallback - stableSourcererValues - ); }; diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.test.tsx b/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.test.tsx deleted file mode 100644 index 3aee25657fb0e..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.test.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 React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { useKibana } from '../../../../common/lib/kibana/kibana_react'; -import { useDispatch, useSelector } from 'react-redux'; -import { selectDataView } from '../../redux/actions'; -import { DataViewPicker } from '.'; - -// Mock the required hooks and dependencies -jest.mock('../../../../common/lib/kibana/kibana_react', () => ({ - useKibana: jest.fn(), -})); - -jest.mock('react-redux', () => ({ - useDispatch: jest.fn(), - useSelector: jest.fn(), -})); - -jest.mock('../../redux/actions', () => ({ - selectDataView: jest.fn(), -})); - -jest.mock('@kbn/unified-search-plugin/public', () => ({ - DataViewPicker: jest.fn((props) => ( - <div> - <div>{props.trigger.label}</div> - <button - type="button" - onClick={() => props.onChangeDataView('new-id')} - >{`Change DataView`}</button> - <button type="button" onClick={props.onAddField}> - {`Add Field`} - </button> - <button type="button" onClick={props.onDataViewCreated}> - {`Create New DataView`} - </button> - </div> - )), -})); - -describe('DataViewPicker', () => { - const mockDispatch = jest.fn(); - const mockDataViewEditor = { - openEditor: jest.fn(), - }; - const mockDataViewFieldEditor = { - openEditor: jest.fn(), - }; - const mockData = { - dataViews: { - get: jest.fn().mockResolvedValue({}), - }, - }; - - beforeEach(() => { - (useDispatch as jest.Mock).mockReturnValue(mockDispatch); - (useKibana as jest.Mock).mockReturnValue({ - services: { - dataViewEditor: mockDataViewEditor, - data: mockData, - dataViewFieldEditor: mockDataViewFieldEditor, - }, - }); - (useSelector as jest.Mock).mockReturnValue({ dataViewId: 'test-id' }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('renders the DataviewPicker component', () => { - render(<DataViewPicker />); - expect(screen.getByText('Dataview')).toBeInTheDocument(); - }); - - test('calls dispatch on data view change', () => { - render(<DataViewPicker />); - fireEvent.click(screen.getByText('Change DataView')); - expect(mockDispatch).toHaveBeenCalledWith(selectDataView('new-id')); - }); - - test('opens data view editor when creating a new data view', () => { - render(<DataViewPicker />); - fireEvent.click(screen.getByText('Create New DataView')); - expect(mockDataViewEditor.openEditor).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.tsx b/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.tsx deleted file mode 100644 index 59bfcc9ec98eb..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DataViewPicker as USDataViewPicker } from '@kbn/unified-search-plugin/public'; -import React, { useCallback, useRef, useMemo, memo } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; - -import { useKibana } from '../../../../common/lib/kibana/kibana_react'; -import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../../constants'; -import { selectDataView } from '../../redux/actions'; -import { sourcererAdapterSelector } from '../../redux/selectors'; - -const TRIGGER_CONFIG = { - label: 'Dataview', - color: 'danger', - title: 'Experimental data view picker', - iconType: 'beaker', -} as const; - -export const DataViewPicker = memo(() => { - const dispatch = useDispatch(); - - const { - services: { dataViewEditor, data, dataViewFieldEditor }, - } = useKibana(); - - const closeDataViewEditor = useRef<() => void | undefined>(); - const closeFieldEditor = useRef<() => void | undefined>(); - - // TODO: should this be implemented like that? If yes, we need to source dataView somehow or implement the same thing based on the existing state value. - // const canEditDataView = - // Boolean(dataViewEditor?.userPermissions.editDataView()) || !dataView.isPersisted(); - const canEditDataView = true; - - const { dataViewId } = useSelector(sourcererAdapterSelector); - - const createNewDataView = useCallback(async () => { - closeDataViewEditor.current = await dataViewEditor.openEditor({ - // eslint-disable-next-line no-console - onSave: () => console.log('new data view saved'), - allowAdHocDataView: true, - }); - }, [dataViewEditor]); - - const onFieldEdited = useCallback(() => {}, []); - - const editField = useMemo(() => { - if (!canEditDataView) { - return; - } - return async (fieldName?: string, _uiAction: 'edit' | 'add' = 'edit') => { - if (!dataViewId) { - return; - } - - const dataViewInstance = await data.dataViews.get(dataViewId); - closeFieldEditor.current = await dataViewFieldEditor.openEditor({ - ctx: { - dataView: dataViewInstance, - }, - fieldName, - onSave: async () => { - onFieldEdited(); - }, - }); - }; - }, [canEditDataView, dataViewId, data.dataViews, dataViewFieldEditor, onFieldEdited]); - - const addField = useMemo( - () => (canEditDataView && editField ? () => editField(undefined, 'add') : undefined), - [editField, canEditDataView] - ); - - const handleChangeDataView = useCallback( - (id: string) => { - dispatch(selectDataView(id)); - }, - [dispatch] - ); - - const handleEditDataView = useCallback(() => {}, []); - - return ( - <USDataViewPicker - currentDataViewId={dataViewId || DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID} - trigger={TRIGGER_CONFIG} - onChangeDataView={handleChangeDataView} - onEditDataView={handleEditDataView} - onAddField={addField} - onDataViewCreated={createNewDataView} - /> - ); -}); - -DataViewPicker.displayName = 'DataviewPicker'; diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/readme.md b/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/readme.md deleted file mode 100644 index 1ce2de83ccc05..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Dataview Picker - -A replacement for the Sourcerer component, based on the Discover implementation. diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/constants.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/constants.ts deleted file mode 100644 index 357e38c11c906..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/constants.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 const DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID = 'security-solution-default'; diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/containers/dataview_picker_provider.test.tsx b/x-pack/plugins/security_solution/public/sourcerer/experimental/containers/dataview_picker_provider.test.tsx deleted file mode 100644 index 183d03bc02007..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/containers/dataview_picker_provider.test.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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'; -import { useDispatch } from 'react-redux'; -import { useKibana } from '../../../common/lib/kibana'; -import { DataViewPickerProvider } from './dataview_picker_provider'; -import { - startAppListening, - listenerMiddleware, - createChangeDataviewListener, - createInitDataviewListener, -} from '../redux/listeners'; -import { init } from '../redux/actions'; -import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../constants'; -import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; - -jest.mock('../../../common/lib/kibana', () => ({ - useKibana: jest.fn(), -})); - -jest.mock('react-redux', () => ({ - useDispatch: jest.fn(), -})); - -jest.mock('../redux/listeners', () => ({ - listenerMiddleware: { - clearListeners: jest.fn(), - }, - startAppListening: jest.fn(), - createChangeDataviewListener: jest.fn(), - createInitDataviewListener: jest.fn(), -})); - -describe('DataviewPickerProvider', () => { - const mockDispatch = jest.fn(); - const mockServices = { - dataViews: {} as unknown as DataViewsServicePublic, - }; - - beforeEach(() => { - (useDispatch as jest.Mock).mockReturnValue(mockDispatch); - (useKibana as jest.Mock).mockReturnValue({ services: mockServices }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('starts listeners and dispatches init action on mount', () => { - render( - <DataViewPickerProvider> - <div>{`Test Child`}</div> - </DataViewPickerProvider> - ); - - expect(startAppListening).toHaveBeenCalledWith(createInitDataviewListener({})); - expect(startAppListening).toHaveBeenCalledWith( - createChangeDataviewListener({ dataViewsService: mockServices.dataViews }) - ); - expect(mockDispatch).toHaveBeenCalledWith(init(DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID)); - }); - - test('clears listeners on unmount', () => { - const { unmount } = render( - <DataViewPickerProvider> - <div>{`Test Child`}</div> - </DataViewPickerProvider> - ); - - unmount(); - - expect(listenerMiddleware.clearListeners).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/containers/dataview_picker_provider.tsx b/x-pack/plugins/security_solution/public/sourcerer/experimental/containers/dataview_picker_provider.tsx deleted file mode 100644 index dd6d01f21e73a..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/containers/dataview_picker_provider.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 React, { memo, useEffect, type FC, type PropsWithChildren } from 'react'; -import { useDispatch } from 'react-redux'; - -import { useKibana } from '../../../common/lib/kibana'; -import { - createChangeDataviewListener, - createInitDataviewListener, - listenerMiddleware, - startAppListening, -} from '../redux/listeners'; -import { init } from '../redux/actions'; -import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../constants'; - -// NOTE: this can be spawned multiple times, eq. when you need something like a separate data view picker for a subsection of the app - -// for example, in the timeline. -export const DataViewPickerProvider: FC<PropsWithChildren<{}>> = memo(({ children }) => { - const { services } = useKibana(); - - const dispatch = useDispatch(); - - useEffect(() => { - // NOTE: the goal here is to move all side effects and business logic to Redux, - // so that we only do presentation layer things on React side - for performance reasons and - // to make the state easier to predict. - // see: https://redux-toolkit.js.org/api/createListenerMiddleware#overview - startAppListening(createInitDataviewListener({})); - startAppListening(createChangeDataviewListener({ dataViewsService: services.dataViews })); - - // NOTE: this can be dispatched at any point, with any data view id - dispatch(init(DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID)); - - // NOTE: Clear existing listeners when services change for some reason (they should not) - return () => listenerMiddleware.clearListeners(); - }, [services, dispatch]); - - return <>{children}</>; -}); - -DataViewPickerProvider.displayName = 'DataviewPickerProvider'; diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/is_enabled.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/is_enabled.ts deleted file mode 100644 index caf4b92c1d5d2..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/is_enabled.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. - */ - -/** - * Allows toggling between sourcerer implementations in runtime. Simply set the value in local storage - * to: - * - display the experimental component instead of the stable one - * - use experimental data views hook instead of the stable one - */ -export const isExperimentalSourcererEnabled = () => - !!window.localStorage.getItem('EXPERIMENTAL_SOURCERER_ENABLED'); diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/readme.md b/x-pack/plugins/security_solution/public/sourcerer/experimental/readme.md deleted file mode 100644 index 26894153f2e90..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/readme.md +++ /dev/null @@ -1,21 +0,0 @@ -# Experimental Sourcerer Replacement - -## Introduction - -This directory is a home for Discovery Components based re-implementation of the Sourcerer. - -Currently, it can be enabled and used only by setting the localStorage value, like this: - -``` -window.localStorage.setItem('EXPERIMENTAL_SOURCERER_ENABLED', true) -``` - -The reason for having this feature toggle like this is we want to be able to inspect both implementations side by side, -using the same Kibana instance deployed locally (for now). - -## Architecture - -- Redux based -- Limited use of useEffect or stateful hooks - in favor of thunks and redux middleware (supporting request cancellation and caching) -- Allows multiple instances of the picker - just wrap the subsection of the app with its own DataviewPickerProvider -- Data exposed back to Security Solution is memoized with `reselect` for performance diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/actions.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/actions.ts deleted file mode 100644 index f1b490f1cf734..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/actions.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 { type DataViewSpec } from '@kbn/data-views-plugin/common'; -import { createAction } from '@reduxjs/toolkit'; - -type DataViewId = string; - -export const init = createAction<DataViewId>('init'); -export const selectDataView = createAction<DataViewId>('changeDataView'); -export const setDataViewData = createAction<DataViewSpec>('setDataView'); -export const setPatternList = createAction<string[]>('setPatternList'); diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/listeners.test.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/listeners.test.ts deleted file mode 100644 index 099fbb9dbcf3c..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/listeners.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - init, - selectDataView, - setDataViewData as setDataViewSpec, - setPatternList, -} from './actions'; -import { createInitDataviewListener, createChangeDataviewListener } from './listeners'; -import { isExperimentalSourcererEnabled } from '../is_enabled'; -import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; -import { type ListenerEffectAPI } from '@reduxjs/toolkit'; -import type { AppDispatch } from './listeners'; -import { type State } from '../../../common/store/types'; - -jest.mock('../is_enabled', () => ({ - isExperimentalSourcererEnabled: jest.fn().mockReturnValue(true), -})); - -type ListenerApi = ListenerEffectAPI<State, AppDispatch>; - -describe('Listeners', () => { - describe('createInitDataviewListener', () => { - let listenerOptions: ReturnType<typeof createInitDataviewListener>; - let listenerApi: ListenerApi; - - beforeEach(() => { - listenerOptions = createInitDataviewListener({}); - listenerApi = { - dispatch: jest.fn(), - getState: jest.fn(() => ({ dataViewPicker: { state: 'pristine' } })), - } as unknown as ListenerApi; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('does not dispatch if experimental feature is disabled', async () => { - jest.mocked(isExperimentalSourcererEnabled).mockReturnValue(false); - - await listenerOptions.effect(init('test-view'), listenerApi); - expect(listenerApi.dispatch).not.toHaveBeenCalled(); - }); - - test('does not dispatch if state is not pristine', async () => { - jest.mocked(isExperimentalSourcererEnabled).mockReturnValue(true); - listenerApi.getState = jest.fn(() => ({ - dataViewPicker: { state: 'not_pristine' }, - })) as unknown as ListenerApi['getState']; - - await listenerOptions.effect(init('test-view'), listenerApi); - expect(listenerApi.dispatch).not.toHaveBeenCalled(); - }); - - test('dispatches selectDataView action if state is pristine and experimental feature is enabled', async () => { - jest.mocked(isExperimentalSourcererEnabled).mockReturnValue(true); - await listenerOptions.effect(init('test-id'), listenerApi); - expect(listenerApi.dispatch).toHaveBeenCalledWith(selectDataView('test-id')); - }); - }); - - describe('createChangeDataviewListener', () => { - let listenerOptions: ReturnType<typeof createChangeDataviewListener>; - let listenerApi: ListenerApi; - let dataViewsServiceMock: DataViewsServicePublic; - - beforeEach(() => { - dataViewsServiceMock = { - get: jest.fn(async () => ({ - toSpec: jest.fn(() => ({ id: 'test_spec' })), - getIndexPattern: jest.fn(() => 'index_pattern'), - })), - getExistingIndices: jest.fn(async () => ['pattern1', 'pattern2']), - } as unknown as DataViewsServicePublic; - - listenerOptions = createChangeDataviewListener({ dataViewsService: dataViewsServiceMock }); - listenerApi = { - dispatch: jest.fn(), - } as unknown as ListenerApi; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('fetches data view and dispatches setDataViewSpec and setPatternList actions', async () => { - await listenerOptions.effect(selectDataView('test_id'), listenerApi); - - expect(dataViewsServiceMock.get).toHaveBeenCalledWith('test_id', true, false); - expect(listenerApi.dispatch).toHaveBeenCalledWith(setDataViewSpec({ id: 'test_spec' })); - expect(dataViewsServiceMock.getExistingIndices).toHaveBeenCalledWith(['index_pattern']); - expect(listenerApi.dispatch).toHaveBeenCalledWith(setPatternList(['pattern1', 'pattern2'])); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/listeners.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/listeners.ts deleted file mode 100644 index 359794eca331a..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/listeners.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; -import { - createListenerMiddleware, - type ActionCreator, - type ListenerEffectAPI, -} from '@reduxjs/toolkit'; -import type { ListenerPredicate } from '@reduxjs/toolkit/dist/listenerMiddleware/types'; -import type { Action, Store } from 'redux'; - -import { ensurePatternFormat } from '../../../../common/utils/sourcerer'; -import { isExperimentalSourcererEnabled } from '../is_enabled'; -import { - init, - selectDataView, - setDataViewData as setDataViewSpec, - setPatternList, -} from './actions'; -import { type State } from '../../../common/store/types'; - -export type AppDispatch = Store<State, Action>['dispatch']; - -export type DatapickerActions = ReturnType<typeof selectDataView>; - -// NOTE: types below exist because we are using redux-toolkit version lower than 2.x -// in v2, there are TS helpers that make it easy to setup overrides that are necessary here. -export interface ListenerOptions { - // Match with a function accepting action and state. This is broken in v1.x, - // the predicate is always required - predicate?: ListenerPredicate<DatapickerActions, State>; - // Match action by type - type?: string; - // Exact action type match based on the RTK action creator - actionCreator?: ActionCreator<DatapickerActions>; - // An effect to call - effect: (action: DatapickerActions, api: ListenerEffectAPI<State, AppDispatch>) => Promise<void>; -} - -/** - * This is the proposed way of handling side effects within sourcerer code. We will no longer rely on useEffect for doing things like - * enriching the store with data fetched asynchronously in response to user doing something. - * Thunks are also considered for simpler flows but this has the advantage of cancellation support through `listnerApi` below. - */ - -export type ListenerCreator<TDependencies> = ( - // Only specify a subset of required services here, so that it is easier to mock and therefore test the listener - dependencies: TDependencies -) => ListenerOptions; - -// NOTE: this should only be executed once in the application lifecycle, to LAZILY setup the component data -export const createInitDataviewListener: ListenerCreator<{}> = (): ListenerOptions => { - return { - actionCreator: init, - effect: async (action, listenerApi) => { - // WARN: Skip the init call if the experimental implementation is disabled - if (!isExperimentalSourcererEnabled()) { - return; - } - // NOTE: We should only run this once, when particular sourcerer instance is in pristine state (not touched by the user) - if (listenerApi.getState().dataViewPicker.state !== 'pristine') { - return; - } - - // NOTE: dispatch the regular change listener - listenerApi.dispatch(selectDataView(action.payload)); - }, - }; -}; - -// NOTE: this listener is executed whenever user decides to select dataview from the picker -export const createChangeDataviewListener: ListenerCreator<{ - dataViewsService: DataViewsServicePublic; -}> = ({ dataViewsService }): ListenerOptions => { - return { - actionCreator: selectDataView, - effect: async (action, listenerApi) => { - const dataViewId = action.payload; - const refreshFields = false; - - const dataView = await dataViewsService.get(dataViewId, true, refreshFields); - const dataViewData = dataView.toSpec(); - listenerApi.dispatch(setDataViewSpec(dataViewData)); - - const defaultPatternsList = ensurePatternFormat(dataView.getIndexPattern().split(',')); - const patternList = await dataViewsService.getExistingIndices(defaultPatternsList); - listenerApi.dispatch(setPatternList(patternList)); - }, - }; -}; - -export const listenerMiddleware = createListenerMiddleware(); - -// NOTE: register side effect listeners -export const startAppListening = listenerMiddleware.startListening as unknown as ( - options: ListenerOptions -) => void; diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/reducer.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/reducer.ts deleted file mode 100644 index d8d626a1141ce..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/reducer.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; 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 { createReducer } from '@reduxjs/toolkit'; - -import { DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID } from '../constants'; - -import { selectDataView, setDataViewData, setPatternList } from './actions'; - -export interface SelectedDataViewState { - dataView: DataViewSpec; - patternList: string[]; - /** - * There are several states the picker can be in internally: - * - pristine - not initialized yet - * - loading - * - error - some kind of a problem during data init - * - ready - ready to provide index information to the client - */ - state: 'pristine' | 'loading' | 'error' | 'ready'; -} - -export const initialDataView: DataViewSpec = { - id: DEFAULT_SECURITY_SOLUTION_DATA_VIEW_ID, - title: '', - fields: {}, -}; - -export const initialState: SelectedDataViewState = { - dataView: initialDataView, - state: 'pristine', - patternList: [], -}; - -export const reducer = createReducer(initialState, (builder) => { - builder.addCase(selectDataView, (state) => { - state.state = 'loading'; - }); - - builder.addCase(setDataViewData, (state, action) => { - state.dataView = action.payload; - }); - - builder.addCase(setPatternList, (state, action) => { - state.patternList = action.payload; - state.state = 'ready'; - }); -}); - -export type DataviewPickerState = ReturnType<typeof reducer>; diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/selectors.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/selectors.ts deleted file mode 100644 index cdfc3a9882b0d..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/redux/selectors.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { createSelector } from '@reduxjs/toolkit'; -import type { SelectedDataView } from '../../store/model'; -import { type State } from '../../../common/store/types'; - -/** - * Compatibility layer / adapter for legacy selector consumers. - * It is used in useSecuritySolutionDataView hook as alternative data source (behind a flag) - */ -export const sourcererAdapterSelector = createSelector( - [(state: State) => state.dataViewPicker], - (dataViewPicker): SelectedDataView => { - return { - loading: dataViewPicker.state === 'loading', - dataViewId: dataViewPicker.dataView.id || '', - patternList: dataViewPicker.patternList, - indicesExist: true, - browserFields: {}, - activePatterns: dataViewPicker.patternList, - runtimeMappings: {}, - selectedPatterns: dataViewPicker.patternList, - indexPattern: { fields: [], title: dataViewPicker.dataView.title || '' }, - sourcererDataView: {}, - }; - } -); diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/use_unstable_security_solution_data_view.ts b/x-pack/plugins/security_solution/public/sourcerer/experimental/use_unstable_security_solution_data_view.ts deleted file mode 100644 index 4bca209b8e50a..0000000000000 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/use_unstable_security_solution_data_view.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 { useMemo } from 'react'; -import { useSelector } from 'react-redux'; - -import { type SourcererScopeName, type SelectedDataView } from '../store/model'; - -import { isExperimentalSourcererEnabled } from './is_enabled'; -import { sourcererAdapterSelector } from './redux/selectors'; - -/** - * WARN: FOR INTERNAL USE ONLY - * This hook provides data for experimental Sourcerer replacement in Security Solution. - * Do not use in client code as the API will change frequently. - * It will be extended in the future, covering more and more functionality from the current sourcerer. - */ -export const useUnstableSecuritySolutionDataView = ( - _scopeId: SourcererScopeName, - fallbackDataView: SelectedDataView -): SelectedDataView => { - const dataView: SelectedDataView = useSelector(sourcererAdapterSelector); - - const dataViewWithFallbacks: SelectedDataView = useMemo(() => { - return { - ...dataView, - // NOTE: temporary values sourced from the fallback. Will be replaced in the near future. - browserFields: fallbackDataView.browserFields, - sourcererDataView: fallbackDataView.sourcererDataView, - }; - }, [dataView, fallbackDataView.browserFields, fallbackDataView.sourcererDataView]); - - return isExperimentalSourcererEnabled() ? dataViewWithFallbacks : fallbackDataView; -}; diff --git a/x-pack/plugins/security_solution/public/sourcerer/store/model.ts b/x-pack/plugins/security_solution/public/sourcerer/store/model.ts index 15c1d2f6a27a1..6447eff3c1ee7 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/store/model.ts +++ b/x-pack/plugins/security_solution/public/sourcerer/store/model.ts @@ -6,7 +6,7 @@ */ import type { BrowserFields } from '@kbn/timelines-plugin/common'; -import { EMPTY_BROWSER_FIELDS, EMPTY_INDEX_FIELDS } from '@kbn/timelines-plugin/common'; +import { EMPTY_BROWSER_FIELDS } from '@kbn/timelines-plugin/common'; import type { DataViewSpec } from '@kbn/data-views-plugin/public'; import type { RuntimeFieldSpec, RuntimePrimitiveTypes } from '@kbn/data-views-plugin/common'; import type { SecuritySolutionDataViewBase } from '../../common/types'; @@ -69,10 +69,6 @@ export interface SourcererDataView extends KibanaDataView { * category, description, format * indices the field is included in etc*/ browserFields: BrowserFields; - /** - * @deprecated use sourcererDataView.fields - * comes from dataView.fields.toSpec() */ - indexFields: SecuritySolutionDataViewBase['fields']; fields: DataViewSpec['fields'] | undefined; /** set when data view fields are fetched */ loading: boolean; @@ -96,7 +92,7 @@ export interface SelectedDataView { /** * @deprecated use EcsFlat or fields / indexFields from data view */ - browserFields: SourcererDataView['browserFields']; + browserFields: BrowserFields; dataViewId: string | null; // null if legacy pre-8.0 timeline /** * @deprecated use sourcererDataView @@ -166,7 +162,6 @@ export const initSourcererScope: Omit<SourcererScope, 'id'> = { export const initDataView: SourcererDataView & { id: string; error?: unknown } = { browserFields: EMPTY_BROWSER_FIELDS, id: '', - indexFields: EMPTY_INDEX_FIELDS, fields: undefined, loading: false, patternList: [], diff --git a/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts b/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts index 4ba624440ddc2..64e616d0a1ea9 100644 --- a/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts +++ b/x-pack/plugins/security_solution/public/threat_intelligence/use_investigate_in_timeline.ts @@ -11,7 +11,7 @@ import { timelineDefaults } from '../timelines/store/defaults'; import { APP_UI_ID } from '../../common/constants'; import type { DataProvider } from '../../common/types'; import { TimelineId } from '../../common/types/timeline'; -import { TimelineType } from '../../common/api/timeline'; +import { TimelineTypeEnum } from '../../common/api/timeline'; import { useStartTransaction } from '../common/lib/apm/use_start_transaction'; import { timelineActions } from '../timelines/store'; import { useCreateTimeline } from '../timelines/hooks/use_create_timeline'; @@ -56,7 +56,7 @@ export const useInvestigateInTimeline = ({ const clearActiveTimeline = useCreateTimeline({ timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); const updateTimeline = useUpdateTimeline(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.test.tsx index 39f40dc74ed9e..54427b25e74ed 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { mockTimelineModel, TestProviders } from '../../../common/mock'; import { AddToFavoritesButton } from '.'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../common/api/timeline'; const mockGetState = jest.fn(); jest.mock('react-redux', () => { @@ -41,7 +41,7 @@ describe('AddToFavoritesButton', () => { it('should render favorite button enabled and unchecked', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, }); const { getByTestId, queryByTestId } = renderAddFavoritesButton(); @@ -57,7 +57,7 @@ describe('AddToFavoritesButton', () => { it('should render favorite button disabled for a draft timeline', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }); const { getByTestId } = renderAddFavoritesButton(); @@ -68,7 +68,7 @@ describe('AddToFavoritesButton', () => { it('should render favorite button disabled for an immutable timeline', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }); const { getByTestId } = renderAddFavoritesButton(); @@ -91,7 +91,7 @@ describe('AddToFavoritesButton', () => { it('should use id for guided tour if prop is true', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, }); const { getByTestId } = renderAddFavoritesButton(true); diff --git a/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.tsx index 06055802e2fff..c0c89f1e08a2d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/add_to_favorites/index.tsx @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import type { State } from '../../../common/store'; import { selectTimelineById } from '../../store/selectors'; import { timelineActions } from '../../store'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../common/api/timeline'; import { TIMELINE_TOUR_CONFIG_ANCHORS } from '../timeline/tour/step_config'; const ADD_TO_FAVORITES = i18n.translate( @@ -51,7 +51,7 @@ export const AddToFavoritesButton = React.memo<AddToFavoritesButtonProps>( selectTimelineById(state, timelineId) ); - const isTimelineDraftOrImmutable = status !== TimelineStatus.active; + const isTimelineDraftOrImmutable = status !== TimelineStatusEnum.active; const label = isFavorite ? REMOVE_FROM_FAVORITES : ADD_TO_FAVORITES; const handleClick = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/components/bottom_bar/add_timeline_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/bottom_bar/add_timeline_button.tsx index 15d8e188370dc..3d6d3677440cb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/bottom_bar/add_timeline_button.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/bottom_bar/add_timeline_button.tsx @@ -9,7 +9,7 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiPopover } import React, { useCallback, useMemo, useState } from 'react'; import * as i18n from './translations'; import { useCreateTimeline } from '../../hooks/use_create_timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { OpenTimelineModal } from '../open_timeline/open_timeline_modal'; import type { ActionTimelineToShow } from '../open_timeline/types'; @@ -39,12 +39,12 @@ export const AddTimelineButton = React.memo<AddTimelineButtonComponentProps>(({ const createNewTimeline = useCreateTimeline({ timelineId, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, onClick: togglePopover, }); const createNewTimelineTemplate = useCreateTimeline({ timelineId, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, onClick: togglePopover, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx index fa1663c6b6364..ada9bfd8e1aeb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { DataProviderType } from '../../../../common/api/timeline'; +import { DataProviderTypeEnum } from '../../../../common/api/timeline'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { @@ -144,7 +144,7 @@ describe('helpers', () => { label: 'is', }, ], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }) ).toBe(true); }); @@ -163,7 +163,7 @@ describe('helpers', () => { label: 'is', }, ], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }) ).toBe(false); }); @@ -182,7 +182,7 @@ describe('helpers', () => { label: 'is one of', }, ], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }) ).toBe(false); }); @@ -201,7 +201,7 @@ describe('helpers', () => { label: '', }, ], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }) ).toBe(false); }); @@ -220,7 +220,7 @@ describe('helpers', () => { label: 'invalid-operator', }, ], - type: DataProviderType.default, + type: DataProviderTypeEnum.default, }) ).toBe(false); }); @@ -239,7 +239,7 @@ describe('helpers', () => { label: 'is one of', }, ], - type: DataProviderType.template, + type: DataProviderTypeEnum.template, }) ).toBe(false); }); @@ -258,7 +258,7 @@ describe('helpers', () => { label: 'is not one of', }, ], - type: DataProviderType.template, + type: DataProviderTypeEnum.template, }) ).toBe(false); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx index f215a790414d4..8de2c2f8e849c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/helpers.tsx @@ -9,7 +9,7 @@ import { findIndex } from 'lodash/fp'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import type { FieldCategory } from '@kbn/timelines-plugin/common/search_strategy'; -import { DataProviderType } from '../../../../common/api/timeline'; +import { type DataProviderType, DataProviderTypeEnum } from '../../../../common/api/timeline'; import type { BrowserFields } from '../../../common/containers/source'; import { getAllFieldsByName } from '../../../common/containers/source'; @@ -83,7 +83,7 @@ export const selectionsAreValid = ({ const fieldIsValid = browserFields && getAllFieldsByName(browserFields)[fieldId] != null; const operatorIsValid = findIndex((o) => o.label === operator, operatorLabels) !== -1; const isOneOfOperatorSelectionWithTemplate = - type === DataProviderType.template && + type === DataProviderTypeEnum.template && (operator === i18n.IS_ONE_OF || operator === i18n.IS_NOT_ONE_OF); return fieldIsValid && operatorIsValid && !isOneOfOperatorSelectionWithTemplate; diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx index edcadfb2d391a..806fb1f5b0410 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.test.tsx @@ -12,11 +12,11 @@ import React from 'react'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { TestProviders } from '../../../common/mock'; import { - DataProviderType, IS_OPERATOR, EXISTS_OPERATOR, IS_ONE_OF_OPERATOR, } from '../timeline/data_providers/data_provider'; +import { DataProviderTypeEnum } from '../../../../common/api/timeline'; import { StatefulEditDataProvider } from '.'; @@ -393,7 +393,7 @@ describe('StatefulEditDataProvider', () => { providerId={`hosts-table-hostName-${value}`} timelineId={timelineId} value={value} - type={DataProviderType.template} + type={DataProviderTypeEnum.template} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx index ff960c4f60997..75a4ae66f4697 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/edit_data_provider/index.tsx @@ -24,7 +24,7 @@ import type { BrowserFields } from '../../../common/containers/source'; import type { PrimitiveOrArrayOfPrimitives } from '../../../common/lib/kuery'; import type { OnDataProviderEdited } from '../timeline/events'; import type { QueryOperator } from '../timeline/data_providers/data_provider'; -import { DataProviderType } from '../timeline/data_providers/data_provider'; +import { type DataProviderType, DataProviderTypeEnum } from '../../../../common/api/timeline'; import { getCategorizedFieldNames, @@ -86,7 +86,7 @@ export const StatefulEditDataProvider = React.memo<Props>( providerId, timelineId, value, - type = DataProviderType.default, + type = DataProviderTypeEnum.default, }) => { const [updatedField, setUpdatedField] = useState<EuiComboBoxOptionOption[]>([{ label: field }]); const [updatedOperator, setUpdatedOperator] = useState<EuiComboBoxOptionOption[]>( @@ -105,7 +105,7 @@ export const StatefulEditDataProvider = React.memo<Props>( const showValueInput = useMemo( () => - type !== DataProviderType.template && + type !== DataProviderTypeEnum.template && updatedOperator.length > 0 && updatedOperator[0].label !== i18n.EXISTS && updatedOperator[0].label !== i18n.DOES_NOT_EXIST && @@ -137,7 +137,7 @@ export const StatefulEditDataProvider = React.memo<Props>( (selectedField: EuiComboBoxOptionOption[]) => { setUpdatedField(selectedField); - if (type === DataProviderType.template) { + if (type === DataProviderTypeEnum.template) { setUpdatedValue(`{${selectedField[0].label}}`); } @@ -256,7 +256,7 @@ export const StatefulEditDataProvider = React.memo<Props>( </EuiFormRow> )} - {showComboBoxInput && type !== DataProviderType.template && ( + {showComboBoxInput && type !== DataProviderTypeEnum.template && ( <EuiFormRow label={i18n.VALUE_LABEL}> <ControlledComboboxInput onChangeCallback={onValueChange} value={value} /> </EuiFormRow> @@ -268,7 +268,7 @@ export const StatefulEditDataProvider = React.memo<Props>( </EuiFlexItem> <EuiFlexItem grow={false}> - {type === DataProviderType.template && showComboBoxInput && ( + {type === DataProviderTypeEnum.template && showComboBoxInput && ( <> <EuiCallOut color="warning" diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx index a5a22fd70f563..0dc8c4367e4fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx @@ -236,7 +236,7 @@ describe('useFieldBrowserOptions', () => { it('should dispatch the proper actions when a field is removed', async () => { let onDelete: ((fields: string[]) => void) | undefined; useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView); - useKibanaMock().services.dataViewFieldEditor.openDeleteModal = (options) => { + useKibanaMock().services.dataViewFieldEditor.openDeleteModal = async (options) => { onDelete = options.onDelete; return () => {}; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/attach_to_case_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/attach_to_case_button.tsx index fb9f6d9edcbc7..770d4092df945 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/attach_to_case_button.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/attach_to_case_button.tsx @@ -16,7 +16,7 @@ import { APP_ID, APP_UI_ID } from '../../../../../common/constants'; import { setInsertTimeline, showTimeline } from '../../../store/actions'; import { useKibana } from '../../../../common/lib/kibana'; import { TimelineId } from '../../../../../common/types/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { getCreateCaseUrl, getCaseDetailsUrl } from '../../../../common/components/link_to'; import { SecurityPageName } from '../../../../app/types'; import * as i18n from './translations'; @@ -117,7 +117,9 @@ export const AttachToCaseButton = React.memo<AttachToCaseButtonProps>(({ timelin <EuiButtonEmpty iconType="arrowDown" iconSide="right" - disabled={timelineStatus === TimelineStatus.draft || timelineType !== TimelineType.default} + disabled={ + timelineStatus === TimelineStatusEnum.draft || timelineType !== TimelineTypeEnum.default + } data-test-subj="timeline-modal-attach-to-case-dropdown-button" onClick={togglePopover} > diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx index 355668f2b952e..e1fdbe8817041 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.test.tsx @@ -13,7 +13,7 @@ import { timelineActions } from '../../../store'; import { defaultHeaders } from '../../timeline/body/column_headers/default_headers'; import { TestProviders } from '../../../../common/mock'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { RowRendererId } from '../../../../../common/api/timeline'; +import { RowRendererValues } from '../../../../../common/api/timeline'; import { defaultUdtHeaders } from '../../timeline/unified_components/default_headers'; jest.mock('../../../../common/components/discover_in_timeline/use_discover_in_timeline_context'); @@ -73,7 +73,7 @@ describe('NewTimelineButton', () => { show: true, timelineType: 'default', updated: undefined, - excludedRowRendererIds: [...Object.keys(RowRendererId)], + excludedRowRendererIds: RowRendererValues, }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.tsx index acbf2c12595b9..4b9b8ef136ce0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/new_timeline_button.tsx @@ -8,7 +8,7 @@ import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import React, { useMemo, useState, useCallback } from 'react'; import { useCreateTimeline } from '../../../hooks/use_create_timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import * as i18n from './translations'; interface NewTimelineButtonProps { @@ -27,12 +27,12 @@ export const NewTimelineButton = React.memo(({ timelineId }: NewTimelineButtonPr const createNewTimeline = useCreateTimeline({ timelineId, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, onClick: togglePopover, }); const createNewTimelineTemplate = useCreateTimeline({ timelineId, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, onClick: togglePopover, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.test.tsx index 9ca5f236f23fe..e0a48cebf4209 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/open_timeline_button.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { OpenTimelineButton } from './open_timeline_button'; import { TestProviders } from '../../../../common/mock/test_providers'; import { useParams } from 'react-router-dom'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useSourcererDataView } from '../../../../sourcerer/containers'; import { useTimelineStatus } from '../../open_timeline/use_timeline_status'; @@ -60,7 +60,7 @@ describe('OpenTimelineButton', () => { }); it('should open the modal after clicking on the button', async () => { - (useParams as jest.Mock).mockReturnValue({ tabName: TimelineType.template }); + (useParams as jest.Mock).mockReturnValue({ tabName: TimelineTypeEnum.template }); (useStartTransaction as jest.Mock).mockReturnValue({ startTransaction: jest.fn() }); (useSourcererDataView as jest.Mock).mockReturnValue({ dataViewId: '', selectedPatterns: [] }); (useTimelineStatus as jest.Mock).mockReturnValue({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.test.tsx index fa5d22666ff46..2f923a12e3f33 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.test.tsx @@ -10,7 +10,7 @@ import { render, waitFor } from '@testing-library/react'; import { SaveTimelineButton } from './save_timeline_button'; import { mockTimelineModel, TestProviders } from '../../../../common/mock'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; -import { TimelineStatus } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../../common/api/timeline'; import { useCreateTimeline } from '../../../hooks/use_create_timeline'; jest.mock('../../../../common/components/user_privileges'); @@ -49,7 +49,7 @@ describe('SaveTimelineButton', () => { }); mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, isSaving: false, }); (useCreateTimeline as jest.Mock).mockReturnValue({}); @@ -68,7 +68,7 @@ describe('SaveTimelineButton', () => { }); mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, isSaving: false, }); (useCreateTimeline as jest.Mock).mockReturnValue({}); @@ -98,7 +98,7 @@ describe('SaveTimelineButton', () => { (useUserPrivileges as jest.Mock).mockReturnValue({ kibanaSecuritySolutionsPrivileges: { crud: true }, }); - mockGetState.mockReturnValue({ ...mockTimelineModel, status: TimelineStatus.immutable }); + mockGetState.mockReturnValue({ ...mockTimelineModel, status: TimelineStatusEnum.immutable }); const { getByTestId } = renderSaveTimelineButton(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.tsx index 9d332d66fb2f5..f89471e36827f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_button.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useState } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; import { useSelector } from 'react-redux'; -import { TimelineStatus } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../../common/api/timeline'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { SaveTimelineModal } from './save_timeline_modal'; import * as i18n from './translations'; @@ -41,8 +41,8 @@ export const SaveTimelineButton = React.memo<SaveTimelineButtonProps>(({ timelin const { status, isSaving } = useSelector((state: State) => selectTimelineById(state, timelineId)); - const canSaveTimeline = canEditTimelinePrivilege && status !== TimelineStatus.immutable; - const isUnsaved = status === TimelineStatus.draft; + const canSaveTimeline = canEditTimelinePrivilege && status !== TimelineStatusEnum.immutable; + const isUnsaved = status === TimelineStatusEnum.draft; const unauthorizedMessage = canSaveTimeline ? null : i18n.CALL_OUT_UNAUTHORIZED_MSG; return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.test.tsx index 23885c087c24d..c4b6bcb4cf92c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { mockTimelineModel, TestProviders } from '../../../../common/mock'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { SaveTimelineModal } from './save_timeline_modal'; import * as i18n from './translations'; @@ -73,8 +73,8 @@ describe('SaveTimelineModal', () => { it('should show correct header for save timeline template modal', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.draft, - timelineType: TimelineType.template, + status: TimelineStatusEnum.draft, + timelineType: TimelineTypeEnum.template, }); const { getByTestId } = renderSaveTimelineModal(); @@ -88,7 +88,7 @@ describe('SaveTimelineModal', () => { it('should render all the dom elements of the modal', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }); const { getByTestId, queryByTestId } = renderSaveTimelineModal(); @@ -120,7 +120,7 @@ describe('SaveTimelineModal', () => { it('should show correct header for edit timeline template modal', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, }); const { getByTestId } = renderSaveTimelineModal(); @@ -131,8 +131,8 @@ describe('SaveTimelineModal', () => { it('should show correct header for save timeline template modal', () => { mockGetState.mockReturnValue({ - status: TimelineStatus.active, - timelineType: TimelineType.template, + status: TimelineStatusEnum.active, + timelineType: TimelineTypeEnum.template, }); const { getByTestId } = renderSaveTimelineModal(); @@ -147,9 +147,9 @@ describe('SaveTimelineModal', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, description: 'my description', - status: TimelineStatus.active, + status: TimelineStatusEnum.active, title: 'my timeline', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); const { getByTestId } = renderSaveTimelineModal(); @@ -179,7 +179,7 @@ describe('SaveTimelineModal', () => { it('should show discard timeline in the close button', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }); const { getByTestId } = renderSaveTimelineModal(true); @@ -192,8 +192,8 @@ describe('SaveTimelineModal', () => { it('should show discard timeline template in the close button', () => { mockGetState.mockReturnValue({ ...mockTimelineModel, - timelineType: TimelineType.template, - status: TimelineStatus.draft, + timelineType: TimelineTypeEnum.template, + status: TimelineStatusEnum.draft, }); const { getByTestId } = renderSaveTimelineModal(true); diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.tsx b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.tsx index 42bcbff20f322..67409a91c8542 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/save_timeline_modal.tsx @@ -26,7 +26,7 @@ import type { State } from '../../../../common/store'; import { selectTimelineById } from '../../../store/selectors'; import { getUseField, Field, Form, useForm } from '../../../../shared_imports'; import { TimelineId } from '../../../../../common/types/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { timelineActions } from '../../../store'; import * as commonI18n from '../../timeline/properties/translations'; import * as i18n from './translations'; @@ -78,13 +78,13 @@ export const SaveTimelineModal = React.memo<SaveTimelineModalProps>( [] ); - const isUnsaved = status === TimelineStatus.draft; + const isUnsaved = status === TimelineStatusEnum.draft; const prevIsSaving = usePrevious(isSaving); // Resetting the timeline by replacing the active one with a new empty one const resetTimeline = useCreateTimeline({ timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); const handleSubmit = useCallback( @@ -139,8 +139,8 @@ export const SaveTimelineModal = React.memo<SaveTimelineModalProps>( }, [closeSaveTimeline, resetTimeline, showWarning]); const closeModalText = useMemo(() => { - if (status === TimelineStatus.draft && showWarning) { - return timelineType === TimelineType.template + if (status === TimelineStatusEnum.draft && showWarning) { + return timelineType === TimelineTypeEnum.template ? i18n.DISCARD_TIMELINE_TEMPLATE : i18n.DISCARD_TIMELINE; } @@ -149,11 +149,11 @@ export const SaveTimelineModal = React.memo<SaveTimelineModalProps>( const modalHeader = useMemo( () => - status === TimelineStatus.draft - ? timelineType === TimelineType.template + status === TimelineStatusEnum.draft + ? timelineType === TimelineTypeEnum.template ? i18n.SAVE_TIMELINE_TEMPLATE : i18n.SAVE_TIMELINE - : timelineType === TimelineType.template + : timelineType === TimelineTypeEnum.template ? i18n.NAME_TIMELINE_TEMPLATE : i18n.SAVE_TIMELINE, [status, timelineType] @@ -161,8 +161,8 @@ export const SaveTimelineModal = React.memo<SaveTimelineModalProps>( const saveButtonTitle = useMemo( () => - status === TimelineStatus.draft && showWarning - ? timelineType === TimelineType.template + status === TimelineStatusEnum.draft && showWarning + ? timelineType === TimelineTypeEnum.template ? i18n.SAVE_TIMELINE_TEMPLATE : i18n.SAVE_TIMELINE : i18n.SAVE, @@ -182,7 +182,7 @@ export const SaveTimelineModal = React.memo<SaveTimelineModalProps>( disabled: isSaving, spellCheck: true, placeholder: - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? commonI18n.UNTITLED_TEMPLATE : commonI18n.UNTITLED_TIMELINE, }), diff --git a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts index 0a388fd6cdbfd..9ffc2f2f00ad1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/modal/actions/translations.ts @@ -6,8 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import type { TimelineTypeLiteral } from '../../../../../common/api/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; export const NEW_TIMELINE_BTN = i18n.translate( 'xpack.securitySolution.timeline.modal.newTimelineBtn', @@ -115,10 +114,10 @@ export const CLOSE_MODAL = i18n.translate( } ); -export const UNSAVED_TIMELINE_WARNING = (timelineType: TimelineTypeLiteral) => +export const UNSAVED_TIMELINE_WARNING = (timelineType: TimelineType) => i18n.translate('xpack.securitySolution.timeline.saveTimeline.modal.warning.title', { values: { - timeline: timelineType === TimelineType.template ? 'timeline template' : 'timeline', + timeline: timelineType === TimelineTypeEnum.template ? 'timeline template' : 'timeline', }, defaultMessage: 'You have an unsaved {timeline}. Do you wish to save it?', }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx index d343bb1371742..8e08e4b957d64 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.test.tsx @@ -11,7 +11,11 @@ import { NewTimelineButton } from '.'; import { TimelineId } from '../../../../common/types'; import { timelineActions } from '../../store'; import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context'; -import { RowRendererId, TimelineType } from '../../../../common/api/timeline'; +import { + RowRendererValues, + type TimelineType, + TimelineTypeEnum, +} from '../../../../common/api/timeline'; import { TestProviders } from '../../../common/mock'; import { defaultUdtHeaders } from '../timeline/unified_components/default_headers'; @@ -43,7 +47,7 @@ describe('NewTimelineButton', () => { const spy = jest.spyOn(timelineActions, 'createTimeline'); const { getByTestId, queryByTestId, queryByText } = renderNewTimelineButton( - TimelineType.default + TimelineTypeEnum.default ); const button = getByTestId('timelines-page-create-new-timeline'); @@ -62,9 +66,9 @@ describe('NewTimelineButton', () => { id: TimelineId.active, indexNames: selectedPatterns, show: true, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, updated: undefined, - excludedRowRendererIds: [...Object.values(RowRendererId)], + excludedRowRendererIds: RowRendererValues, }); }); }); @@ -73,7 +77,7 @@ describe('NewTimelineButton', () => { const spy = jest.spyOn(timelineActions, 'createTimeline'); const { getByTestId, queryByTestId, queryByText } = renderNewTimelineButton( - TimelineType.template + TimelineTypeEnum.template ); const button = getByTestId('timelines-page-create-new-timeline-template'); @@ -92,7 +96,7 @@ describe('NewTimelineButton', () => { id: TimelineId.active, indexNames: selectedPatterns, show: true, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, updated: undefined, excludedRowRendererIds: [], }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.tsx index 2bf38ecf3d5fd..a986f532484f2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/new_timeline/index.tsx @@ -10,7 +10,7 @@ import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { TimelineId } from '../../../../common/types'; import { useCreateTimeline } from '../../hooks/use_create_timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../common/api/timeline'; const NEW_TIMELINE = i18n.translate('xpack.securitySolution.timelines.newTimelineButtonLabel', { defaultMessage: 'Create new Timeline', @@ -41,7 +41,7 @@ export const NewTimelineButton = React.memo<NewTimelineButtonProps>(({ type }) = }); const dataTestSubj = `timelines-page-create-new-${ - type === TimelineType.default ? 'timeline' : 'timeline-template' + type === TimelineTypeEnum.default ? 'timeline' : 'timeline-template' }`; const handleCreateNewTimeline = useCallback(async () => { @@ -55,7 +55,7 @@ export const NewTimelineButton = React.memo<NewTimelineButtonProps>(({ type }) = onClick={handleCreateNewTimeline} fill > - {type === TimelineType.default ? NEW_TIMELINE : NEW_TEMPLATE_TIMELINE} + {type === TimelineTypeEnum.default ? NEW_TIMELINE : NEW_TEMPLATE_TIMELINE} </EuiButton> ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx index f5006589310c1..3bf635fe53a8b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import '../../../../common/mock/formatted_relative'; import { NoteCards } from '.'; -import { TimelineStatus } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../../common/api/timeline'; import { TestProviders } from '../../../../common/mock'; import type { TimelineResultNote } from '../../open_timeline/types'; import { TimelineId } from '../../../../../common/types'; @@ -58,7 +58,7 @@ describe('NoteCards', () => { getNewNoteId: jest.fn(), notes: [], showAddNote: true, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, toggleShowAddNote: jest.fn(), updateNote: jest.fn(), timelineId: TimelineId.test, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts index 3a8c347e33951..df761c8854f41 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/__mocks__/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; export const mockTimeline = { data: { @@ -144,9 +144,9 @@ export const mockTimeline = { noteIds: [], pinnedEventIds: [], pinnedEventsSaveObject: [], - status: TimelineStatus.active, + status: TimelineStatusEnum.active, title: 'my timeline', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, savedQueryId: null, @@ -398,7 +398,7 @@ export const mockTemplate = { noteIds: [], pinnedEventIds: [], pinnedEventsSaveObject: [], - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, title: 'Generic Process Timeline', timelineType: 'template', templateTimelineId: 'cd55e52b-7bce-4887-88e2-f1ece4c75447', diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/constants.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/constants.ts index 9a261c415545a..458246c097e01 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/constants.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/constants.ts @@ -5,5 +5,7 @@ * 2.0. */ -export const DEFAULT_SORT_FIELD = 'updated'; +import { SortFieldTimelineEnum } from '../../../../common/api/timeline'; + +export const DEFAULT_SORT_FIELD = SortFieldTimelineEnum.updated; export const DEFAULT_SORT_DIRECTION = 'desc'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx index 287266eeb1065..8b5a72917d9a0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx @@ -12,7 +12,7 @@ import { useParams } from 'react-router-dom'; import { DeleteTimelineModal } from './delete_timeline_modal'; import * as i18n from '../translations'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; jest.mock('react-router-dom', () => { const actual = jest.requireActual('react-router-dom'); @@ -24,7 +24,7 @@ jest.mock('react-router-dom', () => { describe('DeleteTimelineModal', () => { beforeAll(() => { - (useParams as jest.Mock).mockReturnValue({ tabName: TimelineType.default }); + (useParams as jest.Mock).mockReturnValue({ tabName: TimelineTypeEnum.default }); }); test('it renders the expected title when a timeline is selected', () => { @@ -134,7 +134,7 @@ describe('DeleteTimelineModal', () => { describe('DeleteTimelineTemplateModal', () => { beforeAll(() => { - (useParams as jest.Mock).mockReturnValue({ tabName: TimelineType.template }); + (useParams as jest.Mock).mockReturnValue({ tabName: TimelineTypeEnum.template }); }); test('it renders a deletion warning', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx index b24913428357b..7ab6532db2b57 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx @@ -12,7 +12,7 @@ import { isEmpty } from 'lodash/fp'; import { useParams } from 'react-router-dom'; import * as i18n from '../translations'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; interface Props { title?: string | null; @@ -28,7 +28,7 @@ export const DELETE_TIMELINE_MODAL_WIDTH = 600; // px export const DeleteTimelineModal = React.memo<Props>(({ title, closeModal, onDelete }) => { const { tabName } = useParams<{ tabName: TimelineType }>(); const warning = - tabName === TimelineType.template + tabName === TimelineTypeEnum.template ? i18n.DELETE_TIMELINE_TEMPLATE_WARNING : i18n.DELETE_TIMELINE_WARNING; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx index 510871651dd1a..42cb673acc7a0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { DeleteTimelineModalOverlay } from '.'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import * as i18n from '../translations'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; @@ -42,7 +42,7 @@ describe('DeleteTimelineModal', () => { }; beforeAll(() => { - (useParams as jest.Mock).mockReturnValue({ tabName: TimelineType.default }); + (useParams as jest.Mock).mockReturnValue({ tabName: TimelineTypeEnum.default }); }); describe('showModalState', () => { @@ -78,7 +78,7 @@ describe('DeleteTimelineModal', () => { }); test('it shows correct toast message on success for deleted templates', async () => { - (useParams as jest.Mock).mockReturnValue({ tabName: TimelineType.template }); + (useParams as jest.Mock).mockReturnValue({ tabName: TimelineTypeEnum.template }); const wrapper = mountWithIntl(<DeleteTimelineModalOverlay {...defaultProps} />); wrapper.find('button[data-test-subj="confirmModalConfirmButton"]').simulate('click'); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx index b76565989fb74..aa85b4c3f1ee9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx @@ -12,7 +12,7 @@ import { createGlobalStyle } from 'styled-components'; import { useParams } from 'react-router-dom'; import { DeleteTimelineModal, DELETE_TIMELINE_MODAL_WIDTH } from './delete_timeline_modal'; import type { DeleteTimelines } from '../types'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import * as i18n from '../translations'; @@ -48,7 +48,7 @@ export const DeleteTimelineModalOverlay = React.memo<Props>( deleteTimelines(savedObjectIds, savedSearchIds); addSuccess({ title: - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? i18n.SUCCESSFULLY_DELETED_TIMELINE_TEMPLATES(savedObjectIds.length) : i18n.SUCCESSFULLY_DELETED_TIMELINES(savedObjectIds.length), }); @@ -56,7 +56,7 @@ export const DeleteTimelineModalOverlay = React.memo<Props>( deleteTimelines(savedObjectIds); addSuccess({ title: - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? i18n.SUCCESSFULLY_DELETED_TIMELINE_TEMPLATES(savedObjectIds.length) : i18n.SUCCESSFULLY_DELETED_TIMELINES(savedObjectIds.length), }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx index 073e9c486ac6d..ee4ffc9569445 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/edit_timeline_batch_actions.tsx @@ -9,7 +9,7 @@ import type { EuiBasicTable } from '@elastic/eui'; import { EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { TimelineType } from '../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../common/api/timeline'; import * as i18n from './translations'; import type { DeleteTimelines, OpenTimelineResult } from './types'; @@ -21,7 +21,7 @@ export const useEditTimelineBatchActions = ({ deleteTimelines, selectedItems, tableRef, - timelineType = TimelineType.default, + timelineType = TimelineTypeEnum.default, }: { deleteTimelines?: DeleteTimelines; selectedItems?: OpenTimelineResult[]; @@ -108,7 +108,7 @@ export const useEditTimelineBatchActions = ({ onComplete={onCompleteBatchActions.bind(null, closePopover)} title={ selectedItems?.length !== 1 - ? timelineType === TimelineType.template + ? timelineType === TimelineTypeEnum.template ? i18n.SELECTED_TEMPLATES(selectedItems?.length ?? 0) : i18n.SELECTED_TIMELINES(selectedItems?.length ?? 0) : selectedItems[0]?.title ?? '' diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx index 7b2ab90ca7248..3ca9f88422a24 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/export_timeline/export_timeline.tsx @@ -9,7 +9,7 @@ import React, { useCallback, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import * as i18n from '../translations'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { exportSelectedTimeline } from '../../../containers/api'; import { downloadBlob } from '../../../../common/utils/download_blob'; @@ -30,7 +30,7 @@ const ExportTimeline: React.FC<{ addSuccess({ title: - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? i18n.SUCCESSFULLY_EXPORTED_TIMELINE_TEMPLATES(exportCount) : i18n.SUCCESSFULLY_EXPORTED_TIMELINES(exportCount), 'data-test-subj': 'addObjectToContainerSuccess', diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index a647c0ed44535..d2151d8bff509 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -24,7 +24,7 @@ import { } from './helpers'; import type { OpenTimelineResult } from './types'; import { TimelineId } from '../../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import { mockTimeline as mockSelectedTimeline, mockTemplate as mockSelectedTemplate, @@ -325,14 +325,18 @@ describe('helpers', () => { savedObjectId: 'savedObject-1', title: 'Awesome Timeline', version: '1', - status: TimelineStatus.active, - timelineType: TimelineType.default, + status: TimelineStatusEnum.active, + timelineType: TimelineTypeEnum.default, }; - const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.template); + const newTimeline = defaultTimelineToTimelineModel( + timeline, + false, + TimelineTypeEnum.template + ); expect(newTimeline).toEqual({ ...defaultTimeline, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, columns: defaultUdtHeaders, }); }); @@ -342,11 +346,11 @@ describe('helpers', () => { savedObjectId: 'savedObject-1', title: 'Awesome Template', version: '1', - status: TimelineStatus.active, - timelineType: TimelineType.template, + status: TimelineStatusEnum.active, + timelineType: TimelineTypeEnum.template, }; - const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.default); + const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineTypeEnum.default); expect(newTimeline).toEqual({ ...defaultTimeline, columns: defaultUdtHeaders, @@ -484,21 +488,21 @@ describe('helpers', () => { savedObjectId: 'savedObject-1', title: 'Awesome Timeline', version: '1', - status: TimelineStatus.immutable, - timelineType: TimelineType.template, + status: TimelineStatusEnum.immutable, + timelineType: TimelineTypeEnum.template, }; const newTimeline = defaultTimelineToTimelineModel( timeline, false, - TimelineType.template, + TimelineTypeEnum.template, false ); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-10-28T11:37:31.655Z', start: '2020-10-27T11:37:31.655Z' }, - status: TimelineStatus.immutable, - timelineType: TimelineType.template, + status: TimelineStatusEnum.immutable, + timelineType: TimelineTypeEnum.template, title: 'Awesome Timeline', columns: defaultUdtHeaders, excludedRowRendererIds: [], @@ -510,20 +514,20 @@ describe('helpers', () => { savedObjectId: 'savedObject-1', title: 'Awesome Timeline', version: '1', - status: TimelineStatus.active, - timelineType: TimelineType.default, + status: TimelineStatusEnum.active, + timelineType: TimelineTypeEnum.default, }; const newTimeline = defaultTimelineToTimelineModel( timeline, false, - TimelineType.default, + TimelineTypeEnum.default, false ); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, title: 'Awesome Timeline', columns: defaultUdtHeaders, }); @@ -534,22 +538,22 @@ describe('helpers', () => { savedObjectId: 'savedObject-1', title: 'Awesome Timeline', version: '1', - status: TimelineStatus.active, - timelineType: TimelineType.default, + status: TimelineStatusEnum.active, + timelineType: TimelineTypeEnum.default, }; const newTimeline = defaultTimelineToTimelineModel( timeline, false, - TimelineType.default, + TimelineTypeEnum.default, false ); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, title: 'Awesome Timeline', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, defaultColumns: defaultUdtHeaders, columns: defaultUdtHeaders, }); @@ -561,23 +565,23 @@ describe('helpers', () => { savedObjectId: 'savedObject-1', title: 'Awesome Timeline', version: '1', - status: TimelineStatus.active, - timelineType: TimelineType.default, + status: TimelineStatusEnum.active, + timelineType: TimelineTypeEnum.default, columns: customColumns, }; const newTimeline = defaultTimelineToTimelineModel( timeline, false, - TimelineType.default, + TimelineTypeEnum.default, false ); expect(newTimeline).toEqual({ ...defaultTimeline, dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' }, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, title: 'Awesome Timeline', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, defaultColumns: defaultUdtHeaders, columns: customColumns, }); @@ -623,7 +627,7 @@ describe('helpers', () => { duplicate: false, graphEventId: '', timelineId: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, onError, onOpenTimeline, openTimeline: true, @@ -682,7 +686,7 @@ describe('helpers', () => { duplicate: false, graphEventId: '', timelineId: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, openTimeline: true, }; @@ -754,7 +758,7 @@ describe('helpers', () => { duplicate: false, graphEventId: '', timelineId: '', - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, onOpenTimeline, openTimeline: true, }; @@ -817,7 +821,7 @@ describe('helpers', () => { duplicate: false, graphEventId: '', timelineId: undefined, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, onOpenTimeline, openTimeline: true, unifiedComponentsInTimelineDisabled: false, @@ -853,7 +857,7 @@ describe('helpers', () => { duplicate: false, graphEventId: '', timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, onOpenTimeline: undefined, openTimeline: true, unifiedComponentsInTimelineDisabled: false, @@ -892,7 +896,7 @@ describe('helpers', () => { duplicate: false, graphEventId: '', timelineId: TimelineId.active, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, onOpenTimeline, openTimeline: true, unifiedComponentsInTimelineDisabled: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index ec90e8477c72e..2f4ee8c2e1d20 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -23,10 +23,11 @@ import type { Note, } from '../../../../common/api/timeline'; import { - RowRendererId, - DataProviderType, - TimelineStatus, - TimelineType, + DataProviderTypeEnum, + RowRendererValues, + TimelineStatusEnum, + type TimelineType, + TimelineTypeEnum, } from '../../../../common/api/timeline'; import { TimelineId, TimelineTabs } from '../../../../common/types/timeline'; import { useUpdateTimeline } from './use_update_timeline'; @@ -168,22 +169,22 @@ const getTemplateTimelineId = ( targetTimelineType?: TimelineType ) => { if ( - targetTimelineType === TimelineType.default && - timeline.timelineType === TimelineType.template + targetTimelineType === TimelineTypeEnum.default && + timeline.timelineType === TimelineTypeEnum.template ) { return timeline.templateTimelineId; } - return duplicate && timeline.timelineType === TimelineType.template + return duplicate && timeline.timelineType === TimelineTypeEnum.template ? // TODO: MOVE TO THE BACKEND uuidv4() : timeline.templateTimelineId; }; const convertToDefaultField = ({ and, ...dataProvider }: DataProviderResult) => { - if (dataProvider.type === DataProviderType.template) { + if (dataProvider.type === DataProviderTypeEnum.template) { return deepMerge(dataProvider, { - type: DataProviderType.default, + type: DataProviderTypeEnum.default, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion enabled: dataProvider.queryMatch!.operator !== IS_OPERATOR, queryMatch: { @@ -202,7 +203,7 @@ const getDataProviders = ( dataProviders: TimelineResult['dataProviders'], timelineType?: TimelineType ) => { - if (duplicate && dataProviders && timelineType === TimelineType.default) { + if (duplicate && dataProviders && timelineType === TimelineTypeEnum.default) { return dataProviders.map((dataProvider) => ({ ...convertToDefaultField(dataProvider), and: dataProvider.and?.map(convertToDefaultField) ?? [], @@ -229,9 +230,9 @@ export const getTimelineStatus = ( timelineType?: TimelineType ) => { const isCreateTimelineFromAction = timelineType && timeline.timelineType !== timelineType; - if (isCreateTimelineFromAction) return TimelineStatus.draft; + if (isCreateTimelineFromAction) return TimelineStatusEnum.draft; - return duplicate ? TimelineStatus.active : timeline.status; + return duplicate ? TimelineStatusEnum.active : timeline.status; }; export const defaultTimelineToTimelineModel = ( @@ -240,7 +241,7 @@ export const defaultTimelineToTimelineModel = ( timelineType?: TimelineType, unifiedComponentsInTimelineDisabled?: boolean ): TimelineModel => { - const isTemplate = timeline.timelineType === TimelineType.template; + const isTemplate = timeline.timelineType === TimelineTypeEnum.template; const defaultHeadersValue = !unifiedComponentsInTimelineDisabled ? defaultUdtHeaders : defaultHeaders; @@ -253,15 +254,15 @@ export const defaultTimelineToTimelineModel = ( : defaultHeadersValue, defaultColumns: defaultHeadersValue, dateRange: - timeline.status === TimelineStatus.immutable && - timeline.timelineType === TimelineType.template + timeline.status === TimelineStatusEnum.immutable && + timeline.timelineType === TimelineTypeEnum.template ? { start: DEFAULT_FROM_MOMENT.toISOString(), end: DEFAULT_TO_MOMENT.toISOString(), } : timeline.dateRange, dataProviders: getDataProviders(duplicate, timeline.dataProviders, timelineType), - excludedRowRendererIds: isTemplate ? [] : Object.keys(RowRendererId), + excludedRowRendererIds: isTemplate ? [] : RowRendererValues, eventIdToNoteIds: setEventIdToNoteIds(duplicate, timeline.eventIdToNoteIds), filters: timeline.filters != null ? timeline.filters.map(setTimelineFilters) : [], isFavorite: duplicate @@ -365,7 +366,7 @@ export const useQueryTimelineById = () => { initialized: true, savedSearchId: savedSearchId ?? null, excludedRowRendererIds: - !unifiedComponentsInTimelineDisabled && timelineType !== TimelineType.template + !unifiedComponentsInTimelineDisabled && timelineType !== TimelineTypeEnum.template ? timelineDefaults.excludedRowRendererIds : [], }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx index 26a6b69709640..68f6071200ec1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.test.tsx @@ -13,7 +13,7 @@ import { useHistory, useParams } from 'react-router-dom'; import '../../../common/mock/formatted_relative'; import { SecurityPageName } from '../../../app/types'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { TimelineId } from '../../../../common/types'; import { TestProviders, @@ -107,7 +107,7 @@ describe('StatefulOpenTimeline', () => { beforeEach(() => { (useParams as jest.Mock).mockReturnValue({ - tabName: TimelineType.default, + tabName: TimelineTypeEnum.default, pageName: SecurityPageName.timelines, }); useUserPrivilegesMock.mockReturnValue({ @@ -172,12 +172,12 @@ describe('StatefulOpenTimeline', () => { } ); - expect(result.current.timelineType).toBe(TimelineType.default); + expect(result.current.timelineType).toBe(TimelineTypeEnum.default); }); test("should land on correct timelines' tab with url timelines/template", () => { (useParams as jest.Mock).mockReturnValue({ - tabName: TimelineType.template, + tabName: TimelineTypeEnum.template, pageName: SecurityPageName.timelines, }); @@ -188,12 +188,12 @@ describe('StatefulOpenTimeline', () => { } ); - expect(result.current.timelineType).toBe(TimelineType.template); + expect(result.current.timelineType).toBe(TimelineTypeEnum.template); }); test("should land on correct templates' tab after switching tab", async () => { (useParams as jest.Mock).mockReturnValue({ - tabName: TimelineType.template, + tabName: TimelineTypeEnum.template, pageName: SecurityPageName.timelines, }); @@ -209,7 +209,7 @@ describe('StatefulOpenTimeline', () => { ); await waitFor(() => { wrapper - .find(`[data-test-subj="timeline-${TimelineTabsStyle.tab}-${TimelineType.template}"]`) + .find(`[data-test-subj="timeline-${TimelineTabsStyle.tab}-${TimelineTypeEnum.template}"]`) .first() .simulate('click'); @@ -230,7 +230,7 @@ describe('StatefulOpenTimeline', () => { } ); - expect(result.current.timelineType).toBe(TimelineType.default); + expect(result.current.timelineType).toBe(TimelineTypeEnum.default); }); test('should not change url after switching filter', async () => { @@ -252,7 +252,7 @@ describe('StatefulOpenTimeline', () => { await waitFor(() => { wrapper .find( - `[data-test-subj="open-timeline-modal-body-${TimelineTabsStyle.filter}-${TimelineType.template}"]` + `[data-test-subj="open-timeline-modal-body-${TimelineTabsStyle.filter}-${TimelineTypeEnum.template}"]` ) .first() .simulate('click'); @@ -613,7 +613,7 @@ describe('StatefulOpenTimeline', () => { expect( wrapper .find( - `[data-test-subj="open-timeline-modal-body-${TimelineTabsStyle.filter}-${TimelineType.default}"]` + `[data-test-subj="open-timeline-modal-body-${TimelineTabsStyle.filter}-${TimelineTypeEnum.default}"]` ) .exists() ).toEqual(true); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx index 18c3255ac848b..ea86d5eaa54fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/index.tsx @@ -153,7 +153,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>( /** The requested sort direction of the query results */ const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION); /** The requested field to sort on */ - const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); + const [sortField, setSortField] = useState<SortFieldTimeline>(DEFAULT_SORT_FIELD); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const timelineSavedObjectId = useShallowEqualSelector( @@ -194,7 +194,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>( }, search, sort: { - sortField: sortField as SortFieldTimeline, + sortField, sortOrder: sortDirection as Direction, }, onlyUserFavorite: onlyFavorites, @@ -318,7 +318,7 @@ export const StatefulOpenTimelineComponent = React.memo<OpenTimelineOwnProps>( if (sort != null) { const { field, direction } = sort; setSortDirection(direction); - setSortField(field); + setSortField(field as SortFieldTimeline); } }, []); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx index 2ed4300dc7e33..713dff571847b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx @@ -17,7 +17,7 @@ import type { TimelinesTableProps } from './timelines_table'; import { mockTimelineResults } from '../../../common/mock/timeline_results'; import { OpenTimeline } from './open_timeline'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from './constants'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock'; import { useUserPrivileges } from '../../../common/components/user_privileges'; @@ -72,8 +72,8 @@ describe('OpenTimeline', () => { sortDirection: DEFAULT_SORT_DIRECTION, sortField: DEFAULT_SORT_FIELD, title, - timelineType: TimelineType.default, - timelineStatus: TimelineStatus.active, + timelineType: TimelineTypeEnum.default, + timelineStatus: TimelineStatusEnum.active, templateTimelineFilter: [<div key="mock-a" />, <div key="mock-b" />], totalSearchResultsCount: mockSearchResults.length, }); @@ -317,7 +317,7 @@ describe('OpenTimeline', () => { test("it should render bulk actions if timelineStatus is active (selecting custom templates' tab)", () => { const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.active, + timelineStatus: TimelineStatusEnum.active, }; const wrapper = mountWithIntl( <ThemeProvider theme={mockTheme}> @@ -436,7 +436,7 @@ describe('OpenTimeline', () => { }); const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.active, + timelineStatus: TimelineStatusEnum.active, }; const wrapper = mountWithIntl( <ThemeProvider theme={mockTheme}> @@ -455,7 +455,7 @@ describe('OpenTimeline', () => { }); const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.active, + timelineStatus: TimelineStatusEnum.active, }; const wrapper = mountWithIntl( <ThemeProvider theme={mockTheme}> @@ -471,7 +471,7 @@ describe('OpenTimeline', () => { test('it should NOT include createFrom, duplicate, createRule, delete in timeline actions when user has read only access', () => { const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.active, + timelineStatus: TimelineStatusEnum.active, }; useUserPrivilegesMock.mockReturnValue({ kibanaSecuritySolutionsPrivileges: { crud: false, read: true }, @@ -490,7 +490,7 @@ describe('OpenTimeline', () => { test("it should render selected count if timelineStatus is active (selecting custom templates' tab)", () => { const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.active, + timelineStatus: TimelineStatusEnum.active, }; const wrapper = mountWithIntl( <ThemeProvider theme={mockTheme}> @@ -504,7 +504,7 @@ describe('OpenTimeline', () => { test("it should not render bulk actions if timelineStatus is immutable (selecting Elastic templates' tab)", () => { const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.immutable, + timelineStatus: TimelineStatusEnum.immutable, }; const wrapper = mountWithIntl( <ThemeProvider theme={mockTheme}> @@ -521,7 +521,7 @@ describe('OpenTimeline', () => { }); const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.immutable, + timelineStatus: TimelineStatusEnum.immutable, }; const wrapper = mountWithIntl( <ThemeProvider theme={mockTheme}> @@ -537,7 +537,7 @@ describe('OpenTimeline', () => { test("it should not render selected count if timelineStatus is immutable (selecting Elastic templates' tab)", () => { const defaultProps = { ...getDefaultTestProps(mockResults), - timelineStatus: TimelineStatus.immutable, + timelineStatus: TimelineStatusEnum.immutable, }; const wrapper = mountWithIntl( <ThemeProvider theme={mockTheme}> diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx index 1b20d1643ff3a..524d3bee9640a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.tsx @@ -10,7 +10,7 @@ import type { EuiBasicTable } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import { ImportDataModal } from '../../../common/components/import_data_modal'; import { UtilityBarGroup, @@ -65,7 +65,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( setImportDataModalToggle, sortField, tabName, - timelineType = TimelineType.default, + timelineType = TimelineTypeEnum.default, timelineStatus, timelineFilter, templateTimelineFilter, @@ -168,7 +168,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( ...(onCreateRuleFromEql != null ? createRuleFromEql : []), ]; - if (timelineStatus !== TimelineStatus.immutable) { + if (timelineStatus !== TimelineStatusEnum.immutable) { timelineActions.push('export'); timelineActions.push('selectable'); } @@ -176,7 +176,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( if ( onDeleteSelected != null && deleteTimelines != null && - timelineStatus !== TimelineStatus.immutable + timelineStatus !== TimelineStatusEnum.immutable ) { timelineActions.push('delete'); } @@ -184,7 +184,7 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( return timelineActions; } // user with read access should only see export - if (timelineStatus !== TimelineStatus.immutable) { + if (timelineStatus !== TimelineStatusEnum.immutable) { return ['export', 'selectable']; } return []; @@ -248,15 +248,15 @@ export const OpenTimeline = React.memo<OpenTimelineProps>( <UtilityBarText data-test-subj="query-message"> <> {i18n.SHOWING}{' '} - {timelineType === TimelineType.template ? nTemplates : nTimelines} + {timelineType === TimelineTypeEnum.template ? nTemplates : nTimelines} </> </UtilityBarText> </UtilityBarGroup> <UtilityBarGroup> - {timelineStatus !== TimelineStatus.immutable && ( + {timelineStatus !== TimelineStatusEnum.immutable && ( <> <UtilityBarText data-test-subj="selected-count"> - {timelineType === TimelineType.template + {timelineType === TimelineTypeEnum.template ? i18n.SELECTED_TEMPLATES((selectedItems || []).length) : i18n.SELECTED_TIMELINES((selectedItems || []).length)} </UtilityBarText> diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx index 4a817d113c5ca..c4e4b7efbdd1e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx @@ -16,7 +16,7 @@ import type { TimelinesTableProps } from '../timelines_table'; import { mockTimelineResults } from '../../../../common/mock/timeline_results'; import { OpenTimelineModalBody } from './open_timeline_modal_body'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; -import { TimelineType, TimelineStatus } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../../common/api/timeline'; import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock'; jest.mock('../../../../common/lib/kibana'); @@ -55,8 +55,8 @@ describe('OpenTimelineModal', () => { selectedItems: [], sortDirection: DEFAULT_SORT_DIRECTION, sortField: DEFAULT_SORT_FIELD, - timelineType: TimelineType.default, - timelineStatus: TimelineStatus.active, + timelineType: TimelineTypeEnum.default, + timelineStatus: TimelineStatusEnum.active, templateTimelineFilter: [<div key={0} />], title, totalSearchResultsCount: mockSearchResults.length, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx index e7bda4a5b79b9..ef60552e7c42a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx @@ -10,7 +10,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { SearchRow } from '.'; @@ -32,7 +32,7 @@ describe('SearchRow', () => { onQueryChange={jest.fn()} onToggleOnlyFavorites={jest.fn()} query="" - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </ThemeProvider> ); @@ -52,7 +52,7 @@ describe('SearchRow', () => { onQueryChange={jest.fn()} onToggleOnlyFavorites={jest.fn()} query="" - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </ThemeProvider> ); @@ -72,7 +72,7 @@ describe('SearchRow', () => { onQueryChange={jest.fn()} onToggleOnlyFavorites={onToggleOnlyFavorites} query="" - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </ThemeProvider> ); @@ -90,7 +90,7 @@ describe('SearchRow', () => { onQueryChange={jest.fn()} onToggleOnlyFavorites={jest.fn()} query="" - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </ThemeProvider> ); @@ -111,7 +111,7 @@ describe('SearchRow', () => { onQueryChange={jest.fn()} onToggleOnlyFavorites={jest.fn()} query="" - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </ThemeProvider> ); @@ -136,7 +136,7 @@ describe('SearchRow', () => { onQueryChange={onQueryChange} onToggleOnlyFavorites={jest.fn()} query="" - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} /> </ThemeProvider> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.tsx index ad198881bfd44..17a12054bfa5d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.tsx @@ -15,7 +15,7 @@ import { import React, { useMemo } from 'react'; import styled from 'styled-components'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import * as i18n from '../translations'; import type { OpenTimelineProps } from '../types'; @@ -58,7 +58,7 @@ export const SearchRow = React.memo<Props>( const searchBox = useMemo( () => ({ placeholder: - timelineType === TimelineType.default + timelineType === TimelineTypeEnum.default ? i18n.SEARCH_PLACEHOLDER : i18n.SEARCH_TEMPLATE_PLACEHOLDER, incremental: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx index 8348fc951ac8d..0ad523f34f9fa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.tsx @@ -16,7 +16,7 @@ import type { OnOpenDeleteTimelineModal, } from '../types'; import * as i18n from '../translations'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; type Action = EuiTableActionsColumnType<object>['actions'][number]; /** @@ -47,7 +47,7 @@ export const getActionsColumns = ({ onClick: ({ savedObjectId }: OpenTimelineResult) => { onOpenTimeline({ duplicate: true, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion timelineId: savedObjectId!, }); @@ -57,7 +57,8 @@ export const getActionsColumns = ({ description: i18n.CREATE_TIMELINE_FROM_TEMPLATE, 'data-test-subj': 'create-from-template', available: (item: OpenTimelineResult) => - item.timelineType === TimelineType.template && actionTimelineToShow.includes('createFrom'), + item.timelineType === TimelineTypeEnum.template && + actionTimelineToShow.includes('createFrom'), } as Action; const createTemplateFromTimeline = { @@ -66,7 +67,7 @@ export const getActionsColumns = ({ onClick: ({ savedObjectId }: OpenTimelineResult) => { onOpenTimeline({ duplicate: true, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion timelineId: savedObjectId!, }); @@ -76,7 +77,8 @@ export const getActionsColumns = ({ description: i18n.CREATE_TEMPLATE_FROM_TIMELINE, 'data-test-subj': 'create-template-from-timeline', available: (item: OpenTimelineResult) => - item.timelineType !== TimelineType.template && actionTimelineToShow.includes('createFrom'), + item.timelineType !== TimelineTypeEnum.template && + actionTimelineToShow.includes('createFrom'), } as Action; const openAsDuplicateColumn = { @@ -93,7 +95,7 @@ export const getActionsColumns = ({ description: i18n.OPEN_AS_DUPLICATE, 'data-test-subj': 'open-duplicate', available: (item: OpenTimelineResult) => - item.timelineType !== TimelineType.template && actionTimelineToShow.includes('duplicate'), + item.timelineType !== TimelineTypeEnum.template && actionTimelineToShow.includes('duplicate'), } as Action; const openAsDuplicateTemplateColumn = { @@ -110,7 +112,7 @@ export const getActionsColumns = ({ description: i18n.OPEN_AS_DUPLICATE_TEMPLATE, 'data-test-subj': 'open-duplicate-template', available: (item: OpenTimelineResult) => - item.timelineType === TimelineType.template && actionTimelineToShow.includes('duplicate'), + item.timelineType === TimelineTypeEnum.template && actionTimelineToShow.includes('duplicate'), } as Action; const exportTimelineAction = { @@ -121,7 +123,7 @@ export const getActionsColumns = ({ if (enableExportTimelineDownloader != null) enableExportTimelineDownloader(selectedTimeline); }, enabled: (timeline: OpenTimelineResult) => { - return timeline.savedObjectId != null && timeline.status !== TimelineStatus.immutable; + return timeline.savedObjectId != null && timeline.status !== TimelineStatusEnum.immutable; }, description: i18n.EXPORT_SELECTED, 'data-test-subj': 'export-timeline', @@ -136,7 +138,7 @@ export const getActionsColumns = ({ if (onOpenDeleteTimelineModal != null) onOpenDeleteTimelineModal(selectedTimeline); }, enabled: ({ savedObjectId, status }: OpenTimelineResult) => - savedObjectId != null && status !== TimelineStatus.immutable, + savedObjectId != null && status !== TimelineStatusEnum.immutable, description: i18n.DELETE_SELECTED, 'data-test-subj': 'delete-timeline', available: () => actionTimelineToShow.includes('delete') && deleteTimelines != null, @@ -153,7 +155,7 @@ export const getActionsColumns = ({ enabled: (timeline: OpenTimelineResult) => onCreateRule != null && timeline.savedObjectId != null && - timeline.status !== TimelineStatus.immutable, + timeline.status !== TimelineStatusEnum.immutable, description: i18n.CREATE_RULE_FROM_TIMELINE, 'data-test-subj': 'create-rule-from-timeline', available: ({ queryType }: OpenTimelineResult) => @@ -174,7 +176,7 @@ export const getActionsColumns = ({ enabled: (timeline: OpenTimelineResult) => onCreateRuleFromEql != null && timeline.savedObjectId != null && - timeline.status !== TimelineStatus.immutable, + timeline.status !== TimelineStatusEnum.immutable, description: i18n.CREATE_RULE_FROM_TIMELINE, 'data-test-subj': 'create-rule-from-eql', available: ({ queryType }: OpenTimelineResult) => diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx index 7c1e0a419683e..05870b128b2d5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.tsx @@ -17,7 +17,7 @@ import * as i18n from '../translations'; import type { OnOpenTimeline, OnToggleShowNotes, OpenTimelineResult } from '../types'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { TimelineId } from '../../../../../common/types'; const LineClampTextContainer = styled.span` @@ -68,7 +68,8 @@ export const getCommonColumns = ({ { dataType: 'string' as EuiTableDataType, field: 'title', - name: timelineType === TimelineType.default ? i18n.TIMELINE_NAME : i18n.TIMELINE_TEMPLATE_NAME, + name: + timelineType === TimelineTypeEnum.default ? i18n.TIMELINE_NAME : i18n.TIMELINE_TEMPLATE_NAME, render: (title: string, timelineResult: OpenTimelineResult) => timelineResult.savedObjectId != null ? ( <EuiLink diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx index 412ccd72c815c..5717677ef6166 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.tsx @@ -13,8 +13,7 @@ import { ACTION_COLUMN_WIDTH } from './common_styles'; import { getNotesCount, getPinnedEventCount } from '../helpers'; import * as i18n from '../translations'; import type { FavoriteTimelineResult, OpenTimelineResult } from '../types'; -import type { TimelineTypeLiteralWithNull } from '../../../../../common/api/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; /** * Returns the columns that have icon headers @@ -22,7 +21,7 @@ import { TimelineType } from '../../../../../common/api/timeline'; export const getIconHeaderColumns = ({ timelineType, }: { - timelineType: TimelineTypeLiteralWithNull; + timelineType: TimelineType | null; }): Array<EuiTableFieldDataColumnType<object>> => { const columns = { note: { @@ -77,5 +76,5 @@ export const getIconHeaderColumns = ({ }; const templateColumns = [columns.note, columns.favorite]; const defaultColumns = [columns.pinnedEvent, columns.note, columns.favorite]; - return timelineType === TimelineType.template ? templateColumns : defaultColumns; + return timelineType === TimelineTypeEnum.template ? templateColumns : defaultColumns; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx index 1e49028326b5d..0df2170b0697e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.tsx @@ -27,8 +27,11 @@ import { getActionsColumns } from './actions_columns'; import { getCommonColumns } from './common_columns'; import { getExtendedColumns } from './extended_columns'; import { getIconHeaderColumns } from './icon_header_columns'; -import type { TimelineTypeLiteralWithNull } from '../../../../../common/api/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { + TimelineStatusEnum, + type TimelineType, + TimelineTypeEnum, +} from '../../../../../common/api/timeline'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; /** @@ -63,7 +66,7 @@ export const getTimelinesTableColumns = ({ onSelectionChange: OnSelectionChange; onToggleShowNotes: OnToggleShowNotes; showExtendedColumns: boolean; - timelineType: TimelineTypeLiteralWithNull; + timelineType: TimelineType | null; hasCrudAccess: boolean; }): Array<EuiBasicTableColumn<object>> => { return [ @@ -110,7 +113,7 @@ export interface TimelinesTableProps { showExtendedColumns: boolean; sortDirection: 'asc' | 'desc'; sortField: string; - timelineType: TimelineTypeLiteralWithNull; + timelineType: TimelineType | null; tableRef: React.MutableRefObject<EuiBasicTable<OpenTimelineResult> | null>; totalSearchResultsCount: number; } @@ -171,7 +174,7 @@ export const TimelinesTable = React.memo<TimelinesTableProps>( return { selectable: (timelineResult: OpenTimelineResult) => timelineResult.savedObjectId != null && - timelineResult.status !== TimelineStatus.immutable, + timelineResult.status !== TimelineStatusEnum.immutable, selectableMessage: (selectable: boolean) => !selectable ? i18n.MISSING_SAVED_OBJECT_ID : '', onSelectionChange, @@ -215,7 +218,7 @@ export const TimelinesTable = React.memo<TimelinesTableProps>( const noItemsMessage = isLoading || searchResults == null ? i18n.LOADING - : timelineType === TimelineType.template + : timelineType === TimelineTypeEnum.template ? i18n.ZERO_TIMELINE_TEMPLATES_MATCH : i18n.ZERO_TIMELINES_MATCH; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts index 075f4aca49f3f..68055c6c5a17b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/mocks.ts @@ -9,7 +9,7 @@ import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines_page'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; import type { OpenTimelineResult } from '../types'; import type { TimelinesTableProps } from '.'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; export const getMockTimelinesTableProps = ( mockOpenTimelineResults: OpenTimelineResult[] @@ -31,7 +31,7 @@ export const getMockTimelinesTableProps = ( showExtendedColumns: true, sortDirection: DEFAULT_SORT_DIRECTION, sortField: DEFAULT_SORT_FIELD, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, totalSearchResultsCount: mockOpenTimelineResults.length, tableRef: { current: null }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts index 9595d991fbde4..1fb75887d6a75 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts @@ -11,11 +11,9 @@ import type { TimelineModel } from '../../store/model'; import type { RowRendererId, SingleTimelineResolveResponse, - TimelineTypeLiteral, - TimelineTypeLiteralWithNull, + TimelineType, TimelineStatus, - TemplateTimelineTypeLiteral, - TimelineStatusLiteralWithNull, + TemplateTimelineType, Note, } from '../../../../common/api/timeline'; @@ -63,7 +61,7 @@ export interface OpenTimelineResult { status?: TimelineStatus | null; title?: string | null; templateTimelineId?: string | null; - timelineType?: TimelineTypeLiteral; + timelineType?: TimelineType; updated?: number | null; updatedBy?: string | null; } @@ -98,7 +96,7 @@ export type OnOpenTimeline = ({ }: { duplicate: boolean; timelineId: string; - timelineType?: TimelineTypeLiteral; + timelineType?: TimelineType; }) => void; export type OnOpenDeleteTimelineModal = (selectedItem: OpenTimelineResult) => void; @@ -195,9 +193,9 @@ export interface OpenTimelineProps { /** the requested field to sort on */ sortField: string; /** this affects timeline's behaviour like editable / duplicatible */ - timelineType: TimelineTypeLiteralWithNull; + timelineType: TimelineType | null; /* active or immutable */ - timelineStatus: TimelineStatusLiteralWithNull; + timelineStatus: TimelineStatus | null; /** when timelineType === template, templatetimelineFilter is a JSX.Element */ templateTimelineFilter: JSX.Element[] | null; /** timeline / timeline template */ @@ -240,13 +238,13 @@ export enum TimelineTabsStyle { export interface TimelineTab { disabled: boolean; href: string; - id: TimelineTypeLiteral; + id: TimelineType; name: string; onClick: (ev: { preventDefault: () => void }) => void; } export interface TemplateTimelineFilter { - id: TemplateTimelineTypeLiteral; + id: TemplateTimelineType; name: string; disabled: boolean; withNext: boolean; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx index a0719b6ac3679..9e39bbf8ece34 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_status.tsx @@ -9,14 +9,14 @@ import React, { useState, useCallback, useMemo } from 'react'; import { EuiFilterButton } from '@elastic/eui'; import type { - TimelineTypeLiteralWithNull, - TemplateTimelineTypeLiteralWithNull, - TimelineStatusLiteralWithNull, -} from '../../../../common/api/timeline'; -import { + TemplateTimelineType, TimelineStatus, TimelineType, - TemplateTimelineType, +} from '../../../../common/api/timeline'; +import { + TemplateTimelineTypeEnum, + TimelineStatusEnum, + TimelineTypeEnum, } from '../../../../common/api/timeline'; import * as i18n from './translations'; @@ -28,18 +28,18 @@ export const useTimelineStatus = ({ elasticTemplateTimelineCount, customTemplateTimelineCount, }: { - timelineType: TimelineTypeLiteralWithNull; + timelineType: TimelineType | null; elasticTemplateTimelineCount?: number | null; customTemplateTimelineCount?: number | null; }): { - timelineStatus: TimelineStatusLiteralWithNull; - templateTimelineType: TemplateTimelineTypeLiteralWithNull; + timelineStatus: TimelineStatus | null; + templateTimelineType: TemplateTimelineType | null; templateTimelineFilter: JSX.Element[] | null; installPrepackagedTimelines: () => void; } => { - const [selectedTab, setSelectedTab] = useState<TemplateTimelineTypeLiteralWithNull>(null); + const [selectedTab, setSelectedTab] = useState<TemplateTimelineType | null>(null); const isTemplateFilterEnabled = useMemo( - () => timelineType === TimelineType.template, + () => timelineType === TimelineTypeEnum.template, [timelineType] ); @@ -52,23 +52,23 @@ export const useTimelineStatus = ({ () => templateTimelineType == null ? null - : templateTimelineType === TemplateTimelineType.elastic - ? TimelineStatus.immutable - : TimelineStatus.active, + : templateTimelineType === TemplateTimelineTypeEnum.elastic + ? TimelineStatusEnum.immutable + : TimelineStatusEnum.active, [templateTimelineType] ); const filters = useMemo( () => [ { - id: TemplateTimelineType.elastic, + id: TemplateTimelineTypeEnum.elastic, name: i18n.FILTER_ELASTIC_TIMELINES, disabled: !isTemplateFilterEnabled, withNext: true, count: elasticTemplateTimelineCount ?? undefined, }, { - id: TemplateTimelineType.custom, + id: TemplateTimelineTypeEnum.custom, name: i18n.FILTER_CUSTOM_TIMELINES, disabled: !isTemplateFilterEnabled, withNext: false, @@ -108,7 +108,7 @@ export const useTimelineStatus = ({ }, [templateTimelineType, filters, isTemplateFilterEnabled, onFilterClicked]); const installPrepackagedTimelines = useCallback(async () => { - if (templateTimelineType !== TemplateTimelineType.custom) { + if (templateTimelineType !== TemplateTimelineTypeEnum.custom) { await installPrepackedTimelines(); } }, [templateTimelineType]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx index d8943b0f674e7..8002798291cc1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx @@ -10,8 +10,7 @@ import { useParams } from 'react-router-dom'; import { EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import type { TimelineTypeLiteralWithNull } from '../../../../common/api/timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../common/api/timeline'; import { SecurityPageName } from '../../../app/types'; import { getTimelineTabsUrl, useFormatUrl } from '../../../common/components/link_to'; import * as i18n from './translations'; @@ -25,7 +24,7 @@ export interface UseTimelineTypesArgs { } export interface UseTimelineTypesResult { - timelineType: TimelineTypeLiteralWithNull; + timelineType: TimelineType | null; timelineTabs: JSX.Element; timelineFilters: JSX.Element; } @@ -37,19 +36,19 @@ export const useTimelineTypes = ({ const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.timelines); const { navigateToUrl } = useKibana().services.application; const { tabName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); - const [timelineType, setTimelineTypes] = useState<TimelineTypeLiteralWithNull>( - tabName === TimelineType.default || tabName === TimelineType.template + const [timelineType, setTimelineTypes] = useState<TimelineType | null>( + tabName === TimelineTypeEnum.default || tabName === TimelineTypeEnum.template ? tabName - : TimelineType.default + : TimelineTypeEnum.default ); const notesEnabled = useIsExperimentalFeatureEnabled('securitySolutionNotesEnabled'); const timelineUrl = useMemo(() => { - return formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)); + return formatUrl(getTimelineTabsUrl(TimelineTypeEnum.default, urlSearch)); }, [formatUrl, urlSearch]); const templateUrl = useMemo(() => { - return formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)); + return formatUrl(getTimelineTabsUrl(TimelineTypeEnum.template, urlSearch)); }, [formatUrl, urlSearch]); const notesUrl = useMemo(() => { @@ -83,7 +82,7 @@ export const useTimelineTypes = ({ const getFilterOrTabs: (timelineTabsStyle: TimelineTabsStyle) => TimelineTab[] = useCallback( (timelineTabsStyle: TimelineTabsStyle) => [ { - id: TimelineType.default, + id: TimelineTypeEnum.default, name: i18n.TAB_TIMELINES, href: timelineUrl, disabled: false, @@ -91,7 +90,7 @@ export const useTimelineTypes = ({ onClick: timelineTabsStyle === TimelineTabsStyle.tab ? goToTimeline : noop, }, { - id: TimelineType.template, + id: TimelineTypeEnum.template, name: i18n.TAB_TEMPLATES, href: templateUrl, disabled: false, diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx index 1be17b4c599d4..2d380e700f7d3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_update_timeline.tsx @@ -9,7 +9,7 @@ import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { isEmpty } from 'lodash/fp'; import type { Note } from '../../../../common/api/timeline'; -import { TimelineStatus, TimelineType } from '../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import { createNote } from '../notes/helpers'; import { InputsModelId } from '../../../common/store/inputs/constants'; @@ -65,8 +65,8 @@ export const useUpdateTimeline = () => { ); } if ( - _timeline.status === TimelineStatus.immutable && - _timeline.timelineType === TimelineType.template + _timeline.status === TimelineStatusEnum.immutable && + _timeline.timelineType === TimelineTypeEnum.template ) { dispatch( dispatchSetRelativeRangeDatePicker({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx index 3978c06f2784d..94a4477778f3e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.test.tsx @@ -11,13 +11,12 @@ import { createMockStore, mockGlobalState, TestProviders } from '../../../common import { fireEvent, render, waitFor } from '@testing-library/react'; import { RowRendererSwitch } from '.'; import { TimelineId } from '../../../../common/types'; -import { RowRendererId } from '../../../../common/api/timeline'; +import { RowRendererValues } from '../../../../common/api/timeline'; const localState = structuredClone(mockGlobalState); // exclude all row renderers by default -localState.timeline.timelineById[TimelineId.test].excludedRowRendererIds = - Object.values(RowRendererId); +localState.timeline.timelineById[TimelineId.test].excludedRowRendererIds = RowRendererValues; const renderTestComponent = (props?: ComponentProps<typeof TestProviders>) => { const store = props?.store ?? createMockStore(localState); @@ -69,7 +68,7 @@ describe('Row Renderer Switch', () => { expect(getByTestId('row-renderer-switch')).toHaveAttribute('aria-checked', 'false'); expect( localStore.getState().timeline.timelineById[TimelineId.test].excludedRowRendererIds - ).toMatchObject(Object.values(RowRendererId)); + ).toMatchObject(RowRendererValues); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx index 12a6127a39053..986b417bf806d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderer_switch/index.tsx @@ -10,7 +10,7 @@ import { EuiToolTip, EuiSwitch, EuiFormRow, useGeneratedHtmlId } from '@elastic/ import React, { useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; -import { RowRendererId } from '../../../../common/api/timeline'; +import { RowRendererValues } from '../../../../common/api/timeline'; import type { State } from '../../../common/store'; import { setExcludedRowRendererIds } from '../../store/actions'; import { selectExcludedRowRendererIds } from '../../store/selectors'; @@ -40,7 +40,7 @@ export const RowRendererSwitch = React.memo(function RowRendererSwitch( ); const isAnyRowRendererEnabled = useMemo( - () => Object.values(RowRendererId).some((id) => !excludedRowRendererIds.includes(id)), + () => RowRendererValues.some((id) => !excludedRowRendererIds.includes(id)), [excludedRowRendererIds] ); @@ -48,7 +48,7 @@ export const RowRendererSwitch = React.memo(function RowRendererSwitch( dispatch( setExcludedRowRendererIds({ id: timelineId, - excludedRowRendererIds: Object.values(RowRendererId), + excludedRowRendererIds: RowRendererValues, }) ); }, [dispatch, timelineId]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/constants.ts b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/constants.ts index 02e8b4057ee7c..3e6c90e30e5b2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/constants.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/constants.ts @@ -4,26 +4,26 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { RowRendererId } from '../../../../../common/api/timeline'; +import { type RowRendererId, RowRendererIdEnum } from '../../../../../common/api/timeline'; import * as i18n from './translations'; export const eventRendererNames: { [key in RowRendererId]: string } = { - [RowRendererId.alert]: i18n.ALERT_NAME, - [RowRendererId.alerts]: i18n.ALERTS_NAME, - [RowRendererId.auditd]: i18n.AUDITD_NAME, - [RowRendererId.auditd_file]: i18n.AUDITD_FILE_NAME, - [RowRendererId.library]: i18n.LIBRARY_NAME, - [RowRendererId.system_security_event]: i18n.AUTHENTICATION_NAME, - [RowRendererId.system_dns]: i18n.DNS_NAME, - [RowRendererId.netflow]: i18n.FLOW_NAME, - [RowRendererId.system]: i18n.SYSTEM_NAME, - [RowRendererId.system_endgame_process]: i18n.PROCESS, - [RowRendererId.registry]: i18n.REGISTRY_NAME, - [RowRendererId.system_fim]: i18n.FIM_NAME, - [RowRendererId.system_file]: i18n.FILE_NAME, - [RowRendererId.system_socket]: i18n.SOCKET_NAME, - [RowRendererId.suricata]: 'Suricata', - [RowRendererId.threat_match]: i18n.THREAT_MATCH_NAME, - [RowRendererId.zeek]: i18n.ZEEK_NAME, - [RowRendererId.plain]: '', + [RowRendererIdEnum.alert]: i18n.ALERT_NAME, + [RowRendererIdEnum.alerts]: i18n.ALERTS_NAME, + [RowRendererIdEnum.auditd]: i18n.AUDITD_NAME, + [RowRendererIdEnum.auditd_file]: i18n.AUDITD_FILE_NAME, + [RowRendererIdEnum.library]: i18n.LIBRARY_NAME, + [RowRendererIdEnum.system_security_event]: i18n.AUTHENTICATION_NAME, + [RowRendererIdEnum.system_dns]: i18n.DNS_NAME, + [RowRendererIdEnum.netflow]: i18n.FLOW_NAME, + [RowRendererIdEnum.system]: i18n.SYSTEM_NAME, + [RowRendererIdEnum.system_endgame_process]: i18n.PROCESS, + [RowRendererIdEnum.registry]: i18n.REGISTRY_NAME, + [RowRendererIdEnum.system_fim]: i18n.FIM_NAME, + [RowRendererIdEnum.system_file]: i18n.FILE_NAME, + [RowRendererIdEnum.system_socket]: i18n.SOCKET_NAME, + [RowRendererIdEnum.suricata]: 'Suricata', + [RowRendererIdEnum.threat_match]: i18n.THREAT_MATCH_NAME, + [RowRendererIdEnum.zeek]: i18n.ZEEK_NAME, + [RowRendererIdEnum.plain]: '', }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx index 6632c7322e373..f236402fa925b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx @@ -8,7 +8,7 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { RowRendererId } from '../../../../../common/api/timeline'; +import { type RowRendererId, RowRendererIdEnum } from '../../../../../common/api/timeline'; import { AlertsExample, AuditdExample, @@ -51,15 +51,15 @@ export interface RowRendererOption { export const renderers: RowRendererOption[] = [ { - id: RowRendererId.alerts, - name: eventRendererNames[RowRendererId.alerts], + id: RowRendererIdEnum.alerts, + name: eventRendererNames[RowRendererIdEnum.alerts], description: i18n.ALERTS_DESCRIPTION, example: AlertsExample, searchableDescription: i18n.ALERTS_DESCRIPTION, }, { - id: RowRendererId.auditd, - name: eventRendererNames[RowRendererId.auditd], + id: RowRendererIdEnum.auditd, + name: eventRendererNames[RowRendererIdEnum.auditd], description: ( <span> <Link url="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-auditd.html"> @@ -72,8 +72,8 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.AUDITD_NAME} ${i18n.AUDITD_DESCRIPTION_PART1}`, }, { - id: RowRendererId.auditd_file, - name: eventRendererNames[RowRendererId.auditd_file], + id: RowRendererIdEnum.auditd_file, + name: eventRendererNames[RowRendererIdEnum.auditd_file], description: ( <span> <Link url="https://www.elastic.co/guide/en/beats/auditbeat/current/auditbeat-module-auditd.html"> @@ -86,15 +86,15 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.AUDITD_FILE_NAME} ${i18n.AUDITD_FILE_DESCRIPTION_PART1}`, }, { - id: RowRendererId.library, - name: eventRendererNames[RowRendererId.library], + id: RowRendererIdEnum.library, + name: eventRendererNames[RowRendererIdEnum.library], description: i18n.LIBRARY_DESCRIPTION, example: LibraryExample, searchableDescription: i18n.LIBRARY_DESCRIPTION, }, { - id: RowRendererId.system_security_event, - name: eventRendererNames[RowRendererId.system_security_event], + id: RowRendererIdEnum.system_security_event, + name: eventRendererNames[RowRendererIdEnum.system_security_event], description: ( <div> <p>{i18n.AUTHENTICATION_DESCRIPTION_PART1}</p> @@ -106,15 +106,15 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.AUTHENTICATION_DESCRIPTION_PART1} ${i18n.AUTHENTICATION_DESCRIPTION_PART2}`, }, { - id: RowRendererId.system_dns, - name: eventRendererNames[RowRendererId.system_dns], + id: RowRendererIdEnum.system_dns, + name: eventRendererNames[RowRendererIdEnum.system_dns], description: i18n.DNS_DESCRIPTION_PART1, example: SystemDnsExample, searchableDescription: i18n.DNS_DESCRIPTION_PART1, }, { - id: RowRendererId.netflow, - name: eventRendererNames[RowRendererId.netflow], + id: RowRendererIdEnum.netflow, + name: eventRendererNames[RowRendererIdEnum.netflow], description: ( <div> <p>{i18n.FLOW_DESCRIPTION_PART1}</p> @@ -126,8 +126,8 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.FLOW_DESCRIPTION_PART1} ${i18n.FLOW_DESCRIPTION_PART2}`, }, { - id: RowRendererId.system, - name: eventRendererNames[RowRendererId.system], + id: RowRendererIdEnum.system, + name: eventRendererNames[RowRendererIdEnum.system], description: ( <div> <p> @@ -145,8 +145,8 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.SYSTEM_DESCRIPTION_PART1} ${i18n.SYSTEM_NAME} ${i18n.SYSTEM_DESCRIPTION_PART2} ${i18n.SYSTEM_DESCRIPTION_PART3}`, }, { - id: RowRendererId.system_endgame_process, - name: eventRendererNames[RowRendererId.system_endgame_process], + id: RowRendererIdEnum.system_endgame_process, + name: eventRendererNames[RowRendererIdEnum.system_endgame_process], description: ( <div> <p>{i18n.PROCESS_DESCRIPTION_PART1}</p> @@ -158,29 +158,29 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.PROCESS_DESCRIPTION_PART1} ${i18n.PROCESS_DESCRIPTION_PART2}`, }, { - id: RowRendererId.registry, - name: eventRendererNames[RowRendererId.registry], + id: RowRendererIdEnum.registry, + name: eventRendererNames[RowRendererIdEnum.registry], description: i18n.REGISTRY_DESCRIPTION, example: RegistryExample, searchableDescription: i18n.REGISTRY_DESCRIPTION, }, { - id: RowRendererId.system_fim, - name: eventRendererNames[RowRendererId.system_fim], + id: RowRendererIdEnum.system_fim, + name: eventRendererNames[RowRendererIdEnum.system_fim], description: i18n.FIM_DESCRIPTION_PART1, example: SystemFimExample, searchableDescription: i18n.FIM_DESCRIPTION_PART1, }, { - id: RowRendererId.system_file, - name: eventRendererNames[RowRendererId.system_file], + id: RowRendererIdEnum.system_file, + name: eventRendererNames[RowRendererIdEnum.system_file], description: i18n.FILE_DESCRIPTION_PART1, example: SystemFileExample, searchableDescription: i18n.FILE_DESCRIPTION_PART1, }, { - id: RowRendererId.system_socket, - name: eventRendererNames[RowRendererId.system_socket], + id: RowRendererIdEnum.system_socket, + name: eventRendererNames[RowRendererIdEnum.system_socket], description: ( <div> <p>{i18n.SOCKET_DESCRIPTION_PART1}</p> @@ -192,8 +192,8 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.SOCKET_DESCRIPTION_PART1} ${i18n.SOCKET_DESCRIPTION_PART2}`, }, { - id: RowRendererId.suricata, - name: eventRendererNames[RowRendererId.suricata], + id: RowRendererIdEnum.suricata, + name: eventRendererNames[RowRendererIdEnum.suricata], description: ( <p> {i18n.SURICATA_DESCRIPTION_PART1}{' '} @@ -207,15 +207,15 @@ export const renderers: RowRendererOption[] = [ searchableDescription: `${i18n.SURICATA_DESCRIPTION_PART1} ${i18n.SURICATA_NAME} ${i18n.SURICATA_DESCRIPTION_PART2}`, }, { - id: RowRendererId.threat_match, - name: eventRendererNames[RowRendererId.threat_match], + id: RowRendererIdEnum.threat_match, + name: eventRendererNames[RowRendererIdEnum.threat_match], description: i18n.THREAT_MATCH_DESCRIPTION, example: ThreatMatchExample, searchableDescription: `${i18n.THREAT_MATCH_NAME} ${i18n.THREAT_MATCH_DESCRIPTION}`, }, { - id: RowRendererId.zeek, - name: eventRendererNames[RowRendererId.zeek], + id: RowRendererIdEnum.zeek, + name: eventRendererNames[RowRendererIdEnum.zeek], description: ( <p> {i18n.ZEEK_DESCRIPTION_PART1}{' '} diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx index be33b84ca5f0c..776d1279ae715 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx @@ -23,7 +23,7 @@ import { useDispatch } from 'react-redux'; import styled from 'styled-components'; import type { State } from '../../../common/store'; -import { RowRendererId } from '../../../../common/api/timeline'; +import { RowRendererValues } from '../../../../common/api/timeline'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { setExcludedRowRendererIds as dispatchSetExcludedRowRendererIds } from '../../store/actions'; import { timelineSelectors } from '../../store'; @@ -94,7 +94,7 @@ const StatefulRowRenderersBrowserComponent: React.FC<StatefulRowRenderersBrowser const hideFieldBrowser = useCallback(() => setShow(false), []); const handleDisableAll = useCallback(() => { - setExcludedRowRendererIds(Object.values(RowRendererId)); + setExcludedRowRendererIds(RowRendererValues); }, [setExcludedRowRendererIds]); const handleEnableAll = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/save_status/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/save_status/index.test.tsx index 395f09bc273f3..3422774127a69 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/save_status/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/save_status/index.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { TimelineSaveStatus } from '.'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../common/api/timeline'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { TimelineId } from '../../../../common/types'; @@ -26,7 +26,7 @@ const renderTimelineSaveStatus = () => { describe('TimelineSaveStatus', () => { it('should render unsaved status if draft timeline', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }); const { getByTestId, getByText } = renderTimelineSaveStatus(); @@ -61,7 +61,7 @@ describe('TimelineSaveStatus', () => { it('should not render any status', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ changed: false, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, updated: Date.now(), }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/save_status/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/save_status/index.tsx index 39fc9d963cfd6..606f674fcd595 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/save_status/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/save_status/index.tsx @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import { timelineSelectors } from '../../store'; import { timelineDefaults } from '../../store/defaults'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../common/api/timeline'; const UNSAVED = i18n.translate('xpack.securitySolution.timeline.saveStatus.unsavedLabel', { defaultMessage: 'Unsaved', @@ -47,7 +47,7 @@ export const TimelineSaveStatus = React.memo<TimelineSaveStatusProps>(({ timelin pick(['changed', 'status', 'updated'], getTimeline(state, timelineId) ?? timelineDefaults) ); - const isDraft = status === TimelineStatus.draft; + const isDraft = status === TimelineStatusEnum.draft; let statusContent: React.ReactNode; if (isDraft || !updated) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx deleted file mode 100644 index a269dd422ca38..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { FlyoutFooter } from './footer'; -import { TestProviders } from '../../../../../common/mock'; -import { TimelineId } from '../../../../../../common/types/timeline'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { mockAlertDetailsData } from '../../../../../common/components/event_details/__mocks__'; -import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; -import { KibanaServices, useKibana } from '../../../../../common/lib/kibana'; -import { coreMock } from '@kbn/core/public/mocks'; -import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; - -const ecsData: Ecs = { - _id: '1', - agent: { type: ['blah'] }, - kibana: { - alert: { - workflow_status: ['open'], - rule: { - parameters: {}, - uuid: ['testId'], - }, - }, - }, -}; - -const mockAlertDetailsDataWithIsObject = mockAlertDetailsData.map((detail) => { - return { - ...detail, - isObjectArray: false, - }; -}) as TimelineEventsDetailsItem[]; - -jest.mock('../../../../../common/components/endpoint/host_isolation'); -jest.mock('../../../../../common/components/endpoint/responder'); - -jest.mock( - '../../../../../common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status', - () => { - return { - useEndpointHostIsolationStatus: jest.fn().mockReturnValue({ - loading: false, - isIsolated: false, - agentStatus: 'healthy', - }), - }; - } -); - -jest.mock('../../../../../common/hooks/use_experimental_features', () => ({ - useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), -})); - -jest.mock('../../../../../detections/components/user_info', () => ({ - useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), -})); - -jest.mock('../../../../../common/lib/kibana'); - -jest.mock( - '../../../../../detections/containers/detection_engine/alerts/use_alerts_privileges', - () => ({ - useAlertsPrivileges: jest.fn().mockReturnValue({ hasIndexWrite: true, hasKibanaCRUD: true }), - }) -); -jest.mock('../../../../../cases/components/use_insert_timeline'); - -jest.mock( - '../../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline', - () => { - return { - useInvestigateInTimeline: jest.fn().mockReturnValue({ - investigateInTimelineActionItems: [<div />], - investigateInTimelineAlertClick: () => {}, - }), - }; - } -); -jest.mock('../../../../../detections/components/alerts_table/actions'); -jest.mock('../../../../../common/components/guided_onboarding_tour/tour_step'); - -const defaultProps = { - scopeId: TimelineId.test, - loadingEventDetails: false, - detailsEcsData: ecsData, - isHostIsolationPanelOpen: false, - handleOnEventClosed: jest.fn(), - onAddIsolationStatusClick: jest.fn(), - detailsData: mockAlertDetailsDataWithIsObject, - refetchFlyoutData: jest.fn(), -}; - -describe('event details footer component', () => { - beforeEach(() => { - const coreStartMock = coreMock.createStart(); - (KibanaServices.get as jest.Mock).mockReturnValue(coreStartMock); - (useKibana as jest.Mock).mockReturnValue({ - services: { - data: { - search: { - searchStrategyClient: jest.fn(), - }, - query: jest.fn(), - }, - cases: mockCasesContract(), - application: { - ...coreStartMock.application, - capabilities: { - ...coreStartMock.application.capabilities, - siem: { - crudEndpointExceptions: true, - }, - }, - }, - }, - }); - }); - afterEach(() => { - jest.clearAllMocks(); - }); - test('it renders the take action dropdown', () => { - const wrapper = render( - <TestProviders> - <FlyoutFooter {...defaultProps} /> - </TestProviders> - ); - expect(wrapper.getByTestId('take-action-dropdown-btn')).toBeTruthy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx deleted file mode 100644 index 85dab63c432d6..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx +++ /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 React, { useCallback, useMemo, useState } from 'react'; -import { EuiFlyoutFooter, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; -import { find } from 'lodash/fp'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { getAlertDetailsFieldValue } from '../../../../../common/lib/endpoint/utils/get_event_details_field_values'; -import { isActiveTimeline } from '../../../../../helpers'; -import { TakeActionDropdown } from '../../../../../detections/components/take_action_dropdown'; -import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; -import { useExceptionFlyout } from '../../../../../detections/components/alerts_table/timeline_actions/use_add_exception_flyout'; -import { AddExceptionFlyoutWrapper } from '../../../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; -import { EventFiltersFlyout } from '../../../../../management/pages/event_filters/view/components/event_filters_flyout'; -import { useEventFilterModal } from '../../../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; -import type { Status } from '../../../../../../common/api/detection_engine'; -import { OsqueryFlyout } from '../../../../../detections/components/osquery/osquery_flyout'; -import { useRefetchByScope } from './use_refetch_by_scope'; - -interface FlyoutFooterProps { - detailsData: TimelineEventsDetailsItem[] | null; - detailsEcsData: Ecs | null; - handleOnEventClosed: () => void; - isHostIsolationPanelOpen: boolean; - isReadOnly?: boolean; - loadingEventDetails: boolean; - onAddIsolationStatusClick: (action: 'isolateHost' | 'unisolateHost') => void; - scopeId: string; - refetchFlyoutData: () => Promise<void>; -} - -interface AddExceptionModalWrapperData { - alertStatus: Status; - eventId: string; - ruleId: string; - ruleRuleId: string; - ruleName: string; -} - -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 -export const FlyoutFooterComponent = ({ - detailsData, - detailsEcsData, - handleOnEventClosed, - isHostIsolationPanelOpen, - isReadOnly, - loadingEventDetails, - onAddIsolationStatusClick, - scopeId, - refetchFlyoutData, -}: FlyoutFooterProps) => { - const { euiTheme } = useEuiTheme(); - const flyoutZIndex = useMemo( - () => ({ style: `z-index: ${(euiTheme.levels.flyout as number) + 3}` }), - [euiTheme] - ); - - const alertId = detailsEcsData?.kibana?.alert ? detailsEcsData?._id : null; - const ruleIndexRaw = useMemo( - () => - find({ category: 'signal', field: 'signal.rule.index' }, detailsData)?.values ?? - find({ category: 'kibana', field: 'kibana.alert.rule.parameters.index' }, detailsData) - ?.values, - [detailsData] - ); - const ruleIndex = useMemo( - (): string[] | undefined => (Array.isArray(ruleIndexRaw) ? ruleIndexRaw : undefined), - [ruleIndexRaw] - ); - const ruleDataViewIdRaw = useMemo( - () => - find({ category: 'signal', field: 'signal.rule.data_view_id' }, detailsData)?.values ?? - find({ category: 'kibana', field: 'kibana.alert.rule.parameters.data_view_id' }, detailsData) - ?.values, - [detailsData] - ); - const ruleDataViewId = useMemo( - (): string | undefined => (Array.isArray(ruleDataViewIdRaw) ? ruleDataViewIdRaw[0] : undefined), - [ruleDataViewIdRaw] - ); - - const addExceptionModalWrapperData = useMemo( - () => - [ - { category: 'signal', field: 'signal.rule.id', name: 'ruleId' }, - { category: 'signal', field: 'signal.rule.rule_id', name: 'ruleRuleId' }, - { category: 'signal', field: 'signal.rule.name', name: 'ruleName' }, - { category: 'signal', field: 'kibana.alert.workflow_status', name: 'alertStatus' }, - { category: '_id', field: '_id', name: 'eventId' }, - ].reduce<AddExceptionModalWrapperData>( - (acc, curr) => ({ - ...acc, - [curr.name]: getAlertDetailsFieldValue( - { category: curr.category, field: curr.field }, - detailsData - ), - }), - {} as AddExceptionModalWrapperData - ), - [detailsData] - ); - - const { refetch: refetchAll } = useRefetchByScope({ scopeId }); - - const { - exceptionFlyoutType, - openAddExceptionFlyout, - onAddExceptionTypeClick, - onAddExceptionCancel, - onAddExceptionConfirm, - } = useExceptionFlyout({ - refetch: refetchAll, - isActiveTimelines: isActiveTimeline(scopeId), - }); - const { closeAddEventFilterModal, isAddEventFilterModalOpen, onAddEventFilterClick } = - useEventFilterModal(); - - const [isOsqueryFlyoutOpenWithAgentId, setOsqueryFlyoutOpenWithAgentId] = useState<null | string>( - null - ); - - const closeOsqueryFlyout = useCallback(() => { - setOsqueryFlyoutOpenWithAgentId(null); - }, [setOsqueryFlyoutOpenWithAgentId]); - - if (isReadOnly) { - return null; - } - - return ( - <> - <EuiFlyoutFooter - className="side-panel-flyout-footer" - data-test-subj="side-panel-flyout-footer" - > - <EuiFlexGroup justifyContent="flexEnd"> - <EuiFlexItem grow={false}> - {detailsEcsData && ( - <TakeActionDropdown - detailsData={detailsData} - ecsData={detailsEcsData} - handleOnEventClosed={handleOnEventClosed} - isHostIsolationPanelOpen={isHostIsolationPanelOpen} - loadingEventDetails={loadingEventDetails} - onAddEventFilterClick={onAddEventFilterClick} - onAddExceptionTypeClick={onAddExceptionTypeClick} - onAddIsolationStatusClick={onAddIsolationStatusClick} - refetchFlyoutData={refetchFlyoutData} - refetch={refetchAll} - scopeId={scopeId} - onOsqueryClick={setOsqueryFlyoutOpenWithAgentId} - /> - )} - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlyoutFooter> - {/* This is still wrong to do render flyout/modal inside of the flyout - We need to completely refactor the EventDetails component to be correct - */} - {openAddExceptionFlyout && - addExceptionModalWrapperData.ruleId != null && - addExceptionModalWrapperData.ruleRuleId != null && - addExceptionModalWrapperData.eventId != null && ( - <AddExceptionFlyoutWrapper - {...addExceptionModalWrapperData} - ruleIndices={ruleIndex} - ruleDataViewId={ruleDataViewId} - exceptionListType={exceptionFlyoutType} - onCancel={onAddExceptionCancel} - onConfirm={onAddExceptionConfirm} - /> - )} - {isAddEventFilterModalOpen && detailsEcsData != null && ( - <EventFiltersFlyout - data={detailsEcsData} - onCancel={closeAddEventFilterModal} - // EUI TODO: This z-index override of EuiOverlayMask is a workaround, and ideally should be resolved with a cleaner UI/UX flow long-term - maskProps={flyoutZIndex} // we need this flyout to be above the timeline flyout (which has a z-index of 1002) - /> - )} - {isOsqueryFlyoutOpenWithAgentId && detailsEcsData != null && ( - <OsqueryFlyout - agentId={isOsqueryFlyoutOpenWithAgentId} - defaultValues={alertId ? { alertIds: [alertId] } : undefined} - onClose={closeOsqueryFlyout} - ecsData={detailsEcsData} - /> - )} - </> - ); -}; - -export const FlyoutFooter = React.memo(FlyoutFooterComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx deleted file mode 100644 index 50eabbaa2e115..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/index.tsx +++ /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. - */ - -// TODO: DELETE FIND WHEN FOOTER IS MOVED TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 -export { FlyoutFooter } from './footer'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx deleted file mode 100644 index 69f5889c7d2d8..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useMemo, useReducer } from 'react'; - -import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/from_cases/endpoint_host_isolation_cases_context'; - -interface HostIsolationStateReducer { - isolateAction: 'isolateHost' | 'unisolateHost'; - isHostIsolationPanelOpen: boolean; - isIsolateActionSuccessBannerVisible: boolean; -} - -type HostIsolationActions = - | { - type: 'setIsHostIsolationPanel'; - isHostIsolationPanelOpen: boolean; - } - | { - type: 'setIsolateAction'; - isolateAction: 'isolateHost' | 'unisolateHost'; - } - | { - type: 'setIsIsolateActionSuccessBannerVisible'; - isIsolateActionSuccessBannerVisible: boolean; - }; - -const initialHostIsolationState: HostIsolationStateReducer = { - isolateAction: 'isolateHost', - isHostIsolationPanelOpen: false, - isIsolateActionSuccessBannerVisible: false, -}; - -function hostIsolationReducer(state: HostIsolationStateReducer, action: HostIsolationActions) { - switch (action.type) { - case 'setIsolateAction': - return { ...state, isolateAction: action.isolateAction }; - case 'setIsHostIsolationPanel': - return { ...state, isHostIsolationPanelOpen: action.isHostIsolationPanelOpen }; - case 'setIsIsolateActionSuccessBannerVisible': - return { - ...state, - isIsolateActionSuccessBannerVisible: action.isIsolateActionSuccessBannerVisible, - }; - default: - throw new Error(); - } -} - -// TODO: MOVE TO FLYOUT FOLDER - https://github.com/elastic/security-team/issues/7462 -const useHostIsolationTools = () => { - const [ - { isolateAction, isHostIsolationPanelOpen, isIsolateActionSuccessBannerVisible }, - dispatch, - ] = useReducer(hostIsolationReducer, initialHostIsolationState); - - const showAlertDetails = useCallback(() => { - dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: false }); - dispatch({ - type: 'setIsIsolateActionSuccessBannerVisible', - isIsolateActionSuccessBannerVisible: false, - }); - }, []); - - const showHostIsolationPanel = useCallback((action) => { - if (action === 'isolateHost' || action === 'unisolateHost') { - dispatch({ type: 'setIsHostIsolationPanel', isHostIsolationPanelOpen: true }); - dispatch({ type: 'setIsolateAction', isolateAction: action }); - } - }, []); - - const caseDetailsRefresh = useWithCaseDetailsRefresh(); - - const handleIsolationActionSuccess = useCallback(() => { - dispatch({ - type: 'setIsIsolateActionSuccessBannerVisible', - isIsolateActionSuccessBannerVisible: true, - }); - // If a case details refresh ref is defined, then refresh actions and comments - if (caseDetailsRefresh) { - caseDetailsRefresh.refreshCase(); - } - }, [caseDetailsRefresh]); - - return useMemo( - () => ({ - isolateAction, - isHostIsolationPanelOpen, - isIsolateActionSuccessBannerVisible, - handleIsolationActionSuccess, - showAlertDetails, - showHostIsolationPanel, - }), - [ - isHostIsolationPanelOpen, - isIsolateActionSuccessBannerVisible, - isolateAction, - handleIsolationActionSuccess, - showAlertDetails, - showHostIsolationPanel, - ] - ); -}; - -export { useHostIsolationTools }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 11cc6032242e3..21d74337c4aab 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -13,7 +13,7 @@ import { TestProviders } from '../../../../../common/mock'; import { EventColumnView } from './event_column_view'; import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; -import { TimelineType } from '../../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../../common/api/timeline'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { getDefaultControlColumn } from '../control_columns'; @@ -79,7 +79,7 @@ jest.mock('../../../../../common/lib/kibana', () => { describe('EventColumnView', () => { useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - (useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default); + (useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineTypeEnum.default); const ACTION_BUTTON_COUNT = 4; const leadingControlColumns = getDefaultControlColumn(ACTION_BUTTON_COUNT); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts index 95cfcdf0b0725..2ebbe54b6fac5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.test.ts @@ -6,7 +6,7 @@ */ import { eventHasNotes, eventIsPinned, getPinOnClick, getPinTooltip } from './helpers'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; describe('helpers', () => { describe('eventHasNotes', () => { @@ -26,7 +26,7 @@ describe('helpers', () => { isAlert: false, isPinned: true, eventHasNotes: true, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('This event cannot be unpinned because it has notes'); }); @@ -37,7 +37,7 @@ describe('helpers', () => { isAlert: true, isPinned: true, eventHasNotes: true, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('This alert cannot be unpinned because it has notes'); }); @@ -48,7 +48,7 @@ describe('helpers', () => { isAlert: false, isPinned: true, eventHasNotes: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('Unpin event'); }); @@ -59,7 +59,7 @@ describe('helpers', () => { isAlert: true, isPinned: true, eventHasNotes: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('Unpin alert'); }); @@ -70,7 +70,7 @@ describe('helpers', () => { isAlert: false, isPinned: false, eventHasNotes: true, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('This event cannot be unpinned because it has notes'); }); @@ -81,7 +81,7 @@ describe('helpers', () => { isAlert: true, isPinned: false, eventHasNotes: true, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('This alert cannot be unpinned because it has notes'); }); @@ -92,7 +92,7 @@ describe('helpers', () => { isAlert: false, isPinned: false, eventHasNotes: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('Pin event'); }); @@ -103,7 +103,7 @@ describe('helpers', () => { isAlert: true, isPinned: false, eventHasNotes: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }) ).toEqual('Pin alert'); }); @@ -114,7 +114,7 @@ describe('helpers', () => { isAlert: false, isPinned: false, eventHasNotes: false, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }) ).toEqual('This event may not be pinned while editing a template timeline'); }); @@ -125,7 +125,7 @@ describe('helpers', () => { isAlert: true, isPinned: false, eventHasNotes: false, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }) ).toEqual('This alert may not be pinned while editing a template timeline'); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index f4f785b92974f..caf864d4b189b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -10,8 +10,7 @@ import { isEmpty } from 'lodash/fp'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy'; import type { TimelineEventsType } from '../../../../../common/types/timeline'; -import type { TimelineTypeLiteral } from '../../../../../common/api/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import type { OnPinEvent, OnUnPinEvent } from '../events'; import * as i18n from './translations'; @@ -27,9 +26,9 @@ export const getPinTooltip = ({ isAlert: boolean; isPinned: boolean; eventHasNotes: boolean; - timelineType: TimelineTypeLiteral; + timelineType: TimelineType; }) => { - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return i18n.DISABLE_PIN(isAlert); } else if (eventHasNotes) { return i18n.PINNED_WITH_NOTES(isAlert); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/alert_renderer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/alert_renderer/index.tsx index 0361c7d470215..5d6506bfcaa9b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/alert_renderer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/alert_renderer/index.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { AlertField } from './alert_field'; import type { RowRenderer } from '../../../../../../../common/types'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { ID, DESTINATION_IP, @@ -52,7 +52,7 @@ export const ALERT_RENDERER_FIELDS = [ ]; export const alertRenderer: RowRenderer = { - id: RowRendererId.alert, + id: RowRendererIdEnum.alert, isInstance: (ecs) => eventKindMatches(get('event.kind', ecs)), renderRow: ({ contextId = DEFAULT_CONTEXT_ID, data, isDraggable, scopeId }) => { const eventId = get(ID, data); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx index ff3c48454e987..2d527e0ecec3f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/auditd/generic_row_renderer.tsx @@ -10,7 +10,7 @@ import { get } from 'lodash/fp'; import React from 'react'; import type { RowRenderer } from '../../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { RowRendererContainer } from '../row_renderer'; import { AuditdGenericDetails } from './generic_details'; @@ -24,7 +24,7 @@ export const createGenericAuditRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.auditd, + id: RowRendererIdEnum.auditd, isInstance: (ecs) => { const module: string | null | undefined = get('event.module[0]', ecs); const action: string | null | undefined = get('event.action[0]', ecs); @@ -57,7 +57,7 @@ export const createGenericFileRowRenderer = ({ text: string; fileIcon?: IconType; }): RowRenderer => ({ - id: RowRendererId.auditd_file, + id: RowRendererIdEnum.auditd_file, isInstance: (ecs) => { const module: string | null | undefined = get('event.module[0]', ecs); const action: string | null | undefined = get('event.action[0]', ecs); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/combine_renderers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/combine_renderers/index.test.tsx index 75c285d727379..61ab172d74d8c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/combine_renderers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/combine_renderers/index.test.tsx @@ -7,20 +7,20 @@ import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TimelineId } from '../../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { combineRenderers } from '.'; describe('combineRenderers', () => { const contextId = 'abcd'; const a = { - id: RowRendererId.netflow, + id: RowRendererIdEnum.netflow, isInstance: jest.fn(), renderRow: jest.fn(), }; const b = { - id: RowRendererId.registry, + id: RowRendererIdEnum.registry, isInstance: jest.fn(), renderRow: jest.fn(), }; @@ -32,7 +32,7 @@ describe('combineRenderers', () => { beforeEach(() => jest.clearAllMocks()); it('returns a renderer with the expected id', () => { - const id = RowRendererId.library; // typically id from 'a', or 'b', but it can be any value + const id = RowRendererIdEnum.library; // typically id from 'a', or 'b', but it can be any value expect(combineRenderers({ a, b, id }).id).toEqual(id); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx index 6d994a7f91a9f..51a6e2b29189c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/cti/threat_match_row_renderer.tsx @@ -6,12 +6,12 @@ */ import type { RowRenderer } from '../../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { hasThreatMatchValue } from './helpers'; import { renderThreatMatchRows } from './threat_match_rows'; export const threatMatchRowRenderer: RowRenderer = { - id: RowRendererId.threat_match, + id: RowRendererIdEnum.threat_match, isInstance: hasThreatMatchValue, renderRow: renderThreatMatchRows, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 61bff652c807f..8b6242fbdb205 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -12,8 +12,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { isEmpty, isNumber } from 'lodash/fp'; import React from 'react'; import { css } from '@emotion/css'; +import type { FieldSpec } from '@kbn/data-plugin/common'; -import type { BrowserField } from '../../../../../common/containers/source'; import { ALERT_HOST_CRITICALITY, ALERT_USER_CRITICALITY, @@ -71,7 +71,7 @@ const FormattedFieldValueComponent: React.FC<{ isObjectArray?: boolean; isUnifiedDataTable?: boolean; fieldFormat?: string; - fieldFromBrowserField?: Partial<BrowserField>; + fieldFromBrowserField?: Partial<FieldSpec>; fieldName: string; fieldType?: string; isButton?: boolean; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx index ff56d2a0791f7..097096667171c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx @@ -10,7 +10,7 @@ import React from 'react'; import styled from 'styled-components'; import type { RowRenderer } from '../../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { asArrayIfExists } from '../../../../../../common/lib/helpers'; import { TLS_CLIENT_CERTIFICATE_FINGERPRINT_SHA1_FIELD_NAME, @@ -85,7 +85,7 @@ export const eventActionMatches = (eventAction: string | object | undefined | nu }; export const netflowRowRenderer: RowRenderer = { - id: RowRendererId.netflow, + id: RowRendererIdEnum.netflow, isInstance: (ecs) => eventCategoryMatches(get(EVENT_CATEGORY_FIELD, ecs)) || eventActionMatches(get(EVENT_ACTION_FIELD, ecs)), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx index b4b5062d80809..6e2d63503dd5a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/plain_row_renderer.tsx @@ -8,14 +8,14 @@ import React from 'react'; import type { RowRenderer } from '../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../common/api/timeline'; const PlainRowRenderer = () => <></>; PlainRowRenderer.displayName = 'PlainRowRenderer'; export const plainRowRenderer: RowRenderer = { - id: RowRendererId.plain, + id: RowRendererIdEnum.plain, isInstance: (_) => true, renderRow: PlainRowRenderer, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx index 6828fa020fff6..a5cd33efdd5c4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/reason_column_renderer.test.tsx @@ -14,7 +14,7 @@ import { reasonColumnRenderer } from './reason_column_renderer'; import { plainColumnRenderer } from './plain_column_renderer'; import type { ColumnHeaderOptions, RowRenderer } from '../../../../../../common/types'; -import { RowRendererId } from '../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../common/api/timeline'; import { render } from '@testing-library/react'; import { cloneDeep } from 'lodash'; @@ -41,7 +41,7 @@ const field: ColumnHeaderOptions = { const rowRenderers: RowRenderer[] = [ { - id: RowRendererId.alerts, + id: RowRendererIdEnum.alerts, isInstance: (ecs) => ecs === validEcs, renderRow: () => <span data-test-subj="test-row-render" />, }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx index d116a557a3d95..044f945375d61 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx @@ -9,13 +9,13 @@ import { get } from 'lodash/fp'; import React from 'react'; import type { RowRenderer } from '../../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { RowRendererContainer } from '../row_renderer'; import { SuricataDetails } from './suricata_details'; export const suricataRowRenderer: RowRenderer = { - id: RowRendererId.suricata, + id: RowRendererIdEnum.suricata, isInstance: (ecs) => { const module: string | null | undefined = get('event.module[0]', ecs); return module != null && module.toLowerCase() === 'suricata'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx index e0282ab0b34bc..f9aa64c6d11b4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx @@ -9,7 +9,7 @@ import { get } from 'lodash/fp'; import React from 'react'; import type { RowRenderer } from '../../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { DnsRequestEventDetails } from '../dns/dns_request_event_details'; import { EndgameSecurityEventDetails } from '../endgame/endgame_security_event_details'; @@ -28,7 +28,7 @@ export const createGenericSystemRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.system, + id: RowRendererIdEnum.system, isInstance: (ecs) => { const module: string | null | undefined = get('event.module[0]', ecs); const action: string | null | undefined = get('event.action[0]', ecs); @@ -59,7 +59,7 @@ export const createEndgameProcessRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.system_file, + id: RowRendererIdEnum.system_file, isInstance: (ecs) => { const action: string | null | undefined = get('event.action[0]', ecs); const category: string | null | undefined = get('event.category[0]', ecs); @@ -91,7 +91,7 @@ export const createFimRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.system_fim, + id: RowRendererIdEnum.system_fim, isInstance: (ecs) => { const action: string | null | undefined = get('event.action[0]', ecs); const category: string | null | undefined = get('event.category[0]', ecs); @@ -132,7 +132,7 @@ export const createEndpointAlertsRowRenderer = ({ skipRedundantProcessDetails = false, text, }: EndpointAlertCriteria): RowRenderer => ({ - id: RowRendererId.alerts, + id: RowRendererIdEnum.alerts, isInstance: (ecs) => { const action: string[] | null | undefined = get('event.action', ecs); const category: string[] | null | undefined = get('event.category', ecs); @@ -173,7 +173,7 @@ export const createEndpointLibraryRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.library, + id: RowRendererIdEnum.library, isInstance: (ecs) => { const action: string | null | undefined = get('event.action[0]', ecs); const dataset: string | null | undefined = get('event.dataset[0]', ecs); @@ -202,7 +202,7 @@ export const createGenericFileRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.system_file, + id: RowRendererIdEnum.system_file, isInstance: (ecs) => { const module: string | null | undefined = get('event.module[0]', ecs); const action: string | null | undefined = get('event.action[0]', ecs); @@ -233,7 +233,7 @@ export const createSocketRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.system_socket, + id: RowRendererIdEnum.system_socket, isInstance: (ecs) => { const action: string | null | undefined = get('event.action[0]', ecs); return action != null && action.toLowerCase() === actionName; @@ -256,7 +256,7 @@ export const createSecurityEventRowRenderer = ({ }: { actionName: string; }): RowRenderer => ({ - id: RowRendererId.system_security_event, + id: RowRendererIdEnum.system_security_event, isInstance: (ecs) => { const category: string | null | undefined = get('event.category[0]', ecs); const action: string | null | undefined = get('event.action[0]', ecs); @@ -280,7 +280,7 @@ export const createSecurityEventRowRenderer = ({ }); export const createDnsRowRenderer = (): RowRenderer => ({ - id: RowRendererId.system_dns, + id: RowRendererIdEnum.system_dns, isInstance: (ecs) => { const dnsQuestionType: string | null | undefined = get('dns.question.type[0]', ecs); const dnsQuestionName: string | null | undefined = get('dns.question.name[0]', ecs); @@ -305,7 +305,7 @@ export const createEndpointRegistryRowRenderer = ({ actionName: string; text: string; }): RowRenderer => ({ - id: RowRendererId.registry, + id: RowRendererIdEnum.registry, isInstance: (ecs) => { const action: string | null | undefined = get('event.action[0]', ecs); const dataset: string | null | undefined = get('event.dataset[0]', ecs); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx index b93fce554258c..1908bcf6d5bc4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx @@ -9,13 +9,13 @@ import { get } from 'lodash/fp'; import React from 'react'; import type { RowRenderer } from '../../../../../../../common/types/timeline'; -import { RowRendererId } from '../../../../../../../common/api/timeline'; +import { RowRendererIdEnum } from '../../../../../../../common/api/timeline'; import { RowRendererContainer } from '../row_renderer'; import { ZeekDetails } from './zeek_details'; export const zeekRowRenderer: RowRenderer = { - id: RowRendererId.zeek, + id: RowRendererIdEnum.zeek, isInstance: (ecs) => { const module: string | null | undefined = get('event.module[0]', ecs); return module != null && module.toLowerCase() === 'zeek'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx index 5903d4bfe3022..d09444edd6c99 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx @@ -21,11 +21,10 @@ import { v4 as uuidv4 } from 'uuid'; import { useDispatch } from 'react-redux'; import type { BrowserFields } from '../../../../common/containers/source'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { DataProviderTypeEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { StatefulEditDataProvider } from '../../edit_data_provider'; import { addContentToTimeline, getDisplayValue } from './helpers'; -import { DataProviderType } from './data_provider'; import { timelineSelectors } from '../../../store'; import { ADD_FIELD_LABEL, ADD_TEMPLATE_FIELD_LABEL } from './translations'; @@ -102,9 +101,9 @@ const AddDataProviderPopoverComponent: React.FC<AddDataProviderPopoverProps> = ( icon: <EuiIcon type="plusInCircle" size="m" />, panel: 1, }, - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? { - disabled: timelineType !== TimelineType.template, + disabled: timelineType !== TimelineTypeEnum.template, name: ADD_TEMPLATE_FIELD_LABEL, icon: <EuiIcon type="visText" size="m" />, panel: 2, @@ -125,7 +124,7 @@ const AddDataProviderPopoverComponent: React.FC<AddDataProviderPopoverProps> = ( operator=":" timelineId={timelineId} value="" - type={DataProviderType.default} + type={DataProviderTypeEnum.default} providerId={`${timelineId}-${uuidv4()}`} /> ), @@ -143,7 +142,7 @@ const AddDataProviderPopoverComponent: React.FC<AddDataProviderPopoverProps> = ( operator=":" timelineId={timelineId} value="" - type={DataProviderType.template} + type={DataProviderTypeEnum.template} providerId={`${timelineId}-${uuidv4()}`} /> ), @@ -153,7 +152,7 @@ const AddDataProviderPopoverComponent: React.FC<AddDataProviderPopoverProps> = ( ); const button = useMemo(() => { - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return ( <EuiButton size="s" @@ -181,7 +180,7 @@ const AddDataProviderPopoverComponent: React.FC<AddDataProviderPopoverProps> = ( }, [togglePopoverState, timelineType]); const content = useMemo(() => { - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return <EuiContextMenu initialPanelId={0} panels={panels} />; } @@ -194,7 +193,7 @@ const AddDataProviderPopoverComponent: React.FC<AddDataProviderPopoverProps> = ( operator=":" timelineId={timelineId} value="" - type={DataProviderType.default} + type={DataProviderTypeEnum.default} providerId={`${timelineId}-${uuidv4()}`} /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts index 2778874c688e5..4d5e52b7c210d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/data_provider.ts @@ -5,7 +5,7 @@ * 2.0. */ import type { QueryType } from '@kbn/elastic-assistant'; - +import type { DataProviderType } from '../../../../../common/api/timeline'; import type { PrimitiveOrArrayOfPrimitives } from '../../../../common/lib/kuery'; /** Represents the Timeline data providers */ @@ -21,11 +21,6 @@ export const IS_ONE_OF_OPERATOR = 'includes'; /** The operator applied to a field */ export type QueryOperator = typeof IS_OPERATOR | typeof EXISTS_OPERATOR | typeof IS_ONE_OF_OPERATOR; -export enum DataProviderType { - default = 'default', - template = 'template', -} - export interface QueryMatch { field: string; displayField?: string; @@ -64,7 +59,7 @@ export interface DataProvider { /** * Returns a DataProviderType */ - type?: DataProviderType.default | DataProviderType.template; + type?: DataProviderType; /** * Array of multiple values for a field */ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx index 5b26cff4f6d84..aae5ba7a73fff 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider.tsx @@ -9,8 +9,9 @@ import { noop } from 'lodash/fp'; import React, { useState } from 'react'; import type { DataProvider } from './data_provider'; -import { DataProviderType, IS_OPERATOR } from './data_provider'; +import { IS_OPERATOR } from './data_provider'; import { ProviderItemBadge } from './provider_item_badge'; +import { DataProviderTypeEnum } from '../../../../../common/api/timeline'; interface OwnProps { dataProvider: DataProvider; @@ -35,7 +36,7 @@ export const Provider = React.memo<OwnProps>(({ dataProvider }) => { displayValue={String(dataProvider.queryMatch.displayValue ?? dataProvider.queryMatch.value)} val={dataProvider.queryMatch.value} operator={dataProvider.queryMatch.operator || IS_OPERATOR} - type={dataProvider.type || DataProviderType.default} + type={dataProvider.type || DataProviderTypeEnum.default} /> ); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx index eccfb1b3eec77..72b2cf5792bfa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_badge.tsx @@ -12,12 +12,17 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import type { PrimitiveOrArrayOfPrimitives } from '../../../../common/lib/kuery'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { + type DataProviderType, + DataProviderTypeEnum, + type TimelineType, + TimelineTypeEnum, +} from '../../../../../common/api/timeline'; import { getEmptyString } from '../../../../common/components/empty_value'; import { ProviderContainer } from '../../../../common/components/drag_and_drop/provider_container'; import type { QueryOperator } from './data_provider'; -import { DataProviderType, EXISTS_OPERATOR, IS_ONE_OF_OPERATOR } from './data_provider'; +import { EXISTS_OPERATOR, IS_ONE_OF_OPERATOR } from './data_provider'; import * as i18n from './translations'; @@ -38,7 +43,7 @@ const ProviderBadgeStyled = styled(EuiBadge)<ProviderBadgeStyledType>` &.globalFilterItem { white-space: nowrap; min-width: ${({ $timelineType }) => - $timelineType === TimelineType.template ? '140px' : 'none'}; + $timelineType === TimelineTypeEnum.template ? '140px' : 'none'}; display: flex; &.globalFilterItem-isDisabled { @@ -83,7 +88,7 @@ const ConvertFieldBadge = styled(ProviderFieldBadge)` `; const TemplateFieldBadgeComponent: React.FC<TemplateFieldBadgeProps> = ({ type, toggleType }) => { - if (type !== DataProviderType.template) { + if (type !== DataProviderTypeEnum.template) { return ( <ConvertFieldBadge onClick={toggleType}>{i18n.CONVERT_TO_TEMPLATE_FIELD}</ConvertFieldBadge> ); @@ -204,7 +209,7 @@ export const ProviderBadge = React.memo<ProviderBadgeProps>( {/* Add a UI feature to let users know the is one of operator doesnt work with timeline templates: https://github.com/elastic/kibana/issues/142437 */} - {timelineType === TimelineType.template && operator !== IS_ONE_OF_OPERATOR && ( + {timelineType === TimelineTypeEnum.template && operator !== IS_ONE_OF_OPERATOR && ( <TemplateFieldBadge toggleType={toggleType} type={type} /> )} </> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx index 0da2d6ca7bd88..a5bef9b83551b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_actions.tsx @@ -11,13 +11,18 @@ import type { FC, PropsWithChildren } from 'react'; import React from 'react'; import styled from 'styled-components'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { + type DataProviderType, + DataProviderTypeEnum, + type TimelineType, + TimelineTypeEnum, +} from '../../../../../common/api/timeline'; import type { PrimitiveOrArrayOfPrimitives } from '../../../../common/lib/kuery'; import type { BrowserFields } from '../../../../common/containers/source'; import type { OnDataProviderEdited } from '../events'; import type { QueryOperator } from './data_provider'; -import { DataProviderType, EXISTS_OPERATOR, IS_ONE_OF_OPERATOR } from './data_provider'; +import { EXISTS_OPERATOR, IS_ONE_OF_OPERATOR } from './data_provider'; import { StatefulEditDataProvider } from '../../edit_data_provider'; import * as i18n from './translations'; @@ -137,13 +142,13 @@ export const getProviderActions = ({ name: i18n.FILTER_FOR_FIELD_PRESENT, onClick: onFilterForFieldPresent, }, - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? { className: CONVERT_TO_FIELD_CLASS_NAME, disabled: isLoading || operator === IS_ONE_OF_OPERATOR, icon: 'visText', name: - type === DataProviderType.template + type === DataProviderTypeEnum.template ? i18n.CONVERT_TO_FIELD : i18n.CONVERT_TO_TEMPLATE_FIELD, onClick: toggleType, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx index 871a82a9beb29..5f6f567bf32c7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/provider_item_badge.tsx @@ -9,7 +9,11 @@ import { noop } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { + type DataProviderType, + DataProviderTypeEnum, + TimelineTypeEnum, +} from '../../../../../common/api/timeline'; import type { BrowserFields } from '../../../../common/containers/source'; import { useDeepEqualSelector, @@ -22,7 +26,6 @@ import type { OnDataProviderEdited } from '../events'; import { ProviderBadge } from './provider_badge'; import { ProviderItemActions } from './provider_item_actions'; import type { DataProvidersAnd, QueryOperator } from './data_provider'; -import { DataProviderType } from './data_provider'; import { dragAndDropActions } from '../../../../common/store/drag_and_drop'; import { timelineDefaults } from '../../../store/defaults'; @@ -71,16 +74,16 @@ export const ProviderItemBadge = React.memo<ProviderItemBadgeProps>( toggleTypeProvider, displayValue, val, - type = DataProviderType.default, + type = DataProviderTypeEnum.default, wrapperRef, }) => { const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const timelineType = useShallowEqualSelector((state) => { if (!timelineId) { - return TimelineType.default; + return TimelineTypeEnum.default; } - return getTimeline(state, timelineId)?.timelineType ?? TimelineType.default; + return getTimeline(state, timelineId)?.timelineType ?? TimelineTypeEnum.default; }); const { isLoading } = useDeepEqualSelector( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx index dd7eb5cf11051..11c13f16275f8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/providers.tsx @@ -28,7 +28,8 @@ import { getTimelineProviderDroppableId, } from '../../../../common/components/drag_and_drop/helpers'; import type { DataProvider, DataProvidersAnd } from './data_provider'; -import { DataProviderType, IS_OPERATOR } from './data_provider'; +import { IS_OPERATOR } from './data_provider'; +import { DataProviderTypeEnum } from '../../../../../common/api/timeline'; import { EMPTY_GROUP, flattenIntoAndGroups } from './helpers'; import { ProviderItemBadge } from './provider_item_badge'; @@ -212,9 +213,9 @@ export const DataProvidersGroupItem = React.memo<DataProvidersGroupItem>( id: timelineId, providerId: index > 0 ? group[0].id : dataProvider.id, type: - dataProvider.type === DataProviderType.template - ? DataProviderType.default - : DataProviderType.template, + dataProvider.type === DataProviderTypeEnum.template + ? DataProviderTypeEnum.default + : DataProviderTypeEnum.template, andProviderId: index > 0 ? dataProvider.id : undefined, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx index e5587921e9ff4..f7dad276cb939 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.test.tsx @@ -7,7 +7,8 @@ import { cloneDeep } from 'lodash/fp'; -import { DataProviderType, EXISTS_OPERATOR, IS_OPERATOR } from './data_providers/data_provider'; +import { EXISTS_OPERATOR, IS_OPERATOR } from './data_providers/data_provider'; +import { DataProviderTypeEnum } from '../../../../common/api/timeline'; import { mockDataProviders } from './data_providers/mock/mock_data_providers'; import { @@ -33,7 +34,7 @@ describe('Build KQL Query', () => { test('Build KQL query with one template data provider', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 1)); - dataProviders[0].type = DataProviderType.template; + dataProviders[0].type = DataProviderTypeEnum.template; const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual('name :*'); }); @@ -133,14 +134,14 @@ describe('Build KQL Query', () => { test('Build KQL query with two data provider (first is template)', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[0].type = DataProviderType.template; + dataProviders[0].type = DataProviderTypeEnum.template; const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual('(name :*) or (name : "Provider 2")'); }); test('Build KQL query with two data provider (second is template)', () => { const dataProviders = cloneDeep(mockDataProviders.slice(0, 2)); - dataProviders[1].type = DataProviderType.template; + dataProviders[1].type = DataProviderTypeEnum.template; const kqlQuery = buildGlobalQuery(dataProviders, mockBrowserFields); expect(cleanUpKqlQuery(kqlQuery)).toEqual('(name : "Provider 1") or (name :*)'); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx index 82ea7bac73fad..04f08f203ec7f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx @@ -28,12 +28,8 @@ import { type PrimitiveOrArrayOfPrimitives, } from '../../../common/lib/kuery'; import type { DataProvider, DataProvidersAnd } from './data_providers/data_provider'; -import { - DataProviderType, - EXISTS_OPERATOR, - IS_ONE_OF_OPERATOR, - IS_OPERATOR, -} from './data_providers/data_provider'; +import { EXISTS_OPERATOR, IS_ONE_OF_OPERATOR, IS_OPERATOR } from './data_providers/data_provider'; +import { type DataProviderType, DataProviderTypeEnum } from '../../../../common/api/timeline'; import { EVENTS_TABLE_CLASS_NAME } from './styles'; const buildQueryMatch = ( @@ -211,7 +207,7 @@ export const handleIsOperator = ({ }) => { if (!isPrimitiveArray(value)) { return `${isExcluded}${ - type !== DataProviderType.template + type !== DataProviderTypeEnum.template ? buildIsQueryMatch({ browserFields, field, isFieldTypeNested, value }) : buildExistsQueryMatch({ browserFields, field, isFieldTypeNested }) }`; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index 817dd576511a5..f502c1b8884aa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -21,7 +21,7 @@ import type { CellValueElementProps } from './cell_rendering'; import { SourcererScopeName } from '../../../sourcerer/store/model'; import { TimelineModalHeader } from '../modal/header'; import type { TimelineId, RowRenderer, TimelineTabs } from '../../../../common/types/timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector'; import type { State } from '../../../common/store'; import { EVENTS_COUNT_BUTTON_CLASS_NAME, onTimelineTabKeyPressed } from './helpers'; @@ -237,7 +237,7 @@ const StatefulTimelineComponent: React.FC<Props> = ({ > <TimelineSavingProgress timelineId={timelineId} /> <TimelineBody data-test-subj="timeline-body"> - {timelineType === TimelineType.template && ( + {timelineType === TimelineTypeEnum.template && ( <TimelineTemplateBadge className="timeline-template-badge"> {i18n.TIMELINE_TEMPLATE} </TimelineTemplateBadge> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.test.tsx index 589005c0b21a2..570d5f184e9b1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.test.tsx @@ -8,7 +8,7 @@ import { mount } from 'enzyme'; import React from 'react'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { getDefaultAriaLabel, getPinIcon, Pin } from '.'; @@ -133,7 +133,7 @@ describe('pin', () => { test('the button is disabled for an event when allowUnpinning is true, and timelineType is `template`', () => { const isAlert = false; const allowUnpinning = true; - const timelineType = TimelineType.template; + const timelineType = TimelineTypeEnum.template; const wrapper = mount( <Pin allowUnpinning={allowUnpinning} @@ -152,7 +152,7 @@ describe('pin', () => { test('the button is disabled for an alert when allowUnpinning is true, and timelineType is `template`', () => { const isAlert = true; const allowUnpinning = true; - const timelineType = TimelineType.template; + const timelineType = TimelineTypeEnum.template; const wrapper = mount( <Pin allowUnpinning={allowUnpinning} @@ -171,7 +171,7 @@ describe('pin', () => { test('the button is disabled for an event when allowUnpinning is false, and timelineType is `template`', () => { const isAlert = false; const allowUnpinning = false; - const timelineType = TimelineType.template; + const timelineType = TimelineTypeEnum.template; const wrapper = mount( <Pin allowUnpinning={allowUnpinning} @@ -190,7 +190,7 @@ describe('pin', () => { test('the button is disabled for an alert when allowUnpinning is false, and timelineType is `template`', () => { const isAlert = true; const allowUnpinning = false; - const timelineType = TimelineType.template; + const timelineType = TimelineTypeEnum.template; const wrapper = mount( <Pin allowUnpinning={allowUnpinning} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.tsx index 369fadfb82457..992fa6f3d1bab 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pin/index.tsx @@ -9,8 +9,7 @@ import { EuiButtonIcon } from '@elastic/eui'; import { noop } from 'lodash/fp'; import React from 'react'; -import type { TimelineTypeLiteral } from '../../../../../common/api/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import * as i18n from '../body/translations'; @@ -23,7 +22,7 @@ interface Props { allowUnpinning: boolean; isAlert: boolean; isDisabled?: boolean; - timelineType?: TimelineTypeLiteral; + timelineType?: TimelineType; onClick?: () => void; pinned: boolean; } @@ -48,7 +47,7 @@ export const getDefaultAriaLabel = ({ export const Pin = React.memo<Props>( ({ ariaLabel, allowUnpinning, isAlert, isDisabled, onClick = noop, pinned, timelineType }) => { - const isTemplate = timelineType === TimelineType.template; + const isTemplate = timelineType === TimelineTypeEnum.template; const defaultAriaLabel = getDefaultAriaLabel({ isAlert, isTemplate, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx index e8508aaf0b4cd..aa8e0ae5cdede 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import { NotesButton } from './helpers'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { ThemeProvider } from 'styled-components'; const toggleShowNotesMock = jest.fn(); @@ -21,7 +21,7 @@ const defaultProps: ComponentProps<typeof NotesButton> = { toggleShowNotes: toggleShowNotesMock, eventId: 'event-id', notesCount: 1, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, toolTip: 'Sample Tooltip', }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index 739e07dca1995..e43d89f707a7c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -9,8 +9,7 @@ import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/e import React, { useCallback } from 'react'; import styled from 'styled-components'; -import type { TimelineTypeLiteral } from '../../../../../common/api/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import * as i18n from './translations'; @@ -29,7 +28,7 @@ interface SmallNotesButtonProps { ariaLabel?: string; isDisabled?: boolean; toggleShowNotes?: (eventId?: string) => void; - timelineType: TimelineTypeLiteral; + timelineType: TimelineType; eventId?: string; /** * Number of notes. If > 0, then a red dot is shown in the top right corner of the icon. @@ -45,7 +44,7 @@ const NotesButtonContainer = styled(EuiFlexGroup)` const SmallNotesButton = React.memo<SmallNotesButtonProps>( ({ ariaLabel = i18n.NOTES, isDisabled, toggleShowNotes, timelineType, eventId, notesCount }) => { - const isTemplate = timelineType === TimelineType.template; + const isTemplate = timelineType === TimelineTypeEnum.template; const onClick = useCallback(() => { if (eventId != null) { toggleShowNotes?.(eventId); @@ -85,7 +84,7 @@ interface NotesButtonProps { isDisabled?: boolean; toggleShowNotes?: () => void | ((eventId: string) => void); toolTip: string; - timelineType: TimelineTypeLiteral; + timelineType: TimelineType; eventId?: string; /** * Number of notes. If > 0, then a red dot is shown in the top right corner of the icon. diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx index 7fccbd9103ae9..bd5adf6b38cc4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/search_or_filter.tsx @@ -11,9 +11,7 @@ import styled from 'styled-components'; import type { Filter } from '@kbn/es-query'; import type { FilterManager } from '@kbn/data-plugin/public'; -import { DataViewPicker } from '../../../../sourcerer/experimental/components/dataview_picker'; -import { isExperimentalSourcererEnabled } from '../../../../sourcerer/experimental/is_enabled'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { InputsModelId } from '../../../../common/store/inputs/constants'; import type { KqlMode } from '../../../store/model'; import type { DispatchUpdateReduxTime } from '../../../../common/components/super_date_picker'; @@ -105,12 +103,6 @@ export const SearchOrFilter = React.memo<Props>( [isDataProviderEmpty, isDataProviderVisible] ); - const dataviewPicker = isExperimentalSourcererEnabled() ? ( - <DataViewPicker /> - ) : ( - <Sourcerer scope={SourcererScopeName.timeline} /> - ); - return ( <> <SearchOrFilterContainer> @@ -121,7 +113,7 @@ export const SearchOrFilter = React.memo<Props>( responsive={false} > <EuiFlexItem grow={false} id={TIMELINE_TOUR_CONFIG_ANCHORS.DATA_VIEW}> - {dataviewPicker} + <Sourcerer scope={SourcererScopeName.timeline} /> </EuiFlexItem> <EuiFlexItem data-test-subj="timeline-search-or-filter-search-container" grow={1}> <QueryBarTimeline @@ -147,7 +139,7 @@ export const SearchOrFilter = React.memo<Props>( DataProvider toggle is not needed in template timeline because it is always visible */ - timelineType === TimelineType.default ? ( + timelineType === TimelineTypeEnum.default ? ( <EuiFlexItem grow={false}> <EuiToolTip content={dataProviderIconTooltipContent}> <EuiButtonIcon diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx index 6dfb17c7c4065..33bbfe53bda65 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx @@ -12,15 +12,14 @@ import React, { memo, useCallback, useMemo, useState } from 'react'; import type { OpenTimelineResult } from '../../open_timeline/types'; import { SelectableTimeline } from '../selectable_timeline'; import * as i18n from '../translations'; -import type { TimelineTypeLiteral } from '../../../../../common/api/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; interface SearchTimelineSuperSelectProps { isDisabled: boolean; hideUntitled?: boolean; timelineId: string | null; timelineTitle: string | null; - timelineType?: TimelineTypeLiteral; + timelineType?: TimelineType; placeholder?: string; onTimelineChange: (timelineTitle: string, timelineId: string | null) => void; 'aria-label'?: string; @@ -42,7 +41,7 @@ const SearchTimelineSuperSelectComponent: React.FC<SearchTimelineSuperSelectProp hideUntitled = false, timelineId, timelineTitle, - timelineType = TimelineType.template, + timelineType = TimelineTypeEnum.template, onTimelineChange, placeholder, 'aria-label': ariaLabel, @@ -103,7 +102,8 @@ const SearchTimelineSuperSelectComponent: React.FC<SearchTimelineSuperSelectProp description: t.description, favorite: t.favorite, label: t.title, - id: timelineType === TimelineType.template ? t.templateTimelineId : t.savedObjectId, + id: + timelineType === TimelineTypeEnum.template ? t.templateTimelineId : t.savedObjectId, key: `${t.title}-${index}`, title: t.title, checked: [t.savedObjectId, t.templateTimelineId].includes(timelineId) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.test.tsx index bc3a36af0ed8b..8b7acb3a33cb8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import type { ShallowWrapper } from 'enzyme'; import { shallow, mount } from 'enzyme'; -import { SortFieldTimeline, TimelineType } from '../../../../../common/api/timeline'; +import { SortFieldTimelineEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { SelectableTimeline, ORIGINAL_PAGE_SIZE } from '.'; import { Direction } from '../../../../../common/search_strategy'; @@ -29,7 +29,7 @@ describe('SelectableTimeline', () => { getSelectableOptions: jest.fn().mockReturnValue([]), onClosePopover: jest.fn(), onTimelineChange: jest.fn(), - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }; describe('should render', () => { @@ -53,7 +53,7 @@ describe('SelectableTimeline', () => { }); describe('timeline template', () => { - const templateTimelineProps = { ...props, timelineType: TimelineType.template }; + const templateTimelineProps = { ...props, timelineType: TimelineTypeEnum.template }; beforeAll(() => { wrapper = shallow(<SelectableTimeline {...templateTimelineProps} />); }); @@ -79,12 +79,12 @@ describe('SelectableTimeline', () => { }, search: '', sort: { - sortField: SortFieldTimeline.updated, + sortField: SortFieldTimelineEnum.updated, sortOrder: Direction.desc, }, status: null, onlyUserFavorite: false, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }; beforeAll(() => { mount(<SelectableTimeline {...props} />); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx index a21a000df762d..71c5b273d9dc1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/selectable_timeline/index.tsx @@ -20,11 +20,7 @@ import { isEmpty, debounce } from 'lodash/fp'; import React, { memo, useCallback, useMemo, useState, useEffect, useRef } from 'react'; import styled from 'styled-components'; -import type { - TimelineTypeLiteralWithNull, - TimelineTypeLiteral, -} from '../../../../../common/api/timeline'; -import { SortFieldTimeline } from '../../../../../common/api/timeline'; +import { type TimelineType, SortFieldTimelineEnum } from '../../../../../common/api/timeline'; import { useGetAllTimeline } from '../../../containers/all'; import { isUntitled } from '../../open_timeline/helpers'; @@ -60,7 +56,7 @@ const replaceTitleInOptions = (options: EuiSelectableOption[]): EuiSelectableOpt export interface GetSelectableOptions { timelines: OpenTimelineResult[]; onlyFavorites: boolean; - timelineType?: TimelineTypeLiteralWithNull; + timelineType?: TimelineType | null; searchTimelineValue: string; } @@ -78,7 +74,7 @@ export interface SelectableTimelineProps { timelineId: string | null, graphEventId?: string ) => void; - timelineType: TimelineTypeLiteral; + timelineType: TimelineType; placeholder?: string; } @@ -254,7 +250,7 @@ const SelectableTimelineComponent: React.FC<SelectableTimelineProps> = ({ }, search: searchTimelineValue, sort: { - sortField: SortFieldTimeline.updated, + sortField: SortFieldTimelineEnum.updated, sortOrder: Direction.desc, }, onlyUserFavorite: onlyFavorites, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx index a3048347a6b7f..f315fa22a42ba 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.test.tsx @@ -12,7 +12,7 @@ import { TestProviders } from '../../../../common/mock/test_providers'; import { TabsContent } from '.'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useEsqlAvailability } from '../../../../common/hooks/esql/use_esql_availability'; import { render, screen, waitFor } from '@testing-library/react'; @@ -40,7 +40,7 @@ describe('Timeline', () => { renderCellValue: () => {}, rowRenderers: [], timelineId: TimelineId.test, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, timelineDescription: '', }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx index 7f5ab6f316a62..2401e0014fd8a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/index.tsx @@ -16,7 +16,7 @@ import { useEsqlAvailability } from '../../../../common/hooks/esql/use_esql_avai import type { SessionViewConfig } from '../../../../../common/types'; import type { RowRenderer, TimelineId } from '../../../../../common/types/timeline'; import { TimelineTabs } from '../../../../../common/types/timeline'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useShallowEqualSelector, useDeepEqualSelector, @@ -173,7 +173,7 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>( timelineId={timelineId} /> </HideShowContainer> - {timelineType === TimelineType.default && ( + {timelineType === TimelineTypeEnum.default && ( <HideShowContainer $isVisible={TimelineTabs.eql === activeTimelineTab} data-test-subj={`timeline-tab-content-${TimelineTabs.eql}`} @@ -359,7 +359,7 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({ <span>{i18n.DISCOVER_ESQL_IN_TIMELINE_TAB}</span> </StyledEuiTab> )} - {timelineType === TimelineType.default && ( + {timelineType === TimelineTypeEnum.default && ( <StyledEuiTab data-test-subj={`timelineTabs-${TimelineTabs.eql}`} onClick={setEqlAsActiveTab} @@ -395,11 +395,11 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({ data-test-subj={`timelineTabs-${TimelineTabs.notes}`} onClick={setNotesAsActiveTab} isSelected={activeTab === TimelineTabs.notes} - disabled={timelineType === TimelineType.template} + disabled={timelineType === TimelineTypeEnum.template} key={TimelineTabs.notes} > <span>{i18n.NOTES_TAB}</span> - {showTimeline && numberOfNotes > 0 && timelineType === TimelineType.default && ( + {showTimeline && numberOfNotes > 0 && timelineType === TimelineTypeEnum.default && ( <div> <CountBadge>{numberOfNotes}</CountBadge> </div> @@ -408,16 +408,18 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({ <StyledEuiTab data-test-subj={`timelineTabs-${TimelineTabs.pinned}`} onClick={setPinnedAsActiveTab} - disabled={timelineType === TimelineType.template} + disabled={timelineType === TimelineTypeEnum.template} isSelected={activeTab === TimelineTabs.pinned} key={TimelineTabs.pinned} > <span>{i18n.PINNED_TAB}</span> - {showTimeline && numberOfPinnedEvents > 0 && timelineType === TimelineType.default && ( - <div> - <CountBadge>{numberOfPinnedEvents}</CountBadge> - </div> - )} + {showTimeline && + numberOfPinnedEvents > 0 && + timelineType === TimelineTypeEnum.default && ( + <div> + <CountBadge>{numberOfPinnedEvents}</CountBadge> + </div> + )} </StyledEuiTab> </StyledEuiTabs> )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/notes/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/notes/index.tsx index 1a836b6a3a392..27d82f01828fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/notes/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/notes/index.tsx @@ -27,7 +27,7 @@ import { useDeepEqualSelector, useShallowEqualSelector, } from '../../../../../common/hooks/use_selector'; -import { TimelineStatus } from '../../../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../../../common/api/timeline'; import { appSelectors } from '../../../../../common/store/app'; import { AddNote } from '../../../notes/add_note'; import { CREATED_BY, NOTES } from '../../../notes/translations'; @@ -140,7 +140,7 @@ const NotesTabContentComponent: React.FC<NotesTabContentProps> = ({ timelineId } [] ); const [newNote, setNewNote] = useState(''); - const isImmutable = timelineStatus === TimelineStatus.immutable; + const isImmutable = timelineStatus === TimelineStatusEnum.immutable; const appNotes: TimelineResultNote[] = useDeepEqualSelector(getNotesAsCommentsList); const allTimelineNoteIds = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.test.tsx index 526963cd78607..7189c59c1b564 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.test.tsx @@ -15,7 +15,7 @@ import { mockDataProviders } from '../../../data_providers/mock/mock_data_provid import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; import { QueryTabHeader } from '.'; -import { TimelineStatus, TimelineType } from '../../../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../../../common/api/timeline'; import { waitFor } from '@testing-library/react'; import { TimelineId, TimelineTabs } from '../../../../../../../common/types'; @@ -46,9 +46,9 @@ describe('Header', () => { onToggleDataProviderType: jest.fn(), show: true, showCallOutUnauthorizedMsg: false, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, timelineId: TimelineId.test, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }; describe('rendering', () => { @@ -122,7 +122,7 @@ describe('Header', () => { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: false, - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }; const wrapper = await getWrapper( @@ -139,7 +139,7 @@ describe('Header', () => { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: false, - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }; const wrapper = await getWrapper( @@ -158,7 +158,7 @@ describe('Header', () => { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: false, - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }; const wrapper = await getWrapper( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.tsx index 069d3c33234b3..b70475787eb65 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/header/index.tsx @@ -16,8 +16,11 @@ import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/ import { useTimelineEventsCountPortal } from '../../../../../../common/hooks/use_timeline_events_count'; import { useTimelineFullScreen } from '../../../../../../common/containers/use_full_screen'; import { ExitFullScreen } from '../../../../../../common/components/exit_full_screen'; -import type { TimelineStatusLiteralWithNull } from '../../../../../../../common/api/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../../../common/api/timeline'; +import { + type TimelineStatus, + TimelineStatusEnum, + TimelineTypeEnum, +} from '../../../../../../../common/api/timeline'; import type { TimelineTabs } from '../../../../../../../common/types/timeline'; import { timelineSelectors } from '../../../../../store'; import { useDeepEqualSelector } from '../../../../../../common/hooks/use_selector'; @@ -33,7 +36,7 @@ interface Props { show: boolean; showCallOutUnauthorizedMsg: boolean; showEventsCountBadge: boolean; - status: TimelineStatusLiteralWithNull; + status: TimelineStatus | null; timelineId: string; totalCount: number; } @@ -87,7 +90,8 @@ const QueryTabHeaderComponent: React.FC<Props> = ({ (state) => getIsDataProviderVisible(state, timelineId) ?? timelineDefaults.isDataProviderVisible ); - const shouldShowQueryBuilder = isDataProviderVisible || timelineType === TimelineType.template; + const shouldShowQueryBuilder = + isDataProviderVisible || timelineType === TimelineTypeEnum.template; return ( <StyledEuiFlyoutHeader data-test-subj={`${activeTab}-tab-flyout-header`} hasBorder={false}> @@ -126,7 +130,7 @@ const QueryTabHeaderComponent: React.FC<Props> = ({ /> </EuiFlexItem> )} - {status === TimelineStatus.immutable && ( + {status === TimelineStatusEnum.immutable && ( <EuiFlexItem> <EuiCallOut data-test-subj="timelineImmutableCallOut" diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx index 8a9d5461f9893..4f879ce52b310 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.test.tsx @@ -21,7 +21,7 @@ import type { Sort } from '../../body/sort'; import { mockDataProviders } from '../../data_providers/mock/mock_data_providers'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; -import { TimelineStatus } from '../../../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../../../common/api/timeline'; import { useTimelineEvents } from '../../../../containers'; import { useTimelineEventsDetails } from '../../../../containers/details'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; @@ -128,7 +128,7 @@ describe('Timeline', () => { showCallOutUnauthorizedMsg: false, sort, start: startDate, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, timerangeKind: 'absolute', activeTab: TimelineTabs.query, show: true, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx index 2c5a1687f30ae..1644982533a9e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx @@ -83,7 +83,7 @@ const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof Experimental jest.mock('../../../../../common/lib/kibana'); // unified-field-list is reporting multiple analytics events -jest.mock(`@kbn/ebt/client`); +jest.mock(`@elastic/ebt/client`); const mockOpenFlyout = jest.fn(); const mockCloseFlyout = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/index.test.tsx index c5ae53a66032b..5a46b3b1b561c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/index.test.tsx @@ -17,7 +17,7 @@ import { TestProviders, } from '../../../../common/mock'; import { TimelineTabs } from '../../../../../common/types'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { useKibana as mockUseKibana } from '../../../../common/lib/kibana/__mocks__'; import { useKibana } from '../../../../common/lib/kibana'; @@ -38,7 +38,7 @@ const TestComponent = (props: Partial<TimelineTourProps> = {}) => { <TimelineTour activeTab={TimelineTabs.query} switchToTab={switchTabMock} - timelineType={TimelineType.default} + timelineType={TimelineTypeEnum.default} {...props} /> {Object.values(TIMELINE_TOUR_CONFIG_ANCHORS).map((anchor) => { @@ -103,7 +103,7 @@ describe('Timeline Tour', () => { }); it('should render different tour steps when timeline type is template', async () => { - render(<TestComponent timelineType={TimelineType.template} />); + render(<TestComponent timelineType={TimelineTypeEnum.template} />); await waitFor(() => { expect(screen.getByTestId('timeline-tour-step-1')).toBeVisible(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/step_config.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/step_config.tsx index 265f87a61d0bc..719a06de6d457 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/step_config.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tour/step_config.tsx @@ -8,7 +8,7 @@ import { EuiText, EuiCode } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import { TimelineTabs } from '../../../../../common/types'; import * as i18n from './translations'; @@ -66,7 +66,7 @@ export const timelineTourSteps = [ anchor: TIMELINE_TOUR_CONFIG_ANCHORS.DATA_VIEW, }, { - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, timelineTab: TimelineTabs.query, title: i18n.TIMELINE_TOUR_DATA_PROVIDER_VISIBILITY_TITLE, content: <EuiText>{i18n.TIMELINE_TOUR_DATA_PROVIDER_VISIBILITY_DESCRIPTION}</EuiText>, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/translations.ts index 2caf4eee6f1c5..f799a0e64a43e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/translations.ts @@ -6,8 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import type { TimelineTypeLiteral } from '../../../../common/api/timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../../common/api/timeline'; export const DEFAULT_TIMELINE_TITLE = i18n.translate( 'xpack.securitySolution.timeline.defaultTimelineTitle', @@ -35,10 +34,10 @@ export const EVENTS_TABLE_ARIA_LABEL = ({ defaultMessage: 'events; Page {activePage} of {totalPages}', }); -export const SEARCH_BOX_TIMELINE_PLACEHOLDER = (timelineType: TimelineTypeLiteral) => +export const SEARCH_BOX_TIMELINE_PLACEHOLDER = (timelineType: TimelineType) => i18n.translate('xpack.securitySolution.timeline.searchBoxPlaceholder', { values: { - timeline: timelineType === TimelineType.template ? 'Timeline template' : 'Timeline', + timeline: timelineType === TimelineTypeEnum.template ? 'Timeline template' : 'Timeline', }, defaultMessage: 'e.g. {timeline} name or description', }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx index 63bbab88ec6ce..b1f05281de803 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx @@ -76,7 +76,7 @@ const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof Experimental jest.mock('../../../../common/lib/kibana'); // unified-field-list is reporting multiple analytics events -jest.mock(`@kbn/ebt/client`); +jest.mock(`@elastic/ebt/client`); const columnsToDisplay = [ ...defaultUdtHeaders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx index 6c9ca63cb4e68..79e3368f79d25 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/styles.tsx @@ -73,7 +73,10 @@ export const StyledTimelineUnifiedDataTable = styled.div.attrs(({ className = '' margin-top: 3px; } - .udtTimeline .euiDataGridHeaderCell.euiDataGridHeaderCell--controlColumn { + .udtTimeline + .euiDataGridHeaderCell.euiDataGridHeaderCell--controlColumn:not( + [data-gridcell-column-id='select'] + ) { padding: 0; position: relative; } diff --git a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx index 5e88cf8b63cfe..7a2b3eca0f74b 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/all/index.tsx @@ -18,14 +18,14 @@ import { inputsActions } from '../../../common/store/inputs'; import * as i18n from '../../pages/translations'; import type { - TimelineTypeLiteralWithNull, - TimelineStatusLiteralWithNull, + TimelineType, + TimelineStatus, PageInfoTimeline, TimelineResult, SortTimeline, GetAllTimelineVariables, } from '../../../../common/api/timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { getAllTimelines } from '../api'; export interface AllTimelinesArgs { @@ -52,8 +52,8 @@ export interface AllTimelinesVariables { pageInfo: PageInfoTimeline; search: string; sort: SortTimeline; - status: TimelineStatusLiteralWithNull; - timelineType: TimelineTypeLiteralWithNull; + status: TimelineStatus | null; + timelineType: TimelineType | null; } export const ALL_TIMELINE_QUERY_ID = 'FETCH_ALL_TIMELINES'; @@ -93,7 +93,7 @@ export const getAllTimeline = memoizeOne( title: timeline.title, updated: timeline.updated, updatedBy: timeline.updatedBy, - timelineType: timeline.timelineType ?? TimelineType.default, + timelineType: timeline.timelineType ?? TimelineTypeEnum.default, templateTimelineId: timeline.templateTimelineId, queryType: getTimelineQueryTypes(timeline), })) diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts index e6238d462e3c3..ba76f857dc34c 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts @@ -9,7 +9,7 @@ import { createSearchSourceMock } from '@kbn/data-plugin/public/mocks'; import { buildDataViewMock, shallowMockedFields } from '@kbn/discover-utils/src/__mocks__'; import * as api from './api'; import { KibanaServices } from '../../common/lib/kibana'; -import { TimelineType, TimelineStatus } from '../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../common/api/timeline'; import { TIMELINE_DRAFT_URL, TIMELINE_URL, TIMELINE_COPY_URL } from '../../../common/constants'; import type { ImportDataProps } from '../../detection_engine/rule_management/logic/types'; @@ -70,7 +70,7 @@ const timelineData = { filterQuery: null, }, title: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineVersion: null, templateTimelineId: null, dateRange: { @@ -84,7 +84,7 @@ const timelineData = { sortDirection: 'desc', }, ], - status: TimelineStatus.active, + status: TimelineStatusEnum.active, savedSearchId: null, }; const mockPatchTimelineResponse = { @@ -105,7 +105,7 @@ describe('persistTimeline', () => { const timelineId = null; const initialDraftTimeline = { ...timelineData, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }; const mockDraftResponse = { data: { @@ -177,7 +177,7 @@ describe('persistTimeline', () => { const timelineId = null; const initialDraftTimeline = { ...timelineData, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }; const version = null; @@ -388,7 +388,7 @@ describe('exportSelectedTimeline', () => { }); describe('getDraftTimeline', () => { - const timelineType = { timelineType: TimelineType.default }; + const timelineType = { timelineType: TimelineTypeEnum.default }; const getMock = jest.fn(); beforeAll(() => { @@ -426,7 +426,7 @@ describe('cleanDraftTimeline', () => { }); test('should pass correct args to KibanaServices - timeline', () => { - const args = { timelineType: TimelineType.default }; + const args = { timelineType: TimelineTypeEnum.default }; api.cleanDraftTimeline(args); @@ -438,7 +438,7 @@ describe('cleanDraftTimeline', () => { test('should pass correct args to KibanaServices - timeline template', () => { const args = { - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, templateTimelineId: 'test-123', templateTimelineVersion: 1, }; diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index 4b1c106230fdd..a6e1f448f5191 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -25,13 +25,14 @@ import type { } from '../../../common/api/timeline'; import { TimelineResponseType, - TimelineStatus, + TimelineStatusEnum, TimelineErrorResponseType, importTimelineResultSchema, allTimelinesResponse, responseFavoriteTimeline, SingleTimelineResponseType, - TimelineType, + type TimelineType, + TimelineTypeEnum, ResolvedSingleTimelineResponseType, } from '../../../common/api/timeline'; import { @@ -229,7 +230,7 @@ export const persistTimeline = async ({ savedSearch, }: RequestPersistTimeline): Promise<TimelineResponse | TimelineErrorResponse> => { try { - if (isEmpty(timelineId) && timeline.status === TimelineStatus.draft && timeline) { + if (isEmpty(timelineId) && timeline.status === TimelineStatusEnum.draft && timeline) { const temp: TimelineResponse | TimelineErrorResponse = await cleanDraftTimeline({ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion timelineType: timeline.timelineType!, @@ -239,7 +240,7 @@ export const persistTimeline = async ({ const draftTimeline = decodeTimelineResponse(temp); const templateTimelineInfo = - timeline.timelineType === TimelineType.template + timeline.timelineType === TimelineTypeEnum.template ? { templateTimelineId: draftTimeline.data.persistTimeline.timeline.templateTimelineId ?? @@ -355,7 +356,7 @@ export const cleanDraftTimeline = async ({ }): Promise<TimelineResponse | TimelineErrorResponse> => { let requestBody; const templateTimelineInfo = - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? { templateTimelineId, templateTimelineVersion, 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 ecb98656597bc..6c40ca1b7dfd1 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -96,6 +96,7 @@ export interface UseTimelineEventsProps { sort?: TimelineRequestSortField[]; startDate?: string; timerangeKind?: 'absolute' | 'relative'; + fetchNotes?: boolean; } const getTimelineEvents = (timelineEdges: TimelineEdges[]): TimelineItem[] => @@ -482,6 +483,7 @@ export const useTimelineEvents = ({ sort = initSortDefault, skip = false, timerangeKind, + fetchNotes = true, }: UseTimelineEventsProps): [DataLoadingState, TimelineArgs] => { const [dataLoadingState, timelineResponse, timelineSearchHandler] = useTimelineEventsHandler({ dataViewId, @@ -503,9 +505,9 @@ export const useTimelineEvents = ({ const onTimelineSearchComplete: OnNextResponseHandler = useCallback( (response) => { - onLoad(response.events); + if (fetchNotes) onLoad(response.events); }, - [onLoad] + [fetchNotes, onLoad] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx index 49e16b927c0ca..59a1936465d52 100644 --- a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import { useCreateTimeline } from './use_create_timeline'; import type { TimeRange } from '../../common/store/inputs/model'; -import { RowRendererCount, TimelineType } from '../../../common/api/timeline'; +import { RowRendererCount, TimelineTypeEnum } from '../../../common/api/timeline'; import { TimelineId } from '../../../common/types'; import { useDiscoverInTimelineContext } from '../../common/components/discover_in_timeline/use_discover_in_timeline_context'; import { timelineActions } from '../store'; @@ -41,7 +41,8 @@ describe('useCreateTimeline', () => { it('should return a function', () => { const hookResult = renderHook( - () => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }), + () => + useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineTypeEnum.default }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, } @@ -57,7 +58,8 @@ describe('useCreateTimeline', () => { const addNotes = jest.spyOn(appActions, 'addNotes'); const hookResult = renderHook( - () => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }), + () => + useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineTypeEnum.default }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, } @@ -67,7 +69,7 @@ describe('useCreateTimeline', () => { await hookResult.result.current(); expect(createTimeline.mock.calls[0][0].id).toEqual(TimelineId.test); - expect(createTimeline.mock.calls[0][0].timelineType).toEqual(TimelineType.default); + expect(createTimeline.mock.calls[0][0].timelineType).toEqual(TimelineTypeEnum.default); expect(createTimeline.mock.calls[0][0].columns).toEqual(defaultUdtHeaders); expect(createTimeline.mock.calls[0][0].dataViewId).toEqual( mockGlobalState.sourcerer.defaultDataView.id @@ -99,7 +101,7 @@ describe('useCreateTimeline', () => { () => useCreateTimeline({ timelineId: TimelineId.test, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, onClick, }), { @@ -118,7 +120,8 @@ describe('useCreateTimeline', () => { const setAbsoluteRangeDatePicker = jest.spyOn(inputsActions, 'setAbsoluteRangeDatePicker'); const hookResult = renderHook( - () => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }), + () => + useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineTypeEnum.default }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, } @@ -138,7 +141,8 @@ describe('useCreateTimeline', () => { const setRelativeRangeDatePicker = jest.spyOn(inputsActions, 'setRelativeRangeDatePicker'); const hookResult = renderHook( - () => useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineType.default }), + () => + useCreateTimeline({ timelineId: TimelineId.test, timelineType: TimelineTypeEnum.default }), { wrapper: ({ children }) => <TestProviders>{children}</TestProviders>, } diff --git a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx index 31404b54957f7..c7b5252e0897b 100644 --- a/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/hooks/use_create_timeline.tsx @@ -12,8 +12,7 @@ import { defaultHeaders } from '../components/timeline/body/column_headers/defau import { timelineActions } from '../store'; import { useTimelineFullScreen } from '../../common/containers/use_full_screen'; import { TimelineId } from '../../../common/types/timeline'; -import type { TimelineTypeLiteral } from '../../../common/api/timeline'; -import { TimelineType } from '../../../common/api/timeline'; +import { type TimelineType, TimelineTypeEnum } from '../../../common/api/timeline'; import { useDeepEqualSelector } from '../../common/hooks/use_selector'; import { inputsActions, inputsSelectors } from '../../common/store/inputs'; import { sourcererActions, sourcererSelectors } from '../../sourcerer/store'; @@ -33,7 +32,7 @@ export interface UseCreateTimelineParams { /** * Type of the timeline (default, template) */ - timelineType: TimelineTypeLiteral; + timelineType: TimelineType; /** * Callback to be called when the timeline is created */ @@ -88,7 +87,7 @@ export const useCreateTimeline = ({ timelineType, updated: undefined, excludedRowRendererIds: - !unifiedComponentsInTimelineDisabled && timelineType !== TimelineType.template + !unifiedComponentsInTimelineDisabled && timelineType !== TimelineTypeEnum.template ? timelineDefaults.excludedRowRendererIds : [], }) diff --git a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx index 0fc2c87246a70..c7e8cb9887efe 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Redirect } from 'react-router-dom'; import { Routes, Route } from '@kbn/shared-ux-router'; -import { TimelineType } from '../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../common/api/timeline'; import { TimelinesPage } from './timelines_page'; @@ -17,8 +17,8 @@ import { appendSearch } from '../../common/components/link_to/helpers'; import { TIMELINES_PATH } from '../../../common/constants'; -const timelinesPagePath = `${TIMELINES_PATH}/:tabName(${TimelineType.default}|${TimelineType.template}|notes)`; -const timelinesDefaultPath = `${TIMELINES_PATH}/${TimelineType.default}`; +const timelinesPagePath = `${TIMELINES_PATH}/:tabName(${TimelineTypeEnum.default}|${TimelineTypeEnum.template}|notes)`; +const timelinesDefaultPath = `${TIMELINES_PATH}/${TimelineTypeEnum.default}`; export const Timelines = React.memo(() => ( <Routes> diff --git a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx index 459c37a4133f8..9d3c05c97b685 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/timelines_page.tsx @@ -9,7 +9,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useCallback, useState } from 'react'; import { useParams } from 'react-router-dom'; import { NewTimelineButton } from '../components/new_timeline'; -import { TimelineType } from '../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../common/api/timeline'; import { HeaderPage } from '../../common/components/header_page'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { useKibana } from '../../common/lib/kibana'; @@ -33,8 +33,8 @@ export const TimelinesPage = React.memo(() => { setImportDataModal(true); }, [setImportDataModal]); - const timelineType: TimelineType = - tabName === TimelineType.default ? TimelineType.default : TimelineType.template; + const timelineType = + tabName === TimelineTypeEnum.default ? TimelineTypeEnum.default : TimelineTypeEnum.template; return ( <> diff --git a/x-pack/plugins/security_solution/public/timelines/store/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/actions.ts index 8c228abc5a292..2517b44d2daae 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/actions.ts @@ -12,7 +12,6 @@ import type { SavedSearch } from '@kbn/saved-search-plugin/common'; import type { SessionViewConfig } from '../../../common/types'; import type { DataProvider, - DataProviderType, QueryOperator, } from '../components/timeline/data_providers/data_provider'; @@ -29,7 +28,7 @@ import type { ColumnHeaderOptions, SortColumnTimeline, } from '../../../common/types/timeline'; -import type { RowRendererId } from '../../../common/api/timeline'; +import type { DataProviderType, RowRendererId } from '../../../common/api/timeline'; import type { ResolveTimelineConfig } from '../components/open_timeline/types'; const actionCreator = actionCreatorFactory('x-pack/security_solution/local/timeline'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts index 1dfe5ed6bc1ee..dd9b811e144e8 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/defaults.ts @@ -6,7 +6,11 @@ */ import { TimelineTabs } from '../../../common/types/timeline'; -import { TimelineType, TimelineStatus, RowRendererId } from '../../../common/api/timeline'; +import { + TimelineTypeEnum, + TimelineStatusEnum, + RowRendererIdEnum, +} from '../../../common/api/timeline'; import { defaultHeaders } from '../components/timeline/body/column_headers/default_headers'; import { normalizeTimeRange } from '../../common/utils/normalize_time_range'; @@ -37,24 +41,24 @@ export const timelineDefaults: SubsetTimelineModel & eventType: 'all', eventIdToNoteIds: {}, excludedRowRendererIds: [ - RowRendererId.alert, - RowRendererId.alerts, - RowRendererId.auditd, - RowRendererId.auditd_file, - RowRendererId.library, - RowRendererId.netflow, - RowRendererId.plain, - RowRendererId.registry, - RowRendererId.suricata, - RowRendererId.system, - RowRendererId.system_dns, - RowRendererId.system_endgame_process, - RowRendererId.system_file, - RowRendererId.system_fim, - RowRendererId.system_security_event, - RowRendererId.system_socket, - RowRendererId.threat_match, - RowRendererId.zeek, + RowRendererIdEnum.alert, + RowRendererIdEnum.alerts, + RowRendererIdEnum.auditd, + RowRendererIdEnum.auditd_file, + RowRendererIdEnum.library, + RowRendererIdEnum.netflow, + RowRendererIdEnum.plain, + RowRendererIdEnum.registry, + RowRendererIdEnum.suricata, + RowRendererIdEnum.system, + RowRendererIdEnum.system_dns, + RowRendererIdEnum.system_endgame_process, + RowRendererIdEnum.system_file, + RowRendererIdEnum.system_fim, + RowRendererIdEnum.system_security_event, + RowRendererIdEnum.system_socket, + RowRendererIdEnum.threat_match, + RowRendererIdEnum.zeek, ], highlightedDropAndProviderId: '', historyIds: [], @@ -73,7 +77,7 @@ export const timelineDefaults: SubsetTimelineModel & resolveTimelineConfig: undefined, queryFields: [], title: '', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, noteIds: [], @@ -91,7 +95,7 @@ export const timelineDefaults: SubsetTimelineModel & sortDirection: 'desc', }, ], - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, version: null, deletedEventIds: [], selectedEventIds: {}, diff --git a/x-pack/plugins/security_solution/public/timelines/store/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/store/helpers.test.ts index a34853eb39f90..4503d1026d7c8 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/helpers.test.ts @@ -8,12 +8,16 @@ import { cloneDeep } from 'lodash/fp'; import type { ColumnHeaderOptions } from '../../../common/types/timeline'; import { TimelineTabs, TimelineId } from '../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../common/api/timeline'; +import { + DataProviderTypeEnum, + TimelineTypeEnum, + TimelineStatusEnum, +} from '../../../common/api/timeline'; import type { DataProvider, DataProvidersAnd, } from '../components/timeline/data_providers/data_provider'; -import { IS_OPERATOR, DataProviderType } from '../components/timeline/data_providers/data_provider'; +import { IS_OPERATOR } from '../components/timeline/data_providers/data_provider'; import { defaultColumnHeaderType } from '../components/timeline/body/column_headers/default_headers'; import { DEFAULT_COLUMN_MIN_WIDTH, @@ -123,10 +127,10 @@ const basicTimeline: TimelineModel = { sortDirection: Direction.desc, }, ], - status: TimelineStatus.active, + status: TimelineStatusEnum.active, templateTimelineId: null, templateTimelineVersion: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, title: '', version: null, savedSearchId: null, @@ -141,7 +145,7 @@ const timelineByIdMock: TimelineById = { const timelineByIdTemplateMock: TimelineById = { foo: { ...basicTimeline, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }, }; @@ -175,8 +179,8 @@ describe('Timeline', () => { id: 'foo', timeline: { ...basicTimeline, - status: TimelineStatus.immutable, - timelineType: TimelineType.template, + status: TimelineStatusEnum.immutable, + timelineType: TimelineTypeEnum.template, }, timelineById: timelineByIdMock, }); @@ -184,8 +188,8 @@ describe('Timeline', () => { expect(update).toEqual({ foo: { ...basicTimeline, - status: TimelineStatus.immutable, - timelineType: TimelineType.template, + status: TimelineStatusEnum.immutable, + timelineType: TimelineTypeEnum.template, dateRange: { start: '2020-10-27T11:37:31.655Z', end: '2020-10-28T11:37:31.655Z', @@ -204,7 +208,7 @@ describe('Timeline', () => { dataViewId: null, indexNames: [], timelineById: timelineByIdMock, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, savedSearchId: null, }); expect(update).not.toBe(timelineByIdMock); @@ -217,7 +221,7 @@ describe('Timeline', () => { dataViewId: null, indexNames: [], timelineById: timelineByIdMock, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, savedSearchId: null, }); expect(update).toEqual({ @@ -236,7 +240,7 @@ describe('Timeline', () => { dataViewId: null, indexNames: [], timelineById: timelineByIdMock, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, savedSearchId: null, }); expect(update).toEqual({ @@ -1218,7 +1222,7 @@ describe('Timeline', () => { const update = updateTimelineProviderType({ id: 'foo', providerId: '123', - type: DataProviderType.template, // value we are updating from default to template + type: DataProviderTypeEnum.template, // value we are updating from default to template timelineById: timelineByIdMock, }); expect(update).toBe(timelineByIdMock); @@ -1228,7 +1232,7 @@ describe('Timeline', () => { const update = updateTimelineProviderType({ id: 'foo', providerId: '123', - type: DataProviderType.template, // value we are updating from default to template + type: DataProviderTypeEnum.template, // value we are updating from default to template timelineById: timelineByIdTemplateMock, }); expect(update).not.toBe(timelineByIdTemplateMock); @@ -1238,7 +1242,7 @@ describe('Timeline', () => { const update = updateTimelineProviderType({ id: 'foo', providerId: '123', - type: DataProviderType.template, // value we are updating from default to template + type: DataProviderTypeEnum.template, // value we are updating from default to template timelineById: timelineByIdTemplateMock, }); expect(update.foo.dataProviders).not.toBe(timelineByIdTemplateMock.foo.dataProviders); @@ -1248,7 +1252,7 @@ describe('Timeline', () => { const update = updateTimelineProviderType({ id: 'foo', providerId: '123', - type: DataProviderType.template, + type: DataProviderTypeEnum.template, timelineById: timelineByIdTemplateMock, }); const expected: TimelineById = { @@ -1263,10 +1267,10 @@ describe('Timeline', () => { value: '{}', operator: IS_OPERATOR, }, - type: DataProviderType.template, + type: DataProviderTypeEnum.template, }, ], - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }, }; @@ -1278,7 +1282,7 @@ describe('Timeline', () => { { ...basicDataProvider, id: '456', - type: DataProviderType.template, + type: DataProviderTypeEnum.template, }, ]; @@ -1292,14 +1296,14 @@ describe('Timeline', () => { const update = updateTimelineProviderType({ id: 'foo', providerId: '123', - type: DataProviderType.template, // value we are updating from default to template + type: DataProviderTypeEnum.template, // value we are updating from default to template timelineById: multiDataProviderMock, }); const expected = [ { ...basicDataProvider, name: '', - type: DataProviderType.template, + type: DataProviderTypeEnum.template, queryMatch: { field: '', value: '{}', @@ -1309,7 +1313,7 @@ describe('Timeline', () => { { ...basicDataProvider, id: '456', - type: DataProviderType.template, + type: DataProviderTypeEnum.template, }, ]; expect(update.foo.dataProviders).toEqual(expected); diff --git a/x-pack/plugins/security_solution/public/timelines/store/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/helpers.ts index 5b8f9c9c099eb..dca69b8615804 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/helpers.ts @@ -16,11 +16,13 @@ import type { QueryOperator, QueryMatch, } from '../components/timeline/data_providers/data_provider'; +import { IS_OPERATOR, EXISTS_OPERATOR } from '../components/timeline/data_providers/data_provider'; import { - DataProviderType, - IS_OPERATOR, - EXISTS_OPERATOR, -} from '../components/timeline/data_providers/data_provider'; + type DataProviderType, + DataProviderTypeEnum, + TimelineStatusEnum, + TimelineTypeEnum, +} from '../../../common/api/timeline'; import type { ColumnHeaderOptions, TimelineEventsType, @@ -28,9 +30,8 @@ import type { TimelinePersistInput, SortColumnTimeline, } from '../../../common/types/timeline'; -import type { RowRendererId, TimelineTypeLiteral } from '../../../common/api/timeline'; +import type { RowRendererId, TimelineType } from '../../../common/api/timeline'; import { TimelineId } from '../../../common/types/timeline'; -import { TimelineStatus, TimelineType } from '../../../common/api/timeline'; import { normalizeTimeRange } from '../../common/utils/normalize_time_range'; import { getTimelineManageDefaults, timelineDefaults } from './defaults'; import type { KqlMode, TimelineModel } from './model'; @@ -133,8 +134,8 @@ export const addTimelineToStore = ({ initialized: timeline.initialized ?? timelineById[id].initialized, resolveTimelineConfig, dateRange: - timeline.status === TimelineStatus.immutable && - timeline.timelineType === TimelineType.template + timeline.status === TimelineStatusEnum.immutable && + timeline.timelineType === TimelineTypeEnum.template ? { start: DEFAULT_FROM_MOMENT.toISOString(), end: DEFAULT_TO_MOMENT.toISOString(), @@ -146,7 +147,7 @@ export const addTimelineToStore = ({ interface AddNewTimelineParams extends TimelinePersistInput { timelineById: TimelineById; - timelineType: TimelineTypeLiteral; + timelineType: TimelineType; } /** Adds a new `Timeline` to the provided collection of `TimelineById` */ @@ -161,7 +162,7 @@ export const addNewTimeline = ({ const { from: startDateRange, to: endDateRange } = normalizeTimeRange({ from: '', to: '' }); const dateRange = maybeDateRange ?? { start: startDateRange, end: endDateRange }; const templateTimelineInfo = - timelineType === TimelineType.template + timelineType === TimelineTypeEnum.template ? { templateTimelineId: uuidv4(), templateTimelineVersion: 1, @@ -971,14 +972,17 @@ const updateTypeAndProvider = ( ? { ...andProvider, type, - name: type === DataProviderType.template ? `${andProvider.queryMatch.field}` : '', + name: + type === DataProviderTypeEnum.template ? `${andProvider.queryMatch.field}` : '', queryMatch: { ...andProvider.queryMatch, displayField: undefined, displayValue: undefined, value: - type === DataProviderType.template ? `{${andProvider.queryMatch.field}}` : '', - operator: (type === DataProviderType.template + type === DataProviderTypeEnum.template + ? `{${andProvider.queryMatch.field}}` + : '', + operator: (type === DataProviderTypeEnum.template ? IS_OPERATOR : EXISTS_OPERATOR) as QueryOperator, }, @@ -995,13 +999,13 @@ const updateTypeProvider = (type: DataProviderType, providerId: string, timeline ? { ...provider, type, - name: type === DataProviderType.template ? `${provider.queryMatch.field}` : '', + name: type === DataProviderTypeEnum.template ? `${provider.queryMatch.field}` : '', queryMatch: { ...provider.queryMatch, displayField: undefined, displayValue: undefined, - value: type === DataProviderType.template ? `{${provider.queryMatch.field}}` : '', - operator: (type === DataProviderType.template + value: type === DataProviderTypeEnum.template ? `{${provider.queryMatch.field}}` : '', + operator: (type === DataProviderTypeEnum.template ? IS_OPERATOR : EXISTS_OPERATOR) as QueryOperator, }, @@ -1018,7 +1022,10 @@ export const updateTimelineProviderType = ({ }: UpdateTimelineProviderTypeParams): TimelineById => { const timeline = timelineById[id]; - if (timeline.timelineType !== TimelineType.template && type === DataProviderType.template) { + if ( + timeline.timelineType !== TimelineTypeEnum.template && + type === DataProviderTypeEnum.template + ) { // Not supported, timeline template cannot have template type providers return timelineById; } diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts index e58d797c95120..85bf3652ab0b9 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.test.ts @@ -7,7 +7,7 @@ import { createMockStore, kibanaMock, mockGlobalState } from '../../../common/mock'; import { TimelineId } from '../../../../common/types/timeline'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../common/api/timeline'; import { persistTimeline } from '../../containers/api'; import { ensureTimelineIsSaved } from './helpers'; @@ -58,7 +58,7 @@ describe('Timeline middleware helpers', () => { }); expect(returnedTimeline.savedObjectId).toBe(mockSavedObjectId); - expect(returnedTimeline.status).toBe(TimelineStatus.draft); + expect(returnedTimeline.status).toBe(TimelineStatusEnum.draft); }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts index cf1d74e07051c..c5cb62b523bb8 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/helpers.ts @@ -12,7 +12,7 @@ import type { inputsModel } from '../../../common/store/inputs'; import { inputsSelectors } from '../../../common/store/inputs'; import type { TimelineModel } from '../model'; import { saveTimeline, updateTimeline } from '../actions'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineStatusEnum } from '../../../../common/api/timeline'; import { selectTimelineById } from '../selectors'; /** @@ -51,7 +51,7 @@ export async function ensureTimelineIsSaved({ id: localTimelineId, timeline: { ...timeline, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }, }) ); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts index 8cf58908bb2ed..17fd55ee194c1 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_favorite.ts @@ -18,7 +18,7 @@ import { showCallOutUnauthorizedMsg, } from '../actions'; import type { ResponseFavoriteTimeline } from '../../../../common/api/timeline'; -import { TimelineType } from '../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../common/api/timeline'; import { persistFavorite } from '../../containers/api'; import { selectTimelineById } from '../selectors'; import * as i18n from '../../pages/translations'; @@ -46,7 +46,7 @@ export const favoriteTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, S timelineId: timeline.id, templateTimelineId: timeline.templateTimelineId, templateTimelineVersion: timeline.templateTimelineVersion, - timelineType: timeline.timelineType ?? TimelineType.default, + timelineType: timeline.timelineType ?? TimelineTypeEnum.default, }); const response: ResponseFavoriteTimeline = get('data.persistFavorite', result); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts index b42c520e72b60..6051c9c1bb4e5 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.test.ts @@ -9,7 +9,7 @@ import type { Filter } from '@kbn/es-query'; import { FilterStateStore } from '@kbn/es-query'; import { Direction } from '../../../../common/search_strategy'; import { TimelineId, TimelineTabs } from '../../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import { convertTimelineAsInput } from './timeline_save'; import type { TimelineModel } from '../model'; import { createMockStore, kibanaMock } from '../../../common/mock'; @@ -310,7 +310,7 @@ describe('Timeline save middleware', () => { loadingEventIds: [], queryFields: [], title: 'saved', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: null, noteIds: [], @@ -330,7 +330,7 @@ describe('Timeline save middleware', () => { sortDirection: Direction.desc, }, ], - status: TimelineStatus.active, + status: TimelineStatusEnum.active, version: 'WzM4LDFd', id: '11169110-fc22-11e9-8ca9-072f15ce2685', savedQueryId: 'my endgame timeline query', @@ -483,9 +483,9 @@ describe('Timeline save middleware', () => { ], templateTimelineId: null, templateTimelineVersion: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, title: 'saved', - status: TimelineStatus.active, + status: TimelineStatusEnum.active, }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts index e8d1e335ab569..dcbbc5cadb6d0 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/middlewares/timeline_save.ts @@ -33,7 +33,7 @@ import { inputsSelectors } from '../../../common/store/inputs'; import { selectTimelineById } from '../selectors'; import * as i18n from '../../pages/translations'; import type { inputsModel } from '../../../common/store/inputs'; -import { TimelineStatus, TimelineType } from '../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import type { TimelineErrorResponse, TimelineResponse } from '../../../../common/api/timeline'; import type { TimelineInput } from '../../../../common/search_strategy'; import type { TimelineModel } from '../model'; @@ -124,8 +124,8 @@ export const saveTimelineMiddleware: (kibana: CoreStart) => Middleware<{}, State updated: response.timeline.updated ?? undefined, savedObjectId: response.timeline.savedObjectId, version: response.timeline.version, - status: response.timeline.status ?? TimelineStatus.active, - timelineType: response.timeline.timelineType ?? TimelineType.default, + status: response.timeline.status ?? TimelineStatusEnum.active, + timelineType: response.timeline.timelineType ?? TimelineTypeEnum.default, templateTimelineId: response.timeline.templateTimelineId ?? null, templateTimelineVersion: response.timeline.templateTimelineVersion ?? null, savedSearchId: response.timeline.savedSearchId ?? null, @@ -168,7 +168,7 @@ const timelineInput: TimelineInput = { kqlQuery: null, indexNames: null, title: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineVersion: null, templateTimelineId: null, dateRange: null, diff --git a/x-pack/plugins/security_solution/public/timelines/store/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/reducer.ts index f51ee7b451373..ba93b512136c8 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/reducer.ts @@ -109,7 +109,7 @@ import { import type { TimelineState } from './types'; import { EMPTY_TIMELINE_BY_ID } from './types'; -import { TimelineType } from '../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../common/api/timeline'; export const initialTimelineState: TimelineState = { timelineById: EMPTY_TIMELINE_BY_ID, @@ -128,17 +128,20 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) timelineById: state.timelineById, }), })) - .case(createTimeline, (state, { id, timelineType = TimelineType.default, ...timelineProps }) => { - return { - ...state, - timelineById: addNewTimeline({ - id, - timelineById: state.timelineById, - timelineType, - ...timelineProps, - }), - }; - }) + .case( + createTimeline, + (state, { id, timelineType = TimelineTypeEnum.default, ...timelineProps }) => { + return { + ...state, + timelineById: addNewTimeline({ + id, + timelineById: state.timelineById, + timelineType, + ...timelineProps, + }), + }; + } + ) .case(addNote, (state, { id, noteId }) => ({ ...state, timelineById: addTimelineNote({ id, noteId, timelineById: state.timelineById }), diff --git a/x-pack/plugins/security_solution/public/timelines/store/selectors.ts b/x-pack/plugins/security_solution/public/timelines/store/selectors.ts index 19281ba06883d..842ae76c43b1c 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/selectors.ts @@ -17,12 +17,12 @@ import { TimelineTabs } from '../../../common/types'; import type { State } from '../../common/store/types'; import type { TimelineModel } from './model'; import type { InsertTimeline, TimelineById } from './types'; -import { TimelineStatus, TimelineType } from '../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../common/api/timeline'; export const getTimelineShowStatusByIdSelector = () => createSelector(timelineSelectors.selectTimeline, (timeline) => ({ activeTab: timeline?.activeTab ?? TimelineTabs.query, - status: timeline?.status ?? TimelineStatus.draft, + status: timeline?.status ?? TimelineStatusEnum.draft, show: timeline?.show ?? false, updated: timeline?.updated ?? undefined, changed: timeline?.changed ?? false, @@ -145,7 +145,7 @@ export const selectTitleByTimelineById = createSelector( if (!isEmpty(savedTitle)) { return savedTitle; } - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { return UNTITLED_TEMPLATE; } return UNTITLED_TIMELINE; diff --git a/x-pack/plugins/security_solution/scripts/beat_docs/build.js b/x-pack/plugins/security_solution/scripts/beat_docs/build.js index 15c8ce3d642c8..db766e6f738a6 100644 --- a/x-pack/plugins/security_solution/scripts/beat_docs/build.js +++ b/x-pack/plugins/security_solution/scripts/beat_docs/build.js @@ -15,10 +15,6 @@ const yaml = require('js-yaml'); const https = require('https'); // eslint-disable-next-line import/no-extraneous-dependencies const { get, isArray, isEmpty, isNumber, isString, pick } = require('lodash'); -// eslint-disable-next-line import/no-extraneous-dependencies -const Q = require('q'); -// eslint-disable-next-line import/no-extraneous-dependencies -const rimraf = require('rimraf'); const { resolve } = require('path'); // eslint-disable-next-line import/no-extraneous-dependencies const tar = require('tar'); @@ -57,26 +53,31 @@ const beats = [ const download = async (url, filepath) => { const fileStream = fs.createWriteStream(filepath); - const deferred = Q.defer(); + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); fileStream .on('open', function () { https.get(url, function (res) { res.on('error', function (err) { - deferred.reject(err); + reject(err); }); res.pipe(fileStream); }); }) .on('error', function (err) { - deferred.reject(err); + reject(err); }) .on('finish', function () { - deferred.resolve(filepath); + resolve(filepath); }); - return deferred.promise; + return promise; }; const paramsToPick = ['category', 'description', 'example', 'name', 'type', 'format']; @@ -144,8 +145,8 @@ const manageZipFields = async (beat, filePath, beatFields) => { ); const eBeatFields = convertSchemaToHash(obj, beatFields); console.log('deleting files', beat.index); - rimraf.sync(`${beat.outputDir}/winlogbeat-${BEATS_VERSION}-windows-x86_64`); - rimraf.sync(beat.filePath); + fs.rmSync(`${beat.outputDir}/winlogbeat-${BEATS_VERSION}-windows-x86_64`, { recursive: true }); + fs.rmSync(beat.filePath, { recursive: true }); return eBeatFields; } catch (err) { @@ -176,8 +177,8 @@ const manageTarFields = async (beat, filePath, beatFields) => ); const ebeatFields = convertSchemaToHash(obj, beatFields); console.log('deleting files', beat.index); - rimraf.sync(beat.outputDir); - rimraf.sync(beat.filePath); + fs.rmSync(beat.outputDir, { recursive: true }); + fs.rmSync(beat.filePath, { recursive: true }); resolve(ebeatFields); }); }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_loader.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_loader.ts index b2e0f8a5c0cdf..431de13b329af 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_loader.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_loader.ts @@ -11,6 +11,8 @@ import pMap from 'p-map'; import type { CreatePackagePolicyResponse } from '@kbn/fleet-plugin/common'; import type { ToolingLog } from '@kbn/tooling-log'; import { kibanaPackageJson } from '@kbn/repo-info'; +import { isServerlessKibanaFlavor } from '../../../../common/endpoint/utils/kibana_status'; +import { fetchFleetLatestAvailableAgentVersion } from '../../../../common/endpoint/utils/fetch_fleet_version'; import { indexAlerts } from '../../../../common/endpoint/data_loaders/index_alerts'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { fetchEndpointMetadataList } from '../../common/endpoint_metadata_services'; @@ -21,16 +23,9 @@ import { METADATA_DATASTREAM } from '../../../../common/endpoint/constants'; import { EndpointMetadataGenerator } from '../../../../common/endpoint/data_generators/endpoint_metadata_generator'; import { getEndpointPackageInfo } from '../../../../common/endpoint/utils/package'; import { ENDPOINT_ALERTS_INDEX, ENDPOINT_EVENTS_INDEX } from '../../common/constants'; -import { isServerlessKibanaFlavor } from '../../common/stack_services'; let WAS_FLEET_SETUP_DONE = false; -const CurrentKibanaVersionDocGenerator = EndpointDocGenerator.custom({ - CustomMetadataGenerator: EndpointMetadataGenerator.custom({ - version: kibanaPackageJson.version, - }), -}); - export const loadEndpointsIfNoneExist = async ( esClient: Client, kbnClient: KbnClient, @@ -84,14 +79,20 @@ export const loadEndpoints = async ({ log, onProgress, count = 2, - DocGeneratorClass = CurrentKibanaVersionDocGenerator, + DocGeneratorClass, }: LoadEndpointsOptions): Promise<void> => { if (log) { log.verbose(`loadEndpoints(): Loading ${count} endpoints...`); } + const isServerless = await isServerlessKibanaFlavor(kbnClient); + let version = kibanaPackageJson.version; + + if (isServerless) { + version = await fetchFleetLatestAvailableAgentVersion(kbnClient); + } + if (!WAS_FLEET_SETUP_DONE) { - const isServerless = await isServerlessKibanaFlavor(kbnClient); await setupFleetForEndpoint(kbnClient); await enableFleetServerIfNecessary(esClient, isServerless, kbnClient, log); // eslint-disable-next-line require-atomic-updates @@ -120,10 +121,16 @@ export const loadEndpoints = async ({ } }; + const CurrentKibanaVersionDocGenerator = EndpointDocGenerator.custom({ + CustomMetadataGenerator: EndpointMetadataGenerator.custom({ + version, + }), + }); + await pMap( Array.from({ length: count }), async () => { - const endpointGenerator = new DocGeneratorClass(); + const endpointGenerator = new (DocGeneratorClass ?? CurrentKibanaVersionDocGenerator)(); await indexEndpointHostDocs({ numDocs: 1, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts index 5cbcc3d010ec2..53e7056311751 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/delete_all_endpoint_data.ts @@ -8,7 +8,8 @@ import type { Client, estypes } from '@elastic/elasticsearch'; import assert from 'assert'; import type { ToolingLog } from '@kbn/tooling-log'; -import { createEsClient, isServerlessKibanaFlavor } from './stack_services'; +import { isServerlessKibanaFlavor } from '../../../common/endpoint/utils/kibana_status'; +import { createEsClient } from './stack_services'; import type { CreatedSecuritySuperuser } from './security_user_services'; import { createSecuritySuperuser } from './security_user_services'; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts index cb2718e72ba26..f56073f4b5bc3 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts @@ -8,6 +8,8 @@ import { kibanaPackageJson } from '@kbn/repo-info'; import type { KbnClient } from '@kbn/test'; import type { ToolingLog } from '@kbn/tooling-log'; +import { isServerlessKibanaFlavor } from '../../../common/endpoint/utils/kibana_status'; +import { fetchFleetLatestAvailableAgentVersion } from '../../../common/endpoint/utils/fetch_fleet_version'; import { prefixedOutputLogger } from './utils'; import type { HostVm } from './types'; import type { BaseVmCreateOptions } from './vm_services'; @@ -23,6 +25,8 @@ export interface CreateAndEnrollEndpointHostOptions agentPolicyId: string; /** version of the Agent to install. Defaults to stack version */ version?: string; + /** skip all checks and use provided version */ + forceVersion?: boolean; /** The name for the host. Will also be the name of the VM */ hostname?: string; /** If `version` should be exact, or if this is `true`, then the closest version will be used. Defaults to `false` */ @@ -49,13 +53,22 @@ export const createAndEnrollEndpointHost = async ({ memory, hostname, version = kibanaPackageJson.version, + forceVersion = false, useClosestVersionMatch = false, useCache = true, }: CreateAndEnrollEndpointHostOptions): Promise<CreateAndEnrollEndpointHostResponse> => { const log = prefixedOutputLogger('createAndEnrollEndpointHost()', _log); + let agentVersion = version; + + if (!forceVersion) { + const isServerless = await isServerlessKibanaFlavor(kbnClient); + if (isServerless) { + agentVersion = await fetchFleetLatestAvailableAgentVersion(kbnClient); + } + } const isRunningInCI = Boolean(process.env.CI); const vmName = hostname ?? `test-host-${Math.random().toString().substring(2, 6)}`; - const { url: agentUrl } = await getAgentDownloadUrl(version, useClosestVersionMatch, log); + const { url: agentUrl } = await getAgentDownloadUrl(agentVersion, useClosestVersionMatch, log); const agentDownload = isRunningInCI ? await downloadAndStoreAgent(agentUrl) : undefined; // TODO: remove dependency on env. var and keep function pure @@ -84,7 +97,7 @@ export const createAndEnrollEndpointHost = async ({ log, hostVm, agentPolicyId, - version, + version: agentVersion, closestVersionMatch: useClosestVersionMatch, useAgentCache: useCache, }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts index af2c2f82803d8..2a429bd3fac7f 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_server/fleet_server_services.ts @@ -41,9 +41,9 @@ import { } from '@kbn/dev-utils'; import { maybeCreateDockerNetwork, SERVERLESS_NODES, verifyDockerInstalled } from '@kbn/es'; import { resolve } from 'path'; +import { isServerlessKibanaFlavor } from '../../../../common/endpoint/utils/kibana_status'; import { captureCallingStack, dump, prefixedOutputLogger } from '../utils'; import { createToolingLogger } from '../../../../common/endpoint/data_loaders/utils'; -import { isServerlessKibanaFlavor } from '../stack_services'; import type { FormattedAxiosError } from '../../../../common/endpoint/format_axios_error'; import { catchAxiosErrorFormatAndThrow } from '../../../../common/endpoint/format_axios_error'; import { 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 75cb868c247e5..c657cc6e53119 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 @@ -11,6 +11,7 @@ import type { Agent, AgentPolicy, AgentStatus, + CopyAgentPolicyResponse, CreateAgentPolicyRequest, CreateAgentPolicyResponse, CreatePackagePolicyRequest, @@ -24,7 +25,6 @@ import type { GetPackagePoliciesResponse, PackagePolicy, PostFleetSetupResponse, - CopyAgentPolicyResponse, } from '@kbn/fleet-plugin/common'; import { AGENT_API_ROUTES, @@ -37,8 +37,8 @@ import { APP_API_ROUTES, epmRouteService, PACKAGE_POLICY_API_ROUTES, - SETUP_API_ROUTE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, + SETUP_API_ROUTE, } from '@kbn/fleet-plugin/common'; import type { ToolingLog } from '@kbn/tooling-log'; import type { KbnClient } from '@kbn/test'; @@ -49,6 +49,7 @@ import { outputRoutesService, } from '@kbn/fleet-plugin/common/services'; import type { + CopyAgentPolicyRequest, DeleteAgentPolicyResponse, EnrollmentAPIKey, GenerateServiceTokenResponse, @@ -56,12 +57,12 @@ import type { GetEnrollmentAPIKeysResponse, GetOutputsResponse, PostAgentUnenrollResponse, - CopyAgentPolicyRequest, } from '@kbn/fleet-plugin/common/types'; import semver from 'semver'; import axios from 'axios'; import { userInfo } from 'os'; import pRetry from 'p-retry'; +import { fetchKibanaStatus } from '../../../common/endpoint/utils/kibana_status'; import { isFleetServerRunning } from './fleet_server/fleet_server_services'; import { getEndpointPackageInfo } from '../../../common/endpoint/utils/package'; import type { DownloadAndStoreAgentResponse } from './agent_downloads_service'; @@ -72,7 +73,6 @@ import { RETRYABLE_TRANSIENT_ERRORS, retryOnError, } from '../../../common/endpoint/data_loaders/utils'; -import { fetchKibanaStatus } from './stack_services'; import { catchAxiosErrorFormatAndThrow } from '../../../common/endpoint/format_axios_error'; import { FleetAgentGenerator } from '../../../common/endpoint/data_generators/fleet_agent_generator'; @@ -524,24 +524,6 @@ export const getAgentDownloadUrl = async ( }; }; -/** - * Fetches the latest version of the Elastic Agent available for download - * @param kbnClient - */ - -export const fetchFleetAvailableVersions = async (kbnClient: KbnClient): Promise<string> => { - return kbnClient - .request<{ items: string[] }>({ - method: 'GET', - path: AGENT_API_ROUTES.AVAILABLE_VERSIONS_PATTERN, - headers: { - 'elastic-api-version': '2023-10-31', - }, - }) - .then((response) => response.data.items[0]) - .catch(catchAxiosErrorFormatAndThrow); -}; - /** * 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. diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts index 23a44df2d0808..f743f4e3db10c 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/serverless/index.ts @@ -7,6 +7,7 @@ import { resolve, join } from 'path'; import { readFileSync } from 'fs'; +import { REPO_ROOT } from '@kbn/repo-info'; const ES_RESOURCES_DIR = resolve(__dirname, 'es_serverless_resources'); @@ -16,6 +17,8 @@ export const ES_RESOURCES = Object.freeze({ users_roles: join(ES_RESOURCES_DIR, 'users_roles'), }); +export const resolveCloudUsersFilePath = (filename: string) => resolve(REPO_ROOT, '.ftr', filename); + export const ES_LOADED_USERS = readFileSync(ES_RESOURCES.users) .toString() .split(/\n/) 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 5d321ec09cc48..95cd75ec9eb88 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 @@ -9,7 +9,6 @@ import { Client } from '@elastic/elasticsearch'; import type { 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 type { ReqOptions } from '@kbn/test/src/kbn_client/kbn_client_requester'; import { type AxiosResponse } from 'axios'; @@ -17,8 +16,11 @@ import type { ClientOptions } from '@elastic/elasticsearch/lib/client'; import fs from 'fs'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import { omit } from 'lodash'; +import { + fetchKibanaStatus, + isServerlessKibanaFlavor, +} from '../../../common/endpoint/utils/kibana_status'; import { createToolingLogger } from '../../../common/endpoint/data_loaders/utils'; -import { catchAxiosErrorFormatAndThrow } from '../../../common/endpoint/format_axios_error'; import { isLocalhost } from './is_localhost'; import { getLocalhostRealIp } from './network_services'; import { createSecuritySuperuser } from './security_user_services'; @@ -313,10 +315,6 @@ export const fetchStackVersion = async (kbnClient: KbnClient): Promise<string> = return status.version.number; }; -export const fetchKibanaStatus = async (kbnClient: KbnClient): Promise<StatusResponse> => { - return (await kbnClient.status.get().catch(catchAxiosErrorFormatAndThrow)) as StatusResponse; -}; - /** * Checks to ensure Kibana is up and running * @param kbnClient @@ -335,26 +333,3 @@ export const waitForKibana = async (kbnClient: KbnClient): Promise<void> => { { maxTimeout: 10000 } ); }; - -/** - * Checks to see if Kibana/ES is running in serverless mode - * @param client - */ -export const isServerlessKibanaFlavor = async (client: KbnClient | Client): Promise<boolean> => { - if (client instanceof KbnClient) { - const kbnStatus = await fetchKibanaStatus(client); - - // 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'; - } else { - return (await client.info()).version.build_flavor === 'serverless'; - } -}; 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 a14caafa3c5e9..d09a83e9dc832 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 @@ -14,13 +14,14 @@ import { CA_CERT_PATH } from '@kbn/dev-utils'; import type { ToolingLog } from '@kbn/tooling-log'; import type { KbnClientOptions } from '@kbn/test'; import { KbnClient } from '@kbn/test'; +import { isServerlessKibanaFlavor } from '../../common/endpoint/utils/kibana_status'; import { createToolingLogger } from '../../common/endpoint/data_loaders/utils'; import { EndpointSecurityTestRolesLoader } from './common/role_and_user_loader'; 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, isServerlessKibanaFlavor } from './common/stack_services'; +import { fetchStackVersion } from './common/stack_services'; import { ENDPOINT_ALERTS_INDEX, ENDPOINT_EVENTS_INDEX } from './common/constants'; main(); diff --git a/x-pack/plugins/security_solution/scripts/openapi/bundle_detections.js b/x-pack/plugins/security_solution/scripts/openapi/bundle_detections.js index f79437c33222c..0d503403b5667 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/bundle_detections.js +++ b/x-pack/plugins/security_solution/scripts/openapi/bundle_detections.js @@ -20,10 +20,19 @@ const ROOT = resolve(__dirname, '../..'); ), options: { includeLabels: ['serverless'], - specInfo: { - title: 'Security Solution Detections API (Elastic Cloud Serverless)', - description: - 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + prototypeDocument: { + info: { + title: 'Security Solution Detections API (Elastic Cloud Serverless)', + description: + 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + }, + tags: [ + { + name: 'Security Solution Detections API', + description: + 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + }, + ], }, }, }); @@ -36,10 +45,19 @@ const ROOT = resolve(__dirname, '../..'); ), options: { includeLabels: ['ess'], - specInfo: { - title: 'Security Solution Detections API (Elastic Cloud and self-hosted)', - description: - 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + prototypeDocument: { + info: { + title: 'Security Solution Detections API (Elastic Cloud and self-hosted)', + description: + 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + }, + tags: [ + { + name: 'Security Solution Detections API', + description: + 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + }, + ], }, }, }); diff --git a/x-pack/plugins/security_solution/scripts/openapi/bundle_endpoint_management.js b/x-pack/plugins/security_solution/scripts/openapi/bundle_endpoint_management.js index d4d994b993057..2a63affc932b7 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/bundle_endpoint_management.js +++ b/x-pack/plugins/security_solution/scripts/openapi/bundle_endpoint_management.js @@ -20,9 +20,18 @@ const ROOT = resolve(__dirname, '../..'); ), options: { includeLabels: ['serverless'], - specInfo: { - title: 'Security Solution Endpoint Management API (Elastic Cloud Serverless)', - description: 'Interact with and manage endpoints running the Elastic Defend integration.', + prototypeDocument: { + info: { + title: 'Security Solution Endpoint Management API (Elastic Cloud Serverless)', + description: 'Interact with and manage endpoints running the Elastic Defend integration.', + }, + tags: [ + { + name: 'Security Solution Endpoint Management API', + description: + 'Interact with and manage endpoints running the Elastic Defend integration.', + }, + ], }, }, }); @@ -35,9 +44,18 @@ const ROOT = resolve(__dirname, '../..'); ), options: { includeLabels: ['ess'], - specInfo: { - title: 'Security Solution Endpoint Management API (Elastic Cloud and self-hosted)', - description: 'Interact with and manage endpoints running the Elastic Defend integration.', + prototypeDocument: { + info: { + title: 'Security Solution Endpoint Management API (Elastic Cloud and self-hosted)', + description: 'Interact with and manage endpoints running the Elastic Defend integration.', + }, + tags: [ + { + name: 'Security Solution Endpoint Management API', + description: + 'Interact with and manage endpoints running the Elastic Defend integration.', + }, + ], }, }, }); diff --git a/x-pack/plugins/security_solution/scripts/openapi/bundle_entity_analytics.js b/x-pack/plugins/security_solution/scripts/openapi/bundle_entity_analytics.js index 28131d418e09c..3975e57f1c012 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/bundle_entity_analytics.js +++ b/x-pack/plugins/security_solution/scripts/openapi/bundle_entity_analytics.js @@ -11,32 +11,50 @@ const { join, resolve } = require('path'); const ROOT = resolve(__dirname, '../..'); -bundle({ - sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'), - outputFilePath: join( - ROOT, - 'docs/openapi/serverless/security_solution_entity_analytics_api_{version}.bundled.schema.yaml' - ), - options: { - includeLabels: ['serverless'], - specInfo: { - title: 'Security Solution Entity Analytics API (Elastic Cloud Serverless)', - description: '', +(async () => { + await bundle({ + sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'), + outputFilePath: join( + ROOT, + 'docs/openapi/serverless/security_solution_entity_analytics_api_{version}.bundled.schema.yaml' + ), + options: { + includeLabels: ['serverless'], + prototypeDocument: { + info: { + title: 'Security Solution Entity Analytics API (Elastic Cloud Serverless)', + description: '', + }, + tags: [ + { + name: 'Security Solution Entity Analytics API', + description: '', + }, + ], + }, }, - }, -}); + }); -bundle({ - sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'), - outputFilePath: join( - ROOT, - 'docs/openapi/ess/security_solution_entity_analytics_api_{version}.bundled.schema.yaml' - ), - options: { - includeLabels: ['ess'], - specInfo: { - title: 'Security Solution Entity Analytics API (Elastic Cloud and self-hosted)', - description: '', + await bundle({ + sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'), + outputFilePath: join( + ROOT, + 'docs/openapi/ess/security_solution_entity_analytics_api_{version}.bundled.schema.yaml' + ), + options: { + includeLabels: ['ess'], + prototypeDocument: { + info: { + title: 'Security Solution Entity Analytics API (Elastic Cloud and self-hosted)', + description: '', + }, + tags: [ + { + name: 'Security Solution Entity Analytics API', + description: '', + }, + ], + }, }, - }, -}); + }); +})(); diff --git a/x-pack/plugins/security_solution/scripts/openapi/bundle_timeline.js b/x-pack/plugins/security_solution/scripts/openapi/bundle_timeline.js new file mode 100644 index 0000000000000..a6b4a17d6cae3 --- /dev/null +++ b/x-pack/plugins/security_solution/scripts/openapi/bundle_timeline.js @@ -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. + */ + +require('../../../../../src/setup_node_env'); +const { bundle } = require('@kbn/openapi-bundler'); +const { join, resolve } = require('path'); + +const ROOT = resolve(__dirname, '../..'); + +(async () => { + await bundle({ + sourceGlob: join(ROOT, 'common/api/timeline/**/*.schema.yaml'), + outputFilePath: join( + ROOT, + 'docs/openapi/serverless/security_solution_timeline_api_{version}.bundled.schema.yaml' + ), + options: { + includeLabels: ['serverless'], + prototypeDocument: { + info: { + title: 'Security Solution Timeline API (Elastic Cloud Serverless)', + description: + 'You can create Timelines and Timeline templates via the API, as well as import new Timelines from an ndjson file.', + }, + tags: [ + { + name: 'Security Solution Timeline API', + description: + 'You can create Timelines and Timeline templates via the API, as well as import new Timelines from an ndjson file.', + }, + ], + }, + }, + }); + + await bundle({ + sourceGlob: join(ROOT, 'common/api/timeline/**/*.schema.yaml'), + outputFilePath: join( + ROOT, + 'docs/openapi/ess/security_solution_timeline_api_{version}.bundled.schema.yaml' + ), + options: { + includeLabels: ['ess'], + prototypeDocument: { + info: { + title: 'Security Solution Timeline API (Elastic Cloud and self-hosted)', + description: + 'You can create Timelines and Timeline templates via the API, as well as import new Timelines from an ndjson file.', + }, + tags: [ + { + name: 'Security Solution Timeline API', + description: + 'You can create Timelines and Timeline templates via the API, as well as import new Timelines from an ndjson file.', + }, + ], + }, + }, + }); +})(); diff --git a/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson index ee1293900f4ec..9b69cdc5a8493 100644 --- a/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson +++ b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson @@ -1,2 +1,2 @@ -{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.model\":{},\"properties.resourceAccessed\":{},\"properties.resultCount\":{},\"properties.responseTime\":{},\"properties.errorMessage\":{},\"properties.isEnabledKnowledgeBase\":{},\"properties.isEnabledRAGAlerts\":{},\"properties.assistantStreamingEnabled\":{},\"properties.actionTypeId\":{},\"properties.message\":{},\"properties.productTier\":{},\"properties.failedToDeleteCount\":{},\"properties.totalInstalledCount\":{},\"properties.scoresWritten\":{},\"properties.taskDurationInSeconds\":{},\"properties.interval\":{},\"properties.alertSampleSizePerShard\":{},\"properties.status\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.result.successful\":{},\"properties.result.failed\":{},\"properties.result.total\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-server","runtimeFieldMap":"{\"properties.message\":{\"type\":\"keyword\"},\"properties.productTier\":{\"type\":\"keyword\"},\"properties.failedToDeleteCount\":{\"type\":\"long\"},\"properties.totalInstalledCount\":{\"type\":\"long\"},\"properties.model\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.scoresWritten\":{\"type\":\"long\"},\"properties.taskDurationInSeconds\":{\"type\":\"long\"},\"properties.interval\":{\"type\":\"keyword\"},\"properties.alertSampleSizePerShard\":{\"type\":\"long\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.result.successful\":{\"type\":\"long\"},\"properties.result.failed\":{\"type\":\"long\"},\"properties.result.total\":{\"type\":\"long\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-server"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-30T16:12:44.874Z","id":"security-solution-ebt-kibana-server","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-05-30T16:52:11.038Z","version":"WzMwNTYxLDVd"} -{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} +{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.model\":{},\"properties.resourceAccessed\":{},\"properties.resultCount\":{},\"properties.responseTime\":{},\"properties.errorMessage\":{},\"properties.isEnabledKnowledgeBase\":{},\"properties.isEnabledRAGAlerts\":{},\"properties.assistantStreamingEnabled\":{},\"properties.actionTypeId\":{},\"properties.message\":{},\"properties.productTier\":{},\"properties.failedToDeleteCount\":{},\"properties.totalInstalledCount\":{},\"properties.scoresWritten\":{},\"properties.taskDurationInSeconds\":{},\"properties.interval\":{},\"properties.alertSampleSizePerShard\":{},\"properties.status\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.result.successful\":{},\"properties.result.failed\":{},\"properties.result.total\":{},\"properties.alertsContextCount\":{},\"properties.alertsCount\":{},\"properties.configuredAlertsCount\":{},\"properties.discoveriesGenerated\":{},\"properties.durationMs\":{},\"properties.provider\":{},\"properties.total_tokens\":{},\"properties.prompt_tokens\":{},\"properties.completion_tokens\":{},\"properties.suppressionRuleType\":{},\"properties.suppressionMissingFields\":{},\"properties.suppressionAlertsCreated\":{},\"properties.suppressionAlertsSuppressed\":{},\"properties.suppressionRuleName\":{},\"properties.suppressionDuration\":{},\"properties.suppressionFieldsNumber\":{},\"properties.suppressionGroupByFieldsNumber\":{},\"properties.suppressionGroupByFields\":{},\"properties.suppressionRuleId\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-server","runtimeFieldMap":"{\"properties.message\":{\"type\":\"keyword\"},\"properties.productTier\":{\"type\":\"keyword\"},\"properties.failedToDeleteCount\":{\"type\":\"long\"},\"properties.totalInstalledCount\":{\"type\":\"long\"},\"properties.model\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.alertsContextCount\":{\"type\":\"long\"},\"properties.alertsCount\":{\"type\":\"long\"},\"properties.configuredAlertsCount\":{\"type\":\"long\"},\"properties.discoveriesGenerated\":{\"type\":\"long\"},\"properties.durationMs\":{\"type\":\"long\"},\"properties.provider\":{\"type\":\"keyword\"},\"properties.scoresWritten\":{\"type\":\"long\"},\"properties.taskDurationInSeconds\":{\"type\":\"long\"},\"properties.interval\":{\"type\":\"keyword\"},\"properties.alertSampleSizePerShard\":{\"type\":\"long\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.result.successful\":{\"type\":\"long\"},\"properties.result.failed\":{\"type\":\"long\"},\"properties.result.total\":{\"type\":\"long\"},\"properties.total_tokens\":{\"type\":\"long\"},\"properties.prompt_tokens\":{\"type\":\"long\"},\"properties.completion_tokens\":{\"type\":\"keyword\"},\"properties.suppressionMissingFields\":{\"type\":\"boolean\"},\"properties.suppressionAlertsCreated\":{\"type\":\"long\"},\"properties.suppressionAlertsSuppressed\":{\"type\":\"long\"},\"properties.suppressionRuleName\":{\"type\":\"keyword\"},\"properties.suppressionDuration\":{\"type\":\"long\"},\"properties.suppressionRuleType\":{\"type\":\"keyword\"},\"properties.suppressionGroupByFieldsNumber\":{\"type\":\"long\"},\"properties.suppressionGroupByFields\":{\"type\":\"keyword\"},\"properties.suppressionRuleId\":{\"type\":\"keyword\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-server"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-30T16:12:44.874Z","id":"security-solution-ebt-kibana-server","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-07-30T11:12:43.928Z","version":"WzM4ODczLDVd"} +{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} \ No newline at end of file diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts index ed6f65ffb7610..2b8f85bbd65ac 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.ts @@ -22,7 +22,7 @@ export interface OpenAndAcknowledgedAlertsToolParams extends AssistantToolParams } export const OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL_DESCRIPTION = - 'Call this for knowledge about the latest n open and acknowledged alerts (sorted by `kibana.alert.risk_score`) in the environment, or when answering questions about open alerts. Do not call this tool for alert count or quantity. Input should be an empty object. The output is an array of the latest n open and acknowledged alerts.'; + 'Call this for knowledge about the latest n open and acknowledged alerts (sorted by `kibana.alert.risk_score`) in the environment, or when answering questions about open alerts. Do not call this tool for alert count or quantity. The output is an array of the latest n open and acknowledged alerts.'; /** * Returns a tool for querying open and acknowledged alerts, or null if the diff --git a/x-pack/plugins/security_solution/server/client/client.test.ts b/x-pack/plugins/security_solution/server/client/client.test.ts index 4c0c4361b3a72..59be6609b3374 100644 --- a/x-pack/plugins/security_solution/server/client/client.test.ts +++ b/x-pack/plugins/security_solution/server/client/client.test.ts @@ -17,7 +17,7 @@ describe('SiemClient', () => { [SIGNALS_INDEX_KEY]: 'mockSignalsIndex', }; const spaceId = 'fooSpace'; - const client = new AppClient(spaceId, mockConfig, '8.7', 'main'); + const client = new AppClient(spaceId, mockConfig, '8.7', 'main', 'traditional'); expect(client.getSignalsIndex()).toEqual('mockSignalsIndex-fooSpace'); }); diff --git a/x-pack/plugins/security_solution/server/client/client.ts b/x-pack/plugins/security_solution/server/client/client.ts index c7e63769ac1e0..b2418e0263699 100644 --- a/x-pack/plugins/security_solution/server/client/client.ts +++ b/x-pack/plugins/security_solution/server/client/client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { BuildFlavor } from '@kbn/config'; import type { ConfigType } from '../config'; import { DEFAULT_ALERTS_INDEX, @@ -20,8 +21,15 @@ export class AppClient { private readonly sourcererDataViewId: string; private readonly kibanaVersion: string; private readonly kibanaBranch: string; + private readonly buildFlavor: BuildFlavor; - constructor(spaceId: string, config: ConfigType, kibanaVersion: string, kibanaBranch: string) { + constructor( + spaceId: string, + config: ConfigType, + kibanaVersion: string, + kibanaBranch: string, + buildFlavor: BuildFlavor + ) { const configuredSignalsIndex = config.signalsIndex; this.alertsIndex = `${DEFAULT_ALERTS_INDEX}-${spaceId}`; @@ -31,6 +39,7 @@ export class AppClient { this.spaceId = spaceId; this.kibanaVersion = kibanaVersion; this.kibanaBranch = kibanaBranch; + this.buildFlavor = buildFlavor; } public getAlertsIndex = (): string => this.alertsIndex; @@ -40,4 +49,5 @@ export class AppClient { public getSpaceId = (): string => this.spaceId; public getKibanaVersion = (): string => this.kibanaVersion; public getKibanaBranch = (): string => this.kibanaBranch; + public getBuildFlavor = (): BuildFlavor => this.buildFlavor; } diff --git a/x-pack/plugins/security_solution/server/client/factory.test.ts b/x-pack/plugins/security_solution/server/client/factory.test.ts index 681daf88fc0d9..81f9f73d759a7 100644 --- a/x-pack/plugins/security_solution/server/client/factory.test.ts +++ b/x-pack/plugins/security_solution/server/client/factory.test.ts @@ -23,6 +23,7 @@ describe('AppClientFactory', () => { config: createMockConfig(), kibanaVersion: '8.7', kibanaBranch: 'main', + buildFlavor: 'traditional', }); factory.create(mockRequest); @@ -30,6 +31,7 @@ describe('AppClientFactory', () => { 'mockSpace', expect.anything(), expect.anything(), + expect.anything(), expect.anything() ); }); @@ -42,6 +44,7 @@ describe('AppClientFactory', () => { config: createMockConfig(), kibanaVersion: '8.7', kibanaBranch: 'main', + buildFlavor: 'traditional', }); factory.create(mockRequest); @@ -49,6 +52,7 @@ describe('AppClientFactory', () => { 'default', expect.anything(), expect.anything(), + expect.anything(), expect.anything() ); }); diff --git a/x-pack/plugins/security_solution/server/client/factory.ts b/x-pack/plugins/security_solution/server/client/factory.ts index 3dc8925c0c6e3..d36b076e55531 100644 --- a/x-pack/plugins/security_solution/server/client/factory.ts +++ b/x-pack/plugins/security_solution/server/client/factory.ts @@ -6,6 +6,7 @@ */ import type { KibanaRequest } from '@kbn/core/server'; +import type { BuildFlavor } from '@kbn/config'; import { AppClient } from './client'; import type { ConfigType } from '../config'; import { invariant } from '../../common/utils/invariant'; @@ -15,6 +16,7 @@ interface SetupDependencies { config: ConfigType; kibanaVersion: string; kibanaBranch: string; + buildFlavor: BuildFlavor; } export class AppClientFactory { @@ -22,12 +24,20 @@ export class AppClientFactory { private config?: SetupDependencies['config']; private kibanaVersion?: string; private kibanaBranch?: string; + private buildFlavor?: BuildFlavor; - public setup({ getSpaceId, config, kibanaBranch, kibanaVersion }: SetupDependencies) { + public setup({ + getSpaceId, + config, + kibanaBranch, + kibanaVersion, + buildFlavor, + }: SetupDependencies) { this.getSpaceId = getSpaceId; this.config = config; this.kibanaVersion = kibanaVersion; this.kibanaBranch = kibanaBranch; + this.buildFlavor = buildFlavor; } public create(request: KibanaRequest): AppClient { @@ -43,8 +53,18 @@ export class AppClientFactory { this.kibanaBranch != null, 'Cannot create AppClient as kibanaBranch is not present. Did you forget to call setup()?' ); + invariant( + this.buildFlavor != null, + 'Cannot create AppClient as buildFlavor is not present. Did you forget to call setup()?' + ); const spaceId = this.getSpaceId?.(request) ?? 'default'; - return new AppClient(spaceId, this.config, this.kibanaVersion, this.kibanaBranch); + return new AppClient( + spaceId, + this.config, + this.kibanaVersion, + this.kibanaBranch, + this.buildFlavor + ); } } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 47099ae060efa..2fdfa5143cfe2 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -42,7 +42,6 @@ import type { HostMetadata, LogsEndpointAction, ResponseActionApiResponse, - ResponseActionRequestBody, } from '../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import type { EndpointAuthz } from '../../../../common/endpoint/types/authz'; @@ -63,7 +62,10 @@ import * as ActionDetailsService from '../../services/actions/action_details_by_ import { CaseStatuses } from '@kbn/cases-components'; import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks'; import { getResponseActionsClient as _getResponseActionsClient } from '../../services'; -import type { UploadActionApiRequestBody } from '../../../../common/api/endpoint'; +import type { + ResponseActionsRequestBody, + UploadActionApiRequestBody, +} from '../../../../common/api/endpoint'; import type { FleetToHostFileClientInterface } from '@kbn/fleet-plugin/server'; import type { HapiReadableStream, SecuritySolutionRequestHandlerContext } from '../../../types'; import { createHapiReadableStreamMock } from '../../services/actions/mocks'; @@ -92,7 +94,7 @@ jest.mock('../../services', () => { const getResponseActionsClientMock = _getResponseActionsClient; interface CallRouteInterface { - body?: ResponseActionRequestBody; + body?: ResponseActionsRequestBody; indexErrorResponse?: any; searchResponse?: HostMetadata; mockUser?: any; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 723daf576c38e..9f40852fec380 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -13,6 +13,10 @@ import { stringify } from '../../utils/stringify'; import { getResponseActionsClient, NormalizedExternalConnectorClient } from '../../services'; import type { ResponseActionsClient } from '../../services/actions/clients/lib/types'; import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import type { + KillProcessRequestBody, + SuspendProcessRequestBody, +} from '../../../../common/api/endpoint'; import { EndpointActionGetFileSchema, type ExecuteActionRequestBody, @@ -50,8 +54,6 @@ import type { ResponseActionParametersWithProcessData, ResponseActionsExecuteParameters, ResponseActionScanParameters, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../common/endpoint/types'; import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants'; import type { 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 index e1677451ff577..79eb38211387e 100644 --- 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 @@ -16,7 +16,7 @@ import { protectionUpdatesNoteSavedObjectType } from '../../lib/protection_updat import type { CreateUpdateProtectionUpdatesNoteSchema, GetProtectionUpdatesNoteSchema, -} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; +} from '../../../../common/api/endpoint/protection_updates_note'; const getProtectionNote = async (SOClient: SavedObjectsClientContract, packagePolicyId: string) => { return SOClient.find<{ note: string }>({ 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 index 4d398bbe14e6e..7b28ccfcf9fe7 100644 --- 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 @@ -10,7 +10,7 @@ import { getProtectionUpdatesNoteHandler, postProtectionUpdatesNoteHandler } fro import { GetProtectionUpdatesNoteSchema, CreateUpdateProtectionUpdatesNoteSchema, -} from '../../../../common/api/endpoint/protection_updates_note/protection_updates_note_schema'; +} from '../../../../common/api/endpoint/protection_updates_note'; import { withEndpointAuthz } from '../with_endpoint_authz'; import { PROTECTION_UPDATES_NOTE_ROUTE } from '../../../../common/endpoint/constants'; import type { EndpointAppContext } from '../../types'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts index 958c51014c6a0..b39c85726651f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts @@ -27,7 +27,10 @@ import type { EndpointActionResponseDataOutput, LogsEndpointAction, } from '../../../../../../common/endpoint/types'; -import type { IsolationRouteRequestBody } from '../../../../../../common/api/endpoint'; +import type { + IsolationRouteRequestBody, + UnisolationRouteRequestBody, +} from '../../../../../../common/api/endpoint'; import type { ResponseActionsClientOptions, ResponseActionsClientWriteActionRequestToEndpointIndexOptions, @@ -238,7 +241,7 @@ export class CrowdstrikeActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise<ActionDetails> { const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions = { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts index eaaa5fa259927..a406397c2be19 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.test.ts @@ -11,20 +11,21 @@ import { EndpointActionsClient } from '../../..'; import { endpointActionClientMock } from './mocks'; import { responseActionsClientMock } from '../mocks'; import { ENDPOINT_ACTIONS_INDEX } from '../../../../../../common/endpoint/constants'; -import type { ResponseActionRequestBody } from '../../../../../../common/endpoint/types'; + import { DEFAULT_EXECUTE_ACTION_TIMEOUT } from '../../../../../../common/endpoint/service/response_actions/constants'; import { applyEsClientSearchMock } from '../../../../mocks/utils.mock'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { BaseDataGenerator } from '../../../../../../common/endpoint/data_generators/base_data_generator'; import { Readable } from 'stream'; import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator'; +import type { ResponseActionsRequestBody } from '../../../../../../common/api/endpoint'; describe('EndpointActionsClient', () => { let classConstructorOptions: ResponseActionsClientOptions; let endpointActionsClient: ResponseActionsClient; const getCommonResponseActionOptions = (): Pick< - ResponseActionRequestBody, + ResponseActionsRequestBody, 'endpoint_ids' | 'case_ids' > => { return { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts index 59328beb46c12..df2b3c323ee08 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts @@ -24,6 +24,9 @@ import type { UploadActionApiRequestBody, ResponseActionsRequestBody, ScanActionRequestBody, + SuspendProcessRequestBody, + KillProcessRequestBody, + UnisolationRouteRequestBody, } from '../../../../../../common/api/endpoint'; import { ResponseActionsClientImpl } from '../lib/base_response_actions_client'; import type { @@ -44,8 +47,6 @@ import type { UploadedFileInfo, ResponseActionScanParameters, ResponseActionScanOutputContent, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { CommonResponseActionMethodOptions, @@ -236,10 +237,10 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise<ActionDetails> { - return this.handleResponseAction<IsolationRouteRequestBody, ActionDetails>( + return this.handleResponseAction<UnisolationRouteRequestBody, ActionDetails>( 'unisolate', actionRequest, options diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts index 67aec8d861ce4..07ab63b77a312 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts @@ -66,16 +66,17 @@ import type { SuspendProcessActionOutputContent, UploadedFileInfo, WithAllKeys, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { ExecuteActionRequestBody, GetProcessesRequestBody, IsolationRouteRequestBody, + KillProcessRequestBody, ResponseActionGetFileRequestBody, ResponseActionsRequestBody, ScanActionRequestBody, + SuspendProcessRequestBody, + UnisolationRouteRequestBody, UploadActionApiRequestBody, } from '../../../../../../common/api/endpoint'; import { stringify } from '../../../../utils/stringify'; @@ -716,7 +717,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient } public async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options?: CommonResponseActionMethodOptions ): Promise<ActionDetails> { throw new ResponseActionsNotSupportedError('unisolate'); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts index 4a7b7efd4d4a5..e3407b5ba959a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/types.ts @@ -23,17 +23,18 @@ import type { UploadedFileInfo, ResponseActionScanOutputContent, ResponseActionScanParameters, - KillProcessRequestBody, - SuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { IsolationRouteRequestBody, + UnisolationRouteRequestBody, GetProcessesRequestBody, ResponseActionGetFileRequestBody, ExecuteActionRequestBody, UploadActionApiRequestBody, BaseActionRequestBody, ScanActionRequestBody, + KillProcessRequestBody, + SuspendProcessRequestBody, } from '../../../../../../common/api/endpoint'; type OmitUnsupportedAttributes<T extends BaseActionRequestBody> = Omit< @@ -84,7 +85,7 @@ export interface ResponseActionsClient { ) => Promise<ActionDetails>; release: ( - actionRequest: OmitUnsupportedAttributes<IsolationRouteRequestBody>, + actionRequest: OmitUnsupportedAttributes<UnisolationRouteRequestBody>, options?: CommonResponseActionMethodOptions ) => Promise<ActionDetails>; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts index 906125df4c2b8..2933f25cfd0ad 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts @@ -57,7 +57,6 @@ import type { EndpointActionResponseDataOutput, GetProcessesActionOutputContent, KillProcessActionOutputContent, - KillProcessRequestBody, LogsEndpointAction, LogsEndpointActionResponse, ResponseActionGetFileOutputContent, @@ -81,6 +80,8 @@ import type { GetProcessesRequestBody, IsolationRouteRequestBody, ResponseActionGetFileRequestBody, + KillProcessRequestBody, + UnisolationRouteRequestBody, } from '../../../../../../common/api/endpoint'; import type { ResponseActionsClientOptions, @@ -361,7 +362,7 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { } async release( - actionRequest: IsolationRouteRequestBody, + actionRequest: UnisolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise<ActionDetails> { const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< diff --git a/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts index d45e59b2fe295..558f7e7ade2f6 100644 --- a/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts +++ b/x-pack/plugins/security_solution/server/integration_tests/telemetry.test.ts @@ -148,8 +148,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/187719 - describe.skip('detection-rules', () => { + describe('detection-rules', () => { it('should execute when scheduled', async () => { await mockAndScheduleDetectionRulesTask(); @@ -169,8 +168,7 @@ describe('telemetry tasks', () => { }); it('should send task metrics', async () => { - const task = await mockAndScheduleDetectionRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleDetectionRulesTask(); const requests = await getTaskMetricsRequests(task, started); @@ -181,13 +179,10 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/178918 - // FLAKY: https://github.com/elastic/kibana/issues/187720 - describe.skip('sender configuration', () => { + describe('sender configuration', () => { it('should use legacy sender by default', async () => { // launch a random task and verify it uses the new configuration - const task = await mockAndScheduleDetectionRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleDetectionRulesTask(); const requests = await getTaskMetricsRequests(task, started); expect(requests.length).toBeGreaterThan(0); @@ -216,8 +211,7 @@ describe('telemetry tasks', () => { expect(found).toBeFalsy(); }); - const task = await mockAndScheduleDetectionRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleDetectionRulesTask(); const requests = await getTaskMetricsRequests(task, started); expect(requests.length).toBeGreaterThan(0); @@ -258,8 +252,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/189192 - describe.skip('endpoint-diagnostics', () => { + describe('endpoint-diagnostics', () => { it('should execute when scheduled', async () => { await mockAndScheduleEndpointDiagnosticsTask(); @@ -298,8 +291,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/189330 - describe.skip('endpoint-meta-telemetry', () => { + describe('endpoint-meta-telemetry', () => { beforeEach(async () => { await initEndpointIndices(esClient); }); @@ -335,8 +327,7 @@ describe('telemetry tasks', () => { Promise.reject(Error(errorMessage)) ); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const requests = await getTaskMetricsRequests(task, started); @@ -361,8 +352,7 @@ describe('telemetry tasks', () => { agentClient.listAgents = jest.fn((_) => Promise.reject(Error(errorMessage))); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -401,8 +391,7 @@ describe('telemetry tasks', () => { }) ); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -434,8 +423,7 @@ describe('telemetry tasks', () => { telemetryReceiver.fetchPolicyConfigs = jest.fn((_) => Promise.reject(Error(errorMessage))); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -478,8 +466,7 @@ describe('telemetry tasks', () => { } as unknown as AgentPolicy); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -512,8 +499,7 @@ describe('telemetry tasks', () => { return Promise.reject(Error(errorMessage)); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -545,8 +531,7 @@ describe('telemetry tasks', () => { return Promise.resolve(new Map()); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -579,8 +564,7 @@ describe('telemetry tasks', () => { return Promise.reject(Error(errorMessage)); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -615,8 +599,7 @@ describe('telemetry tasks', () => { return Promise.resolve(new Map()); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -663,8 +646,7 @@ describe('telemetry tasks', () => { return esClient.search(query); }); - const task = await mockAndScheduleEndpointTask(); - const started = performance.now(); + const [task, started] = await mockAndScheduleEndpointTask(); const endpointMetaRequests = await getEndpointMetaRequests(); @@ -688,8 +670,7 @@ describe('telemetry tasks', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/188234 - describe.skip('telemetry-prebuilt-rule-alerts', () => { + describe('telemetry-prebuilt-rule-alerts', () => { it('should execute when scheduled', async () => { await mockAndSchedulePrebuiltRulesTask(); @@ -722,8 +703,7 @@ describe('telemetry tasks', () => { telemetryReceiver.fetchPrebuiltRuleAlertsBatch = mockedGenerator; - const task = await mockAndSchedulePrebuiltRulesTask(); - const started = performance.now(); + const [task, started] = await mockAndSchedulePrebuiltRulesTask(); const requests = await getTaskMetricsRequests(task, started); @@ -781,7 +761,7 @@ describe('telemetry tasks', () => { }); } - async function mockAndScheduleDetectionRulesTask(): Promise<SecurityTelemetryTask> { + async function mockAndScheduleDetectionRulesTask(): Promise<[SecurityTelemetryTask, number]> { const task = getTelemetryTask(tasks, 'security:telemetry-detection-rules'); // create some data @@ -797,50 +777,52 @@ describe('telemetry tasks', () => { exceptionsListItem.push(exceptionListItem); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } - async function mockAndScheduleEndpointTask(): Promise<SecurityTelemetryTask> { + async function mockAndScheduleEndpointTask(): Promise<[SecurityTelemetryTask, number]> { const task = getTelemetryTask(tasks, 'security:endpoint-meta-telemetry'); await mockEndpointData(esClient, kibanaServer.coreStart.savedObjects); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } - async function mockAndSchedulePrebuiltRulesTask(): Promise<SecurityTelemetryTask> { + async function mockAndSchedulePrebuiltRulesTask(): Promise<[SecurityTelemetryTask, number]> { const task = getTelemetryTask(tasks, 'security:telemetry-prebuilt-rule-alerts'); await mockPrebuiltRulesData(esClient); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } - async function mockAndScheduleEndpointDiagnosticsTask(): Promise<SecurityTelemetryTask> { + async function mockAndScheduleEndpointDiagnosticsTask(): Promise< + [SecurityTelemetryTask, number] + > { const task = getTelemetryTask(tasks, 'security:endpoint-diagnostics'); await createMockedEndpointAlert(kibanaServer.coreStart.elasticsearch.client.asInternalUser); // schedule task to run ASAP - await eventually(async () => { + return eventually(async () => { + const started = performance.now(); await taskManagerPlugin.runSoon(task.getTaskId()); + return [task, started]; }); - - return task; } function mockAxiosGet(bufferConfig: unknown = fakeBufferAndSizesConfigAsyncDisabled) { @@ -877,6 +859,7 @@ describe('telemetry tasks', () => { requestConfig: AxiosRequestConfig<unknown> | undefined; }> > { + const taskType = getTelemetryTaskType(task); return eventually(async () => { const calls = mockedAxiosPost.mock.calls.flatMap(([url, data, config]) => { return (data as string).split('\n').map((body) => { @@ -886,20 +869,24 @@ describe('telemetry tasks', () => { const requests = calls.filter(({ url, body }) => { return ( - body.indexOf(getTelemetryTaskType(task)) !== -1 && + body.indexOf(taskType) !== -1 && url.startsWith(ENDPOINT_STAGING) && url.endsWith('task-metrics') ); }); expect(requests.length).toBeGreaterThan(0); - return requests + const filtered = requests .map((r) => { return { taskMetric: JSON.parse(r.body) as TaskMetric, requestConfig: r.config, }; }) - .filter((t) => t.taskMetric.start_time >= olderThan); + .filter((t) => { + return t.taskMetric.start_time >= olderThan; + }); + expect(filtered.length).toBeGreaterThan(0); + return filtered; }); } }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.ts new file mode 100644 index 0000000000000..c2982d4b16092 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.test.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. + */ + +import { bootstrapPrebuiltRulesRoute } from './bootstrap_prebuilt_rules'; + +import type { Installation, RegistryPackage } from '@kbn/fleet-plugin/common'; +import { requestContextMock, serverMock } from '../../../routes/__mocks__'; +import { getBootstrapRulesRequest } from '../../../routes/__mocks__/request_responses'; + +const packageMock: RegistryPackage = { + name: 'detection_engine', + version: '1.0.0', + format_version: '1.0.0', + title: 'Test package', + description: 'Test package', + owner: { github: 'elastic' }, + download: '', + path: '', +}; + +const installationMock: Installation = { + name: 'detection_engine', + version: '1.0.0', + installed_kibana: [], + installed_es: [], + es_index_patterns: {}, + install_status: 'installed', + verification_status: 'verified', + install_version: '1.0.0', + install_source: 'registry', + install_started_at: '2021-08-02T15:00:00.000Z', +}; + +describe('bootstrap_prebuilt_rules_route', () => { + let server: ReturnType<typeof serverMock.create>; + let { clients, context } = requestContextMock.createTools(); + + beforeEach(() => { + jest.clearAllMocks(); + server = serverMock.create(); + ({ clients, context } = requestContextMock.createTools()); + + bootstrapPrebuiltRulesRoute(server.router); + }); + + it('returns information about installed packages', async () => { + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + packages: expect.arrayContaining([ + expect.objectContaining({ + name: 'detection_engine', + version: '1.0.0', + status: 'installed', + }), + ]), + }); + }); + + it('installs pre-release packages in dev mode', async () => { + // Mock the package installation + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + + // Mock Kibana build and branch + clients.appClient.getBuildFlavor.mockReturnValue('traditional'); + clients.appClient.getKibanaBranch.mockReturnValue('main'); + + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(clients.internalFleetServices.packages.fetchFindLatestPackage).toHaveBeenCalledWith( + 'security_detection_engine', + { prerelease: true } + ); + }); + + it('installs pre-release packages for release candidates', async () => { + // Mock the package installation + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + + // Mock Kibana build and branch + clients.appClient.getBuildFlavor.mockReturnValue('traditional'); + clients.appClient.getKibanaBranch.mockReturnValue('8.16.0'); + clients.appClient.getKibanaVersion.mockReturnValue('8.16.0-SNAPSHOT'); + + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(clients.internalFleetServices.packages.fetchFindLatestPackage).toHaveBeenCalledWith( + 'security_detection_engine', + { prerelease: true } + ); + }); + + it('installs release packages for Serverless', async () => { + // Mock the package installation + clients.internalFleetServices.packages.fetchFindLatestPackage.mockResolvedValue(packageMock); + clients.internalFleetServices.packages.ensureInstalledPackage.mockResolvedValue({ + status: 'installed', + package: installationMock, + }); + + // Mock Kibana build and branch + clients.appClient.getBuildFlavor.mockReturnValue('serverless'); + clients.appClient.getKibanaBranch.mockReturnValue('main'); + + const response = await server.inject( + getBootstrapRulesRequest(), + requestContextMock.convertContext(context) + ); + expect(response.status).toEqual(200); + expect(clients.internalFleetServices.packages.fetchFindLatestPackage).toHaveBeenCalledWith( + 'security_detection_engine', + { prerelease: false } + ); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts new file mode 100644 index 0000000000000..d17435a543320 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.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 { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core/server'; +import { BOOTSTRAP_PREBUILT_RULES_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import type { BootstrapPrebuiltRulesResponse } from '../../../../../../common/api/detection_engine/prebuilt_rules/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.gen'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { buildSiemResponse } from '../../../routes/utils'; +import { + installEndpointPackage, + installPrebuiltRulesPackage, +} from '../install_prebuilt_rules_and_timelines/install_prebuilt_rules_package'; + +export const bootstrapPrebuiltRulesRoute = (router: SecuritySolutionPluginRouter) => { + router.versioned + .post({ + access: 'internal', + path: BOOTSTRAP_PREBUILT_RULES_URL, + options: { + tags: ['access:securitySolution'], + }, + }) + .addVersion( + { + version: '1', + validate: {}, + }, + async (context, _, response): Promise<IKibanaResponse<BootstrapPrebuiltRulesResponse>> => { + const siemResponse = buildSiemResponse(response); + + try { + const ctx = await context.resolve(['securitySolution']); + const securityContext = ctx.securitySolution; + const config = securityContext.getConfig(); + + const results = await Promise.all([ + installPrebuiltRulesPackage(config, securityContext), + installEndpointPackage(config, securityContext), + ]); + + const responseBody: BootstrapPrebuiltRulesResponse = { + packages: results.map((result) => ({ + name: result.package.name, + version: result.package.version, + status: result.status, + })), + }; + + return response.ok({ + body: responseBody, + }); + } 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/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts index 84cb18032f50e..6b88ec1923d0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts @@ -12,7 +12,7 @@ import { buildSiemResponse } from '../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { - GetPrebuiltRulesAndTimelinesStatusResponse, + ReadPrebuiltRulesAndTimelinesStatusResponse, PREBUILT_RULES_STATUS_URL, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; @@ -74,7 +74,7 @@ export const getPrebuiltRulesAndTimelinesStatusRoute = (router: SecuritySolution checkTimelineStatusRt ); - const responseBody: GetPrebuiltRulesAndTimelinesStatusResponse = { + const responseBody: ReadPrebuiltRulesAndTimelinesStatusResponse = { rules_custom_installed: customRules.total, rules_installed: installedPrebuiltRules.size, rules_not_installed: rulesToInstall.length, @@ -86,7 +86,7 @@ export const getPrebuiltRulesAndTimelinesStatusRoute = (router: SecuritySolution }; return response.ok({ - body: GetPrebuiltRulesAndTimelinesStatusResponse.parse(responseBody), + body: ReadPrebuiltRulesAndTimelinesStatusResponse.parse(responseBody), }); } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts index 4bbc4c3561674..23bcc654eb780 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_package.ts @@ -6,7 +6,10 @@ */ import type { SecuritySolutionApiRequestHandlerContext } from '../../../../../types'; -import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../../common/detection_engine/constants'; +import { + ENDPOINT_PACKAGE_NAME, + PREBUILT_RULES_PACKAGE_NAME, +} from '../../../../../../common/detection_engine/constants'; import type { ConfigType } from '../../../../../config'; /** @@ -19,24 +22,45 @@ export async function installPrebuiltRulesPackage( config: ConfigType, context: SecuritySolutionApiRequestHandlerContext ) { - // Get package version from the config + const pkgVersion = await findLatestPackageVersion(config, context, PREBUILT_RULES_PACKAGE_NAME); + + return context + .getInternalFleetServices() + .packages.ensureInstalledPackage({ pkgName: PREBUILT_RULES_PACKAGE_NAME, pkgVersion }); +} + +export async function installEndpointPackage( + config: ConfigType, + context: SecuritySolutionApiRequestHandlerContext +) { + const pkgVersion = await findLatestPackageVersion(config, context, ENDPOINT_PACKAGE_NAME); + + return context.getInternalFleetServices().packages.ensureInstalledPackage({ + pkgName: ENDPOINT_PACKAGE_NAME, + pkgVersion, + }); +} + +async function findLatestPackageVersion( + config: ConfigType, + context: SecuritySolutionApiRequestHandlerContext, + packageName: string +) { let pkgVersion = config.prebuiltRulesPackageVersion; // Find latest package if the version isn't specified in the config if (!pkgVersion) { + const securityAppClient = context.getAppClient(); // Use prerelease versions in dev environment const isPrerelease = - context.getAppClient().getKibanaVersion().includes('-SNAPSHOT') || - context.getAppClient().getKibanaBranch() === 'main'; + securityAppClient.getBuildFlavor() === 'traditional' && + (securityAppClient.getKibanaVersion().includes('-SNAPSHOT') || + securityAppClient.getKibanaBranch() === 'main'); const result = await context .getInternalFleetServices() - .packages.fetchFindLatestPackage(PREBUILT_RULES_PACKAGE_NAME, { prerelease: isPrerelease }); + .packages.fetchFindLatestPackage(packageName, { prerelease: isPrerelease }); pkgVersion = result.version; } - - // Install the package - await context - .getInternalFleetServices() - .packages.ensureInstalledPackage({ pkgName: PREBUILT_RULES_PACKAGE_NAME, pkgVersion }); + return pkgVersion; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts index 0d1693a69806e..f95189d6af34d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -6,11 +6,12 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { PERFORM_RULE_UPGRADE_URL, - SkipRuleUpgradeReason, PerformRuleUpgradeRequestBody, - PickVersionValues, + PickVersionValuesEnum, + SkipRuleUpgradeReasonEnum, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import type { PerformRuleUpgradeResponseBody, @@ -18,7 +19,6 @@ import type { } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { assertUnreachable } from '../../../../../../common/utility_types'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { buildRouteValidation } from '../../../../../utils/build_validation/route_validation'; import type { PromisePoolError } from '../../../../../utils/promise_pool'; import { buildSiemResponse } from '../../../routes/utils'; import { aggregatePrebuiltRuleErrors } from '../../logic/aggregate_prebuilt_rule_errors'; @@ -48,7 +48,7 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => version: '1', validate: { request: { - body: buildRouteValidation(PerformRuleUpgradeRequestBody), + body: buildRouteValidationWithZod(PerformRuleUpgradeRequestBody), }, }, }, @@ -63,7 +63,8 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); - const { mode, pick_version: globalPickVersion = PickVersionValues.TARGET } = request.body; + const { mode, pick_version: globalPickVersion = PickVersionValuesEnum.TARGET } = + request.body; const fetchErrors: Array<PromisePoolError<{ rule_id: string }>> = []; const targetRules: PrebuiltRuleAsset[] = []; @@ -105,7 +106,7 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => if (!upgradeableRuleIds.has(rule.rule_id)) { skippedRules.push({ rule_id: rule.rule_id, - reason: SkipRuleUpgradeReason.RULE_UP_TO_DATE, + reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, }); return; } @@ -132,7 +133,7 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => const rulePickVersion = versionSpecifiersMap?.get(current.rule_id)?.pick_version ?? globalPickVersion; switch (rulePickVersion) { - case PickVersionValues.BASE: + case PickVersionValuesEnum.BASE: const baseVersion = ruleVersionsMap.get(current.rule_id)?.base; if (baseVersion) { targetRules.push({ ...baseVersion, version: target.version }); @@ -143,10 +144,14 @@ export const performRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => }); } break; - case PickVersionValues.CURRENT: + case PickVersionValuesEnum.CURRENT: targetRules.push({ ...current, version: target.version }); break; - case PickVersionValues.TARGET: + case PickVersionValuesEnum.TARGET: + targetRules.push(target); + break; + case PickVersionValuesEnum.MERGED: + // TODO: Implement functionality to handle MERGED targetRules.push(target); break; default: diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts index 71740086e3fa5..c9871f86a43e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts @@ -14,6 +14,7 @@ import { reviewRuleInstallationRoute } from './review_rule_installation/review_r import { reviewRuleUpgradeRoute } from './review_rule_upgrade/review_rule_upgrade_route'; import { performRuleInstallationRoute } from './perform_rule_installation/perform_rule_installation_route'; import { performRuleUpgradeRoute } from './perform_rule_upgrade/perform_rule_upgrade_route'; +import { bootstrapPrebuiltRulesRoute } from './bootstrap_prebuilt_rules/bootstrap_prebuilt_rules'; export const registerPrebuiltRulesRoutes = (router: SecuritySolutionPluginRouter) => { // Legacy endpoints that we're going to deprecate @@ -26,4 +27,5 @@ export const registerPrebuiltRulesRoutes = (router: SecuritySolutionPluginRouter performRuleUpgradeRoute(router); reviewRuleInstallationRoute(router); reviewRuleUpgradeRoute(router); + bootstrapPrebuiltRulesRoute(router); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts new file mode 100644 index 0000000000000..6020738bd5e66 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts @@ -0,0 +1,501 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + RuleDataSource, + ThreeVersionsOf, +} from '../../../../../../../../common/api/detection_engine'; +import { + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, + MissingVersion, + DataSourceType, + ThreeWayDiffConflict, +} from '../../../../../../../../common/api/detection_engine'; +import { dataSourceDiffAlgorithm } from './data_source_diff_algorithm'; + +describe('dataSourceDiffAlgorithm', () => { + describe('returns current_version as merged output if there is no update - scenario AAA', () => { + it('if all versions are index patterns', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'two', 'three'], + }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'two'], + }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + + it('if all versions are data views', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: { type: DataSourceType.data_view, data_view_id: '123' }, + target_version: { type: DataSourceType.data_view, data_view_id: '123' }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.StockValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + }); + + describe('returns current_version as merged output if current_version is different and there is no update - scenario ABA', () => { + it('if current version is different data type than base and target', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + current_version: { type: DataSourceType.data_view, data_view_id: '123' }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + + it('if all versions are same data type', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + + it('if current version is undefined', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + current_version: undefined, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + }); + + describe('returns target_version as merged output if current_version is the same and there is an update - scenario AAB', () => { + it('if target version is different data type than base and current', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: { type: DataSourceType.data_view, data_view_id: '123' }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + + it('if all versions are same data type', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: { type: DataSourceType.data_view, data_view_id: '123' }, + target_version: { type: DataSourceType.data_view, data_view_id: '456' }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + }); + + describe('returns current_version as merged output if current version is different but it matches the update - scenario ABB', () => { + it('if all versions are index patterns', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + + it('if all versions are data views', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: { type: DataSourceType.data_view, data_view_id: '456' }, + target_version: { type: DataSourceType.data_view, data_view_id: '456' }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + }); + + describe('returns current_version as merged output if all three versions are different - scenario ABC', () => { + it('if all versions are index patterns', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'five'], + }, + }; + + const expectedMergedVersion: RuleDataSource = { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'four', 'five'], + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + }) + ); + }); + + it('if all versions are data views', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: { type: DataSourceType.data_view, data_view_id: '456' }, + target_version: { type: DataSourceType.data_view, data_view_id: '789' }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }) + ); + }); + + it('if base version is a data view and others are index patterns ', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'five'], + }, + }; + + const expectedMergedVersion: RuleDataSource = { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four', 'two', 'five'], + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + }) + ); + }); + + it('if base version is a index patterns and other are data views', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + current_version: { type: DataSourceType.data_view, data_view_id: '123' }, + target_version: { type: DataSourceType.data_view, data_view_id: '456' }, + }; + + const expectedMergedVersion: RuleDataSource = { + type: DataSourceType.data_view, + data_view_id: '123', + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: expectedMergedVersion, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }) + ); + }); + + it('if currrent version is a different data type', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + target_version: { type: DataSourceType.data_view, data_view_id: '789' }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }) + ); + }); + + it('if target version is a different data type', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'two', 'three'], + }, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + target_version: { type: DataSourceType.data_view, data_view_id: '789' }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }) + ); + }); + + it('if currrent version is undefined', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: { type: DataSourceType.data_view, data_view_id: '123' }, + current_version: undefined, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + }) + ); + }); + }); + + describe('if base_version is missing', () => { + it('returns current_version as merged output if current_version and target_version are the same - scenario -AA', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: MissingVersion, + current_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + has_base_version: false, + base_version: undefined, + merged_version: mockVersions.current_version, + diff_outcome: ThreeWayDiffOutcome.MissingBaseNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + }) + ); + }); + + describe('returns target_version as merged output if current_version and target_version are different - scenario -AB', () => { + it('if versions are different types', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: MissingVersion, + current_version: { type: DataSourceType.data_view, data_view_id: '456' }, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + has_base_version: false, + base_version: undefined, + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + }) + ); + }); + + it('if current version is undefined', () => { + const mockVersions: ThreeVersionsOf<RuleDataSource | undefined> = { + base_version: MissingVersion, + current_version: undefined, + target_version: { + type: DataSourceType.index_patterns, + index_patterns: ['one', 'three', 'four'], + }, + }; + + const result = dataSourceDiffAlgorithm(mockVersions); + + expect(result).toEqual( + expect.objectContaining({ + has_base_version: false, + base_version: undefined, + merged_version: mockVersions.target_version, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + }) + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts new file mode 100644 index 0000000000000..86aa886592468 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.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 { assertUnreachable } from '../../../../../../../../common/utility_types'; +import type { + RuleDataSource, + ThreeVersionsOf, + ThreeWayDiff, +} from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import { + determineIfValueCanUpdate, + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, + MissingVersion, + DataSourceType, + ThreeWayDiffConflict, + determineDiffOutcomeForDataSource, + isIndexPatternDataSourceType, +} from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import { getDedupedDataSourceVersion, mergeDedupedArrays } from './helpers'; + +/** + * Takes a type of `RuleDataSource | undefined` because the data source can be index patterns, a data view id, or neither in some cases + */ +export const dataSourceDiffAlgorithm = ( + versions: ThreeVersionsOf<RuleDataSource | undefined> +): ThreeWayDiff<RuleDataSource | undefined> => { + const { + base_version: baseVersion, + current_version: currentVersion, + target_version: targetVersion, + } = versions; + + const diffOutcome = determineDiffOutcomeForDataSource(baseVersion, currentVersion, targetVersion); + + const valueCanUpdate = determineIfValueCanUpdate(diffOutcome); + + const hasBaseVersion = baseVersion !== MissingVersion; + + const { mergeOutcome, conflict, mergedVersion } = mergeVersions({ + baseVersion: hasBaseVersion ? baseVersion : undefined, + currentVersion, + targetVersion, + diffOutcome, + }); + + return { + has_base_version: hasBaseVersion, + base_version: hasBaseVersion ? baseVersion : undefined, + current_version: currentVersion, + target_version: targetVersion, + merged_version: mergedVersion, + merge_outcome: mergeOutcome, + + diff_outcome: diffOutcome, + conflict, + has_update: valueCanUpdate, + }; +}; + +interface MergeResult { + mergeOutcome: ThreeWayMergeOutcome; + mergedVersion: RuleDataSource | undefined; + conflict: ThreeWayDiffConflict; +} + +interface MergeArgs { + baseVersion: RuleDataSource | undefined; + currentVersion: RuleDataSource | undefined; + targetVersion: RuleDataSource | undefined; + diffOutcome: ThreeWayDiffOutcome; +} + +const mergeVersions = ({ + baseVersion, + currentVersion, + targetVersion, + diffOutcome, +}: MergeArgs): MergeResult => { + const dedupedBaseVersion = baseVersion ? getDedupedDataSourceVersion(baseVersion) : baseVersion; + const dedupedCurrentVersion = currentVersion + ? getDedupedDataSourceVersion(currentVersion) + : currentVersion; + const dedupedTargetVersion = targetVersion + ? getDedupedDataSourceVersion(targetVersion) + : targetVersion; + + switch (diffOutcome) { + // Scenario -AA is treated as scenario AAA: + // https://github.com/elastic/kibana/pull/184889#discussion_r1636421293 + case ThreeWayDiffOutcome.MissingBaseNoUpdate: + case ThreeWayDiffOutcome.StockValueNoUpdate: + case ThreeWayDiffOutcome.CustomizedValueNoUpdate: + case ThreeWayDiffOutcome.CustomizedValueSameUpdate: + return { + conflict: ThreeWayDiffConflict.NONE, + mergeOutcome: ThreeWayMergeOutcome.Current, + mergedVersion: dedupedCurrentVersion, + }; + + case ThreeWayDiffOutcome.StockValueCanUpdate: + return { + conflict: ThreeWayDiffConflict.NONE, + mergeOutcome: ThreeWayMergeOutcome.Target, + mergedVersion: dedupedTargetVersion, + }; + + case ThreeWayDiffOutcome.CustomizedValueCanUpdate: { + if ( + isIndexPatternDataSourceType(dedupedCurrentVersion) && + isIndexPatternDataSourceType(dedupedTargetVersion) + ) { + const baseVersionToMerge = + dedupedBaseVersion && dedupedBaseVersion.type === DataSourceType.index_patterns + ? dedupedBaseVersion.index_patterns + : []; + + return { + conflict: ThreeWayDiffConflict.SOLVABLE, + mergeOutcome: ThreeWayMergeOutcome.Merged, + mergedVersion: { + type: DataSourceType.index_patterns, + index_patterns: mergeDedupedArrays( + baseVersionToMerge, + dedupedCurrentVersion.index_patterns, + dedupedTargetVersion.index_patterns + ), + }, + }; + } + + return { + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + mergeOutcome: ThreeWayMergeOutcome.Current, + mergedVersion: dedupedCurrentVersion, + }; + } + + // Scenario -AB is treated as scenario ABC, but marked as + // SOLVABLE, and returns the target version as the merged version + // https://github.com/elastic/kibana/pull/184889#discussion_r1636421293 + case ThreeWayDiffOutcome.MissingBaseCanUpdate: { + return { + mergedVersion: targetVersion, + mergeOutcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + }; + } + default: + return assertUnreachable(diffOutcome); + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/helpers.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/helpers.ts new file mode 100644 index 0000000000000..498b857eb0428 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/helpers.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 { difference, union, uniq } from 'lodash'; +import type { RuleDataSource } from '../../../../../../../../common/api/detection_engine'; +import { DataSourceType } from '../../../../../../../../common/api/detection_engine'; + +export const mergeDedupedArrays = <T>( + dedupedBaseVersion: T[], + dedupedCurrentVersion: T[], + dedupedTargetVersion: T[] +) => { + const addedCurrent = difference(dedupedCurrentVersion, dedupedBaseVersion); + const removedCurrent = difference(dedupedBaseVersion, dedupedCurrentVersion); + + const addedTarget = difference(dedupedTargetVersion, dedupedBaseVersion); + const removedTarget = difference(dedupedBaseVersion, dedupedTargetVersion); + + const bothAdded = union(addedCurrent, addedTarget); + const bothRemoved = union(removedCurrent, removedTarget); + + return difference(union(dedupedBaseVersion, bothAdded), bothRemoved); +}; + +export const getDedupedDataSourceVersion = (version: RuleDataSource): RuleDataSource => { + if (version.type === DataSourceType.index_patterns) { + return { + ...version, + index_patterns: uniq(version.index_patterns), + }; + } + return version; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts index b7c0a1143f1a7..fc895543e66b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts @@ -10,3 +10,4 @@ export { singleLineStringDiffAlgorithm } from './single_line_string_diff_algorit export { scalarArrayDiffAlgorithm } from './scalar_array_diff_algorithm'; export { simpleDiffAlgorithm } from './simple_diff_algorithm'; export { multiLineStringDiffAlgorithm } from './multi_line_string_diff_algorithm'; +export { dataSourceDiffAlgorithm } from './data_source_diff_algorithm'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts index e990ad9aa7c33..215e92377a596 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { difference, union, uniq } from 'lodash'; +import { uniq } from 'lodash'; import { assertUnreachable } from '../../../../../../../../common/utility_types'; import type { ThreeVersionsOf, @@ -19,6 +19,7 @@ import { ThreeWayDiffConflict, ThreeWayMergeOutcome, } from '../../../../../../../../common/api/detection_engine/prebuilt_rules'; +import { mergeDedupedArrays } from './helpers'; /** * Diff algorithm used for arrays of scalar values (eg. numbers, strings, booleans, etc.) @@ -105,16 +106,11 @@ const mergeVersions = <TValue>({ } case ThreeWayDiffOutcome.CustomizedValueCanUpdate: { - const addedCurrent = difference(dedupedCurrentVersion, dedupedBaseVersion); - const removedCurrent = difference(dedupedBaseVersion, dedupedCurrentVersion); - - const addedTarget = difference(dedupedTargetVersion, dedupedBaseVersion); - const removedTarget = difference(dedupedBaseVersion, dedupedTargetVersion); - - const bothAdded = union(addedCurrent, addedTarget); - const bothRemoved = union(removedCurrent, removedTarget); - - const merged = difference(union(dedupedBaseVersion, bothAdded), bothRemoved); + const merged = mergeDedupedArrays( + dedupedBaseVersion, + dedupedCurrentVersion, + dedupedTargetVersion + ); return { conflict: ThreeWayDiffConflict.SOLVABLE, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index 0a260bb990d0f..0aeab9cf8ccbf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -38,6 +38,7 @@ import type { ThreeVersionsOf } from '../../../../../../../common/api/detection_ import { MissingVersion } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; import { calculateFieldsDiffFor } from './diff_calculation_helpers'; import { + dataSourceDiffAlgorithm, multiLineStringDiffAlgorithm, numberDiffAlgorithm, scalarArrayDiffAlgorithm, @@ -209,7 +210,7 @@ const calculateCustomQueryFieldsDiff = ( const customQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableCustomQueryFields> = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, }; const calculateSavedQueryFieldsDiff = ( @@ -221,7 +222,7 @@ const calculateSavedQueryFieldsDiff = ( const savedQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableSavedQueryFields> = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, }; const calculateEqlFieldsDiff = ( @@ -233,7 +234,7 @@ const calculateEqlFieldsDiff = ( const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableEqlFields> = { type: simpleDiffAlgorithm, eql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, event_category_override: singleLineStringDiffAlgorithm, timestamp_field: singleLineStringDiffAlgorithm, tiebreaker_field: singleLineStringDiffAlgorithm, @@ -259,7 +260,7 @@ const calculateThreatMatchFieldsDiff = ( const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableThreatMatchFields> = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, threat_query: simpleDiffAlgorithm, threat_index: scalarArrayDiffAlgorithm, threat_mapping: simpleDiffAlgorithm, @@ -277,7 +278,7 @@ const calculateThresholdFieldsDiff = ( const thresholdFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableThresholdFields> = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, threshold: simpleDiffAlgorithm, }; @@ -303,7 +304,7 @@ const calculateNewTermsFieldsDiff = ( const newTermsFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor<DiffableNewTermsFields> = { type: simpleDiffAlgorithm, kql_query: simpleDiffAlgorithm, - data_source: simpleDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, new_terms_fields: scalarArrayDiffAlgorithm, history_window_start: singleLineStringDiffAlgorithm, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts index fa9fcc1fa5e22..b39cba0cf4952 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -39,6 +39,8 @@ import { riskScoreDataClientMock } from '../../../entity_analytics/risk_score/ri import { assetCriticalityDataClientMock } from '../../../entity_analytics/asset_criticality/asset_criticality_data_client.mock'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { detectionRulesClientMock } from '../../rule_management/logic/detection_rules_client/__mocks__/detection_rules_client'; +import { packageServiceMock } from '@kbn/fleet-plugin/server/services/epm/package_service.mock'; +import type { EndpointInternalFleetServicesInterface } from '../../../../endpoint/services/fleet'; export const createMockClients = () => { const core = coreMock.createRequestHandlerContext(); @@ -70,6 +72,10 @@ export const createMockClients = () => { riskEngineDataClient: riskEngineDataClientMock.create(), riskScoreDataClient: riskScoreDataClientMock.create(), assetCriticalityDataClient: assetCriticalityDataClientMock.create(), + + internalFleetServices: { + packages: packageServiceMock.createClient(), + }, }; }; @@ -146,10 +152,9 @@ const createSecuritySolutionRequestContextMock = ( getDetectionEngineHealthClient: jest.fn(() => clients.detectionEngineHealthClient), getRuleExecutionLog: jest.fn(() => clients.ruleExecutionLog), getExceptionListClient: jest.fn(() => clients.lists.exceptionListClient), - getInternalFleetServices: jest.fn(() => { - // TODO: Mock EndpointInternalFleetServicesInterface and return the mocked object. - throw new Error('Not implemented'); - }), + getInternalFleetServices: jest.fn( + () => clients.internalFleetServices as unknown as EndpointInternalFleetServicesInterface + ), getRiskEngineDataClient: jest.fn(() => clients.riskEngineDataClient), getRiskScoreDataClient: jest.fn(() => clients.riskScoreDataClient), getAssetCriticalityDataClient: jest.fn(() => clients.assetCriticalityDataClient), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 2bd7efdbbdcf2..3a48bb449c55d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -33,6 +33,7 @@ import { import { RULE_MANAGEMENT_FILTERS_URL } from '../../../../../common/api/detection_engine/rule_management/urls'; import { + BOOTSTRAP_PREBUILT_RULES_URL, PREBUILT_RULES_STATUS_URL, PREBUILT_RULES_URL, } from '../../../../../common/api/detection_engine/prebuilt_rules'; @@ -203,6 +204,12 @@ export const getRuleManagementFiltersRequest = () => path: RULE_MANAGEMENT_FILTERS_URL, }); +export const getBootstrapRulesRequest = () => + requestMock.create({ + method: 'post', + path: BOOTSTRAP_PREBUILT_RULES_URL, + }); + export interface FindHit<T = RuleAlertType> { page: number; perPage: number; 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 1f7b62ee5209f..bd13fe09b687b 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 @@ -8,7 +8,7 @@ import { transformError, getBootstrapIndexExists } from '@kbn/securitysolution-es-utils'; import type { RuleDataPluginService } from '@kbn/rule-registry-plugin/server'; import type { IKibanaResponse } from '@kbn/core/server'; -import type { GetAlertsIndexResponse } from '../../../../../common/api/detection_engine/index_management'; +import type { ReadAlertsIndexResponse } from '../../../../../common/api/detection_engine/index_management'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_INDEX_URL } from '../../../../../common/constants'; @@ -35,7 +35,7 @@ export const readIndexRoute = ( version: '2023-10-31', validate: false, }, - async (context, _, response): Promise<IKibanaResponse<GetAlertsIndexResponse>> => { + async (context, _, response): Promise<IKibanaResponse<ReadAlertsIndexResponse>> => { const siemResponse = buildSiemResponse(response); try { 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 2ca4790d3c203..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 @@ -12,7 +12,7 @@ import type { IKibanaResponse } from '@kbn/core/server'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_PRIVILEGES_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; -import type { GetPrivilegesResponse } from '../../../../../common/api/detection_engine'; +import type { ReadPrivilegesResponse } from '../../../../../common/api/detection_engine'; export const readPrivilegesRoute = ( router: SecuritySolutionPluginRouter, @@ -31,7 +31,7 @@ export const readPrivilegesRoute = ( version: '2023-10-31', validate: false, }, - async (context, request, response): Promise<IKibanaResponse<GetPrivilegesResponse>> => { + async (context, request, response): Promise<IKibanaResponse<ReadPrivilegesResponse>> => { const siemResponse = buildSiemResponse(response); try { 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 418db17b566c5..ece4d3444be99 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 @@ -7,7 +7,7 @@ import { transformError, getIndexAliases } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { GetAlertsMigrationStatusRequestQuery } from '../../../../../common/api/detection_engine/signals_migration'; +import { ReadAlertsMigrationStatusRequestQuery } from '../../../../../common/api/detection_engine/signals_migration'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL } from '../../../../../common/constants'; import { getIndexVersionsByIndex } from '../../migrations/get_index_versions_by_index'; @@ -31,7 +31,7 @@ export const getSignalsMigrationStatusRoute = (router: SecuritySolutionPluginRou { version: '2023-10-31', validate: { - request: { query: buildRouteValidationWithZod(GetAlertsMigrationStatusRequestQuery) }, + request: { query: buildRouteValidationWithZod(ReadAlertsMigrationStatusRequestQuery) }, }, }, async (context, request, response) => { 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 c6997cc9a9bed..d285c381c4b54 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 @@ -8,7 +8,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { uniq } from 'lodash/fp'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { ManageAlertTagsRequestBody } from '../../../../../common/api/detection_engine/alert_tags'; +import { SetAlertTagsRequestBody } from '../../../../../common/api/detection_engine/alert_tags'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DEFAULT_ALERTS_INDEX, @@ -31,7 +31,7 @@ export const setAlertTagsRoute = (router: SecuritySolutionPluginRouter) => { version: '2023-10-31', validate: { request: { - body: buildRouteValidationWithZod(ManageAlertTagsRequestBody), + body: buildRouteValidationWithZod(SetAlertTagsRequestBody), }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts index 1402518103e64..7441aec8c8fa5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts @@ -172,10 +172,6 @@ describe('Create rule route', () => { }); }); describe('rule containing response actions', () => { - beforeEach(() => { - // @ts-expect-error We're writting to a read only property just for the purpose of the test - clients.config.experimentalFeatures.endpointResponseActionsEnabled = true; - }); const getResponseAction = (command: string = 'isolate', config?: object) => ({ action_type_id: '.endpoint', params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts index 80db9f68a853b..87f42a014c1d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts @@ -179,10 +179,6 @@ describe('Update rule route', () => { }); }); describe('rule containing response actions', () => { - beforeEach(() => { - // @ts-expect-error We're writting to a read only property just for the purpose of the test - clients.config.experimentalFeatures.endpointResponseActionsEnabled = true; - }); const getResponseAction = (command: string = 'isolate', config?: object) => ({ action_type_id: '.endpoint', params: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index dd77122ac4560..500db54acd867 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -64,9 +64,7 @@ export const validateResponseActionsPermissions = async ( ruleUpdate: RuleCreateProps | RuleUpdateProps, existingRule?: RuleAlertType | null ): Promise<void> => { - const { experimentalFeatures } = await securitySolution.getConfig(); - - if (!experimentalFeatures.endpointResponseActionsEnabled || !isQueryRule(ruleUpdate.type)) { + if (!isQueryRule(ruleUpdate.type)) { return; } 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 81685984439cd..e45f2babe94f7 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 @@ -49,6 +49,7 @@ import { TIMESTAMP_RUNTIME_FIELD } from './constants'; import { buildTimestampRuntimeMapping } from './utils/build_timestamp_runtime_mapping'; import { getFieldsForWildcard } from './utils/get_fields_for_wildcard'; import { alertsFieldMap, rulesFieldMap } from '../../../../common/field_maps'; +import { sendAlertSuppressionTelemetryEvent } from './utils/telemetry/send_alert_suppression_telemetry_event'; const aliasesFieldMap: FieldMap = {}; Object.entries(aadFieldConversion).forEach(([key, value]) => { @@ -80,6 +81,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = isPreview, experimentalFeatures, alerting, + analytics, }) => (type) => { const { alertIgnoreFields: ignoreFields, alertMergeStrategy: mergeStrategy } = config; @@ -448,6 +450,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = enrichmentTimes: result.enrichmentTimes.concat(runResult.enrichmentTimes), createdSignals, createdSignalsCount: createdSignals.length, + suppressedAlertsCount: runResult.suppressedAlertsCount, errors: result.errors.concat(runResult.errors), lastLookbackDate: runResult.lastLookBackDate, searchAfterTimes: result.searchAfterTimes.concat(runResult.searchAfterTimes), @@ -465,6 +468,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = enrichmentTimes: [], createdSignals: [], createdSignalsCount: 0, + suppressedAlertsCount: 0, errors: [], searchAfterTimes: [], state, @@ -549,6 +553,16 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = }); } + if (!isPreview && analytics) { + sendAlertSuppressionTelemetryEvent({ + analytics, + suppressedAlertsCount: result.suppressedAlertsCount ?? 0, + createdAlertsCount: result.createdSignalsCount, + ruleAttributes: rule, + ruleParams: params, + }); + } + return { state: result.state }; }); }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts index 5f19f8f2218f1..b129a7ef0c5bb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/esql.ts @@ -196,7 +196,6 @@ export const esqlExecutor = async ({ maxNumberOfAlertsMultiplier: 1, }); - addToSearchAfterReturn({ current: result, next: bulkCreateResult }); ruleExecutionLogger.debug( `Created ${bulkCreateResult.createdItemsCount} alerts. Suppressed ${bulkCreateResult.suppressedItemsCount} alerts` ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/group_and_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/group_and_bulk_create.ts index b1d897250ca4e..38018060e2252 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/group_and_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/group_and_bulk_create.ts @@ -32,6 +32,7 @@ import type { ITelemetryEventsSender } from '../../../../telemetry/sender'; import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../../common/detection_engine/constants'; import type { ExperimentalFeatures } from '../../../../../../common'; import { createEnrichEventsFunction } from '../../utils/enrichments'; +import { getNumberOfSuppressedAlerts } from '../../utils/get_number_of_suppressed_alerts'; export interface BucketHistory { key: Record<string, string | number | null>; @@ -274,6 +275,7 @@ export const groupAndBulkCreate = async ({ suppressionWindow, alertTimestampOverride: runOpts.alertTimestampOverride, experimentalFeatures, + ruleType: 'query', }); addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult }); runOpts.ruleExecutionLogger.debug(`created ${bulkCreateResult.createdItemsCount} signals`); @@ -286,7 +288,13 @@ export const groupAndBulkCreate = async ({ logger: runOpts.ruleExecutionLogger, }) ); - addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult }); + addToSearchAfterReturn({ + current: toReturn, + next: { + ...bulkCreateResult, + suppressedItemsCount: getNumberOfSuppressedAlerts(bulkCreateResult.createdItems, []), + }, + }); runOpts.ruleExecutionLogger.debug(`created ${bulkCreateResult.createdItemsCount} signals`); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index 8f7a50b195e4f..4069b7782e0e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -11,6 +11,7 @@ import type { Logger } from '@kbn/logging'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; import type { QUERY_RULE_TYPE_ID, SAVED_QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; @@ -72,6 +73,7 @@ export interface SecurityAlertTypeReturnValue<TState extends RuleTypeState> { success: boolean; warning: boolean; warningMessages: string[]; + suppressedAlertsCount?: number; } export interface RunOpts<TParams extends RuleParams> { @@ -139,6 +141,7 @@ export interface CreateSecurityRuleTypeWrapperProps { isPreview?: boolean; experimentalFeatures?: ExperimentalFeatures; alerting: SetupPlugins['alerting']; + analytics?: AnalyticsServiceSetup; } export type CreateSecurityRuleTypeWrapper = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts index 75aa46d039277..7decfd8294913 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts @@ -7,12 +7,14 @@ import { performance } from 'perf_hooks'; import { isEmpty } from 'lodash'; - +import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types'; import type { SuppressedAlertService } from '@kbn/rule-registry-plugin/server'; import type { AlertWithCommonFieldsLatest, SuppressionFieldsLatest, } from '@kbn/rule-registry-plugin/common/schemas'; + +import { isQueryRule } from '../../../../../common/detection_engine/utils'; import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; import { makeFloatString } from './utils'; import type { @@ -22,6 +24,7 @@ import type { import type { RuleServices } from '../types'; import { createEnrichEventsFunction } from './enrichments'; import type { ExperimentalFeatures } from '../../../../../common'; +import { getNumberOfSuppressedAlerts } from './get_number_of_suppressed_alerts'; export interface GenericBulkCreateResponse<T extends BaseFieldsLatest> { success: boolean; @@ -46,6 +49,7 @@ export const bulkCreateWithSuppression = async < isSuppressionPerRuleExecution, maxAlerts, experimentalFeatures, + ruleType, }: { alertWithSuppression: SuppressedAlertService; ruleExecutionLogger: IRuleExecutionLogForExecutors; @@ -56,6 +60,7 @@ export const bulkCreateWithSuppression = async < isSuppressionPerRuleExecution?: boolean; maxAlerts?: number; experimentalFeatures: ExperimentalFeatures; + ruleType?: RuleType; }): Promise<GenericBulkCreateResponse<T>> => { if (wrappedDocs.length === 0) { return { @@ -110,6 +115,15 @@ export const bulkCreateWithSuppression = async < ruleExecutionLogger.debug(`Alerts bulk process took ${makeFloatString(end - start)} ms`); + // query rule type suppression does not happen in memory, so we can't just count createdAlerts and suppressedAlerts + // for this rule type we need to look into alerts suppression properties, extract those values and sum up + const suppressedItemsCount = isQueryRule(ruleType) + ? getNumberOfSuppressedAlerts( + createdAlerts, + suppressedAlerts.map(({ _source, _id }) => ({ _id, ..._source })) + ) + : suppressedAlerts.length; + if (!isEmpty(errors)) { ruleExecutionLogger.warn(`Alerts bulk process finished with errors: ${JSON.stringify(errors)}`); return { @@ -119,7 +133,7 @@ export const bulkCreateWithSuppression = async < bulkCreateDuration: makeFloatString(end - start), createdItemsCount: createdAlerts.length, createdItems: createdAlerts, - suppressedItemsCount: suppressedAlerts.length, + suppressedItemsCount, alertsWereTruncated, }; } else { @@ -130,7 +144,7 @@ export const bulkCreateWithSuppression = async < enrichmentDuration: makeFloatString(enrichmentsTimeFinish - enrichmentsTimeStart), createdItemsCount: createdAlerts.length, createdItems: createdAlerts, - suppressedItemsCount: suppressedAlerts.length, + suppressedItemsCount, alertsWereTruncated, }; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_number_of_suppressed_alerts.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_number_of_suppressed_alerts.test.ts new file mode 100644 index 0000000000000..c817c58679899 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_number_of_suppressed_alerts.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 { getNumberOfSuppressedAlerts } from './get_number_of_suppressed_alerts'; +import { ALERT_SUPPRESSION_DOCS_COUNT } from '@kbn/rule-data-utils'; +import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; + +import type { BaseFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts'; +describe('getNumberOfSuppressedAlerts', () => { + it('should count total number of suppressed alerts in created alerts', () => { + const createdAlerts = [ + { _id: '1', [ALERT_SUPPRESSION_DOCS_COUNT]: 2 }, + { _id: '2', [ALERT_SUPPRESSION_DOCS_COUNT]: 0 }, + { _id: '3', [ALERT_SUPPRESSION_DOCS_COUNT]: 4 }, + { _id: '4' }, + ] as Array<SuppressionFieldsLatest & BaseFieldsLatest & { _id: string }>; + + expect(getNumberOfSuppressedAlerts(createdAlerts, [])).toBe(6); + }); + + // each alert in suppressed alerts counts as +1, since it is also suppressed + it('should count total number of suppressed alerts in suppressed alerts', () => { + const suppressedAlerts = [ + { _id: '1', [ALERT_SUPPRESSION_DOCS_COUNT]: 2 }, + { _id: '2', [ALERT_SUPPRESSION_DOCS_COUNT]: 0 }, + { _id: '3', [ALERT_SUPPRESSION_DOCS_COUNT]: 4 }, + ] as Array<SuppressionFieldsLatest & BaseFieldsLatest & { _id: string }>; + + expect(getNumberOfSuppressedAlerts([], suppressedAlerts)).toBe(9); + }); + + it('should count total number of suppressed alerts', () => { + const createdAlerts = [{ _id: '1', [ALERT_SUPPRESSION_DOCS_COUNT]: 2 }] as Array< + SuppressionFieldsLatest & BaseFieldsLatest & { _id: string } + >; + const suppressedAlerts = [{ _id: '3', [ALERT_SUPPRESSION_DOCS_COUNT]: 4 }] as Array< + SuppressionFieldsLatest & BaseFieldsLatest & { _id: string } + >; + expect(getNumberOfSuppressedAlerts(createdAlerts, suppressedAlerts)).toBe(7); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_number_of_suppressed_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_number_of_suppressed_alerts.ts new file mode 100644 index 0000000000000..7b4615b56509d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_number_of_suppressed_alerts.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ALERT_SUPPRESSION_DOCS_COUNT } from '@kbn/rule-data-utils'; +import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; + +import type { BaseFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts'; + +export const getNumberOfSuppressedAlerts = < + T extends SuppressionFieldsLatest & BaseFieldsLatest & { _id: string } +>( + createdAlerts: T[], + suppressedAlerts: T[] +): number => { + return ( + createdAlerts.reduce((acc, alert) => acc + (alert?.[ALERT_SUPPRESSION_DOCS_COUNT] || 0), 0) + + suppressedAlerts.reduce( + (acc, alert) => acc + (alert?.[ALERT_SUPPRESSION_DOCS_COUNT] || 0) + 1, + 0 + ) + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts new file mode 100644 index 0000000000000..c7eecaa5ce076 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.test.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { coreMock } from '@kbn/core/server/mocks'; +import type { AnalyticsServiceSetup } from '@kbn/core/public'; +import type { RuleParams } from '../../../rule_schema'; +import type { SanitizedRuleConfig } from '@kbn/alerting-plugin/common'; +import { ALERT_SUPPRESSION_EVENT } from '../../../../telemetry/event_based/events'; + +import { + sendAlertSuppressionTelemetryEvent, + suppressionDurationToSeconds, +} from './send_alert_suppression_telemetry_event'; + +describe('suppressionDurationToSeconds', () => { + it('should convert hours to seconds', () => { + expect(suppressionDurationToSeconds({ value: 5, unit: 'h' })).toBe(18000); + }); + it('should convert minutes to seconds', () => { + expect(suppressionDurationToSeconds({ value: 23, unit: 'm' })).toBe(1380); + }); + it('should return seconds', () => { + expect(suppressionDurationToSeconds({ value: 5, unit: 's' })).toBe(5); + }); + it('should return -1 when duration is undefined', () => { + expect(suppressionDurationToSeconds(undefined)).toBe(-1); + }); +}); + +describe('sendAlertSuppressionTelemetryEvent', () => { + let mockAnalytics: jest.Mocked<AnalyticsServiceSetup>; + let mockCore: ReturnType<typeof coreMock.createSetup>; + const ruleAttributes = { name: 'Detects suspicious activity on endpoint' } as SanitizedRuleConfig; + beforeEach(() => { + mockCore = coreMock.createSetup(); + mockAnalytics = mockCore.analytics; + }); + + it('should not report event if suppression is not configured', () => { + const ruleParams = { + type: 'query', + immutable: false, + ruleId: 'test-rule-id', + } as RuleParams; + + sendAlertSuppressionTelemetryEvent({ + analytics: mockAnalytics, + suppressedAlertsCount: 4, + createdAlertsCount: 4, + ruleParams, + ruleAttributes, + }); + + expect(mockAnalytics.reportEvent).not.toHaveBeenCalled(); + }); + it('should not report event if no alerts created or suppressed', () => { + const ruleParams = { + type: 'query', + immutable: false, + alertSuppression: { + groupBy: ['host.name'], + }, + } as RuleParams; + + sendAlertSuppressionTelemetryEvent({ + analytics: mockAnalytics, + suppressedAlertsCount: 0, + createdAlertsCount: 0, + ruleParams, + ruleAttributes, + }); + + expect(mockAnalytics.reportEvent).not.toHaveBeenCalled(); + }); + it('should report correct event data for threshold rule type', () => { + const ruleParams = { + type: 'threshold', + immutable: false, + alertSuppression: { + duration: { + unit: 'm', + value: 20, + }, + }, + ruleId: 'test-rule-id', + } as RuleParams; + + sendAlertSuppressionTelemetryEvent({ + analytics: mockAnalytics, + suppressedAlertsCount: 1, + createdAlertsCount: 6, + ruleParams, + ruleAttributes, + }); + + expect(mockAnalytics.reportEvent).toHaveBeenCalledWith(ALERT_SUPPRESSION_EVENT.eventType, { + suppressionAlertsCreated: 6, + suppressionAlertsSuppressed: 1, + suppressionDuration: 1200, + suppressionMissingFields: false, + suppressionRuleName: 'Custom rule', + suppressionRuleType: 'threshold', + suppressionGroupByFields: [], + suppressionGroupByFieldsNumber: 0, + suppressionRuleId: 'test-rule-id', + }); + }); + it('should report correct event data for query rule type with per time period suppression', () => { + const ruleParams = { + type: 'query', + immutable: false, + alertSuppression: { + groupBy: ['host.name'], + duration: { + unit: 'h', + value: 1, + }, + }, + ruleId: 'test-rule-id', + } as RuleParams; + + sendAlertSuppressionTelemetryEvent({ + analytics: mockAnalytics, + suppressedAlertsCount: 0, + createdAlertsCount: 10, + ruleParams, + ruleAttributes, + }); + + expect(mockAnalytics.reportEvent).toHaveBeenCalledWith(ALERT_SUPPRESSION_EVENT.eventType, { + suppressionAlertsCreated: 10, + suppressionAlertsSuppressed: 0, + suppressionDuration: 3600, + suppressionMissingFields: true, + suppressionRuleType: 'query', + suppressionRuleName: 'Custom rule', + suppressionGroupByFields: ['host.name'], + suppressionGroupByFieldsNumber: 1, + suppressionRuleId: 'test-rule-id', + }); + }); + + it('should report correct event data for esql rule type with per execution suppression', () => { + const ruleParams = { + type: 'esql', + immutable: false, + alertSuppression: { + groupBy: ['host.name', 'host.ip'], + missingFieldsStrategy: 'doNotSuppress', + }, + ruleId: 'test-rule-id', + } as RuleParams; + + sendAlertSuppressionTelemetryEvent({ + analytics: mockAnalytics, + suppressedAlertsCount: 2, + createdAlertsCount: 11, + ruleParams, + ruleAttributes, + }); + + expect(mockAnalytics.reportEvent).toHaveBeenCalledWith(ALERT_SUPPRESSION_EVENT.eventType, { + suppressionAlertsCreated: 11, + suppressionAlertsSuppressed: 2, + suppressionDuration: -1, + suppressionMissingFields: false, + suppressionRuleName: 'Custom rule', + suppressionRuleType: 'esql', + suppressionGroupByFields: ['host.name', 'host.ip'], + suppressionGroupByFieldsNumber: 2, + suppressionRuleId: 'test-rule-id', + }); + }); + it('should report prebuilt rule name', () => { + const ruleParams = { + type: 'esql', + immutable: true, + alertSuppression: { + groupBy: ['host.name', 'host.ip'], + missingFieldsStrategy: 'doNotSuppress', + }, + } as RuleParams; + + sendAlertSuppressionTelemetryEvent({ + analytics: mockAnalytics, + suppressedAlertsCount: 2, + createdAlertsCount: 11, + ruleParams, + ruleAttributes, + }); + + expect(mockAnalytics.reportEvent).toHaveBeenCalledWith( + ALERT_SUPPRESSION_EVENT.eventType, + expect.objectContaining({ + suppressionRuleName: 'Detects suspicious activity on endpoint', + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.ts new file mode 100644 index 0000000000000..ee597c59f3246 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/telemetry/send_alert_suppression_telemetry_event.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; +import type { SanitizedRuleConfig } from '@kbn/alerting-plugin/common'; +import { ALERT_SUPPRESSION_EVENT } from '../../../../telemetry/event_based/events'; +import type { AlertSuppressionDuration } from '../../../../../../common/api/detection_engine/model/rule_schema/common_attributes.gen'; +import type { RuleParams } from '../../../rule_schema'; + +import { isThresholdParams } from '../utils'; + +export const suppressionDurationToSeconds = ( + duration: AlertSuppressionDuration | undefined +): number => { + if (!duration) { + return -1; + } + switch (duration.unit) { + case 's': + return duration.value; + case 'm': + return duration.value * 60; + case 'h': + return duration.value * 60 * 60; + default: + return -1; + } +}; + +interface SendAlertSuppressionEventArgs { + analytics: AnalyticsServiceSetup; + suppressedAlertsCount: number; + createdAlertsCount: number; + ruleParams: RuleParams; + ruleAttributes: SanitizedRuleConfig; +} + +export const sendAlertSuppressionTelemetryEvent = ({ + analytics, + suppressedAlertsCount, + createdAlertsCount, + ruleParams, + ruleAttributes, +}: SendAlertSuppressionEventArgs): void => { + // do not send any telemetry event if suppression is not configured + if (ruleParams.alertSuppression == null) { + return; + } + + // do not send any telemetry if no alerts were suppressed or created + if (suppressedAlertsCount + createdAlertsCount === 0) { + return; + } + + const suppressionGroupByFieldsNumber = isThresholdParams(ruleParams) + ? ruleParams.threshold?.field?.length || 0 + : ruleParams.alertSuppression.groupBy.length; + + const suppressionGroupByFields = isThresholdParams(ruleParams) + ? ruleParams.threshold?.field || [] + : ruleParams.alertSuppression.groupBy; + + const suppressionMissingFields = isThresholdParams(ruleParams) + ? false + : ruleParams.alertSuppression.missingFieldsStrategy !== 'doNotSuppress'; + + const telemetryEvent = { + suppressionAlertsCreated: createdAlertsCount, + suppressionAlertsSuppressed: suppressedAlertsCount, + suppressionRuleName: ruleParams.immutable ? ruleAttributes.name : 'Custom rule', + suppressionDuration: suppressionDurationToSeconds(ruleParams.alertSuppression.duration), + suppressionGroupByFieldsNumber, + suppressionGroupByFields, + suppressionRuleType: ruleParams.type, + suppressionMissingFields, + suppressionRuleId: ruleParams.ruleId, + }; + analytics.reportEvent(ALERT_SUPPRESSION_EVENT.eventType, telemetryEvent); +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts index 4770d051f2e99..39473c69c5a2e 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts @@ -272,12 +272,37 @@ export class AssetCriticalityDataClient { return { errors, stats }; }; - public async delete(idParts: AssetCriticalityIdParts, refresh = 'wait_for' as const) { - await this.options.esClient.delete({ - id: createId(idParts), - index: this.getIndex(), - refresh: refresh ?? false, - }); + public async delete( + idParts: AssetCriticalityIdParts, + refresh = 'wait_for' as const + ): Promise<AssetCriticalityRecord | undefined> { + let record: AssetCriticalityRecord | undefined; + try { + record = await this.get(idParts); + } catch (err) { + if (err.statusCode === 404) { + return undefined; + } else { + throw err; + } + } + + if (!record) { + return undefined; + } + + try { + await this.options.esClient.delete({ + id: createId(idParts), + index: this.getIndex(), + refresh: refresh ?? false, + }); + } catch (err) { + this.options.logger.error(`Failed to delete asset criticality record: ${err.message}`); + throw err; + } + + return record; } public formatSearchResponse(response: SearchResponse<AssetCriticalityRecord>): { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts index 822c8a644d9b3..960f6c87be283 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts @@ -4,13 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { Readable } from 'node:stream'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import type { BulkUpsertAssetCriticalityRecordsResponse } from '../../../../../common/api/entity_analytics'; -import { BulkUpsertAssetCriticalityRecordsRequestBody } from '../../../../../common/api/entity_analytics'; +import { + BulkUpsertAssetCriticalityRecordsRequestBody, + type BulkUpsertAssetCriticalityRecordsResponse, +} from '../../../../../common/api/entity_analytics'; import type { ConfigType } from '../../../../config'; import { ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, @@ -46,7 +48,11 @@ export const assetCriticalityPublicBulkUploadRoute = ( }, }, }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise<IKibanaResponse<BulkUpsertAssetCriticalityRecordsResponse>> => { const { errorRetries, maxBulkRequestBodySizeBytes } = config.entityAnalytics.assetCriticality.csvUpload; const { records } = request.body; @@ -90,9 +96,7 @@ export const assetCriticalityPublicBulkUploadRoute = ( () => `Asset criticality Bulk upload completed in ${tookMs}ms ${JSON.stringify(stats)}` ); - const resBody: BulkUpsertAssetCriticalityRecordsResponse = { errors, stats }; - - return response.ok({ body: resBody }); + return response.ok({ body: { errors, stats } }); } catch (e) { logger.error(`Error during asset criticality bulk upload: ${e}`); const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts index 6fb6ef7488b27..4e0692f631718 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts @@ -4,18 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { - DeleteAssetCriticalityRecordRequestQuery, - InternalDeleteAssetCriticalityRecordRequestQuery, -} from '../../../../../common/api/entity_analytics'; -import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; +import type { DeleteAssetCriticalityRecordResponse } from '../../../../../common/api/entity_analytics'; +import { DeleteAssetCriticalityRecordRequestQuery } from '../../../../../common/api/entity_analytics'; import { ASSET_CRITICALITY_PUBLIC_URL, - ASSET_CRITICALITY_INTERNAL_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, @@ -26,79 +22,6 @@ import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -type DeleteHandler = ( - context: SecuritySolutionRequestHandlerContext, - request: { - query: DeleteAssetCriticalityRecordRequestQuery; - }, - response: KibanaResponseFactory -) => Promise<IKibanaResponse>; - -const handler: (logger: Logger) => DeleteHandler = - (logger) => async (context, request, response) => { - const securitySolution = await context.securitySolution; - - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to un-assign asset criticality from an entity', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UNASSIGN, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.DELETION, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); - - const siemResponse = buildSiemResponse(response); - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - await assetCriticalityClient.delete( - { - idField: request.query.id_field, - idValue: request.query.id_value, - }, - request.query.refresh - ); - - return response.ok(); - } catch (e) { - const error = transformError(e); - - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } - }; - -export const assetCriticalityInternalDeleteRoute = ( - router: EntityAnalyticsRoutesDeps['router'], - logger: Logger -) => { - router.versioned - .delete({ - access: 'internal', - path: ASSET_CRITICALITY_INTERNAL_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], - }, - }) - .addVersion( - { - version: API_VERSIONS.internal.v1, - validate: { - request: { - query: buildRouteValidationWithZod(InternalDeleteAssetCriticalityRecordRequestQuery), - }, - }, - }, - handler(logger) - ); -}; - export const assetCriticalityPublicDeleteRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger @@ -120,6 +43,52 @@ export const assetCriticalityPublicDeleteRoute = ( }, }, }, - handler(logger) + async ( + context, + request, + response + ): Promise<IKibanaResponse<DeleteAssetCriticalityRecordResponse>> => { + const securitySolution = await context.securitySolution; + + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to un-assign asset criticality from an entity', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UNASSIGN, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.DELETION, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); + + const siemResponse = buildSiemResponse(response); + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + const deletedRecord = await assetCriticalityClient.delete( + { + idField: request.query.id_field, + idValue: request.query.id_value, + }, + request.query.refresh + ); + + return response.ok({ + body: { + deleted: deletedRecord !== undefined, + record: deletedRecord, + }, + }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + } ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts index 9e5203bd24741..ed63f6207fec1 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts @@ -4,14 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; -import { GetAssetCriticalityRecordRequestQuery } from '../../../../../common/api/entity_analytics'; -import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; import { - ASSET_CRITICALITY_INTERNAL_URL, + GetAssetCriticalityRecordRequestQuery, + type GetAssetCriticalityRecordResponse, +} from '../../../../../common/api/entity_analytics'; +import { ASSET_CRITICALITY_PUBLIC_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, @@ -22,77 +23,6 @@ import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setti import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -type GetHandler = ( - context: SecuritySolutionRequestHandlerContext, - request: { - query: GetAssetCriticalityRecordRequestQuery; - }, - response: KibanaResponseFactory -) => Promise<IKibanaResponse>; - -const handler: (logger: Logger) => GetHandler = (logger) => async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - - const securitySolution = await context.securitySolution; - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - const record = await assetCriticalityClient.get({ - idField: request.query.id_field, - idValue: request.query.id_value, - }); - - if (!record) { - return response.notFound(); - } - - securitySolution.getAuditLogger()?.log({ - message: 'User accessed the criticality level for an entity', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_GET, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.ACCESS, - outcome: AUDIT_OUTCOME.SUCCESS, - }, - }); - - return response.ok({ body: record }); - } catch (e) { - const error = transformError(e); - - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } -}; - -export const assetCriticalityInternalGetRoute = ( - router: EntityAnalyticsRoutesDeps['router'], - logger: Logger -) => { - router.versioned - .get({ - access: 'internal', - path: ASSET_CRITICALITY_INTERNAL_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], - }, - }) - .addVersion( - { - version: API_VERSIONS.internal.v1, - validate: { - request: { - query: buildRouteValidationWithZod(GetAssetCriticalityRecordRequestQuery), - }, - }, - }, - handler(logger) - ); -}; export const assetCriticalityPublicGetRoute = ( router: EntityAnalyticsRoutesDeps['router'], @@ -115,6 +45,47 @@ export const assetCriticalityPublicGetRoute = ( }, }, }, - handler(logger) + async ( + context, + request, + response + ): Promise<IKibanaResponse<GetAssetCriticalityRecordResponse>> => { + const siemResponse = buildSiemResponse(response); + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + const record = await assetCriticalityClient.get({ + idField: request.query.id_field, + idValue: request.query.id_value, + }); + + if (!record) { + return response.notFound(); + } + + securitySolution.getAuditLogger()?.log({ + message: 'User accessed the criticality level for an entity', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_GET, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.ACCESS, + outcome: AUDIT_OUTCOME.SUCCESS, + }, + }); + + return response.ok({ body: record }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + } ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts index 711426e4df510..64bbca127ed77 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/list.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; @@ -43,7 +43,11 @@ export const assetCriticalityPublicListRoute = ( }, }, }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise<IKibanaResponse<FindAssetCriticalityRecordsResponse>> => { const siemResponse = buildSiemResponse(response); try { await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); @@ -81,7 +85,7 @@ export const assetCriticalityPublicListRoute = ( }, }); - const body: FindAssetCriticalityRecordsResponse = { + const body = { records, total, page, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts index a3b4c48d828df..7f6b80dd92909 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts @@ -4,9 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { AssetCriticalityGetPrivilegesResponse } from '../../../../../common/api/entity_analytics'; import { ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, APP_ID, @@ -38,7 +39,11 @@ export const assetCriticalityInternalPrivilegesRoute = ( version: API_VERSIONS.internal.v1, validate: false, }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise<IKibanaResponse<AssetCriticalityGetPrivilegesResponse>> => { const siemResponse = buildSiemResponse(response); try { await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts index 07899be6b9175..c7134ee0eafb6 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts @@ -5,14 +5,11 @@ * 2.0. */ import { assetCriticalityInternalStatusRoute } from './status'; -import { assetCriticalityPublicUpsertRoute, assetCriticalityInternalUpsertRoute } from './upsert'; -import { assetCriticalityInternalGetRoute, assetCriticalityPublicGetRoute } from './get'; -import { assetCriticalityPublicDeleteRoute, assetCriticalityInternalDeleteRoute } from './delete'; +import { assetCriticalityPublicUpsertRoute } from './upsert'; +import { assetCriticalityPublicGetRoute } from './get'; +import { assetCriticalityPublicDeleteRoute } from './delete'; import { assetCriticalityInternalPrivilegesRoute } from './privileges'; -import { - assetCriticalityInternalCSVUploadRoute, - assetCriticalityPublicCSVUploadRoute, -} from './upload_csv'; +import { assetCriticalityPublicCSVUploadRoute } from './upload_csv'; import { assetCriticalityPublicListRoute } from './list'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { assetCriticalityPublicBulkUploadRoute } from './bulk_upload'; @@ -24,13 +21,8 @@ export const registerAssetCriticalityRoutes = ({ getStartServices, }: EntityAnalyticsRoutesDeps) => { // Internal routes - assetCriticalityInternalCSVUploadRoute(router, logger, config, getStartServices); - assetCriticalityInternalDeleteRoute(router, logger); - assetCriticalityInternalGetRoute(router, logger); assetCriticalityInternalPrivilegesRoute(router, logger, getStartServices); assetCriticalityInternalStatusRoute(router, logger); - assetCriticalityInternalUpsertRoute(router, logger); - // Public routes assetCriticalityPublicCSVUploadRoute(router, logger, config, getStartServices); assetCriticalityPublicBulkUploadRoute(router, logger, config); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts index 9d77817a20d98..a0070503a3f8c 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { GetAssetCriticalityStatusResponse } from '../../../../../common/api/entity_analytics'; @@ -34,7 +34,11 @@ export const assetCriticalityInternalStatusRoute = ( }) .addVersion( { version: API_VERSIONS.internal.v1, validate: {} }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise<IKibanaResponse<GetAssetCriticalityStatusResponse>> => { const siemResponse = buildSiemResponse(response); try { await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); @@ -55,11 +59,10 @@ export const assetCriticalityInternalStatusRoute = ( }, }); - const body: GetAssetCriticalityStatusResponse = { - asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled, - }; return response.ok({ - body, + body: { + asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled, + }, }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts index 98d9ae6c75a96..cbe434ccb25cf 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts @@ -4,18 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { schema } from '@kbn/config-schema'; import Papa from 'papaparse'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type internal from 'stream'; import type { UploadAssetCriticalityRecordsResponse } from '../../../../../common/api/entity_analytics'; import { CRITICALITY_CSV_MAX_SIZE_BYTES_WITH_TOLERANCE } from '../../../../../common/entity_analytics/asset_criticality'; import type { ConfigType } from '../../../../config'; -import type { HapiReadableStream, SecuritySolutionRequestHandlerContext } from '../../../../types'; +import type { HapiReadableStream } from '../../../../types'; import { - ASSET_CRITICALITY_INTERNAL_CSV_UPLOAD_URL, ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, @@ -29,135 +27,6 @@ import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -type CSVUploadHandler = ( - context: SecuritySolutionRequestHandlerContext, - request: { - body: { file: internal.Stream }; - }, - response: KibanaResponseFactory -) => Promise<IKibanaResponse>; - -const handler: ( - logger: Logger, - getStartServices: EntityAnalyticsRoutesDeps['getStartServices'], - config: ConfigType -) => CSVUploadHandler = - (logger, getStartServices, config) => async (context, request, response) => { - const { errorRetries, maxBulkRequestBodySizeBytes } = - config.entityAnalytics.assetCriticality.csvUpload; - - const securitySolution = await context.securitySolution; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to assign many asset criticalities via file upload', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_BULK_UPDATE, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CREATION, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); - - const start = new Date(); - const siemResponse = buildSiemResponse(response); - const [coreStart] = await getStartServices(); - const telemetry = coreStart.analytics; - - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - const fileStream = request.body.file as HapiReadableStream; - - logger.debug(`Parsing asset criticality CSV file ${fileStream.hapi.filename}`); - - const csvStream = Papa.parse(Papa.NODE_STREAM_INPUT, { - header: false, - dynamicTyping: true, - skipEmptyLines: true, - }); - - const recordsStream = fileStream.pipe(csvStream).pipe(transformCSVToUpsertRecords()); - - const { errors, stats } = await assetCriticalityClient.bulkUpsertFromStream({ - recordsStream, - retries: errorRetries, - flushBytes: maxBulkRequestBodySizeBytes, - }); - const end = new Date(); - - const tookMs = end.getTime() - start.getTime(); - logger.debug( - () => `Asset criticality CSV upload completed in ${tookMs}ms ${JSON.stringify(stats)}` - ); - - // type assignment here to ensure that the response body stays in sync with the API schema - const resBody: UploadAssetCriticalityRecordsResponse = { errors, stats }; - - const [eventType, event] = createAssetCriticalityProcessedFileEvent({ - startTime: start, - endTime: end, - result: stats, - }); - - telemetry.reportEvent(eventType, event); - - return response.ok({ body: resBody }); - } catch (e) { - logger.error(`Error during asset criticality csv upload: ${e}`); - try { - const end = new Date(); - - const [eventType, event] = createAssetCriticalityProcessedFileEvent({ - startTime: start, - endTime: end, - }); - - telemetry.reportEvent(eventType, event); - } catch (error) { - logger.error(`Error reporting telemetry event: ${error}`); - } - - const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: error.message, - }); - } - }; - -export const assetCriticalityInternalCSVUploadRoute = ( - router: EntityAnalyticsRoutesDeps['router'], - logger: Logger, - config: ConfigType, - getStartServices: EntityAnalyticsRoutesDeps['getStartServices'] -) => { - router.versioned - .post({ - access: 'internal', - path: ASSET_CRITICALITY_INTERNAL_CSV_UPLOAD_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], - body: { - output: 'stream', - accepts: 'multipart/form-data', - maxBytes: CRITICALITY_CSV_MAX_SIZE_BYTES_WITH_TOLERANCE, - }, - }, - }) - .addVersion( - { - version: API_VERSIONS.internal.v1, - validate: { - request: { - body: schema.object({ - file: schema.stream(), - }), - }, - }, - }, - handler(logger, getStartServices, config) - ); -}; export const assetCriticalityPublicCSVUploadRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger, @@ -188,6 +57,91 @@ export const assetCriticalityPublicCSVUploadRoute = ( }, }, }, - handler(logger, getStartServices, config) + async ( + context, + request, + response + ): Promise<IKibanaResponse<UploadAssetCriticalityRecordsResponse>> => { + const { errorRetries, maxBulkRequestBodySizeBytes } = + config.entityAnalytics.assetCriticality.csvUpload; + + const securitySolution = await context.securitySolution; + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to assign many asset criticalities via file upload', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_BULK_UPDATE, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CREATION, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); + + const start = new Date(); + const siemResponse = buildSiemResponse(response); + const [coreStart] = await getStartServices(); + const telemetry = coreStart.analytics; + + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + const fileStream = request.body.file as HapiReadableStream; + + logger.debug(`Parsing asset criticality CSV file ${fileStream.hapi.filename}`); + + const csvStream = Papa.parse(Papa.NODE_STREAM_INPUT, { + header: false, + dynamicTyping: true, + skipEmptyLines: true, + }); + + const recordsStream = fileStream.pipe(csvStream).pipe(transformCSVToUpsertRecords()); + + const { errors, stats } = await assetCriticalityClient.bulkUpsertFromStream({ + recordsStream, + retries: errorRetries, + flushBytes: maxBulkRequestBodySizeBytes, + }); + const end = new Date(); + + const tookMs = end.getTime() - start.getTime(); + logger.debug( + () => `Asset criticality CSV upload completed in ${tookMs}ms ${JSON.stringify(stats)}` + ); + + // type assignment here to ensure that the response body stays in sync with the API schema + const resBody: UploadAssetCriticalityRecordsResponse = { errors, stats }; + + const [eventType, event] = createAssetCriticalityProcessedFileEvent({ + startTime: start, + endTime: end, + result: stats, + }); + + telemetry.reportEvent(eventType, event); + + return response.ok({ body: resBody }); + } catch (e) { + logger.error(`Error during asset criticality csv upload: ${e}`); + try { + const end = new Date(); + + const [eventType, event] = createAssetCriticalityProcessedFileEvent({ + startTime: start, + endTime: end, + }); + + telemetry.reportEvent(eventType, event); + } catch (error) { + logger.error(`Error reporting telemetry event: ${error}`); + } + + const error = transformError(e); + return siemResponse.error({ + statusCode: error.statusCode, + body: error.message, + }); + } + } ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts index 59da04beff855..8feeb822bdddf 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts @@ -4,18 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { CreateAssetCriticalityRecordRequestBody, - InternalCreateAssetCriticalityRecordRequestBody, + type CreateAssetCriticalityRecordResponse, } from '../../../../../common/api/entity_analytics'; -import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; import { ASSET_CRITICALITY_PUBLIC_URL, - ASSET_CRITICALITY_INTERNAL_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, API_VERSIONS, @@ -26,84 +24,6 @@ import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; -type UpsertHandler = ( - context: SecuritySolutionRequestHandlerContext, - request: { - body: CreateAssetCriticalityRecordRequestBody; - }, - response: KibanaResponseFactory -) => Promise<IKibanaResponse>; - -const handler: (logger: Logger) => UpsertHandler = - (logger) => async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - - const securitySolution = await context.securitySolution; - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - - const assetCriticalityRecord = { - idField: request.body.id_field, - idValue: request.body.id_value, - criticalityLevel: request.body.criticality_level, - }; - - const result = await assetCriticalityClient.upsert( - assetCriticalityRecord, - request.body.refresh - ); - - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to assign the asset criticality level for an entity', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UPDATE, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CREATION, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); - - return response.ok({ - body: result, - }); - } catch (e) { - const error = transformError(e); - - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } - }; - -export const assetCriticalityInternalUpsertRoute = ( - router: EntityAnalyticsRoutesDeps['router'], - logger: Logger -) => { - router.versioned - .post({ - access: 'internal', - path: ASSET_CRITICALITY_INTERNAL_URL, - options: { - tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], - }, - }) - .addVersion( - { - version: API_VERSIONS.internal.v1, - validate: { - request: { - body: buildRouteValidationWithZod(InternalCreateAssetCriticalityRecordRequestBody), - }, - }, - }, - handler(logger) - ); -}; - export const assetCriticalityPublicUpsertRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger @@ -125,6 +45,52 @@ export const assetCriticalityPublicUpsertRoute = ( }, }, }, - handler(logger) + async ( + context, + request, + response + ): Promise<IKibanaResponse<CreateAssetCriticalityRecordResponse>> => { + const siemResponse = buildSiemResponse(response); + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + + const assetCriticalityRecord = { + idField: request.body.id_field, + idValue: request.body.id_value, + criticalityLevel: request.body.criticality_level, + }; + + const result = await assetCriticalityClient.upsert( + assetCriticalityRecord, + request.body.refresh + ); + + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to assign the asset criticality level for an entity', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UPDATE, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CREATION, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); + + return response.ok({ + body: result, + }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + } ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts index df45eb4ddb934..59b4b4f77537e 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/disable.ts @@ -7,6 +7,7 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import type { DisableRiskEngineResponse } from '../../../../../common/api/entity_analytics'; import { RISK_ENGINE_DISABLE_URL, APP_ID } from '../../../../../common/constants'; import { TASK_MANAGER_UNAVAILABLE_ERROR } from './translations'; @@ -29,59 +30,60 @@ export const riskEngineDisableRoute = ( }) .addVersion( { version: '1', validate: {} }, - withRiskEnginePrivilegeCheck(getStartServices, async (context, request, response) => { - const securitySolution = await context.securitySolution; + withRiskEnginePrivilegeCheck( + getStartServices, + async (context, request, response): Promise<IKibanaResponse<DisableRiskEngineResponse>> => { + const securitySolution = await context.securitySolution; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to disable the risk engine.', - event: { - action: RiskEngineAuditActions.RISK_ENGINE_DISABLE, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CHANGE, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); - - const siemResponse = buildSiemResponse(response); - const [_, { taskManager }] = await getStartServices(); - - const riskEngineClient = securitySolution.getRiskEngineDataClient(); - - if (!taskManager) { securitySolution.getAuditLogger()?.log({ - message: - 'User attempted to disable the risk engine, but the Kibana Task Manager was unavailable', + message: 'User attempted to disable the risk engine.', event: { action: RiskEngineAuditActions.RISK_ENGINE_DISABLE, category: AUDIT_CATEGORY.DATABASE, type: AUDIT_TYPE.CHANGE, - outcome: AUDIT_OUTCOME.FAILURE, - }, - error: { - message: - 'User attempted to disable the risk engine, but the Kibana Task Manager was unavailable', + outcome: AUDIT_OUTCOME.UNKNOWN, }, }); - return siemResponse.error({ - statusCode: 400, - body: TASK_MANAGER_UNAVAILABLE_ERROR, - }); - } + const siemResponse = buildSiemResponse(response); + const [_, { taskManager }] = await getStartServices(); - try { - await riskEngineClient.disableRiskEngine({ taskManager }); - const body: DisableRiskEngineResponse = { success: true }; - return response.ok({ body }); - } catch (e) { - const error = transformError(e); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); + if (!taskManager) { + securitySolution.getAuditLogger()?.log({ + message: + 'User attempted to disable the risk engine, but the Kibana Task Manager was unavailable', + event: { + action: RiskEngineAuditActions.RISK_ENGINE_DISABLE, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CHANGE, + outcome: AUDIT_OUTCOME.FAILURE, + }, + error: { + message: + 'User attempted to disable the risk engine, but the Kibana Task Manager was unavailable', + }, + }); + + return siemResponse.error({ + statusCode: 400, + body: TASK_MANAGER_UNAVAILABLE_ERROR, + }); + } + + try { + await riskEngineClient.disableRiskEngine({ taskManager }); + return response.ok({ body: { success: true } }); + } catch (e) { + const error = transformError(e); + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } } - }) + ) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts index e537a49b498a8..24b3c3816440d 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/enable.ts @@ -7,6 +7,7 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import type { EnableRiskEngineResponse } from '../../../../../common/api/entity_analytics'; import { RISK_ENGINE_ENABLE_URL, APP_ID } from '../../../../../common/constants'; import { TASK_MANAGER_UNAVAILABLE_ERROR } from './translations'; @@ -29,57 +30,59 @@ export const riskEngineEnableRoute = ( }) .addVersion( { version: '1', validate: {} }, - withRiskEnginePrivilegeCheck(getStartServices, async (context, request, response) => { - const securitySolution = await context.securitySolution; + withRiskEnginePrivilegeCheck( + getStartServices, + async (context, request, response): Promise<IKibanaResponse<EnableRiskEngineResponse>> => { + const securitySolution = await context.securitySolution; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to enable the risk engine', - event: { - action: RiskEngineAuditActions.RISK_ENGINE_ENABLE, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CHANGE, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); - - const siemResponse = buildSiemResponse(response); - const [_, { taskManager }] = await getStartServices(); - const riskEngineClient = securitySolution.getRiskEngineDataClient(); - if (!taskManager) { securitySolution.getAuditLogger()?.log({ - message: - 'User attempted to enable the risk engine, but the Kibana Task Manager was unavailable', + message: 'User attempted to enable the risk engine', event: { action: RiskEngineAuditActions.RISK_ENGINE_ENABLE, category: AUDIT_CATEGORY.DATABASE, type: AUDIT_TYPE.CHANGE, - outcome: AUDIT_OUTCOME.FAILURE, + outcome: AUDIT_OUTCOME.UNKNOWN, }, - error: { + }); + + const siemResponse = buildSiemResponse(response); + const [_, { taskManager }] = await getStartServices(); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); + if (!taskManager) { + securitySolution.getAuditLogger()?.log({ message: 'User attempted to enable the risk engine, but the Kibana Task Manager was unavailable', - }, - }); + event: { + action: RiskEngineAuditActions.RISK_ENGINE_ENABLE, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CHANGE, + outcome: AUDIT_OUTCOME.FAILURE, + }, + error: { + message: + 'User attempted to enable the risk engine, but the Kibana Task Manager was unavailable', + }, + }); - return siemResponse.error({ - statusCode: 400, - body: TASK_MANAGER_UNAVAILABLE_ERROR, - }); - } + return siemResponse.error({ + statusCode: 400, + body: TASK_MANAGER_UNAVAILABLE_ERROR, + }); + } - try { - await riskEngineClient.enableRiskEngine({ taskManager }); - const body: EnableRiskEngineResponse = { success: true }; - return response.ok({ body }); - } catch (e) { - const error = transformError(e); + try { + await riskEngineClient.enableRiskEngine({ taskManager }); + return response.ok({ body: { success: true } }); + } catch (e) { + const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } } - }) + ) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts index 160d040f6d9fc..4657d21cbcbe0 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/init.ts @@ -7,6 +7,7 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import type { InitRiskEngineResponse, InitRiskEngineResult, @@ -31,75 +32,78 @@ export const riskEngineInitRoute = ( }) .addVersion( { version: '1', validate: {} }, - withRiskEnginePrivilegeCheck(getStartServices, async (context, request, response) => { - const securitySolution = await context.securitySolution; + withRiskEnginePrivilegeCheck( + getStartServices, + async (context, request, response): Promise<IKibanaResponse<InitRiskEngineResponse>> => { + const securitySolution = await context.securitySolution; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to initialize the risk engine', - event: { - action: RiskEngineAuditActions.RISK_ENGINE_INIT, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CHANGE, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to initialize the risk engine', + event: { + action: RiskEngineAuditActions.RISK_ENGINE_INIT, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CHANGE, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); - const siemResponse = buildSiemResponse(response); - const [_, { taskManager }] = await getStartServices(); - const riskEngineDataClient = securitySolution.getRiskEngineDataClient(); - const riskScoreDataClient = securitySolution.getRiskScoreDataClient(); - const spaceId = securitySolution.getSpaceId(); + const siemResponse = buildSiemResponse(response); + const [_, { taskManager }] = await getStartServices(); + const riskEngineDataClient = securitySolution.getRiskEngineDataClient(); + const riskScoreDataClient = securitySolution.getRiskScoreDataClient(); + const spaceId = securitySolution.getSpaceId(); - try { - if (!taskManager) { - return siemResponse.error({ - statusCode: 400, - body: TASK_MANAGER_UNAVAILABLE_ERROR, - }); - } + try { + if (!taskManager) { + return siemResponse.error({ + statusCode: 400, + body: TASK_MANAGER_UNAVAILABLE_ERROR, + }); + } - const initResult = await riskEngineDataClient.init({ - taskManager, - namespace: spaceId, - riskScoreDataClient, - }); - - const result: InitRiskEngineResult = { - risk_engine_enabled: initResult.riskEngineEnabled, - risk_engine_resources_installed: initResult.riskEngineResourcesInstalled, - risk_engine_configuration_created: initResult.riskEngineConfigurationCreated, - legacy_risk_engine_disabled: initResult.legacyRiskEngineDisabled, - errors: initResult.errors, - }; + const initResult = await riskEngineDataClient.init({ + taskManager, + namespace: spaceId, + riskScoreDataClient, + }); - const initResponse: InitRiskEngineResponse = { - result, - }; + const result: InitRiskEngineResult = { + risk_engine_enabled: initResult.riskEngineEnabled, + risk_engine_resources_installed: initResult.riskEngineResourcesInstalled, + risk_engine_configuration_created: initResult.riskEngineConfigurationCreated, + legacy_risk_engine_disabled: initResult.legacyRiskEngineDisabled, + errors: initResult.errors, + }; - if ( - !initResult.riskEngineEnabled || - !initResult.riskEngineResourcesInstalled || - !initResult.riskEngineConfigurationCreated - ) { - return siemResponse.error({ - statusCode: 400, + if ( + !initResult.riskEngineEnabled || + !initResult.riskEngineResourcesInstalled || + !initResult.riskEngineConfigurationCreated + ) { + return siemResponse.error({ + statusCode: 400, + body: { + message: result.errors.join('\n'), + full_error: result, + }, + bypassErrorFormat: true, + }); + } + return response.ok({ body: { - message: result.errors.join('\n'), - full_error: result, + result, }, + }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, bypassErrorFormat: true, }); } - return response.ok({ body: initResponse }); - } catch (e) { - const error = transformError(e); - - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); } - }) + ) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts index 38b48aca7e5ab..f14e06fa72868 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts @@ -7,7 +7,8 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { EntityAnalyticsPrivileges } from '../../../../../common/api/entity_analytics'; +import type { IKibanaResponse } from '@kbn/core-http-server'; +import type { RiskEngineGetPrivilegesResponse } from '../../../../../common/api/entity_analytics'; import { RISK_ENGINE_PRIVILEGES_URL, APP_ID } from '../../../../../common/constants'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; import { RiskScoreAuditActions } from '../../risk_score/audit'; @@ -27,34 +28,41 @@ export const riskEnginePrivilegesRoute = ( tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) - .addVersion({ version: '1', validate: false }, async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - const [_, { security }] = await getStartServices(); - const securitySolution = await context.securitySolution; + .addVersion( + { version: '1', validate: false }, + async ( + context, + request, + response + ): Promise<IKibanaResponse<RiskEngineGetPrivilegesResponse>> => { + const siemResponse = buildSiemResponse(response); + const [_, { security }] = await getStartServices(); + const securitySolution = await context.securitySolution; - const body: EntityAnalyticsPrivileges = await getUserRiskEnginePrivileges(request, security); + const body = await getUserRiskEnginePrivileges(request, security); - securitySolution.getAuditLogger()?.log({ - message: 'User checked if they have the required privileges to configure the risk engine', - event: { - action: RiskScoreAuditActions.RISK_ENGINE_PRIVILEGES_GET, - category: AUDIT_CATEGORY.AUTHENTICATION, - type: AUDIT_TYPE.ACCESS, - outcome: AUDIT_OUTCOME.SUCCESS, - }, - }); - - try { - return response.ok({ - body, + securitySolution.getAuditLogger()?.log({ + message: 'User checked if they have the required privileges to configure the risk engine', + event: { + action: RiskScoreAuditActions.RISK_ENGINE_PRIVILEGES_GET, + category: AUDIT_CATEGORY.AUTHENTICATION, + type: AUDIT_TYPE.ACCESS, + outcome: AUDIT_OUTCOME.SUCCESS, + }, }); - } catch (e) { - const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: error.message, - }); + try { + return response.ok({ + body, + }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: error.message, + }); + } } - }); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts index 1d39fbaf18420..e300f012b86cf 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/settings.ts @@ -7,6 +7,7 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import type { ReadRiskEngineSettingsResponse } from '../../../../../common/api/entity_analytics/risk_engine'; import { RISK_ENGINE_SETTINGS_URL, APP_ID } from '../../../../../common/constants'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; @@ -22,41 +23,47 @@ export const riskEngineSettingsRoute = (router: EntityAnalyticsRoutesDeps['route tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) - .addVersion({ version: '1', validate: {} }, async (context, request, response) => { - const siemResponse = buildSiemResponse(response); + .addVersion( + { version: '1', validate: {} }, + async ( + context, + request, + response + ): Promise<IKibanaResponse<ReadRiskEngineSettingsResponse>> => { + const siemResponse = buildSiemResponse(response); - const securitySolution = await context.securitySolution; - const riskEngineClient = securitySolution.getRiskEngineDataClient(); + const securitySolution = await context.securitySolution; + const riskEngineClient = securitySolution.getRiskEngineDataClient(); - try { - const result = await riskEngineClient.getConfiguration(); - securitySolution.getAuditLogger()?.log({ - message: 'User accessed risk engine configuration information', - event: { - action: RiskEngineAuditActions.RISK_ENGINE_CONFIGURATION_GET, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.ACCESS, - outcome: AUDIT_OUTCOME.SUCCESS, - }, - }); + try { + const result = await riskEngineClient.getConfiguration(); + securitySolution.getAuditLogger()?.log({ + message: 'User accessed risk engine configuration information', + event: { + action: RiskEngineAuditActions.RISK_ENGINE_CONFIGURATION_GET, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.ACCESS, + outcome: AUDIT_OUTCOME.SUCCESS, + }, + }); - if (!result) { - throw new Error('Unable to get risk engine configuration'); - } - const body: ReadRiskEngineSettingsResponse = { - range: result.range, - }; - return response.ok({ - body, - }); - } catch (e) { - const error = transformError(e); + if (!result) { + throw new Error('Unable to get risk engine configuration'); + } + return response.ok({ + body: { + range: result.range, + }, + }); + } catch (e) { + const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } } - }); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts index 00806bfd43720..b3d0cc4082446 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/status.ts @@ -7,6 +7,7 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import type { IKibanaResponse } from '@kbn/core-http-server'; import type { RiskEngineStatusResponse } from '../../../../../common/api/entity_analytics'; import { RISK_ENGINE_STATUS_URL, APP_ID } from '../../../../../common/constants'; import type { EntityAnalyticsRoutesDeps } from '../../types'; @@ -20,34 +21,37 @@ export const riskEngineStatusRoute = (router: EntityAnalyticsRoutesDeps['router' tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) - .addVersion({ version: '1', validate: {} }, async (context, request, response) => { - const siemResponse = buildSiemResponse(response); + .addVersion( + { version: '1', validate: {} }, + async (context, request, response): Promise<IKibanaResponse<RiskEngineStatusResponse>> => { + const siemResponse = buildSiemResponse(response); - const securitySolution = await context.securitySolution; - const riskEngineClient = securitySolution.getRiskEngineDataClient(); - const spaceId = securitySolution.getSpaceId(); + const securitySolution = await context.securitySolution; + const riskEngineClient = securitySolution.getRiskEngineDataClient(); + const spaceId = securitySolution.getSpaceId(); - try { - const { riskEngineStatus, legacyRiskEngineStatus, isMaxAmountOfRiskEnginesReached } = - await riskEngineClient.getStatus({ - namespace: spaceId, - }); - - const body: RiskEngineStatusResponse = { - risk_engine_status: riskEngineStatus, - legacy_risk_engine_status: legacyRiskEngineStatus, - is_max_amount_of_risk_engines_reached: isMaxAmountOfRiskEnginesReached, - }; + try { + const { riskEngineStatus, legacyRiskEngineStatus, isMaxAmountOfRiskEnginesReached } = + await riskEngineClient.getStatus({ + namespace: spaceId, + }); - return response.ok({ body }); - } catch (e) { - const error = transformError(e); + return response.ok({ + body: { + risk_engine_status: riskEngineStatus, + legacy_risk_engine_status: legacyRiskEngineStatus, + is_max_amount_of_risk_engines_reached: isMaxAmountOfRiskEnginesReached, + }, + }); + } catch (e) { + const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } } - }); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts index c72a1706f089e..4b1cf773a572b 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts @@ -33,7 +33,7 @@ type Handler = ( context: SecuritySolutionRequestHandlerContext, request: KibanaRequest<unknown, unknown, RiskScoresEntityCalculationRequest>, response: KibanaResponseFactory -) => Promise<IKibanaResponse>; +) => Promise<IKibanaResponse<RiskScoresCalculationResponse>>; const handler: (logger: Logger) => Handler = (logger) => async (context, request, response) => { const securityContext = await context.securitySolution; @@ -101,7 +101,7 @@ const handler: (logger: Logger) => Handler = (logger) => async (context, request const filter = isEmpty(userFilter) ? [identifierFilter] : [userFilter, identifierFilter]; - const result: RiskScoresCalculationResponse = await riskScoreService.calculateAndPersistScores({ + const result = await riskScoreService.calculateAndPersistScores({ pageSize, identifierType, index, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts index 68e7f2fc50b74..ae265d415288a 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts @@ -5,10 +5,11 @@ * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { RiskScoresPreviewResponse } from '../../../../../common/api/entity_analytics'; import { RiskScoresPreviewRequest } from '../../../../../common/api/entity_analytics'; import { APP_ID, @@ -40,7 +41,7 @@ export const riskScorePreviewRoute = ( request: { body: buildRouteValidationWithZod(RiskScoresPreviewRequest) }, }, }, - async (context, request, response) => { + async (context, request, response): Promise<IKibanaResponse<RiskScoresPreviewResponse>> => { const siemResponse = buildSiemResponse(response); const securityContext = await context.securitySolution; const coreContext = await context.core; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts b/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts index 8eb46b2046c10..e367f012ad0bc 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts @@ -123,6 +123,86 @@ export const ASSET_CRITICALITY_SYSTEM_PROCESSED_ASSIGNMENT_FILE_EVENT: EventType }, }; +export const ALERT_SUPPRESSION_EVENT: EventTypeOpts<{ + suppressionAlertsCreated: number; + suppressionAlertsSuppressed: number; + suppressionRuleName: string; + suppressionDuration: number; + suppressionGroupByFieldsNumber: number; + suppressionGroupByFields: string[]; + suppressionRuleType: string; + suppressionMissingFields: boolean; + suppressionRuleId: string; +}> = { + eventType: 'alert_suppression_on_rule_execution', + schema: { + suppressionAlertsCreated: { + type: 'long', + _meta: { + description: + 'Number of alerts created during rule execution with configured alert suppression', + }, + }, + suppressionAlertsSuppressed: { + type: 'long', + _meta: { + description: + 'Number of alerts suppressed during rule execution with configured alert suppression', + }, + }, + suppressionRuleName: { + type: 'keyword', + _meta: { + description: 'Name of rule', + }, + }, + suppressionDuration: { + type: 'long', + _meta: { + description: 'Duration in seconds of suppression period. -1 for per rule execution config', + }, + }, + suppressionGroupByFieldsNumber: { + type: 'long', + _meta: { + description: 'Number of Suppress by fields', + }, + }, + suppressionGroupByFields: { + type: 'array', + items: { + type: 'keyword', + _meta: { + description: 'Tag attached to the element...', + optional: false, + }, + }, + _meta: { + description: 'List of tags attached to the element...', + optional: false, + }, + }, + suppressionRuleType: { + type: 'keyword', + _meta: { + description: 'Rule type', + }, + }, + suppressionMissingFields: { + type: 'boolean', + _meta: { + description: 'Suppression of missing fields enabled', + }, + }, + suppressionRuleId: { + type: 'keyword', + _meta: { + description: 'ruleId', + }, + }, + }, +}; + interface CreateAssetCriticalityProcessedFileEvent { result?: BulkUpsertAssetCriticalityRecordsResponse['stats']; startTime: Date; @@ -175,4 +255,5 @@ export const events = [ RISK_SCORE_EXECUTION_ERROR_EVENT, RISK_SCORE_EXECUTION_CANCELLATION_EVENT, ASSET_CRITICALITY_SYSTEM_PROCESSED_ASSIGNMENT_FILE_EVENT, + ALERT_SUPPRESSION_EVENT, ]; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/import_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/import_timelines.ts index eea6d60c739d3..34b6897ce530f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/import_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/import_timelines.ts @@ -7,7 +7,7 @@ import { omit } from 'lodash/fp'; import { TimelineId } from '../../../../common/types/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; export const mockDuplicateIdErrors = []; @@ -140,7 +140,7 @@ export const mockGetTimelineValue = { kqlMode: 'filter', kqlQuery: { filterQuery: [] }, title: 'My duplicate timeline', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, dateRange: { start: '2020-03-18T09:31:47.294Z', end: '2020-03-19T09:31:47.294Z' }, savedQueryId: null, sort: { columnId: '@timestamp', sortDirection: 'desc' }, @@ -154,7 +154,7 @@ export const mockGetTimelineValue = { export const mockGetTemplateTimelineValue = { ...mockGetTimelineValue, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, templateTimelineId: '79deb4c0-6bc1-0000-0000-f5341fb7a189', templateTimelineVersion: 1, }; @@ -187,8 +187,8 @@ export const mockGetDraftTimelineValue = { updatedBy: 'angela', noteIds: [], pinnedEventIds: ['k-gi8nABm-sIqJ_scOoS'], - timelineType: TimelineType.default, - status: TimelineStatus.draft, + timelineType: TimelineTypeEnum.default, + status: TimelineStatusEnum.draft, }; export const mockParsedTimelineObject = omit( diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts index 1fdc16d4aeec3..eb4649f941d11 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts @@ -21,7 +21,11 @@ import type { createTimelineSchema, GetTimelineQuery, } from '../../../../common/api/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { + type TimelineType, + TimelineTypeEnum, + TimelineStatusEnum, +} from '../../../../common/api/timeline'; import { requestMock } from '../../detection_engine/routes/__mocks__'; @@ -74,7 +78,7 @@ export const inputTimeline: SavedTimeline = { kqlMode: 'filter', kqlQuery: { filterQuery: null }, title: 't', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, templateTimelineId: null, templateTimelineVersion: 1, dateRange: { start: '2020-03-26T12:50:05.527Z', end: '2020-03-27T12:50:05.527Z' }, @@ -84,7 +88,7 @@ export const inputTimeline: SavedTimeline = { export const inputTemplateTimeline = { ...inputTimeline, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, templateTimelineId: '79deb4c0-6bc1-11ea-inpt-templatea189', templateTimelineVersion: null, }; @@ -94,15 +98,15 @@ export const createTimelineWithoutTimelineId = { timeline: inputTimeline, timelineId: null, version: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }; export const createTemplateTimelineWithoutTimelineId = { timeline: inputTemplateTimeline, timelineId: null, version: null, - timelineType: TimelineType.template, - status: TimelineStatus.active, + timelineType: TimelineTypeEnum.template, + status: TimelineStatusEnum.active, }; export const createTimelineWithTimelineId = { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts index b3f2d8adbaba9..ea875a71416c1 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/resolve_timeline.ts @@ -6,7 +6,7 @@ */ import type { ResolvedTimelineWithOutcomeSavedObject } from '../../../../common/api/timeline'; -import { TimelineStatus, TimelineType } from '../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; export const mockResolvedSavedObject = { saved_object: { @@ -74,7 +74,7 @@ export const mockResolvedTimeline = { eventType: 'all', filters: [], kqlMode: 'filter', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, kqlQuery: { filterQuery: null }, title: 'Test Timeline', sort: [ @@ -84,7 +84,7 @@ export const mockResolvedTimeline = { columnId: '@timestamp', }, ], - status: TimelineStatus.active, + status: TimelineStatusEnum.active, created: 1632932987378, createdBy: 'tester', updated: 1632932988422, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts index acd28651e23cb..ba727f6c0d707 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.test.ts @@ -6,7 +6,7 @@ */ import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; -import { TimelineType } from '../../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../../common/api/timeline'; import { serverMock, @@ -82,10 +82,10 @@ describe('clean draft timelines', () => { }); const response = await server.inject( - cleanDraftTimelinesRequest(TimelineType.default), + cleanDraftTimelinesRequest(TimelineTypeEnum.default), requestContextMock.convertContext(context) ); - const req = cleanDraftTimelinesRequest(TimelineType.default); + const req = cleanDraftTimelinesRequest(TimelineTypeEnum.default); expect(mockPersistTimeline).toHaveBeenCalled(); expect(mockPersistTimeline.mock.calls[0][3]).toEqual({ ...draftTimelineDefaults, @@ -109,10 +109,10 @@ describe('clean draft timelines', () => { mockGetTimeline.mockResolvedValue({ ...mockGetDraftTimelineValue }); const response = await server.inject( - cleanDraftTimelinesRequest(TimelineType.default), + cleanDraftTimelinesRequest(TimelineTypeEnum.default), requestContextMock.convertContext(context) ); - const req = cleanDraftTimelinesRequest(TimelineType.default); + const req = cleanDraftTimelinesRequest(TimelineTypeEnum.default); expect(mockPersistTimeline).not.toHaveBeenCalled(); expect(mockResetTimeline).toHaveBeenCalled(); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts index ae1cff768fded..f8b9ad8392982 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts @@ -21,7 +21,7 @@ import { persistTimeline, } from '../../../saved_object/timelines'; import { draftTimelineDefaults } from '../../../utils/default_timeline'; -import { cleanDraftTimelineSchema, TimelineType } from '../../../../../../common/api/timeline'; +import { cleanDraftTimelineSchema, TimelineTypeEnum } from '../../../../../../common/api/timeline'; export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { router.versioned @@ -70,7 +70,7 @@ export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter, _ }); } const templateTimelineData = - request.body.timelineType === TimelineType.template + request.body.timelineType === TimelineTypeEnum.template ? { timelineType: request.body.timelineType, templateTimelineId: uuidv4(), diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts index fd38d5c4a865d..75c9b361bc78f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.test.ts @@ -6,7 +6,7 @@ */ import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; -import { TimelineType } from '../../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../../common/api/timeline'; import { serverMock, @@ -81,7 +81,7 @@ describe('get draft timelines', () => { mockGetDraftTimeline.mockResolvedValue({ timeline: [], }); - const req = getDraftTimelinesRequest(TimelineType.default); + const req = getDraftTimelinesRequest(TimelineTypeEnum.default); const response = await server.inject(req, requestContextMock.convertContext(context)); expect(mockPersistTimeline).toHaveBeenCalled(); expect(mockPersistTimeline.mock.calls[0][3]).toEqual({ @@ -105,7 +105,7 @@ describe('get draft timelines', () => { }); const response = await server.inject( - getDraftTimelinesRequest(TimelineType.default), + getDraftTimelinesRequest(TimelineTypeEnum.default), requestContextMock.convertContext(context) ); expect(mockPersistTimeline).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.test.ts index 93d7794c059d1..db62235ea592f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.test.ts @@ -13,10 +13,10 @@ import { requestMock, } from '../../../detection_engine/routes/__mocks__'; import { NOTE_URL } from '../../../../../common/constants'; -import type { getNotesPaginated } from '../../utils/common'; +import type { getNotesSchema } from '../../../../../common/api/timeline'; import { mockGetCurrentUser } from '../../__mocks__/import_timelines'; -const getAllNotesRequest = (query?: typeof getNotesPaginated) => +const getAllNotesRequest = (query?: typeof getNotesSchema) => requestMock.create({ method: 'get', path: NOTE_URL, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts index f230d0832a96c..8e8a88a4a6aa9 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts @@ -6,13 +6,16 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { NOTE_URL } from '../../../../../common/constants'; import type { ConfigType } from '../../../..'; import { buildSiemResponse } from '../../../detection_engine/routes/utils'; -import { buildFrameworkRequest, getNotesPaginated } from '../../utils/common'; +import { buildFrameworkRequest } from '../../utils/common'; +import { getNotesSchema } from '../../../../../common/api/timeline'; +import { buildRouteValidationWithExcess } from '../../../../utils/build_validation/route_validation'; import { getAllSavedNote, MAX_UNASSOCIATED_NOTES } from '../../saved_object/notes'; import { noteSavedObjectType } from '../../saved_object_mappings/notes'; @@ -28,7 +31,7 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp .addVersion( { validate: { - request: { query: getNotesPaginated }, + request: { query: buildRouteValidationWithExcess(getNotesSchema) }, }, version: '2023-10-31', }, @@ -63,9 +66,9 @@ export const getNotesRoute = (router: SecuritySolutionPluginRouter, _: ConfigTyp } else { const perPage = queryParams?.perPage ? parseInt(queryParams.perPage, 10) : 10; const page = queryParams?.page ? parseInt(queryParams.page, 10) : 1; - const search = queryParams?.search; - const sortField = queryParams?.sortField; - const sortOrder = queryParams?.sortOrder; + const search = queryParams?.search ?? undefined; + const sortField = queryParams?.sortField ?? undefined; + const sortOrder = (queryParams?.sortOrder as SortOrder) ?? undefined; const filter = queryParams?.filter; const options = { type: noteSavedObjectType, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts index 0858df47ac360..6f7767247396d 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/helpers.ts @@ -12,7 +12,10 @@ import { createPromiseFromStreams } from '@kbn/utils'; import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { ImportTimelineResultSchema } from '../../../../../../common/api/timeline'; -import { importTimelineResultSchema, TimelineStatus } from '../../../../../../common/api/timeline'; +import { + importTimelineResultSchema, + TimelineStatusEnum, +} from '../../../../../../common/api/timeline'; import type { BulkError } from '../../../../detection_engine/routes/utils'; import { createBulkErrorObject } from '../../../../detection_engine/routes/utils'; @@ -70,9 +73,9 @@ export const setTimeline = ( return { ...parsedTimelineObject, status: - parsedTimeline.status === TimelineStatus.draft - ? TimelineStatus.active - : parsedTimeline.status ?? TimelineStatus.active, + parsedTimeline.status === TimelineStatusEnum.draft + ? TimelineStatusEnum.active + : parsedTimeline.status ?? TimelineStatusEnum.active, templateTimelineVersion: isTemplateTimeline ? parsedTimeline.templateTimelineVersion ?? 1 : null, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts index 9f66040d8704b..3415df46e9ac4 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.test.ts @@ -13,7 +13,7 @@ import { createMockConfig, } from '../../../../detection_engine/routes/__mocks__'; import { TIMELINE_EXPORT_URL } from '../../../../../../common/constants'; -import { TimelineStatus, TimelineType } from '../../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../../common/api/timeline'; import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import { @@ -184,7 +184,7 @@ describe('import timelines', () => { await server.inject(mockRequest, requestContextMock.convertContext(context)); expect(mockPersistTimeline.mock.calls[0][3]).toEqual({ ...mockParsedTimelineObject, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, templateTimelineId: null, templateTimelineVersion: null, }); @@ -429,7 +429,7 @@ describe('import timelines', () => { [ { ...mockGetTimelineValue, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }, ], ]); @@ -630,7 +630,7 @@ describe('import timeline templates', () => { await server.inject(mockRequest, requestContextMock.convertContext(context)); expect(mockPersistTimeline.mock.calls[0][3]).toEqual({ ...mockParsedTemplateTimelineObject, - status: TimelineStatus.active, + status: TimelineStatusEnum.active, }); }); @@ -851,7 +851,7 @@ describe('import timeline templates', () => { [ { ...mockUniqueParsedTemplateTimelineObjects[0], - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }, ], ]); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts index 3b220ccf57e20..f53acf25803a8 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts @@ -17,7 +17,7 @@ import { buildSiemResponse } from '../../../../detection_engine/routes/utils'; import { buildFrameworkRequest } from '../../../utils/common'; import { persistFavorite } from '../../../saved_object/timelines'; -import { TimelineType, persistFavoriteSchema } from '../../../../../../common/api/timeline'; +import { TimelineTypeEnum, persistFavoriteSchema } from '../../../../../../common/api/timeline'; export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: ConfigType) => { router.versioned @@ -48,7 +48,7 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter, _: Co timelineId || null, templateTimelineId || null, templateTimelineVersion || null, - timelineType || TimelineType.default + timelineType || TimelineTypeEnum.default ); return response.ok({ diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts index 60d2e1cdca502..2cde936f1c462 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/convert_saved_object_to_savedtimeline.ts @@ -17,7 +17,12 @@ import { SavedObjectTimelineStatus, } from '../../../../../common/types/timeline/saved_object'; import type { TimelineSavedObject } from '../../../../../common/api/timeline'; -import { TimelineType, TimelineStatus } from '../../../../../common/api/timeline'; +import { + type TimelineType, + TimelineTypeEnum, + type TimelineStatus, + TimelineStatusEnum, +} from '../../../../../common/api/timeline'; // TODO: Added to support legacy TimelineType.draft, can be removed in 7.10 const TimelineSavedObjectWithDraftRuntime = intersection([ @@ -100,10 +105,10 @@ function savedObjectTimelineTypeToAPITimelineType( ): TimelineType { switch (timelineType) { case SavedObjectTimelineType.template: - return TimelineType.template; + return TimelineTypeEnum.template; case 'draft': default: - return TimelineType.default; + return TimelineTypeEnum.default; } } @@ -113,15 +118,15 @@ function savedObjectTimelineStatusToAPITimelineStatus( ): TimelineStatus { // TODO: Added to support legacy TimelineType.draft, can be removed in 7.10 if (timelineType === 'draft') { - return TimelineStatus.draft; + return TimelineStatusEnum.draft; } switch (status) { case SavedObjectTimelineStatus.draft: - return TimelineStatus.draft; + return TimelineStatusEnum.draft; case SavedObjectTimelineStatus.immutable: - return TimelineStatus.immutable; + return TimelineStatusEnum.immutable; case SavedObjectTimelineStatus.active: default: - return TimelineStatus.active; + return TimelineStatusEnum.active; } } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts index f8ce73092a804..20049a63bddba 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.test.ts @@ -21,7 +21,7 @@ import { import { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_savedtimeline'; import { getNotesByTimelineId, persistNote } from '../notes/saved_object'; import { getAllPinnedEventsByTimelineId, persistPinnedEventOnTimeline } from '../pinned_events'; -import { TimelineType } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum } from '../../../../../common/api/timeline'; import type { AllTimelinesResponse, ResolvedTimelineWithOutcomeSavedObject, @@ -455,7 +455,7 @@ describe('saved_object', () => { }); test('should get draft filtered by current user', async () => { - await getDraftTimeline(mockRequest, TimelineType.default); + await getDraftTimeline(mockRequest, TimelineTypeEnum.default); expect(mockFindSavedObject).toBeCalledWith({ filter: 'not siem-ui-timeline.attributes.timelineType: template and siem-ui-timeline.attributes.status: draft and not siem-ui-timeline.attributes.status: immutable and siem-ui-timeline.attributes.updatedBy: "username" and siem-ui-timeline.attributes.createdBy: "username"', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts index d9b770c258319..71d61c22ab33c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/index.ts @@ -27,14 +27,14 @@ import type { ResponseTimeline, SortTimeline, TimelineResult, - TimelineTypeLiteralWithNull, - TimelineStatusLiteralWithNull, + TimelineType, + TimelineStatus, ResolvedTimelineWithOutcomeSavedObject, TimelineSavedObject, SavedTimeline, TimelineWithoutExternalRefs, } from '../../../../../common/api/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import type { SavedObjectTimelineWithoutExternalRefs } from '../../../../../common/types/timeline/saved_object'; import type { FrameworkRequest } from '../../../framework'; import * as note from '../notes/saved_object'; @@ -52,11 +52,11 @@ export { convertSavedObjectToSavedTimeline } from './convert_saved_object_to_sav export const getTimeline = async ( request: FrameworkRequest, timelineId: string, - timelineType: TimelineTypeLiteralWithNull = TimelineType.default + timelineType: TimelineType | null = TimelineTypeEnum.default ): Promise<TimelineSavedObject> => { let timelineIdToUse = timelineId; try { - if (timelineType === TimelineType.template) { + if (timelineType === TimelineTypeEnum.template) { const options = { type: timelineSavedObjectType, perPage: 1, @@ -130,33 +130,33 @@ export const getTimelineTemplateOrNull = async ( /** The filter here is able to handle the legacy data, * which has no timelineType exists in the savedObject */ const getTimelineTypeFilter = ( - timelineType: TimelineTypeLiteralWithNull, - status: TimelineStatusLiteralWithNull + timelineType: TimelineType | null, + status: TimelineStatus | null ) => { const typeFilter = timelineType == null ? null - : timelineType === TimelineType.template - ? `siem-ui-timeline.attributes.timelineType: ${TimelineType.template}` /** Show only whose timelineType exists and equals to "template" */ + : timelineType === TimelineTypeEnum.template + ? `siem-ui-timeline.attributes.timelineType: ${TimelineTypeEnum.template}` /** Show only whose timelineType exists and equals to "template" */ : /** Show me every timeline whose timelineType is not "template". * which includes timelineType === 'default' and * those timelineType doesn't exists */ - `not siem-ui-timeline.attributes.timelineType: ${TimelineType.template}`; + `not siem-ui-timeline.attributes.timelineType: ${TimelineTypeEnum.template}`; /** Show me every timeline whose status is not "draft". * which includes status === 'active' and * those status doesn't exists */ const draftFilter = - status === TimelineStatus.draft - ? `siem-ui-timeline.attributes.status: ${TimelineStatus.draft}` - : `not siem-ui-timeline.attributes.status: ${TimelineStatus.draft}`; + status === TimelineStatusEnum.draft + ? `siem-ui-timeline.attributes.status: ${TimelineStatusEnum.draft}` + : `not siem-ui-timeline.attributes.status: ${TimelineStatusEnum.draft}`; const immutableFilter = status == null ? null - : status === TimelineStatus.immutable - ? `siem-ui-timeline.attributes.status: ${TimelineStatus.immutable}` - : `not siem-ui-timeline.attributes.status: ${TimelineStatus.immutable}`; + : status === TimelineStatusEnum.immutable + ? `siem-ui-timeline.attributes.status: ${TimelineStatusEnum.immutable}` + : `not siem-ui-timeline.attributes.status: ${TimelineStatusEnum.immutable}`; const filters = [typeFilter, draftFilter, immutableFilter]; return combineFilters(filters); @@ -204,7 +204,7 @@ export const getExistingPrepackagedTimelines = async ( const elasticTemplateTimelineOptions = { type: timelineSavedObjectType, ...queryPageInfo, - filter: getTimelineTypeFilter(TimelineType.template, TimelineStatus.immutable), + filter: getTimelineTypeFilter(TimelineTypeEnum.template, TimelineStatusEnum.immutable), }; return getAllSavedTimeline(request, elasticTemplateTimelineOptions); @@ -216,8 +216,8 @@ export const getAllTimeline = async ( pageInfo: PageInfoTimeline, search: string | null, sort: SortTimeline | null, - status: TimelineStatusLiteralWithNull, - timelineType: TimelineTypeLiteralWithNull + status: TimelineStatus | null, + timelineType: TimelineType | null ): Promise<AllTimelinesResponse> => { const searchTerm = search != null ? search : undefined; const searchFields = ['title', 'description']; @@ -240,21 +240,21 @@ export const getAllTimeline = async ( type: timelineSavedObjectType, perPage: 1, page: 1, - filter: getTimelineTypeFilter(TimelineType.default, TimelineStatus.active), + filter: getTimelineTypeFilter(TimelineTypeEnum.default, TimelineStatusEnum.active), }; const templateTimelineOptions = { type: timelineSavedObjectType, perPage: 1, page: 1, - filter: getTimelineTypeFilter(TimelineType.template, null), + filter: getTimelineTypeFilter(TimelineTypeEnum.template, null), }; const customTemplateTimelineOptions = { type: timelineSavedObjectType, perPage: 1, page: 1, - filter: getTimelineTypeFilter(TimelineType.template, TimelineStatus.active), + filter: getTimelineTypeFilter(TimelineTypeEnum.template, TimelineStatusEnum.active), }; const favoriteTimelineOptions = { @@ -264,7 +264,7 @@ export const getAllTimeline = async ( perPage: 1, page: 1, filter: combineFilters([ - getTimelineTypeFilter(timelineType ?? null, TimelineStatus.active), + getTimelineTypeFilter(timelineType ?? null, TimelineStatusEnum.active), getTimelineFavoriteFilter({ onlyUserFavorite: true, request }), ]), }; @@ -290,10 +290,10 @@ export const getAllTimeline = async ( export const getDraftTimeline = async ( request: FrameworkRequest, - timelineType: TimelineTypeLiteralWithNull + timelineType: TimelineType | null ): Promise<ResponseTimelines> => { const filter = combineFilters([ - getTimelineTypeFilter(timelineType ?? null, TimelineStatus.draft), + getTimelineTypeFilter(timelineType ?? null, TimelineStatusEnum.draft), getTimelinesCreatedAndUpdatedByCurrentUser({ request }), ]); const options: SavedObjectsFindOptions = { @@ -597,7 +597,7 @@ export const copyTimeline = async ( pinnedEvent.getAllPinnedEventsByTimelineId(request, timelineId), ]); - const isImmutable = timeline.status === TimelineStatus.immutable; + const isImmutable = timeline.status === TimelineStatusEnum.immutable; const userInfo = isImmutable ? ({ username: 'Elastic' } as AuthenticatedUser) : request.user; const timelineResponse = await createTimeline({ @@ -773,7 +773,7 @@ export const getSelectedTimelines = async ( }, null, null, - TimelineStatus.active, + TimelineStatusEnum.active, null ); exportedIds = savedAllTimelines.map((t) => t.savedObjectId); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts index db32de9dc65ce..e14c49bf5dbee 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.test.ts @@ -8,7 +8,7 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import type { SavedTimeline, Note } from '../../../../../common/api/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline'; import { pickSavedTimeline } from './pick_saved_timeline'; @@ -55,7 +55,7 @@ describe('pickSavedTimeline', () => { sort: { sortDirection: 'desc', columnType: 'number', columnId: '@timestamp' }, title: 'title', kqlMode: 'filter', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, savedQueryId: null, kqlQuery: { filterQuery: null }, dataProviders: [], @@ -185,7 +185,7 @@ describe('pickSavedTimeline', () => { const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.status).toEqual(TimelineStatusEnum.active); }); test('Creating a timeline without title', () => { @@ -194,7 +194,7 @@ describe('pickSavedTimeline', () => { const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.draft); + expect(result.status).toEqual(TimelineStatusEnum.draft); }); test('Updating a timeline with a new title', () => { @@ -203,7 +203,7 @@ describe('pickSavedTimeline', () => { const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.status).toEqual(TimelineStatusEnum.active); }); test('Updating a timeline without title', () => { @@ -212,65 +212,65 @@ describe('pickSavedTimeline', () => { const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.status).toEqual(TimelineStatusEnum.active); }); test('Updating an immutable timeline with a new title', () => { - const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.immutable }; + const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatusEnum.immutable }; const timelineId = savedTimeline.savedObjectId ?? null; const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.immutable); + expect(result.status).toEqual(TimelineStatusEnum.immutable); }); test('Creating a draft timeline with title', () => { - const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.draft }; + const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatusEnum.draft }; const timelineId = null; const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.status).toEqual(TimelineStatusEnum.active); }); test('Creating a draft timeline without title', () => { const savedTimeline = { ...getMockSavedTimeline(), title: null, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }; const timelineId = null; const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.draft); + expect(result.status).toEqual(TimelineStatusEnum.draft); }); test('Updating an untitled draft timeline with a title', () => { - const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.draft }; + const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatusEnum.draft }; const timelineId = savedTimeline.savedObjectId ?? null; const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.status).toEqual(TimelineStatusEnum.active); }); test('Updating a draft timeline with a new title', () => { - const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.draft }; + const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatusEnum.draft }; const timelineId = savedTimeline.savedObjectId ?? null; const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.status).toEqual(TimelineStatusEnum.active); }); test('Updating a draft timeline without title', () => { - const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatus.draft }; + const savedTimeline = { ...getMockSavedTimeline(), status: TimelineStatusEnum.draft }; const timelineId = savedTimeline.savedObjectId ?? null; const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.status).toEqual(TimelineStatusEnum.active); }); }); @@ -280,8 +280,8 @@ describe('pickSavedTimeline', () => { const userInfo = { username: 'elastic' } as AuthenticatedUser; const result = pickSavedTimeline(timelineId, savedTimeline, userInfo); - expect(result.timelineType).toEqual(TimelineType.default); - expect(result.status).toEqual(TimelineStatus.active); + expect(result.timelineType).toEqual(TimelineTypeEnum.default); + expect(result.status).toEqual(TimelineStatusEnum.active); expect(result.templateTimelineId).toBeNull(); expect(result.templateTimelineVersion).toBeNull(); }); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts index 90467a4568244..50b57bf96bd5b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object/timelines/pick_saved_timeline.ts @@ -10,7 +10,7 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import { getUserDisplayName } from '@kbn/user-profile-components'; import { UNAUTHENTICATED_USER } from '../../../../../common/constants'; import type { SavedTimelineWithSavedObjectId } from '../../../../../common/api/timeline'; -import { TimelineType, TimelineStatus } from '../../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../../common/api/timeline'; export const pickSavedTimeline = ( timelineId: string | null, @@ -29,21 +29,21 @@ export const pickSavedTimeline = ( savedTimeline.updatedBy = userInfo ? getUserDisplayName(userInfo) : UNAUTHENTICATED_USER; } - if (savedTimeline.status === TimelineStatus.draft || savedTimeline.status == null) { + if (savedTimeline.status === TimelineStatusEnum.draft || savedTimeline.status == null) { savedTimeline.status = !isEmpty(savedTimeline.title) - ? TimelineStatus.active - : TimelineStatus.draft; + ? TimelineStatusEnum.active + : TimelineStatusEnum.draft; } - if (savedTimeline.timelineType === TimelineType.default) { - savedTimeline.timelineType = savedTimeline.timelineType ?? TimelineType.default; - savedTimeline.status = savedTimeline.status ?? TimelineStatus.active; + if (savedTimeline.timelineType === TimelineTypeEnum.default) { + savedTimeline.timelineType = savedTimeline.timelineType ?? TimelineTypeEnum.default; + savedTimeline.status = savedTimeline.status ?? TimelineStatusEnum.active; savedTimeline.templateTimelineId = null; savedTimeline.templateTimelineVersion = null; } - if (!isEmpty(savedTimeline.title) && savedTimeline.status === TimelineStatus.draft) { - savedTimeline.status = TimelineStatus.active; + if (!isEmpty(savedTimeline.title) && savedTimeline.status === TimelineStatusEnum.draft) { + savedTimeline.status = TimelineStatusEnum.active; } savedTimeline.excludedRowRendererIds = savedTimeline.excludedRowRendererIds ?? []; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts index cfa804b848fcd..edee5ee4cd912 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts @@ -38,16 +38,6 @@ export const buildFrameworkRequest = async ( export const escapeHatch = schema.object({}, { unknowns: 'allow' }); -export const getNotesPaginated = schema.object({ - documentIds: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), - page: schema.maybe(schema.string()), - perPage: schema.maybe(schema.string()), - search: schema.maybe(schema.string()), - sortField: schema.maybe(schema.string()), - sortOrder: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), - filter: schema.maybe(schema.string()), -}); - type ErrorFactory = (message: string) => Error; export const throwErrors = (createError: ErrorFactory) => (errors: rt.Errors) => { diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts index 2cff9c903c734..89cd06342c0df 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import type { FrameworkRequest } from '../../framework'; import { @@ -62,7 +62,7 @@ describe('CompareTimelinesStatus', () => { id: mockUniqueParsedObjects[0].savedObjectId, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, title: mockUniqueParsedObjects[0].title, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, @@ -137,7 +137,7 @@ describe('CompareTimelinesStatus', () => { id: mockUniqueParsedObjects[0].savedObjectId, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, title: mockUniqueParsedObjects[0].title, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, @@ -203,7 +203,7 @@ describe('CompareTimelinesStatus', () => { id: mockUniqueParsedObjects[0].savedObjectId, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, title: mockUniqueParsedObjects[0].title, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, @@ -288,7 +288,7 @@ describe('CompareTimelinesStatus', () => { id: mockUniqueParsedObjects[0].savedObjectId, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, title: mockUniqueParsedObjects[0].title, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, @@ -380,14 +380,14 @@ describe('CompareTimelinesStatus', () => { timelineObj = new CompareTimelinesStatus({ timelineInput: { id: mockUniqueParsedObjects[0].savedObjectId, - type: TimelineType.default, + type: TimelineTypeEnum.default, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, title: null, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, - type: TimelineType.template, + type: TimelineTypeEnum.template, version: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineVersion, }, frameworkRequest: {} as FrameworkRequest, @@ -460,14 +460,14 @@ describe('CompareTimelinesStatus', () => { timelineObj = new CompareTimelinesStatus({ timelineInput: { id: mockUniqueParsedObjects[0].savedObjectId, - type: TimelineType.default, + type: TimelineTypeEnum.default, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, title: null, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, - type: TimelineType.template, + type: TimelineTypeEnum.template, version: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineVersion, }, frameworkRequest: {} as FrameworkRequest, @@ -537,7 +537,7 @@ describe('CompareTimelinesStatus', () => { return { getTimelineOrNull: mockGetTimeline.mockReturnValue({ ...mockGetTimelineValue, - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }), getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue(null), }; @@ -550,15 +550,15 @@ describe('CompareTimelinesStatus', () => { timelineObj = new CompareTimelinesStatus({ timelineInput: { id: mockUniqueParsedObjects[0].savedObjectId, - type: TimelineType.default, + type: TimelineTypeEnum.default, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, title: 'mock title', - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, - type: TimelineType.template, + type: TimelineTypeEnum.template, version: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineVersion, }, frameworkRequest: {} as FrameworkRequest, @@ -610,11 +610,11 @@ describe('CompareTimelinesStatus', () => { return { getTimelineOrNull: mockGetTimeline.mockReturnValue({ ...mockGetTemplateTimelineValue, - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }), getTimelineTemplateOrNull: mockGetTemplateTimeline.mockReturnValue({ ...mockGetTemplateTimelineValue, - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }), }; }); @@ -626,15 +626,15 @@ describe('CompareTimelinesStatus', () => { timelineObj = new CompareTimelinesStatus({ timelineInput: { id: mockUniqueParsedObjects[0].savedObjectId, - type: TimelineType.default, + type: TimelineTypeEnum.default, version: mockUniqueParsedObjects[0].version, }, - status: TimelineStatus.immutable, - timelineType: TimelineType.template, + status: TimelineStatusEnum.immutable, + timelineType: TimelineTypeEnum.template, title: 'mock title', templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, - type: TimelineType.template, + type: TimelineTypeEnum.template, version: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineVersion, }, frameworkRequest: {} as FrameworkRequest, @@ -696,7 +696,7 @@ describe('CompareTimelinesStatus', () => { id: mockUniqueParsedObjects[0].savedObjectId, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, title: mockUniqueParsedObjects[0].title, templateTimelineInput: { id: null, @@ -761,7 +761,7 @@ describe('CompareTimelinesStatus', () => { id: mockUniqueParsedObjects[0].savedObjectId, version: mockUniqueParsedObjects[0].version, }, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, title: mockUniqueParsedObjects[0].title, templateTimelineInput: { id: mockUniqueParsedTemplateTimelineObjects[0].templateTimelineId, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts index 7c41cfa9c5449..3892fc067f9bd 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/compare_timelines_status.ts @@ -6,11 +6,12 @@ */ import { isEmpty, isInteger } from 'lodash/fp'; -import type { - TimelineTypeLiteralWithNull, - TimelineTypeLiteral, +import { + type TimelineType, + TimelineTypeEnum, + type TimelineStatus, + TimelineStatusEnum, } from '../../../../common/api/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; import type { FrameworkRequest } from '../../framework'; import type { TimelineStatusAction } from './common'; @@ -26,14 +27,14 @@ import { interface GivenTimelineInput { id: string | null | undefined; - type?: TimelineTypeLiteralWithNull; + type?: TimelineType | null; version: string | number | null | undefined; } interface TimelinesStatusProps { status: TimelineStatus | null | undefined; title: string | null | undefined; - timelineType: TimelineTypeLiteralWithNull | undefined; + timelineType: TimelineType | null | undefined; timelineInput: GivenTimelineInput; templateTimelineInput: GivenTimelineInput; frameworkRequest: FrameworkRequest; @@ -42,33 +43,33 @@ interface TimelinesStatusProps { export class CompareTimelinesStatus { public readonly timelineObject: TimelineObject; public readonly templateTimelineObject: TimelineObject; - private readonly timelineType: TimelineTypeLiteral; + private readonly timelineType: TimelineType; private readonly title: string | null; private readonly status: TimelineStatus; constructor({ - status = TimelineStatus.active, + status = TimelineStatusEnum.active, title, - timelineType = TimelineType.default, + timelineType = TimelineTypeEnum.default, timelineInput, templateTimelineInput, frameworkRequest, }: TimelinesStatusProps) { this.timelineObject = new TimelineObject({ id: timelineInput.id, - type: timelineInput.type ?? TimelineType.default, + type: timelineInput.type ?? TimelineTypeEnum.default, version: timelineInput.version, frameworkRequest, }); this.templateTimelineObject = new TimelineObject({ id: templateTimelineInput.id, - type: templateTimelineInput.type ?? TimelineType.template, + type: templateTimelineInput.type ?? TimelineTypeEnum.template, version: templateTimelineInput.version, frameworkRequest, }); - this.timelineType = timelineType ?? TimelineType.default; + this.timelineType = timelineType ?? TimelineTypeEnum.default; this.title = title ?? null; - this.status = status ?? TimelineStatus.active; + this.status = status ?? TimelineStatusEnum.active; } public get isCreatable() { @@ -109,8 +110,8 @@ export class CompareTimelinesStatus { const obj = this.isHandlingTemplateTimeline ? this.templateTimelineObject : this.timelineObject; return obj.isExists - ? this.status === obj.getData?.status && this.status !== TimelineStatus.draft - : this.status !== TimelineStatus.draft; + ? this.status === obj.getData?.status && this.status !== TimelineStatusEnum.draft + : this.status !== TimelineStatusEnum.draft; } public get isUpdatable() { @@ -124,7 +125,7 @@ export class CompareTimelinesStatus { private get isTimelineTypeValid() { const obj = this.isHandlingTemplateTimeline ? this.templateTimelineObject : this.timelineObject; - const existintTimelineType = obj.getData?.timelineType ?? TimelineType.default; + const existintTimelineType = obj.getData?.timelineType ?? TimelineTypeEnum.default; return obj.isExists ? this.timelineType === existintTimelineType : true; } @@ -142,8 +143,8 @@ export class CompareTimelinesStatus { public get isTitleValid() { return ( - (this.status !== TimelineStatus.draft && !isEmpty(this.title)) || - this.status === TimelineStatus.draft + (this.status !== TimelineStatusEnum.draft && !isEmpty(this.title)) || + this.status === TimelineStatusEnum.draft ); } @@ -196,7 +197,7 @@ export class CompareTimelinesStatus { } public get isHandlingTemplateTimeline() { - return this.timelineType === TimelineType.template; + return this.timelineType === TimelineTypeEnum.template; } private get isSavedObjectVersionConflict() { @@ -242,8 +243,8 @@ export class CompareTimelinesStatus { ? this.templateTimelineInput.data?.status : this.timelineInput.data?.status; return ( - ((existingStatus == null || existingStatus === TimelineStatus.active) && - (status == null || status === TimelineStatus.active)) || + ((existingStatus == null || existingStatus === TimelineStatusEnum.active) && + (status == null || status === TimelineStatusEnum.active)) || (existingStatus != null && status === existingStatus) ); } diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline.ts index 3624458c9e7fd..33c2af9d4e39c 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/default_timeline.ts @@ -7,7 +7,7 @@ import { defaultHeaders } from './default_timeline_headers'; import type { SavedTimeline } from '../../../../common/api/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline'; import { Direction } from '../../../../common/search_strategy'; export const draftTimelineDefaults: SavedTimeline = { @@ -17,7 +17,7 @@ export const draftTimelineDefaults: SavedTimeline = { eventType: 'all', filters: [], kqlMode: 'filter', - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, kqlQuery: { filterQuery: null, }, @@ -26,5 +26,5 @@ export const draftTimelineDefaults: SavedTimeline = { columnId: '@timestamp', sortDirection: Direction.desc, }, - status: TimelineStatus.draft, + status: TimelineStatusEnum.draft, }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts index de4a0bd8ca678..e5e247c1209fe 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.test.ts @@ -27,19 +27,19 @@ import { TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE, } from './failure_cases'; import type { TimelineSavedObject } from '../../../../common/api/timeline'; -import { TimelineStatus, TimelineType } from '../../../../common/api/timeline'; +import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline'; import { mockGetTimelineValue, mockGetTemplateTimelineValue } from '../__mocks__/import_timelines'; describe('failure cases', () => { describe('commonFailureChecker', () => { test('If timeline type is draft, it should not return error if title is not given', () => { - const result = commonFailureChecker(TimelineStatus.draft, null); + const result = commonFailureChecker(TimelineStatusEnum.draft, null); expect(result).toBeNull(); }); test('If timeline type is active, it should return error if title is not given', () => { - const result = commonFailureChecker(TimelineStatus.active, null); + const result = commonFailureChecker(TimelineStatusEnum.active, null); expect(result).toEqual({ body: EMPTY_TITLE_ERROR_MESSAGE, @@ -48,7 +48,7 @@ describe('failure cases', () => { }); test('If timeline type is immutable, it should return error if title is not given', () => { - const result = commonFailureChecker(TimelineStatus.immutable, null); + const result = commonFailureChecker(TimelineStatusEnum.immutable, null); expect(result).toEqual({ body: EMPTY_TITLE_ERROR_MESSAGE, @@ -57,7 +57,7 @@ describe('failure cases', () => { }); test('If timeline type is not a draft, it should return no error if title is given', () => { - const result = commonFailureChecker(TimelineStatus.active, 'title'); + const result = commonFailureChecker(TimelineStatusEnum.active, 'title'); expect(result).toBeNull(); }); @@ -73,8 +73,8 @@ describe('failure cases', () => { const existTemplateTimeline = null; const result = checkIsCreateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -97,8 +97,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsCreateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -121,8 +121,8 @@ describe('failure cases', () => { const existTemplateTimeline = null; const result = checkIsCreateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -145,13 +145,13 @@ describe('failure cases', () => { const templateTimelineId = null; const existTimeline = { ...(mockGetTimelineValue as TimelineSavedObject), - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }; const existTemplateTimeline = null; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -173,12 +173,12 @@ describe('failure cases', () => { const existTimeline = null; const existTemplateTimeline = { ...(mockGetTemplateTimelineValue as TimelineSavedObject), - status: TimelineStatus.immutable, + status: TimelineStatusEnum.immutable, }; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -201,8 +201,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -225,8 +225,8 @@ describe('failure cases', () => { const existTemplateTimeline = null; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -252,8 +252,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -276,8 +276,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -303,8 +303,8 @@ describe('failure cases', () => { const existTemplateTimeline = null; const result = checkIsUpdateFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -329,8 +329,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.draft, - TimelineType.template, + TimelineStatusEnum.draft, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -353,8 +353,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -377,8 +377,8 @@ describe('failure cases', () => { const existTemplateTimeline = null; const result = checkIsCreateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -403,8 +403,8 @@ describe('failure cases', () => { const existTemplateTimeline = null; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -427,8 +427,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -451,8 +451,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.immutable, - TimelineType.template, + TimelineStatusEnum.immutable, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -475,8 +475,8 @@ describe('failure cases', () => { const existTemplateTimeline = null; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.default, + TimelineStatusEnum.active, + TimelineTypeEnum.default, version, templateTimelineVersion, templateTimelineId, @@ -502,8 +502,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -526,8 +526,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, @@ -550,8 +550,8 @@ describe('failure cases', () => { const existTemplateTimeline = mockGetTemplateTimelineValue as TimelineSavedObject; const result = checkIsUpdateViaImportFailureCases( isHandlingTemplateTimeline, - TimelineStatus.active, - TimelineType.template, + TimelineStatusEnum.active, + TimelineTypeEnum.template, version, templateTimelineVersion, templateTimelineId, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts index f71e825e3514c..045793e2251ba 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/failure_cases.ts @@ -6,8 +6,8 @@ */ import { isEmpty } from 'lodash/fp'; -import type { TimelineTypeLiteral, TimelineSavedObject } from '../../../../common/api/timeline'; -import { TimelineStatus } from '../../../../common/api/timeline'; +import type { TimelineType, TimelineSavedObject } from '../../../../common/api/timeline'; +import { type TimelineStatus, TimelineStatusEnum } from '../../../../common/api/timeline'; export const UPDATE_TIMELINE_ERROR_MESSAGE = 'You cannot create new timelines with PATCH. Use POST instead.'; @@ -46,11 +46,12 @@ const isUpdatingStatus = ( existTemplateTimeline: TimelineSavedObject | null ) => { const obj = isHandlingTemplateTimeline ? existTemplateTimeline : existTimeline; - return obj?.status === TimelineStatus.immutable ? UPDATE_STATUS_ERROR_MESSAGE : null; + return obj?.status === TimelineStatusEnum.immutable ? UPDATE_STATUS_ERROR_MESSAGE : null; }; const isGivenTitleValid = (status: TimelineStatus, title: string | null | undefined) => { - return (status !== TimelineStatus.draft && !isEmpty(title)) || status === TimelineStatus.draft + return (status !== TimelineStatusEnum.draft && !isEmpty(title)) || + status === TimelineStatusEnum.draft ? null : EMPTY_TITLE_ERROR_MESSAGE; }; @@ -71,7 +72,7 @@ export const commonFailureChecker = (status: TimelineStatus, title: string | nul const commonUpdateTemplateTimelineCheck = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, @@ -124,7 +125,7 @@ const commonUpdateTemplateTimelineCheck = ( const commonUpdateTimelineCheck = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, @@ -153,7 +154,7 @@ const commonUpdateTimelineCheck = ( const commonUpdateCases = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, @@ -188,7 +189,7 @@ const commonUpdateCases = ( const createTemplateTimelineCheck = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, @@ -214,7 +215,7 @@ const createTemplateTimelineCheck = ( export const checkIsUpdateViaImportFailureCases = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, @@ -233,8 +234,8 @@ export const checkIsUpdateViaImportFailureCases = ( } else { const isStatusValid = ((existTemplateTimeline?.status == null || - existTemplateTimeline?.status === TimelineStatus.active) && - (status == null || status === TimelineStatus.active)) || + existTemplateTimeline?.status === TimelineStatusEnum.active) && + (status == null || status === TimelineStatusEnum.active)) || (existTemplateTimeline?.status != null && status === existTemplateTimeline?.status); if (!isStatusValid) { @@ -276,7 +277,7 @@ export const checkIsUpdateViaImportFailureCases = ( export const checkIsUpdateFailureCases = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, @@ -310,7 +311,7 @@ export const checkIsUpdateFailureCases = ( export const checkIsCreateFailureCases = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, @@ -341,14 +342,14 @@ export const checkIsCreateFailureCases = ( export const checkIsCreateViaImportFailureCases = ( isHandlingTemplateTimeline: boolean, status: TimelineStatus | null | undefined, - timelineType: TimelineTypeLiteral, + timelineType: TimelineType, version: string | null, templateTimelineVersion: number | null, templateTimelineId: string | null | undefined, existTimeline: TimelineSavedObject | null, existTemplateTimeline: TimelineSavedObject | null ) => { - if (status === TimelineStatus.draft) { + if (status === TimelineStatusEnum.draft) { return { body: CREATE_WITH_INVALID_STATUS_ERROR_MESSAGE, statusCode: 405, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts index d7e8e240fea1d..360125e450764 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/timeline_object.ts @@ -5,21 +5,25 @@ * 2.0. */ -import type { TimelineSavedObject, TimelineTypeLiteral } from '../../../../common/api/timeline'; -import { TimelineType, TimelineStatus } from '../../../../common/api/timeline'; +import type { TimelineSavedObject } from '../../../../common/api/timeline'; +import { + type TimelineType, + TimelineTypeEnum, + TimelineStatusEnum, +} from '../../../../common/api/timeline'; import type { FrameworkRequest } from '../../framework'; import { getTimelineOrNull, getTimelineTemplateOrNull } from '../saved_object/timelines'; interface TimelineObjectProps { id: string | null | undefined; - type: TimelineTypeLiteral; + type: TimelineType; version: string | number | null | undefined; frameworkRequest: FrameworkRequest; } export class TimelineObject { public readonly id: string | null; - private type: TimelineTypeLiteral; + private type: TimelineType; public readonly version: string | number | null; private frameworkRequest: FrameworkRequest; @@ -27,7 +31,7 @@ export class TimelineObject { constructor({ id = null, - type = TimelineType.default, + type = TimelineTypeEnum.default, version = null, frameworkRequest, }: TimelineObjectProps) { @@ -42,7 +46,7 @@ export class TimelineObject { public async getTimeline() { this.data = this.id != null - ? this.type === TimelineType.template + ? this.type === TimelineTypeEnum.template ? await getTimelineTemplateOrNull(this.frameworkRequest, this.id) : await getTimelineOrNull(this.frameworkRequest, this.id) : null; @@ -54,7 +58,7 @@ export class TimelineObject { } private get isImmutable() { - return this.data?.status === TimelineStatus.immutable; + return this.data?.status === TimelineStatusEnum.immutable; } public get isExists() { @@ -70,7 +74,7 @@ export class TimelineObject { } public get isUpdatableViaImport() { - return this.type === TimelineType.template && this.isExists; + return this.type === TimelineTypeEnum.template && this.isExists; } public get getVersion() { diff --git a/x-pack/plugins/security_solution/server/mocks.ts b/x-pack/plugins/security_solution/server/mocks.ts index 7e71246fd1c2c..1d838476a8325 100644 --- a/x-pack/plugins/security_solution/server/mocks.ts +++ b/x-pack/plugins/security_solution/server/mocks.ts @@ -18,6 +18,9 @@ const createAppClientMock = (): AppClientMock => getAlertsIndex: jest.fn(), getSignalsIndex: jest.fn(), getSourcererDataViewId: jest.fn().mockReturnValue('security-solution'), + getKibanaVersion: jest.fn().mockReturnValue('8.0.0'), + getKibanaBranch: jest.fn().mockReturnValue('main'), + getBuildFlavor: jest.fn().mockReturnValue('traditional'), } as unknown as AppClientMock); export const siemMock = { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0afd5bbfe6057..c24e70baa5db8 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -197,7 +197,9 @@ export class Plugin implements ISecuritySolutionPlugin { initUiSettings(core.uiSettings, experimentalFeatures, config.enableUiSettingsValidations); productFeaturesService.init(plugins.features); - events.forEach((eventConfig) => core.analytics.registerEventType(eventConfig)); + events.forEach((eventConfig) => { + core.analytics.registerEventType(eventConfig); + }); this.ruleMonitoringService.setup(core, plugins); @@ -222,6 +224,7 @@ export class Plugin implements ISecuritySolutionPlugin { ruleMonitoringService: this.ruleMonitoringService, kibanaVersion: pluginContext.env.packageInfo.version, kibanaBranch: pluginContext.env.packageInfo.branch, + buildFlavor: pluginContext.env.packageInfo.buildFlavor, }); productFeaturesService.registerApiAccessControl(core.http); @@ -305,6 +308,7 @@ export class Plugin implements ISecuritySolutionPlugin { version: pluginContext.env.packageInfo.version, experimentalFeatures: config.experimentalFeatures, alerting: plugins.alerting, + analytics: core.analytics, }; const queryRuleAdditionalOptions: CreateQueryRuleAdditionalOptions = { @@ -414,6 +418,7 @@ export class Plugin implements ISecuritySolutionPlugin { config, kibanaVersion: pluginContext.env.packageInfo.version, kibanaBranch: pluginContext.env.packageInfo.branch, + buildFlavor: pluginContext.env.packageInfo.buildFlavor, }); const endpointFieldsStrategy = endpointFieldsProvider( diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index 780b69681caf4..4bda7e0338aa8 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -9,6 +9,7 @@ import { memoize } from 'lodash'; import type { Logger, KibanaRequest, RequestHandlerContext } from '@kbn/core/server'; +import type { BuildFlavor } from '@kbn/config'; import { DEFAULT_SPACE_ID } from '../common/constants'; import { AppClientFactory } from './client'; import type { ConfigType } from './config'; @@ -47,6 +48,7 @@ interface ConstructorOptions { ruleMonitoringService: IRuleMonitoringService; kibanaVersion: string; kibanaBranch: string; + buildFlavor: BuildFlavor; } export class RequestContextFactory implements IRequestContextFactory { @@ -79,6 +81,7 @@ export class RequestContextFactory implements IRequestContextFactory { config, kibanaVersion: options.kibanaVersion, kibanaBranch: options.kibanaBranch, + buildFlavor: options.buildFlavor, }); const getAuditLogger = () => security?.audit.asScoped(request); diff --git a/x-pack/plugins/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index 8aab08ea621d8..ca016d07d5099 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -78,6 +78,68 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled query rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: 'Number of disabled query rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: 'Number of query rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: 'Number of query rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: 'Number of query rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: + 'Number of query rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of query rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of query rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of query rules configured do not suppress alerts with missing fields', + }, + }, + }, }, threshold: { enabled: { @@ -121,6 +183,71 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled threshold rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: 'Number of disabled threshold rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: + 'Number of threshold rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: + 'Number of threshold rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: + 'Number of threshold rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: + 'Number of threshold rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of threshold rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of threshold rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of threshold rules configured do not suppress alerts with missing fields', + }, + }, + }, }, eql: { enabled: { type: 'long', _meta: { description: 'Number of eql rules enabled' } }, @@ -156,6 +283,67 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled eql rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: 'Number of disabled eql rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: 'Number of eql rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: 'Number of eql rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: 'Number of eql rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: 'Number of eql rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of eql rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of eql rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of eql rules configured do not suppress alerts with missing fields', + }, + }, + }, }, machine_learning: { enabled: { @@ -199,6 +387,73 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: + 'Number of enabled machine_learning rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: + 'Number of disabled machine_learning rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: + 'Number of machine_learning rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: + 'Number of machine_learning rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: + 'Number of machine_learning rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: + 'Number of machine_learning rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of machine_learning rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of machine_learning rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of machine_learning rules configured do not suppress alerts with missing fields', + }, + }, + }, }, threat_match: { enabled: { @@ -242,6 +497,72 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled threat_match rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: + 'Number of disabled threat_match rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: + 'Number of threat_match rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: + 'Number of threat_match rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: + 'Number of threat_match rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: + 'Number of threat_match rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of threat_match rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of threat_match rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of threat_match rules configured do not suppress alerts with missing fields', + }, + }, + }, }, new_terms: { enabled: { @@ -285,6 +606,71 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled new_terms rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: 'Number of disabled new_terms rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: + 'Number of new_terms rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: + 'Number of new_terms rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: + 'Number of new_terms rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: + 'Number of new_terms rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of new_terms rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of new_terms rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of new_terms rules configured do not suppress alerts with missing fields', + }, + }, + }, }, esql: { enabled: { @@ -328,6 +714,67 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled esql rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: 'Number of disabled esql rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: 'Number of esql rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: 'Number of esql rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: 'Number of esql rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: 'Number of esql rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of esql rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of esql rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of esql rules configured do not suppress alerts with missing fields', + }, + }, + }, }, elastic_total: { enabled: { type: 'long', _meta: { description: 'Number of elastic rules enabled' } }, @@ -366,6 +813,69 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled elastic rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: 'Number of disabled elastic rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: 'Number of elastic rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: 'Number of elastic rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: + 'Number of elastic rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: + 'Number of elastic rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of elastic rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of elastic rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of elastic rules configured do not suppress alerts with missing fields', + }, + }, + }, }, custom_total: { enabled: { type: 'long', _meta: { description: 'Number of custom rules enabled' } }, @@ -401,6 +911,68 @@ export const registerCollector: RegisterCollector = ({ 'Number of rules using the legacy investigation fields type introduced only in 8.10 ESS', }, }, + alert_suppression: { + enabled: { + type: 'long', + _meta: { + description: 'Number of enabled custom rules configured with suppression', + }, + }, + disabled: { + type: 'long', + _meta: { + description: 'Number of disabled custom rules configured with suppression', + }, + }, + suppressed_fields_count: { + one: { + type: 'long', + _meta: { + description: 'Number of custom rules configured with one suppression field', + }, + }, + two: { + type: 'long', + _meta: { + description: 'Number of custom rules configured with two suppression field', + }, + }, + three: { + type: 'long', + _meta: { + description: 'Number of custom rules configured with three suppression field', + }, + }, + }, + suppressed_per_time_period: { + type: 'long', + _meta: { + description: + 'Number of custom rules configured with suppression per time period', + }, + }, + suppressed_per_rule_execution: { + type: 'long', + _meta: { + description: + 'Number of custom rules configured with suppression per rule execution', + }, + }, + suppresses_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of custom rules configured to suppress alerts with missing fields', + }, + }, + does_not_suppress_missing_fields: { + type: 'long', + _meta: { + description: + 'Number of custom rules configured do not suppress alerts with missing fields', + }, + }, + }, }, }, detection_rule_detail: { diff --git a/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts b/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts index 6c4d3c5a377d9..be5044fbb4e21 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/get_metrics.test.ts @@ -34,7 +34,7 @@ import { } from './rules/get_metrics.mocks'; import { getInitialDetectionMetrics } from './get_initial_usage'; import { getDetectionsMetrics } from './get_metrics'; -import { getInitialRulesUsage } from './rules/get_initial_usage'; +import { getInitialRulesUsage, initialAlertSuppression } from './rules/get_initial_usage'; describe('Detections Usage and Metrics', () => { let esClient: ReturnType<typeof elasticsearchServiceMock.createElasticsearchClient>; @@ -100,6 +100,10 @@ describe('Detections Usage and Metrics', () => { has_legacy_notification: false, has_notification: false, has_legacy_investigation_field: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + alert_suppression_fields_count: 0, }, ], detection_rule_usage: { @@ -114,6 +118,7 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, elastic_total: { alerts: 3400, @@ -125,6 +130,7 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, }, }, @@ -167,6 +173,7 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, query: { alerts: 800, @@ -178,6 +185,7 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, }, }, @@ -211,6 +219,7 @@ describe('Detections Usage and Metrics', () => { detection_rule_detail: [ { alert_count_daily: 0, + alert_suppression_fields_count: 0, cases_count_total: 1, created_on: '2021-03-23T17:15:59.634Z', elastic_rule: true, @@ -223,6 +232,9 @@ describe('Detections Usage and Metrics', () => { has_legacy_notification: false, has_notification: false, has_legacy_investigation_field: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, }, ], detection_rule_usage: { @@ -237,6 +249,7 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, query: { alerts: 0, @@ -248,6 +261,7 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, }, }, diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts index 4313abdc336bd..835f43b05e4f4 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/get_initial_usage.ts @@ -11,8 +11,23 @@ import type { RulesTypeUsage, SingleEventLogStatusMetric, SingleEventMetric, + AlertSuppressionUsage, } from './types'; +export const initialAlertSuppression: AlertSuppressionUsage = { + enabled: 0, + disabled: 0, + suppressed_per_time_period: 0, + suppressed_per_rule_execution: 0, + suppressed_fields_count: { + one: 0, + two: 0, + three: 0, + }, + suppresses_missing_fields: 0, + does_not_suppress_missing_fields: 0, +}; + /** * Default detection rule usage count, split by type + elastic/custom */ @@ -27,6 +42,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, threshold: { enabled: 0, @@ -38,6 +54,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, eql: { enabled: 0, @@ -49,6 +66,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, machine_learning: { enabled: 0, @@ -60,6 +78,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, threat_match: { enabled: 0, @@ -71,6 +90,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, new_terms: { enabled: 0, @@ -82,6 +102,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, esql: { enabled: 0, @@ -93,6 +114,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, elastic_total: { enabled: 0, @@ -104,6 +126,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, custom_total: { enabled: 0, @@ -115,6 +138,7 @@ export const getInitialRulesUsage = (): RulesTypeUsage => ({ notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: initialAlertSuppression, }, }); diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts index 116015b25db9d..7e3dc6ee18169 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_object_correlations.ts @@ -8,6 +8,7 @@ import type { SavedObjectsFindResult } from '@kbn/core/server'; import type { RuleMetric } from '../types'; import type { RuleSearchResult } from '../../../types'; +import { getAlertSuppressionUsage } from '../usage_utils/get_alert_suppression_usage'; export interface RuleObjectCorrelationsOptions { ruleResults: Array<SavedObjectsFindResult<RuleSearchResult>>; @@ -41,6 +42,13 @@ export const getRuleObjectCorrelations = ({ attributes.actions.length > 0 && attributes.muteAll !== true; + const { + hasAlertSuppressionPerRuleExecution, + hasAlertSuppressionPerTimePeriod, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress, + alertSuppressionFieldsCount, + } = getAlertSuppressionUsage(attributes); + return { rule_name: attributes.name, rule_id: attributes.params.ruleId, @@ -56,6 +64,11 @@ export const getRuleObjectCorrelations = ({ has_legacy_notification: hasLegacyNotification, has_notification: hasNotification, has_legacy_investigation_field: Array.isArray(attributes.params.investigationFields), + has_alert_suppression_per_rule_execution: hasAlertSuppressionPerRuleExecution, + has_alert_suppression_per_time_period: hasAlertSuppressionPerTimePeriod, + has_alert_suppression_missing_fields_strategy_do_not_suppress: + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress, + alert_suppression_fields_count: alertSuppressionFieldsCount, }; }); }; diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts index e212ba8a9e15e..bd340d76181d3 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/types.ts @@ -5,6 +5,20 @@ * 2.0. */ +export interface AlertSuppressionUsage { + enabled: number; + disabled: number; + suppressed_fields_count: { + one: number; + two: number; + three: number; + }; + suppressed_per_time_period: number; + suppressed_per_rule_execution: number; + suppresses_missing_fields: number; + does_not_suppress_missing_fields: number; +} + export interface FeatureTypeUsage { enabled: number; disabled: number; @@ -15,6 +29,7 @@ export interface FeatureTypeUsage { notifications_enabled: number; notifications_disabled: number; legacy_investigation_fields: number; + alert_suppression: AlertSuppressionUsage; } export interface RulesTypeUsage { @@ -49,6 +64,10 @@ export interface RuleMetric { has_legacy_notification: boolean; has_notification: boolean; has_legacy_investigation_field: boolean; + has_alert_suppression_per_rule_execution: boolean; + has_alert_suppression_per_time_period: boolean; + has_alert_suppression_missing_fields_strategy_do_not_suppress: boolean; + alert_suppression_fields_count: number; } /** diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/update_usage.test.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/update_usage.test.ts index 3edacf0ae9e1b..b6884222ff2c7 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/update_usage.test.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/update_usage.test.ts @@ -18,6 +18,10 @@ interface StubRuleOptions { hasLegacyNotification: boolean; hasNotification: boolean; hasLegacyInvestigationField: boolean; + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: boolean; + hasAlertSuppressionPerRuleExecution: boolean; + hasAlertSuppressionPerTimePeriod: boolean; + alertSuppressionFieldsCount: number; } const createStubRule = ({ @@ -29,6 +33,10 @@ const createStubRule = ({ hasLegacyNotification, hasNotification, hasLegacyInvestigationField, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress, + hasAlertSuppressionPerRuleExecution, + hasAlertSuppressionPerTimePeriod, + alertSuppressionFieldsCount, }: StubRuleOptions): RuleMetric => ({ rule_name: 'rule-name', rule_id: 'id-123', @@ -43,6 +51,11 @@ const createStubRule = ({ has_legacy_notification: hasLegacyNotification, has_notification: hasNotification, has_legacy_investigation_field: hasLegacyInvestigationField, + has_alert_suppression_missing_fields_strategy_do_not_suppress: + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress, + has_alert_suppression_per_rule_execution: hasAlertSuppressionPerRuleExecution, + has_alert_suppression_per_time_period: hasAlertSuppressionPerTimePeriod, + alert_suppression_fields_count: alertSuppressionFieldsCount, }); describe('Detections Usage and Metrics', () => { @@ -57,6 +70,10 @@ describe('Detections Usage and Metrics', () => { hasLegacyNotification: false, hasNotification: false, hasLegacyInvestigationField: false, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: false, + hasAlertSuppressionPerRuleExecution: true, + hasAlertSuppressionPerTimePeriod: false, + alertSuppressionFieldsCount: 3, }); const usage = updateRuleUsage(stubRule, getInitialRulesUsage()); @@ -72,6 +89,19 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: { + disabled: 0, + does_not_suppress_missing_fields: 0, + enabled: 1, + suppressed_fields_count: { + one: 0, + three: 1, + two: 0, + }, + suppressed_per_rule_execution: 1, + suppressed_per_time_period: 0, + suppresses_missing_fields: 1, + }, }, eql: { alerts: 1, @@ -83,6 +113,19 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: { + disabled: 0, + does_not_suppress_missing_fields: 0, + enabled: 1, + suppressed_fields_count: { + one: 0, + three: 1, + two: 0, + }, + suppressed_per_rule_execution: 1, + suppressed_per_time_period: 0, + suppresses_missing_fields: 1, + }, }, }); }); @@ -97,6 +140,10 @@ describe('Detections Usage and Metrics', () => { hasLegacyNotification: false, hasNotification: false, hasLegacyInvestigationField: false, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: true, + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: false, + alertSuppressionFieldsCount: 0, }); const stubQueryRuleOne = createStubRule({ ruleType: 'query', @@ -107,6 +154,10 @@ describe('Detections Usage and Metrics', () => { hasLegacyNotification: false, hasNotification: false, hasLegacyInvestigationField: true, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: true, + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: false, + alertSuppressionFieldsCount: 0, }); const stubQueryRuleTwo = createStubRule({ ruleType: 'query', @@ -117,6 +168,10 @@ describe('Detections Usage and Metrics', () => { hasLegacyNotification: false, hasNotification: false, hasLegacyInvestigationField: false, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: true, + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: true, + alertSuppressionFieldsCount: 2, }); const stubMachineLearningOne = createStubRule({ ruleType: 'machine_learning', @@ -127,6 +182,10 @@ describe('Detections Usage and Metrics', () => { hasLegacyNotification: false, hasNotification: false, hasLegacyInvestigationField: false, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: true, + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: true, + alertSuppressionFieldsCount: 2, }); const stubMachineLearningTwo = createStubRule({ ruleType: 'machine_learning', @@ -137,6 +196,10 @@ describe('Detections Usage and Metrics', () => { hasLegacyNotification: false, hasNotification: false, hasLegacyInvestigationField: false, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: true, + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: false, + alertSuppressionFieldsCount: 0, }); let usage = updateRuleUsage(stubEqlRule, getInitialRulesUsage()); @@ -157,6 +220,19 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: { + disabled: 1, + does_not_suppress_missing_fields: 2, + enabled: 1, + suppressed_fields_count: { + one: 0, + three: 0, + two: 2, + }, + suppressed_per_rule_execution: 0, + suppressed_per_time_period: 2, + suppresses_missing_fields: 0, + }, }, elastic_total: { alerts: 28, @@ -168,6 +244,19 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 1, + alert_suppression: { + disabled: 0, + does_not_suppress_missing_fields: 0, + enabled: 0, + suppressed_fields_count: { + one: 0, + three: 0, + two: 0, + }, + suppressed_per_rule_execution: 0, + suppressed_per_time_period: 0, + suppresses_missing_fields: 0, + }, }, eql: { alerts: 1, @@ -179,6 +268,19 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: { + disabled: 0, + does_not_suppress_missing_fields: 0, + enabled: 0, + suppressed_fields_count: { + one: 0, + three: 0, + two: 0, + }, + suppressed_per_rule_execution: 0, + suppressed_per_time_period: 0, + suppresses_missing_fields: 0, + }, }, machine_learning: { alerts: 22, @@ -190,6 +292,19 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: { + disabled: 1, + does_not_suppress_missing_fields: 1, + enabled: 0, + suppressed_fields_count: { + one: 0, + three: 0, + two: 1, + }, + suppressed_per_rule_execution: 0, + suppressed_per_time_period: 1, + suppresses_missing_fields: 0, + }, }, query: { alerts: 10, @@ -201,6 +316,19 @@ describe('Detections Usage and Metrics', () => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 1, + alert_suppression: { + disabled: 0, + does_not_suppress_missing_fields: 1, + enabled: 1, + suppressed_fields_count: { + one: 0, + three: 0, + two: 1, + }, + suppressed_per_rule_execution: 0, + suppressed_per_time_period: 1, + suppresses_missing_fields: 0, + }, }, }); }); @@ -279,6 +407,10 @@ describe('Detections Usage and Metrics', () => { alertCount: 0, caseCount: 0, hasLegacyInvestigationField, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: false, + hasAlertSuppressionPerRuleExecution: true, + hasAlertSuppressionPerTimePeriod: false, + alertSuppressionFieldsCount: 3, }); const usage = updateRuleUsage(rule1, getInitialRulesUsage()) as ReturnType< typeof updateRuleUsage @@ -303,6 +435,10 @@ describe('Detections Usage and Metrics', () => { alertCount: 0, caseCount: 0, hasLegacyInvestigationField, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: false, + hasAlertSuppressionPerRuleExecution: true, + hasAlertSuppressionPerTimePeriod: false, + alertSuppressionFieldsCount: 3, }); const usageAddedByOne = updateRuleUsage(rule2, usage) as ReturnType< typeof updateRuleUsage diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/get_alert_suppression_usage.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/get_alert_suppression_usage.ts new file mode 100644 index 0000000000000..a4fab29e0a216 --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/get_alert_suppression_usage.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RuleSearchResult } from '../../../types'; + +export const getAlertSuppressionUsage = ( + ruleAttributes: RuleSearchResult +): { + hasAlertSuppressionPerRuleExecution: boolean; + hasAlertSuppressionPerTimePeriod: boolean; + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: boolean; + alertSuppressionFieldsCount: number; +} => { + if (ruleAttributes.params.alertSuppression == null) { + return { + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: false, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: false, + alertSuppressionFieldsCount: 0, + }; + } + + switch (ruleAttributes.params.type) { + case 'threshold': + return { + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: true, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: false, + alertSuppressionFieldsCount: ruleAttributes.params?.threshold?.field?.length || 0, + }; + case 'query': + case 'saved_query': + case 'new_terms': + case 'threat_match': + case 'machine_learning': + case 'esql': + case 'eql': + return { + hasAlertSuppressionPerRuleExecution: + ruleAttributes.params.alertSuppression.duration == null, + hasAlertSuppressionPerTimePeriod: ruleAttributes.params.alertSuppression.duration != null, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: + ruleAttributes.params.alertSuppression.missingFieldsStrategy === 'doNotSuppress', + alertSuppressionFieldsCount: ruleAttributes.params.alertSuppression.groupBy?.length || 0, + }; + default: + return { + hasAlertSuppressionPerRuleExecution: false, + hasAlertSuppressionPerTimePeriod: false, + hasAlertSuppressionMissingFieldsStrategyDoNotSuppress: false, + alertSuppressionFieldsCount: 0, + }; + } +}; diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_alert_suppression_usage.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_alert_suppression_usage.ts new file mode 100644 index 0000000000000..085e5f4d90ec9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_alert_suppression_usage.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AlertSuppressionUsage, RuleMetric, FeatureTypeUsage } from '../types'; + +export interface UpdateAlertSuppressionUsage { + detectionRuleMetric: RuleMetric; + usage: FeatureTypeUsage; +} + +export const updateAlertSuppressionUsage = ({ + detectionRuleMetric, + usage, +}: UpdateAlertSuppressionUsage): AlertSuppressionUsage => { + const isAlertSuppressionConfigured = + detectionRuleMetric.has_alert_suppression_per_rule_execution || + detectionRuleMetric.has_alert_suppression_per_time_period; + + // if rule does not have suppression configuration alert suppression usage + // returned unchanged + if (!isAlertSuppressionConfigured) { + return usage.alert_suppression; + } + + return { + enabled: detectionRuleMetric.enabled + ? usage.alert_suppression.enabled + 1 + : usage.alert_suppression.enabled, + disabled: !detectionRuleMetric.enabled + ? usage.alert_suppression.disabled + 1 + : usage.alert_suppression.disabled, + suppressed_fields_count: { + one: + detectionRuleMetric.alert_suppression_fields_count === 1 + ? usage.alert_suppression.suppressed_fields_count.one + 1 + : usage.alert_suppression.suppressed_fields_count.one, + two: + detectionRuleMetric.alert_suppression_fields_count === 2 + ? usage.alert_suppression.suppressed_fields_count.two + 1 + : usage.alert_suppression.suppressed_fields_count.two, + three: + detectionRuleMetric.alert_suppression_fields_count === 3 + ? usage.alert_suppression.suppressed_fields_count.three + 1 + : usage.alert_suppression.suppressed_fields_count.three, + }, + suppressed_per_time_period: detectionRuleMetric.has_alert_suppression_per_time_period + ? usage.alert_suppression.suppressed_per_time_period + 1 + : usage.alert_suppression.suppressed_per_time_period, + suppressed_per_rule_execution: detectionRuleMetric.has_alert_suppression_per_rule_execution + ? usage.alert_suppression.suppressed_per_rule_execution + 1 + : usage.alert_suppression.suppressed_per_rule_execution, + suppresses_missing_fields: + !detectionRuleMetric.has_alert_suppression_missing_fields_strategy_do_not_suppress + ? usage.alert_suppression.suppresses_missing_fields + 1 + : usage.alert_suppression.suppresses_missing_fields, + does_not_suppress_missing_fields: + detectionRuleMetric.has_alert_suppression_missing_fields_strategy_do_not_suppress + ? usage.alert_suppression.does_not_suppress_missing_fields + 1 + : usage.alert_suppression.does_not_suppress_missing_fields, + }; +}; diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_query_usage.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_query_usage.ts index e6c4e897b0b9b..5245d2c9abeeb 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_query_usage.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_query_usage.ts @@ -7,6 +7,7 @@ import type { RulesTypeUsage, RuleMetric, FeatureTypeUsage } from '../types'; import { getNotificationsEnabledDisabled } from './get_notifications_enabled_disabled'; +import { updateAlertSuppressionUsage } from './update_alert_suppression_usage'; export interface UpdateQueryUsageOptions { ruleType: keyof RulesTypeUsage; @@ -47,5 +48,6 @@ export const updateQueryUsage = ({ legacy_investigation_fields: detectionRuleMetric.has_legacy_investigation_field ? usage[ruleType].legacy_investigation_fields + 1 : usage[ruleType].legacy_investigation_fields, + alert_suppression: updateAlertSuppressionUsage({ usage: usage[ruleType], detectionRuleMetric }), }; }; diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_total_usage.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_total_usage.ts index 3a63af1ce081a..c7ce17f8ab0e3 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_total_usage.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/usage_utils/update_total_usage.ts @@ -7,6 +7,7 @@ import type { RulesTypeUsage, RuleMetric, FeatureTypeUsage } from '../types'; import { getNotificationsEnabledDisabled } from './get_notifications_enabled_disabled'; +import { updateAlertSuppressionUsage } from './update_alert_suppression_usage'; export interface UpdateTotalUsageOptions { detectionRuleMetric: RuleMetric; @@ -50,5 +51,9 @@ export const updateTotalUsage = ({ legacy_investigation_fields: detectionRuleMetric.has_legacy_investigation_field ? updatedUsage[totalType].legacy_investigation_fields + 1 : updatedUsage[totalType].legacy_investigation_fields, + alert_suppression: updateAlertSuppressionUsage({ + usage: updatedUsage[totalType], + detectionRuleMetric, + }), }; }; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index ba89ca2864d74..bdaf656b9c986 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -208,5 +208,8 @@ "@kbn/core-theme-browser", "@kbn/integration-assistant-plugin", "@kbn/avc-banner", + "@kbn/esql-ast", + "@kbn/esql-validation-autocomplete", + "@kbn/config", ] } diff --git a/x-pack/plugins/serverless_observability/public/plugin.ts b/x-pack/plugins/serverless_observability/public/plugin.ts index b7c99e9765dac..25cb2dae38192 100644 --- a/x-pack/plugins/serverless_observability/public/plugin.ts +++ b/x-pack/plugins/serverless_observability/public/plugin.ts @@ -59,12 +59,13 @@ export class ServerlessObservabilityPlugin observabilityAiAssistantManagement: { category: appCategories.OTHER, title: i18n.translate('xpack.serverlessObservability.aiAssistantManagementTitle', { - defaultMessage: 'AI assistant for Observability settings', + defaultMessage: 'AI Assistant for Observability Settings', }), description: i18n.translate( 'xpack.serverlessObservability.aiAssistantManagementDescription', { - defaultMessage: 'Manage your AI assistant for Observability settings.', + defaultMessage: + 'Manage knowledge base and control assistant behavior, including response language.', } ), icon: 'sparkles', diff --git a/x-pack/plugins/spaces/common/index.ts b/x-pack/plugins/spaces/common/index.ts index 4a767fb403ee2..65342bf2e43f4 100644 --- a/x-pack/plugins/spaces/common/index.ts +++ b/x-pack/plugins/spaces/common/index.ts @@ -18,5 +18,6 @@ export type { GetAllSpacesOptions, GetAllSpacesPurpose, GetSpaceResult, + SolutionView, } from './types/latest'; export { spaceV1 } from './types'; diff --git a/x-pack/plugins/spaces/common/types/space/v1.ts b/x-pack/plugins/spaces/common/types/space/v1.ts index 9f110dc3098e3..9ba2deb09aaa2 100644 --- a/x-pack/plugins/spaces/common/types/space/v1.ts +++ b/x-pack/plugins/spaces/common/types/space/v1.ts @@ -7,6 +7,8 @@ import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common'; +export type SolutionView = OnBoardingDefaultSolution | 'classic'; + /** * A Space. */ @@ -64,7 +66,7 @@ export interface Space { /** * Solution selected for this space. */ - solution?: OnBoardingDefaultSolution | 'classic'; + solution?: SolutionView; } /** diff --git a/x-pack/plugins/spaces/kibana.jsonc b/x-pack/plugins/spaces/kibana.jsonc index 3c32314168417..f59caa16837c3 100644 --- a/x-pack/plugins/spaces/kibana.jsonc +++ b/x-pack/plugins/spaces/kibana.jsonc @@ -20,7 +20,6 @@ "management", "usageCollection", "cloud", - "cloudExperiments" ], "requiredBundles": [ "esUiShared", diff --git a/x-pack/plugins/spaces/public/analytics/event_tracker.ts b/x-pack/plugins/spaces/public/analytics/event_tracker.ts new file mode 100644 index 0000000000000..ec936d1c1ef20 --- /dev/null +++ b/x-pack/plugins/spaces/public/analytics/event_tracker.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 type { AnalyticsServiceStart } from '@kbn/core/public'; + +import type { SolutionView } from '../../common'; + +export enum EventType { + SPACE_SOLUTION_CHANGED = 'space_solution_changed', + SPACE_CHANGED = 'space_changed', +} + +export enum FieldType { + ACTION = 'action', + SPACE_ID = 'space_id', + SPACE_ID_PREV = 'space_id_prev', + SOLUTION = 'solution', + SOLUTION_PREV = 'solution_prev', +} + +export class EventTracker { + constructor(private analytics: Pick<AnalyticsServiceStart, 'reportEvent'>) {} + + private track(eventType: string, eventFields: object) { + try { + this.analytics.reportEvent(eventType, eventFields); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } + } + + /** + * Track whenever the space "solution" is changed. + */ + public spaceSolutionChanged({ + spaceId, + action, + solution, + solutionPrev, + }: { + spaceId: string; + action: 'create' | 'edit'; + solution: SolutionView; + solutionPrev?: SolutionView; + }) { + this.track(EventType.SPACE_SOLUTION_CHANGED, { + [FieldType.SPACE_ID]: spaceId, + [FieldType.SOLUTION]: solution, + [FieldType.SOLUTION_PREV]: solutionPrev, + [FieldType.ACTION]: action, + }); + } + + /** + * Track whenever the user changes space. + */ + public changeSpace({ + prevSpaceId, + prevSolution, + nextSpaceId, + nextSolution, + }: { + prevSpaceId: string; + prevSolution?: SolutionView; + nextSpaceId: string; + nextSolution?: SolutionView; + }) { + this.track(EventType.SPACE_CHANGED, { + [FieldType.SPACE_ID]: nextSpaceId, + [FieldType.SPACE_ID_PREV]: prevSpaceId, + [FieldType.SOLUTION]: nextSolution, + [FieldType.SOLUTION_PREV]: prevSolution, + }); + } +} diff --git a/x-pack/plugins/spaces/public/analytics/index.ts b/x-pack/plugins/spaces/public/analytics/index.ts new file mode 100644 index 0000000000000..a7d363f988aa2 --- /dev/null +++ b/x-pack/plugins/spaces/public/analytics/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 { registerAnalyticsContext } from './register_analytics_context'; + +export { EventTracker } from './event_tracker'; + +export { registerSpacesEventTypes } from './register_event_types'; diff --git a/x-pack/plugins/spaces/public/analytics/register_analytics_context.ts b/x-pack/plugins/spaces/public/analytics/register_analytics_context.ts new file mode 100644 index 0000000000000..8f6f319d3333c --- /dev/null +++ b/x-pack/plugins/spaces/public/analytics/register_analytics_context.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 { Observable } from 'rxjs'; +import { map } from 'rxjs'; + +import type { AnalyticsClient } from '@kbn/core-analytics-browser'; + +import type { SolutionView, Space } from '../../common'; + +export interface SpaceMetadata { + spaceSolutionView?: SolutionView; +} + +export function registerAnalyticsContext( + analytics: Pick<AnalyticsClient, 'registerContextProvider'>, + activeSpace: Observable<Space> +) { + analytics.registerContextProvider({ + name: 'Spaces Metadata', + context$: activeSpace.pipe(map((space) => ({ spaceSolution: space.solution }))), + schema: { + spaceSolution: { + type: 'keyword', + _meta: { description: 'The Space solution view', optional: true }, + }, + }, + }); +} diff --git a/x-pack/plugins/spaces/public/analytics/register_event_types.ts b/x-pack/plugins/spaces/public/analytics/register_event_types.ts new file mode 100644 index 0000000000000..701fdadb3c4ee --- /dev/null +++ b/x-pack/plugins/spaces/public/analytics/register_event_types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, EventTypeOpts, RootSchema } from '@kbn/core/public'; + +import { EventType, FieldType } from './event_tracker'; + +const fields: Record<FieldType, RootSchema<Record<string, unknown>>> = { + [FieldType.SPACE_ID]: { + [FieldType.SPACE_ID]: { + type: 'keyword', + _meta: { + description: 'The ID of the space.', + }, + }, + }, + [FieldType.SPACE_ID_PREV]: { + [FieldType.SPACE_ID_PREV]: { + type: 'keyword', + _meta: { + description: 'The previous ID of the space (before switching space).', + }, + }, + }, + [FieldType.SOLUTION]: { + [FieldType.SOLUTION]: { + type: 'keyword', + _meta: { + description: 'The solution set for the space.', + }, + }, + }, + [FieldType.SOLUTION_PREV]: { + [FieldType.SOLUTION_PREV]: { + type: 'keyword', + _meta: { + description: 'The previous solution value before editing the space.', + optional: true, + }, + }, + }, + [FieldType.ACTION]: { + [FieldType.ACTION]: { + type: 'keyword', + _meta: { + description: 'The user action, either create or edit a space.', + }, + }, + }, +}; + +const eventTypes: Array<EventTypeOpts<Record<string, unknown>>> = [ + { + eventType: EventType.SPACE_SOLUTION_CHANGED, + schema: { + ...fields[FieldType.SPACE_ID], + ...fields[FieldType.SOLUTION_PREV], + ...fields[FieldType.SOLUTION], + ...fields[FieldType.ACTION], + }, + }, + { + eventType: EventType.SPACE_CHANGED, + schema: { + ...fields[FieldType.SPACE_ID], + ...fields[FieldType.SPACE_ID_PREV], + ...fields[FieldType.SOLUTION_PREV], + ...fields[FieldType.SOLUTION], + }, + }, +]; + +export function registerSpacesEventTypes(core: CoreSetup) { + const { analytics } = core; + for (const eventType of eventTypes) { + analytics.registerEventType(eventType); + } +} diff --git a/x-pack/plugins/spaces/public/config.ts b/x-pack/plugins/spaces/public/config.ts index 567c01c66cfe1..dcd203eb696e3 100644 --- a/x-pack/plugins/spaces/public/config.ts +++ b/x-pack/plugins/spaces/public/config.ts @@ -8,4 +8,5 @@ export interface ConfigType { maxSpaces: number; allowFeatureVisibility: boolean; + allowSolutionVisibility: boolean; } diff --git a/x-pack/plugins/spaces/public/experiments/index.ts b/x-pack/plugins/spaces/public/experiments/index.ts deleted file mode 100644 index 454fb8980a6d7..0000000000000 --- a/x-pack/plugins/spaces/public/experiments/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 { isSolutionNavEnabled } from './is_solution_nav_enabled'; diff --git a/x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts b/x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.ts deleted file mode 100644 index 5351c20c99d0f..0000000000000 --- a/x-pack/plugins/spaces/public/experiments/is_solution_nav_enabled.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 type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; -import type { CloudStart } from '@kbn/cloud-plugin/public'; - -const SOLUTION_NAV_FEATURE_FLAG_NAME = 'solutionNavEnabled'; - -export const isSolutionNavEnabled = ( - cloud?: CloudStart, - cloudExperiments?: CloudExperimentsPluginStart -) => { - return Boolean(cloud?.isCloudEnabled) && cloudExperiments - ? cloudExperiments.getVariation(SOLUTION_NAV_FEATURE_FLAG_NAME, false) - : Promise.resolve(false); -}; diff --git a/x-pack/plugins/spaces/public/management/edit_space/index.ts b/x-pack/plugins/spaces/public/management/edit_space/index.ts index fa27a38ab5855..78c3b0fc42e04 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/index.ts +++ b/x-pack/plugins/spaces/public/management/edit_space/index.ts @@ -5,5 +5,4 @@ * 2.0. */ -// @ts-ignore export { ManageSpacePage } from './manage_space_page'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index 2bcf35ccc6cc4..3b82be30c6026 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -10,6 +10,7 @@ import { EuiButton } from '@elastic/eui'; import { waitFor } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; +import { act } from 'react-dom/test-utils'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { notificationServiceMock, scopedHistoryMock } from '@kbn/core/public/mocks'; @@ -20,6 +21,8 @@ import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { EnabledFeatures } from './enabled_features'; import { ManageSpacePage } from './manage_space_page'; +import type { SolutionView, Space } from '../../../common/types/latest'; +import { EventTracker } from '../../analytics'; import type { SpacesManager } from '../../spaces_manager'; import { spacesManagerMock } from '../../spaces_manager/mocks'; @@ -31,7 +34,7 @@ jest.mock('@elastic/eui/lib/components/overlay_mask', () => { }; }); -const space = { +const space: Space = { id: 'my-space', name: 'My Space', disabledFeatures: [], @@ -48,6 +51,9 @@ featuresStart.getFeatures.mockResolvedValue([ }), ]); +const reportEvent = jest.fn(); +const eventTracker = new EventTracker({ reportEvent }); + describe('ManageSpacePage', () => { beforeAll(() => { Object.defineProperty(window, 'location', { @@ -75,7 +81,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility + allowSolutionVisibility /> ); @@ -105,7 +113,7 @@ describe('ManageSpacePage', () => { }); }); - it('shows solution view select when enabled', async () => { + it('shows solution view select when visible', async () => { const spacesManager = spacesManagerMock.create(); spacesManager.createSpace = jest.fn(spacesManager.createSpace); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); @@ -123,7 +131,8 @@ describe('ManageSpacePage', () => { spaces: { manage: true }, }} allowFeatureVisibility - solutionNavExperiment={Promise.resolve(true)} + allowSolutionVisibility + eventTracker={eventTracker} /> ); @@ -135,61 +144,35 @@ describe('ManageSpacePage', () => { expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(1); }); - it('hides solution view select when not enabled or undefined', async () => { + it('hides solution view select when not visible', async () => { const spacesManager = spacesManagerMock.create(); spacesManager.createSpace = jest.fn(spacesManager.createSpace); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); - { - const wrapper = mountWithIntl( - <ManageSpacePage - spacesManager={spacesManager as unknown as SpacesManager} - getFeatures={featuresStart.getFeatures} - notifications={notificationServiceMock.createStartContract()} - history={history} - capabilities={{ - navLinks: {}, - management: {}, - catalogue: {}, - spaces: { manage: true }, - }} - allowFeatureVisibility - /> - ); - - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('input[name="name"]')).toHaveLength(1); - }); + const wrapper = mountWithIntl( + <ManageSpacePage + spacesManager={spacesManager as unknown as SpacesManager} + getFeatures={featuresStart.getFeatures} + notifications={notificationServiceMock.createStartContract()} + history={history} + capabilities={{ + navLinks: {}, + management: {}, + catalogue: {}, + spaces: { manage: true }, + }} + allowFeatureVisibility + allowSolutionVisibility={false} + eventTracker={eventTracker} + /> + ); - expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(0); - } - - { - const wrapper = mountWithIntl( - <ManageSpacePage - spacesManager={spacesManager as unknown as SpacesManager} - getFeatures={featuresStart.getFeatures} - notifications={notificationServiceMock.createStartContract()} - history={history} - capabilities={{ - navLinks: {}, - management: {}, - catalogue: {}, - spaces: { manage: true }, - }} - allowFeatureVisibility - solutionNavExperiment={Promise.resolve(false)} - /> - ); - - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('input[name="name"]')).toHaveLength(1); - }); + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('input[name="name"]')).toHaveLength(1); + }); - expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(0); - } + expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(0); }); it('shows feature visibility controls when allowed', async () => { @@ -209,7 +192,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility + allowSolutionVisibility /> ); @@ -238,7 +223,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility={false} + allowSolutionVisibility /> ); @@ -258,6 +245,7 @@ describe('ManageSpacePage', () => { color: '#aabbcc', initials: 'AB', disabledFeatures: [], + solution: 'es', }; const spacesManager = spacesManagerMock.create(); @@ -282,7 +270,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility + allowSolutionVisibility /> ); @@ -299,7 +289,7 @@ describe('ManageSpacePage', () => { wrapper.update(); - updateSpace(wrapper); + updateSpace(wrapper, true, 'oblt'); await clickSaveButton(wrapper); @@ -311,6 +301,14 @@ describe('ManageSpacePage', () => { initials: 'AB', imageUrl: '', disabledFeatures: ['feature-1'], + solution: 'oblt', // solution has been changed + }); + + expect(reportEvent).toHaveBeenCalledWith('space_solution_changed', { + action: 'edit', + solution: 'oblt', + solution_prev: 'es', + space_id: 'existing-space', }); }); @@ -350,7 +348,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility + allowSolutionVisibility /> ); @@ -399,7 +399,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility + allowSolutionVisibility /> ); @@ -436,7 +438,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility + allowSolutionVisibility /> ); @@ -497,7 +501,9 @@ describe('ManageSpacePage', () => { catalogue: {}, spaces: { manage: true }, }} + eventTracker={eventTracker} allowFeatureVisibility + allowSolutionVisibility /> ); @@ -521,7 +527,11 @@ describe('ManageSpacePage', () => { }); }); -function updateSpace(wrapper: ReactWrapper<any, any>, updateFeature = true) { +function updateSpace( + wrapper: ReactWrapper<any, any>, + updateFeature = true, + solution?: SolutionView +) { const nameInput = wrapper.find('input[name="name"]'); const descriptionInput = wrapper.find('textarea[name="description"]'); @@ -531,6 +541,16 @@ function updateSpace(wrapper: ReactWrapper<any, any>, updateFeature = true) { if (updateFeature) { toggleFeature(wrapper); } + + if (solution) { + act(() => { + findTestSubject(wrapper, `solutionViewSelect`).simulate('click'); + }); + wrapper.update(); + findTestSubject(wrapper, `solutionView${capitalizeFirstLetter(solution)}Option`).simulate( + 'click' + ); + } } function toggleFeature(wrapper: ReactWrapper<any, any>) { @@ -552,3 +572,7 @@ async function clickSaveButton(wrapper: ReactWrapper<any, any>) { wrapper.update(); } + +function capitalizeFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx index 7594778062857..b56a4f6434047 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -33,6 +33,7 @@ import { EnabledFeatures } from './enabled_features'; import { SolutionView } from './solution_view'; import type { Space } from '../../../common'; import { isReservedSpace } from '../../../common'; +import type { EventTracker } from '../../analytics'; import { getSpacesFeatureDescription } from '../../constants'; import { getSpaceColor, getSpaceInitials } from '../../space_avatar'; import type { SpacesManager } from '../../spaces_manager'; @@ -56,7 +57,8 @@ interface Props { capabilities: Capabilities; history: ScopedHistory; allowFeatureVisibility: boolean; - solutionNavExperiment?: Promise<boolean>; + allowSolutionVisibility: boolean; + eventTracker: EventTracker; } interface State { @@ -72,7 +74,6 @@ interface State { isInvalid: boolean; error?: string; }; - isSolutionNavEnabled: boolean; } export class ManageSpacePage extends Component<Props, State> { @@ -89,7 +90,6 @@ export class ManageSpacePage extends Component<Props, State> { color: getSpaceColor({}), }, features: [], - isSolutionNavEnabled: false, haveDisabledFeaturesChanged: false, hasSolutionViewChanged: false, }; @@ -116,10 +116,6 @@ export class ManageSpacePage extends Component<Props, State> { }), }); } - - this.props.solutionNavExperiment?.then((isEnabled) => { - this.setState({ isSolutionNavEnabled: isEnabled }); - }); } public async componentDidUpdate(previousProps: Props, prevState: State) { @@ -199,7 +195,7 @@ export class ManageSpacePage extends Component<Props, State> { validator={this.validator} /> - {this.state.isSolutionNavEnabled && ( + {!!this.props.allowSolutionVisibility && ( <> <EuiSpacer size="l" /> <SolutionView space={this.state.space} onChange={this.onSpaceChange} /> @@ -454,14 +450,30 @@ export class ManageSpacePage extends Component<Props, State> { }; let action; - if (this.editingExistingSpace()) { - action = this.props.spacesManager.updateSpace(params); + const isEditing = this.editingExistingSpace(); + const { spacesManager, eventTracker } = this.props; + + if (isEditing) { + action = spacesManager.updateSpace(params); } else { - action = this.props.spacesManager.createSpace(params); + action = spacesManager.createSpace(params); } this.setState({ saveInProgress: true }); + const trackSpaceSolutionChange = () => { + const hasChangedSolution = this.state.originalSpace?.solution !== solution; + + if (!hasChangedSolution || solution === undefined) return; + + eventTracker.spaceSolutionChanged({ + spaceId: id, + solution, + solutionPrev: this.state.originalSpace?.solution, + action: isEditing ? 'edit' : 'create', + }); + }; + action .then(() => { this.props.notifications.toasts.addSuccess( @@ -474,11 +486,15 @@ export class ManageSpacePage extends Component<Props, State> { ) ); + trackSpaceSolutionChange(); this.backToSpacesList(); if (requireRefresh) { - setTimeout(() => { - window.location.reload(); + const flushAnalyticsEvents = window.__kbnAnalytics?.flush ?? (() => Promise.resolve()); + flushAnalyticsEvents().then(() => { + setTimeout(() => { + window.location.reload(); + }); }); } }) diff --git a/x-pack/plugins/spaces/public/management/management_service.test.ts b/x-pack/plugins/spaces/public/management/management_service.test.ts index 2eaacfda7c3a9..e01ddd8db9ae2 100644 --- a/x-pack/plugins/spaces/public/management/management_service.test.ts +++ b/x-pack/plugins/spaces/public/management/management_service.test.ts @@ -12,14 +12,18 @@ import { managementPluginMock } from '@kbn/management-plugin/public/mocks'; import { ManagementService } from './management_service'; import { getRolesAPIClientMock } from './roles_api_client.mock'; +import { EventTracker } from '../analytics'; import type { ConfigType } from '../config'; import type { PluginsStart } from '../plugin'; import { spacesManagerMock } from '../spaces_manager/mocks'; +const eventTracker = new EventTracker({ reportEvent: jest.fn() }); + describe('ManagementService', () => { const config: ConfigType = { maxSpaces: 1000, allowFeatureVisibility: true, + allowSolutionVisibility: true, }; describe('#setup', () => { @@ -38,7 +42,7 @@ describe('ManagementService', () => { spacesManager: spacesManagerMock.create(), config, getRolesAPIClient: getRolesAPIClientMock, - solutionNavExperiment: Promise.resolve(false), + eventTracker, }); expect(mockKibanaSection.registerApp).toHaveBeenCalledTimes(1); @@ -59,7 +63,7 @@ describe('ManagementService', () => { spacesManager: spacesManagerMock.create(), config, getRolesAPIClient: getRolesAPIClientMock, - solutionNavExperiment: Promise.resolve(false), + eventTracker, }); }); }); @@ -81,7 +85,7 @@ describe('ManagementService', () => { spacesManager: spacesManagerMock.create(), config, getRolesAPIClient: jest.fn(), - solutionNavExperiment: Promise.resolve(false), + eventTracker, }); service.stop(); diff --git a/x-pack/plugins/spaces/public/management/management_service.tsx b/x-pack/plugins/spaces/public/management/management_service.tsx index 143aebba39d96..9bc94e70b83f5 100644 --- a/x-pack/plugins/spaces/public/management/management_service.tsx +++ b/x-pack/plugins/spaces/public/management/management_service.tsx @@ -10,6 +10,7 @@ import type { ManagementApp, ManagementSetup } from '@kbn/management-plugin/publ import type { RolesAPIClient } from '@kbn/security-plugin-types-public'; import { spacesManagementApp } from './spaces_management_app'; +import type { EventTracker } from '../analytics'; import type { ConfigType } from '../config'; import type { PluginsStart } from '../plugin'; import type { SpacesManager } from '../spaces_manager'; @@ -20,7 +21,7 @@ interface SetupDeps { spacesManager: SpacesManager; config: ConfigType; getRolesAPIClient: () => Promise<RolesAPIClient>; - solutionNavExperiment: Promise<boolean>; + eventTracker: EventTracker; } export class ManagementService { @@ -32,7 +33,7 @@ export class ManagementService { spacesManager, config, getRolesAPIClient, - solutionNavExperiment, + eventTracker, }: SetupDeps) { this.registeredSpacesManagementApp = management.sections.section.kibana.registerApp( spacesManagementApp.create({ @@ -40,7 +41,7 @@ export class ManagementService { spacesManager, config, getRolesAPIClient, - solutionNavExperiment, + eventTracker, }) ); } diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx index ee0bda60373a7..e8239e85f9ec3 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx @@ -58,6 +58,11 @@ featuresStart.getFeatures.mockResolvedValue([ }), ]); +const spacesGridCommonProps = { + serverBasePath: '', + maxSpaces: 1000, +}; + describe('SpacesGridPage', () => { const getUrlForApp = (appId: string) => appId; const history = scopedHistoryMock.create(); @@ -79,7 +84,8 @@ describe('SpacesGridPage', () => { catalogue: {}, spaces: { manage: true }, }} - maxSpaces={1000} + allowSolutionVisibility={false} + {...spacesGridCommonProps} /> ); @@ -138,8 +144,8 @@ describe('SpacesGridPage', () => { catalogue: {}, spaces: { manage: true }, }} - maxSpaces={1000} - solutionNavExperiment={Promise.resolve(true)} + allowSolutionVisibility + {...spacesGridCommonProps} /> ); @@ -156,6 +162,103 @@ describe('SpacesGridPage', () => { }); }); + it('renders a "current" badge for the current space', async () => { + spacesManager.getActiveSpace.mockResolvedValue(spaces[2]); + const current = await spacesManager.getActiveSpace(); + expect(current.id).toBe('custom-2'); + + const wrapper = mountWithIntl( + <SpacesGridPage + spacesManager={spacesManager as unknown as SpacesManager} + getFeatures={featuresStart.getFeatures} + notifications={notificationServiceMock.createStartContract()} + getUrlForApp={getUrlForApp} + history={history} + capabilities={{ + navLinks: {}, + management: {}, + catalogue: {}, + spaces: { manage: true }, + }} + allowSolutionVisibility + {...spacesGridCommonProps} + /> + ); + + // allow spacesManager to load spaces and lazy-load SpaceAvatar + await act(async () => {}); + wrapper.update(); + + const activeRow = wrapper.find('[data-test-subj="spacesListTableRow-custom-2"]'); + const nameCell = activeRow.find('[data-test-subj="spacesListTableRowNameCell"]'); + const activeBadge = nameCell.find('EuiBadge'); + expect(activeBadge.text()).toBe('current'); + }); + + it('renders a non-clickable "switch" action for the current space', async () => { + spacesManager.getActiveSpace.mockResolvedValue(spaces[2]); + const current = await spacesManager.getActiveSpace(); + expect(current.id).toBe('custom-2'); + + const wrapper = mountWithIntl( + <SpacesGridPage + spacesManager={spacesManager as unknown as SpacesManager} + getFeatures={featuresStart.getFeatures} + notifications={notificationServiceMock.createStartContract()} + getUrlForApp={getUrlForApp} + history={history} + capabilities={{ + navLinks: {}, + management: {}, + catalogue: {}, + spaces: { manage: true }, + }} + allowSolutionVisibility + {...spacesGridCommonProps} + /> + ); + + // allow spacesManager to load spaces and lazy-load SpaceAvatar + await act(async () => {}); + wrapper.update(); + + const activeRow = wrapper.find('[data-test-subj="spacesListTableRow-custom-2"]'); + const switchAction = activeRow.find('EuiButtonIcon[data-test-subj="Custom 2-switchSpace"]'); + expect(switchAction.prop('isDisabled')).toBe(true); + }); + + it('renders a clickable "switch" action for the non-current space', async () => { + spacesManager.getActiveSpace.mockResolvedValue(spaces[2]); + const current = await spacesManager.getActiveSpace(); + expect(current.id).toBe('custom-2'); + + const wrapper = mountWithIntl( + <SpacesGridPage + spacesManager={spacesManager as unknown as SpacesManager} + getFeatures={featuresStart.getFeatures} + notifications={notificationServiceMock.createStartContract()} + getUrlForApp={getUrlForApp} + history={history} + capabilities={{ + navLinks: {}, + management: {}, + catalogue: {}, + spaces: { manage: true }, + }} + allowSolutionVisibility + {...spacesGridCommonProps} + /> + ); + + // allow spacesManager to load spaces and lazy-load SpaceAvatar + await act(async () => {}); + wrapper.update(); + + const nonActiveRow = wrapper.find('[data-test-subj="spacesListTableRow-default"]'); + const switchAction = nonActiveRow.find('EuiButtonIcon[data-test-subj="Default-switchSpace"]'); + expect(switchAction.prop('isDisabled')).toBe(false); + }); + it('renders a create spaces button', async () => { const httpStart = httpServiceMock.createStartContract(); httpStart.get.mockResolvedValue([]); @@ -173,7 +276,8 @@ describe('SpacesGridPage', () => { catalogue: {}, spaces: { manage: true }, }} - maxSpaces={1000} + allowSolutionVisibility + {...spacesGridCommonProps} /> ); @@ -203,6 +307,8 @@ describe('SpacesGridPage', () => { spaces: { manage: true }, }} maxSpaces={1} + allowSolutionVisibility + serverBasePath={spacesGridCommonProps.serverBasePath} /> ); @@ -236,7 +342,8 @@ describe('SpacesGridPage', () => { catalogue: {}, spaces: { manage: true }, }} - maxSpaces={1000} + allowSolutionVisibility + {...spacesGridCommonProps} /> ); @@ -271,7 +378,8 @@ describe('SpacesGridPage', () => { catalogue: {}, spaces: { manage: true }, }} - maxSpaces={1000} + allowSolutionVisibility + {...spacesGridCommonProps} /> ); diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx index c6a321b3bf484..95ef9a563162f 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import type { EuiBasicTableColumn } from '@elastic/eui'; import { + EuiBadge, + type EuiBasicTableColumn, EuiButton, - EuiButtonIcon, EuiCallOut, + EuiFlexGroup, + EuiFlexItem, EuiInMemoryTable, EuiLink, EuiLoadingSpinner, @@ -31,9 +33,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; -import type { Space } from '../../../common'; +import { addSpaceIdToPath, type Space } from '../../../common'; import { isReservedSpace } from '../../../common'; -import { DEFAULT_SPACE_ID } from '../../../common/constants'; +import { DEFAULT_SPACE_ID, ENTER_SPACE_PATH } from '../../../common/constants'; import { getSpacesFeatureDescription } from '../../constants'; import { getSpaceAvatarComponent } from '../../space_avatar'; import { SpaceSolutionBadge } from '../../space_solution_badge'; @@ -49,21 +51,22 @@ const LazySpaceAvatar = lazy(() => interface Props { spacesManager: SpacesManager; notifications: NotificationsStart; + serverBasePath: string; getFeatures: FeaturesPluginStart['getFeatures']; capabilities: Capabilities; history: ScopedHistory; getUrlForApp: ApplicationStart['getUrlForApp']; maxSpaces: number; - solutionNavExperiment?: Promise<boolean>; + allowSolutionVisibility: boolean; } interface State { spaces: Space[]; + activeSpace: Space | null; features: KibanaFeature[]; loading: boolean; showConfirmDeleteModal: boolean; selectedSpace: Space | null; - isSolutionNavEnabled: boolean; } export class SpacesGridPage extends Component<Props, State> { @@ -71,11 +74,11 @@ export class SpacesGridPage extends Component<Props, State> { super(props); this.state = { spaces: [], + activeSpace: null, features: [], loading: true, showConfirmDeleteModal: false, selectedSpace: null, - isSolutionNavEnabled: false, }; } @@ -83,10 +86,6 @@ export class SpacesGridPage extends Component<Props, State> { if (this.props.capabilities.spaces.manage) { this.loadGrid(); } - - this.props.solutionNavExperiment?.then((isEnabled) => { - this.setState({ isSolutionNavEnabled: isEnabled }); - }); } public render() { @@ -133,11 +132,15 @@ export class SpacesGridPage extends Component<Props, State> { ) : undefined} <EuiInMemoryTable itemId={'id'} + data-test-subj="spacesListTable" items={this.state.spaces} tableCaption={i18n.translate('xpack.spaces.management.spacesGridPage.tableCaption', { defaultMessage: 'Kibana spaces', })} rowHeader="name" + rowProps={(item) => ({ + 'data-test-subj': `spacesListTableRow-${item.id}`, + })} columns={this.getColumnConfig()} pagination={true} sorting={true} @@ -221,12 +224,18 @@ export class SpacesGridPage extends Component<Props, State> { }); const getSpaces = spacesManager.getSpaces(); + const getActiveSpace = spacesManager.getActiveSpace(); try { - const [spaces, features] = await Promise.all([getSpaces, getFeatures()]); + const [spaces, activeSpace, features] = await Promise.all([ + getSpaces, + getActiveSpace, + getFeatures(), + ]); this.setState({ loading: false, spaces, + activeSpace, features, }); } catch (error) { @@ -247,11 +256,13 @@ export class SpacesGridPage extends Component<Props, State> { field: 'initials', name: '', width: '50px', - render: (value: string, record: Space) => { + render: (_value: string, rowRecord) => { return ( <Suspense fallback={<EuiLoadingSpinner />}> - <EuiLink {...reactRouterNavigate(this.props.history, this.getEditSpacePath(record))}> - <LazySpaceAvatar space={record} size="s" /> + <EuiLink + {...reactRouterNavigate(this.props.history, this.getEditSpacePath(rowRecord))} + > + <LazySpaceAvatar space={rowRecord} size="s" /> </EuiLink> </Suspense> ); @@ -263,11 +274,28 @@ export class SpacesGridPage extends Component<Props, State> { defaultMessage: 'Space', }), sortable: true, - render: (value: string, record: Space) => ( - <EuiLink {...reactRouterNavigate(this.props.history, this.getEditSpacePath(record))}> - {value} - </EuiLink> + render: (value: string, rowRecord: Space) => ( + <EuiFlexGroup responsive={false} alignItems="center" gutterSize="m"> + <EuiFlexItem grow={false}> + <EuiLink + {...reactRouterNavigate(this.props.history, this.getEditSpacePath(rowRecord))} + data-test-subj={`${rowRecord.id}-hyperlink`} + > + {value} + </EuiLink> + </EuiFlexItem> + {this.state.activeSpace?.name === rowRecord.name && ( + <EuiFlexItem grow={false}> + <EuiBadge color="primary" data-test-subj={`spacesListCurrentBadge-${rowRecord.id}`}> + {i18n.translate('xpack.spaces.management.spacesGridPage.currentSpaceMarkerText', { + defaultMessage: 'current', + })} + </EuiBadge> + </EuiFlexItem> + )} + </EuiFlexGroup> ), + 'data-test-subj': 'spacesListTableRowNameCell', }, { field: 'description', @@ -275,17 +303,19 @@ export class SpacesGridPage extends Component<Props, State> { defaultMessage: 'Description', }), sortable: true, + truncateText: true, + width: '30%', }, { field: 'disabledFeatures', name: i18n.translate('xpack.spaces.management.spacesGridPage.featuresColumnName', { - defaultMessage: 'Features', + defaultMessage: 'Features visible', }), sortable: (space: Space) => { return getEnabledFeatures(this.state.features, space).length; }, - render: (disabledFeatures: string[], record: Space) => { - const enabledFeatureCount = getEnabledFeatures(this.state.features, record).length; + render: (_disabledFeatures: string[], rowRecord: Space) => { + const enabledFeatureCount = getEnabledFeatures(this.state.features, rowRecord).length; if (enabledFeatureCount === this.state.features.length) { return ( <FormattedMessage @@ -307,7 +337,7 @@ export class SpacesGridPage extends Component<Props, State> { return ( <FormattedMessage id="xpack.spaces.management.spacesGridPage.someFeaturesEnabled" - defaultMessage="{enabledFeatureCount} / {totalFeatureCount} features visible" + defaultMessage="{enabledFeatureCount} / {totalFeatureCount}" values={{ enabledFeatureCount, totalFeatureCount: this.state.features.length, @@ -331,7 +361,7 @@ export class SpacesGridPage extends Component<Props, State> { }, ]; - if (this.state.isSolutionNavEnabled) { + if (this.props.allowSolutionVisibility) { config.push({ field: 'solution', name: i18n.translate('xpack.spaces.management.spacesGridPage.solutionColumnName', { @@ -350,39 +380,80 @@ export class SpacesGridPage extends Component<Props, State> { }), actions: [ { - render: (record: Space) => ( - <EuiButtonIcon - data-test-subj={`${record.name}-editSpace`} - aria-label={i18n.translate( - 'xpack.spaces.management.spacesGridPage.editSpaceActionName', - { - defaultMessage: `Edit {spaceName}.`, - values: { spaceName: record.name }, - } - )} - color={'primary'} - iconType={'pencil'} - {...reactRouterNavigate(this.props.history, this.getEditSpacePath(record))} - /> - ), + isPrimary: true, + name: i18n.translate('xpack.spaces.management.spacesGridPage.editSpaceActionName', { + defaultMessage: `Edit`, + }), + description: (rowRecord) => + i18n.translate('xpack.spaces.management.spacesGridPage.editSpaceActionDescription', { + defaultMessage: `Edit {spaceName}.`, + values: { spaceName: rowRecord.name }, + }), + type: 'icon', + icon: 'pencil', + color: 'primary', + href: (rowRecord) => + reactRouterNavigate(this.props.history, this.getEditSpacePath(rowRecord)).href, + onClick: (rowRecord) => + reactRouterNavigate(this.props.history, this.getEditSpacePath(rowRecord)).onClick, + 'data-test-subj': (rowRecord) => `${rowRecord.name}-editSpace`, }, { - available: (record: Space) => !isReservedSpace(record), - render: (record: Space) => ( - <EuiButtonIcon - data-test-subj={`${record.name}-deleteSpace`} - aria-label={i18n.translate( - 'xpack.spaces.management.spacesGridPage.deleteActionName', - { - defaultMessage: `Delete {spaceName}.`, - values: { spaceName: record.name }, - } - )} - color={'danger'} - iconType={'trash'} - onClick={() => this.onDeleteSpaceClick(record)} - /> - ), + isPrimary: true, + name: i18n.translate('xpack.spaces.management.spacesGridPage.switchSpaceActionName', { + defaultMessage: 'Switch', + }), + description: (rowRecord) => + this.state.activeSpace?.name !== rowRecord.name + ? i18n.translate( + 'xpack.spaces.management.spacesGridPage.switchSpaceActionDescription', + { + defaultMessage: 'Switch to {spaceName}', + values: { spaceName: rowRecord.name }, + } + ) + : i18n.translate( + 'xpack.spaces.management.spacesGridPage.switchSpaceActionDisabledDescription', + { + defaultMessage: '{spaceName} is the current space', + values: { spaceName: rowRecord.name }, + } + ), + type: 'icon', + icon: 'merge', + color: 'primary', + href: (rowRecord: Space) => + addSpaceIdToPath( + this.props.serverBasePath, + rowRecord.id, + `${ENTER_SPACE_PATH}?next=/app/management/kibana/spaces/` + ), + enabled: (rowRecord) => this.state.activeSpace?.name !== rowRecord.name, + 'data-test-subj': (rowRecord) => `${rowRecord.name}-switchSpace`, + }, + { + name: i18n.translate('xpack.spaces.management.spacesGridPage.deleteActionName', { + defaultMessage: `Delete`, + }), + description: (rowRecord) => + isReservedSpace(rowRecord) + ? i18n.translate( + 'xpack.spaces.management.spacesGridPage.deleteActionDisabledDescription', + { + defaultMessage: `{spaceName} is reserved`, + values: { spaceName: rowRecord.name }, + } + ) + : i18n.translate('xpack.spaces.management.spacesGridPage.deleteActionDescription', { + defaultMessage: `Delete {spaceName}`, + values: { spaceName: rowRecord.name }, + }), + type: 'icon', + icon: 'trash', + color: 'danger', + onClick: (rowRecord: Space) => this.onDeleteSpaceClick(rowRecord), + enabled: (rowRecord: Space) => !isReservedSpace(rowRecord), + 'data-test-subj': (rowRecord) => `${rowRecord.name}-deleteSpace`, }, ], }); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index e953c324be285..2415d603086e9 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -22,6 +22,7 @@ import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/ import { featuresPluginMock } from '@kbn/features-plugin/public/mocks'; import { spacesManagementApp } from './spaces_management_app'; +import { EventTracker } from '../analytics'; import type { ConfigType } from '../config'; import type { PluginsStart } from '../plugin'; import { spacesManagerMock } from '../spaces_manager/mocks'; @@ -29,8 +30,11 @@ import { spacesManagerMock } from '../spaces_manager/mocks'; const config: ConfigType = { maxSpaces: 1000, allowFeatureVisibility: true, + allowSolutionVisibility: true, }; +const eventTracker = new EventTracker({ reportEvent: jest.fn() }); + async function mountApp(basePath: string, pathname: string, spaceId?: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -53,7 +57,7 @@ async function mountApp(basePath: string, pathname: string, spaceId?: string) { getStartServices: async () => [coreStart, pluginsStart as PluginsStart, {}], config, getRolesAPIClient: jest.fn(), - solutionNavExperiment: Promise.resolve(false), + eventTracker, }) .mount({ basePath, @@ -75,7 +79,7 @@ describe('spacesManagementApp', () => { getStartServices: coreMock.createSetup().getStartServices as any, config, getRolesAPIClient: jest.fn(), - solutionNavExperiment: Promise.resolve(false), + eventTracker, }) ).toMatchInlineSnapshot(` Object { @@ -100,7 +104,7 @@ describe('spacesManagementApp', () => { css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)." data-test-subj="kbnRedirectAppLink" > - Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"maxSpaces":1000,"solutionNavExperiment":{}} + Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"serverBasePath":"","history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"maxSpaces":1000,"allowSolutionVisibility":true} </div> </div> `); @@ -127,7 +131,7 @@ describe('spacesManagementApp', () => { css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)." data-test-subj="kbnRedirectAppLink" > - Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"allowFeatureVisibility":true,"solutionNavExperiment":{}} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true,"eventTracker":{"analytics":{}}} </div> </div> `); @@ -160,7 +164,7 @@ describe('spacesManagementApp', () => { css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)." data-test-subj="kbnRedirectAppLink" > - Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"solutionNavExperiment":{}} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"allowSolutionVisibility":true,"eventTracker":{"analytics":{}}} </div> </div> `); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index c551b47cde9c6..9883286122653 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -19,6 +19,7 @@ import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { Route, Router, Routes } from '@kbn/shared-ux-router'; import type { Space } from '../../common'; +import type { EventTracker } from '../analytics'; import type { ConfigType } from '../config'; import type { PluginsStart } from '../plugin'; import type { SpacesManager } from '../spaces_manager'; @@ -28,12 +29,12 @@ interface CreateParams { spacesManager: SpacesManager; config: ConfigType; getRolesAPIClient: () => Promise<RolesAPIClient>; - solutionNavExperiment: Promise<boolean>; + eventTracker: EventTracker; } export const spacesManagementApp = Object.freeze({ id: 'spaces', - create({ getStartServices, spacesManager, config, solutionNavExperiment }: CreateParams) { + create({ getStartServices, spacesManager, config, eventTracker }: CreateParams) { const title = i18n.translate('xpack.spaces.displayName', { defaultMessage: 'Spaces', }); @@ -51,7 +52,7 @@ export const spacesManagementApp = Object.freeze({ text: title, href: `/`, }; - const { notifications, application, chrome } = coreStart; + const { notifications, application, chrome, http } = coreStart; chrome.docTitle.change(title); @@ -63,10 +64,11 @@ export const spacesManagementApp = Object.freeze({ getFeatures={features.getFeatures} notifications={notifications} spacesManager={spacesManager} + serverBasePath={http.basePath.serverBasePath} history={history} getUrlForApp={application.getUrlForApp} maxSpaces={config.maxSpaces} - solutionNavExperiment={solutionNavExperiment} + allowSolutionVisibility={config.allowSolutionVisibility} /> ); }; @@ -89,7 +91,8 @@ export const spacesManagementApp = Object.freeze({ spacesManager={spacesManager} history={history} allowFeatureVisibility={config.allowFeatureVisibility} - solutionNavExperiment={solutionNavExperiment} + allowSolutionVisibility={config.allowSolutionVisibility} + eventTracker={eventTracker} /> ); }; @@ -116,7 +119,8 @@ export const spacesManagementApp = Object.freeze({ onLoadSpace={onLoadSpace} history={history} allowFeatureVisibility={config.allowFeatureVisibility} - solutionNavExperiment={solutionNavExperiment} + allowSolutionVisibility={config.allowSolutionVisibility} + eventTracker={eventTracker} /> ); }; diff --git a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx index fab2158cb1c7d..47f7d840b9bee 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/spaces_menu.tsx @@ -30,6 +30,7 @@ import { FormattedMessage, injectI18n } from '@kbn/i18n-react'; import { ManageSpacesButton } from './manage_spaces_button'; import type { Space } from '../../../common'; import { addSpaceIdToPath, ENTER_SPACE_PATH, SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common'; +import type { EventTracker } from '../../analytics'; import { getSpaceAvatarComponent } from '../../space_avatar'; import { SpaceSolutionBadge } from '../../space_solution_badge'; @@ -47,7 +48,8 @@ interface Props { navigateToApp: ApplicationStart['navigateToApp']; navigateToUrl: ApplicationStart['navigateToUrl']; readonly activeSpace: Space | null; - isSolutionNavEnabled: boolean; + allowSolutionVisibility: boolean; + eventTracker: EventTracker; } class SpacesMenuUI extends Component<Props> { public render() { @@ -138,7 +140,7 @@ class SpacesMenuUI extends Component<Props> { <LazySpaceAvatar space={space} size={'s'} announceSpaceName={false} /> </Suspense> ), - ...(this.props.isSolutionNavEnabled && { + ...(this.props.allowSolutionVisibility && { append: <SpaceSolutionBadge solution={space.solution} />, }), checked: this.props.activeSpace?.id === space.id ? 'on' : undefined, @@ -148,16 +150,36 @@ class SpacesMenuUI extends Component<Props> { }); }; - private spaceSelectionChange = ( + private getSpaceDetails = (id: string): Space | undefined => { + return this.props.spaces.find((space) => space.id === id); + }; + + private spaceSelectionChange = async ( newOptions: EuiSelectableOption[], event: EuiSelectableOnChangeEvent ) => { const selectedSpaceItem = newOptions.filter((item) => item.checked === 'on')[0]; + const trackSpaceChange = (nextId?: string) => { + if (!nextId) return; + const nextSpace = this.getSpaceDetails(nextId); + const currentSpace = this.props.activeSpace; + if (!nextSpace || !currentSpace) return; + + this.props.eventTracker.changeSpace({ + nextSpaceId: nextSpace.id, + nextSolution: nextSpace.solution, + prevSpaceId: currentSpace.id, + prevSolution: currentSpace.solution, + }); + }; + if (!!selectedSpaceItem) { + const spaceId = selectedSpaceItem.key; // the key is the unique space id + const urlToSelectedSpace = addSpaceIdToPath( this.props.serverBasePath, - selectedSpaceItem.key, // the key is the unique space id + spaceId, ENTER_SPACE_PATH ); @@ -169,15 +191,23 @@ class SpacesMenuUI extends Component<Props> { if (event.shiftKey) { // Open in new window, shift is given priority over other modifiers this.props.toggleSpaceSelector(); + trackSpaceChange(spaceId); window.open(urlToSelectedSpace); } else if (event.ctrlKey || event.metaKey || middleClick) { // Open in new tab - either a ctrl click or middle mouse button + trackSpaceChange(spaceId); window.open(urlToSelectedSpace, '_blank'); } else { // Force full page reload (usually not a good idea, but we need to in order to change spaces) // If the selected space is already the active space, gracefully close the popover - if (this.props.activeSpace?.id === selectedSpaceItem.key) this.props.toggleSpaceSelector(); - else this.props.navigateToUrl(urlToSelectedSpace); + if (this.props.activeSpace?.id === selectedSpaceItem.key) { + this.props.toggleSpaceSelector(); + } else { + trackSpaceChange(spaceId); + const flushAnalyticsEvents = window.__kbnAnalytics?.flush ?? (() => Promise.resolve()); + await flushAnalyticsEvents(); + this.props.navigateToUrl(urlToSelectedSpace); + } } } }; diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx index 7e104e56c6548..7cb32fff01e1e 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control.tsx @@ -12,12 +12,15 @@ import ReactDOM from 'react-dom'; import type { CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import type { EventTracker } from '../analytics'; +import type { ConfigType } from '../config'; import type { SpacesManager } from '../spaces_manager'; export function initSpacesNavControl( spacesManager: SpacesManager, core: CoreStart, - solutionNavExperiment: Promise<boolean> + config: ConfigType, + eventTracker: EventTracker ) { core.chrome.navControls.registerLeft({ order: 1000, @@ -42,7 +45,8 @@ export function initSpacesNavControl( capabilities={core.application.capabilities} navigateToApp={core.application.navigateToApp} navigateToUrl={core.application.navigateToUrl} - solutionNavExperiment={solutionNavExperiment} + allowSolutionVisibility={config.allowSolutionVisibility} + eventTracker={eventTracker} /> </Suspense> </KibanaRenderContextProvider>, 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 528103c5ccbc2..9b573615c65b9 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 @@ -16,10 +16,11 @@ import { act, render, waitFor } from '@testing-library/react'; import React from 'react'; import * as Rx from 'rxjs'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { NavControlPopover } from './nav_control_popover'; import type { Space } from '../../common'; +import { EventTracker } from '../analytics'; import { SpaceAvatarInternal } from '../space_avatar/space_avatar_internal'; import { SpaceSolutionBadge } from '../space_solution_badge'; import type { SpacesManager } from '../spaces_manager'; @@ -44,11 +45,19 @@ const mockSpaces = [ }, ]; +const reportEvent = jest.fn(); +const eventTracker = new EventTracker({ reportEvent }); + describe('NavControlPopover', () => { - async function setup(spaces: Space[], isSolutionNavEnabled = false) { + async function setup(spaces: Space[], allowSolutionVisibility = false, activeSpace?: Space) { const spacesManager = spacesManagerMock.create(); spacesManager.getSpaces = jest.fn().mockResolvedValue(spaces); + if (activeSpace) { + // @ts-ignore readonly check + spacesManager.onActiveSpaceChange$ = Rx.of(activeSpace); + } + const wrapper = mountWithIntl( <NavControlPopover spacesManager={spacesManager as unknown as SpacesManager} @@ -57,7 +66,8 @@ describe('NavControlPopover', () => { capabilities={{ navLinks: {}, management: {}, catalogue: {}, spaces: { manage: true } }} navigateToApp={jest.fn()} navigateToUrl={jest.fn()} - solutionNavExperiment={Promise.resolve(isSolutionNavEnabled)} + allowSolutionVisibility={allowSolutionVisibility} + eventTracker={eventTracker} /> ); @@ -79,7 +89,8 @@ describe('NavControlPopover', () => { capabilities={{ navLinks: {}, management: {}, catalogue: {}, spaces: { manage: true } }} navigateToApp={jest.fn()} navigateToUrl={jest.fn()} - solutionNavExperiment={Promise.resolve(false)} + allowSolutionVisibility={false} + eventTracker={eventTracker} /> ); expect(baseElement).toMatchSnapshot(); @@ -104,7 +115,8 @@ describe('NavControlPopover', () => { capabilities={{ navLinks: {}, management: {}, catalogue: {}, spaces: { manage: true } }} navigateToApp={jest.fn()} navigateToUrl={jest.fn()} - solutionNavExperiment={Promise.resolve(false)} + allowSolutionVisibility={false} + eventTracker={eventTracker} /> ); @@ -253,4 +265,40 @@ describe('NavControlPopover', () => { expect(wrapper.find(SpaceSolutionBadge)).toHaveLength(2); }); + + it('should report event when switching space', async () => { + const spaces: Space[] = [ + { + id: 'space-1', + name: 'Space-1', + disabledFeatures: [], + solution: 'classic', + }, + { + id: 'space-2', + name: 'Space 2', + disabledFeatures: [], + solution: 'security', + }, + ]; + + const activeSpace = spaces[0]; + const wrapper = await setup(spaces, true /** allowSolutionVisibility **/, activeSpace); + + await act(async () => { + wrapper.find(EuiHeaderSectionItemButton).find('button').simulate('click'); + }); + wrapper.update(); + + expect(reportEvent).not.toHaveBeenCalled(); + + findTestSubject(wrapper, 'space-2-selectableSpaceItem').simulate('click'); + + expect(reportEvent).toHaveBeenCalledWith('space_changed', { + solution: 'security', + solution_prev: 'classic', + space_id: 'space-2', + space_id_prev: 'space-1', + }); + }); }); diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx index 6f40db60bfd4f..b9830b2063dd5 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n'; import { SpacesDescription } from './components/spaces_description'; import { SpacesMenu } from './components/spaces_menu'; import type { Space } from '../../common'; +import type { EventTracker } from '../analytics'; import { getSpaceAvatarComponent } from '../space_avatar'; import type { SpacesManager } from '../spaces_manager'; @@ -37,7 +38,8 @@ interface Props { navigateToUrl: ApplicationStart['navigateToUrl']; serverBasePath: string; theme: WithEuiThemeProps['theme']; - solutionNavExperiment: Promise<boolean>; + allowSolutionVisibility: boolean; + eventTracker: EventTracker; } interface State { @@ -45,7 +47,6 @@ interface State { loading: boolean; activeSpace: Space | null; spaces: Space[]; - isSolutionNavEnabled: boolean; } const popoutContentId = 'headerSpacesMenuContent'; @@ -60,7 +61,6 @@ class NavControlPopoverUI extends Component<Props, State> { loading: false, activeSpace: null, spaces: [], - isSolutionNavEnabled: false, }; } @@ -72,10 +72,6 @@ class NavControlPopoverUI extends Component<Props, State> { }); }, }); - - this.props.solutionNavExperiment.then((isEnabled) => { - this.setState({ isSolutionNavEnabled: isEnabled }); - }); } public componentWillUnmount() { @@ -108,7 +104,8 @@ class NavControlPopoverUI extends Component<Props, State> { navigateToApp={this.props.navigateToApp} navigateToUrl={this.props.navigateToUrl} activeSpace={this.state.activeSpace} - isSolutionNavEnabled={this.state.isSolutionNavEnabled} + allowSolutionVisibility={this.props.allowSolutionVisibility} + eventTracker={this.props.eventTracker} /> ); } diff --git a/x-pack/plugins/spaces/public/plugin.tsx b/x-pack/plugins/spaces/public/plugin.tsx index c915a4ea73880..b07595618c3cc 100644 --- a/x-pack/plugins/spaces/public/plugin.tsx +++ b/x-pack/plugins/spaces/public/plugin.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import type { FeaturesPluginStart } from '@kbn/features-plugin/public'; @@ -13,9 +12,9 @@ import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; import type { SecurityPluginStart } from '@kbn/security-plugin-types-public'; +import { EventTracker, registerAnalyticsContext, registerSpacesEventTypes } from './analytics'; import type { ConfigType } from './config'; import { createSpacesFeatureCatalogueEntry } from './create_feature_catalogue_entry'; -import { isSolutionNavEnabled } from './experiments'; import { ManagementService } from './management'; import { initSpacesNavControl } from './nav_control'; import { spaceSelectorApp } from './space_selector'; @@ -33,7 +32,6 @@ export interface PluginsStart { features: FeaturesPluginStart; management?: ManagementStart; cloud?: CloudStart; - cloudExperiments?: CloudExperimentsPluginStart; } /** @@ -49,11 +47,11 @@ export type SpacesPluginStart = ReturnType<SpacesPlugin['start']>; export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart> { private spacesManager!: SpacesManager; private spacesApi!: SpacesApi; + private eventTracker!: EventTracker; private managementService?: ManagementService; - private readonly config: ConfigType; + private config: ConfigType; private readonly isServerless: boolean; - private solutionNavExperiment = Promise.resolve(false); constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get<ConfigType>(); @@ -73,14 +71,15 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart hasOnlyDefaultSpace, }; - this.solutionNavExperiment = core - .getStartServices() - .then(([, { cloud, cloudExperiments }]) => isSolutionNavEnabled(cloud, cloudExperiments)) - .catch((err) => { - this.initializerContext.logger.get().error(`Failed to retrieve cloud experiment: ${err}`); - - return false; - }); + const onCloud = plugins.cloud !== undefined && plugins.cloud.isCloudEnabled; + if (!onCloud) { + this.config = { + ...this.config, + allowSolutionVisibility: false, + }; + } + registerSpacesEventTypes(core); + this.eventTracker = new EventTracker(core.analytics); // Only skip setup of space selector and management service if serverless and only one space is allowed if (!(this.isServerless && hasOnlyDefaultSpace)) { @@ -108,7 +107,7 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart spacesManager: this.spacesManager, config: this.config, getRolesAPIClient, - solutionNavExperiment: this.solutionNavExperiment, + eventTracker: this.eventTracker, }); } @@ -119,13 +118,15 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart }); } + registerAnalyticsContext(core.analytics, this.spacesManager.onActiveSpaceChange$); + return { hasOnlyDefaultSpace }; } public start(core: CoreStart) { // Only skip spaces navigation if serverless and only one space is allowed if (!(this.isServerless && this.config.maxSpaces === 1)) { - initSpacesNavControl(this.spacesManager, core, this.solutionNavExperiment); + initSpacesNavControl(this.spacesManager, core, this.config, this.eventTracker); } return this.spacesApi; diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts index bc3edf8cf4e5b..19d4a7cbe7f90 100644 --- a/x-pack/plugins/spaces/server/config.test.ts +++ b/x-pack/plugins/spaces/server/config.test.ts @@ -21,6 +21,7 @@ describe('config schema', () => { expect(ConfigSchema.validate({})).toMatchInlineSnapshot(` Object { "allowFeatureVisibility": true, + "allowSolutionVisibility": true, "enabled": true, "maxSpaces": 1000, } @@ -29,6 +30,7 @@ describe('config schema', () => { expect(ConfigSchema.validate({}, { dev: false })).toMatchInlineSnapshot(` Object { "allowFeatureVisibility": true, + "allowSolutionVisibility": true, "enabled": true, "maxSpaces": 1000, } @@ -37,6 +39,7 @@ describe('config schema', () => { expect(ConfigSchema.validate({}, { dev: true })).toMatchInlineSnapshot(` Object { "allowFeatureVisibility": true, + "allowSolutionVisibility": true, "enabled": true, "maxSpaces": 1000, } @@ -61,19 +64,36 @@ describe('config schema', () => { expect(() => ConfigSchema.validate({ allowFeatureVisibility: false }, {})).toThrow(); }); - it('should not throw error if allowFeatureVisibility is disabled in serverless offering', () => { + it('should not throw error if allowFeatureVisibility and allowSolutionVisibility are disabled in serverless offering', () => { expect(() => - ConfigSchema.validate({ allowFeatureVisibility: false }, { serverless: true }) + ConfigSchema.validate( + { allowFeatureVisibility: false, allowSolutionVisibility: false }, + { serverless: true } + ) ).not.toThrow(); }); - it('should not throw error if allowFeatureVisibility is enabled in classic offering', () => { - expect(() => ConfigSchema.validate({ allowFeatureVisibility: true }, {})).not.toThrow(); + it('should not throw error if allowFeatureVisibility and allowSolutionVisibility are enabled in classic offering', () => { + expect(() => + ConfigSchema.validate({ allowFeatureVisibility: true, allowSolutionVisibility: true }, {}) + ).not.toThrow(); }); it('should throw error if allowFeatureVisibility is enabled in serverless offering', () => { expect(() => - ConfigSchema.validate({ allowFeatureVisibility: true }, { serverless: true }) + ConfigSchema.validate( + { allowFeatureVisibility: true, allowSolutionVisibility: false }, + { serverless: true } + ) + ).toThrow(); + }); + + it('should throw error if allowSolutionVisibility is enabled in serverless offering', () => { + expect(() => + ConfigSchema.validate( + { allowSolutionVisibility: true, allowFeatureVisibility: false }, + { serverless: true } + ) ).toThrow(); }); }); diff --git a/x-pack/plugins/spaces/server/config.ts b/x-pack/plugins/spaces/server/config.ts index 52f63f3526904..a7c9606e74543 100644 --- a/x-pack/plugins/spaces/server/config.ts +++ b/x-pack/plugins/spaces/server/config.ts @@ -40,6 +40,20 @@ export const ConfigSchema = schema.object({ defaultValue: true, }), }), + allowSolutionVisibility: offeringBasedSchema({ + serverless: schema.literal(false), + traditional: schema.boolean({ + validate: (rawValue) => { + // This setting should not be configurable on-prem to avoid bugs when e.g. existing spaces + // have custom solution but admins would be unable to change the navigation solution if the + // UI/APIs are disabled. + if (rawValue === false) { + return 'Solution visibility can only be disabled on serverless'; + } + }, + defaultValue: true, + }), + }), }); export function createConfig$(context: PluginInitializerContext) { diff --git a/x-pack/plugins/spaces/server/index.ts b/x-pack/plugins/spaces/server/index.ts index ba949628c56e1..a568f52c7c29a 100644 --- a/x-pack/plugins/spaces/server/index.ts +++ b/x-pack/plugins/spaces/server/index.ts @@ -33,6 +33,7 @@ export const config: PluginConfigDescriptor = { exposeToBrowser: { maxSpaces: true, allowFeatureVisibility: true, + allowSolutionVisibility: true, }, }; diff --git a/x-pack/plugins/spaces/server/plugin.test.ts b/x-pack/plugins/spaces/server/plugin.test.ts index b181b87e9cc94..1ca1f600d8bad 100644 --- a/x-pack/plugins/spaces/server/plugin.test.ts +++ b/x-pack/plugins/spaces/server/plugin.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { lastValueFrom } from 'rxjs'; +import { firstValueFrom } from 'rxjs'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; @@ -36,7 +36,10 @@ describe('Spaces plugin', () => { "hasOnlyDefaultSpace$": Observable { "operator": [Function], "source": Observable { - "_subscribe": [Function], + "operator": [Function], + "source": Observable { + "_subscribe": [Function], + }, }, }, "spacesClient": Object { @@ -120,7 +123,10 @@ describe('Spaces plugin', () => { "hasOnlyDefaultSpace$": Observable { "operator": [Function], "source": Observable { - "_subscribe": [Function], + "operator": [Function], + "source": Observable { + "_subscribe": [Function], + }, }, }, "spacesService": Object { @@ -150,8 +156,8 @@ describe('Spaces plugin', () => { 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); + await expect(firstValueFrom(spacesSetup.hasOnlyDefaultSpace$)).resolves.toEqual(true); + await expect(firstValueFrom(spacesStart.hasOnlyDefaultSpace$)).resolves.toEqual(true); }); it('determines hasOnlyDefaultSpace$ correctly when maxSpaces=1000', async () => { @@ -168,7 +174,7 @@ describe('Spaces plugin', () => { 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); + await expect(firstValueFrom(spacesSetup.hasOnlyDefaultSpace$)).resolves.toEqual(false); + await expect(firstValueFrom(spacesStart.hasOnlyDefaultSpace$)).resolves.toEqual(false); }); }); diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 89900deb03b83..2b76c8dda93c1 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -6,7 +6,7 @@ */ import type { Observable } from 'rxjs'; -import { map } from 'rxjs'; +import { BehaviorSubject, combineLatest, map } from 'rxjs'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { @@ -119,8 +119,21 @@ export class SpacesPlugin private defaultSpaceService?: DefaultSpaceService; + private onCloud$ = new BehaviorSubject<boolean>(false); + constructor(private readonly initializerContext: PluginInitializerContext) { - this.config$ = initializerContext.config.create<ConfigType>(); + this.config$ = combineLatest([ + initializerContext.config.create<ConfigType>(), + this.onCloud$, + ]).pipe( + map( + ([config, onCloud]): ConfigType => ({ + ...config, + // We only allow "solution" to be set on cloud environments, not on prem + allowSolutionVisibility: onCloud ? config.allowSolutionVisibility : false, + }) + ) + ); this.hasOnlyDefaultSpace$ = this.config$.pipe(map(({ maxSpaces }) => maxSpaces === 1)); this.log = initializerContext.logger.get(); this.spacesService = new SpacesService(); @@ -131,6 +144,7 @@ export class SpacesPlugin } public setup(core: CoreSetup<PluginsStart>, plugins: PluginsSetup): SpacesPluginSetup { + this.onCloud$.next(plugins.cloud !== undefined && plugins.cloud.isCloudEnabled); const spacesClientSetup = this.spacesClientService.setup({ config$: this.config$ }); const spacesServiceSetup = this.spacesService.setup({ diff --git a/x-pack/plugins/spaces/server/routes/views/index.test.ts b/x-pack/plugins/spaces/server/routes/views/index.test.ts index cb93a62256ed2..b87bfe86c022a 100644 --- a/x-pack/plugins/spaces/server/routes/views/index.test.ts +++ b/x-pack/plugins/spaces/server/routes/views/index.test.ts @@ -107,72 +107,63 @@ describe('Enter Space view routes', () => { }); }); - it('correctly enters space default route.', async () => { - const request = httpServerMock.createKibanaRequest(); - const responseFactory = httpResourcesMock.createResponseFactory(); - const contextMock = coreMock.createRequestHandlerContext(); - - contextMock.uiSettings.client.get.mockResolvedValue('/home'); + const testCase = ( + description: string, + { + query, + defaultRoute, + expectedLocation, + }: { query?: Record<string, string>; defaultRoute?: string; expectedLocation: string } + ) => { + it(description, async () => { + const request = httpServerMock.createKibanaRequest({ + query, + }); - await routeHandler( - { core: contextMock } as unknown as RequestHandlerContext, - request, - responseFactory - ); + const responseFactory = httpResourcesMock.createResponseFactory(); + const contextMock = coreMock.createRequestHandlerContext(); + contextMock.uiSettings.client.get.mockResolvedValue(defaultRoute); - expect(responseFactory.redirected).toHaveBeenCalledWith({ - headers: { location: '/mock-server-basepath/home' }, - }); - }); + await routeHandler( + { core: contextMock } as unknown as RequestHandlerContext, + request, + responseFactory + ); - it('correctly enters space with specified route.', async () => { - const nextRoute = '/app/management/kibana/objects'; - const request = httpServerMock.createKibanaRequest({ - query: { - next: nextRoute, - }, + expect(responseFactory.redirected).toHaveBeenCalledWith({ + headers: { location: expectedLocation }, + }); }); + }; - const responseFactory = httpResourcesMock.createResponseFactory(); - const contextMock = coreMock.createRequestHandlerContext(); - - await routeHandler( - { core: contextMock } as unknown as RequestHandlerContext, - request, - responseFactory - ); - - expect(responseFactory.redirected).toHaveBeenCalledWith({ - headers: { location: `/mock-server-basepath${nextRoute}` }, - }); + testCase('correctly enters space default route.', { + defaultRoute: '/home', + expectedLocation: '/mock-server-basepath/home', }); - it('correctly enters space with specified route without leading slash.', async () => { - const nextRoute = 'app/management/kibana/objects'; - const request = httpServerMock.createKibanaRequest({ - query: { - next: nextRoute, - }, - }); - - const responseFactory = httpResourcesMock.createResponseFactory(); - const contextMock = coreMock.createRequestHandlerContext(); - - await routeHandler( - { core: contextMock } as unknown as RequestHandlerContext, - request, - responseFactory - ); + testCase('correctly enters space with specified route.', { + query: { + next: '/app/management/kibana/objects', + }, + expectedLocation: '/mock-server-basepath/app/management/kibana/objects', + }); - expect(responseFactory.redirected).toHaveBeenCalledWith({ - headers: { location: `/mock-server-basepath/${nextRoute}` }, - }); + testCase('correctly enters space with specified route without leading slash.', { + query: { + next: 'app/management/kibana/objects', + }, + expectedLocation: '/mock-server-basepath/app/management/kibana/objects', }); - it('correctly enters space and normalizes specified route.', async () => { - const responseFactory = httpResourcesMock.createResponseFactory(); - const contextMock = coreMock.createRequestHandlerContext(); + testCase('correctly enters space with default route if specified route is not relative.', { + query: { + next: 'http://evil.com/mock-server-basepath/app/kibana', + }, + defaultRoute: '/home', + expectedLocation: '/mock-server-basepath/home', + }); + describe('specified route normalization', () => { for (const { query, expectedLocation } of [ { query: { @@ -205,56 +196,47 @@ describe('Enter Space view routes', () => { expectedLocation: '/mock-server-basepath/app/management/kibana/objects?initialQuery=type:(visualization)', }, + ]) { + testCase(`url ${query.next}`, { query, expectedLocation }); + } + }); + + describe('default route', () => { + for (const { defaultRoute, expectedLocation, query, testCaseName } of [ { - query: { - next: '/app/discover#/view/uuid', - }, - expectedLocation: '/mock-server-basepath/app/discover#/view/uuid', + defaultRoute: '/home', + expectedLocation: '/mock-server-basepath/home', + testCaseName: 'correctly enters space with default route.', }, { - query: { - next: '/app/discover?initialQuery=type:(visualization)#/view/uuid', - }, + defaultRoute: '/home', + expectedLocation: '/mock-server-basepath/home', + testCaseName: 'correctly enters space with default route if specified url is empty string.', + query: { next: '' }, + }, + { + defaultRoute: '/home/#view', + expectedLocation: '/mock-server-basepath/home/#view', + testCaseName: 'correctly enters space with default route preserving url hash.', + }, + { + defaultRoute: '/home?initialQuery=type:(visualization)', + expectedLocation: '/mock-server-basepath/home?initialQuery=type:(visualization)', + testCaseName: 'correctly enters space with default route preserving url search.', + }, + { + defaultRoute: '/app/discover?initialQuery=type:(visualization)#/view/uuid', expectedLocation: '/mock-server-basepath/app/discover?initialQuery=type:(visualization)#/view/uuid', + testCaseName: 'correctly enters space with default route preserving url search and hash.', + }, + { + defaultRoute: '../../app/../app/management/kibana/objects', + expectedLocation: '/mock-server-basepath/app/management/kibana/objects', + testCaseName: 'correctly enters space with default route normalizing url.', }, ]) { - const request = httpServerMock.createKibanaRequest({ - query, - }); - await routeHandler( - { core: contextMock } as unknown as RequestHandlerContext, - request, - responseFactory - ); - - expect(responseFactory.redirected).toHaveBeenCalledWith({ - headers: { location: expectedLocation }, - }); - - responseFactory.redirected.mockClear(); + testCase(testCaseName, { query, defaultRoute, expectedLocation }); } }); - - it('correctly enters space with default route if specificed route is not relative.', async () => { - const request = httpServerMock.createKibanaRequest({ - query: { - next: 'http://evil.com/mock-server-basepath/app/kibana', - }, - }); - - const responseFactory = httpResourcesMock.createResponseFactory(); - const contextMock = coreMock.createRequestHandlerContext(); - contextMock.uiSettings.client.get.mockResolvedValue('/home'); - - await routeHandler( - { core: contextMock } as unknown as RequestHandlerContext, - request, - responseFactory - ); - - expect(responseFactory.redirected).toHaveBeenCalledWith({ - headers: { location: '/mock-server-basepath/home' }, - }); - }); }); diff --git a/x-pack/plugins/spaces/server/routes/views/index.ts b/x-pack/plugins/spaces/server/routes/views/index.ts index 2b4cdd9dbcf1f..ab06b17374f13 100644 --- a/x-pack/plugins/spaces/server/routes/views/index.ts +++ b/x-pack/plugins/spaces/server/routes/views/index.ts @@ -44,6 +44,7 @@ export function initSpacesViewsRoutes(deps: ViewRouteDeps) { // need to get reed of ../../ to make sure we will not be out of space basePath const normalizedRoute = new URL(route, 'https://localhost'); + // preserving of the hash is important for the navigation to work correctly with default route return response.redirected({ headers: { location: `${basePath}${normalizedRoute.pathname}${normalizedRoute.search}${normalizedRoute.hash}`, diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index d9a23a3bc0c45..7faff9f8a0acf 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -22,6 +22,7 @@ const createMockConfig = ( enabled: true, maxSpaces: 1000, allowFeatureVisibility: true, + allowSolutionVisibility: true, } ) => { return ConfigSchema.validate(mockConfig, { serverless: !mockConfig.allowFeatureVisibility }); @@ -311,6 +312,7 @@ describe('#create', () => { enabled: true, maxSpaces, allowFeatureVisibility: true, + allowSolutionVisibility: true, }); const client = new SpacesClient( @@ -347,6 +349,7 @@ describe('#create', () => { enabled: true, maxSpaces, allowFeatureVisibility: true, + allowSolutionVisibility: true, }); const client = new SpacesClient( @@ -382,6 +385,7 @@ describe('#create', () => { enabled: true, maxSpaces, allowFeatureVisibility: true, + allowSolutionVisibility: true, }); const client = new SpacesClient( @@ -428,6 +432,7 @@ describe('#create', () => { enabled: true, maxSpaces, allowFeatureVisibility: true, + allowSolutionVisibility: true, }); const client = new SpacesClient( @@ -470,6 +475,7 @@ describe('#create', () => { enabled: true, maxSpaces, allowFeatureVisibility: false, + allowSolutionVisibility: false, }); const client = new SpacesClient( @@ -506,6 +512,7 @@ describe('#create', () => { enabled: true, maxSpaces, allowFeatureVisibility: false, + allowSolutionVisibility: false, }); const client = new SpacesClient( @@ -530,6 +537,46 @@ describe('#create', () => { expect(mockCallWithRequestRepository.create).not.toHaveBeenCalled(); }); }); + + describe('when config.allowSolutionVisibility is disabled', () => { + test(`throws bad request when creating space with solution`, async () => { + const maxSpaces = 5; + const mockDebugLogger = createMockDebugLogger(); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + mockCallWithRequestRepository.create.mockResolvedValue(savedObject); + mockCallWithRequestRepository.find.mockResolvedValue({ + total: maxSpaces - 1, + } as any); + + const mockConfig = createMockConfig({ + enabled: true, + maxSpaces, + allowFeatureVisibility: false, + allowSolutionVisibility: false, + }); + + const client = new SpacesClient( + mockDebugLogger, + mockConfig, + mockCallWithRequestRepository, + [], + 'traditional' + ); + + await expect( + client.create({ ...spaceToCreate, solution: 'es' }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unable to create Space, the solution property can not be set when xpack.spaces.allowSolutionVisibility setting is disabled"` + ); + + expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({ + type: 'space', + page: 1, + perPage: 0, + }); + expect(mockCallWithRequestRepository.create).not.toHaveBeenCalled(); + }); + }); }); describe('#update', () => { @@ -675,6 +722,7 @@ describe('#update', () => { enabled: true, maxSpaces: 1000, allowFeatureVisibility: false, + allowSolutionVisibility: false, }); const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue(savedObject); @@ -700,6 +748,7 @@ describe('#update', () => { enabled: true, maxSpaces: 1000, allowFeatureVisibility: false, + allowSolutionVisibility: false, }); const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue(savedObject); @@ -723,6 +772,38 @@ describe('#update', () => { expect(mockCallWithRequestRepository.get).not.toHaveBeenCalled(); }); }); + + describe('when config.allowSolutionVisibility is disabled', () => { + test(`throws bad request when updating space with solution`, async () => { + const mockDebugLogger = createMockDebugLogger(); + const mockConfig = createMockConfig({ + enabled: true, + maxSpaces: 1000, + allowFeatureVisibility: false, + allowSolutionVisibility: false, + }); + const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); + mockCallWithRequestRepository.get.mockResolvedValue(savedObject); + + const client = new SpacesClient( + mockDebugLogger, + mockConfig, + mockCallWithRequestRepository, + [], + 'traditional' + ); + const id = savedObject.id; + + await expect( + client.update(id, { ...spaceToUpdate, solution: 'es' }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unable to update Space, the solution property can not be set when xpack.spaces.allowSolutionVisibility setting is disabled"` + ); + + expect(mockCallWithRequestRepository.update).not.toHaveBeenCalled(); + expect(mockCallWithRequestRepository.get).not.toHaveBeenCalled(); + }); + }); }); describe('#delete', () => { diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts index 536765e8dac47..7019520b9498c 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts @@ -136,6 +136,12 @@ export class SpacesClient implements ISpacesClient { ); } + if (Boolean(space.solution) && !this.config.allowSolutionVisibility) { + throw Boom.badRequest( + 'Unable to create Space, the solution property can not be set when xpack.spaces.allowSolutionVisibility setting is disabled' + ); + } + if (this.isServerless && space.hasOwnProperty('solution')) { throw Boom.badRequest('Unable to create Space, solution property is forbidden in serverless'); } @@ -163,6 +169,12 @@ export class SpacesClient implements ISpacesClient { ); } + if (Boolean(space.solution) && !this.config.allowSolutionVisibility) { + throw Boom.badRequest( + 'Unable to update Space, the solution property can not be set when xpack.spaces.allowSolutionVisibility setting is disabled' + ); + } + if (this.isServerless && space.hasOwnProperty('solution')) { throw Boom.badRequest('Unable to update Space, solution property is forbidden in serverless'); } diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index d222c21fc72be..b57ba097ab6a4 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -13,6 +13,7 @@ import type { UsageCollectionSetup, } from '@kbn/usage-collection-plugin/server'; +import type { SolutionView } from '../../common'; import type { PluginsSetup } from '../plugin'; import type { UsageStats, UsageStatsServiceSetup } from '../usage_stats'; @@ -46,7 +47,13 @@ async function getSpacesUsage( } const knownFeatureIds = features.getKibanaFeatures().map((feature) => feature.id); - const knownSolutions = ['classic', 'es', 'oblt', 'security', 'unset']; + const knownSolutions: Array<SolutionView | 'unset'> = [ + 'classic', + 'es', + 'oblt', + 'security', + 'unset', + ]; const resp = (await esClient.search({ index: kibanaIndex, diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json index 9f79252873307..ba13f984ef66d 100644 --- a/x-pack/plugins/spaces/tsconfig.json +++ b/x-pack/plugins/spaces/tsconfig.json @@ -37,7 +37,7 @@ "@kbn/utility-types-jest", "@kbn/security-plugin-types-public", "@kbn/cloud-plugin", - "@kbn/cloud-experiments-plugin", + "@kbn/core-analytics-browser" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/stack_alerts/README.md b/x-pack/plugins/stack_alerts/README.md index da97af05790ef..d213bbb757b0a 100644 --- a/x-pack/plugins/stack_alerts/README.md +++ b/x-pack/plugins/stack_alerts/README.md @@ -20,4 +20,4 @@ export interface IService { Each Stack RuleType is described in it's own README: -- index threshold: [`server/alert_types/index_threshold`](server/alert_types/index_threshold/README.md) +- index threshold: [`server/rule_types/index_threshold`](server/rule_types/index_threshold/README.md) diff --git a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx index bce2f3b73704f..acb65de2a9a16 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/components/data_view_select_popover.tsx @@ -207,7 +207,6 @@ export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopove setPopoverIsOpen={setDataViewPopoverOpen} onChangeDataView={onChangeDataView} onCreateDefaultAdHocDataView={onCreateDefaultAdHocDataView} - isTextBasedLangSelected={false} /> {createDataView ? ( <EuiPopoverFooter paddingSize="none"> 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 index 4f3093be152ca..e1c4554c8ed36 100644 --- 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 @@ -194,13 +194,11 @@ export const EsqlQueryExpression: React.FC< setParam('esqlQuery', q); refreshTimeFields(q); }} - expandCodeEditor={() => true} - isCodeEditorExpanded={true} onTextLangQuerySubmit={async () => {}} detectedTimestamp={detectedTimestamp} - hideMinimizeButton={true} hideRunQueryText={true} isLoading={isLoading} + hasOutline /> </EuiFormRow> <EuiSpacer /> 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 005adb7be25ef..e938be8e2f6c5 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 @@ -7,7 +7,6 @@ import React, { useMemo } from 'react'; import { - EuiBetaBadge, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, @@ -22,24 +21,6 @@ import { i18n } from '@kbn/i18n'; import { SearchType } from '../types'; import { useTriggerUiActionServices } from '../util'; -export const ExperimentalBadge = React.memo(() => ( - <EuiBetaBadge - size="s" - label={i18n.translate('xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalLabel', { - defaultMessage: 'Technical preview', - })} - tooltipContent={i18n.translate( - 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalDescription', - { - defaultMessage: - 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', - } - )} - tooltipPosition="bottom" - /> -)); -ExperimentalBadge.displayName = 'ExperimentalBadge'; - export interface QueryFormTypeProps { searchType: SearchType | null; onFormTypeSelect: (formType: SearchType | null) => void; @@ -119,11 +100,6 @@ export const QueryFormTypeChooser: React.FC<QueryFormTypeProps> = ({ <h5>{activeFormTypeItem?.label}</h5> </EuiTitle> </EuiFlexItem> - {activeFormTypeItem?.formType === SearchType.esqlQuery && ( - <EuiFlexItem> - <ExperimentalBadge /> - </EuiFlexItem> - )} </EuiFlexGroup> </EuiFlexItem> <EuiFlexItem grow={false}> @@ -172,11 +148,6 @@ export const QueryFormTypeChooser: React.FC<QueryFormTypeProps> = ({ <EuiFlexItem grow={false}> <strong>{item.label}</strong> </EuiFlexItem> - {item.formType === SearchType.esqlQuery && ( - <EuiFlexItem> - <ExperimentalBadge /> - </EuiFlexItem> - )} </EuiFlexGroup> <EuiText color="subdued" size="s"> <p>{item.description}</p> diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts index cdc053aac565b..7ca0057ac3c59 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.test.ts @@ -6,6 +6,7 @@ */ import { OnlySearchSourceRuleParams } from '../types'; +import { searchSourceCommonMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import { createSearchSourceMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { @@ -13,6 +14,7 @@ import { generateLink, updateFilterReferences, getSmallerDataViewSpec, + fetchSearchSourceQuery, } from './fetch_search_source_query'; import { createStubDataView, @@ -24,6 +26,11 @@ import { Comparator } from '../../../../common/comparator_types'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { LocatorPublic } from '@kbn/share-plugin/common'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; +import { + getErrorSource, + TaskErrorSource, +} from '@kbn/task-manager-plugin/server/task_running/errors'; const createDataView = () => { const id = 'test-id'; @@ -423,6 +430,54 @@ describe('fetchSearchSourceQuery', () => { `); expect(logger.warn).toHaveBeenCalledWith('Top hits size is capped at 100'); }); + + it('should throw user error if data view is not found', async () => { + searchSourceCommonMock.createLazy.mockImplementationOnce(() => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('index-pattern', 'abc'); + }); + + try { + await fetchSearchSourceQuery({ + ruleId: 'abc', + params: defaultParams, + // @ts-expect-error + services: { + logger, + searchSourceClient: searchSourceCommonMock, + }, + spacePrefix: '', + dateStart: new Date().toISOString(), + dateEnd: new Date().toISOString(), + }); + } catch (err) { + expect(getErrorSource(err)).toBe(TaskErrorSource.USER); + expect(err.message).toBe('Saved object [index-pattern/abc] not found'); + } + }); + + it('should re-throw error for generic errors', async () => { + searchSourceCommonMock.createLazy.mockImplementationOnce(() => { + throw new Error('fail'); + }); + + try { + await fetchSearchSourceQuery({ + ruleId: 'abc', + params: defaultParams, + // @ts-expect-error + services: { + logger, + searchSourceClient: searchSourceCommonMock, + }, + spacePrefix: '', + dateStart: new Date().toISOString(), + dateEnd: new Date().toISOString(), + }); + } catch (err) { + expect(getErrorSource(err)).not.toBeDefined(); + expect(err.message).toBe('fail'); + } + }); }); describe('generateLink', () => { diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts index 6290186653426..2e0e0397b10d7 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_search_source_query.ts @@ -22,8 +22,9 @@ import { import { isGroupAggregation } from '@kbn/triggers-actions-ui-plugin/common'; import { SharePluginStart } from '@kbn/share-plugin/server'; import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; -import { Logger } from '@kbn/core/server'; +import { Logger, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { LocatorPublic } from '@kbn/share-plugin/common'; +import { createTaskRunError, TaskErrorSource } from '@kbn/task-manager-plugin/server'; import { OnlySearchSourceRuleParams } from '../types'; import { getComparatorScript } from '../../../../common'; @@ -56,7 +57,16 @@ export async function fetchSearchSourceQuery({ const { logger, searchSourceClient } = services; const isGroupAgg = isGroupAggregation(params.termField); const isCountAgg = isCountAggregation(params.aggType); - const initialSearchSource = await searchSourceClient.createLazy(params.searchConfiguration); + + let initialSearchSource; + try { + initialSearchSource = await searchSourceClient.createLazy(params.searchConfiguration); + } catch (err) { + if (SavedObjectsErrorHelpers.isNotFoundError(err)) { + throw createTaskRunError(err, TaskErrorSource.USER); + } + throw err; + } const index = initialSearchSource.getField('index') as DataView; const { searchSource, filterToExcludeHitsFromPreviousRun } = await updateSearchSource( diff --git a/x-pack/plugins/stack_alerts/tsconfig.json b/x-pack/plugins/stack_alerts/tsconfig.json index f187bf466e375..60b02c4946b4b 100644 --- a/x-pack/plugins/stack_alerts/tsconfig.json +++ b/x-pack/plugins/stack_alerts/tsconfig.json @@ -53,7 +53,8 @@ "@kbn/search-types", "@kbn/alerting-comparators", "@kbn/task-manager-plugin", - "@kbn/core-logging-server-mocks" + "@kbn/core-logging-server-mocks", + "@kbn/core-saved-objects-server" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/stack_connectors/common/gemini/schema.ts b/x-pack/plugins/stack_connectors/common/gemini/schema.ts index 6cccea399e957..543070c705907 100644 --- a/x-pack/plugins/stack_connectors/common/gemini/schema.ts +++ b/x-pack/plugins/stack_connectors/common/gemini/schema.ts @@ -72,6 +72,7 @@ export const InvokeAIRawActionParamsSchema = schema.object({ stopSequences: schema.maybe(schema.arrayOf(schema.string())), signal: schema.maybe(schema.any()), timeout: schema.maybe(schema.number()), + tools: schema.maybe(schema.arrayOf(schema.any())), }); export const InvokeAIActionResponseSchema = schema.object({ diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx index 26ec00f07e684..0d1986ab9ceca 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx @@ -304,6 +304,16 @@ describe('ServiceNowITSMParamsFields renders', () => { expect(wrapper.find('input[data-test-subj="short_descriptionInput"]').exists()).toBeFalsy(); }); + test('shows incident details when action group is undefined', () => { + const newProps = { + ...defaultProps, + selectedActionGroupId: undefined, + }; + const wrapper = mountWithIntl(<ServiceNowITSMParamsFields {...newProps} />); + expect(wrapper.find('input[data-test-subj="short_descriptionInput"]').exists()).toBeTruthy(); + expect(wrapper.find('input[data-test-subj="correlation_idInput"]').exists()).toBeTruthy(); + }); + test('A short description change triggers editAction', () => { const wrapper = mountWithIntl( <ServiceNowITSMParamsFields diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx index 613b72a0198a4..51cbd8f46fc92 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx @@ -131,9 +131,6 @@ const ServiceNowParamsFields: React.FunctionComponent< const actionConnectorRef = useRef(actionConnector?.id ?? ''); - const showAllIncidentDetails = - (selectedActionGroupId && selectedActionGroupId !== ACTION_GROUP_RECOVERED) || - isTestTriggerAction; const showOnlyCorrelationId = (selectedActionGroupId && selectedActionGroupId === ACTION_GROUP_RECOVERED) || isTestResolveAction; @@ -287,7 +284,16 @@ const ServiceNowParamsFields: React.FunctionComponent< <h3>{i18n.INCIDENT}</h3> </EuiTitle> <EuiSpacer size="m" /> - {showAllIncidentDetails && ( + {showOnlyCorrelationId ? ( + <CorrelationIdField + index={index} + messageVariables={messageVariables} + correlationId={incident.correlation_id} + editSubActionProperty={editSubActionProperty} + isRequired={showOnlyCorrelationId} + errors={errors} + /> + ) : ( <> <EuiFormRow fullWidth label={i18n.URGENCY_LABEL}> <EuiSelect @@ -467,16 +473,6 @@ const ServiceNowParamsFields: React.FunctionComponent< )} </> )} - {showOnlyCorrelationId && ( - <CorrelationIdField - index={index} - messageVariables={messageVariables} - correlationId={incident.correlation_id} - editSubActionProperty={editSubActionProperty} - isRequired={showOnlyCorrelationId} - errors={errors} - /> - )} </> ); }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts b/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts index 5bf28c830b679..6cb4671b7aeeb 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts @@ -299,9 +299,10 @@ export class GeminiConnector extends SubActionConnector<Config, Secrets> { temperature = 0, signal, timeout, + tools, }: InvokeAIRawActionParams): Promise<InvokeAIRawActionResponse> { const res = await this.runApi({ - body: JSON.stringify(formatGeminiPayload(messages, temperature)), + body: JSON.stringify({ ...formatGeminiPayload(messages, temperature), tools }), model, signal, timeout, diff --git a/x-pack/plugins/task_manager/kibana.jsonc b/x-pack/plugins/task_manager/kibana.jsonc index e1141bbc58377..33edc225e42c1 100644 --- a/x-pack/plugins/task_manager/kibana.jsonc +++ b/x-pack/plugins/task_manager/kibana.jsonc @@ -11,6 +11,7 @@ "task_manager" ], "optionalPlugins": [ + "cloud", "usageCollection" ] } diff --git a/x-pack/plugins/task_manager/server/MONITORING.md b/x-pack/plugins/task_manager/server/MONITORING.md index 02946b9b3e53f..c4e66ab92bad5 100644 --- a/x-pack/plugins/task_manager/server/MONITORING.md +++ b/x-pack/plugins/task_manager/server/MONITORING.md @@ -50,7 +50,7 @@ The root `timestamp` is the time in which the summary was exposed (either to the Follow this step-by-step guide to make sense of the stats: https://www.elastic.co/guide/en/kibana/master/task-manager-troubleshooting.html#task-manager-diagnosing-root-cause #### The Configuration Section -The `configuration` section summarizes Task Manager's current configuration, including dynamic configurations which change over time, such as `poll_interval` and `max_workers` which adjust in reaction to changing load on the system. +The `configuration` section summarizes Task Manager's current configuration, including dynamic configurations which change over time, such as `poll_interval` and `capacity` which adjust in reaction to changing load on the system. These are "Hot" stats which are updated whenever a change happens in the configuration. @@ -69,8 +69,8 @@ The `runtime` tracks Task Manager's performance as it runs, making note of task These include: - The time it takes a task to run (p50, p90, p95 & p99, using a configurable running average window, `50` by default) - The average _drift_ that tasks experience (p50, p90, p95 & p99, using the same configurable running average window as above). Drift tells us how long after a task's scheduled a task typically executes. - - The average _load_ (p50, p90, p95 & p99, using the same configurable running average window as above). Load tells us what percentage of workers is in use at the end of each polling cycle. - - The polling rate (the timestamp of the last time a polling cycle completed), the polling health stats (number of version clashes and mismatches) and the result [`No tasks | Filled task pool | Unexpectedly ran out of workers`] frequency the past 50 polling cycles (using the same window size as the one used for running averages) + - The average _load_ (p50, p90, p95 & p99, using the same configurable running average window as above). Load tells us what percentage of capacity is in use at the end of each polling cycle. + - The polling rate (the timestamp of the last time a polling cycle completed), the polling health stats (number of version clashes and mismatches) and the result [`No tasks | Filled task pool | Unexpectedly ran out of capacity`] frequency the past 50 polling cycles (using the same window size as the one used for running averages) - The `Success | Retry | Failure ratio` by task type. This is different than the workload stats which tell you what's in the queue, but ca't keep track of retries and of non recurring tasks as they're wiped off the index when completed. These are "Hot" stats which are updated reactively as Tasks are executed and interacted with. diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index bb59a73a305d6..0268d61c9b975 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { configSchema } from './config'; +import { configSchema, CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET } from './config'; describe('config validation', () => { test('task manager defaults', () => { @@ -23,7 +23,6 @@ describe('config validation', () => { "warn_threshold": 5000, }, "max_attempts": 3, - "max_workers": 10, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, "monitored_stats_health_verbose_log": Object { @@ -81,7 +80,6 @@ describe('config validation', () => { "warn_threshold": 5000, }, "max_attempts": 3, - "max_workers": 10, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, "monitored_stats_health_verbose_log": Object { @@ -137,7 +135,6 @@ describe('config validation', () => { "warn_threshold": 5000, }, "max_attempts": 3, - "max_workers": 10, "metrics_reset_interval": 30000, "monitored_aggregated_stats_refresh_rate": 60000, "monitored_stats_health_verbose_log": Object { @@ -245,4 +242,14 @@ describe('config validation', () => { test('any claim strategy is valid', () => { configSchema.validate({ claim_strategy: 'anything!' }); }); + + test('default claim strategy defaults poll interval to 3000ms', () => { + const result = configSchema.validate({ claim_strategy: CLAIM_STRATEGY_DEFAULT }); + expect(result.poll_interval).toEqual(3000); + }); + + test('mget claim strategy defaults poll interval to 500ms', () => { + const result = configSchema.validate({ claim_strategy: CLAIM_STRATEGY_MGET }); + expect(result.poll_interval).toEqual(500); + }); }); diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index eec63c5be489c..3e5da0df9be99 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -8,8 +8,12 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const MAX_WORKERS_LIMIT = 100; +export const DEFAULT_CAPACITY = 10; +export const MAX_CAPACITY = 50; +export const MIN_CAPACITY = 5; export const DEFAULT_MAX_WORKERS = 10; export const DEFAULT_POLL_INTERVAL = 3000; +export const MGET_DEFAULT_POLL_INTERVAL = 500; export const DEFAULT_VERSION_CONFLICT_THRESHOLD = 80; export const DEFAULT_MAX_EPHEMERAL_REQUEST_CAPACITY = MAX_WORKERS_LIMIT; @@ -64,6 +68,8 @@ const requestTimeoutsConfig = schema.object({ export const configSchema = schema.object( { allow_reading_invalid_state: schema.boolean({ defaultValue: true }), + /* The number of normal cost tasks that this Kibana instance will run simultaneously */ + capacity: schema.maybe(schema.number({ min: MIN_CAPACITY, max: MAX_CAPACITY })), ephemeral_tasks: schema.object({ enabled: schema.boolean({ defaultValue: false }), /* How many requests can Task Manager buffer before it rejects new requests. */ @@ -81,11 +87,12 @@ export const configSchema = schema.object( min: 1, }), /* The maximum number of tasks that this Kibana instance will run simultaneously. */ - max_workers: schema.number({ - defaultValue: DEFAULT_MAX_WORKERS, - // disable the task manager rather than trying to specify it with 0 workers - min: 1, - }), + max_workers: schema.maybe( + schema.number({ + // disable the task manager rather than trying to specify it with 0 workers + min: 1, + }) + ), /* The interval at which monotonically increasing metrics counters will reset */ metrics_reset_interval: schema.number({ defaultValue: DEFAULT_METRICS_RESET_INTERVAL, @@ -127,10 +134,18 @@ export const configSchema = schema.object( default: taskExecutionFailureThresholdSchema, }), /* How often, in milliseconds, the task manager will look for more work. */ - poll_interval: schema.number({ - defaultValue: DEFAULT_POLL_INTERVAL, - min: 100, - }), + poll_interval: schema.conditional( + schema.siblingRef('claim_strategy'), + CLAIM_STRATEGY_MGET, + schema.number({ + defaultValue: MGET_DEFAULT_POLL_INTERVAL, + min: 100, + }), + schema.number({ + defaultValue: DEFAULT_POLL_INTERVAL, + min: 100, + }) + ), /* How many requests can Task Manager buffer before it rejects new requests. */ request_capacity: schema.number({ // a nice round contrived number, feel free to change as we learn how it behaves 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 19cfa2943502c..2a6f1bf8c33b8 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 @@ -18,7 +18,7 @@ import { v4 as uuidv4 } from 'uuid'; import { asTaskPollingCycleEvent, asTaskRunEvent, TaskPersistence } from './task_events'; import { TaskRunResult } from './task_running'; import { TaskPoolRunResult } from './task_pool'; -import { TaskPoolMock } from './task_pool.mock'; +import { TaskPoolMock } from './task_pool/task_pool.mock'; import { executionContextServiceMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from './mocks'; @@ -45,7 +45,6 @@ describe('EphemeralTaskLifecycle', () => { definitions: new TaskTypeDictionary(taskManagerLogger), executionContext, config: { - max_workers: 10, max_attempts: 9, poll_interval: 6000000, version_conflict_threshold: 80, @@ -156,7 +155,7 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.attemptToRun(task)).toMatchObject(asOk(task)); poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); lifecycleEvent$.next( @@ -179,7 +178,7 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.attemptToRun(task)).toMatchObject(asOk(task)); poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); lifecycleEvent$.next( @@ -216,7 +215,7 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.attemptToRun(tasks[2])).toMatchObject(asOk(tasks[2])); poolCapacity.mockReturnValue({ - availableWorkers: 2, + availableCapacity: 2, }); lifecycleEvent$.next( @@ -256,9 +255,9 @@ describe('EphemeralTaskLifecycle', () => { // pool has capacity for both poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); - pool.getOccupiedWorkersByType.mockReturnValue(0); + pool.getUsedCapacityByType.mockReturnValue(0); lifecycleEvent$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed })) @@ -296,10 +295,10 @@ describe('EphemeralTaskLifecycle', () => { // pool has capacity in general poolCapacity.mockReturnValue({ - availableWorkers: 2, + availableCapacity: 2, }); // but when we ask how many it has occupied by type - wee always have one worker already occupied by that type - pool.getOccupiedWorkersByType.mockReturnValue(1); + pool.getUsedCapacityByType.mockReturnValue(1); lifecycleEvent$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed })) @@ -308,7 +307,7 @@ describe('EphemeralTaskLifecycle', () => { expect(pool.run).toHaveBeenCalledTimes(0); // now we release the worker in the pool and cause another cycle in the epheemral queue - pool.getOccupiedWorkersByType.mockReturnValue(0); + pool.getUsedCapacityByType.mockReturnValue(0); lifecycleEvent$.next( asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed })) ); @@ -356,9 +355,9 @@ describe('EphemeralTaskLifecycle', () => { // pool has capacity for all poolCapacity.mockReturnValue({ - availableWorkers: 10, + availableCapacity: 10, }); - pool.getOccupiedWorkersByType.mockReturnValue(0); + pool.getUsedCapacityByType.mockReturnValue(0); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); @@ -389,19 +388,19 @@ describe('EphemeralTaskLifecycle', () => { expect(ephemeralTaskLifecycle.queuedTasks).toBe(3); poolCapacity.mockReturnValue({ - availableWorkers: 1, + availableCapacity: 1, }); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); expect(ephemeralTaskLifecycle.queuedTasks).toBe(2); poolCapacity.mockReturnValue({ - availableWorkers: 1, + availableCapacity: 1, }); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); expect(ephemeralTaskLifecycle.queuedTasks).toBe(1); poolCapacity.mockReturnValue({ - availableWorkers: 1, + availableCapacity: 1, }); lifecycleEvent$.next(asTaskPollingCycleEvent(asOk({ result: FillPoolResult.NoTasksClaimed }))); expect(ephemeralTaskLifecycle.queuedTasks).toBe(0); diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts index 37cc166ece211..c7ee267b848e5 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts @@ -143,13 +143,13 @@ export class EphemeralTaskLifecycle { taskType && this.definitions.get(taskType)?.maxConcurrency ? Math.max( Math.min( - this.pool.availableWorkers, + this.pool.availableCapacity(), this.definitions.get(taskType)!.maxConcurrency! - - this.pool.getOccupiedWorkersByType(taskType) + this.pool.getUsedCapacityByType(taskType) ), 0 ) - : this.pool.availableWorkers; + : this.pool.availableCapacity(); private emitEvent = (event: TaskLifecycleEvent) => { this.events$.next(event); diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 8d50c37adda0b..965df090911fd 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -55,9 +55,6 @@ export type { export const config: PluginConfigDescriptor<TaskManagerConfig> = { schema: configSchema, - exposeToUsage: { - max_workers: true, - }, deprecations: ({ deprecate }) => { return [ deprecate('ephemeral_tasks.enabled', 'a future version', { @@ -68,6 +65,10 @@ export const config: PluginConfigDescriptor<TaskManagerConfig> = { level: 'warning', message: `Configuring "xpack.task_manager.ephemeral_tasks.request_capacity" is deprecated and will be removed in a future version. Remove this setting to increase task execution resiliency.`, }), + deprecate('max_workers', 'a future version', { + level: 'warning', + message: `Configuring "xpack.task_manager.max_workers" is deprecated and will be removed in a future version. Remove this setting and use "xpack.task_manager.capacity" instead.`, + }), (settings, fromPath, addDeprecation) => { const taskManager = get(settings, fromPath); if (taskManager?.index) { diff --git a/x-pack/plugins/task_manager/server/integration_tests/__snapshots__/task_cost_check.test.ts.snap b/x-pack/plugins/task_manager/server/integration_tests/__snapshots__/task_cost_check.test.ts.snap new file mode 100644 index 0000000000000..e59912ed91905 --- /dev/null +++ b/x-pack/plugins/task_manager/server/integration_tests/__snapshots__/task_cost_check.test.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Task cost checks detects tasks with cost definitions 1`] = ` +Array [ + Object { + "cost": 10, + "taskType": "alerting:siem.indicatorRule", + }, +] +`; diff --git a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts index c0939b5b31667..cc16b8d0544cf 100644 --- a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts @@ -35,164 +35,362 @@ describe('managed configuration', () => { }, }; - beforeEach(async () => { - jest.resetAllMocks(); - clock = sinon.useFakeTimers(); - - const context = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - max_workers: 10, - max_attempts: 9, - poll_interval: 3000, - allow_reading_invalid_state: false, - version_conflict_threshold: 80, - monitored_aggregated_stats_refresh_rate: 60000, - monitored_stats_health_verbose_log: { - enabled: false, - level: 'debug' as const, - warn_delayed_task_start_in_seconds: 60, - }, - monitored_stats_required_freshness: 4000, - monitored_stats_running_average_window: 50, - request_capacity: 1000, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, - }, - custom: {}, - }, - ephemeral_tasks: { - enabled: true, - request_capacity: 10, - }, - unsafe: { - exclude_task_types: [], - authenticate_background_task_utilization: true, - }, - event_loop_delay: { - monitor: true, - warn_threshold: 5000, - }, - worker_utilization_running_average_window: 5, - metrics_reset_interval: 3000, - claim_strategy: 'default', - request_timeouts: { - update_by_query: 1000, - }, + afterEach(() => clock.restore()); + + describe('managed poll interval', () => { + beforeEach(async () => { + jest.resetAllMocks(); + clock = sinon.useFakeTimers(); + + const context = coreMock.createPluginInitializerContext<TaskManagerConfig>({ + capacity: 10, + max_attempts: 9, + poll_interval: 3000, + allow_reading_invalid_state: false, + version_conflict_threshold: 80, + monitored_aggregated_stats_refresh_rate: 60000, + monitored_stats_health_verbose_log: { + enabled: false, + level: 'debug' as const, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 4000, + monitored_stats_running_average_window: 50, + request_capacity: 1000, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: true, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + authenticate_background_task_utilization: true, + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, + worker_utilization_running_average_window: 5, + metrics_reset_interval: 3000, + claim_strategy: 'default', + request_timeouts: { + update_by_query: 1000, + }, + }); + logger = context.logger.get('taskManager'); + + const taskManager = new TaskManagerPlugin(context); + ( + await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) + ).registerTaskDefinitions({ + foo: { + title: 'Foo', + createTaskRunner: jest.fn(), + }, + }); + + const coreStart = coreMock.createStart(); + coreStart.elasticsearch = esStart; + esStart.client.asInternalUser.child.mockReturnValue( + esStart.client.asInternalUser as unknown as Client + ); + coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); + taskManagerStart = await taskManager.start(coreStart, {}); + + // force rxjs timers to fire when they are scheduled for setTimeout(0) as the + // sinon fake timers cause them to stall + clock.tick(0); }); - logger = context.logger.get('taskManager'); - - const taskManager = new TaskManagerPlugin(context); - ( - await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) - ).registerTaskDefinitions({ - foo: { - title: 'Foo', - createTaskRunner: jest.fn(), - }, + + test('should increase poll interval when Elasticsearch returns 429 error', async () => { + savedObjectsClient.create.mockRejectedValueOnce( + SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') + ); + + // Cause "too many requests" error to be thrown + await expect( + taskManagerStart.schedule({ + taskType: 'foo', + state: {}, + params: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); }); - const coreStart = coreMock.createStart(); - coreStart.elasticsearch = esStart; - esStart.client.asInternalUser.child.mockReturnValue( - esStart.client.asInternalUser as unknown as Client - ); - coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); - taskManagerStart = await taskManager.start(coreStart); - - // force rxjs timers to fire when they are scheduled for setTimeout(0) as the - // sinon fake timers cause them to stall - clock.tick(0); - }); + test('should increase poll interval when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { + const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked<Client>; + childEsClient.search.mockImplementationOnce(async () => { + throw inlineScriptError; + }); - afterEach(() => clock.restore()); + await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( + `"cannot execute [inline] scripts\\" error"` + ); - test('should lower max workers when Elasticsearch returns 429 error', async () => { - savedObjectsClient.create.mockRejectedValueOnce( - SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') - ); - - // Cause "too many requests" error to be thrown - await expect( - taskManagerStart.schedule({ - taskType: 'foo', - state: {}, - params: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - - expect(logger.warn).toHaveBeenCalledWith( - 'Max workers configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Max workers configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task pool now using 10 as the max worker value'); - }); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); - test('should increase poll interval when Elasticsearch returns 429 error', async () => { - savedObjectsClient.create.mockRejectedValueOnce( - SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') - ); - - // Cause "too many requests" error to be thrown - await expect( - taskManagerStart.schedule({ - taskType: 'foo', - state: {}, - params: {}, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - - expect(logger.warn).toHaveBeenCalledWith( - 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); + expect(logger.warn).toHaveBeenCalledWith( + 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); + }); }); - test('should lower max workers when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { - const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked<Client>; - childEsClient.search.mockImplementationOnce(async () => { - throw inlineScriptError; + describe('managed capacity with default claim strategy', () => { + beforeEach(async () => { + jest.resetAllMocks(); + clock = sinon.useFakeTimers(); + + const context = coreMock.createPluginInitializerContext<TaskManagerConfig>({ + capacity: 10, + max_attempts: 9, + poll_interval: 3000, + allow_reading_invalid_state: false, + version_conflict_threshold: 80, + monitored_aggregated_stats_refresh_rate: 60000, + monitored_stats_health_verbose_log: { + enabled: false, + level: 'debug' as const, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 4000, + monitored_stats_running_average_window: 50, + request_capacity: 1000, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: true, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + authenticate_background_task_utilization: true, + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, + worker_utilization_running_average_window: 5, + metrics_reset_interval: 3000, + claim_strategy: 'default', + request_timeouts: { + update_by_query: 1000, + }, + }); + logger = context.logger.get('taskManager'); + + const taskManager = new TaskManagerPlugin(context); + ( + await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) + ).registerTaskDefinitions({ + foo: { + title: 'Foo', + createTaskRunner: jest.fn(), + }, + }); + + const coreStart = coreMock.createStart(); + coreStart.elasticsearch = esStart; + esStart.client.asInternalUser.child.mockReturnValue( + esStart.client.asInternalUser as unknown as Client + ); + coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); + taskManagerStart = await taskManager.start(coreStart, {}); + + // force rxjs timers to fire when they are scheduled for setTimeout(0) as the + // sinon fake timers cause them to stall + clock.tick(0); }); - await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( - `"cannot execute [inline] scripts\\" error"` - ); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - - expect(logger.warn).toHaveBeenCalledWith( - 'Max workers configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Max workers configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task pool now using 10 as the max worker value'); + test('should lower capacity when Elasticsearch returns 429 error', async () => { + savedObjectsClient.create.mockRejectedValueOnce( + SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') + ); + + // Cause "too many requests" error to be thrown + await expect( + taskManagerStart.schedule({ + taskType: 'foo', + state: {}, + params: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 10 as the max worker value which is based on a capacity of 10' + ); + }); + + test('should lower capacity when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { + const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked<Client>; + childEsClient.search.mockImplementationOnce(async () => { + throw inlineScriptError; + }); + + await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( + `"cannot execute [inline] scripts\\" error"` + ); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 10 as the max worker value which is based on a capacity of 10' + ); + }); }); - test('should increase poll interval when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { - const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked<Client>; - childEsClient.search.mockImplementationOnce(async () => { - throw inlineScriptError; + describe('managed capacity with mget claim strategy', () => { + beforeEach(async () => { + jest.resetAllMocks(); + clock = sinon.useFakeTimers(); + + const context = coreMock.createPluginInitializerContext<TaskManagerConfig>({ + capacity: 10, + max_attempts: 9, + poll_interval: 3000, + allow_reading_invalid_state: false, + version_conflict_threshold: 80, + monitored_aggregated_stats_refresh_rate: 60000, + monitored_stats_health_verbose_log: { + enabled: false, + level: 'debug' as const, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 4000, + monitored_stats_running_average_window: 50, + request_capacity: 1000, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: true, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + authenticate_background_task_utilization: true, + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, + worker_utilization_running_average_window: 5, + metrics_reset_interval: 3000, + claim_strategy: 'unsafe_mget', + request_timeouts: { + update_by_query: 1000, + }, + }); + logger = context.logger.get('taskManager'); + + const taskManager = new TaskManagerPlugin(context); + ( + await taskManager.setup(coreMock.createSetup(), { usageCollection: undefined }) + ).registerTaskDefinitions({ + foo: { + title: 'Foo', + createTaskRunner: jest.fn(), + }, + }); + + const coreStart = coreMock.createStart(); + coreStart.elasticsearch = esStart; + esStart.client.asInternalUser.child.mockReturnValue( + esStart.client.asInternalUser as unknown as Client + ); + coreStart.savedObjects.createInternalRepository.mockReturnValue(savedObjectsClient); + taskManagerStart = await taskManager.start(coreStart, {}); + + // force rxjs timers to fire when they are scheduled for setTimeout(0) as the + // sinon fake timers cause them to stall + clock.tick(0); }); - await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( - `"cannot execute [inline] scripts\\" error"` - ); + test('should lower capacity when Elasticsearch returns 429 error', async () => { + savedObjectsClient.create.mockRejectedValueOnce( + SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b') + ); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); + // Cause "too many requests" error to be thrown + await expect( + taskManagerStart.schedule({ + taskType: 'foo', + state: {}, + params: {}, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Too Many Requests"`); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 20 as the max allowed cost which is based on a capacity of 10' + ); + }); - expect(logger.warn).toHaveBeenCalledWith( - 'Poll interval configuration is temporarily increased after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - expect(logger.debug).toHaveBeenCalledWith( - 'Poll interval configuration changing from 3000 to 3600 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' - ); - expect(logger.debug).toHaveBeenCalledWith('Task poller now using interval of 3600ms'); + test('should lower capacity when Elasticsearch returns "cannot execute [inline] scripts" error', async () => { + const childEsClient = esStart.client.asInternalUser.child({}) as jest.Mocked<Client>; + childEsClient.search.mockImplementationOnce(async () => { + throw inlineScriptError; + }); + + await expect(taskManagerStart.fetch({})).rejects.toThrowErrorMatchingInlineSnapshot( + `"cannot execute [inline] scripts\\" error"` + ); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Capacity configuration changing from 10 to 8 after seeing 1 "too many request" and/or "execute [inline] script" error(s)' + ); + expect(logger.debug).toHaveBeenCalledWith( + 'Task pool now using 20 as the max allowed cost which is based on a capacity of 10' + ); + }); }); }); diff --git a/x-pack/plugins/task_manager/server/integration_tests/task_cost_check.test.ts b/x-pack/plugins/task_manager/server/integration_tests/task_cost_check.test.ts new file mode 100644 index 0000000000000..96678f714ac69 --- /dev/null +++ b/x-pack/plugins/task_manager/server/integration_tests/task_cost_check.test.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 TestElasticsearchUtils, + type TestKibanaUtils, +} from '@kbn/core-test-helpers-kbn-server'; +import { TaskCost, TaskDefinition } from '../task'; +import { setupTestServers } from './lib'; +import { TaskTypeDictionary } from '../task_type_dictionary'; + +jest.mock('../task_type_dictionary', () => { + const actual = jest.requireActual('../task_type_dictionary'); + return { + ...actual, + TaskTypeDictionary: jest.fn().mockImplementation((opts) => { + return new actual.TaskTypeDictionary(opts); + }), + }; +}); + +// Notify response-ops if a task sets a cost to something other than `Normal` +describe('Task cost checks', () => { + let esServer: TestElasticsearchUtils; + let kibanaServer: TestKibanaUtils; + let taskTypeDictionary: TaskTypeDictionary; + + beforeAll(async () => { + const setupResult = await setupTestServers(); + esServer = setupResult.esServer; + kibanaServer = setupResult.kibanaServer; + + const mockedTaskTypeDictionary = jest.requireMock('../task_type_dictionary'); + expect(mockedTaskTypeDictionary.TaskTypeDictionary).toHaveBeenCalledTimes(1); + taskTypeDictionary = mockedTaskTypeDictionary.TaskTypeDictionary.mock.results[0].value; + }); + + afterAll(async () => { + if (kibanaServer) { + await kibanaServer.stop(); + } + if (esServer) { + await esServer.stop(); + } + }); + + it('detects tasks with cost definitions', async () => { + const taskTypes = taskTypeDictionary.getAllDefinitions(); + const taskTypesWithCost = taskTypes + .map((taskType: TaskDefinition) => + !!taskType.cost ? { taskType: taskType.type, cost: taskType.cost } : null + ) + .filter( + (tt: { taskType: string; cost: TaskCost } | null) => + null != tt && tt.cost !== TaskCost.Normal + ); + expect(taskTypesWithCost).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts b/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts index fc2f34701e3c1..49c68459982ba 100644 --- a/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts +++ b/x-pack/plugins/task_manager/server/lib/calculate_health_status.test.ts @@ -16,7 +16,6 @@ Date.now = jest.fn().mockReturnValue(new Date(now)); const logger = loggingSystemMock.create().get(); const config = { enabled: true, - max_workers: 10, index: 'foo', max_attempts: 9, poll_interval: 3000, @@ -73,6 +72,8 @@ const getStatsWithTimestamp = ({ configuration: { timestamp, value: { + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, monitored_stats_running_average_window: 50, @@ -84,7 +85,6 @@ const getStatsWithTimestamp = ({ }, }, poll_interval: 3000, - max_workers: 10, }, status: HealthStatus.OK, }, @@ -213,24 +213,29 @@ const getStatsWithTimestamp = ({ timestamp, value: { count: 2, + cost: 4, task_types: { taskType1: { count: 1, + cost: 2, status: { idle: 1, }, }, taskType2: { count: 1, + cost: 2, status: { idle: 1, }, }, }, non_recurring: 2, + non_recurring_cost: 4, owner_ids: 0, schedule: [['5m', 2]], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimated_schedule_density: [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts index b1d6ce92c323a..a93da63ae693a 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.test.ts @@ -13,6 +13,7 @@ import { ADJUST_THROUGHPUT_INTERVAL, } from './create_managed_configuration'; import { mockLogger } from '../test_utils'; +import { CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET, TaskManagerConfig } from '../config'; describe('createManagedConfiguration()', () => { let clock: sinon.SinonFakeTimers; @@ -26,51 +27,141 @@ describe('createManagedConfiguration()', () => { afterEach(() => clock.restore()); test('returns observables with initialized values', async () => { - const maxWorkersSubscription = jest.fn(); + const capacitySubscription = jest.fn(); const pollIntervalSubscription = jest.fn(); - const { maxWorkersConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ logger, errors$: new Subject<Error>(), - startingMaxWorkers: 1, - startingPollInterval: 2, + config: { + capacity: 20, + poll_interval: 2, + } as TaskManagerConfig, }); - maxWorkersConfiguration$.subscribe(maxWorkersSubscription); + capacityConfiguration$.subscribe(capacitySubscription); pollIntervalConfiguration$.subscribe(pollIntervalSubscription); - expect(maxWorkersSubscription).toHaveBeenCalledTimes(1); - expect(maxWorkersSubscription).toHaveBeenNthCalledWith(1, 1); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 20); expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); }); + test('uses max_workers config as capacity if only max workers is defined', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + logger, + errors$: new Subject<Error>(), + config: { + max_workers: 10, + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 10); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + }); + + test('uses max_workers config as capacity but does not exceed MAX_CAPACITY', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + logger, + errors$: new Subject<Error>(), + config: { + max_workers: 1000, + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 50); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + }); + + test('uses provided defaultCapacity if neither capacity nor max_workers is defined', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + defaultCapacity: 500, + logger, + errors$: new Subject<Error>(), + config: { + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 500); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + }); + + test('logs warning and uses capacity config if both capacity and max_workers is defined', async () => { + const capacitySubscription = jest.fn(); + const pollIntervalSubscription = jest.fn(); + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + logger, + errors$: new Subject<Error>(), + config: { + capacity: 30, + max_workers: 10, + poll_interval: 2, + } as TaskManagerConfig, + }); + capacityConfiguration$.subscribe(capacitySubscription); + pollIntervalConfiguration$.subscribe(pollIntervalSubscription); + expect(capacitySubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenNthCalledWith(1, 30); + expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); + expect(pollIntervalSubscription).toHaveBeenNthCalledWith(1, 2); + expect(logger.warn).toHaveBeenCalledWith( + `Both \"xpack.task_manager.capacity\" and \"xpack.task_manager.max_workers\" configs are set, max_workers will be ignored in favor of capacity and the setting should be removed.` + ); + }); + test(`skips errors that aren't about too many requests`, async () => { - const maxWorkersSubscription = jest.fn(); + const capacitySubscription = jest.fn(); const pollIntervalSubscription = jest.fn(); const errors$ = new Subject<Error>(); - const { maxWorkersConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ + const { capacityConfiguration$, pollIntervalConfiguration$ } = createManagedConfiguration({ errors$, logger, - startingMaxWorkers: 100, - startingPollInterval: 100, + config: { + capacity: 10, + poll_interval: 100, + } as TaskManagerConfig, }); - maxWorkersConfiguration$.subscribe(maxWorkersSubscription); + capacityConfiguration$.subscribe(capacitySubscription); pollIntervalConfiguration$.subscribe(pollIntervalSubscription); errors$.next(new Error('foo')); clock.tick(ADJUST_THROUGHPUT_INTERVAL); - expect(maxWorkersSubscription).toHaveBeenCalledTimes(1); + expect(capacitySubscription).toHaveBeenCalledTimes(1); expect(pollIntervalSubscription).toHaveBeenCalledTimes(1); }); - describe('maxWorker configuration', () => { - function setupScenario(startingMaxWorkers: number) { + describe('capacity configuration', () => { + function setupScenario( + startingCapacity: number, + claimStrategy: string = CLAIM_STRATEGY_DEFAULT + ) { const errors$ = new Subject<Error>(); const subscription = jest.fn(); - const { maxWorkersConfiguration$ } = createManagedConfiguration({ + const { capacityConfiguration$ } = createManagedConfiguration({ errors$, - startingMaxWorkers, logger, - startingPollInterval: 1, + config: { + capacity: startingCapacity, + poll_interval: 1, + claim_strategy: claimStrategy, + } as TaskManagerConfig, }); - maxWorkersConfiguration$.subscribe(subscription); + capacityConfiguration$.subscribe(subscription); return { subscription, errors$ }; } @@ -81,66 +172,103 @@ describe('createManagedConfiguration()', () => { afterEach(() => clock.restore()); - test('should decrease configuration at the next interval when an error is emitted', async () => { - const { subscription, errors$ } = setupScenario(100); - errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); - clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); - expect(subscription).toHaveBeenCalledTimes(1); - clock.tick(1); - expect(subscription).toHaveBeenCalledTimes(2); - expect(subscription).toHaveBeenNthCalledWith(2, 80); - }); + describe('default claim strategy', () => { + test('should decrease configuration at the next interval when an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); - test('should log a warning when the configuration changes from the starting value', async () => { - const { errors$ } = setupScenario(100); - errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); - clock.tick(ADJUST_THROUGHPUT_INTERVAL); - expect(logger.warn).toHaveBeenCalledWith( - 'Max workers configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' - ); - }); + test('should log a warning when the configuration changes from the starting value', async () => { + const { errors$ } = setupScenario(10); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + }); - test('should increase configuration back to normal incrementally after an error is emitted', async () => { - const { subscription, errors$ } = setupScenario(100); - errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); - clock.tick(ADJUST_THROUGHPUT_INTERVAL * 10); - expect(subscription).toHaveBeenNthCalledWith(2, 80); - expect(subscription).toHaveBeenNthCalledWith(3, 84); - // 88.2- > 89 from Math.ceil - expect(subscription).toHaveBeenNthCalledWith(4, 89); - expect(subscription).toHaveBeenNthCalledWith(5, 94); - expect(subscription).toHaveBeenNthCalledWith(6, 99); - // 103.95 -> 100 from Math.min with starting value - expect(subscription).toHaveBeenNthCalledWith(7, 100); - // No new calls due to value not changing and usage of distinctUntilChanged() - expect(subscription).toHaveBeenCalledTimes(7); + test('should increase configuration back to normal incrementally after an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL * 10); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 9); + expect(subscription).toHaveBeenNthCalledWith(4, 10); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(4); + }); + + test('should keep reducing configuration when errors keep emitting until it reaches minimum', async () => { + const { subscription, errors$ } = setupScenario(10); + for (let i = 0; i < 20; i++) { + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + } + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 6); + expect(subscription).toHaveBeenNthCalledWith(4, 4); + expect(subscription).toHaveBeenNthCalledWith(5, 3); + expect(subscription).toHaveBeenNthCalledWith(6, 2); + expect(subscription).toHaveBeenNthCalledWith(7, 1); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(7); + }); }); - test('should keep reducing configuration when errors keep emitting', async () => { - const { subscription, errors$ } = setupScenario(100); - for (let i = 0; i < 20; i++) { + describe('mget claim strategy', () => { + test('should decrease configuration at the next interval when an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL - 1); + expect(subscription).toHaveBeenCalledTimes(1); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + clock.tick(1); + expect(subscription).toHaveBeenCalledTimes(2); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + }); + + test('should log a warning when the configuration changes from the starting value', async () => { + const { errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); clock.tick(ADJUST_THROUGHPUT_INTERVAL); - } - expect(subscription).toHaveBeenNthCalledWith(2, 80); - expect(subscription).toHaveBeenNthCalledWith(3, 64); - // 51.2 -> 51 from Math.floor - expect(subscription).toHaveBeenNthCalledWith(4, 51); - expect(subscription).toHaveBeenNthCalledWith(5, 40); - expect(subscription).toHaveBeenNthCalledWith(6, 32); - expect(subscription).toHaveBeenNthCalledWith(7, 25); - expect(subscription).toHaveBeenNthCalledWith(8, 20); - expect(subscription).toHaveBeenNthCalledWith(9, 16); - expect(subscription).toHaveBeenNthCalledWith(10, 12); - expect(subscription).toHaveBeenNthCalledWith(11, 9); - expect(subscription).toHaveBeenNthCalledWith(12, 7); - expect(subscription).toHaveBeenNthCalledWith(13, 5); - expect(subscription).toHaveBeenNthCalledWith(14, 4); - expect(subscription).toHaveBeenNthCalledWith(15, 3); - expect(subscription).toHaveBeenNthCalledWith(16, 2); - expect(subscription).toHaveBeenNthCalledWith(17, 1); - // No new calls due to value not changing and usage of distinctUntilChanged() - expect(subscription).toHaveBeenCalledTimes(17); + expect(logger.warn).toHaveBeenCalledWith( + 'Capacity configuration is temporarily reduced after Elasticsearch returned 1 "too many request" and/or "execute [inline] script" error(s).' + ); + }); + + test('should increase configuration back to normal incrementally after an error is emitted', async () => { + const { subscription, errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL * 10); + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 9); + expect(subscription).toHaveBeenNthCalledWith(4, 10); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(4); + }); + + test('should keep reducing configuration when errors keep emitting until it reaches minimum', async () => { + const { subscription, errors$ } = setupScenario(10, CLAIM_STRATEGY_MGET); + for (let i = 0; i < 20; i++) { + errors$.next(SavedObjectsErrorHelpers.createTooManyRequestsError('a', 'b')); + clock.tick(ADJUST_THROUGHPUT_INTERVAL); + } + expect(subscription).toHaveBeenNthCalledWith(1, 10); + expect(subscription).toHaveBeenNthCalledWith(2, 8); + expect(subscription).toHaveBeenNthCalledWith(3, 6); + expect(subscription).toHaveBeenNthCalledWith(4, 5); + // No new calls due to value not changing and usage of distinctUntilChanged() + expect(subscription).toHaveBeenCalledTimes(4); + }); }); }); @@ -151,8 +279,10 @@ describe('createManagedConfiguration()', () => { const { pollIntervalConfiguration$ } = createManagedConfiguration({ logger, errors$, - startingPollInterval, - startingMaxWorkers: 1, + config: { + poll_interval: startingPollInterval, + capacity: 20, + } as TaskManagerConfig, }); pollIntervalConfiguration$.subscribe(subscription); return { subscription, errors$ }; diff --git a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts index 5c7b1a16a4308..3036eb2008de6 100644 --- a/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts +++ b/x-pack/plugins/task_manager/server/lib/create_managed_configuration.ts @@ -10,17 +10,26 @@ import { filter, mergeScan, map, scan, distinctUntilChanged, startWith } from 'r import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { Logger } from '@kbn/core/server'; import { isEsCannotExecuteScriptError } from './identify_es_error'; +import { CLAIM_STRATEGY_MGET, DEFAULT_CAPACITY, MAX_CAPACITY, TaskManagerConfig } from '../config'; +import { TaskCost } from '../task'; const FLUSH_MARKER = Symbol('flush'); export const ADJUST_THROUGHPUT_INTERVAL = 10 * 1000; export const PREFERRED_MAX_POLL_INTERVAL = 60 * 1000; + +// Capacity is measured in number of normal cost tasks that can be run +// At a minimum, we need to be able to run a single task with the greatest cost +// so we should convert the greatest cost to normal cost +export const MIN_COST = TaskCost.ExtraLarge / TaskCost.Normal; + +// For default claim strategy export const MIN_WORKERS = 1; -// When errors occur, reduce maxWorkers by MAX_WORKERS_DECREASE_PERCENTAGE -// When errors no longer occur, start increasing maxWorkers by MAX_WORKERS_INCREASE_PERCENTAGE +// When errors occur, reduce capacity by CAPACITY_DECREASE_PERCENTAGE +// When errors no longer occur, start increasing capacity by CAPACITY_INCREASE_PERCENTAGE // until starting value is reached -const MAX_WORKERS_DECREASE_PERCENTAGE = 0.8; -const MAX_WORKERS_INCREASE_PERCENTAGE = 1.05; +const CAPACITY_DECREASE_PERCENTAGE = 0.8; +const CAPACITY_INCREASE_PERCENTAGE = 1.05; // When errors occur, increase pollInterval by POLL_INTERVAL_INCREASE_PERCENTAGE // When errors no longer occur, start decreasing pollInterval by POLL_INTERVAL_DECREASE_PERCENTAGE @@ -29,28 +38,32 @@ const POLL_INTERVAL_DECREASE_PERCENTAGE = 0.95; const POLL_INTERVAL_INCREASE_PERCENTAGE = 1.2; interface ManagedConfigurationOpts { - logger: Logger; - startingMaxWorkers: number; - startingPollInterval: number; + config: TaskManagerConfig; + defaultCapacity?: number; errors$: Observable<Error>; + logger: Logger; } export interface ManagedConfiguration { - maxWorkersConfiguration$: Observable<number>; + startingCapacity: number; + capacityConfiguration$: Observable<number>; pollIntervalConfiguration$: Observable<number>; } export function createManagedConfiguration({ + config, + defaultCapacity = DEFAULT_CAPACITY, logger, - startingMaxWorkers, - startingPollInterval, errors$, }: ManagedConfigurationOpts): ManagedConfiguration { const errorCheck$ = countErrors(errors$, ADJUST_THROUGHPUT_INTERVAL); + const startingCapacity = calculateStartingCapacity(config, logger, defaultCapacity); + const startingPollInterval = config.poll_interval; return { - maxWorkersConfiguration$: errorCheck$.pipe( - createMaxWorkersScan(logger, startingMaxWorkers), - startWith(startingMaxWorkers), + startingCapacity, + capacityConfiguration$: errorCheck$.pipe( + createCapacityScan(config, logger, startingCapacity), + startWith(startingCapacity), distinctUntilChanged() ), pollIntervalConfiguration$: errorCheck$.pipe( @@ -61,37 +74,39 @@ export function createManagedConfiguration({ }; } -function createMaxWorkersScan(logger: Logger, startingMaxWorkers: number) { - return scan((previousMaxWorkers: number, errorCount: number) => { - let newMaxWorkers: number; +function createCapacityScan(config: TaskManagerConfig, logger: Logger, startingCapacity: number) { + return scan((previousCapacity: number, errorCount: number) => { + let newCapacity: number; if (errorCount > 0) { - // Decrease max workers by MAX_WORKERS_DECREASE_PERCENTAGE while making sure it doesn't go lower than 1. + const minCapacity = getMinCapacity(config); + // Decrease capacity by CAPACITY_DECREASE_PERCENTAGE while making sure it doesn't go lower than minCapacity. // Using Math.floor to make sure the number is different than previous while not being a decimal value. - newMaxWorkers = Math.max( - Math.floor(previousMaxWorkers * MAX_WORKERS_DECREASE_PERCENTAGE), - MIN_WORKERS + newCapacity = Math.max( + Math.floor(previousCapacity * CAPACITY_DECREASE_PERCENTAGE), + minCapacity ); } else { - // Increase max workers by MAX_WORKERS_INCREASE_PERCENTAGE while making sure it doesn't go + // Increase capacity by CAPACITY_INCREASE_PERCENTAGE while making sure it doesn't go // higher than the starting value. Using Math.ceil to make sure the number is different than // previous while not being a decimal value - newMaxWorkers = Math.min( - startingMaxWorkers, - Math.ceil(previousMaxWorkers * MAX_WORKERS_INCREASE_PERCENTAGE) + newCapacity = Math.min( + startingCapacity, + Math.ceil(previousCapacity * CAPACITY_INCREASE_PERCENTAGE) ); } - if (newMaxWorkers !== previousMaxWorkers) { + + if (newCapacity !== previousCapacity) { logger.debug( - `Max workers configuration changing from ${previousMaxWorkers} to ${newMaxWorkers} after seeing ${errorCount} "too many request" and/or "execute [inline] script" error(s)` + `Capacity configuration changing from ${previousCapacity} to ${newCapacity} after seeing ${errorCount} "too many request" and/or "execute [inline] script" error(s)` ); - if (previousMaxWorkers === startingMaxWorkers) { + if (previousCapacity === startingCapacity) { logger.warn( - `Max workers configuration is temporarily reduced after Elasticsearch returned ${errorCount} "too many request" and/or "execute [inline] script" error(s).` + `Capacity configuration is temporarily reduced after Elasticsearch returned ${errorCount} "too many request" and/or "execute [inline] script" error(s).` ); } } - return newMaxWorkers; - }, startingMaxWorkers); + return newCapacity; + }, startingCapacity); } function createPollIntervalScan(logger: Logger, startingPollInterval: number) { @@ -186,3 +201,36 @@ function resetErrorCount() { count: 0, }; } + +function getMinCapacity(config: TaskManagerConfig) { + switch (config.claim_strategy) { + case CLAIM_STRATEGY_MGET: + return MIN_COST; + + default: + return MIN_WORKERS; + } +} + +export function calculateStartingCapacity( + config: TaskManagerConfig, + logger: Logger, + defaultCapacity: number +): number { + if (config.capacity !== undefined && config.max_workers !== undefined) { + logger.warn( + `Both "xpack.task_manager.capacity" and "xpack.task_manager.max_workers" configs are set, max_workers will be ignored in favor of capacity and the setting should be removed.` + ); + } + + if (config.capacity) { + // Use capacity if explicitly set + return config.capacity!; + } else if (config.max_workers) { + // Otherwise use max_worker value as capacity, capped at MAX_CAPACITY + return Math.min(config.max_workers, MAX_CAPACITY); + } + + // Neither are set, use the given default capacity + return defaultCapacity; +} diff --git a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts index 9fdb16fb5f677..d3533ac058314 100644 --- a/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts +++ b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts @@ -30,7 +30,6 @@ describe('fillPool', () => { tasksUpdated: tasks?.length ?? 0, tasksConflicted: 0, tasksClaimed: 0, - tasksRejected: 0, }, docs: tasks, }) diff --git a/x-pack/plugins/task_manager/server/lib/get_default_capacity.test.ts b/x-pack/plugins/task_manager/server/lib/get_default_capacity.test.ts new file mode 100644 index 0000000000000..fb68a3620e43c --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/get_default_capacity.test.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 { CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET, DEFAULT_CAPACITY } from '../config'; +import { getDefaultCapacity } from './get_default_capacity'; + +describe('getDefaultCapacity', () => { + it('returns default capacity when not in cloud', () => { + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + }); + + it('returns default capacity when default claim strategy', () => { + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_DEFAULT, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_DEFAULT, + }) + ).toBe(DEFAULT_CAPACITY); + }); + + it('returns default capacity when serverless', () => { + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: false, + isServerless: true, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: true, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: true, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(DEFAULT_CAPACITY); + }); + + it('returns capacity as expected when in cloud and claim strategy is mget', () => { + // 1GB + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(10); + + // 1GB but somehow background task node only is true + expect( + getDefaultCapacity({ + heapSizeLimit: 851443712, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(10); + + // 2GB + expect( + getDefaultCapacity({ + heapSizeLimit: 1702887424, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(15); + + // 2GB but somehow background task node only is true + expect( + getDefaultCapacity({ + heapSizeLimit: 1702887424, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(15); + + // 4GB + expect( + getDefaultCapacity({ + heapSizeLimit: 3405774848, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: false, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(25); + + // 4GB background task only + expect( + getDefaultCapacity({ + heapSizeLimit: 3405774848, + isCloud: true, + isServerless: false, + isBackgroundTaskNodeOnly: true, + claimStrategy: CLAIM_STRATEGY_MGET, + }) + ).toBe(50); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/get_default_capacity.ts b/x-pack/plugins/task_manager/server/lib/get_default_capacity.ts new file mode 100644 index 0000000000000..aeafa0f63c4d7 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/get_default_capacity.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CLAIM_STRATEGY_MGET, DEFAULT_CAPACITY } from '../config'; + +interface GetDefaultCapacityOpts { + claimStrategy?: string; + heapSizeLimit: number; + isCloud: boolean; + isServerless: boolean; + isBackgroundTaskNodeOnly: boolean; +} + +// Map instance size to desired capacity +const HEAP_TO_CAPACITY_MAP = [ + { minHeap: 0, maxHeap: 1, capacity: 10 }, + { minHeap: 1, maxHeap: 2, capacity: 15 }, + { minHeap: 2, maxHeap: 4, capacity: 25, backgroundTaskNodeOnly: false }, + { minHeap: 2, maxHeap: 4, capacity: 50, backgroundTaskNodeOnly: true }, +]; + +export function getDefaultCapacity({ + claimStrategy, + heapSizeLimit: heapSizeLimitInBytes, + isCloud, + isServerless, + isBackgroundTaskNodeOnly, +}: GetDefaultCapacityOpts) { + // perform heap size based calculations only in cloud + if (isCloud && !isServerless && claimStrategy === CLAIM_STRATEGY_MGET) { + // convert bytes to GB + const heapSizeLimitInGB = heapSizeLimitInBytes / 1e9; + + const config = HEAP_TO_CAPACITY_MAP.find((map) => { + return ( + heapSizeLimitInGB > map.minHeap && + heapSizeLimitInGB <= map.maxHeap && + (map.backgroundTaskNodeOnly === undefined || + isBackgroundTaskNodeOnly === map.backgroundTaskNodeOnly) + ); + }); + + return config?.capacity ?? DEFAULT_CAPACITY; + } + + return DEFAULT_CAPACITY; +} diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts index ea0793b60266b..a39568df5fdd2 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts @@ -435,7 +435,8 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { timestamp: new Date().toISOString(), status: HealthStatus.OK, value: { - max_workers: 10, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -454,16 +455,19 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { status: HealthStatus.OK, value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimatedScheduleDensity: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: 2, estimated_schedule_density: [], capacity_requirements: { diff --git a/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts b/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts index 4f1ebc60aa249..8bab96f85dee5 100644 --- a/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts +++ b/x-pack/plugins/task_manager/server/lib/task_partitioner.test.ts @@ -9,7 +9,7 @@ import { createDiscoveryServiceMock, createFindSO, } from '../kibana_discovery_service/mock_kibana_discovery_service'; -import { TaskPartitioner } from './task_partitioner'; +import { CACHE_INTERVAL, TaskPartitioner } from './task_partitioner'; const POD_NAME = 'test-pod'; @@ -47,24 +47,61 @@ describe('getPodName()', () => { describe('getPartitions()', () => { const lastSeen = '2024-08-10T10:00:00.000Z'; const discoveryServiceMock = createDiscoveryServiceMock(POD_NAME); - discoveryServiceMock.getActiveKibanaNodes.mockResolvedValue([ - createFindSO(POD_NAME, lastSeen), - createFindSO('test-pod-2', lastSeen), - createFindSO('test-pod-3', lastSeen), - ]); + const expectedPartitions = [ + 0, 1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, + 39, 40, 42, 43, 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, + 75, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 99, 100, 102, 103, 105, 106, + 108, 109, 111, 112, 114, 115, 117, 118, 120, 121, 123, 124, 126, 127, 129, 130, 132, 133, 135, + 136, 138, 139, 141, 142, 144, 145, 147, 148, 150, 151, 153, 154, 156, 157, 159, 160, 162, 163, + 165, 166, 168, 169, 171, 172, 174, 175, 177, 178, 180, 181, 183, 184, 186, 187, 189, 190, 192, + 193, 195, 196, 198, 199, 201, 202, 204, 205, 207, 208, 210, 211, 213, 214, 216, 217, 219, 220, + 222, 223, 225, 226, 228, 229, 231, 232, 234, 235, 237, 238, 240, 241, 243, 244, 246, 247, 249, + 250, 252, 253, 255, + ]; + + beforeEach(() => { + jest.useFakeTimers(); + discoveryServiceMock.getActiveKibanaNodes.mockResolvedValue([ + createFindSO(POD_NAME, lastSeen), + createFindSO('test-pod-2', lastSeen), + createFindSO('test-pod-3', lastSeen), + ]); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.clearAllTimers(); + }); test('correctly gets the partitons for this pod', async () => { const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); - expect(await taskPartitioner.getPartitions()).toEqual([ - 0, 1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, - 37, 39, 40, 42, 43, 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, - 72, 73, 75, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 99, 100, 102, 103, - 105, 106, 108, 109, 111, 112, 114, 115, 117, 118, 120, 121, 123, 124, 126, 127, 129, 130, 132, - 133, 135, 136, 138, 139, 141, 142, 144, 145, 147, 148, 150, 151, 153, 154, 156, 157, 159, 160, - 162, 163, 165, 166, 168, 169, 171, 172, 174, 175, 177, 178, 180, 181, 183, 184, 186, 187, 189, - 190, 192, 193, 195, 196, 198, 199, 201, 202, 204, 205, 207, 208, 210, 211, 213, 214, 216, 217, - 219, 220, 222, 223, 225, 226, 228, 229, 231, 232, 234, 235, 237, 238, 240, 241, 243, 244, 246, - 247, 249, 250, 252, 253, 255, - ]); + expect(await taskPartitioner.getPartitions()).toEqual(expectedPartitions); + }); + + test('correctly caches the partitions on 10 second interval', async () => { + const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); + const shorterInterval = CACHE_INTERVAL / 2; + + await taskPartitioner.getPartitions(); + + jest.advanceTimersByTime(shorterInterval); + await taskPartitioner.getPartitions(); + + jest.advanceTimersByTime(shorterInterval); + await taskPartitioner.getPartitions(); + + expect(discoveryServiceMock.getActiveKibanaNodes).toHaveBeenCalledTimes(2); + }); + + test('correctly catches the error from the discovery service and returns the cached value', async () => { + const taskPartitioner = new TaskPartitioner(POD_NAME, discoveryServiceMock); + + await taskPartitioner.getPartitions(); + expect(taskPartitioner.getPodPartitions()).toEqual(expectedPartitions); + + discoveryServiceMock.getActiveKibanaNodes.mockRejectedValueOnce([]); + jest.advanceTimersByTime(CACHE_INTERVAL); + await taskPartitioner.getPartitions(); + expect(taskPartitioner.getPodPartitions()).toEqual(expectedPartitions); }); }); diff --git a/x-pack/plugins/task_manager/server/lib/task_partitioner.ts b/x-pack/plugins/task_manager/server/lib/task_partitioner.ts index 8ff696391a826..f0388ce19d966 100644 --- a/x-pack/plugins/task_manager/server/lib/task_partitioner.ts +++ b/x-pack/plugins/task_manager/server/lib/task_partitioner.ts @@ -17,16 +17,21 @@ function range(start: number, end: number) { } export const MAX_PARTITIONS = 256; +export const CACHE_INTERVAL = 10000; export class TaskPartitioner { private readonly allPartitions: number[]; private readonly podName: string; private kibanaDiscoveryService: KibanaDiscoveryService; + private podPartitions: number[]; + private podPartitionsLastUpdated: number; constructor(podName: string, kibanaDiscoveryService: KibanaDiscoveryService) { this.allPartitions = range(0, MAX_PARTITIONS); this.podName = podName; this.kibanaDiscoveryService = kibanaDiscoveryService; + this.podPartitions = []; + this.podPartitionsLastUpdated = Date.now() - CACHE_INTERVAL; } getAllPartitions(): number[] { @@ -37,10 +42,26 @@ export class TaskPartitioner { return this.podName; } + getPodPartitions(): number[] { + return this.podPartitions; + } + async getPartitions(): Promise<number[]> { - const allPodNames = await this.getAllPodNames(); - const podPartitions = assignPodPartitions(this.podName, allPodNames, this.allPartitions); - return podPartitions; + const lastUpdated = new Date(this.podPartitionsLastUpdated).getTime(); + const now = Date.now(); + + // update the pod partitions cache after 10 seconds + if (now - lastUpdated >= CACHE_INTERVAL) { + try { + const allPodNames = await this.getAllPodNames(); + this.podPartitions = assignPodPartitions(this.podName, allPodNames, this.allPartitions); + this.podPartitionsLastUpdated = now; + } catch (error) { + // return the cached value + return this.podPartitions; + } + } + return this.podPartitions; } private async getAllPodNames(): Promise<string[]> { 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 309617a8e4cc3..b1cf9a90b6cb6 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 @@ -45,7 +45,6 @@ const config: TaskManagerConfig = { warn_threshold: 5000, }, max_attempts: 9, - max_workers: 10, metrics_reset_interval: 30000, monitored_aggregated_stats_refresh_rate: 5000, monitored_stats_health_verbose_log: { diff --git a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts index 837f29c83f108..5a9a9e07aadf7 100644 --- a/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/background_task_utilization_statistics.ts @@ -21,7 +21,7 @@ import { } from '../task_events'; import { MonitoredStat } from './monitoring_stats_stream'; import { AggregatedStat, AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; -import { createRunningAveragedStat } from './task_run_calcultors'; +import { createRunningAveragedStat } from './task_run_calculators'; import { DEFAULT_WORKER_UTILIZATION_RUNNING_AVERAGE_WINDOW } from '../config'; export interface PublicBackgroundTaskUtilizationStat extends JsonObject { diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts index 263f2e9987b7c..9791ac805e500 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.test.ts @@ -21,7 +21,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -77,7 +77,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -135,7 +135,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -172,7 +172,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -228,7 +228,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { // 0 active tasks at this moment in time, so no owners identifiable owner_ids: 0, @@ -285,7 +285,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 3, overdue_non_recurring: 0, @@ -347,7 +347,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: provisionedKibanaInstances, overdue_non_recurring: 0, @@ -428,7 +428,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: provisionedKibanaInstances, overdue_non_recurring: 0, @@ -510,7 +510,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -578,7 +578,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -643,7 +643,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -708,7 +708,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -784,7 +784,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { owner_ids: 1, overdue_non_recurring: 0, @@ -862,7 +862,7 @@ describe('estimateCapacity', () => { estimateCapacity( logger, mockStats( - { max_workers: 10, poll_interval: 3000 }, + { capacity: { config: 10, as_cost: 20, as_workers: 10 }, poll_interval: 3000 }, { overdue: undefined, owner_ids: 1, @@ -949,7 +949,8 @@ function mockStats( status: HealthStatus.OK, timestamp: new Date().toISOString(), value: { - max_workers: 0, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 0, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -969,16 +970,19 @@ function mockStats( timestamp: new Date().toISOString(), value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimated_schedule_density: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: 2, capacity_requirements: { per_minute: 150, diff --git a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts index b12382f16e27b..d1c2f3591ea22 100644 --- a/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts +++ b/x-pack/plugins/task_manager/server/monitoring/capacity_estimation.ts @@ -10,7 +10,7 @@ import stats from 'stats-lite'; import { JsonObject } from '@kbn/utility-types'; import { Logger } from '@kbn/core/server'; import { RawMonitoringStats, RawMonitoredStat, HealthStatus } from './monitoring_stats_stream'; -import { AveragedStat } from './task_run_calcultors'; +import { AveragedStat } from './task_run_calculators'; import { TaskPersistenceTypes } from './task_run_statistics'; import { asErr, asOk, map, Result } from '../lib/result_type'; @@ -61,8 +61,10 @@ export function estimateCapacity( non_recurring: percentageOfExecutionsUsedByNonRecurringTasks, } = capacityStats.runtime.value.execution.persistence; const { overdue, capacity_requirements: capacityRequirements } = workload; - const { poll_interval: pollInterval, max_workers: maxWorkers } = - capacityStats.configuration.value; + const { + poll_interval: pollInterval, + capacity: { config: configuredCapacity }, + } = capacityStats.configuration.value; /** * On average, how many polling cycles does it take to execute a task? @@ -78,10 +80,10 @@ export function estimateCapacity( ); /** - * Given the current configuration how much task capacity do we have? + * Given the current configuration how much capacity do we have to run normal cost tasks? */ const capacityPerMinutePerKibana = Math.round( - ((60 * 1000) / (averagePollIntervalsPerExecution * pollInterval)) * maxWorkers + ((60 * 1000) / (averagePollIntervalsPerExecution * pollInterval)) * configuredCapacity ); /** diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts index 822356e2d6534..0b5387b66dece 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts @@ -13,7 +13,6 @@ import { TaskManagerConfig } from '../config'; describe('Configuration Statistics Aggregator', () => { test('merges the static config with the merged configs', async () => { const configuration: TaskManagerConfig = { - max_workers: 10, max_attempts: 9, poll_interval: 6000000, allow_reading_invalid_state: false, @@ -55,7 +54,8 @@ describe('Configuration Statistics Aggregator', () => { }; const managedConfig = { - maxWorkersConfiguration$: new Subject<number>(), + startingCapacity: 10, + capacityConfiguration$: new Subject<number>(), pollIntervalConfiguration$: new Subject<number>(), }; @@ -65,7 +65,12 @@ describe('Configuration Statistics Aggregator', () => { .pipe(take(3), bufferCount(3)) .subscribe(([initial, updatedWorkers, updatedInterval]) => { expect(initial.value).toEqual({ - max_workers: 10, + capacity: { + config: 10, + as_workers: 10, + as_cost: 20, + }, + claim_strategy: 'default', poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -79,7 +84,12 @@ describe('Configuration Statistics Aggregator', () => { }, }); expect(updatedWorkers.value).toEqual({ - max_workers: 8, + capacity: { + config: 8, + as_workers: 8, + as_cost: 16, + }, + claim_strategy: 'default', poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -93,7 +103,12 @@ describe('Configuration Statistics Aggregator', () => { }, }); expect(updatedInterval.value).toEqual({ - max_workers: 8, + capacity: { + config: 8, + as_workers: 8, + as_cost: 16, + }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -108,7 +123,7 @@ describe('Configuration Statistics Aggregator', () => { }); resolve(); }, reject); - managedConfig.maxWorkersConfiguration$.next(8); + managedConfig.capacityConfiguration$.next(8); managedConfig.pollIntervalConfiguration$.next(3000); } catch (error) { reject(error); diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts index dc3221351a33e..c606b63694b0f 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.ts @@ -8,9 +8,11 @@ import { combineLatest, of } from 'rxjs'; import { pick, merge } from 'lodash'; import { map, startWith } from 'rxjs'; +import { JsonObject } from '@kbn/utility-types'; import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; -import { TaskManagerConfig } from '../config'; +import { CLAIM_STRATEGY_DEFAULT, TaskManagerConfig } from '../config'; import { ManagedConfiguration } from '../lib/create_managed_configuration'; +import { getCapacityInCost, getCapacityInWorkers } from '../task_pool'; const CONFIG_FIELDS_TO_EXPOSE = [ 'request_capacity', @@ -19,10 +21,19 @@ const CONFIG_FIELDS_TO_EXPOSE = [ 'monitored_task_execution_thresholds', ] as const; +interface CapacityConfig extends JsonObject { + capacity: { + config: number; + as_workers: number; + as_cost: number; + }; +} + export type ConfigStat = Pick< TaskManagerConfig, - 'max_workers' | 'poll_interval' | (typeof CONFIG_FIELDS_TO_EXPOSE)[number] ->; + 'poll_interval' | 'claim_strategy' | (typeof CONFIG_FIELDS_TO_EXPOSE)[number] +> & + CapacityConfig; export function createConfigurationAggregator( config: TaskManagerConfig, @@ -30,16 +41,21 @@ export function createConfigurationAggregator( ): AggregatedStatProvider<ConfigStat> { return combineLatest([ of(pick(config, ...CONFIG_FIELDS_TO_EXPOSE)), + of({ claim_strategy: config.claim_strategy ?? CLAIM_STRATEGY_DEFAULT }), managedConfig.pollIntervalConfiguration$.pipe( startWith(config.poll_interval), map<number, Pick<TaskManagerConfig, 'poll_interval'>>((pollInterval) => ({ poll_interval: pollInterval, })) ), - managedConfig.maxWorkersConfiguration$.pipe( - startWith(config.max_workers), - map<number, Pick<TaskManagerConfig, 'max_workers'>>((maxWorkers) => ({ - max_workers: maxWorkers, + managedConfig.capacityConfiguration$.pipe( + startWith(managedConfig.startingCapacity), + map<number, CapacityConfig>((capacity) => ({ + capacity: { + config: capacity, + as_workers: getCapacityInWorkers(capacity), + as_cost: getCapacityInCost(capacity), + }, })) ), ]).pipe( 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 d7135837e052e..ac16070d7c131 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 @@ -176,11 +176,11 @@ describe('Ephemeral Task Statistics', () => { }); const runningAverageWindowSize = 5; - const maxWorkers = 10; + const capacity = 10; const ephemeralTaskAggregator = createEphemeralTaskAggregator( ephemeralTaskLifecycle, runningAverageWindowSize, - maxWorkers + capacity ); function expectWindowEqualsUpdate( @@ -229,7 +229,7 @@ describe('Ephemeral Task Statistics', () => { }); }); -test('returns the average load added per polling cycle cycle by ephemeral tasks when load exceeds max workers', async () => { +test('returns the average load added per polling cycle cycle by ephemeral tasks when load exceeds capacity', async () => { const tasksExecuted = [0, 5, 10, 20, 15, 10, 5, 0, 0, 0, 0, 0]; const expectedLoad = [0, 50, 100, 200, 150, 100, 50, 0, 0, 0, 0, 0]; @@ -241,11 +241,11 @@ test('returns the average load added per polling cycle cycle by ephemeral tasks }); const runningAverageWindowSize = 5; - const maxWorkers = 10; + const capacity = 10; const ephemeralTaskAggregator = createEphemeralTaskAggregator( ephemeralTaskLifecycle, runningAverageWindowSize, - maxWorkers + capacity ); function expectWindowEqualsUpdate( diff --git a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts index b77eae1080fbc..d02080a56a1aa 100644 --- a/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/ephemeral_task_statistics.ts @@ -17,7 +17,7 @@ import { AveragedStat, calculateRunningAverage, createRunningAveragedStat, -} from './task_run_calcultors'; +} from './task_run_calculators'; import { HealthStatus } from './monitoring_stats_stream'; export interface EphemeralTaskStat extends JsonObject { @@ -35,7 +35,7 @@ export interface SummarizedEphemeralTaskStat extends JsonObject { export function createEphemeralTaskAggregator( ephemeralTaskLifecycle: EphemeralTaskLifecycle, runningAverageWindowSize: number, - maxWorkers: number + capacity: number ): AggregatedStatProvider<EphemeralTaskStat> { const ephemeralTaskRunEvents$ = ephemeralTaskLifecycle.events.pipe( filter((taskEvent: TaskLifecycleEvent) => isTaskRunEvent(taskEvent)) @@ -70,7 +70,7 @@ export function createEphemeralTaskAggregator( map(([tasksRanSincePreviousQueueSize, ephemeralQueueSize]) => ({ queuedTasks: ephemeralQueuedTasksQueue(ephemeralQueueSize), executionsPerCycle: ephemeralQueueExecutionsPerCycleQueue(tasksRanSincePreviousQueueSize), - load: ephemeralTaskLoadQueue(calculateWorkerLoad(maxWorkers, tasksRanSincePreviousQueueSize)), + load: ephemeralTaskLoadQueue(calculateWorkerLoad(capacity, tasksRanSincePreviousQueueSize)), })), startWith({ queuedTasks: [], diff --git a/x-pack/plugins/task_manager/server/monitoring/index.ts b/x-pack/plugins/task_manager/server/monitoring/index.ts index 9ee32e97d7758..5dc024b53de10 100644 --- a/x-pack/plugins/task_manager/server/monitoring/index.ts +++ b/x-pack/plugins/task_manager/server/monitoring/index.ts @@ -18,6 +18,7 @@ import { TaskPollingLifecycle } from '../polling_lifecycle'; import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; +import { TaskTypeDictionary } from '../task_type_dictionary'; export type { MonitoringStats, RawMonitoringStats } from './monitoring_stats_stream'; export { @@ -27,27 +28,20 @@ export { createMonitoringStatsStream, } from './monitoring_stats_stream'; +export interface CreateMonitoringStatsOpts { + taskStore: TaskStore; + elasticsearchAndSOAvailability$: Observable<boolean>; + config: TaskManagerConfig; + managedConfig: ManagedConfiguration; + logger: Logger; + adHocTaskCounter: AdHocTaskCounter; + taskDefinitions: TaskTypeDictionary; + taskPollingLifecycle?: TaskPollingLifecycle; + ephemeralTaskLifecycle?: EphemeralTaskLifecycle; +} + export function createMonitoringStats( - taskStore: TaskStore, - elasticsearchAndSOAvailability$: Observable<boolean>, - config: TaskManagerConfig, - managedConfig: ManagedConfiguration, - logger: Logger, - adHocTaskCounter: AdHocTaskCounter, - taskPollingLifecycle?: TaskPollingLifecycle, - ephemeralTaskLifecycle?: EphemeralTaskLifecycle + opts: CreateMonitoringStatsOpts ): Observable<MonitoringStats> { - return createMonitoringStatsStream( - createAggregators( - taskStore, - elasticsearchAndSOAvailability$, - config, - managedConfig, - logger, - adHocTaskCounter, - taskPollingLifecycle, - ephemeralTaskLifecycle - ), - config - ); + return createMonitoringStatsStream(createAggregators(opts)); } diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts index f4da53871ffa3..075b663e4ce83 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { TaskManagerConfig } from '../config'; import { of, Subject } from 'rxjs'; import { take, bufferCount } from 'rxjs'; import { createMonitoringStatsStream } from './monitoring_stats_stream'; @@ -17,51 +16,9 @@ beforeEach(() => { }); describe('createMonitoringStatsStream', () => { - const configuration: TaskManagerConfig = { - max_workers: 10, - max_attempts: 9, - poll_interval: 6000000, - allow_reading_invalid_state: false, - version_conflict_threshold: 80, - monitored_stats_required_freshness: 6000000, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_health_verbose_log: { - enabled: false, - level: 'debug' as const, - warn_delayed_task_start_in_seconds: 60, - }, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, - }, - custom: {}, - }, - ephemeral_tasks: { - enabled: true, - request_capacity: 10, - }, - unsafe: { - exclude_task_types: [], - authenticate_background_task_utilization: true, - }, - event_loop_delay: { - monitor: true, - warn_threshold: 5000, - }, - worker_utilization_running_average_window: 5, - metrics_reset_interval: 3000, - claim_strategy: 'default', - request_timeouts: { - update_by_query: 1000, - }, - }; - it('returns the initial config used to configure Task Manager', async () => { return new Promise<void>((resolve) => { - createMonitoringStatsStream(of(), configuration) + createMonitoringStatsStream(of()) .pipe(take(1)) .subscribe((firstValue) => { expect(firstValue.stats).toEqual({}); @@ -74,7 +31,7 @@ describe('createMonitoringStatsStream', () => { const aggregatedStats$ = new Subject<AggregatedStat>(); return new Promise<void>((resolve) => { - createMonitoringStatsStream(aggregatedStats$, configuration) + createMonitoringStatsStream(aggregatedStats$) .pipe(take(3), bufferCount(3)) .subscribe(([initialValue, secondValue, thirdValue]) => { expect(initialValue.stats).toMatchObject({ @@ -82,7 +39,7 @@ describe('createMonitoringStatsStream', () => { stats: { configuration: { value: { - max_workers: 10, + capacity: 10, poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -115,7 +72,7 @@ describe('createMonitoringStatsStream', () => { configuration: { timestamp: expect.any(String), value: { - max_workers: 10, + capacity: 10, poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -148,7 +105,7 @@ describe('createMonitoringStatsStream', () => { configuration: { timestamp: expect.any(String), value: { - max_workers: 10, + capacity: 10, poll_interval: 6000000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts index 5ee6465dae0eb..e1bffb55d54fa 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts @@ -10,8 +10,6 @@ import { map, scan } from 'rxjs'; import { set } from '@kbn/safer-lodash-set'; import { Logger } from '@kbn/core/server'; import { JsonObject } from '@kbn/utility-types'; -import { TaskStore } from '../task_store'; -import { TaskPollingLifecycle } from '../polling_lifecycle'; import { createWorkloadAggregator, summarizeWorkloadStat, @@ -37,11 +35,9 @@ import { import { ConfigStat, createConfigurationAggregator } from './configuration_statistics'; import { TaskManagerConfig } from '../config'; -import { ManagedConfiguration } from '../lib/create_managed_configuration'; -import { EphemeralTaskLifecycle } from '../ephemeral_task_lifecycle'; import { CapacityEstimationStat, withCapacityEstimate } from './capacity_estimation'; -import { AdHocTaskCounter } from '../lib/adhoc_task_counter'; import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; +import { CreateMonitoringStatsOpts } from '.'; export interface MonitoringStats { last_update: string; @@ -81,26 +77,28 @@ export interface RawMonitoringStats { }; } -export function createAggregators( - taskStore: TaskStore, - elasticsearchAndSOAvailability$: Observable<boolean>, - config: TaskManagerConfig, - managedConfig: ManagedConfiguration, - logger: Logger, - adHocTaskCounter: AdHocTaskCounter, - taskPollingLifecycle?: TaskPollingLifecycle, - ephemeralTaskLifecycle?: EphemeralTaskLifecycle -): AggregatedStatProvider { +export function createAggregators({ + taskStore, + elasticsearchAndSOAvailability$, + config, + managedConfig, + logger, + taskDefinitions, + adHocTaskCounter, + taskPollingLifecycle, + ephemeralTaskLifecycle, +}: CreateMonitoringStatsOpts): AggregatedStatProvider { const aggregators: AggregatedStatProvider[] = [ createConfigurationAggregator(config, managedConfig), - createWorkloadAggregator( + createWorkloadAggregator({ taskStore, elasticsearchAndSOAvailability$, - config.monitored_aggregated_stats_refresh_rate, - config.poll_interval, - logger - ), + refreshInterval: config.monitored_aggregated_stats_refresh_rate, + pollInterval: config.poll_interval, + logger, + taskDefinitions, + }), ]; if (taskPollingLifecycle) { aggregators.push( @@ -118,7 +116,7 @@ export function createAggregators( createEphemeralTaskAggregator( ephemeralTaskLifecycle, config.monitored_stats_running_average_window, - config.max_workers + managedConfig.startingCapacity ) ); } @@ -126,8 +124,7 @@ export function createAggregators( } export function createMonitoringStatsStream( - provider$: AggregatedStatProvider, - config: TaskManagerConfig + provider$: AggregatedStatProvider ): Observable<MonitoringStats> { const initialStats = { last_update: new Date().toISOString(), diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_calculators.test.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_calculators.test.ts new file mode 100644 index 0000000000000..46df2b1b21d42 --- /dev/null +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_calculators.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 as uuidv4 } from 'uuid'; + +import { + calculateRunningAverage, + calculateFrequency, + createRunningAveragedStat, + createMapOfRunningAveragedStats, +} from './task_run_calculators'; + +describe('calculateRunningAverage', () => { + test('calculates the running average and median of a window of values', async () => { + expect(calculateRunningAverage([2, 2, 4, 6, 6])).toMatchInlineSnapshot(` + Object { + "p50": 4, + "p90": 6, + "p95": 6, + "p99": 6, + } + `); + }); +}); + +describe('calculateFrequency', () => { + test('calculates the frequency of each terms in the list as a percentage', async () => { + const [term1, term2, term3] = [uuidv4(), uuidv4(), uuidv4()]; + expect( + calculateFrequency([term1, term2, term2, term3, term1, term1, term2, term1, term3]) + ).toEqual({ + [term3]: 22, + [term1]: 44, + [term2]: 33, + }); + }); +}); + +describe('createRunningAveragedStat', () => { + test('create a function which tracks a window of values', async () => { + const queue = createRunningAveragedStat(3); + expect(queue(1)).toEqual([1]); + expect(queue(2)).toEqual([1, 2]); + expect(queue(3)).toEqual([1, 2, 3]); + expect(queue(4)).toEqual([2, 3, 4]); + expect(queue(5)).toEqual([3, 4, 5]); + }); +}); + +describe('createMapOfRunningAveragedStats', () => { + test('create a function which tracks multiple window of values by key', async () => { + const [term1, term2, term3] = [uuidv4(), uuidv4(), uuidv4()]; + const mappedQueues = createMapOfRunningAveragedStats(3); + expect(mappedQueues(term1, 1)).toEqual({ [term1]: [1] }); + expect(mappedQueues(term1, 2)).toEqual({ [term1]: [1, 2] }); + expect(mappedQueues(term2, 3)).toEqual({ [term1]: [1, 2], [term2]: [3] }); + expect(mappedQueues(term3, 4)).toEqual({ [term1]: [1, 2], [term2]: [3], [term3]: [4] }); + expect(mappedQueues(term2, 5)).toEqual({ [term1]: [1, 2], [term2]: [3, 5], [term3]: [4] }); + expect(mappedQueues(term2, 6)).toEqual({ [term1]: [1, 2], [term2]: [3, 5, 6], [term3]: [4] }); + expect(mappedQueues(term1, 7)).toEqual({ + [term1]: [1, 2, 7], + [term2]: [3, 5, 6], + [term3]: [4], + }); + expect(mappedQueues(term1, 8)).toEqual({ + [term1]: [2, 7, 8], + [term2]: [3, 5, 6], + [term3]: [4], + }); + expect(mappedQueues(term1, 9)).toEqual({ + [term1]: [7, 8, 9], + [term2]: [3, 5, 6], + [term3]: [4], + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_calculators.ts similarity index 100% rename from x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.ts rename to x-pack/plugins/task_manager/server/monitoring/task_run_calculators.ts diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.test.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.test.ts deleted file mode 100644 index b5f6be8b7524d..0000000000000 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_calcultors.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { v4 as uuidv4 } from 'uuid'; - -import { - calculateRunningAverage, - calculateFrequency, - createRunningAveragedStat, - createMapOfRunningAveragedStats, -} from './task_run_calcultors'; - -describe('calculateRunningAverage', () => { - test('calculates the running average and median of a window of values', async () => { - expect(calculateRunningAverage([2, 2, 4, 6, 6])).toMatchInlineSnapshot(` - Object { - "p50": 4, - "p90": 6, - "p95": 6, - "p99": 6, - } - `); - }); -}); - -describe('calculateFrequency', () => { - test('calculates the frequency of each terms in the list as a percentage', async () => { - const [term1, term2, term3] = [uuidv4(), uuidv4(), uuidv4()]; - expect( - calculateFrequency([term1, term2, term2, term3, term1, term1, term2, term1, term3]) - ).toEqual({ - [term3]: 22, - [term1]: 44, - [term2]: 33, - }); - }); -}); - -describe('createRunningAveragedStat', () => { - test('create a function which tracks a window of values', async () => { - const queue = createRunningAveragedStat(3); - expect(queue(1)).toEqual([1]); - expect(queue(2)).toEqual([1, 2]); - expect(queue(3)).toEqual([1, 2, 3]); - expect(queue(4)).toEqual([2, 3, 4]); - expect(queue(5)).toEqual([3, 4, 5]); - }); -}); - -describe('createMapOfRunningAveragedStats', () => { - test('create a function which tracks multiple window of values by key', async () => { - const [term1, term2, term3] = [uuidv4(), uuidv4(), uuidv4()]; - const mappedQueues = createMapOfRunningAveragedStats(3); - expect(mappedQueues(term1, 1)).toEqual({ [term1]: [1] }); - expect(mappedQueues(term1, 2)).toEqual({ [term1]: [1, 2] }); - expect(mappedQueues(term2, 3)).toEqual({ [term1]: [1, 2], [term2]: [3] }); - expect(mappedQueues(term3, 4)).toEqual({ [term1]: [1, 2], [term2]: [3], [term3]: [4] }); - expect(mappedQueues(term2, 5)).toEqual({ [term1]: [1, 2], [term2]: [3, 5], [term3]: [4] }); - expect(mappedQueues(term2, 6)).toEqual({ [term1]: [1, 2], [term2]: [3, 5, 6], [term3]: [4] }); - expect(mappedQueues(term1, 7)).toEqual({ - [term1]: [1, 2, 7], - [term2]: [3, 5, 6], - [term3]: [4], - }); - expect(mappedQueues(term1, 8)).toEqual({ - [term1]: [2, 7, 8], - [term2]: [3, 5, 6], - [term3]: [4], - }); - expect(mappedQueues(term1, 9)).toEqual({ - [term1]: [7, 8, 9], - [term2]: [3, 5, 6], - [term3]: [4], - }); - }); -}); diff --git a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts index 6a7f10b7e75b6..517b29a54cd64 100644 --- a/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/task_run_statistics.ts @@ -35,7 +35,7 @@ import { calculateFrequency, createRunningAveragedStat, createMapOfRunningAveragedStats, -} from './task_run_calcultors'; +} from './task_run_calculators'; import { HealthStatus } from './monitoring_stats_stream'; import { TaskPollingLifecycle } from '../polling_lifecycle'; import { TaskExecutionFailureThreshold, TaskManagerConfig } from '../config'; diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts index 7ef860efa783a..cd37c6661ec00 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.test.ts @@ -15,13 +15,14 @@ import { padBuckets, estimateRecurringTaskScheduling, } from './workload_statistics'; -import { ConcreteTaskInstance } from '../task'; +import { ConcreteTaskInstance, TaskCost } from '../task'; import { times } from 'lodash'; import { taskStoreMock } from '../task_store.mock'; import { of, Subject } from 'rxjs'; import { sleep } from '../test_utils'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { TaskTypeDictionary } from '../task_type_dictionary'; type ResponseWithAggs = Omit<estypes.SearchResponse<ConcreteTaskInstance>, 'aggregations'> & { aggregations: WorkloadAggregationResponse; @@ -32,52 +33,98 @@ const asApiResponse = (body: ResponseWithAggs) => .createSuccessTransportRequestPromise(body as estypes.SearchResponse<ConcreteTaskInstance>) .then((res) => res.body as ResponseWithAggs); +const logger = loggingSystemMock.create().get(); + +const definitions = new TaskTypeDictionary(logger); +definitions.registerTaskDefinitions({ + report: { + title: 'report', + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + foo: { + title: 'foo', + createTaskRunner: jest.fn(), + }, + bar: { + title: 'bar', + cost: TaskCost.Tiny, + createTaskRunner: jest.fn(), + }, +}); describe('Workload Statistics Aggregator', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + test('queries the Task Store at a fixed interval for the current workload', async () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue( asApiResponse({ - hits: { - hits: [], - max_score: 0, - total: { value: 0, relation: 'eq' }, - }, + hits: { hits: [], max_score: 0, total: { value: 3, relation: 'eq' } }, took: 1, timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 1, - failed: 0, - }, + _shards: { total: 1, successful: 1, skipped: 1, failed: 0 }, aggregations: { taskType: { - buckets: [], + buckets: [ + { + key: 'foo', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + { + key: 'bar', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'claiming', doc_count: 1 }], + }, + }, + { + key: 'report', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + ], doc_count_error_upper_bound: 0, sum_other_doc_count: 0, }, schedule: { - buckets: [], + buckets: [{ key: '1m', doc_count: 8 }], doc_count_error_upper_bound: 0, sum_other_doc_count: 0, }, nonRecurringTasks: { - doc_count: 13, - }, - ownerIds: { - ownerIds: { - value: 1, + doc_count: 1, + taskType: { + buckets: [{ key: 'report', doc_count: 1 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, + ownerIds: { ownerIds: { value: 1 } }, // The `FiltersAggregate` doesn't cover the case of a nested `AggregationsAggregationContainer`, in which `FiltersAggregate` // would not have a `buckets` property, but rather a keyed property that's inferred from the request. // @ts-expect-error idleTasks: { doc_count: 0, overdue: { - doc_count: 0, - nonRecurring: { - doc_count: 0, + doc_count: 1, + nonRecurring: { doc_count: 0 }, + taskTypes: { + buckets: [{ key: 'foo', doc_count: 1 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, scheduleDensity: { @@ -89,9 +136,7 @@ describe('Workload Statistics Aggregator', () => { to: 1.601651976274e12, to_as_string: '2020-10-02T15:19:36.274Z', doc_count: 0, - histogram: { - buckets: [], - }, + histogram: { buckets: [] }, }, ], }, @@ -100,87 +145,51 @@ describe('Workload Statistics Aggregator', () => { }) ); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve) => { workloadAggregator.pipe(first()).subscribe(() => { expect(taskStore.aggregate).toHaveBeenCalledWith({ aggs: { taskType: { - terms: { size: 100, field: 'task.taskType' }, - aggs: { - status: { - terms: { field: 'task.status' }, - }, - }, + terms: { size: 3, field: 'task.taskType' }, + aggs: { status: { terms: { field: 'task.status' } } }, }, schedule: { - terms: { - field: 'task.schedule.interval', - size: 100, - }, + terms: { field: 'task.schedule.interval', size: 100 }, }, nonRecurringTasks: { - missing: { field: 'task.schedule' }, + missing: { field: 'task.schedule.interval' }, + aggs: { taskType: { terms: { size: 3, field: 'task.taskType' } } }, }, ownerIds: { - filter: { - range: { - 'task.startedAt': { - gte: 'now-1w/w', - }, - }, - }, - aggs: { - ownerIds: { - cardinality: { - field: 'task.ownerId', - }, - }, - }, + filter: { range: { 'task.startedAt': { gte: 'now-1w/w' } } }, + aggs: { ownerIds: { cardinality: { field: 'task.ownerId' } } }, }, idleTasks: { - filter: { - term: { 'task.status': 'idle' }, - }, + filter: { term: { 'task.status': 'idle' } }, aggs: { scheduleDensity: { - range: { - field: 'task.runAt', - ranges: [{ from: 'now', to: 'now+1m' }], - }, + range: { field: 'task.runAt', ranges: [{ from: 'now', to: 'now+1m' }] }, aggs: { histogram: { - date_histogram: { - field: 'task.runAt', - fixed_interval: '3s', - }, - aggs: { - interval: { - terms: { - field: 'task.schedule.interval', - }, - }, - }, + date_histogram: { field: 'task.runAt', fixed_interval: '3s' }, + aggs: { interval: { terms: { field: 'task.schedule.interval' } } }, }, }, }, overdue: { - filter: { - range: { - 'task.runAt': { lt: 'now' }, - }, - }, + filter: { range: { 'task.runAt': { lt: 'now' } } }, aggs: { - nonRecurring: { - missing: { field: 'task.schedule' }, - }, + nonRecurring: { missing: { field: 'task.schedule.interval' } }, + taskTypes: { terms: { size: 3, field: 'task.taskType' } }, }, }, }, @@ -192,137 +201,189 @@ describe('Workload Statistics Aggregator', () => { }); }); - const mockAggregatedResult = () => - asApiResponse({ - hits: { - hits: [], - max_score: 0, - total: { value: 4, relation: 'eq' }, + const mockResult = (overrides = {}): ResponseWithAggs => ({ + hits: { hits: [], max_score: 0, total: { value: 4, relation: 'eq' } }, + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 1, failed: 0 }, + aggregations: { + schedule: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: '3600s', doc_count: 1 }, + { key: '60s', doc_count: 1 }, + { key: '720m', doc_count: 1 }, + ], }, - took: 1, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 1, - failed: 0, + taskType: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'foo', + doc_count: 2, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 2 }], + }, + }, + { + key: 'bar', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + { + key: 'report', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + ], }, - aggregations: { - schedule: { + nonRecurringTasks: { + doc_count: 1, + taskType: { + buckets: [{ key: 'report', doc_count: 1 }], doc_count_error_upper_bound: 0, sum_other_doc_count: 0, + }, + }, + ownerIds: { ownerIds: { value: 1 } }, + // The `FiltersAggregate` doesn't cover the case of a nested `AggregationsAggregationContainer`, in which `FiltersAggregate` + // would not have a `buckets` property, but rather a keyed property that's inferred from the request. + // @ts-expect-error + idleTasks: { + doc_count: 3, + overdue: { + doc_count: 2, + nonRecurring: { doc_count: 1 }, + taskTypes: { + buckets: [{ key: 'foo', doc_count: 1 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + }, + }, + scheduleDensity: { buckets: [ - { - key: '3600s', - doc_count: 1, - }, - { - key: '60s', - doc_count: 1, - }, - { - key: '720m', - doc_count: 1, - }, + mockHistogram(0, 7 * 3000 + 500, 60 * 1000, 3000, [2, 2, 5, 0, 0, 0, 0, 0, 0, 1]), ], }, + }, + ...overrides, + }, + }); + + const mockAggregatedResult = () => asApiResponse(mockResult()); + const mockAggregatedResultWithUnknownTaskType = () => + asApiResponse( + mockResult({ taskType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { - key: 'actions_telemetry', + key: 'unknownType', + doc_count: 1, + status: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [{ key: 'idle', doc_count: 1 }], + }, + }, + { + key: 'foo', doc_count: 2, status: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, - buckets: [ - { - key: 'idle', - doc_count: 2, - }, - ], + buckets: [{ key: 'idle', doc_count: 2 }], }, }, { - key: 'alerting_telemetry', + key: 'bar', doc_count: 1, status: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, - buckets: [ - { - key: 'idle', - doc_count: 1, - }, - ], + buckets: [{ key: 'idle', doc_count: 1 }], }, }, { - key: 'session_cleanup', + key: 'report', doc_count: 1, status: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, - buckets: [ - { - key: 'idle', - doc_count: 1, - }, - ], + buckets: [{ key: 'idle', doc_count: 1 }], }, }, ], }, - nonRecurringTasks: { - doc_count: 13, - }, - ownerIds: { - ownerIds: { - value: 1, - }, - }, - // The `FiltersAggregate` doesn't cover the case of a nested `AggregationsAggregationContainer`, in which `FiltersAggregate` - // would not have a `buckets` property, but rather a keyed property that's inferred from the request. - // @ts-expect-error - idleTasks: { - doc_count: 13, - overdue: { - doc_count: 6, - nonRecurring: { - doc_count: 6, - }, - }, - scheduleDensity: { - buckets: [ - mockHistogram(0, 7 * 3000 + 500, 60 * 1000, 3000, [2, 2, 5, 0, 0, 0, 0, 0, 0, 1]), - ], - }, - }, - }, - }); + }) + ); test('returns a summary of the workload by task type', async () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); + + return new Promise<void>((resolve) => { + workloadAggregator.pipe(first()).subscribe((result) => { + expect(result.key).toEqual('workload'); + expect(result.value).toMatchObject({ + count: 4, + cost: 15, + task_types: { + foo: { count: 2, cost: 4, status: { idle: 2 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, + }, + }); + resolve(); + }); + }); + }); + + test('excludes unregistered task types from the summary', async () => { + const taskStore = taskStoreMock.create({}); + taskStore.aggregate.mockResolvedValue(mockAggregatedResultWithUnknownTaskType()); + + const workloadAggregator = createWorkloadAggregator({ + taskStore, + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { expect(result.key).toEqual('workload'); expect(result.value).toMatchObject({ count: 4, + cost: 15, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + foo: { count: 2, cost: 4, status: { idle: 2 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, }, }); resolve(); @@ -336,13 +397,14 @@ describe('Workload Statistics Aggregator', () => { const availability$ = new Subject<boolean>(); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - availability$, - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>(async (resolve, reject) => { try { @@ -350,25 +412,11 @@ describe('Workload Statistics Aggregator', () => { expect(result.key).toEqual('workload'); expect(result.value).toMatchObject({ count: 4, + cost: 15, task_types: { - actions_telemetry: { - count: 2, - status: { - idle: 2, - }, - }, - alerting_telemetry: { - count: 1, - status: { - idle: 1, - }, - }, - session_cleanup: { - count: 1, - status: { - idle: 1, - }, - }, + foo: { count: 2, cost: 4, status: { idle: 2 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, }, }); resolve(); @@ -389,19 +437,22 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { expect(result.key).toEqual('workload'); expect(result.value).toMatchObject({ - overdue: 6, + overdue: 2, + overdue_cost: 2, + overdue_non_recurring: 1, }); resolve(); }); @@ -412,13 +463,14 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { @@ -440,13 +492,14 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 60 * 1000, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 60 * 1000, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve) => { workloadAggregator.pipe(first()).subscribe(() => { @@ -478,13 +531,14 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue(mockAggregatedResult()); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 15 * 60 * 1000, - 3000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 15 * 60 * 1000, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { @@ -517,42 +571,41 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate .mockResolvedValueOnce( - mockAggregatedResult().then((res) => - setTaskTypeCount(res, 'alerting_telemetry', { - idle: 2, - }) - ) + mockAggregatedResult().then((res) => setTaskTypeCount(res, 'foo', { idle: 2 })) ) .mockRejectedValueOnce(new Error('Elasticsearch has gone poof')) .mockResolvedValueOnce( - mockAggregatedResult().then((res) => - setTaskTypeCount(res, 'alerting_telemetry', { - idle: 1, - failed: 1, - }) - ) + mockAggregatedResult().then((res) => setTaskTypeCount(res, 'foo', { idle: 1, failed: 1 })) ); - const logger = loggingSystemMock.create().get(); - const workloadAggregator = createWorkloadAggregator(taskStore, of(true), 10, 3000, logger); + const workloadAggregator = createWorkloadAggregator({ + taskStore, + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve, reject) => { workloadAggregator.pipe(take(2), bufferCount(2)).subscribe((results) => { expect(results[0].key).toEqual('workload'); expect(results[0].value).toMatchObject({ - count: 5, + count: 4, + cost: 15, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 2, status: { idle: 2 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, + foo: { count: 2, cost: 4, status: { idle: 2 } }, }, }); expect(results[1].key).toEqual('workload'); expect(results[1].value).toMatchObject({ - count: 5, + count: 4, + cost: 15, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 2, status: { idle: 1, failed: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + bar: { count: 1, cost: 1, status: { idle: 1 } }, + report: { count: 1, cost: 10, status: { idle: 1 } }, + foo: { count: 2, cost: 4, status: { idle: 1, failed: 1 } }, }, }); resolve(); @@ -567,49 +620,27 @@ describe('Workload Statistics Aggregator', () => { const taskStore = taskStoreMock.create({}); taskStore.aggregate.mockResolvedValue( asApiResponse({ - hits: { - hits: [], - max_score: 0, - total: { value: 4, relation: 'eq' }, - }, + hits: { hits: [], max_score: 0, total: { value: 4, relation: 'eq' } }, took: 1, timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 1, - failed: 0, - }, + _shards: { total: 1, successful: 1, skipped: 1, failed: 0 }, aggregations: { schedule: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ // repeats each cycle - { - key: `${pollingIntervalInSeconds}s`, - doc_count: 1, - }, - { - key: `10s`, // 6 times per minute - doc_count: 20, - }, - { - key: `60s`, // 1 times per minute - doc_count: 10, - }, - { - key: '15m', // 4 times per hour - doc_count: 90, - }, - { - key: '720m', // 2 times per day - doc_count: 10, - }, - { - key: '3h', // 8 times per day - doc_count: 100, - }, + { key: `${pollingIntervalInSeconds}s`, doc_count: 1 }, + // 6 times per minute + { key: `10s`, doc_count: 20 }, + // 1 times per minute + { key: `60s`, doc_count: 10 }, + // 4 times per hour + { key: '15m', doc_count: 90 }, + // 2 times per day + { key: '720m', doc_count: 10 }, + // 8 times per day + { key: '3h', doc_count: 100 }, ], }, taskType: { @@ -619,12 +650,13 @@ describe('Workload Statistics Aggregator', () => { }, nonRecurringTasks: { doc_count: 13, - }, - ownerIds: { - ownerIds: { - value: 3, + taskType: { + buckets: [{ key: 'report', doc_count: 13 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, + ownerIds: { ownerIds: { value: 3 } }, // The `FiltersAggregate` doesn't cover the case of a nested `AggregationContainer`, in which `FiltersAggregate` // would not have a `buckets` property, but rather a keyed property that's inferred from the request. // @ts-expect-error @@ -632,8 +664,11 @@ describe('Workload Statistics Aggregator', () => { doc_count: 13, overdue: { doc_count: 6, - nonRecurring: { - doc_count: 0, + nonRecurring: { doc_count: 0 }, + taskTypes: { + buckets: [{ key: 'foo', doc_count: 6 }], + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, }, }, scheduleDensity: { @@ -646,13 +681,14 @@ describe('Workload Statistics Aggregator', () => { }) ); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), - 10, - pollingIntervalInSeconds * 1000, - loggingSystemMock.create().get() - ); + elasticsearchAndSOAvailability$: of(true), + refreshInterval: 10, + pollInterval: pollingIntervalInSeconds * 1000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve) => { workloadAggregator.pipe(first()).subscribe((result) => { @@ -660,7 +696,7 @@ describe('Workload Statistics Aggregator', () => { expect(result.value).toMatchObject({ capacity_requirements: { - // these are buckets of required capacity, rather than aggregated requirmenets. + // these are buckets of required capacity, rather than aggregated requirements. per_minute: 150, per_hour: 360, per_day: 820, @@ -675,14 +711,14 @@ describe('Workload Statistics Aggregator', () => { const refreshInterval = 1000; const taskStore = taskStoreMock.create({}); - const logger = loggingSystemMock.create().get(); - const workloadAggregator = createWorkloadAggregator( + const workloadAggregator = createWorkloadAggregator({ taskStore, - of(true), + elasticsearchAndSOAvailability$: of(true), refreshInterval, - 3000, - logger - ); + pollInterval: 3000, + logger, + taskDefinitions: definitions, + }); return new Promise<void>((resolve, reject) => { let errorWasThrowAt = 0; @@ -694,9 +730,7 @@ describe('Workload Statistics Aggregator', () => { reject(new Error(`Elasticsearch is still poof`)); } - return setTaskTypeCount(await mockAggregatedResult(), 'alerting_telemetry', { - idle: 2, - }); + return setTaskTypeCount(await mockAggregatedResult(), 'foo', { idle: 2 }); }); workloadAggregator.pipe(take(2), bufferCount(2)).subscribe((results) => { @@ -799,7 +833,7 @@ describe('estimateRecurringTaskScheduling', () => { }); describe('padBuckets', () => { - test('returns zeroed out bucklets when there are no buckets in the histogram', async () => { + test('returns zeroed out buckets when there are no buckets in the histogram', async () => { expect( padBuckets(10, 3000, { key: '2020-10-02T19:47:28.128Z-2020-10-02T19:48:28.128Z', diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index 6c372ce0fc453..b62ca8e8169e9 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -16,7 +16,9 @@ import { AggregatedStatProvider } from '../lib/runtime_statistics_aggregator'; import { parseIntervalAsSecond, asInterval, parseIntervalAsMillisecond } from '../lib/intervals'; import { HealthStatus } from './monitoring_stats_stream'; import { TaskStore } from '../task_store'; -import { createRunningAveragedStat } from './task_run_calcultors'; +import { createRunningAveragedStat } from './task_run_calculators'; +import { TaskTypeDictionary } from '../task_type_dictionary'; +import { TaskCost } from '../task'; interface StatusStat extends JsonObject { [status: string]: number; @@ -24,16 +26,20 @@ interface StatusStat extends JsonObject { interface TaskTypeStat extends JsonObject { [taskType: string]: { count: number; + cost: number; status: StatusStat; }; } interface RawWorkloadStat extends JsonObject { count: number; + cost: number; task_types: TaskTypeStat; schedule: Array<[string, number]>; non_recurring: number; + non_recurring_cost: number; overdue: number; + overdue_cost: number; overdue_non_recurring: number; estimated_schedule_density: number[]; capacity_requirements: CapacityRequirements; @@ -109,22 +115,34 @@ type ScheduleDensityResult = AggregationResultOf< type ScheduledIntervals = ScheduleDensityResult['histogram']['buckets'][0]; // Set an upper bound just in case a customer sets a really high refresh rate -const MAX_SHCEDULE_DENSITY_BUCKETS = 50; +const MAX_SCHEDULE_DENSITY_BUCKETS = 50; + +interface CreateWorkloadAggregatorOpts { + taskStore: TaskStore; + elasticsearchAndSOAvailability$: Observable<boolean>; + refreshInterval: number; + pollInterval: number; + logger: Logger; + taskDefinitions: TaskTypeDictionary; +} -export function createWorkloadAggregator( - taskStore: TaskStore, - elasticsearchAndSOAvailability$: Observable<boolean>, - refreshInterval: number, - pollInterval: number, - logger: Logger -): AggregatedStatProvider<WorkloadStat> { +export function createWorkloadAggregator({ + taskStore, + elasticsearchAndSOAvailability$, + refreshInterval, + pollInterval, + logger, + taskDefinitions, +}: CreateWorkloadAggregatorOpts): AggregatedStatProvider<WorkloadStat> { // calculate scheduleDensity going two refreshIntervals or 1 minute into into the future // (the longer of the two) const scheduleDensityBuckets = Math.min( Math.max(Math.round(60000 / pollInterval), Math.round((refreshInterval * 2) / pollInterval)), - MAX_SHCEDULE_DENSITY_BUCKETS + MAX_SCHEDULE_DENSITY_BUCKETS ); + const totalNumTaskDefinitions = taskDefinitions.getAllTypes().length; + const taskTypeTermAggSize = Math.min(totalNumTaskDefinitions, 10000); const ownerIdsQueue = createRunningAveragedStat<number>(scheduleDensityBuckets); return combineLatest([timer(0, refreshInterval), elasticsearchAndSOAvailability$]).pipe( @@ -133,39 +151,24 @@ export function createWorkloadAggregator( taskStore.aggregate({ aggs: { taskType: { - terms: { size: 100, field: 'task.taskType' }, - aggs: { - status: { - terms: { field: 'task.status' }, - }, - }, + terms: { size: taskTypeTermAggSize, field: 'task.taskType' }, + aggs: { status: { terms: { field: 'task.status' } } }, }, schedule: { terms: { field: 'task.schedule.interval', size: 100 }, }, nonRecurringTasks: { - missing: { field: 'task.schedule' }, - }, - ownerIds: { - filter: { - range: { - 'task.startedAt': { - gte: 'now-1w/w', - }, - }, - }, + missing: { field: 'task.schedule.interval' }, aggs: { - ownerIds: { - cardinality: { - field: 'task.ownerId', - }, - }, + taskType: { terms: { size: taskTypeTermAggSize, field: 'task.taskType' } }, }, }, + ownerIds: { + filter: { range: { 'task.startedAt': { gte: 'now-1w/w' } } }, + aggs: { ownerIds: { cardinality: { field: 'task.ownerId' } } }, + }, idleTasks: { - filter: { - term: { 'task.status': 'idle' }, - }, + filter: { term: { 'task.status': 'idle' } }, aggs: { scheduleDensity: { // create a window of upcoming tasks @@ -187,7 +190,7 @@ export function createWorkloadAggregator( field: 'task.runAt', fixed_interval: asInterval(pollInterval), }, - // break down each bucket in the historgram by schedule + // break down each bucket in the histogram by schedule aggs: { interval: { terms: { field: 'task.schedule.interval' }, @@ -197,15 +200,10 @@ export function createWorkloadAggregator( }, }, overdue: { - filter: { - range: { - 'task.runAt': { lt: 'now' }, - }, - }, + filter: { range: { 'task.runAt': { lt: 'now' } } }, aggs: { - nonRecurring: { - missing: { field: 'task.schedule' }, - }, + taskTypes: { terms: { size: taskTypeTermAggSize, field: 'task.taskType' } }, + nonRecurring: { missing: { field: 'task.schedule.interval' } }, }, }, }, @@ -226,11 +224,13 @@ export function createWorkloadAggregator( const taskTypes = aggregations.taskType.buckets; const nonRecurring = aggregations.nonRecurringTasks.doc_count; + const nonRecurringTaskTypes = aggregations.nonRecurringTasks.taskType.buckets; const ownerIds = aggregations.ownerIds.ownerIds.value; const { overdue: { doc_count: overdue, + taskTypes: { buckets: taskTypesOverdue = [] } = {}, nonRecurring: { doc_count: overdueNonRecurring }, }, scheduleDensity: { buckets: [scheduleDensity] = [] } = {}, @@ -243,6 +243,7 @@ export function createWorkloadAggregator( asSeconds: parseIntervalAsSecond(schedule.key as string), count: schedule.doc_count, }; + accm.schedules.push(parsedSchedule); if (parsedSchedule.asSeconds <= 60) { accm.cadence.perMinute += @@ -257,11 +258,7 @@ export function createWorkloadAggregator( return accm; }, { - cadence: { - perMinute: 0, - perHour: 0, - perDay: 0, - }, + cadence: { perMinute: 0, perHour: 0, perDay: 0 }, schedules: [] as Array<{ interval: string; asSeconds: number; @@ -270,20 +267,42 @@ export function createWorkloadAggregator( } ); + const totalNonRecurringCost = getTotalCost(nonRecurringTaskTypes, taskDefinitions); + const totalOverdueCost = getTotalCost(taskTypesOverdue, taskDefinitions); + + let totalCost = 0; + const taskTypeSummary = taskTypes.reduce((acc, bucket) => { + const value = bucket as TaskTypeWithStatusBucket; + const taskDef = taskDefinitions.get(value.key as string); + if (taskDef) { + const cost = value.doc_count * taskDef?.cost ?? TaskCost.Normal; + + totalCost += cost; + return Object.assign(acc, { + [value.key as string]: { + count: value.doc_count, + cost, + status: mapValues(keyBy(value.status.buckets, 'key'), 'doc_count'), + }, + }); + } else { + // task type is not registered with dictionary, do not add to summary + return acc; + } + }, {}); + const summary: WorkloadStat = { count, - task_types: mapValues(keyBy(taskTypes, 'key'), ({ doc_count: docCount, status }) => { - return { - count: docCount, - status: mapValues(keyBy(status.buckets, 'key'), 'doc_count'), - }; - }), + cost: totalCost, + task_types: taskTypeSummary, non_recurring: nonRecurring, + non_recurring_cost: totalNonRecurringCost, owner_ids: ownerIdsQueue(ownerIds), schedule: schedules .sort((scheduleLeft, scheduleRight) => scheduleLeft.asSeconds - scheduleRight.asSeconds) .map((schedule) => [schedule.interval, schedule.count]), overdue, + overdue_cost: totalOverdueCost, overdue_non_recurring: overdueNonRecurring, estimated_schedule_density: padBuckets( scheduleDensityBuckets, @@ -457,40 +476,37 @@ export interface WorkloadAggregationResponse { taskType: TaskTypeAggregation; schedule: ScheduleAggregation; idleTasks: IdleTasksAggregation; - nonRecurringTasks: { - doc_count: number; - }; - ownerIds: { - ownerIds: { - value: number; - }; - }; + nonRecurringTasks: { doc_count: number; taskType: TaskTypeAggregation }; + ownerIds: { ownerIds: { value: number } }; [otherAggs: string]: estypes.AggregationsAggregate; } + +export type TaskTypeWithStatusBucket = TaskTypeBucket & { + status: { + buckets: Array<{ + doc_count: number; + key: string | number; + }>; + doc_count_error_upper_bound?: number | undefined; + sum_other_doc_count?: number | undefined; + }; +}; + +export interface TaskTypeBucket { + doc_count: number; + key: string | number; +} + // @ts-expect-error key doesn't accept a string export interface TaskTypeAggregation extends estypes.AggregationsFiltersAggregate { - buckets: Array<{ - doc_count: number; - key: string | number; - status: { - buckets: Array<{ - doc_count: number; - key: string | number; - }>; - doc_count_error_upper_bound?: number | undefined; - sum_other_doc_count?: number | undefined; - }; - }>; + buckets: Array<TaskTypeBucket | TaskTypeWithStatusBucket>; doc_count_error_upper_bound?: number | undefined; sum_other_doc_count?: number | undefined; } // @ts-expect-error key doesn't accept a string export interface ScheduleAggregation extends estypes.AggregationsFiltersAggregate { - buckets: Array<{ - doc_count: number; - key: string | number; - }>; + buckets: Array<{ doc_count: number; key: string | number }>; doc_count_error_upper_bound?: number | undefined; sum_other_doc_count?: number | undefined; } @@ -518,9 +534,8 @@ export interface IdleTasksAggregation extends estypes.AggregationsFiltersAggrega }; overdue: { doc_count: number; - nonRecurring: { - doc_count: number; - }; + nonRecurring: { doc_count: number }; + taskTypes: TaskTypeAggregation; }; } @@ -537,3 +552,16 @@ interface DateRangeBucket { from_as_string?: string; doc_count: number; } + +function getTotalCost(taskTypeBuckets: TaskTypeBucket[], definitions: TaskTypeDictionary): number { + let cost = 0; + for (const bucket of taskTypeBuckets) { + const taskDef = definitions.get(bucket.key as string); + if (taskDef) { + cost += bucket.doc_count * taskDef?.cost ?? TaskCost.Normal; + } else { + // task type is not registered with dictionary, do not add to cost + } + } + return cost; +} diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts index 7b80920a57559..353062dfec4fc 100644 --- a/x-pack/plugins/task_manager/server/plugin.test.ts +++ b/x-pack/plugins/task_manager/server/plugin.test.ts @@ -11,6 +11,7 @@ import { TaskManagerConfig } from './config'; import { Subject } from 'rxjs'; import { bufferCount, take } from 'rxjs'; import { CoreStatus, ServiceStatusLevels } from '@kbn/core/server'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { taskPollingLifecycleMock } from './polling_lifecycle.mock'; import { TaskPollingLifecycle } from './polling_lifecycle'; import type { TaskPollingLifecycle as TaskPollingLifecycleClass } from './polling_lifecycle'; @@ -38,7 +39,6 @@ jest.mock('./ephemeral_task_lifecycle', () => { const coreStart = coreMock.createStart(); const pluginInitializerContextParams = { - max_workers: 10, max_attempts: 9, poll_interval: 3000, version_conflict_threshold: 80, @@ -148,7 +148,9 @@ describe('TaskManagerPlugin', () => { pluginInitializerContext.node.roles.backgroundTasks = true; const taskManagerPlugin = new TaskManagerPlugin(pluginInitializerContext); taskManagerPlugin.setup(coreMock.createSetup(), { usageCollection: undefined }); - taskManagerPlugin.start(coreStart); + taskManagerPlugin.start(coreStart, { + cloud: cloudMock.createStart(), + }); expect(TaskPollingLifecycle as jest.Mock<TaskPollingLifecycleClass>).toHaveBeenCalledTimes(1); expect( @@ -163,7 +165,9 @@ describe('TaskManagerPlugin', () => { pluginInitializerContext.node.roles.backgroundTasks = false; const taskManagerPlugin = new TaskManagerPlugin(pluginInitializerContext); taskManagerPlugin.setup(coreMock.createSetup(), { usageCollection: undefined }); - taskManagerPlugin.start(coreStart); + taskManagerPlugin.start(coreStart, { + cloud: cloudMock.createStart(), + }); expect(TaskPollingLifecycle as jest.Mock<TaskPollingLifecycleClass>).not.toHaveBeenCalled(); expect( diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 1926b48b31ea6..23ef12605baa2 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -18,6 +18,7 @@ import { ServiceStatusLevels, CoreStatus, } from '@kbn/core/server'; +import type { CloudStart } from '@kbn/cloud-plugin/server'; import { registerDeleteInactiveNodesTaskDefinition, scheduleDeleteInactiveNodesTaskDefinition, @@ -43,6 +44,7 @@ import { setupIntervalLogging } from './lib/log_health_metrics'; import { metricsStream, Metrics } from './metrics'; import { TaskManagerMetricsCollector } from './metrics/task_metrics_collector'; import { TaskPartitioner } from './lib/task_partitioner'; +import { getDefaultCapacity } from './lib/get_default_capacity'; export interface TaskManagerSetupContract { /** @@ -76,6 +78,10 @@ export type TaskManagerStartContract = Pick< getRegisteredTypes: () => string[]; }; +export interface TaskManagerPluginStart { + cloud?: CloudStart; +} + const LogHealthForBackgroundTasksOnlyMinutes = 60; export class TaskManagerPlugin @@ -99,6 +105,7 @@ export class TaskManagerPlugin private taskManagerMetricsCollector?: TaskManagerMetricsCollector; private nodeRoles: PluginInitializerContext['node']['roles']; private kibanaDiscoveryService?: KibanaDiscoveryService; + private heapSizeLimit: number = 0; constructor(private readonly initContext: PluginInitializerContext) { this.initContext = initContext; @@ -122,6 +129,13 @@ export class TaskManagerPlugin ): TaskManagerSetupContract { this.elasticsearchAndSOAvailability$ = getElasticsearchAndSOAvailability(core.status.core$); + core.metrics + .getOpsMetrics$() + .pipe(distinctUntilChanged()) + .subscribe((metrics) => { + this.heapSizeLimit = metrics.process.memory.heap.size_limit; + }); + setupSavedObjects(core.savedObjects, this.config); this.taskManagerId = this.initContext.env.instanceUuid; @@ -232,12 +246,10 @@ export class TaskManagerPlugin }; } - public start({ - savedObjects, - elasticsearch, - executionContext, - docLinks, - }: CoreStart): TaskManagerStartContract { + public start( + { savedObjects, elasticsearch, executionContext, docLinks }: CoreStart, + { cloud }: TaskManagerPluginStart + ): TaskManagerStartContract { const savedObjectsRepository = savedObjects.createInternalRepository([ TASK_SO_NAME, BACKGROUND_TASK_NODE_SO_NAME, @@ -267,11 +279,31 @@ export class TaskManagerPlugin requestTimeouts: this.config.request_timeouts, }); + const isServerless = this.initContext.env.packageInfo.buildFlavor === 'serverless'; + + const defaultCapacity = getDefaultCapacity({ + claimStrategy: this.config?.claim_strategy, + heapSizeLimit: this.heapSizeLimit, + isCloud: cloud?.isCloudEnabled ?? false, + isServerless, + isBackgroundTaskNodeOnly: this.isNodeBackgroundTasksOnly(), + }); + + this.logger.info( + `Task manager isCloud=${ + cloud?.isCloudEnabled ?? false + } isServerless=${isServerless} claimStrategy=${ + this.config!.claim_strategy + } isBackgroundTaskNodeOnly=${this.isNodeBackgroundTasksOnly()} heapSizeLimit=${ + this.heapSizeLimit + } defaultCapacity=${defaultCapacity}` + ); + const managedConfiguration = createManagedConfiguration({ - logger: this.logger, + config: this.config!, errors$: taskStore.errors$, - startingMaxWorkers: this.config!.max_workers, - startingPollInterval: this.config!.poll_interval, + defaultCapacity, + logger: this.logger, }); // Only poll for tasks if configured to run tasks @@ -310,16 +342,17 @@ export class TaskManagerPlugin }); } - createMonitoringStats( + createMonitoringStats({ taskStore, - this.elasticsearchAndSOAvailability$!, - this.config!, - managedConfiguration, - this.logger, - this.adHocTaskCounter, - this.taskPollingLifecycle, - this.ephemeralTaskLifecycle - ).subscribe((stat) => this.monitoringStats$.next(stat)); + elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, + config: this.config!, + managedConfig: managedConfiguration, + logger: this.logger, + adHocTaskCounter: this.adHocTaskCounter, + taskDefinitions: this.definitions, + taskPollingLifecycle: this.taskPollingLifecycle, + ephemeralTaskLifecycle: this.ephemeralTaskLifecycle, + }).subscribe((stat) => this.monitoringStats$.next(stat)); metricsStream({ config: this.config!, diff --git a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts index f06c43bc15587..11741aeadcf2d 100644 --- a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts +++ b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts @@ -22,10 +22,10 @@ describe('delayOnClaimConflicts', () => { 'initializes with a delay of 0', fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 10; + const capacity = 10; const taskLifecycleEvents$ = new Subject<TaskLifecycleEvent>(); const delays = delayOnClaimConflicts( - of(maxWorkers), + of(capacity), of(pollInterval), taskLifecycleEvents$, 80, @@ -42,11 +42,11 @@ describe('delayOnClaimConflicts', () => { 'emits a random delay whenever p50 of claim clashes exceed 80% of available max_workers', fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 10; + const capacity = 10; const taskLifecycleEvents$ = new Subject<TaskLifecycleEvent>(); const delays$ = firstValueFrom<number[]>( - delayOnClaimConflicts(of(maxWorkers), of(pollInterval), taskLifecycleEvents$, 80, 2).pipe( + delayOnClaimConflicts(of(capacity), of(pollInterval), taskLifecycleEvents$, 80, 2).pipe( take(2), bufferCount(2) ) @@ -60,7 +60,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 8, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -94,7 +93,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 8, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -111,7 +109,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 10, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -137,18 +134,14 @@ describe('delayOnClaimConflicts', () => { 'doesnt emit a new delay when conflicts have reduced', fakeSchedulers(async () => { const pollInterval = 100; - const maxWorkers = 10; + const capacity = 10; const taskLifecycleEvents$ = new Subject<TaskLifecycleEvent>(); const handler = jest.fn(); - delayOnClaimConflicts( - of(maxWorkers), - of(pollInterval), - taskLifecycleEvents$, - 80, - 2 - ).subscribe(handler); + delayOnClaimConflicts(of(capacity), of(pollInterval), taskLifecycleEvents$, 80, 2).subscribe( + handler + ); await sleep(0); expect(handler).toHaveBeenCalledWith(0); @@ -161,7 +154,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 8, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -182,7 +174,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 7, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) @@ -201,7 +192,6 @@ describe('delayOnClaimConflicts', () => { tasksUpdated: 0, tasksConflicted: 9, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }) diff --git a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts index f491d58fc59ee..21b16b1a8d5c5 100644 --- a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts +++ b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.ts @@ -19,13 +19,14 @@ import { ManagedConfiguration } from '../lib/create_managed_configuration'; import { TaskLifecycleEvent } from '../polling_lifecycle'; import { isTaskPollingCycleEvent } from '../task_events'; import { ClaimAndFillPoolResult } from '../lib/fill_pool'; -import { createRunningAveragedStat } from '../monitoring/task_run_calcultors'; +import { createRunningAveragedStat } from '../monitoring/task_run_calculators'; +import { getCapacityInWorkers } from '../task_pool'; /** * Emits a delay amount in ms to apply to polling whenever the task store exceeds a threshold of claim claimClashes */ export function delayOnClaimConflicts( - maxWorkersConfiguration$: ManagedConfiguration['maxWorkersConfiguration$'], + capacityConfiguration$: ManagedConfiguration['capacityConfiguration$'], pollIntervalConfiguration$: ManagedConfiguration['pollIntervalConfiguration$'], taskLifecycleEvents$: Observable<TaskLifecycleEvent>, claimClashesPercentageThreshold: number, @@ -37,7 +38,7 @@ export function delayOnClaimConflicts( merge( of(0), combineLatest([ - maxWorkersConfiguration$, + capacityConfiguration$, pollIntervalConfiguration$, taskLifecycleEvents$.pipe( map<TaskLifecycleEvent, Option<number>>((taskEvent: TaskLifecycleEvent) => @@ -51,7 +52,10 @@ export function delayOnClaimConflicts( map((claimClashes: Option<number>) => (claimClashes as Some<number>).value) ), ]).pipe( - map(([maxWorkers, pollInterval, latestClaimConflicts]) => { + map(([capacity, pollInterval, latestClaimConflicts]) => { + // convert capacity to maxWorkers + const maxWorkers = getCapacityInWorkers(capacity); + // add latest claimConflict count to queue claimConflictQueue(latestClaimConflicts); diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts index baf45cb65ea1e..e804f1c166cee 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts @@ -20,6 +20,8 @@ import { asOk, Err, isErr, isOk, Result } from './lib/result_type'; import { FillPoolResult } from './lib/fill_pool'; import { ElasticsearchResponseError } from './lib/identify_es_error'; import { executionContextServiceMock } from '@kbn/core/server/mocks'; +import { TaskCost } from './task'; +import { CLAIM_STRATEGY_MGET } from './config'; import { TaskPartitioner } from './lib/task_partitioner'; import { KibanaDiscoveryService } from './kibana_discovery_service'; @@ -44,7 +46,6 @@ describe('TaskPollingLifecycle', () => { const taskManagerOpts = { config: { enabled: true, - max_workers: 10, index: 'foo', max_attempts: 9, poll_interval: 6000000, @@ -90,7 +91,8 @@ describe('TaskPollingLifecycle', () => { unusedTypes: [], definitions: new TaskTypeDictionary(taskManagerLogger), middleware: createInitialMiddleware(), - maxWorkersConfiguration$: of(100), + startingCapacity: 20, + capacityConfiguration$: of(20), pollIntervalConfiguration$: of(100), executionContext, taskPartitioner: new TaskPartitioner('test', {} as KibanaDiscoveryService), @@ -105,12 +107,23 @@ describe('TaskPollingLifecycle', () => { afterEach(() => clock.restore()); describe('start', () => { + taskManagerOpts.definitions.registerTaskDefinitions({ + report: { + title: 'report', + maxConcurrency: 1, + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + quickReport: { + title: 'quickReport', + maxConcurrency: 5, + createTaskRunner: jest.fn(), + }, + }); + test('begins polling once the ES and SavedObjects services are available', () => { const elasticsearchAndSOAvailability$ = new Subject<boolean>(); - new TaskPollingLifecycle({ - ...taskManagerOpts, - elasticsearchAndSOAvailability$, - }); + new TaskPollingLifecycle({ ...taskManagerOpts, elasticsearchAndSOAvailability$ }); clock.tick(150); expect(mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable).not.toHaveBeenCalled(); @@ -121,56 +134,71 @@ describe('TaskPollingLifecycle', () => { expect(mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable).toHaveBeenCalled(); }); - test('provides TaskClaiming with the capacity available', () => { + test('provides TaskClaiming with the capacity available when strategy = CLAIM_STRATEGY_DEFAULT', () => { const elasticsearchAndSOAvailability$ = new Subject<boolean>(); - const maxWorkers$ = new Subject<number>(); - taskManagerOpts.definitions.registerTaskDefinitions({ - report: { - title: 'report', - maxConcurrency: 1, - createTaskRunner: jest.fn(), - }, - quickReport: { - title: 'quickReport', - maxConcurrency: 5, - createTaskRunner: jest.fn(), - }, - }); + const capacity$ = new Subject<number>(); new TaskPollingLifecycle({ ...taskManagerOpts, elasticsearchAndSOAvailability$, - maxWorkersConfiguration$: maxWorkers$, + capacityConfiguration$: capacity$, }); const taskClaimingGetCapacity = (TaskClaiming as jest.Mock<TaskClaimingClass>).mock - .calls[0][0].getCapacity; + .calls[0][0].getAvailableCapacity; - maxWorkers$.next(20); - expect(taskClaimingGetCapacity()).toEqual(20); + capacity$.next(40); + expect(taskClaimingGetCapacity()).toEqual(40); expect(taskClaimingGetCapacity('report')).toEqual(1); expect(taskClaimingGetCapacity('quickReport')).toEqual(5); - maxWorkers$.next(30); - expect(taskClaimingGetCapacity()).toEqual(30); + capacity$.next(60); + expect(taskClaimingGetCapacity()).toEqual(60); expect(taskClaimingGetCapacity('report')).toEqual(1); expect(taskClaimingGetCapacity('quickReport')).toEqual(5); - maxWorkers$.next(2); - expect(taskClaimingGetCapacity()).toEqual(2); + capacity$.next(4); + expect(taskClaimingGetCapacity()).toEqual(4); expect(taskClaimingGetCapacity('report')).toEqual(1); - expect(taskClaimingGetCapacity('quickReport')).toEqual(2); + expect(taskClaimingGetCapacity('quickReport')).toEqual(4); }); - }); - describe('stop', () => { - test('stops polling once the ES and SavedObjects services become unavailable', () => { + test('provides TaskClaiming with the capacity available when strategy = CLAIM_STRATEGY_MGET', () => { const elasticsearchAndSOAvailability$ = new Subject<boolean>(); + const capacity$ = new Subject<number>(); + new TaskPollingLifecycle({ - elasticsearchAndSOAvailability$, ...taskManagerOpts, + config: { ...taskManagerOpts.config, claim_strategy: CLAIM_STRATEGY_MGET }, + elasticsearchAndSOAvailability$, + capacityConfiguration$: capacity$, }); + const taskClaimingGetCapacity = (TaskClaiming as jest.Mock<TaskClaimingClass>).mock + .calls[0][0].getAvailableCapacity; + + capacity$.next(40); + expect(taskClaimingGetCapacity()).toEqual(80); + expect(taskClaimingGetCapacity('report')).toEqual(10); + expect(taskClaimingGetCapacity('quickReport')).toEqual(10); + + capacity$.next(60); + expect(taskClaimingGetCapacity()).toEqual(120); + expect(taskClaimingGetCapacity('report')).toEqual(10); + expect(taskClaimingGetCapacity('quickReport')).toEqual(10); + + capacity$.next(4); + expect(taskClaimingGetCapacity()).toEqual(8); + expect(taskClaimingGetCapacity('report')).toEqual(8); + expect(taskClaimingGetCapacity('quickReport')).toEqual(8); + }); + }); + + describe('stop', () => { + test('stops polling once the ES and SavedObjects services become unavailable', () => { + const elasticsearchAndSOAvailability$ = new Subject<boolean>(); + new TaskPollingLifecycle({ elasticsearchAndSOAvailability$, ...taskManagerOpts }); + elasticsearchAndSOAvailability$.next(true); clock.tick(150); @@ -216,7 +244,7 @@ describe('TaskPollingLifecycle', () => { of( asOk({ docs: [], - stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, tasksRejected: 0 }, + stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0 }, }) ) ); @@ -298,7 +326,47 @@ describe('TaskPollingLifecycle', () => { of( asOk({ docs: [], - stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, tasksRejected: 0 }, + stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0 }, + }) + ) + ); + const elasticsearchAndSOAvailability$ = new Subject<boolean>(); + const taskPollingLifecycle = new TaskPollingLifecycle({ + ...taskManagerOpts, + elasticsearchAndSOAvailability$, + }); + + const emittedEvents: TaskLifecycleEvent[] = []; + + taskPollingLifecycle.events.subscribe((event: TaskLifecycleEvent) => + emittedEvents.push(event) + ); + + elasticsearchAndSOAvailability$.next(true); + expect(mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable).toHaveBeenCalled(); + await retryUntil('workerUtilizationEvent emitted', () => { + return !!emittedEvents.find( + (event: TaskLifecycleEvent) => event.id === 'workerUtilization' + ); + }); + + const workerUtilizationEvent = emittedEvents.find( + (event: TaskLifecycleEvent) => event.id === 'workerUtilization' + ); + expect(workerUtilizationEvent).toEqual({ + id: 'workerUtilization', + type: 'TASK_MANAGER_STAT', + event: { tag: 'ok', value: 0 }, + }); + }); + + test('should set utilization to max when capacity is not fully reached but there are tasks left unclaimed', async () => { + clock.restore(); + mockTaskClaiming.claimAvailableTasksIfCapacityIsAvailable.mockImplementation(() => + of( + asOk({ + docs: [], + stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, tasksLeftUnclaimed: 2 }, }) ) ); @@ -321,6 +389,15 @@ describe('TaskPollingLifecycle', () => { (event: TaskLifecycleEvent) => event.id === 'workerUtilization' ); }); + + const workerUtilizationEvent = emittedEvents.find( + (event: TaskLifecycleEvent) => event.id === 'workerUtilization' + ); + expect(workerUtilizationEvent).toEqual({ + id: 'workerUtilization', + type: 'TASK_MANAGER_STAT', + event: { tag: 'ok', value: 100 }, + }); }); test('should emit event when polling error occurs', async () => { diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index 3b9c5621da0b9..f13a7ad20806c 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -45,6 +45,8 @@ import { TaskClaiming } from './queries/task_claiming'; import { ClaimOwnershipResult } from './task_claimers'; import { TaskPartitioner } from './lib/task_partitioner'; +const MAX_BUFFER_OPERATIONS = 100; + export interface ITaskEventEmitter<T> { get events(): Observable<T>; } @@ -101,7 +103,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven constructor({ logger, middleware, - maxWorkersConfiguration$, + capacityConfiguration$, pollIntervalConfiguration$, // Elasticsearch and SavedObjects availability status elasticsearchAndSOAvailability$, @@ -124,13 +126,15 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven const emitEvent = (event: TaskLifecycleEvent) => this.events$.next(event); this.bufferedStore = new BufferedTaskStore(this.store, { - bufferMaxOperations: config.max_workers, + bufferMaxOperations: MAX_BUFFER_OPERATIONS, logger, }); this.pool = new TaskPool({ logger, - maxWorkers$: maxWorkersConfiguration$, + strategy: config.claim_strategy, + capacity$: capacityConfiguration$, + definitions: this.definitions, }); this.pool.load.subscribe(emitEvent); @@ -142,17 +146,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven definitions, unusedTypes, logger: this.logger, - getCapacity: (taskType?: string) => - taskType && this.definitions.get(taskType)?.maxConcurrency - ? Math.max( - Math.min( - this.pool.availableWorkers, - this.definitions.get(taskType)!.maxConcurrency! - - this.pool.getOccupiedWorkersByType(taskType) - ), - 0 - ) - : this.pool.availableWorkers, + getAvailableCapacity: (taskType?: string) => this.pool.availableCapacity(taskType), taskPartitioner, }); // pipe taskClaiming events into the lifecycle event stream @@ -163,7 +157,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven let pollIntervalDelay$: Observable<number> | undefined; if (claimStrategy === CLAIM_STRATEGY_DEFAULT) { pollIntervalDelay$ = delayOnClaimConflicts( - maxWorkersConfiguration$, + capacityConfiguration$, pollIntervalConfiguration$, this.events$, config.version_conflict_threshold, @@ -177,19 +171,22 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven pollInterval$: pollIntervalConfiguration$, pollIntervalDelay$, getCapacity: () => { - const capacity = this.pool.availableWorkers; + const capacity = this.pool.availableCapacity(); if (!capacity) { + const usedCapacityPercentage = this.pool.usedCapacityPercentage; + // if there isn't capacity, emit a load event so that we can expose how often // high load causes the poller to skip work (work isn't called when there is no capacity) - this.emitEvent(asTaskManagerStatEvent('load', asOk(this.pool.workerLoad))); + this.emitEvent(asTaskManagerStatEvent('load', asOk(usedCapacityPercentage))); // Emit event indicating task manager utilization - this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(this.pool.workerLoad))); + this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(usedCapacityPercentage))); } return capacity; }, work: this.pollForWork, }); + this.subscribeToPoller(poller.events$); elasticsearchAndSOAvailability$.subscribe((areESAndSOAvailable) => { @@ -262,7 +259,7 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven const [result] = await Promise.all([this.pool.run(tasksToRun), ...removeTaskPromises]); // Emit the load after fetching tasks, giving us a good metric for evaluating how // busy Task manager tends to be in this Kibana instance - this.emitEvent(asTaskManagerStatEvent('load', asOk(this.pool.workerLoad))); + this.emitEvent(asTaskManagerStatEvent('load', asOk(this.pool.usedCapacityPercentage))); return result; } ); @@ -285,16 +282,29 @@ export class TaskPollingLifecycle implements ITaskEventEmitter<TaskLifecycleEven // Emit event indicating task manager utilization % at the end of a polling cycle // Because there was a polling error, no tasks were claimed so this represents the number of workers busy - this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(this.pool.workerLoad))); + this.emitEvent( + asTaskManagerStatEvent('workerUtilization', asOk(this.pool.usedCapacityPercentage)) + ); }) ) ) .pipe( tap( - mapOk(() => { + mapOk((results: TimedFillPoolResult) => { // Emit event indicating task manager utilization % at the end of a polling cycle - // This represents the number of workers busy + number of tasks claimed in this cycle - this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(this.pool.workerLoad))); + + // Get the actual utilization as a percentage + let tmUtilization = this.pool.usedCapacityPercentage; + + // Check whether there are any tasks left unclaimed + // If we're not at capacity and there are unclaimed tasks, then + // there must be high cost tasks that need to be claimed + // Artificially inflate the utilization to represent the unclaimed load + if (tmUtilization < 100 && (results.stats?.tasksLeftUnclaimed ?? 0) > 0) { + tmUtilization = 100; + } + + this.emitEvent(asTaskManagerStatEvent('workerUtilization', asOk(tmUtilization))); }) ) ) diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index bc4adb71dd4a1..de57a73f80533 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -80,7 +80,7 @@ describe('TaskClaiming', () => { unusedTypes: [], taskStore: taskStoreMock.create({ taskManagerId: '' }), maxAttempts: 2, - getCapacity: () => 10, + getAvailableCapacity: () => 10, taskPartitioner, }); @@ -130,7 +130,7 @@ describe('TaskClaiming', () => { unusedTypes: [], taskStore: taskStoreMock.create({ taskManagerId: '' }), maxAttempts: 2, - getCapacity: () => 10, + getAvailableCapacity: () => 10, taskPartitioner, }); 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 188f47b0d2d2f..f5ef18452509b 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts @@ -38,7 +38,7 @@ export interface TaskClaimingOpts { taskStore: TaskStore; maxAttempts: number; excludedTaskTypes: string[]; - getCapacity: (taskType?: string) => number; + getAvailableCapacity: (taskType?: string) => number; taskPartitioner: TaskPartitioner; } @@ -87,7 +87,7 @@ export class TaskClaiming { private definitions: TaskTypeDictionary; private events$: Subject<TaskClaim>; private taskStore: TaskStore; - private getCapacity: (taskType?: string) => number; + private getAvailableCapacity: (taskType?: string) => number; private logger: Logger; private readonly taskClaimingBatchesByType: TaskClaimingBatches; private readonly taskMaxAttempts: Record<string, number>; @@ -106,7 +106,7 @@ export class TaskClaiming { this.definitions = opts.definitions; this.maxAttempts = opts.maxAttempts; this.taskStore = opts.taskStore; - this.getCapacity = opts.getCapacity; + this.getAvailableCapacity = opts.getAvailableCapacity; this.logger = opts.logger.get('taskClaiming'); this.taskClaimingBatchesByType = this.partitionIntoClaimingBatches(this.definitions); this.taskMaxAttempts = Object.fromEntries(this.normalizeMaxAttempts(this.definitions)); @@ -170,13 +170,13 @@ export class TaskClaiming { public claimAvailableTasksIfCapacityIsAvailable( claimingOptions: Omit<OwnershipClaimingOpts, 'size' | 'taskTypes'> ): Observable<Result<ClaimOwnershipResult, FillPoolResult>> { - if (this.getCapacity()) { + if (this.getAvailableCapacity()) { const opts: TaskClaimerOpts = { batches: this.getClaimingBatches(), claimOwnershipUntil: claimingOptions.claimOwnershipUntil, taskStore: this.taskStore, events$: this.events$, - getCapacity: this.getCapacity, + getCapacity: this.getAvailableCapacity, unusedTypes: this.unusedTypes, definitions: this.definitions, taskMaxAttempts: this.taskMaxAttempts, diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts index a97d99079bc58..9c08c5b5fb4c4 100644 --- a/x-pack/plugins/task_manager/server/routes/health.test.ts +++ b/x-pack/plugins/task_manager/server/routes/health.test.ts @@ -823,7 +823,8 @@ function mockHealthStats(overrides = {}) { configuration: { timestamp: new Date().toISOString(), value: { - max_workers: 10, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -841,16 +842,19 @@ function mockHealthStats(overrides = {}) { timestamp: new Date().toISOString(), value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 2, overdue_non_recurring: 0, estimatedScheduleDensity: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: [0, 0, 0, 1, 2, 0, 0, 2, 2, 2, 1, 2, 1, 1], estimated_schedule_density: [], capacity_requirements: { diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index fae99bb8f1f5b..772d2615ce84a 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -11,11 +11,19 @@ import { isErr, tryAsResult } from './lib/result_type'; import { Interval, isInterval, parseIntervalAsMillisecond } from './lib/intervals'; import { DecoratedError } from './task_running'; +export const DEFAULT_TIMEOUT = '5m'; + export enum TaskPriority { Low = 1, Normal = 50, } +export enum TaskCost { + Tiny = 1, + Normal = 2, + ExtraLarge = 10, +} + /* * Type definitions and validations for tasks. */ @@ -127,6 +135,10 @@ export const taskDefinitionSchema = schema.object( * Priority of this task type. Defaults to "NORMAL" if not defined */ priority: schema.maybe(schema.number()), + /** + * Cost to run this task type. Defaults to "Normal". + */ + cost: schema.number({ defaultValue: TaskCost.Normal }), /** * An optional more detailed description of what this task does. */ @@ -138,7 +150,7 @@ export const taskDefinitionSchema = schema.object( * the task will be re-attempted. */ timeout: schema.string({ - defaultValue: '5m', + defaultValue: DEFAULT_TIMEOUT, }), /** * Up to how many times the task should retry when it fails to run. This will @@ -172,7 +184,7 @@ export const taskDefinitionSchema = schema.object( paramsSchema: schema.maybe(schema.any()), }, { - validate({ timeout, priority }) { + validate({ timeout, priority, cost }) { if (!isInterval(timeout) || isErr(tryAsResult(() => parseIntervalAsMillisecond(timeout)))) { return `Invalid timeout "${timeout}". Timeout must be of the form "{number}{cadance}" where number is an integer. Example: 5m.`; } @@ -182,6 +194,12 @@ export const taskDefinitionSchema = schema.object( .filter((key) => isNaN(Number(key))) .map((key) => `${key} => ${TaskPriority[key as keyof typeof TaskPriority]}`)}`; } + + if (cost && (!isNumber(cost) || !(cost in TaskCost))) { + return `Invalid cost "${cost}". Cost must be one of ${Object.keys(TaskCost) + .filter((key) => isNaN(Number(key))) + .map((key) => `${key} => ${TaskCost[key as keyof typeof TaskCost]}`)}`; + } }, } ); diff --git a/x-pack/plugins/task_manager/server/task_claimers/index.ts b/x-pack/plugins/task_manager/server/task_claimers/index.ts index 1caa6e2addb0f..134c72041f96f 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/index.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/index.ts @@ -37,6 +37,7 @@ export interface ClaimOwnershipResult { tasksUpdated: number; tasksConflicted: number; tasksClaimed: number; + tasksLeftUnclaimed?: number; }; docs: ConcreteTaskInstance[]; timing?: TaskTiming; @@ -61,13 +62,12 @@ export function getTaskClaimer(logger: Logger, strategy: string): TaskClaimerFn return claimAvailableTasksDefault; } -export function getEmptyClaimOwnershipResult() { +export function getEmptyClaimOwnershipResult(): ClaimOwnershipResult { return { stats: { tasksUpdated: 0, tasksConflicted: 0, tasksClaimed: 0, - tasksRejected: 0, }, docs: [], }; diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts index 8aa206bbe1872..d58fd83486efa 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts @@ -133,7 +133,7 @@ describe('TaskClaiming', () => { excludedTaskTypes, unusedTypes: unusedTaskTypes, maxAttempts: taskClaimingOpts.maxAttempts ?? 2, - getCapacity: taskClaimingOpts.getCapacity ?? (() => 10), + getAvailableCapacity: taskClaimingOpts.getAvailableCapacity ?? (() => 10), taskPartitioner, ...taskClaimingOpts, }); @@ -158,7 +158,7 @@ describe('TaskClaiming', () => { excludedTaskTypes?: string[]; unusedTaskTypes?: string[]; }) { - const getCapacity = taskClaimingOpts.getCapacity ?? (() => 10); + const getCapacity = taskClaimingOpts.getAvailableCapacity ?? (() => 10); const { taskClaiming, store } = initialiseTestClaiming({ storeOpts, taskClaimingOpts, @@ -447,7 +447,7 @@ if (doc['task.runAt'].size()!=0) { }, taskClaimingOpts: { maxAttempts, - getCapacity: (type) => { + getAvailableCapacity: (type) => { switch (type) { case 'limitedToOne': case 'anotherLimitedToOne': @@ -577,7 +577,7 @@ if (doc['task.runAt'].size()!=0) { }, taskClaimingOpts: { maxAttempts, - getCapacity: (type) => { + getAvailableCapacity: (type) => { switch (type) { case 'limitedToTwo': return 2; @@ -686,7 +686,7 @@ if (doc['task.runAt'].size()!=0) { }, taskClaimingOpts: { maxAttempts, - getCapacity: (type) => { + getAvailableCapacity: (type) => { switch (type) { case 'limitedToOne': case 'anotherLimitedToOne': @@ -1139,7 +1139,7 @@ if (doc['task.runAt'].size()!=0) { storeOpts: { taskManagerId, }, - taskClaimingOpts: { getCapacity: () => maxDocs }, + taskClaimingOpts: { getAvailableCapacity: () => maxDocs }, claimingOpts: { claimOwnershipUntil, }, @@ -1219,9 +1219,9 @@ if (doc['task.runAt'].size()!=0) { function instantiateStoreWithMockedApiResponses({ taskManagerId = uuidv4(), definitions = taskDefinitions, - getCapacity = () => 10, + getAvailableCapacity = () => 10, tasksClaimed, - }: Partial<Pick<TaskClaimingOpts, 'definitions' | 'getCapacity'>> & { + }: Partial<Pick<TaskClaimingOpts, 'definitions' | 'getAvailableCapacity'>> & { taskManagerId?: string; tasksClaimed?: ConcreteTaskInstance[][]; } = {}) { @@ -1254,7 +1254,7 @@ if (doc['task.runAt'].size()!=0) { unusedTypes: [], taskStore, maxAttempts: 2, - getCapacity, + getAvailableCapacity, taskPartitioner, }); diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts index b58ea02893c10..e9df1bda7b81d 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts @@ -15,10 +15,11 @@ import { ConcreteTaskInstance, ConcreteTaskInstanceVersion, TaskPriority, + TaskCost, } from '../task'; import { SearchOpts, StoreOpts } from '../task_store'; import { asTaskClaimEvent, TaskEvent } from '../task_events'; -import { asOk, isOk, unwrap } from '../lib/result_type'; +import { asOk, asErr, isOk, unwrap } from '../lib/result_type'; import { TaskTypeDictionary } from '../task_type_dictionary'; import { mockLogger } from '../test_utils'; import { @@ -33,6 +34,7 @@ import apm from 'elastic-apm-node'; import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; import { ClaimOwnershipResult } from '.'; import { FillPoolResult } from '../lib/fill_pool'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { TaskPartitioner } from '../lib/task_partitioner'; import type { MustNotCondition } from '../queries/query_clauses'; import { @@ -40,10 +42,6 @@ import { createFindSO, } from '../kibana_discovery_service/mock_kibana_discovery_service'; -jest.mock('../lib/assign_pod_partitions', () => ({ - assignPodPartitions: jest.fn().mockReturnValue([1, 3]), -})); - jest.mock('../constants', () => ({ CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: [ 'limitedToZero', @@ -52,6 +50,7 @@ jest.mock('../constants', () => ({ 'anotherLimitedToOne', 'limitedToTwo', 'limitedToFive', + 'yawn', ], })); @@ -74,14 +73,18 @@ const taskDefinitions = new TaskTypeDictionary(taskManagerLogger); taskDefinitions.registerTaskDefinitions({ report: { title: 'report', + cost: TaskCost.Normal, createTaskRunner: jest.fn(), }, dernstraight: { title: 'dernstraight', + cost: TaskCost.ExtraLarge, createTaskRunner: jest.fn(), }, yawn: { title: 'yawn', + cost: TaskCost.Tiny, + maxConcurrency: 1, createTaskRunner: jest.fn(), }, }); @@ -107,9 +110,21 @@ describe('TaskClaiming', () => { .spyOn(apm, 'startTransaction') // eslint-disable-next-line @typescript-eslint/no-explicit-any .mockImplementation(() => mockApmTrans as any); + jest.spyOn(taskPartitioner, 'getPartitions').mockResolvedValue([1, 3]); }); describe('claimAvailableTasks', () => { + function getVersionMapsFromTasks(tasks: ConcreteTaskInstance[]) { + const versionMap = new Map<string, ConcreteTaskInstanceVersion>(); + const docLatestVersions = new Map<string, ConcreteTaskInstanceVersion>(); + for (const task of tasks) { + versionMap.set(task.id, { esId: task.id, seqNo: 32, primaryTerm: 32 }); + docLatestVersions.set(`task:${task.id}`, { esId: task.id, seqNo: 32, primaryTerm: 32 }); + } + + return { versionMap, docLatestVersions }; + } + function initialiseTestClaiming({ storeOpts = {}, taskClaimingOpts = {}, @@ -130,20 +145,27 @@ describe('TaskClaiming', () => { store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); if (hits == null) hits = [generateFakeTasks(1)]; + + const docVersion = []; if (versionMaps == null) { - versionMaps = [new Map<string, ConcreteTaskInstanceVersion>()]; + versionMaps = []; for (const oneHit of hits) { const map = new Map<string, ConcreteTaskInstanceVersion>(); - versionMaps.push(map); + const mapWithTaskPrefix = new Map<string, ConcreteTaskInstanceVersion>(); for (const task of oneHit) { map.set(task.id, { esId: task.id, seqNo: 32, primaryTerm: 32 }); + mapWithTaskPrefix.set(`task:${task.id}`, { esId: task.id, seqNo: 32, primaryTerm: 32 }); } + versionMaps.push(map); + docVersion.push(mapWithTaskPrefix); } } for (let i = 0; i < hits.length; i++) { store.fetch.mockResolvedValueOnce({ docs: hits[i], versionMap: versionMaps[i] }); - store.getDocVersions.mockResolvedValueOnce(versionMaps[i]); + store.getDocVersions.mockResolvedValueOnce(docVersion[i]); + const oneBulkGetResult = hits[i].map((hit) => asOk(hit)); + store.bulkGet.mockResolvedValueOnce(oneBulkGetResult); const oneBulkResult = hits[i].map((hit) => asOk(hit)); store.bulkUpdate.mockResolvedValueOnce(oneBulkResult); } @@ -156,7 +178,7 @@ describe('TaskClaiming', () => { excludedTaskTypes, unusedTypes: unusedTaskTypes, maxAttempts: taskClaimingOpts.maxAttempts ?? 2, - getCapacity: taskClaimingOpts.getCapacity ?? (() => 10), + getAvailableCapacity: taskClaimingOpts.getAvailableCapacity ?? (() => 10), taskPartitioner, ...taskClaimingOpts, }); @@ -203,6 +225,14 @@ describe('TaskClaiming', () => { return unwrap(resultOrErr) as ClaimOwnershipResult; }); + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(store.fetch.mock.calls).toMatchObject({}); + expect(store.getDocVersions.mock.calls).toMatchObject({}); return results.map((result, index) => ({ result, args: { @@ -289,8 +319,1250 @@ describe('TaskClaiming', () => { expect(result).toMatchObject({}); }); + test('should limit claimed tasks based on task cost and available capacity', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), // total cost = 2 + mockInstance({ id: `id-2`, taskType: 'report' }), // total cost = 4 + mockInstance({ id: `id-3`, taskType: 'yawn' }), // total cost = 5 + mockInstance({ id: `id-4`, taskType: 'dernstraight' }), // claiming this will exceed the available capacity + mockInstance({ id: `id-5`, taskType: 'report' }), + mockInstance({ id: `id-6`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2]].map(asOk) + ); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2]].map(asOk) + ); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 3; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + 'task:id-5', + 'task:id-6', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 3, + tasksConflicted: 0, + tasksUpdated: 3, + tasksLeftUnclaimed: 3, + }); + expect(result.docs.length).toEqual(3); + }); + + test('should not claim tasks of removed type', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[0], fetchedTasks[1]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: ['report'], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 2;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(2); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 1, + [ + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 2, + [ + { + ...fetchedTasks[0], + status: 'unrecognized', + }, + { + ...fetchedTasks[1], + status: 'unrecognized', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 1, + tasksConflicted: 0, + tasksUpdated: 1, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(1); + }); + + test('should log warning if error updating single removed task as unrecognized', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + // @ts-expect-error + asErr({ + type: 'task', + id: fetchedTasks[1].id, + error: SavedObjectsErrorHelpers.createBadRequestError(), + }), + ]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: ['report'], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating task id-2:task to mark as unrecognized during claim: Bad Request', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 1;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(2); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 1, + [ + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 2, + [ + { + ...fetchedTasks[0], + status: 'unrecognized', + }, + { + ...fetchedTasks[1], + status: 'unrecognized', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 1, + tasksConflicted: 0, + tasksUpdated: 1, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(1); + }); + + test('should log warning if error updating all removed tasks as unrecognized', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockRejectedValueOnce(new Error('Oh no')); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: ['report'], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating tasks to mark as unrecognized during claim: Error: Oh no', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 1; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkGet).toHaveBeenCalledWith(['id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(2); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 1, + [ + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkUpdate).toHaveBeenNthCalledWith( + 2, + [ + { + ...fetchedTasks[0], + status: 'unrecognized', + }, + { + ...fetchedTasks[1], + status: 'unrecognized', + }, + ], + { validate: false, excludeLargeFields: true } + ); + + expect(result.stats).toEqual({ + tasksClaimed: 1, + tasksConflicted: 0, + tasksUpdated: 1, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(1); + }); + + test('should handle no tasks to claim', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks: ConcreteTaskInstance[] = []; + + const { versionMap } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).not.toHaveBeenCalled(); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).not.toHaveBeenCalled(); + expect(store.bulkGet).not.toHaveBeenCalled(); + expect(store.bulkUpdate).not.toHaveBeenCalled(); + + expect(result.stats).toEqual({ + tasksClaimed: 0, + tasksConflicted: 0, + tasksUpdated: 0, + }); + expect(result.docs.length).toEqual(0); + }); + + test('should handle tasks with no search version', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + versionMap.delete('id-1'); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 2, + tasksConflicted: 0, + tasksUpdated: 2, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(2); + }); + + test('should handle tasks with no latest version', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + docLatestVersions.delete('task:id-1'); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 2; stale: 0; conflicts: 0; missing: 1; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 2, + tasksConflicted: 0, + tasksUpdated: 2, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(2); + }); + + test('should handle stale tasks', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + docLatestVersions.set('task:id-1', { esId: 'task:id-1', seqNo: 33, primaryTerm: 33 }); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + store.bulkUpdate.mockResolvedValueOnce([fetchedTasks[1], fetchedTasks[2]].map(asOk)); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 2; stale: 1; conflicts: 1; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith(['task:id-1', 'task:id-2', 'task:id-3']); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-2', 'id-3']); + + expect(result.stats).toEqual({ + tasksClaimed: 2, + tasksConflicted: 1, + tasksUpdated: 2, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(2); + }); + + test('should correctly handle limited concurrency tasks', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'yawn' }), + mockInstance({ id: `id-5`, taskType: 'report' }), + mockInstance({ id: `id-6`, taskType: 'yawn' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + + store.bulkGet.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[4]].map(asOk) + ); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[4]].map(asOk) + ); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 4; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + 'task:id-5', + 'task:id-6', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[4], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3', 'id-5']); + + expect(result.stats).toEqual({ + tasksClaimed: 4, + tasksConflicted: 0, + tasksUpdated: 4, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(4); + }); + + test('should handle individual errors when bulk getting the full task doc', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[3]].map(asOk) + ); + store.bulkGet.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + // @ts-expect-error + asErr({ + type: 'task', + id: fetchedTasks[1].id, + error: new Error('Oh no'), + }), + asOk(fetchedTasks[2]), + asOk(fetchedTasks[3]), + ]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error getting full task id-2:task during claim: Oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3', 'id-4']); + + expect(result.stats).toEqual({ + tasksClaimed: 3, + tasksConflicted: 0, + tasksUpdated: 3, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(3); + }); + + test('should handle error when bulk getting all full task docs', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockResolvedValueOnce( + [fetchedTasks[0], fetchedTasks[1], fetchedTasks[2], fetchedTasks[3]].map(asOk) + ); + store.bulkGet.mockRejectedValueOnce(new Error('oh no')); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 0; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error getting full task documents during claim: Error: oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-2', 'id-3', 'id-4']); + + expect(result.stats).toEqual({ + tasksClaimed: 0, + tasksConflicted: 0, + tasksUpdated: 0, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(0); + }); + + test('should handle individual errors when bulk updating the task doc', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + // @ts-expect-error + asErr({ + type: 'task', + id: fetchedTasks[1].id, + error: new Error('Oh no'), + }), + asOk(fetchedTasks[2]), + asOk(fetchedTasks[3]), + ]); + store.bulkGet.mockResolvedValueOnce([ + asOk(fetchedTasks[0]), + asOk(fetchedTasks[2]), + asOk(fetchedTasks[3]), + ]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 3; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 1; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating task id-2:task during claim: Oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith(['id-1', 'id-3', 'id-4']); + + expect(result.stats).toEqual({ + tasksClaimed: 3, + tasksConflicted: 0, + tasksUpdated: 3, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(3); + }); + + test('should handle error when bulk updating all task docs', async () => { + const store = taskStoreMock.create({ taskManagerId: 'test-test' }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + const fetchedTasks = [ + mockInstance({ id: `id-1`, taskType: 'report' }), + mockInstance({ id: `id-2`, taskType: 'report' }), + mockInstance({ id: `id-3`, taskType: 'yawn' }), + mockInstance({ id: `id-4`, taskType: 'report' }), + ]; + + const { versionMap, docLatestVersions } = getVersionMapsFromTasks(fetchedTasks); + store.fetch.mockResolvedValueOnce({ docs: fetchedTasks, versionMap }); + store.getDocVersions.mockResolvedValueOnce(docLatestVersions); + store.bulkUpdate.mockRejectedValueOnce(new Error('oh no')); + store.bulkGet.mockResolvedValueOnce([]); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions: taskDefinitions, + taskStore: store, + excludedTaskTypes: [], + unusedTypes: [], + maxAttempts: 2, + getAvailableCapacity: () => 10, + taskPartitioner, + }); + + const [resultOrErr] = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ claimOwnershipUntil: new Date() }) + ); + + if (!isOk<ClaimOwnershipResult, FillPoolResult>(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + + const result = unwrap(resultOrErr) as ClaimOwnershipResult; + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(taskManagerLogger.debug).toHaveBeenCalledWith( + 'task claimer claimed: 0; stale: 0; conflicts: 0; missing: 0; capacity reached: 0; updateErrors: 0; removed: 0;', + { tags: ['claimAvailableTasksMget'] } + ); + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Error updating tasks during claim: Error: oh no', + { tags: ['claimAvailableTasksMget'] } + ); + + expect(store.fetch.mock.calls[0][0]).toMatchObject({ size: 40, seq_no_primary_term: true }); + expect(store.getDocVersions).toHaveBeenCalledWith([ + 'task:id-1', + 'task:id-2', + 'task:id-3', + 'task:id-4', + ]); + expect(store.bulkUpdate).toHaveBeenCalledTimes(1); + expect(store.bulkUpdate).toHaveBeenCalledWith( + [ + { + ...fetchedTasks[0], + ownerId: 'test-test', + retryAt: fetchedTasks[0].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[1], + ownerId: 'test-test', + retryAt: fetchedTasks[1].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[2], + ownerId: 'test-test', + retryAt: fetchedTasks[2].runAt, + status: 'claiming', + }, + { + ...fetchedTasks[3], + ownerId: 'test-test', + retryAt: fetchedTasks[3].runAt, + status: 'claiming', + }, + ], + { validate: false, excludeLargeFields: true } + ); + expect(store.bulkGet).toHaveBeenCalledWith([]); + + expect(result.stats).toEqual({ + tasksClaimed: 0, + tasksConflicted: 0, + tasksUpdated: 0, + tasksLeftUnclaimed: 0, + }); + expect(result.docs.length).toEqual(0); + }); + test('it should filter for specific partitions and tasks without partitions', async () => { const taskManagerId = uuidv4(); + const definitions = new TaskTypeDictionary(mockLogger()); + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + createTaskRunner: jest.fn(), + }, + bar: { + title: 'bar', + createTaskRunner: jest.fn(), + }, + }); const [ { args: { @@ -300,6 +1572,7 @@ describe('TaskClaiming', () => { ] = await testClaimAvailableTasks({ storeOpts: { taskManagerId, + definitions, }, taskClaimingOpts: {}, claimingOpts: { @@ -355,9 +1628,8 @@ describe('TaskClaiming', () => { Object { "terms": Object { "task.taskType": Array [ - "report", - "dernstraight", - "yawn", + "foo", + "bar", ], }, }, @@ -498,9 +1770,9 @@ describe('TaskClaiming', () => { function instantiateStoreWithMockedApiResponses({ taskManagerId = uuidv4(), definitions = taskDefinitions, - getCapacity = () => 10, + getAvailableCapacity = () => 10, tasksClaimed, - }: Partial<Pick<TaskClaimingOpts, 'definitions' | 'getCapacity'>> & { + }: Partial<Pick<TaskClaimingOpts, 'definitions' | 'getAvailableCapacity'>> & { taskManagerId?: string; tasksClaimed?: ConcreteTaskInstance[][]; } = {}) { @@ -533,7 +1805,7 @@ describe('TaskClaiming', () => { unusedTypes: [], taskStore, maxAttempts: 2, - getCapacity, + getAvailableCapacity, taskPartitioner, }); diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts index 362c38166339f..7962fdd2b6f8a 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts @@ -7,9 +7,11 @@ // Basic operation of this task claimer: // - search for candidate tasks to run, more than we actually can run +// - initial search returns a slimmer task document for I/O efficiency (no params or state) // - for each task found, do an mget to get the current seq_no and primary_term // - if the mget result doesn't match the search result, the task is stale -// - from the non-stale search results, return as many as we can run +// - from the non-stale search results, return as many as we can run based on available +// capacity and the cost of each task type to run import { SavedObjectsErrorHelpers } from '@kbn/core/server'; @@ -18,7 +20,7 @@ import { Subject, Observable } from 'rxjs'; import { TaskTypeDictionary } from '../task_type_dictionary'; import { TaskClaimerOpts, ClaimOwnershipResult, getEmptyClaimOwnershipResult } from '.'; -import { ConcreteTaskInstance, TaskStatus, ConcreteTaskInstanceVersion } from '../task'; +import { ConcreteTaskInstance, TaskStatus, ConcreteTaskInstanceVersion, TaskCost } from '../task'; import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; import { isLimited, @@ -112,7 +114,10 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise<ClaimOwnershi taskStore, events$, claimOwnershipUntil, - size: initialCapacity * SIZE_MULTIPLIER_FOR_TASK_FETCH, + // set size to accommodate the possibility of retrieving all + // tasks with the smallest cost, with a size multipler to account + // for possible conflicts + size: initialCapacity * TaskCost.Tiny * SIZE_MULTIPLIER_FOR_TASK_FETCH, taskMaxAttempts, taskPartitioner, }); @@ -156,35 +161,54 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise<ClaimOwnershi continue; } } + // apply limited concurrency limits (TODO: can currently starve other tasks) const candidateTasks = applyLimitedConcurrency(currentTasks, batches); - // build the updated task objects we'll claim - const taskUpdates: ConcreteTaskInstance[] = Array.from(candidateTasks) - .slice(0, initialCapacity) - .map((task) => { - if (task.retryAt != null && new Date(task.retryAt).getTime() < Date.now()) { - task.scheduledAt = task.retryAt; - } else { - task.scheduledAt = task.runAt; - } - task.retryAt = claimOwnershipUntil; - task.ownerId = taskStore.taskManagerId; - task.status = TaskStatus.Claiming; + // apply capacity constraint to candidate tasks + const tasksToRun: ConcreteTaskInstance[] = []; + const leftOverTasks: ConcreteTaskInstance[] = []; + + let capacityAccumulator = 0; + for (const task of candidateTasks) { + const taskCost = definitions.get(task.taskType)?.cost ?? TaskCost.Normal; + if (capacityAccumulator + taskCost <= initialCapacity) { + tasksToRun.push(task); + capacityAccumulator += taskCost; + } else { + leftOverTasks.push(task); + capacityAccumulator = initialCapacity; + } + } - return task; + // build the updated task objects we'll claim + const taskUpdates: ConcreteTaskInstance[] = []; + for (const task of tasksToRun) { + taskUpdates.push({ + ...task, + scheduledAt: + task.retryAt != null && new Date(task.retryAt).getTime() < Date.now() + ? task.retryAt + : task.runAt, + status: TaskStatus.Claiming, + retryAt: claimOwnershipUntil, + ownerId: taskStore.taskManagerId, }); + } // perform the task object updates, deal with errors - const finalResults: ConcreteTaskInstance[] = []; + const updatedTasks: ConcreteTaskInstance[] = []; let conflicts = staleTasks.length; let bulkErrors = 0; try { - const updateResults = await taskStore.bulkUpdate(taskUpdates, { validate: false }); + const updateResults = await taskStore.bulkUpdate(taskUpdates, { + validate: false, + excludeLargeFields: true, + }); for (const updateResult of updateResults) { if (isOk(updateResult)) { - finalResults.push(updateResult.value); + updatedTasks.push(updateResult.value); } else { const { id, type, error } = updateResult.error; @@ -209,6 +233,27 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise<ClaimOwnershi logger.warn(`Error updating tasks during claim: ${err}`, logMeta); } + // perform an mget to get the full task instance for claiming + let fullTasksToRun: ConcreteTaskInstance[] = []; + try { + fullTasksToRun = (await taskStore.bulkGet(updatedTasks.map((task) => task.id))).reduce< + ConcreteTaskInstance[] + >((acc, task) => { + if (isOk(task)) { + acc.push(task.value); + } else { + const { id, type, error } = task.error; + logger.warn( + `Error getting full task ${id}:${type} during claim: ${error.message}`, + logMeta + ); + } + return acc; + }, []); + } catch (err) { + logger.warn(`Error getting full task documents during claim: ${err}`, logMeta); + } + // separate update for removed tasks; shouldn't happen often, so unlikely // a performance concern, and keeps the rest of the logic simpler let removedCount = 0; @@ -220,7 +265,10 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise<ClaimOwnershi // don't worry too much about errors, we'll get them next time try { - const removeResults = await taskStore.bulkUpdate(tasksToRemove, { validate: false }); + const removeResults = await taskStore.bulkUpdate(tasksToRemove, { + validate: false, + excludeLargeFields: true, + }); for (const removeResult of removeResults) { if (isOk(removeResult)) { removedCount++; @@ -238,21 +286,22 @@ async function claimAvailableTasks(opts: TaskClaimerOpts): Promise<ClaimOwnershi } // TODO: need a better way to generate stats - const message = `task claimer claimed: ${finalResults.length}; stale: ${staleTasks.length}; conflicts: ${conflicts}; missing: ${missingTasks.length}; updateErrors: ${bulkErrors}; removed: ${removedCount};`; + const message = `task claimer claimed: ${fullTasksToRun.length}; stale: ${staleTasks.length}; conflicts: ${conflicts}; missing: ${missingTasks.length}; capacity reached: ${leftOverTasks.length}; updateErrors: ${bulkErrors}; removed: ${removedCount};`; logger.debug(message, logMeta); // build results const finalResult = { stats: { - tasksUpdated: finalResults.length, + tasksUpdated: fullTasksToRun.length, tasksConflicted: conflicts, - tasksClaimed: finalResults.length, + tasksClaimed: fullTasksToRun.length, + tasksLeftUnclaimed: leftOverTasks.length, }, - docs: finalResults, + docs: fullTasksToRun, timing: stopTaskTimer(), }; - for (const doc of finalResults) { + for (const doc of fullTasksToRun) { events$.next(asTaskClaimEvent(doc.id, asOk(doc), finalResult.timing)); } @@ -296,12 +345,16 @@ async function searchAvailableTasks({ tasksWithPartitions(partitions) ); - return await taskStore.fetch({ - query, - sort, - size, - seq_no_primary_term: true, - }); + return await taskStore.fetch( + { + query, + sort, + size, + seq_no_primary_term: true, + }, + // limit the response size + true + ); } function applyLimitedConcurrency( diff --git a/x-pack/plugins/task_manager/server/task_pool.mock.ts b/x-pack/plugins/task_manager/server/task_pool.mock.ts deleted file mode 100644 index 77568c8c6cdfa..0000000000000 --- a/x-pack/plugins/task_manager/server/task_pool.mock.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { TaskPool } from './task_pool'; - -const defaultGetCapacityOverride: () => Partial<{ - load: number; - occupiedWorkers: number; - workerLoad: number; - max: number; - availableWorkers: number; -}> = () => ({ - load: 0, - occupiedWorkers: 0, - workerLoad: 0, - max: 10, - availableWorkers: 10, -}); - -const createTaskPoolMock = (getCapacityOverride = defaultGetCapacityOverride) => { - return { - get load() { - return getCapacityOverride().load ?? 0; - }, - get occupiedWorkers() { - return getCapacityOverride().occupiedWorkers ?? 0; - }, - get workerLoad() { - return getCapacityOverride().workerLoad ?? 0; - }, - get max() { - return getCapacityOverride().max ?? 10; - }, - get availableWorkers() { - return getCapacityOverride().availableWorkers ?? 10; - }, - getOccupiedWorkersByType: jest.fn(), - run: jest.fn(), - cancelRunningTasks: jest.fn(), - } as unknown as jest.Mocked<TaskPool>; -}; - -export const TaskPoolMock = { - create: createTaskPoolMock, -}; diff --git a/x-pack/plugins/task_manager/server/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool.test.ts deleted file mode 100644 index 5fb1325da3df9..0000000000000 --- a/x-pack/plugins/task_manager/server/task_pool.test.ts +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { of, Subject } from 'rxjs'; -import { TaskPool, TaskPoolRunResult } from './task_pool'; -import { resolvable, sleep } from './test_utils'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { Logger } from '@kbn/core/server'; -import { asOk } from './lib/result_type'; -import { SavedObjectsErrorHelpers } from '@kbn/core/server'; -import moment from 'moment'; -import { v4 as uuidv4 } from 'uuid'; -import { TaskRunningStage } from './task_running'; - -describe('TaskPool', () => { - beforeEach(() => { - jest.useFakeTimers(); - jest.setSystemTime(new Date(2021, 12, 30)); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - test('occupiedWorkers are a sum of running tasks', async () => { - const pool = new TaskPool({ - maxWorkers$: of(200), - logger: loggingSystemMock.create().get(), - }); - - const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - expect(pool.occupiedWorkers).toEqual(3); - }); - - test('availableWorkers are a function of total_capacity - occupiedWorkers', async () => { - const pool = new TaskPool({ - maxWorkers$: of(10), - logger: loggingSystemMock.create().get(), - }); - - const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - expect(pool.availableWorkers).toEqual(7); - }); - - test('availableWorkers is 0 until maxWorkers$ pushes a value', async () => { - const maxWorkers$ = new Subject<number>(); - const pool = new TaskPool({ - maxWorkers$, - logger: loggingSystemMock.create().get(), - }); - - expect(pool.availableWorkers).toEqual(0); - maxWorkers$.next(10); - expect(pool.availableWorkers).toEqual(10); - }); - - test('does not run tasks that are beyond its available capacity', async () => { - const pool = new TaskPool({ - maxWorkers$: of(2), - logger: loggingSystemMock.create().get(), - }); - - const shouldRun = mockRun(); - const shouldNotRun = mockRun(); - - const result = await pool.run([ - { ...mockTask(), run: shouldRun }, - { ...mockTask(), run: shouldRun }, - { ...mockTask(), run: shouldNotRun }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); - expect(pool.availableWorkers).toEqual(0); - expect(shouldRun).toHaveBeenCalledTimes(2); - expect(shouldNotRun).not.toHaveBeenCalled(); - }); - - test('should log when marking a Task as running fails', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const taskFailedToMarkAsRunning = mockTask(); - taskFailedToMarkAsRunning.markTaskAsRunning.mockImplementation(async () => { - throw new Error(`Mark Task as running has failed miserably`); - }); - - const result = await pool.run([mockTask(), taskFailedToMarkAsRunning, mockTask()]); - - expect((logger as jest.Mocked<Logger>).error.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Failed to mark Task TaskType \\"shooooo\\" as running: Mark Task as running has failed miserably", - ] - `); - - expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); - }); - - test('should log when running a Task fails', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(3), - logger, - }); - - const taskFailedToRun = mockTask(); - taskFailedToRun.run.mockImplementation(async () => { - throw new Error(`Run Task has failed miserably`); - }); - - const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); - - expect((logger as jest.Mocked<Logger>).warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "Task TaskType \\"shooooo\\" failed in attempt to run: Run Task has failed miserably", - ] - `); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - }); - - test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(3), - logger, - }); - - const taskFailedToRun = mockTask(); - taskFailedToRun.run.mockImplementation(async () => { - throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); - }); - - const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); - - expect(logger.debug).toHaveBeenCalledWith( - `Task TaskType "shooooo" failed in attempt to run: Saved object [task/${taskFailedToRun.id}] not found` - ); - expect(logger.warn).not.toHaveBeenCalled(); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - }); - - test('Running a task which fails still takes up capacity', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(1), - logger, - }); - - const taskFailedToRun = mockTask(); - taskFailedToRun.run.mockImplementation(async () => { - await sleep(0); - throw new Error(`Run Task has failed miserably`); - }); - - const result = await pool.run([taskFailedToRun, mockTask()]); - - expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); - }); - - test('clears up capacity when a task completes', async () => { - const pool = new TaskPool({ - maxWorkers$: of(1), - logger: loggingSystemMock.create().get(), - }); - - const firstWork = resolvable(); - const firstRun = sinon.spy(async () => { - await sleep(0); - firstWork.resolve(); - return asOk({ state: {} }); - }); - const secondWork = resolvable(); - const secondRun = sinon.spy(async () => { - await sleep(0); - secondWork.resolve(); - return asOk({ state: {} }); - }); - - const result = await pool.run([ - { ...mockTask(), run: firstRun }, - { ...mockTask(), run: secondRun }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); - expect(pool.occupiedWorkers).toEqual(1); - expect(pool.availableWorkers).toEqual(0); - - await firstWork; - sinon.assert.calledOnce(firstRun); - sinon.assert.notCalled(secondRun); - - expect(pool.occupiedWorkers).toEqual(0); - await pool.run([{ ...mockTask(), run: secondRun }]); - expect(pool.occupiedWorkers).toEqual(1); - - expect(pool.availableWorkers).toEqual(0); - - await secondWork; - - expect(pool.occupiedWorkers).toEqual(0); - expect(pool.availableWorkers).toEqual(1); - sinon.assert.calledOnce(secondRun); - }); - - test('run cancels expired tasks prior to running new tasks', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const haltUntilWeAfterFirstRun = resolvable(); - const taskHasExpired = resolvable(); - const haltTaskSoThatItCanBeCanceled = resolvable(); - - const shouldRun = sinon.spy(() => Promise.resolve()); - const shouldNotRun = sinon.spy(() => Promise.resolve()); - const now = new Date(); - const result = await pool.run([ - { - ...mockTask({ id: '1' }), - async run() { - await haltUntilWeAfterFirstRun; - this.isExpired = true; - taskHasExpired.resolve(); - await haltTaskSoThatItCanBeCanceled; - return asOk({ state: {} }); - }, - get expiration() { - return now; - }, - get startedAt() { - // 5 and a half minutes - return moment(now).subtract(5, 'm').subtract(30, 's').toDate(); - }, - cancel: shouldRun, - }, - { - ...mockTask({ id: '2' }), - async run() { - // halt here so that we can verify that this task is counted in `occupiedWorkers` - await haltUntilWeAfterFirstRun; - return asOk({ state: {} }); - }, - cancel: shouldNotRun, - }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); - expect(pool.occupiedWorkers).toEqual(2); - expect(pool.availableWorkers).toEqual(0); - - // release first stage in task so that it has time to expire, but not complete - haltUntilWeAfterFirstRun.resolve(); - await taskHasExpired; - - expect(await pool.run([{ ...mockTask({ id: '3' }) }])).toBeTruthy(); - - sinon.assert.calledOnce(shouldRun); - sinon.assert.notCalled(shouldNotRun); - - expect(pool.occupiedWorkers).toEqual(1); - expect(pool.availableWorkers).toEqual(1); - - haltTaskSoThatItCanBeCanceled.resolve(); - - expect(logger.warn).toHaveBeenCalledWith( - `Cancelling task TaskType "shooooo" as it expired at ${now.toISOString()} after running for 05m 30s (with timeout set at 5m).` - ); - }); - - test('calls to availableWorkers ensures we cancel expired tasks', async () => { - const pool = new TaskPool({ - maxWorkers$: of(1), - logger: loggingSystemMock.create().get(), - }); - - const taskIsRunning = resolvable(); - const taskHasExpired = resolvable(); - const cancel = sinon.spy(() => Promise.resolve()); - const now = new Date(); - expect( - await pool.run([ - { - ...mockTask(), - async run() { - await sleep(10); - this.isExpired = true; - taskIsRunning.resolve(); - await taskHasExpired; - return asOk({ state: {} }); - }, - get expiration() { - return new Date(now.getTime() + 10); - }, - get startedAt() { - return now; - }, - cancel, - }, - ]) - ).toEqual(TaskPoolRunResult.RunningAtCapacity); - - await taskIsRunning; - - sinon.assert.notCalled(cancel); - expect(pool.occupiedWorkers).toEqual(1); - // The call to `availableWorkers` will clear the expired task so it's 1 instead of 0 - expect(pool.availableWorkers).toEqual(1); - sinon.assert.calledOnce(cancel); - - expect(pool.occupiedWorkers).toEqual(0); - expect(pool.availableWorkers).toEqual(1); - // ensure cancel isn't called twice - sinon.assert.calledOnce(cancel); - taskHasExpired.resolve(); - }); - - test('logs if cancellation errors', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - logger, - maxWorkers$: of(20), - }); - - const cancelled = resolvable(); - const result = await pool.run([ - { - ...mockTask(), - async run() { - this.isExpired = true; - await sleep(10); - return asOk({ state: {} }); - }, - async cancel() { - cancelled.resolve(); - throw new Error('Dern!'); - }, - toString: () => '"shooooo!"', - }, - ]); - - expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); - await pool.run([]); - - expect(pool.occupiedWorkers).toEqual(0); - - // Allow the task to cancel... - await cancelled; - - expect((logger as jest.Mocked<Logger>).error.mock.calls[0][0]).toMatchInlineSnapshot( - `"Failed to cancel task \\"shooooo!\\": Error: Dern!"` - ); - }); - - test('only allows one task with the same id in the task pool', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const shouldRun = mockRun(); - const shouldNotRun = mockRun(); - - const taskId = uuidv4(); - const task1 = mockTask({ id: taskId, run: shouldRun }); - const task2 = mockTask({ - id: taskId, - run: shouldNotRun, - isSameTask() { - return true; - }, - }); - - await pool.run([task1]); - await pool.run([task2]); - - expect(shouldRun).toHaveBeenCalledTimes(1); - expect(shouldNotRun).not.toHaveBeenCalled(); - }); - - // This test is from https://github.com/elastic/kibana/issues/172116 - // It's not clear how to reproduce the actual error, but it is easy to - // reproduce with the wacky test below. It does log the exact error - // from that issue, without the corresponding fix in task_pool.ts - test('works when available workers is 0 but there are tasks to run', async () => { - const logger = loggingSystemMock.create().get(); - const pool = new TaskPool({ - maxWorkers$: of(2), - logger, - }); - - const shouldRun = mockRun(); - - const taskId = uuidv4(); - const task1 = mockTask({ id: taskId, run: shouldRun }); - - // we need to alternate the values of `availableWorkers`. First it - // should be 0, then 1, then 0, then 1, etc. This will cause task_pool.run - // to partition tasks (0 to run, everything as leftover), then at the - // end of run(), to check if it should recurse, it should be > 0. - let awValue = 1; - Object.defineProperty(pool, 'availableWorkers', { - get() { - return ++awValue % 2; - }, - }); - - const result = await pool.run([task1]); - expect(result).toBe(TaskPoolRunResult.RanOutOfCapacity); - - expect((logger as jest.Mocked<Logger>).warn.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "task pool run attempts exceeded 3; assuming ran out of capacity; availableWorkers: 0, tasksToRun: 0, leftOverTasks: 1, maxWorkers: 2, occupiedWorkers: 0, workerLoad: 0", - ] - `); - }); - - function mockRun() { - return jest.fn(async () => { - await sleep(0); - return asOk({ state: {} }); - }); - } - - function mockTask(overrides = {}) { - return { - isExpired: false, - taskExecutionId: uuidv4(), - id: uuidv4(), - cancel: async () => undefined, - markTaskAsRunning: jest.fn(async () => true), - run: mockRun(), - stage: TaskRunningStage.PENDING, - toString: () => `TaskType "shooooo"`, - isAdHocTaskAndOutOfAttempts: false, - removeTask: jest.fn(), - get expiration() { - return new Date(); - }, - get startedAt() { - return new Date(); - }, - get definition() { - return { - type: '', - title: '', - timeout: '5m', - createTaskRunner: jest.fn(), - }; - }, - isSameTask() { - return false; - }, - ...overrides, - }; - } -}); diff --git a/x-pack/plugins/task_manager/server/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts deleted file mode 100644 index c0784f0458f72..0000000000000 --- a/x-pack/plugins/task_manager/server/task_pool.ts +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * This module contains the logic that ensures we don't run too many - * tasks at once in a given Kibana instance. - */ -import { Observable, Subject } from 'rxjs'; -import moment, { Duration } from 'moment'; -import { padStart } from 'lodash'; -import { Logger } from '@kbn/core/server'; -import { TaskRunner } from './task_running'; -import { isTaskSavedObjectNotFoundError } from './lib/is_task_not_found_error'; -import { TaskManagerStat } from './task_events'; - -interface Opts { - maxWorkers$: Observable<number>; - logger: Logger; -} - -export enum TaskPoolRunResult { - // This mean we have no Run Result becuse no tasks were Ran in this cycle - NoTaskWereRan = 'NoTaskWereRan', - // This means we're running all the tasks we claimed - RunningAllClaimedTasks = 'RunningAllClaimedTasks', - // This means we're running all the tasks we claimed and we're at capacity - RunningAtCapacity = 'RunningAtCapacity', - // This means we're prematurely out of capacity and have accidentally claimed more tasks than we had capacity for - RanOutOfCapacity = 'RanOutOfCapacity', -} - -const VERSION_CONFLICT_MESSAGE = 'Task has been claimed by another Kibana service'; -const MAX_RUN_ATTEMPTS = 3; - -/** - * Runs tasks in batches, taking costs into account. - */ -export class TaskPool { - private maxWorkers: number = 0; - private tasksInPool = new Map<string, TaskRunner>(); - private logger: Logger; - private load$ = new Subject<TaskManagerStat>(); - - /** - * Creates an instance of TaskPool. - * - * @param {Opts} opts - * @prop {number} maxWorkers - The total number of workers / work slots available - * (e.g. maxWorkers is 4, then 2 tasks of cost 2 can run at a time, or 4 tasks of cost 1) - * @prop {Logger} logger - The task manager logger. - */ - constructor(opts: Opts) { - this.logger = opts.logger; - opts.maxWorkers$.subscribe((maxWorkers) => { - this.logger.debug(`Task pool now using ${maxWorkers} as the max worker value`); - this.maxWorkers = maxWorkers; - }); - } - - public get load(): Observable<TaskManagerStat> { - return this.load$; - } - - /** - * Gets how many workers are currently in use. - */ - public get occupiedWorkers() { - return this.tasksInPool.size; - } - - /** - * Gets % of workers in use - */ - public get workerLoad() { - return this.maxWorkers ? Math.round((this.occupiedWorkers * 100) / this.maxWorkers) : 100; - } - - /** - * Gets how many workers are currently available. - */ - public get availableWorkers() { - // cancel expired task whenever a call is made to check for capacity - // this ensures that we don't end up with a queue of hung tasks causing both - // the poller and the pool from hanging due to lack of capacity - this.cancelExpiredTasks(); - return this.maxWorkers - this.occupiedWorkers; - } - - /** - * Gets how many workers are currently in use by type. - */ - public getOccupiedWorkersByType(type: string) { - return [...this.tasksInPool.values()].reduce( - (count, runningTask) => (runningTask.definition.type === type ? ++count : count), - 0 - ); - } - - /** - * Attempts to run the specified list of tasks. Returns true if it was able - * to start every task in the list, false if there was not enough capacity - * to run every task. - * - * @param {TaskRunner[]} tasks - * @returns {Promise<boolean>} - */ - public async run(tasks: TaskRunner[], attempt = 1): Promise<TaskPoolRunResult> { - // Note `this.availableWorkers` is a getter with side effects, so we just want - // to call it once for this bit of the code. - const availableWorkers = this.availableWorkers; - const [tasksToRun, leftOverTasks] = partitionListByCount(tasks, availableWorkers); - - if (attempt > MAX_RUN_ATTEMPTS) { - const stats = [ - `availableWorkers: ${availableWorkers}`, - `tasksToRun: ${tasksToRun.length}`, - `leftOverTasks: ${leftOverTasks.length}`, - `maxWorkers: ${this.maxWorkers}`, - `occupiedWorkers: ${this.occupiedWorkers}`, - `workerLoad: ${this.workerLoad}`, - ].join(', '); - this.logger.warn( - `task pool run attempts exceeded ${MAX_RUN_ATTEMPTS}; assuming ran out of capacity; ${stats}` - ); - return TaskPoolRunResult.RanOutOfCapacity; - } - - if (tasksToRun.length) { - await Promise.all( - tasksToRun - .filter( - (taskRunner) => - !Array.from(this.tasksInPool.keys()).some((executionId: string) => - taskRunner.isSameTask(executionId) - ) - ) - .map(async (taskRunner) => { - // We use taskRunner.taskExecutionId instead of taskRunner.id as key for the task pool map because - // task cancellation is a non-blocking procedure. We calculate the expiration and immediately remove - // the task from the task pool. There is a race condition that can occur when a recurring tasks's schedule - // matches its timeout value. A new instance of the task can be claimed and added to the task pool before - // the cancel function (meant for the previous instance of the task) is actually called. This means the wrong - // task instance is cancelled. We introduce the taskExecutionId to differentiate between these overlapping instances and - // ensure that the correct task instance is cancelled. - this.tasksInPool.set(taskRunner.taskExecutionId, taskRunner); - return taskRunner - .markTaskAsRunning() - .then((hasTaskBeenMarkAsRunning: boolean) => - hasTaskBeenMarkAsRunning - ? this.handleMarkAsRunning(taskRunner) - : this.handleFailureOfMarkAsRunning(taskRunner, { - name: 'TaskPoolVersionConflictError', - message: VERSION_CONFLICT_MESSAGE, - }) - ) - .catch((err) => this.handleFailureOfMarkAsRunning(taskRunner, err)); - }) - ); - } - - if (leftOverTasks.length) { - if (this.availableWorkers) { - return this.run(leftOverTasks, attempt + 1); - } - return TaskPoolRunResult.RanOutOfCapacity; - } else if (!this.availableWorkers) { - return TaskPoolRunResult.RunningAtCapacity; - } - return TaskPoolRunResult.RunningAllClaimedTasks; - } - - public cancelRunningTasks() { - this.logger.debug('Cancelling running tasks.'); - for (const task of this.tasksInPool.values()) { - this.cancelTask(task); - } - } - - private handleMarkAsRunning(taskRunner: TaskRunner) { - taskRunner - .run() - .catch((err) => { - // If a task Saved Object can't be found by an in flight task runner - // we asssume the underlying task has been deleted while it was running - // so we will log this as a debug, rather than a warn - const errorLogLine = `Task ${taskRunner.toString()} failed in attempt to run: ${ - err.message || err.error.message - }`; - if (isTaskSavedObjectNotFoundError(err, taskRunner.id)) { - this.logger.debug(errorLogLine); - } else { - this.logger.warn(errorLogLine); - } - }) - .then(() => { - this.tasksInPool.delete(taskRunner.taskExecutionId); - }) - .catch(() => {}); - } - - private handleFailureOfMarkAsRunning(task: TaskRunner, err: Error) { - this.tasksInPool.delete(task.taskExecutionId); - this.logger.error(`Failed to mark Task ${task.toString()} as running: ${err.message}`); - } - - private cancelExpiredTasks() { - for (const taskRunner of this.tasksInPool.values()) { - if (taskRunner.isExpired) { - this.logger.warn( - `Cancelling task ${taskRunner.toString()} as it expired at ${taskRunner.expiration.toISOString()}${ - taskRunner.startedAt - ? ` after running for ${durationAsString( - moment.duration(moment(new Date()).utc().diff(taskRunner.startedAt)) - )}` - : `` - }${ - taskRunner.definition.timeout - ? ` (with timeout set at ${taskRunner.definition.timeout})` - : `` - }.` - ); - this.cancelTask(taskRunner); - } - } - } - - private cancelTask(task: TaskRunner) { - // internally async (without rejections), but public-facing is synchronous - (async () => { - try { - this.logger.debug(`Cancelling task ${task.toString()}.`); - this.tasksInPool.delete(task.taskExecutionId); - await task.cancel(); - } catch (err) { - this.logger.error(`Failed to cancel task ${task.toString()}: ${err}`); - } - })().catch(() => {}); - } -} - -function partitionListByCount<T>(list: T[], count: number): [T[], T[]] { - const listInCount = list.splice(0, count); - return [listInCount, list]; -} - -function durationAsString(duration: Duration): string { - const [m, s] = [duration.minutes(), duration.seconds()].map((value) => - padStart(`${value}`, 2, '0') - ); - return `${m}m ${s}s`; -} diff --git a/x-pack/plugins/task_manager/server/task_pool/capacity.mock.ts b/x-pack/plugins/task_manager/server/task_pool/capacity.mock.ts new file mode 100644 index 0000000000000..ed3fd3b07f07c --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/capacity.mock.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. + */ +const createCapacityMock = () => { + return jest.fn().mockImplementation(() => { + return { + determineTasksToRunBasedOnCapacity: jest.fn(), + getUsedCapacityByType: jest.fn(), + usedCapacityPercentage: jest.fn(), + usedCapacity: jest.fn(), + capacity: jest.fn(), + }; + }); +}; + +export const capacityMock = { + create: createCapacityMock(), +}; diff --git a/x-pack/plugins/task_manager/server/task_pool/cost_capacity.test.ts b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.test.ts new file mode 100644 index 0000000000000..b40c6eb2af37d --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.test.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { of, Subject } from 'rxjs'; +import { TaskCost } from '../task'; +import { CostCapacity } from './cost_capacity'; +import { mockTask } from './test_utils'; + +const logger = loggingSystemMock.create().get(); + +describe('CostCapacity', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('capacity responds to changes from capacity$ observable', () => { + const capacity$ = new Subject<number>(); + const pool = new CostCapacity({ capacity$, logger }); + + expect(pool.capacity).toBe(0); + + capacity$.next(20); + expect(pool.capacity).toBe(40); + + capacity$.next(16); + expect(pool.capacity).toBe(32); + + expect(logger.debug).toHaveBeenCalledTimes(2); + expect(logger.debug).toHaveBeenNthCalledWith( + 1, + `Task pool now using 40 as the max allowed cost which is based on a capacity of 20` + ); + expect(logger.debug).toHaveBeenNthCalledWith( + 2, + `Task pool now using 32 as the max allowed cost which is based on a capacity of 16` + ); + }); + + test('usedCapacity returns the sum of costs of tasks in the pool', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacity(tasksInPool)).toBe(5); + }); + + test('usedCapacityPercentage returns the percentage of capacity used based on cost of tasks in the pool', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacityPercentage(tasksInPool)).toBe(25); + }); + + test('usedCapacityByType returns the sum of of costs of tasks of specified type in the pool', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = [ + { ...mockTask({}, { type: 'type1' }) }, + { ...mockTask({}, { type: 'type1', cost: TaskCost.Tiny }) }, + { ...mockTask({}, { type: 'type2' }) }, + ]; + + expect(pool.getUsedCapacityByType(tasksInPool, 'type1')).toBe(3); + expect(pool.getUsedCapacityByType(tasksInPool, 'type2')).toBe(2); + expect(pool.getUsedCapacityByType(tasksInPool, 'type3')).toBe(0); + }); + + test('availableCapacity returns the full available capacity when no task type is defined', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.availableCapacity(tasksInPool)).toBe(15); + }); + + test('availableCapacity returns the full available capacity when task type with no maxConcurrency is provided', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(15); + }); + + test('availableCapacity returns the available capacity for the task type when task type with maxConcurrency is provided', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask({}, { type: 'type1' }) }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + maxConcurrency: 3, + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(4); + }); + + describe('determineTasksToRunBasedOnCapacity', () => { + test('runs all tasks if there is capacity', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 20); + + expect(tasksToRun).toEqual(tasks); + expect(leftoverTasks).toEqual([]); + }); + + test('runs task in order until capacity is reached', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + const tasks = [ + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + // technically have capacity for these tasks if we skip the previous task, but we're running + // in order to avoid possibly starving large cost tasks + { ...mockTask() }, + { ...mockTask() }, + ]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 20); + + expect(tasksToRun).toEqual([tasks[0], tasks[1], tasks[2], tasks[3]]); + expect(leftoverTasks).toEqual([tasks[4], tasks[5], tasks[6]]); + }); + + test('does not run tasks if there is no capacity', () => { + const pool = new CostCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 1); + + expect(tasksToRun).toEqual([]); + expect(leftoverTasks).toEqual(tasks); + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/task_pool/cost_capacity.ts b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.ts new file mode 100644 index 0000000000000..ead7cf1839714 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/cost_capacity.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 { Logger } from '@kbn/core/server'; +import { TaskDefinition } from '../task'; +import { TaskRunner } from '../task_running'; +import { CapacityOpts, ICapacity } from './types'; +import { getCapacityInCost } from './utils'; + +export class CostCapacity implements ICapacity { + private maxAllowedCost: number = 0; + private logger: Logger; + + constructor(opts: CapacityOpts) { + this.logger = opts.logger; + opts.capacity$.subscribe((capacity) => { + // Capacity config describes the number of normal-cost tasks that can be + // run simulatenously. Multiple by the cost of a normal cost to determine + // the maximum allowed cost + this.maxAllowedCost = getCapacityInCost(capacity); + this.logger.debug( + `Task pool now using ${this.maxAllowedCost} as the max allowed cost which is based on a capacity of ${capacity}` + ); + }); + } + + public get capacity(): number { + return this.maxAllowedCost; + } + + /** + * Gets how much capacity is currently in use. + */ + public usedCapacity(tasksInPool: Map<string, TaskRunner>) { + let result = 0; + tasksInPool.forEach((task) => { + if (task.definition?.cost) { + result += task.definition.cost; + } + }); + return result; + } + + /** + * Gets % of capacity in use + */ + public usedCapacityPercentage(tasksInPool: Map<string, TaskRunner>) { + return this.capacity ? Math.round((this.usedCapacity(tasksInPool) * 100) / this.capacity) : 100; + } + + /** + * Gets how much capacity is currently in use by each type. + */ + public getUsedCapacityByType(tasksInPool: TaskRunner[], type: string) { + return tasksInPool.reduce( + (count, runningTask) => + runningTask.definition?.type === type ? count + runningTask.definition.cost : count, + 0 + ); + } + + public availableCapacity( + tasksInPool: Map<string, TaskRunner>, + taskDefinition?: TaskDefinition | null + ): number { + const allAvailableCapacity = this.capacity - this.usedCapacity(tasksInPool); + if (taskDefinition && taskDefinition.maxConcurrency) { + // calculate the max capacity that can be used for this task type based on cost + const maxCapacityForType = taskDefinition.maxConcurrency * taskDefinition.cost; + return Math.max( + Math.min( + allAvailableCapacity, + maxCapacityForType - + this.getUsedCapacityByType([...tasksInPool.values()], taskDefinition.type) + ), + 0 + ); + } + + return allAvailableCapacity; + } + + public determineTasksToRunBasedOnCapacity( + tasks: TaskRunner[], + availableCapacity: number + ): [TaskRunner[], TaskRunner[]] { + const tasksToRun: TaskRunner[] = []; + const leftOverTasks: TaskRunner[] = []; + + let capacityAccumulator = 0; + for (const task of tasks) { + const taskCost = task.definition?.cost ?? 0; + if (capacityAccumulator + taskCost <= availableCapacity) { + tasksToRun.push(task); + capacityAccumulator += taskCost; + } else { + leftOverTasks.push(task); + // Don't claim further tasks even if lower cost tasks are next. + // It may be an extra large task and we need to make room for it + // for the next claiming cycle + capacityAccumulator = availableCapacity; + } + } + + return [tasksToRun, leftOverTasks]; + } +} diff --git a/x-pack/plugins/task_manager/server/task_pool/index.ts b/x-pack/plugins/task_manager/server/task_pool/index.ts new file mode 100644 index 0000000000000..979a4536639a6 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/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 { TaskPool, TaskPoolRunResult } from './task_pool'; +export { getCapacityInCost, getCapacityInWorkers } from './utils'; diff --git a/x-pack/plugins/task_manager/server/task_pool/task_pool.mock.ts b/x-pack/plugins/task_manager/server/task_pool/task_pool.mock.ts new file mode 100644 index 0000000000000..00c3cfae16317 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/task_pool.mock.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 { TaskPool } from './task_pool'; + +const defaultGetCapacityOverride: () => Partial<{ + load: number; + usedCapacity: number; + usedCapacityPercentage: number; + availableCapacity: number; +}> = () => ({ + load: 0, + usedCapacity: 0, + usedCapacityPercentage: 0, + availableCapacity: 20, +}); + +const createTaskPoolMock = (getCapacityOverride = defaultGetCapacityOverride) => { + return { + get load() { + return getCapacityOverride().load ?? 0; + }, + get usedCapacity() { + return getCapacityOverride().usedCapacity ?? 0; + }, + get usedCapacityPercentage() { + return getCapacityOverride().usedCapacityPercentage ?? 0; + }, + availableCapacity() { + return getCapacityOverride().availableCapacity ?? 20; + }, + getUsedCapacityByType: jest.fn(), + run: jest.fn(), + cancelRunningTasks: jest.fn(), + } as unknown as jest.Mocked<TaskPool>; +}; + +export const TaskPoolMock = { + create: createTaskPoolMock, +}; diff --git a/x-pack/plugins/task_manager/server/task_pool/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool/task_pool.test.ts new file mode 100644 index 0000000000000..e2936b7ccec0a --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/task_pool.test.ts @@ -0,0 +1,867 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { of, Subject } from 'rxjs'; +import { TaskPool, TaskPoolRunResult } from './task_pool'; +import { resolvable, sleep } from '../test_utils'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { Logger } from '@kbn/core/server'; +import { asOk } from '../lib/result_type'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import moment from 'moment'; +import { v4 as uuidv4 } from 'uuid'; +import { TaskCost } from '../task'; +import * as CostCapacityModule from './cost_capacity'; +import * as WorkerCapacityModule from './worker_capacity'; +import { capacityMock } from './capacity.mock'; +import { CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET } from '../config'; +import { mockRun, mockTask } from './test_utils'; +import { TaskTypeDictionary } from '../task_type_dictionary'; + +jest.mock('../constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: ['report', 'quickReport'], +})); + +describe('TaskPool', () => { + const costCapacityMock = capacityMock.create(); + const workerCapacityMock = capacityMock.create(); + const logger = loggingSystemMock.create().get(); + + const definitions = new TaskTypeDictionary(logger); + definitions.registerTaskDefinitions({ + report: { + title: 'report', + maxConcurrency: 1, + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + quickReport: { + title: 'quickReport', + maxConcurrency: 5, + createTaskRunner: jest.fn(), + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + jest.useFakeTimers(); + jest.setSystemTime(new Date(2021, 12, 30)); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + describe('uses the correct capacity calculator based on the strategy', () => { + let costCapacitySpy: jest.SpyInstance; + let workerCapacitySpy: jest.SpyInstance; + beforeEach(() => { + costCapacitySpy = jest + .spyOn(CostCapacityModule, 'CostCapacity') + .mockImplementation(() => costCapacityMock); + + workerCapacitySpy = jest + .spyOn(WorkerCapacityModule, 'WorkerCapacity') + .mockImplementation(() => workerCapacityMock); + }); + + afterEach(() => { + costCapacitySpy.mockRestore(); + workerCapacitySpy.mockRestore(); + }); + + test('uses CostCapacity to calculate capacity when strategy is mget', () => { + new TaskPool({ capacity$: of(20), definitions, logger, strategy: CLAIM_STRATEGY_MGET }); + + expect(CostCapacityModule.CostCapacity).toHaveBeenCalledTimes(1); + expect(WorkerCapacityModule.WorkerCapacity).not.toHaveBeenCalled(); + }); + + test('uses WorkerCapacity to calculate capacity when strategy is default', () => { + new TaskPool({ capacity$: of(20), definitions, logger, strategy: CLAIM_STRATEGY_DEFAULT }); + + expect(CostCapacityModule.CostCapacity).not.toHaveBeenCalled(); + expect(WorkerCapacityModule.WorkerCapacity).toHaveBeenCalledTimes(1); + }); + + test('uses WorkerCapacity to calculate capacity when strategy is unrecognized', () => { + new TaskPool({ capacity$: of(20), definitions, logger, strategy: 'any old strategy' }); + + expect(CostCapacityModule.CostCapacity).not.toHaveBeenCalled(); + expect(WorkerCapacityModule.WorkerCapacity).toHaveBeenCalledTimes(1); + }); + }); + + describe('with CLAIM_STRATEGY_DEFAULT', () => { + test('usedCapacity is the number running tasks', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.usedCapacity).toEqual(3); + }); + + test('availableCapacity are a function of total_capacity - usedCapacity', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.availableCapacity()).toEqual(7); + }); + + test('availableCapacity is 0 until capacity$ pushes a value', async () => { + const capacity$ = new Subject<number>(); + const pool = new TaskPool({ + capacity$, + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + expect(pool.availableCapacity()).toEqual(0); + capacity$.next(10); + expect(pool.availableCapacity()).toEqual(10); + }); + + test('does not run tasks that are beyond its available capacity', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const result = await pool.run([ + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldNotRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.availableCapacity()).toEqual(0); + expect(shouldRun).toHaveBeenCalledTimes(2); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + + test('should log when marking a Task as running fails', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToMarkAsRunning = mockTask(); + taskFailedToMarkAsRunning.markTaskAsRunning.mockImplementation(async () => { + throw new Error(`Mark Task as running has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToMarkAsRunning, mockTask()]); + + expect((logger as jest.Mocked<Logger>).error.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Failed to mark Task TaskType \\"shooooo\\" as running: Mark Task as running has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should log when running a Task fails', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect((logger as jest.Mocked<Logger>).warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Task TaskType \\"shooooo\\" failed in attempt to run: Run Task has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect(logger.debug).toHaveBeenCalledWith( + `Task TaskType "shooooo" failed in attempt to run: Saved object [task/${taskFailedToRun.id}] not found` + ); + expect(logger.warn).not.toHaveBeenCalled(); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('Running a task which fails still takes up capacity', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + await sleep(0); + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([taskFailedToRun, mockTask()]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + }); + + test('clears up capacity when a task completes', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const firstWork = resolvable(); + const firstRun = sinon.spy(async () => { + await sleep(0); + firstWork.resolve(); + return asOk({ state: {} }); + }); + const secondWork = resolvable(); + const secondRun = sinon.spy(async () => { + await sleep(0); + secondWork.resolve(); + return asOk({ state: {} }); + }); + + const result = await pool.run([ + { ...mockTask(), run: firstRun }, + { ...mockTask(), run: secondRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.usedCapacity).toEqual(1); + expect(pool.availableCapacity()).toEqual(0); + + await firstWork; + sinon.assert.calledOnce(firstRun); + sinon.assert.notCalled(secondRun); + + expect(pool.usedCapacity).toEqual(0); + await pool.run([{ ...mockTask(), run: secondRun }]); + expect(pool.usedCapacity).toEqual(1); + + expect(pool.availableCapacity()).toEqual(0); + + await secondWork; + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(1); + sinon.assert.calledOnce(secondRun); + }); + + test('run cancels expired tasks prior to running new tasks', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const haltUntilWeAfterFirstRun = resolvable(); + const taskHasExpired = resolvable(); + const haltTaskSoThatItCanBeCanceled = resolvable(); + + const shouldRun = sinon.spy(() => Promise.resolve()); + const shouldNotRun = sinon.spy(() => Promise.resolve()); + const now = new Date(); + const result = await pool.run([ + { + ...mockTask({ id: '1' }), + async run() { + await haltUntilWeAfterFirstRun; + this.isExpired = true; + taskHasExpired.resolve(); + await haltTaskSoThatItCanBeCanceled; + return asOk({ state: {} }); + }, + get expiration() { + return now; + }, + get startedAt() { + // 5 and a half minutes + return moment(now).subtract(5, 'm').subtract(30, 's').toDate(); + }, + cancel: shouldRun, + }, + { + ...mockTask({ id: '2' }), + async run() { + // halt here so that we can verify that this task is counted in `occupiedWorkers` + await haltUntilWeAfterFirstRun; + return asOk({ state: {} }); + }, + cancel: shouldNotRun, + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); + expect(pool.usedCapacity).toEqual(2); + expect(pool.availableCapacity()).toEqual(0); + + // release first stage in task so that it has time to expire, but not complete + haltUntilWeAfterFirstRun.resolve(); + await taskHasExpired; + + expect(await pool.run([{ ...mockTask({ id: '3' }) }])).toBeTruthy(); + + sinon.assert.calledOnce(shouldRun); + sinon.assert.notCalled(shouldNotRun); + + expect(pool.usedCapacity).toEqual(1); + expect(pool.availableCapacity()).toEqual(1); + + haltTaskSoThatItCanBeCanceled.resolve(); + + expect(logger.warn).toHaveBeenCalledWith( + `Cancelling task TaskType "shooooo" as it expired at ${now.toISOString()} after running for 05m 30s (with timeout set at 5m).` + ); + }); + + test('calls to availableWorkers ensures we cancel expired tasks', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const taskIsRunning = resolvable(); + const taskHasExpired = resolvable(); + const cancel = sinon.spy(() => Promise.resolve()); + const now = new Date(); + expect( + await pool.run([ + { + ...mockTask(), + async run() { + await sleep(10); + this.isExpired = true; + taskIsRunning.resolve(); + await taskHasExpired; + return asOk({ state: {} }); + }, + get expiration() { + return new Date(now.getTime() + 10); + }, + get startedAt() { + return now; + }, + cancel, + }, + ]) + ).toEqual(TaskPoolRunResult.RunningAtCapacity); + + await taskIsRunning; + + sinon.assert.notCalled(cancel); + expect(pool.usedCapacity).toEqual(1); + // The call to `availableCapacity` will clear the expired task so it's 1 instead of 0 + expect(pool.availableCapacity()).toEqual(1); + sinon.assert.calledOnce(cancel); + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(1); + // ensure cancel isn't called twice + sinon.assert.calledOnce(cancel); + taskHasExpired.resolve(); + }); + + test('logs if cancellation errors', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const cancelled = resolvable(); + const result = await pool.run([ + { + ...mockTask(), + async run() { + this.isExpired = true; + await sleep(10); + return asOk({ state: {} }); + }, + async cancel() { + cancelled.resolve(); + throw new Error('Dern!'); + }, + toString: () => '"shooooo!"', + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + await pool.run([]); + + expect(pool.usedCapacity).toEqual(0); + + // Allow the task to cancel... + await cancelled; + + expect((logger as jest.Mocked<Logger>).error.mock.calls[0][0]).toMatchInlineSnapshot( + `"Failed to cancel task \\"shooooo!\\": Error: Dern!"` + ); + }); + + test('only allows one task with the same id in the task pool', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_DEFAULT, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const taskId = uuidv4(); + const task1 = mockTask({ id: taskId, run: shouldRun }); + const task2 = mockTask({ + id: taskId, + run: shouldNotRun, + isSameTask() { + return true; + }, + }); + + await pool.run([task1]); + await pool.run([task2]); + + expect(shouldRun).toHaveBeenCalledTimes(1); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + }); + + describe('with CLAIM_STRATEGY_MGET', () => { + test('usedCapacity is the sum of the cost of running tasks', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.usedCapacity).toEqual(3 * TaskCost.Normal); + }); + + test('availableCapacity are a function of total_capacity - usedCapacity', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const result = await pool.run([{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + expect(pool.availableCapacity()).toEqual(14); + }); + + test('availableCapacity is 0 until capacity$ pushes a value', async () => { + const capacity$ = new Subject<number>(); + const pool = new TaskPool({ capacity$, definitions, logger, strategy: CLAIM_STRATEGY_MGET }); + + expect(pool.availableCapacity()).toEqual(0); + capacity$.next(20); + expect(pool.availableCapacity()).toEqual(40); + }); + + test('does not run tasks that are beyond its available capacity', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const result = await pool.run([ + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldRun }, + { ...mockTask(), run: shouldNotRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.availableCapacity()).toEqual(0); + expect(shouldRun).toHaveBeenCalledTimes(2); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + + test('should log when marking a Task as running fails', async () => { + const pool = new TaskPool({ + capacity$: of(6), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToMarkAsRunning = mockTask(); + taskFailedToMarkAsRunning.markTaskAsRunning.mockImplementation(async () => { + throw new Error(`Mark Task as running has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToMarkAsRunning, mockTask()]); + + expect((logger as jest.Mocked<Logger>).error.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Failed to mark Task TaskType \\"shooooo\\" as running: Mark Task as running has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should log when running a Task fails', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect((logger as jest.Mocked<Logger>).warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Task TaskType \\"shooooo\\" failed in attempt to run: Run Task has failed miserably", + ] + `); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('should not log when running a Task fails due to the Task SO having been deleted while in flight', async () => { + const pool = new TaskPool({ + capacity$: of(3), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('task', taskFailedToRun.id); + }); + + const result = await pool.run([mockTask(), taskFailedToRun, mockTask()]); + + expect(logger.debug).toHaveBeenCalledWith( + `Task TaskType "shooooo" failed in attempt to run: Saved object [task/${taskFailedToRun.id}] not found` + ); + expect(logger.warn).not.toHaveBeenCalled(); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + }); + + test('Running a task which fails still takes up capacity', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskFailedToRun = mockTask(); + taskFailedToRun.run.mockImplementation(async () => { + await sleep(0); + throw new Error(`Run Task has failed miserably`); + }); + + const result = await pool.run([taskFailedToRun, mockTask()]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + }); + + test('clears up capacity when a task completes', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const firstWork = resolvable(); + const firstRun = sinon.spy(async () => { + await sleep(0); + firstWork.resolve(); + return asOk({ state: {} }); + }); + const secondWork = resolvable(); + const secondRun = sinon.spy(async () => { + await sleep(0); + secondWork.resolve(); + return asOk({ state: {} }); + }); + + const result = await pool.run([ + { ...mockTask(), run: firstRun }, + { ...mockTask(), run: secondRun }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RanOutOfCapacity); + expect(pool.usedCapacity).toEqual(2); + expect(pool.availableCapacity()).toEqual(0); + + await firstWork; + sinon.assert.calledOnce(firstRun); + sinon.assert.notCalled(secondRun); + + expect(pool.usedCapacity).toEqual(0); + await pool.run([{ ...mockTask(), run: secondRun }]); + expect(pool.usedCapacity).toEqual(2); + + expect(pool.availableCapacity()).toEqual(0); + + await secondWork; + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(2); + sinon.assert.calledOnce(secondRun); + }); + + test('run cancels expired tasks prior to running new tasks', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const haltUntilWeAfterFirstRun = resolvable(); + const taskHasExpired = resolvable(); + const haltTaskSoThatItCanBeCanceled = resolvable(); + + const shouldRun = sinon.spy(() => Promise.resolve()); + const shouldNotRun = sinon.spy(() => Promise.resolve()); + const now = new Date(); + const result = await pool.run([ + { + ...mockTask({ id: '1' }), + async run() { + await haltUntilWeAfterFirstRun; + this.isExpired = true; + taskHasExpired.resolve(); + await haltTaskSoThatItCanBeCanceled; + return asOk({ state: {} }); + }, + get expiration() { + return now; + }, + get startedAt() { + // 5 and a half minutes + return moment(now).subtract(5, 'm').subtract(30, 's').toDate(); + }, + cancel: shouldRun, + }, + { + ...mockTask({ id: '2' }), + async run() { + // halt here so that we can verify that this task is counted in `occupiedWorkers` + await haltUntilWeAfterFirstRun; + return asOk({ state: {} }); + }, + cancel: shouldNotRun, + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAtCapacity); + expect(pool.usedCapacity).toEqual(4); + expect(pool.availableCapacity()).toEqual(0); + + // release first stage in task so that it has time to expire, but not complete + haltUntilWeAfterFirstRun.resolve(); + await taskHasExpired; + + expect(await pool.run([{ ...mockTask({ id: '3' }) }])).toBeTruthy(); + + sinon.assert.calledOnce(shouldRun); + sinon.assert.notCalled(shouldNotRun); + + expect(pool.usedCapacity).toEqual(2); + expect(pool.availableCapacity()).toEqual(2); + + haltTaskSoThatItCanBeCanceled.resolve(); + + expect(logger.warn).toHaveBeenCalledWith( + `Cancelling task TaskType "shooooo" as it expired at ${now.toISOString()} after running for 05m 30s (with timeout set at 5m).` + ); + }); + + test('calls to availableWorkers ensures we cancel expired tasks', async () => { + const pool = new TaskPool({ + capacity$: of(1), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const taskIsRunning = resolvable(); + const taskHasExpired = resolvable(); + const cancel = sinon.spy(() => Promise.resolve()); + const now = new Date(); + expect( + await pool.run([ + { + ...mockTask(), + async run() { + await sleep(10); + this.isExpired = true; + taskIsRunning.resolve(); + await taskHasExpired; + return asOk({ state: {} }); + }, + get expiration() { + return new Date(now.getTime() + 10); + }, + get startedAt() { + return now; + }, + cancel, + }, + ]) + ).toEqual(TaskPoolRunResult.RunningAtCapacity); + + await taskIsRunning; + + sinon.assert.notCalled(cancel); + expect(pool.usedCapacity).toEqual(2); + // The call to `availableCapacity` will clear the expired task so it's 2 instead of 0 + expect(pool.availableCapacity()).toEqual(2); + sinon.assert.calledOnce(cancel); + + expect(pool.usedCapacity).toEqual(0); + expect(pool.availableCapacity()).toEqual(2); + // ensure cancel isn't called twice + sinon.assert.calledOnce(cancel); + taskHasExpired.resolve(); + }); + + test('logs if cancellation errors', async () => { + const pool = new TaskPool({ + capacity$: of(10), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const cancelled = resolvable(); + const result = await pool.run([ + { + ...mockTask(), + async run() { + this.isExpired = true; + await sleep(10); + return asOk({ state: {} }); + }, + async cancel() { + cancelled.resolve(); + throw new Error('Dern!'); + }, + toString: () => '"shooooo!"', + }, + ]); + + expect(result).toEqual(TaskPoolRunResult.RunningAllClaimedTasks); + await pool.run([]); + + expect(pool.usedCapacity).toEqual(0); + + // Allow the task to cancel... + await cancelled; + + expect((logger as jest.Mocked<Logger>).error.mock.calls[0][0]).toMatchInlineSnapshot( + `"Failed to cancel task \\"shooooo!\\": Error: Dern!"` + ); + }); + + test('only allows one task with the same id in the task pool', async () => { + const pool = new TaskPool({ + capacity$: of(2), + definitions, + logger, + strategy: CLAIM_STRATEGY_MGET, + }); + + const shouldRun = mockRun(); + const shouldNotRun = mockRun(); + + const taskId = uuidv4(); + const task1 = mockTask({ id: taskId, run: shouldRun }); + const task2 = mockTask({ + id: taskId, + run: shouldNotRun, + isSameTask() { + return true; + }, + }); + + await pool.run([task1]); + await pool.run([task2]); + + expect(shouldRun).toHaveBeenCalledTimes(1); + expect(shouldNotRun).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/task_pool/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool/task_pool.ts new file mode 100644 index 0000000000000..abd220796a0c8 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/task_pool.ts @@ -0,0 +1,257 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * This module contains the logic that ensures we don't run too many + * tasks at once in a given Kibana instance. + */ +import { Observable, Subject } from 'rxjs'; +import moment, { Duration } from 'moment'; +import { padStart } from 'lodash'; +import { Logger } from '@kbn/core/server'; +import { TaskRunner } from '../task_running'; +import { isTaskSavedObjectNotFoundError } from '../lib/is_task_not_found_error'; +import { TaskManagerStat } from '../task_events'; +import { ICapacity } from './types'; +import { CLAIM_STRATEGY_MGET } from '../config'; +import { WorkerCapacity } from './worker_capacity'; +import { CostCapacity } from './cost_capacity'; +import { TaskTypeDictionary } from '../task_type_dictionary'; + +interface TaskPoolOpts { + capacity$: Observable<number>; + definitions: TaskTypeDictionary; + logger: Logger; + strategy: string; +} + +export enum TaskPoolRunResult { + // This mean we have no Run Result becuse no tasks were Ran in this cycle + NoTaskWereRan = 'NoTaskWereRan', + // This means we're running all the tasks we claimed + RunningAllClaimedTasks = 'RunningAllClaimedTasks', + // This means we're running all the tasks we claimed and we're at capacity + RunningAtCapacity = 'RunningAtCapacity', + // This means we're prematurely out of capacity and have accidentally claimed more tasks than we had capacity for + RanOutOfCapacity = 'RanOutOfCapacity', +} + +const VERSION_CONFLICT_MESSAGE = 'Task has been claimed by another Kibana service'; + +/** + * Runs tasks in batches, taking costs into account. + */ +export class TaskPool { + private tasksInPool = new Map<string, TaskRunner>(); + private logger: Logger; + private load$ = new Subject<TaskManagerStat>(); + private definitions: TaskTypeDictionary; + private capacityCalculator: ICapacity; + + /** + * Creates an instance of TaskPool. + * + * @param {Opts} opts + * @prop {number} capacity - The total capacity available + * (e.g. capacity is 4, then 2 tasks of cost 2 can run at a time, or 4 tasks of cost 1) + * @prop {Logger} logger - The task manager logger. + */ + constructor(opts: TaskPoolOpts) { + this.logger = opts.logger; + this.definitions = opts.definitions; + + switch (opts.strategy) { + case CLAIM_STRATEGY_MGET: + this.capacityCalculator = new CostCapacity({ + capacity$: opts.capacity$, + logger: this.logger, + }); + break; + + default: + this.capacityCalculator = new WorkerCapacity({ + capacity$: opts.capacity$, + logger: this.logger, + }); + } + } + + public get load(): Observable<TaskManagerStat> { + return this.load$; + } + + /** + * Gets how much capacity is currently in use. + */ + public get usedCapacity() { + return this.capacityCalculator.usedCapacity(this.tasksInPool); + } + + /** + * Gets how much capacity is currently in use as a percentage + */ + public get usedCapacityPercentage() { + return this.capacityCalculator.usedCapacityPercentage(this.tasksInPool); + } + + /** + * Gets how much capacity is currently available. + */ + public availableCapacity(taskType?: string) { + // cancel expired task whenever a call is made to check for capacity + // this ensures that we don't end up with a queue of hung tasks causing both + // the poller and the pool from hanging due to lack of capacity + this.cancelExpiredTasks(); + + return this.capacityCalculator.availableCapacity( + this.tasksInPool, + taskType ? this.definitions.get(taskType) : null + ); + } + + /** + * Gets how much capacity is currently in use by each type. + */ + public getUsedCapacityByType(type: string) { + return this.capacityCalculator.getUsedCapacityByType([...this.tasksInPool.values()], type); + } + + /** + * Attempts to run the specified list of tasks. Returns true if it was able + * to start every task in the list, false if there was not enough capacity + * to run every task. + * + * @param {TaskRunner[]} tasks + * @returns {Promise<boolean>} + */ + public async run(tasks: TaskRunner[]): Promise<TaskPoolRunResult> { + // Note `this.availableCapacity` has side effects, so we just want + // to call it once for this bit of the code. + const availableCapacity = this.availableCapacity(); + const [tasksToRun, leftOverTasks] = this.capacityCalculator.determineTasksToRunBasedOnCapacity( + tasks, + availableCapacity + ); + + if (tasksToRun.length) { + await Promise.all( + tasksToRun + .filter( + (taskRunner) => + !Array.from(this.tasksInPool.keys()).some((executionId: string) => + taskRunner.isSameTask(executionId) + ) + ) + .map(async (taskRunner) => { + // We use taskRunner.taskExecutionId instead of taskRunner.id as key for the task pool map because + // task cancellation is a non-blocking procedure. We calculate the expiration and immediately remove + // the task from the task pool. There is a race condition that can occur when a recurring tasks's schedule + // matches its timeout value. A new instance of the task can be claimed and added to the task pool before + // the cancel function (meant for the previous instance of the task) is actually called. This means the wrong + // task instance is cancelled. We introduce the taskExecutionId to differentiate between these overlapping instances and + // ensure that the correct task instance is cancelled. + this.tasksInPool.set(taskRunner.taskExecutionId, taskRunner); + return taskRunner + .markTaskAsRunning() + .then((hasTaskBeenMarkAsRunning: boolean) => + hasTaskBeenMarkAsRunning + ? this.handleMarkAsRunning(taskRunner) + : this.handleFailureOfMarkAsRunning(taskRunner, { + name: 'TaskPoolVersionConflictError', + message: VERSION_CONFLICT_MESSAGE, + }) + ) + .catch((err) => this.handleFailureOfMarkAsRunning(taskRunner, err)); + }) + ); + } + + if (leftOverTasks.length) { + // leave any leftover tasks + // they will be available for claiming in 30 seconds + return TaskPoolRunResult.RanOutOfCapacity; + } else if (!this.availableCapacity()) { + return TaskPoolRunResult.RunningAtCapacity; + } + return TaskPoolRunResult.RunningAllClaimedTasks; + } + + public cancelRunningTasks() { + this.logger.debug('Cancelling running tasks.'); + for (const task of this.tasksInPool.values()) { + this.cancelTask(task); + } + } + + private handleMarkAsRunning(taskRunner: TaskRunner) { + taskRunner + .run() + .catch((err) => { + // If a task Saved Object can't be found by an in flight task runner + // we asssume the underlying task has been deleted while it was running + // so we will log this as a debug, rather than a warn + const errorLogLine = `Task ${taskRunner.toString()} failed in attempt to run: ${ + err.message || err.error.message + }`; + if (isTaskSavedObjectNotFoundError(err, taskRunner.id)) { + this.logger.debug(errorLogLine); + } else { + this.logger.warn(errorLogLine); + } + }) + .then(() => { + this.tasksInPool.delete(taskRunner.taskExecutionId); + }) + .catch(() => {}); + } + + private handleFailureOfMarkAsRunning(task: TaskRunner, err: Error) { + this.tasksInPool.delete(task.taskExecutionId); + this.logger.error(`Failed to mark Task ${task.toString()} as running: ${err.message}`); + } + + private cancelExpiredTasks() { + for (const taskRunner of this.tasksInPool.values()) { + if (taskRunner.isExpired) { + this.logger.warn( + `Cancelling task ${taskRunner.toString()} as it expired at ${taskRunner.expiration.toISOString()}${ + taskRunner.startedAt + ? ` after running for ${durationAsString( + moment.duration(moment(new Date()).utc().diff(taskRunner.startedAt)) + )}` + : `` + }${ + taskRunner.definition?.timeout + ? ` (with timeout set at ${taskRunner.definition.timeout})` + : `` + }.` + ); + this.cancelTask(taskRunner); + } + } + } + + private cancelTask(task: TaskRunner) { + // internally async (without rejections), but public-facing is synchronous + (async () => { + try { + this.logger.debug(`Cancelling task ${task.toString()}.`); + this.tasksInPool.delete(task.taskExecutionId); + await task.cancel(); + } catch (err) { + this.logger.error(`Failed to cancel task ${task.toString()}: ${err}`); + } + })().catch(() => {}); + } +} + +function durationAsString(duration: Duration): string { + const [m, s] = [duration.minutes(), duration.seconds()].map((value) => + padStart(`${value}`, 2, '0') + ); + return `${m}m ${s}s`; +} diff --git a/x-pack/plugins/task_manager/server/task_pool/test_utils.ts b/x-pack/plugins/task_manager/server/task_pool/test_utils.ts new file mode 100644 index 0000000000000..b518ed7b8f8f5 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/test_utils.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 { v4 as uuidv4 } from 'uuid'; +import { asOk } from '../lib/result_type'; +import { sleep } from '../test_utils'; +import { TaskRunningStage } from '../task_running'; +import { TaskCost } from '../task'; + +export function mockRun() { + return jest.fn(async () => { + await sleep(0); + return asOk({ state: {} }); + }); +} + +export function mockTask(overrides = {}, definitionOverrides = {}) { + return { + isExpired: false, + taskExecutionId: uuidv4(), + id: uuidv4(), + cancel: async () => undefined, + markTaskAsRunning: jest.fn(async () => true), + run: mockRun(), + stage: TaskRunningStage.PENDING, + toString: () => `TaskType "shooooo"`, + isAdHocTaskAndOutOfAttempts: false, + removeTask: jest.fn(), + get expiration() { + return new Date(); + }, + get startedAt() { + return new Date(); + }, + get definition() { + return { + type: '', + title: '', + timeout: '5m', + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + ...definitionOverrides, + }; + }, + isSameTask() { + return false; + }, + ...overrides, + }; +} diff --git a/x-pack/plugins/task_manager/server/task_pool/types.ts b/x-pack/plugins/task_manager/server/task_pool/types.ts new file mode 100644 index 0000000000000..759af4f6d6e70 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/types.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 { Observable } from 'rxjs'; +import { Logger } from '@kbn/core/server'; +import { TaskRunner } from '../task_running'; +import { TaskDefinition } from '../task'; + +export interface ICapacity { + get capacity(): number; + availableCapacity( + tasksInPool: Map<string, TaskRunner>, + taskDefinition?: TaskDefinition | null + ): number; + usedCapacity(tasksInPool: Map<string, TaskRunner>): number; + usedCapacityPercentage(tasksInPool: Map<string, TaskRunner>): number; + getUsedCapacityByType(tasksInPool: TaskRunner[], type: string): number; + determineTasksToRunBasedOnCapacity( + tasks: TaskRunner[], + availableCapacity: number + ): [TaskRunner[], TaskRunner[]]; +} + +export interface CapacityOpts { + capacity$: Observable<number>; + logger: Logger; +} diff --git a/x-pack/plugins/task_manager/server/task_pool/utils.ts b/x-pack/plugins/task_manager/server/task_pool/utils.ts new file mode 100644 index 0000000000000..d4c89be46e02d --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/utils.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 { TaskCost } from '../task'; + +// When configured capacity is the number of normal cost tasks that this Kibana +// can run, the total available workers equals the capacity +export const getCapacityInWorkers = (capacity: number) => capacity; + +// When configured capacity is the number of normal cost tasks that this Kibana +// can run, the total available cost equals the capacity multiplied by the cost of a normal task +export const getCapacityInCost = (capacity: number) => capacity * TaskCost.Normal; diff --git a/x-pack/plugins/task_manager/server/task_pool/worker_capacity.test.ts b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.test.ts new file mode 100644 index 0000000000000..7ed7485ccdd52 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.test.ts @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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/server/mocks'; +import { of, Subject } from 'rxjs'; +import { TaskCost } from '../task'; +import { mockTask } from './test_utils'; +import { WorkerCapacity } from './worker_capacity'; + +const logger = loggingSystemMock.create().get(); + +describe('WorkerCapacity', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('workers set based on capacity responds to changes from capacity$ observable', () => { + const capacity$ = new Subject<number>(); + const pool = new WorkerCapacity({ capacity$, logger }); + + expect(pool.capacity).toBe(0); + + capacity$.next(20); + expect(pool.capacity).toBe(20); + + capacity$.next(16); + expect(pool.capacity).toBe(16); + + capacity$.next(25); + expect(pool.capacity).toBe(25); + + expect(logger.debug).toHaveBeenCalledTimes(3); + expect(logger.debug).toHaveBeenNthCalledWith( + 1, + 'Task pool now using 20 as the max worker value which is based on a capacity of 20' + ); + expect(logger.debug).toHaveBeenNthCalledWith( + 2, + 'Task pool now using 16 as the max worker value which is based on a capacity of 16' + ); + expect(logger.debug).toHaveBeenNthCalledWith( + 3, + 'Task pool now using 25 as the max worker value which is based on a capacity of 25' + ); + }); + + test('usedCapacity returns the number of tasks in the pool', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacity(tasksInPool)).toBe(3); + }); + + test('usedCapacityPercentage returns the percentage of workers in use by tasks in the pool', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.usedCapacityPercentage(tasksInPool)).toBe(30); + }); + + test('usedCapacityByType returns the number of tasks of specified type in the pool', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = [ + { ...mockTask({}, { type: 'type1' }) }, + { ...mockTask({}, { type: 'type1', cost: TaskCost.Tiny }) }, + { ...mockTask({}, { type: 'type2' }) }, + ]; + + expect(pool.getUsedCapacityByType(tasksInPool, 'type1')).toBe(2); + expect(pool.getUsedCapacityByType(tasksInPool, 'type2')).toBe(1); + expect(pool.getUsedCapacityByType(tasksInPool, 'type3')).toBe(0); + }); + + test('availableCapacity returns the overall number of available workers when no task type is defined', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect(pool.availableCapacity(tasksInPool)).toBe(7); + }); + + test('availableCapacity returns the overall number of available workers when task type with no maxConcurrency is provided', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask() }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(7); + }); + + test('availableCapacity returns the number of available workers for the task type when task type with maxConcurrency is provided', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + + const tasksInPool = new Map([ + ['1', { ...mockTask({}, { type: 'type1' }) }], + ['2', { ...mockTask({}, { cost: TaskCost.Tiny }) }], + ['3', { ...mockTask() }], + ]); + + expect( + pool.availableCapacity(tasksInPool, { + type: 'type1', + maxConcurrency: 3, + cost: TaskCost.Normal, + createTaskRunner: jest.fn(), + timeout: '5m', + }) + ).toBe(2); + }); + + describe('determineTasksToRunBasedOnCapacity', () => { + test('runs all tasks if there are workers available', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 10); + + expect(tasksToRun).toEqual(tasks); + expect(leftoverTasks).toEqual([]); + }); + + test('splits tasks if there are more tasks than available workers', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + const tasks = [ + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask() }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + { ...mockTask({}, { cost: TaskCost.ExtraLarge }) }, + { ...mockTask() }, + { ...mockTask() }, + ]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 5); + + expect(tasksToRun).toEqual([tasks[0], tasks[1], tasks[2], tasks[3], tasks[4]]); + expect(leftoverTasks).toEqual([tasks[5], tasks[6]]); + }); + + test('does not run tasks if there is no capacity', () => { + const pool = new WorkerCapacity({ capacity$: of(10), logger }); + const tasks = [{ ...mockTask() }, { ...mockTask() }, { ...mockTask() }]; + const [tasksToRun, leftoverTasks] = pool.determineTasksToRunBasedOnCapacity(tasks, 0); + + expect(tasksToRun).toEqual([]); + expect(leftoverTasks).toEqual(tasks); + }); + }); +}); diff --git a/x-pack/plugins/task_manager/server/task_pool/worker_capacity.ts b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.ts new file mode 100644 index 0000000000000..8363c53f58ec1 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_pool/worker_capacity.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 { Logger } from '@kbn/core/server'; +import { TaskRunner } from '../task_running'; +import { CapacityOpts, ICapacity } from './types'; +import { TaskDefinition } from '../task'; +import { getCapacityInWorkers } from './utils'; + +export class WorkerCapacity implements ICapacity { + private workers: number = 0; + private logger: Logger; + + constructor(opts: CapacityOpts) { + this.logger = opts.logger; + opts.capacity$.subscribe((capacity) => { + // Capacity config describes the number of normal-cost tasks that can be + // run simulatenously. This directly corresponds to the number of workers to use. + this.workers = getCapacityInWorkers(capacity); + this.logger.debug( + `Task pool now using ${this.workers} as the max worker value which is based on a capacity of ${capacity}` + ); + }); + } + + public get capacity(): number { + return this.workers; + } + + /** + * Gets how many workers are currently in use. + */ + public usedCapacity(tasksInPool: Map<string, TaskRunner>) { + return tasksInPool.size; + } + + /** + * Gets % of workers in use + */ + public usedCapacityPercentage(tasksInPool: Map<string, TaskRunner>) { + return this.capacity ? Math.round((this.usedCapacity(tasksInPool) * 100) / this.capacity) : 100; + } + + /** + * Gets how many workers are currently in use by each type. + */ + public getUsedCapacityByType(tasksInPool: TaskRunner[], type: string) { + return tasksInPool.reduce( + (count, runningTask) => (runningTask.definition?.type === type ? ++count : count), + 0 + ); + } + + public availableCapacity( + tasksInPool: Map<string, TaskRunner>, + taskDefinition?: TaskDefinition | null + ): number { + const allAvailableCapacity = this.capacity - this.usedCapacity(tasksInPool); + if (taskDefinition && taskDefinition.maxConcurrency) { + // calculate the max workers that can be used for this task type + return Math.max( + Math.min( + allAvailableCapacity, + taskDefinition.maxConcurrency - + this.getUsedCapacityByType([...tasksInPool.values()], taskDefinition.type) + ), + 0 + ); + } + + return allAvailableCapacity; + } + + public determineTasksToRunBasedOnCapacity( + tasks: TaskRunner[], + availableCapacity: number + ): [TaskRunner[], TaskRunner[]] { + const tasksToRun: TaskRunner[] = []; + const leftOverTasks: TaskRunner[] = []; + + for (let i = 0; i < tasks.length; i++) { + if (i >= availableCapacity) { + leftOverTasks.push(tasks[i]); + } else { + tasksToRun.push(tasks[i]); + } + } + + return [tasksToRun, leftOverTasks]; + } +} 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 1b0a1b9c5b02d..365169b048893 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 @@ -163,7 +163,7 @@ export class EphemeralTaskManagerRunner implements TaskRunner { // this allows us to catch tasks that remain in Pending/Finalizing without being // cleaned up isReadyToRun(this.instance) ? this.instance.task.startedAt : this.instance.timestamp, - this.definition.timeout + this.definition?.timeout )!; } @@ -209,6 +209,12 @@ export class EphemeralTaskManagerRunner implements TaskRunner { * @returns {Promise<Result<SuccessfulRunResult, FailedRunResult>>} */ public async run(): Promise<Result<SuccessfulRunResult, FailedRunResult>> { + const definition = this.definition; + + if (!definition) { + throw new Error(`Running ephemeral task ${this} failed because it has no definition`); + } + if (!isReadyToRun(this.instance)) { throw new Error( `Running ephemeral task ${this} failed as it ${ @@ -227,7 +233,7 @@ export class EphemeralTaskManagerRunner implements TaskRunner { }); const stopTaskTimer = startTaskTimer(); try { - this.task = this.definition.createTaskRunner(modifiedContext); + this.task = definition.createTaskRunner(modifiedContext); const ctx = { type: 'task manager', name: `run ephemeral ${this.instance.task.taskType}`, 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 fb73f295cf372..6eaaf2cd2881f 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 @@ -51,6 +51,7 @@ import { SuccessfulRunResult, TaskDefinition, TaskStatus, + DEFAULT_TIMEOUT, } from '../task'; import { TaskTypeDictionary } from '../task_type_dictionary'; import { isRetryableError, isUnrecoverableError } from './errors'; @@ -67,7 +68,7 @@ export interface TaskRunner { isExpired: boolean; expiration: Date; startedAt: Date | null; - definition: TaskDefinition; + definition: TaskDefinition | undefined; cancel: CancelFunction; markTaskAsRunning: () => Promise<boolean>; run: () => Promise<Result<SuccessfulRunResult, FailedRunResult>>; @@ -242,7 +243,7 @@ export class TaskManagerRunner implements TaskRunner { /** * Gets the task defintion from the dictionary. */ - public get definition() { + public get definition(): TaskDefinition | undefined { return this.definitions.get(this.taskType); } @@ -267,12 +268,12 @@ export class TaskManagerRunner implements TaskRunner { public get timeout() { if (this.instance.task.schedule) { // recurring tasks should use timeout in task type - return this.definition.timeout; + return this.definition?.timeout ?? DEFAULT_TIMEOUT; } return this.instance.task.timeoutOverride ? this.instance.task.timeoutOverride - : this.definition.timeout; + : this.definition?.timeout ?? DEFAULT_TIMEOUT; } /** @@ -313,6 +314,11 @@ export class TaskManagerRunner implements TaskRunner { * @returns {Promise<Result<SuccessfulRunResult, FailedRunResult>>} */ public async run(): Promise<Result<SuccessfulRunResult, FailedRunResult>> { + const definition = this.definition; + if (!definition) { + throw new Error(`Running task ${this} failed because it has no definition`); + } + if (!isReadyToRun(this.instance)) { throw new Error( `Running task ${this} failed as it ${ @@ -357,7 +363,7 @@ export class TaskManagerRunner implements TaskRunner { ); try { - this.task = this.definition.createTaskRunner(modifiedContext); + this.task = definition.createTaskRunner(modifiedContext); const ctx = { type: 'task manager', @@ -806,9 +812,7 @@ export class TaskManagerRunner implements TaskRunner { } private getMaxAttempts() { - return this.definition.maxAttempts !== undefined - ? this.definition.maxAttempts - : this.defaultMaxAttempts; + return this.definition?.maxAttempts ?? this.defaultMaxAttempts; } } diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index afde0ae91ea55..9bc1a64140647 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { Client } from '@elastic/elasticsearch'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import _ from 'lodash'; +import _, { omit } from 'lodash'; import { first } from 'rxjs'; import { @@ -18,7 +18,7 @@ import { SerializedConcreteTaskInstance, } from './task'; import { elasticsearchServiceMock, savedObjectsServiceMock } from '@kbn/core/server/mocks'; -import { TaskStore, SearchOpts, AggregationOpts } from './task_store'; +import { TaskStore, SearchOpts, AggregationOpts, taskInstanceToAttributes } from './task_store'; import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { SavedObjectAttributes, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { TaskTypeDictionary } from './task_type_dictionary'; @@ -292,12 +292,16 @@ describe('TaskStore', () => { }); }); - async function testFetch(opts?: SearchOpts, hits: Array<estypes.SearchHit<unknown>> = []) { + async function testFetch( + opts?: SearchOpts, + hits: Array<estypes.SearchHit<unknown>> = [], + limitResponse: boolean = false + ) { childEsClient.search.mockResponse({ hits: { hits, total: hits.length }, } as estypes.SearchResponse); - const result = await store.fetch(opts); + const result = await store.fetch(opts, limitResponse); expect(childEsClient.search).toHaveBeenCalledTimes(1); @@ -342,6 +346,18 @@ describe('TaskStore', () => { await expect(store.fetch()).rejects.toThrowErrorMatchingInlineSnapshot(`"Failure"`); expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Failure]`); }); + + test('excludes state and params from source when excludeState is true', async () => { + const { args } = await testFetch({}, [], true); + expect(args).toMatchObject({ + index: 'tasky', + body: { + sort: [{ 'task.runAt': 'asc' }], + query: { term: { type: 'task' } }, + }, + _source_excludes: ['task.state', 'task.params'], + }); + }); }); describe('aggregate', () => { @@ -615,10 +631,11 @@ describe('TaskStore', () => { describe('bulkUpdate', () => { let store: TaskStore; + const logger = mockLogger(); beforeAll(() => { store = new TaskStore({ - logger: mockLogger(), + logger, index: 'tasky', taskManagerId: '', serializer, @@ -671,6 +688,125 @@ describe('TaskStore', () => { expect(mockGetValidatedTaskInstanceForUpdating).toHaveBeenCalledWith(task, { validate: false, }); + + expect(savedObjectsClient.bulkUpdate).toHaveBeenCalledWith( + [ + { + id: task.id, + type: 'task', + version: task.version, + attributes: taskInstanceToAttributes(task, task.id), + }, + ], + { refresh: false } + ); + }); + + test(`validates whenever validate:true is passed-in`, async () => { + const task = { + runAt: mockedDate, + scheduledAt: mockedDate, + startedAt: null, + retryAt: null, + id: 'task:324242', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + attempts: 3, + status: 'idle' as TaskStatus, + version: '123', + ownerId: null, + traceparent: '', + }; + + savedObjectsClient.bulkUpdate.mockResolvedValue({ + saved_objects: [ + { + id: '324242', + type: 'task', + attributes: { + ...task, + state: '{"foo":"bar"}', + params: '{"hello":"world"}', + }, + references: [], + version: '123', + }, + ], + }); + + await store.bulkUpdate([task], { validate: true }); + + expect(mockGetValidatedTaskInstanceForUpdating).toHaveBeenCalledWith(task, { + validate: true, + }); + + expect(savedObjectsClient.bulkUpdate).toHaveBeenCalledWith( + [ + { + id: task.id, + type: 'task', + version: task.version, + attributes: taskInstanceToAttributes(task, task.id), + }, + ], + { refresh: false } + ); + }); + + test(`logs warning and doesn't validate whenever excludeLargeFields option is passed-in`, async () => { + const task = { + runAt: mockedDate, + scheduledAt: mockedDate, + startedAt: null, + retryAt: null, + id: 'task:324242', + params: { hello: 'world' }, + state: { foo: 'bar' }, + taskType: 'report', + attempts: 3, + status: 'idle' as TaskStatus, + version: '123', + ownerId: null, + traceparent: '', + }; + + savedObjectsClient.bulkUpdate.mockResolvedValue({ + saved_objects: [ + { + id: '324242', + type: 'task', + attributes: { + ...task, + state: '{"foo":"bar"}', + params: '{"hello":"world"}', + }, + references: [], + version: '123', + }, + ], + }); + + await store.bulkUpdate([task], { validate: true, excludeLargeFields: true }); + + expect(logger.warn).toHaveBeenCalledWith( + `Skipping validation for bulk update because excludeLargeFields=true.` + ); + expect(mockGetValidatedTaskInstanceForUpdating).toHaveBeenCalledWith(task, { + validate: false, + }); + + expect(savedObjectsClient.bulkUpdate).toHaveBeenCalledWith( + [ + { + id: task.id, + type: 'task', + version: task.version, + attributes: omit(taskInstanceToAttributes(task, task.id), ['state', 'params']), + }, + ], + { refresh: false } + ); }); test('pushes error from saved objects client to errors$', async () => { diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index e0ad3dfae149a..9b58d7bc3c18b 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -84,6 +84,11 @@ export interface FetchResult { versionMap: Map<string, ConcreteTaskInstanceVersion>; } +export interface BulkUpdateOpts { + validate: boolean; + excludeLargeFields?: boolean; +} + export type BulkUpdateResult = Result< ConcreteTaskInstance, { type: string; id: string; error: SavedObjectError } @@ -108,6 +113,7 @@ export class TaskStore { public readonly taskManagerId: string; public readonly errors$ = new Subject<Error>(); public readonly taskValidator: TaskValidator; + private readonly logger: Logger; private esClient: ElasticsearchClient; private esClientWithoutRetries: ElasticsearchClient; @@ -134,6 +140,7 @@ export class TaskStore { this.serializer = opts.serializer; this.savedObjectsRepository = opts.savedObjectsRepository; this.adHocTaskCounter = opts.adHocTaskCounter; + this.logger = opts.logger; this.taskValidator = new TaskValidator({ logger: opts.logger, definitions: opts.definitions, @@ -232,15 +239,13 @@ export class TaskStore { * Fetches a list of scheduled tasks with default sorting. * * @param opts - The query options used to filter tasks + * @param limitResponse - Whether to exclude the task state and params from the source for a smaller respose payload */ - public async fetch({ - sort = [{ 'task.runAt': 'asc' }], - ...opts - }: SearchOpts = {}): Promise<FetchResult> { - return this.search({ - ...opts, - sort, - }); + public async fetch( + { sort = [{ 'task.runAt': 'asc' }], ...opts }: SearchOpts = {}, + limitResponse: boolean = false + ): Promise<FetchResult> { + return this.search({ ...opts, sort }, limitResponse); } /** @@ -296,13 +301,23 @@ export class TaskStore { */ public async bulkUpdate( docs: ConcreteTaskInstance[], - options: { validate: boolean } + { validate, excludeLargeFields = false }: BulkUpdateOpts ): Promise<BulkUpdateResult[]> { + // if we're excluding large fields (state and params), we cannot apply validation so log a warning + if (validate && excludeLargeFields) { + validate = false; + this.logger.warn(`Skipping validation for bulk update because excludeLargeFields=true.`); + } + const attributesByDocId = docs.reduce((attrsById, doc) => { const taskInstance = this.taskValidator.getValidatedTaskInstanceForUpdating(doc, { - validate: options.validate, + validate, }); - attrsById.set(doc.id, taskInstanceToAttributes(taskInstance, doc.id)); + const taskAttributes = taskInstanceToAttributes(taskInstance, doc.id); + attrsById.set( + doc.id, + excludeLargeFields ? omit(taskAttributes, 'state', 'params') : taskAttributes + ); return attrsById; }, new Map()); @@ -342,7 +357,7 @@ export class TaskStore { ), }); const result = this.taskValidator.getValidatedTaskInstanceFromReading(taskInstance, { - validate: options.validate, + validate, }); return asOk(result); }); @@ -489,18 +504,20 @@ export class TaskStore { } } - private async search(opts: SearchOpts = {}): Promise<FetchResult> { + private async search( + opts: SearchOpts = {}, + limitResponse: boolean = false + ): Promise<FetchResult> { const { query } = ensureQueryOnlyReturnsTaskObjects(opts); try { const result = await this.esClientWithoutRetries.search<SavedObjectsRawDoc['_source']>({ index: this.index, ignore_unavailable: true, - body: { - ...opts, - query, - }, + body: { ...opts, query }, + ...(limitResponse ? { _source_excludes: ['task.state', 'task.params'] } : {}), }); + const { hits: { hits: tasks }, } = result; @@ -627,7 +644,10 @@ export function correctVersionConflictsForContinuation( return maxDocs && versionConflicts + updated > maxDocs ? maxDocs - updated : versionConflicts; } -function taskInstanceToAttributes(doc: TaskInstance, id: string): SerializedConcreteTaskInstance { +export function taskInstanceToAttributes( + doc: TaskInstance, + id: string +): SerializedConcreteTaskInstance { return { ...omit(doc, 'id', 'version'), params: JSON.stringify(doc.params || {}), diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts index af2f61b188c3c..7be758be03567 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.test.ts @@ -6,7 +6,7 @@ */ import { get } from 'lodash'; -import { RunContext, TaskDefinition, TaskPriority } from './task'; +import { RunContext, TaskCost, TaskDefinition, TaskPriority } from './task'; import { mockLogger } from './test_utils'; import { sanitizeTaskDefinitions, @@ -53,6 +53,7 @@ describe('taskTypeDictionary', () => { const logger = mockLogger(); beforeEach(() => { + jest.resetAllMocks(); definitions = new TaskTypeDictionary(logger); }); @@ -64,6 +65,7 @@ describe('taskTypeDictionary', () => { expect(result).toMatchInlineSnapshot(` Array [ Object { + "cost": 2, "createTaskRunner": [Function], "description": "one super cool task", "timeout": "5m", @@ -71,6 +73,7 @@ describe('taskTypeDictionary', () => { "type": "test_task_type_0", }, Object { + "cost": 2, "createTaskRunner": [Function], "description": "one super cool task", "timeout": "5m", @@ -78,6 +81,7 @@ describe('taskTypeDictionary', () => { "type": "test_task_type_1", }, Object { + "cost": 2, "createTaskRunner": [Function], "description": "one super cool task", "timeout": "5m", @@ -225,6 +229,7 @@ describe('taskTypeDictionary', () => { createTaskRunner: expect.any(Function), maxConcurrency: 2, priority: 1, + cost: 2, timeout: '5m', title: 'foo', type: 'foo', @@ -244,11 +249,42 @@ describe('taskTypeDictionary', () => { expect(logger.error).toHaveBeenCalledWith( `Could not sanitize task definitions: Invalid priority \"23\". Priority must be one of Low => 1,Normal => 50` ); - expect(() => { - definitions.get('foo'); - }).toThrowErrorMatchingInlineSnapshot( - `"Unsupported task type \\"foo\\". Supported types are "` + expect(definitions.get('foo')).toEqual(undefined); + }); + + it('uses task cost if specified', () => { + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + maxConcurrency: 2, + cost: TaskCost.ExtraLarge, + createTaskRunner: jest.fn(), + }, + }); + expect(definitions.get('foo')).toEqual({ + createTaskRunner: expect.any(Function), + maxConcurrency: 2, + cost: 10, + timeout: '5m', + title: 'foo', + type: 'foo', + }); + }); + + it('does not register task with invalid cost schema', () => { + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + maxConcurrency: 2, + // @ts-expect-error upgrade typescript v5.1.6 + cost: 23, + createTaskRunner: jest.fn(), + }, + }); + expect(logger.error).toHaveBeenCalledWith( + `Could not sanitize task definitions: Invalid cost \"23\". Cost must be one of Tiny => 1,Normal => 2,ExtraLarge => 10` ); + expect(definitions.get('foo')).toEqual(undefined); }); it('throws error when registering duplicate task type', () => { diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.ts index f45cbad172d5a..e0b28eccea3cb 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.ts @@ -7,7 +7,13 @@ import { ObjectType } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; -import { TaskDefinition, taskDefinitionSchema, TaskRunCreatorFunction, TaskPriority } from './task'; +import { + TaskDefinition, + taskDefinitionSchema, + TaskRunCreatorFunction, + TaskPriority, + TaskCost, +} from './task'; import { CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE } from './constants'; /** @@ -50,6 +56,10 @@ export interface TaskRegisterDefinition { * claimed before low priority */ priority?: TaskPriority; + /** + * An optional definition of the cost associated with running the task. + */ + cost?: TaskCost; /** * An optional more detailed description of what this task does. */ @@ -117,9 +127,8 @@ export class TaskTypeDictionary { return this.definitions.size; } - public get(type: string): TaskDefinition { - this.ensureHas(type); - return this.definitions.get(type)!; + public get(type: string): TaskDefinition | undefined { + return this.definitions.get(type); } public ensureHas(type: string) { diff --git a/x-pack/plugins/task_manager/server/task_validator.ts b/x-pack/plugins/task_manager/server/task_validator.ts index ddc7304e4e31e..71d092663f1f3 100644 --- a/x-pack/plugins/task_manager/server/task_validator.ts +++ b/x-pack/plugins/task_manager/server/task_validator.ts @@ -57,13 +57,14 @@ export class TaskValidator { return task; } + const taskTypeDef = this.definitions.get(task.taskType); + // In the scenario the task is unused / deprecated and Kibana needs to manipulate the task, // we'll do a pass-through for those - if (!this.definitions.has(task.taskType)) { + if (!taskTypeDef) { return task; } - const taskTypeDef = this.definitions.get(task.taskType); const latestStateSchema = this.cachedGetLatestStateSchema(taskTypeDef); // TODO: Remove once all task types have defined their state schema. @@ -106,13 +107,13 @@ export class TaskValidator { return taskWithValidatedTimeout; } + const taskTypeDef = this.definitions.get(taskWithValidatedTimeout.taskType); + // In the scenario the task is unused / deprecated and Kibana needs to manipulate the task, // we'll do a pass-through for those - if (!this.definitions.has(taskWithValidatedTimeout.taskType)) { + if (!taskTypeDef) { return taskWithValidatedTimeout; } - - const taskTypeDef = this.definitions.get(taskWithValidatedTimeout.taskType); const latestStateSchema = this.cachedGetLatestStateSchema(taskTypeDef); // TODO: Remove once all task types have defined their state schema. diff --git a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts index 019d8bd47c57a..067a32c8a486d 100644 --- a/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts +++ b/x-pack/plugins/task_manager/server/usage/task_manager_usage_collector.test.ts @@ -174,7 +174,8 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { timestamp: new Date().toISOString(), status: HealthStatus.OK, value: { - max_workers: 10, + capacity: { config: 10, as_cost: 20, as_workers: 10 }, + claim_strategy: 'default', poll_interval: 3000, request_capacity: 1000, monitored_aggregated_stats_refresh_rate: 5000, @@ -193,16 +194,19 @@ function getMockMonitoredHealth(overrides = {}): MonitoredHealth { status: HealthStatus.OK, value: { count: 4, + cost: 8, task_types: { - actions_telemetry: { count: 2, status: { idle: 2 } }, - alerting_telemetry: { count: 1, status: { idle: 1 } }, - session_cleanup: { count: 1, status: { idle: 1 } }, + actions_telemetry: { count: 2, cost: 4, status: { idle: 2 } }, + alerting_telemetry: { count: 1, cost: 2, status: { idle: 1 } }, + session_cleanup: { count: 1, cost: 2, status: { idle: 1 } }, }, schedule: [], overdue: 0, + overdue_cost: 0, overdue_non_recurring: 0, estimatedScheduleDensity: [], non_recurring: 20, + non_recurring_cost: 40, owner_ids: 2, estimated_schedule_density: [], capacity_requirements: { diff --git a/x-pack/plugins/task_manager/tsconfig.json b/x-pack/plugins/task_manager/tsconfig.json index 5ae81e9097114..232ab3a36f2c8 100644 --- a/x-pack/plugins/task_manager/tsconfig.json +++ b/x-pack/plugins/task_manager/tsconfig.json @@ -25,7 +25,8 @@ "@kbn/alerting-state-types", "@kbn/core-saved-objects-api-server", "@kbn/logging", - "@kbn/core-lifecycle-server" + "@kbn/core-lifecycle-server", + "@kbn/cloud-plugin" ], "exclude": ["target/**/*"] } 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 39024e4592e11..11c1a0a7edee0 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -11279,6 +11279,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled query rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled query rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of query rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of query rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of query rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of query rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of query rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of query rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of query rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11337,6 +11399,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled threshold rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled threshold rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of threshold rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of threshold rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of threshold rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of threshold rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of threshold rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of threshold rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of threshold rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11395,6 +11519,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled eql rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled eql rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of eql rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of eql rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of eql rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of eql rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of eql rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of eql rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of eql rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11453,6 +11639,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled machine_learning rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled machine_learning rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11511,6 +11759,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled threat_match rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled threat_match rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11569,6 +11879,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled new_terms rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled new_terms rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of new_terms rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of new_terms rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of new_terms rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of new_terms rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of new_terms rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of new_terms rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of new_terms rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11627,6 +11999,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled esql rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled esql rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of esql rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of esql rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of esql rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of esql rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of esql rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of esql rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of esql rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11685,6 +12119,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled elastic rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled elastic rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of elastic rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of elastic rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of elastic rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of elastic rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of elastic rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of elastic rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of elastic rules configured do not suppress alerts with missing fields" + } + } + } } } }, @@ -11743,6 +12239,68 @@ "_meta": { "description": "Number of rules using the legacy investigation fields type introduced only in 8.10 ESS" } + }, + "alert_suppression": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of enabled custom rules configured with suppression" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of disabled custom rules configured with suppression" + } + }, + "suppressed_fields_count": { + "properties": { + "one": { + "type": "long", + "_meta": { + "description": "Number of custom rules configured with one suppression field" + } + }, + "two": { + "type": "long", + "_meta": { + "description": "Number of custom rules configured with two suppression field" + } + }, + "three": { + "type": "long", + "_meta": { + "description": "Number of custom rules configured with three suppression field" + } + } + } + }, + "suppressed_per_time_period": { + "type": "long", + "_meta": { + "description": "Number of custom rules configured with suppression per time period" + } + }, + "suppressed_per_rule_execution": { + "type": "long", + "_meta": { + "description": "Number of custom rules configured with suppression per rule execution" + } + }, + "suppresses_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of custom rules configured to suppress alerts with missing fields" + } + }, + "does_not_suppress_missing_fields": { + "type": "long", + "_meta": { + "description": "Number of custom rules configured do not suppress alerts with missing fields" + } + } + } } } } diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 0b8169996473d..9b289f481a2d5 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -40,7 +40,6 @@ export { IS_OPERATOR, EXISTS_OPERATOR } from './types'; export type { BeatFields, - BrowserField, BrowserFields, CursorType, EqlOptionsData, @@ -67,4 +66,4 @@ export type { PaginationInputPaginated, } from './search_strategy'; -export { Direction, EntityType, EMPTY_BROWSER_FIELDS, EMPTY_INDEX_FIELDS } from './search_strategy'; +export { Direction, EntityType, EMPTY_BROWSER_FIELDS } from './search_strategy'; diff --git a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts index 012001cf3e956..cdf4ddb003d5e 100644 --- a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts @@ -65,23 +65,10 @@ export interface IndexFieldsStrategyResponse extends IEsSearchResponse { runtimeMappings: MappingRuntimeFields; } -/** - * @deprecated use fields list on dataview / "indexPattern" - * about to use browserFields? Reconsider! Maybe you can accomplish - * everything you need via the `fields` property on the data view - * you are working with? Or perhaps you need a description for a - * particular field? Consider using the EcsFlat module from `@kbn/ecs` - */ -export type BrowserField = FieldSpec; - type FieldCategoryName = string; export interface FieldCategory { - fields: Record<string, Partial<BrowserField>>; -} - -export interface FieldCategory { - fields: Record<string, Partial<BrowserField>>; + fields: Record<string, Partial<FieldSpec>>; } /** @@ -94,4 +81,3 @@ export interface FieldCategory { export type BrowserFields = Record<FieldCategoryName, FieldCategory>; export const EMPTY_BROWSER_FIELDS = {}; -export const EMPTY_INDEX_FIELDS: FieldSpec[] = []; diff --git a/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts b/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts index d9ef64c0ea1e3..f89a198638e92 100644 --- a/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts +++ b/x-pack/plugins/timelines/common/types/timeline/data_provider/index.ts @@ -19,11 +19,6 @@ export const IS_ONE_OF_OPERATOR = 'includes'; /** The operator applied to a field */ export type QueryOperator = typeof IS_OPERATOR | typeof EXISTS_OPERATOR | typeof IS_ONE_OF_OPERATOR; -export enum DataProviderType { - default = 'default', - template = 'template', -} - export interface QueryMatch { field: string; displayField?: string; @@ -62,7 +57,7 @@ export interface DataProvider { /** * Returns a DataProviderType */ - type?: DataProviderType.default | DataProviderType.template; + type?: 'default' | 'template'; } export type DataProvidersAnd = Pick<DataProvider, Exclude<keyof DataProvider, 'and'>>; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a528782abc0ec..1396ba78da37a 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -104,7 +104,7 @@ "alertsUIShared.components.alertLifecycleStatusBadge.flappingLabel": "Bagotement", "alertsUIShared.components.alertLifecycleStatusBadge.recoveredLabel": "Récupéré", "alertsUIShared.components.alertLifecycleStatusBadge.untrackedLabel": "Non suivi", - "alertsUIShared.hooks.useAlertDataView.useAlertDataMessage": "Impossible de charger la vue des données de l'alerte", + "alertsUIShared.hooks.useAlertDataView.fetchErrorMessage": "Impossible de charger la vue des données de l'alerte", "alertsUIShared.hooks.useLoadRuleTypesQuery.unableToLoadRuleTypesMessage": "Impossible de charger les types de règles", "alertsUIShared.hooks.useRuleAADFields.errorMessage": "Impossible de charger les champs d'alerte par type de règle", "alertsUIShared.maintenanceWindowCallout.fetchError": "La vérification visant à déterminer si les fenêtres de maintenance sont actives a échoué", @@ -834,7 +834,6 @@ "core.euiProgress.valueText": "{value} %", "core.euiQuickSelect.applyButton": "Appliquer", "core.euiQuickSelect.fullDescription": "Actuellement défini sur {timeTense} {timeValue} {timeUnit}.", - "core.euiQuickSelect.legendText": "Sélection rapide d’une plage temporelle", "core.euiQuickSelect.nextLabel": "Fenêtre temporelle suivante", "core.euiQuickSelect.previousLabel": "Fenêtre temporelle précédente", "core.euiQuickSelect.quickSelectTitle": "Sélection rapide", @@ -846,7 +845,6 @@ "core.euiRecentlyUsed.legend": "Plages de dates récemment utilisées", "core.euiRefreshInterval.fullDescriptionOff": "L'actualisation est désactivée, intervalle défini sur {optionValue} {optionText}.", "core.euiRefreshInterval.fullDescriptionOn": "L'actualisation est activée, intervalle défini sur {optionValue} {optionText}.", - "core.euiRefreshInterval.legend": "Actualiser toutes les", "core.euiRelativeTab.dateInputError": "Doit être une plage valide", "core.euiRelativeTab.fullDescription": "L'unité peut être modifiée. Elle est actuellement définie sur {unit}.", "core.euiRelativeTab.numberInputError": "Doit être >= 0.", @@ -2407,6 +2405,11 @@ "discover.embeddable.search.displayName": "rechercher", "discover.errorCalloutShowErrorMessage": "Afficher les détails", "discover.esqlMode.selectedColumnsCallout": "Affichage de {selectedColumnsNumber} champs sur {esqlQueryColumnsNumber}. Ajoutez-en d’autres depuis la liste des champs disponibles.", + "discover.esqlToDataViewTransitionModal.closeButtonLabel": "Basculer sans sauvegarder", + "discover.esqlToDataViewTransitionModal.dismissButtonLabel": "Ne plus afficher cet avertissement", + "discover.esqlToDataViewTransitionModal.saveButtonLabel": "Sauvegarder et basculer", + "discover.esqlToDataViewTransitionModal.title": "Votre requête sera supprimée", + "discover.esqlToDataviewTransitionModalBody": "Modifier la vue de données supprime la requête ES|QL en cours. Sauvegardez cette recherche pour ne pas perdre de travail.", "discover.fieldChooser.availableFieldsTooltip": "Champs disponibles pour l'affichage dans le tableau.", "discover.fieldChooser.discoverField.addFieldTooltip": "Ajouter le champ en tant que colonne", "discover.fieldChooser.discoverField.removeFieldTooltip": "Supprimer le champ du tableau", @@ -5161,10 +5164,7 @@ "kbn-esql-validation-autocomplete.esql.validation.expectedConstantValue": "L'argument de [{fn}] doit être une constante, reçu [{given}]", "kbn-esql-validation-autocomplete.esql.validation.metadataBracketsDeprecation": "Les crochets \"[]\" doivent être supprimés de la déclaration FROM METADATA", "kbn-esql-validation-autocomplete.esql.validation.missingFunction": "Fonction inconnue [{name}]", - "kbn-esql-validation-autocomplete.esql.validation.noCombinationOfAggAndNonAggValues": "Impossible de combiner les valeurs agrégées et non agrégées dans [STATS], [{expression}] trouvé", "kbn-esql-validation-autocomplete.esql.validation.noNestedArgumentSupport": "Les paramètres de la fonction agrégée doivent être un attribut, un littéral ou une fonction non agrégée ; trouvé [{name}] de type [{argType}]", - "kbn-esql-validation-autocomplete.esql.validation.statsNoAggFunction": "Au moins une fonction d'agrégation requise dans [STATS], [{expression}] trouvé", - "kbn-esql-validation-autocomplete.esql.validation.statsNoArguments": "[STATS] doit contenir au moins une expression d'agrégation ou de regroupement", "kbn-esql-validation-autocomplete.esql.validation.typeOverwrite": "La colonne [{field}] de type {fieldType} a été écrasée par un nouveau type : {newType}", "kbn-esql-validation-autocomplete.esql.validation.unknowAggregateFunction": "Attendait une fonction ou un groupe agrégé mais a obtenu [{value}] de type [{type}]", "kbn-esql-validation-autocomplete.esql.validation.unknownColumn": "Colonne inconnue[{name}]", @@ -5833,8 +5833,6 @@ "searchApiPanels.welcomeBanner.selectClient.callout.description": "Avec la console, vous pouvez directement commencer à utiliser nos API REST. Aucune installation n’est requise.", "searchApiPanels.welcomeBanner.selectClient.callout.link": "Essayez la console maintenant", "searchApiPanels.welcomeBanner.selectClient.callout.title": "Lancez-vous dans la console", - "searchApiPanels.welcomeBanner.selectClient.description": "Elastic construit et assure la maintenance des clients dans plusieurs langues populaires et notre communauté a contribué à beaucoup d'autres. Sélectionnez votre client de langage favori ou explorez la {console} pour commencer.", - "searchApiPanels.welcomeBanner.selectClient.description.console.link": "Console", "searchApiPanels.welcomeBanner.selectClient.elasticsearchClientDocLink": "Clients d'Elasticsearch ", "searchApiPanels.welcomeBanner.selectClient.heading": "Choisissez-en un", "searchApiPanels.welcomeBanner.selectClient.title": "Sélectionner votre client", @@ -6637,14 +6635,11 @@ "textBasedEditor.query.textBasedLanguagesEditor.errorCount": "{count} {count, plural, one {erreur} other {erreurs}}", "textBasedEditor.query.textBasedLanguagesEditor.errorsTitle": "Erreurs", "textBasedEditor.query.textBasedLanguagesEditor.esql": "ES|QL", - "textBasedEditor.query.textBasedLanguagesEditor.expandTooltip": "Développer l’éditeur de requête", "textBasedEditor.query.textBasedLanguagesEditor.feedback": "Commentaires", "textBasedEditor.query.textBasedLanguagesEditor.functions": "Fonctions", "textBasedEditor.query.textBasedLanguagesEditor.functionsDocumentationESQLDescription": "Les fonctions sont compatibles avec \"ROW\" (Ligne), \"EVAL\" (Évaluation) et \"WHERE\" (Où).", "textBasedEditor.query.textBasedLanguagesEditor.lineCount": "{count} {count, plural, one {ligne} other {lignes}}", "textBasedEditor.query.textBasedLanguagesEditor.lineNumber": "Ligne {lineNumber}", - "textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor": "Réduire l'éditeur", - "textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip": "Réduire l’éditeur de requête", "textBasedEditor.query.textBasedLanguagesEditor.operators": "Opérateurs", "textBasedEditor.query.textBasedLanguagesEditor.operatorsDocumentationESQLDescription": "ES|QL est compatible avec les opérateurs suivants :", "textBasedEditor.query.textBasedLanguagesEditor.processingCommands": "Traitement des commandes", @@ -7226,12 +7221,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": "Modifier la vue de données supprime la requête {textBasedLanguage} en cours. Sauvegardez cette recherche pour ne pas perdre de travail.", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "Modifier la vue de données supprime la requête {language} 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", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle": "Votre requête sera supprimée", "unifiedSearch.query.queryBar.kqlLanguageName": "KQL", "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "documents", "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "Ne plus afficher", @@ -7242,7 +7231,6 @@ "unifiedSearch.query.queryBar.searchInputPlaceholder": "Filtrer vos données à l'aide de la syntaxe {language}", "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "Filtrer vos données", "unifiedSearch.query.queryBar.syntaxOptionsTitle": "Options de syntaxe", - "unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "Essayer ES|QL", "unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "La sélection de plage de données pour les requêtes en {language} requiert la présence d'un champ @timestamp dans l'ensemble de données.", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "Tout le temps", "unifiedSearch.queryBarTopRow.submitButton.cancel": "Annuler", @@ -13219,7 +13207,6 @@ "xpack.csp.emptyState.resetFiltersButton": "Réinitialiser les filtres", "xpack.csp.emptyState.title": "Aucun résultat ne correspond à vos critères de recherche.", "xpack.csp.enableBenchmarkRuleButton": "Activer la règle", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "Affichage de {pageStart}-{pageEnd} sur {total} {type}", "xpack.csp.findings.distributionBar.totalFailedLabel": "Échec des résultats", "xpack.csp.findings.distributionBar.totalPassedLabel": "Réussite des résultats", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "Une erreur s’est produite lors de la récupération des résultats de recherche.", @@ -13239,7 +13226,6 @@ "xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle": "Environnement", "xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle": "Résolution", "xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle": "Nom de règle", - "xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTooltip": "Gérer la règle", "xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle": "Balises de règle", "xpack.csp.findings.findingsFlyout.overviewTabTitle": "Aperçu", "xpack.csp.findings.findingsFlyout.paginationLabel": "Navigation de recherche", @@ -13251,7 +13237,6 @@ "xpack.csp.findings.findingsFlyout.ruleTab.disabledRuleText": "Désactivé", "xpack.csp.findings.findingsFlyout.ruleTab.frameworkSourcesTitle": "Sources du framework", "xpack.csp.findings.findingsFlyout.ruleTab.nameTitle": "Nom", - "xpack.csp.findings.findingsFlyout.ruleTab.nameTooltip": "Gérer la règle", "xpack.csp.findings.findingsFlyout.ruleTab.profileApplicabilityTitle": "Applicabilité du profil", "xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle": "Références", "xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle": "Balises", @@ -13260,9 +13245,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "Chemin vers le fichier JSON qui contient les informations d'identification et la clé utilisés pour souscrire", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "Blob JSON qui contient les informations d'identification et la clé utilisées pour souscrire", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "Informations d'identification", - "xpack.csp.findings.groupBySelector.groupByLabel": "Regrouper par", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "Aucun", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "Ressource", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "Aucun compte cloud", "xpack.csp.findings.grouping.default.nullGroupTitle": "Aucun regroupement", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "Aucun cluster Kubernetes", @@ -13452,9 +13434,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "vulnérabilités", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "Aucune ressource", "xpack.csp.vulnerabilities.grouping.severity": "Sévérité", - "xpack.csp.vulnerabilities.searchBar.placeholder": "Rechercher des vulnérabilités (par exemple vulnerability.severity : \"CRITICAL\" )", - "xpack.csp.vulnerabilities.table.filterIn": "Inclure", - "xpack.csp.vulnerabilities.table.filterOut": "Exclure", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, =1 {vulnérabilité} other {vulnérabilités}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "Pack", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "ID ressource", @@ -13479,8 +13458,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "Comptes", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "Tendance par degré de gravité", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "Tout afficher", - "xpack.csp.vulnerabilityTable.column.sortAscending": "Basse -> Critique", - "xpack.csp.vulnerabilityTable.column.sortDescending": "Critique -> Basse", "xpack.csp.vulnerabilityTable.panel.buttonText": "Afficher toutes les vulnérabilités", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", @@ -22351,7 +22328,6 @@ "xpack.infra.waffle.maxGroupByTooltip": "Seuls deux regroupements peuvent être sélectionnés en même temps", "xpack.infra.waffle.metriclabel": "Indicateur", "xpack.infra.waffle.metricOptions.countText": "compte", - "xpack.infra.waffle.metricOptions.cpuUsageText": "Utilisation CPU", "xpack.infra.waffle.metricOptions.diskIOReadBytes": "lectures du disque", "xpack.infra.waffle.metricOptions.diskIOWriteBytes": "écritures sur le disque", "xpack.infra.waffle.metricOptions.hostLogRateText": "taux de log", @@ -24337,13 +24313,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "Fichier chargé depuis le système de fichiers de {fileName}", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "Traces d'appel disponibles", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "Traces d'appel indisponibles", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "Développer les détails du log", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "Les champs fournissant des informations exploitables, comme :", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "L'accès aux traces d'appel disponibles est basé sur :", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "Affiche le {logLevel} du document et les champs {message}.", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "Lorsque le champ de message est vide, l'une des informations suivantes s'affiche :", - "xpack.logsExplorer.dataTable.header.popover.actions": "Actions", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "actions", "xpack.logsExplorer.dataTable.header.popover.content": "Contenu", "xpack.logsExplorer.dataTable.header.popover.resource": "Ressource", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "Les champs fournissant des informations sur la source du document, comme :", @@ -28315,7 +28286,6 @@ "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "Le modèle a des pipelines associés", "xpack.ml.trainedModels.modelsList.downloadFailed": "Échec du téléchargement de \"{modelId}\"", "xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage": "Échec de la vérification du statut du téléchargement", - "xpack.ml.trainedModels.modelsList.downloadSuccess": "Le téléchargement du modèle \"{modelId}\" a bien été démarré.", "xpack.ml.trainedModels.modelsList.e5Title": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1Description": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1x86Description": "E5 (EmbEddings from bidirEctional Encoder rEpresentations), optimisé for linux-x86_64", @@ -28373,7 +28343,6 @@ "xpack.ml.trainedModels.modelsList.pipelines.processorStats.timePerDocHeader": "Temps par document", "xpack.ml.trainedModels.modelsList.pipelines.processorStats.typeHeader": "Type de processeur", "xpack.ml.trainedModels.modelsList.recommendedDownloadContent": "Version du modèle recommandée pour la configuration matérielle de votre cluster", - "xpack.ml.trainedModels.modelsList.recommendedDownloadLabel": "(Recommandée)", "xpack.ml.trainedModels.modelsList.selectableMessage": "Sélectionner un modèle", "xpack.ml.trainedModels.modelsList.selectedModelsMessage": "{modelsCount, plural, one{# modèle sélectionné} other {# modèles sélectionnés}}", "xpack.ml.trainedModels.modelsList.startDeployment.cancelButton": "Annuler", @@ -32897,14 +32866,9 @@ "xpack.securitySolution.alertCountByRuleByStatus.ruleName": "kibana.alert.rule.name", "xpack.securitySolution.alertCountByRuleByStatus.status": "Statut", "xpack.securitySolution.alertCountByRuleByStatus.tooltipTitle": "Nom de règle", - "xpack.securitySolution.alertDetails.enrichmentQueryEndDate": "Date de fin", - "xpack.securitySolution.alertDetails.enrichmentQueryStartDate": "Date de début", - "xpack.securitySolution.alertDetails.investigationTimeQueryTitle": "Enrichissement avec la Threat Intelligence", - "xpack.securitySolution.alertDetails.noEnrichmentsFoundDescription": "Cette alerte ne s'accompagne pas de Threat Intelligence.", "xpack.securitySolution.alertDetails.overview.hostRiskDataTitle": "Données de risque de {riskEntity}", "xpack.securitySolution.alertDetails.overview.insights.suppressedAlertsCountTechnicalPreview": "Version d'évaluation technique", "xpack.securitySolution.alertDetails.overview.investigationGuide": "Guide d'investigation", - "xpack.securitySolution.alertDetails.refresh": "Actualiser", "xpack.securitySolution.alertDetails.summary.readLess": "Lire moins", "xpack.securitySolution.alertDetails.summary.readMore": "En savoir plus", "xpack.securitySolution.alerts.badge.readOnly.tooltip": "Impossible de mettre à jour les alertes", @@ -33353,8 +33317,6 @@ "xpack.securitySolution.components.chartSelect.chartsOption": "Graphiques", "xpack.securitySolution.components.chartSelect.chartsOptionTitle": "Résumé", "xpack.securitySolution.components.chartSelect.legendTitle": "Sélectionner un onglet", - "xpack.securitySolution.components.chartSelect.selectAChartAriaLabel": "Sélectionner un graphique", - "xpack.securitySolution.components.chartSelect.tableOption": "Tableau", "xpack.securitySolution.components.chartSelect.tableOptionTitle": "Comptes", "xpack.securitySolution.components.chartSelect.treemapOption": "Compartimentage", "xpack.securitySolution.components.chartSelect.trendOption": "Tendance", @@ -33644,12 +33606,6 @@ "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.sourceLabel": "source", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.userNameLabel": "utilisateur", "xpack.securitySolution.detectionEngine.alerts.alertsByType.alertRuleChartTitle": "Alertes par nom", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.alertTypeChartTitle": "Alertes par type", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.detection": "Détection", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.detections": "Détections", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.prevention": "Prévention", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.preventions": "Préventions", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.typeColumn": "Type", "xpack.securitySolution.detectionEngine.alerts.chartsTitle": "Graphiques", "xpack.securitySolution.detectionEngine.alerts.closedAlertFailedToastMessage": "Impossible de fermer l'alerte ou les alertes.", "xpack.securitySolution.detectionEngine.alerts.closedAlertsTitle": "Fermé", @@ -33686,7 +33642,6 @@ "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showBuildingBlockTitle": "Inclure les alertes fondamentales", "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showOnlyThreatIndicatorAlerts": "Afficher uniquement les alertes d'indicateur de menaces", "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersTitle": "Filtres supplémentaires", - "xpack.securitySolution.detectionEngine.alerts.utilityBar.takeActionTitle": "Entreprendre une action", "xpack.securitySolution.detectionEngine.alertTitle": "Alertes", "xpack.securitySolution.detectionEngine.body.forEachAlert.message": "La règle {ruleName} a généré l'alerte {alertId}", "xpack.securitySolution.detectionEngine.body.summary.message": "La règle {ruleName} a généré {signalsCount} alertes", @@ -35936,14 +35891,7 @@ "xpack.securitySolution.event.summary.threat_indicator.modal.close": "Fermer", "xpack.securitySolution.event.summary.threat_indicator.showMatches": "Afficher les {count} alertes de correspondance d'indicateur", "xpack.securitySolution.eventDetails.alertReason": "Raison d'alerte", - "xpack.securitySolution.eventDetails.ctiSummary.feedNamePreposition": "de", - "xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTitle": "Correspondance de menace détectée", - "xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipContent": "Affiche les correspondances d'indicateur de menace détectées.", - "xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTitle": "Enrichi avec la Threat Intelligence", - "xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipContent": "Affiche des renseignements supplémentaires sur les menaces (Threat Intelligence) concernant l'alerte. La recherche a porté sur les 30 derniers jours par défaut.", "xpack.securitySolution.eventDetails.description": "Description", - "xpack.securitySolution.eventDetails.field": "Champ", - "xpack.securitySolution.eventDetails.filter.placeholder": "Filtre par Champ, Valeur ou Description...", "xpack.securitySolution.eventDetails.multiFieldBadge": "champ multiple", "xpack.securitySolution.eventDetails.multiFieldTooltipContent": "Les champs multiples peuvent avoir plusieurs valeurs.", "xpack.securitySolution.eventDetails.osqueryView": "Résultats Osquery", @@ -35955,7 +35903,6 @@ "xpack.securitySolution.eventDetails.summaryView": "résumé", "xpack.securitySolution.eventDetails.table": "Tableau", "xpack.securitySolution.eventDetails.table.actions": "Actions", - "xpack.securitySolution.eventDetails.value": "Valeur", "xpack.securitySolution.eventFilter.flyoutForm.creationSuccessToastTitle": "\"{name}\" a été ajouté à la liste de filtres d'événements.", "xpack.securitySolution.eventFilter.form.description.placeholder": "Description", "xpack.securitySolution.eventFilter.form.name.error": "Le nom doit être indiqué", @@ -36200,18 +36147,6 @@ "xpack.securitySolution.fieldBrowser.removeButtonDescription": "Supprimer un champ de temps d'exécution", "xpack.securitySolution.fieldBrowser.runtimeLabel": "Temps d'exécution", "xpack.securitySolution.fieldBrowser.runtimeTitle": "Champ de temps d'exécution", - "xpack.securitySolution.fieldNameIcons.booleanAriaLabel": "Champ booléen", - "xpack.securitySolution.fieldNameIcons.conflictFieldAriaLabel": "Champ conflictuel", - "xpack.securitySolution.fieldNameIcons.dateFieldAriaLabel": "Champ de date", - "xpack.securitySolution.fieldNameIcons.geoPointFieldAriaLabel": "Champ de point géographique", - "xpack.securitySolution.fieldNameIcons.geoShapeFieldAriaLabel": "Champ de forme géométrique", - "xpack.securitySolution.fieldNameIcons.ipAddressFieldAriaLabel": "Champ d'adresse IP", - "xpack.securitySolution.fieldNameIcons.murmur3FieldAriaLabel": "Champ Murmur3", - "xpack.securitySolution.fieldNameIcons.nestedFieldAriaLabel": "Champ imbriqué", - "xpack.securitySolution.fieldNameIcons.numberFieldAriaLabel": "Champ numérique", - "xpack.securitySolution.fieldNameIcons.sourceFieldAriaLabel": "Champ source", - "xpack.securitySolution.fieldNameIcons.stringFieldAriaLabel": "Champ de chaîne", - "xpack.securitySolution.fieldNameIcons.unknownFieldAriaLabel": "Champ inconnu", "xpack.securitySolution.fieldRenderers.moreLabel": "Plus", "xpack.securitySolution.filtersGroup.assignees.buttonTitle": "Utilisateurs affectés", "xpack.securitySolution.filtersGroup.assignees.popoverTooltip": "Filtrer par utilisateurs assignés", @@ -36693,7 +36628,6 @@ "xpack.securitySolution.inspect.modal.somethingWentWrongDescription": "Désolé, un problème est survenu.", "xpack.securitySolution.inspectDescription": "Inspecter", "xpack.securitySolution.inspectPatternDifferent": "Cet élément possède un modèle d'indexation unique séparé du paramètre de vue de données.", - "xpack.securitySolution.investigationEnrichment.requestError": "Une erreur est survenue lors de la demande de Threat Intelligence", "xpack.securitySolution.ja3.fingerprint.ja3.fingerprintLabel": "ja3", "xpack.securitySolution.kpiHosts.hosts.title": "Hôtes", "xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel": "Dest.", @@ -37553,9 +37487,6 @@ "xpack.securitySolution.ruleExceptions.allExceptionItems.noSearchResultsPromptTitle": "Aucun résultat ne correspond à vos critères de recherche.", "xpack.securitySolution.ruleExceptions.allExceptionItems.paginationAriaLabel": "Pagination du tableau d'éléments d'exception", "xpack.securitySolution.ruleExceptions.allExceptionItems.searchPlaceholder": "Exceptions de filtre utilisant une syntaxe de requête simple, par exemple, le nom :\"ma liste\"", - "xpack.securitySolution.ruleExceptions.editException.cancel": "Annuler", - "xpack.securitySolution.ruleExceptions.editException.editEndpointExceptionTitle": "Modifier une exception de point de terminaison", - "xpack.securitySolution.ruleExceptions.editException.editExceptionTitle": "Modifier une exception à une règle", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastErrorTitle": "Erreur lors de la mise à jour de l'exception", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastSuccessText": "{numItems, plural, =1 {L'exception} other {Les exceptions}} - {exceptionItemName} - {numItems, plural, =1 {a été mise à jour} other {ont été mises à jour}}.", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastSuccessTitle": "Exception à la règle mise à jour", @@ -39706,8 +39637,6 @@ "xpack.stackAlerts.esQuery.ui.selectQueryFormType.cancelSelectionAriaLabel": "Annuler la sélection", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeDescription": "Utiliser ES|QL pour définir une requête de type texte.", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeLabel": "ES|QL", - "xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalDescription": "Cette fonctionnalité est en version d'évaluation technique et pourra être modifiée ou retirée complètement dans une future version. Elastic s'efforcera de corriger tout problème, mais les fonctionnalités des versions d'évaluation technique ne sont pas soumises aux SLA de support des fonctionnalités officielles en disponibilité générale.", - "xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalLabel": "Version d'évaluation technique", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeDescription": "Utilisez KQL ou Lucene pour définir une requête de type texte.", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeLabel": "KQL ou Lucene", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.queryDslFormTypeDescription": "Utilisez Elasticsearch Query DSL pour définir une requête.", @@ -42991,7 +42920,6 @@ "xpack.triggersActionsUI.updateApiKeyConfirmModal.failureMessage": "Impossible de mettre à jour {idsToUpdate, plural, one {la clé} other {les clés}} de l'API", "xpack.triggersActionsUI.updateApiKeyConfirmModal.title": "Mettre à jour la clé d'API", "xpack.triggersActionsUI.urlSyncedAlertsSearchBar.invalidQueryTitle": "Chaîne de requête non valide", - "xpack.triggersActionsUI.useAlertDataView.useAlertDataMessage": "Impossible de charger la vue des données de l'alerte", "xpack.triggersActionsUI.useRuleAADFields.errorMessage": "Impossible de charger les champs d'alerte par type de règle", "xpack.upgradeAssistant.app.deniedPrivilegeDescription": "Afin d'utiliser l'assistant de mise à niveau et de résoudre les problèmes de déclassement, vous devez disposer d'un accès permettant de gérer tous les espaces Kibana.", "xpack.upgradeAssistant.app.deniedPrivilegeTitle": "Rôle d'administrateur Kibana requis", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6a3ba57b57fae..de9f53757bd7e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -104,7 +104,7 @@ "alertsUIShared.components.alertLifecycleStatusBadge.flappingLabel": "フラップ中", "alertsUIShared.components.alertLifecycleStatusBadge.recoveredLabel": "回復済み", "alertsUIShared.components.alertLifecycleStatusBadge.untrackedLabel": "未追跡", - "alertsUIShared.hooks.useAlertDataView.useAlertDataMessage": "アラートデータビューを読み込めません", + "alertsUIShared.hooks.useAlertDataView.fetchErrorMessage": "アラートデータビューを読み込めません", "alertsUIShared.hooks.useLoadRuleTypesQuery.unableToLoadRuleTypesMessage": "ルールタイプを読み込めません", "alertsUIShared.hooks.useRuleAADFields.errorMessage": "ルールタイプごとにアラートフィールドを読み込めません", "alertsUIShared.maintenanceWindowCallout.fetchError": "保守時間枠がアクティブであるかどうかを確認できませんでした", @@ -835,7 +835,6 @@ "core.euiProgress.valueText": "{value}%", "core.euiQuickSelect.applyButton": "適用", "core.euiQuickSelect.fullDescription": "現在 {timeTense} {timeValue} {timeUnit}に設定されています。", - "core.euiQuickSelect.legendText": "時間範囲をすばやく選択", "core.euiQuickSelect.nextLabel": "次の時間ウィンドウ", "core.euiQuickSelect.previousLabel": "前の時間ウィンドウ", "core.euiQuickSelect.quickSelectTitle": "すばやく選択", @@ -847,7 +846,6 @@ "core.euiRecentlyUsed.legend": "最近使用した日付範囲", "core.euiRefreshInterval.fullDescriptionOff": "更新はオフです。間隔は{optionValue} {optionText}に設定されています。", "core.euiRefreshInterval.fullDescriptionOn": "更新はオンです。間隔は{optionValue} {optionText}に設定されています。", - "core.euiRefreshInterval.legend": "以下の感覚ごとに更新", "core.euiRelativeTab.dateInputError": "有効な範囲でなければなりません", "core.euiRelativeTab.fullDescription": "単位は変更可能です。現在 {unit} に設定されています。", "core.euiRelativeTab.numberInputError": "0以上でなければなりません", @@ -2404,6 +2402,11 @@ "discover.embeddable.search.displayName": "検索", "discover.errorCalloutShowErrorMessage": "詳細を表示", "discover.esqlMode.selectedColumnsCallout": "{esqlQueryColumnsNumber}フィールド中{selectedColumnsNumber}フィールドを表示中です。利用可能なフィールドリストからさらに追加します。", + "discover.esqlToDataViewTransitionModal.closeButtonLabel": "保存せずに切り替え", + "discover.esqlToDataViewTransitionModal.dismissButtonLabel": "次回以降この警告を表示しない", + "discover.esqlToDataViewTransitionModal.saveButtonLabel": "保存して切り替え", + "discover.esqlToDataViewTransitionModal.title": "クエリは削除されます", + "discover.esqlToDataviewTransitionModalBody": "データビューを切り替えると、現在のES|QLクエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。", "discover.fieldChooser.availableFieldsTooltip": "フィールドをテーブルに表示できます。", "discover.fieldChooser.discoverField.addFieldTooltip": "フィールドを列として追加", "discover.fieldChooser.discoverField.removeFieldTooltip": "フィールドを表から削除", @@ -5142,10 +5145,7 @@ "kbn-esql-validation-autocomplete.esql.validation.expectedConstantValue": "[{fn}]の引数は定数でなければなりません。[{given}]が渡されました", "kbn-esql-validation-autocomplete.esql.validation.metadataBracketsDeprecation": "FROM METADATA宣言から角括弧[]を削除する必要があります", "kbn-esql-validation-autocomplete.esql.validation.missingFunction": "不明な関数[{name}]", - "kbn-esql-validation-autocomplete.esql.validation.noCombinationOfAggAndNonAggValues": "[STATS]では集計値と非集計値を結合できません。[{expression}]が見つかりました", "kbn-esql-validation-autocomplete.esql.validation.noNestedArgumentSupport": "集計関数のパラメーターは属性、リテラル、または非集計関数でなければなりません。タイプ[{argType}]の[{name}]が見つかりました", - "kbn-esql-validation-autocomplete.esql.validation.statsNoAggFunction": "[STATS]では1つ以上の集計関数が必要です。[{expression}]が見つかりました", - "kbn-esql-validation-autocomplete.esql.validation.statsNoArguments": "[STATS]では1つ以上の集計またはグループ式が必要です", "kbn-esql-validation-autocomplete.esql.validation.typeOverwrite": "{fieldType}型の列[{field}]が新しい型の{newType}として上書きされました", "kbn-esql-validation-autocomplete.esql.validation.unknowAggregateFunction": "集計関数またはグループが想定されていますが、[{type}]型の[{value}]が渡されました", "kbn-esql-validation-autocomplete.esql.validation.unknownColumn": "不明な列[{name}]", @@ -5813,8 +5813,6 @@ "searchApiPanels.welcomeBanner.selectClient.callout.description": "コンソールでは、REST APIを使用してすぐに開始できます。インストールは不要です。", "searchApiPanels.welcomeBanner.selectClient.callout.link": "今すぐコンソールを試す", "searchApiPanels.welcomeBanner.selectClient.callout.title": "今すぐコンソールで試す", - "searchApiPanels.welcomeBanner.selectClient.description": "Elasticは複数の一般的な言語でクライアントを構築および保守します。Elasticのコミュニティはさらに多くを提供しています。お気に入りの言語クライアントを選択するか、{console}起動して開始します。", - "searchApiPanels.welcomeBanner.selectClient.description.console.link": "コンソール", "searchApiPanels.welcomeBanner.selectClient.elasticsearchClientDocLink": "Elasticsearchクライアント ", "searchApiPanels.welcomeBanner.selectClient.heading": "1つ選択", "searchApiPanels.welcomeBanner.selectClient.title": "クライアントを選択", @@ -6613,14 +6611,11 @@ "textBasedEditor.query.textBasedLanguagesEditor.errorCount": "{count} {count, plural, other {# 件のエラー}}", "textBasedEditor.query.textBasedLanguagesEditor.errorsTitle": "エラー", "textBasedEditor.query.textBasedLanguagesEditor.esql": "ES|QL", - "textBasedEditor.query.textBasedLanguagesEditor.expandTooltip": "クエリエディターを展開", "textBasedEditor.query.textBasedLanguagesEditor.feedback": "フィードバック", "textBasedEditor.query.textBasedLanguagesEditor.functions": "関数", "textBasedEditor.query.textBasedLanguagesEditor.functionsDocumentationESQLDescription": "関数はROW、EVAL、WHEREでサポートされています。", "textBasedEditor.query.textBasedLanguagesEditor.lineCount": "{count} {count, plural, other {行}}", "textBasedEditor.query.textBasedLanguagesEditor.lineNumber": "行{lineNumber}", - "textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor": "エディターを最小化", - "textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip": "クエリエディターを縮小", "textBasedEditor.query.textBasedLanguagesEditor.operators": "演算子", "textBasedEditor.query.textBasedLanguagesEditor.operatorsDocumentationESQLDescription": "ES|QLは以下の演算子をサポートしています。", "textBasedEditor.query.textBasedLanguagesEditor.processingCommands": "処理コマンド", @@ -7202,12 +7197,6 @@ "unifiedSearch.query.queryBar.indexPattern.findFilterSet": "クエリを検索", "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "このデータビューを管理", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "一時", - "unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "データビューを切り替えると、現在の{textBasedLanguage}クエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "データビューを切り替えると、現在の{language}クエリが削除されます。この検索を保存すると、作業内容が失われないことが保証されます。", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "保存せずに切り替え", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "次回以降この警告を表示しない", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "保存して切り替え", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle": "クエリは削除されます", "unifiedSearch.query.queryBar.kqlLanguageName": "KQL", "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "ドキュメント", "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "今後表示しない", @@ -7218,7 +7207,6 @@ "unifiedSearch.query.queryBar.searchInputPlaceholder": "{language}構文を使用してデータをフィルタリング", "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "データのフィルタリング", "unifiedSearch.query.queryBar.syntaxOptionsTitle": "構文オプション", - "unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "ES|QLを試す", "unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "{language}クエリの日付範囲選択では、データセットに@timestampフィールドが存在している必要があります。", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "常時", "unifiedSearch.queryBarTopRow.submitButton.cancel": "キャンセル", @@ -13179,7 +13167,6 @@ "xpack.csp.emptyState.resetFiltersButton": "フィルターをリセット", "xpack.csp.emptyState.title": "検索条件と一致する結果がありません。", "xpack.csp.enableBenchmarkRuleButton": "ルールを有効にする", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "{total}件中{pageStart}-{pageEnd}件の{type}を表示しています", "xpack.csp.findings.distributionBar.totalFailedLabel": "失敗した調査結果", "xpack.csp.findings.distributionBar.totalPassedLabel": "合格した調査結果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "検索結果の取得中にエラーが発生しました", @@ -13199,7 +13186,6 @@ "xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle": "根拠", "xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle": "修正", "xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle": "ルール名", - "xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTooltip": "ルールの管理", "xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle": "ルールタグ", "xpack.csp.findings.findingsFlyout.overviewTabTitle": "概要", "xpack.csp.findings.findingsFlyout.paginationLabel": "ナビゲーションを検索中", @@ -13211,7 +13197,6 @@ "xpack.csp.findings.findingsFlyout.ruleTab.disabledRuleText": "無効", "xpack.csp.findings.findingsFlyout.ruleTab.frameworkSourcesTitle": "フレームワークソース", "xpack.csp.findings.findingsFlyout.ruleTab.nameTitle": "名前", - "xpack.csp.findings.findingsFlyout.ruleTab.nameTooltip": "ルールの管理", "xpack.csp.findings.findingsFlyout.ruleTab.profileApplicabilityTitle": "プロファイル適用性", "xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle": "基準", "xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle": "タグ", @@ -13220,9 +13205,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "サブスクライブに使用される資格情報とキーを含むJSONファイルへのパス", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "サブスクライブに使用される資格情報とキーを含むJSON blob", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "資格情報", - "xpack.csp.findings.groupBySelector.groupByLabel": "グループ分けの条件", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "なし", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "リソース", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "クラウドアカウントなし", "xpack.csp.findings.grouping.default.nullGroupTitle": "グループ分けなし", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "Kubernetesクラスターなし", @@ -13411,9 +13393,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "脆弱性", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "リソースなし", "xpack.csp.vulnerabilities.grouping.severity": "深刻度", - "xpack.csp.vulnerabilities.searchBar.placeholder": "脆弱性を検索(例:vulnerability.severity :\"CRITICAL\")", - "xpack.csp.vulnerabilities.table.filterIn": "フィルタリング", - "xpack.csp.vulnerabilities.table.filterOut": "除外", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, other {脆弱性}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "パッケージ", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "リソースID", @@ -13438,8 +13417,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "アカウント", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "重要度別傾向", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "すべて表示", - "xpack.csp.vulnerabilityTable.column.sortAscending": "低 -> 重大", - "xpack.csp.vulnerabilityTable.column.sortDescending": "重大 -> 低", "xpack.csp.vulnerabilityTable.panel.buttonText": "すべての脆弱性を表示", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", @@ -22275,7 +22252,6 @@ "xpack.infra.waffle.maxGroupByTooltip": "一度に選択できるグループは 2 つのみです", "xpack.infra.waffle.metriclabel": "メトリック", "xpack.infra.waffle.metricOptions.countText": "カウント", - "xpack.infra.waffle.metricOptions.cpuUsageText": "CPU 使用状況", "xpack.infra.waffle.metricOptions.diskIOReadBytes": "ディスク読み取り", "xpack.infra.waffle.metricOptions.diskIOWriteBytes": "ディスク書き込み", "xpack.infra.waffle.metricOptions.hostLogRateText": "ログレート", @@ -24262,13 +24238,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "ファイルは{fileName}のファイルシステムからアップロードされました", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "スタックトレースがあります", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "スタックトレースがありません", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "ログの詳細を展開", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "次のようなアクショナブルな情報を提供するフィールド:", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "次に基づいて使用可能なスタックトレースにアクセス:", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "ドキュメントの{logLevel}と{message}フィールドを表示します。", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "メッセージフィールドが空のときには、次のいずれかが表示されます。", - "xpack.logsExplorer.dataTable.header.popover.actions": "アクション", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "アクション", "xpack.logsExplorer.dataTable.header.popover.content": "コンテンツ", "xpack.logsExplorer.dataTable.header.popover.resource": "リソース", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "次のようなドキュメントのソースに関する情報を提供するフィールド:", @@ -28220,7 +28191,6 @@ "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "モデルにはパイプラインが関連付けられています", "xpack.ml.trainedModels.modelsList.downloadFailed": "\"{modelId}\"をダウンロードできませんでした", "xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage": "ダウンロードステータスを確認できませんでした", - "xpack.ml.trainedModels.modelsList.downloadSuccess": "\"{modelId}\"モデルのダウンロードが正常に開始しました。", "xpack.ml.trainedModels.modelsList.e5Title": "E5(bidirEctional Encoder rEpresentationsからのEmbEddings)", "xpack.ml.trainedModels.modelsList.e5v1Description": "E5(bidirEctional Encoder rEpresentationsからのEmbEddings)", "xpack.ml.trainedModels.modelsList.e5v1x86Description": "E5(bidirEctional Encoder rEpresentationsからのEmbEddings)、inux-x86_64向けに最適化", @@ -28278,7 +28248,6 @@ "xpack.ml.trainedModels.modelsList.pipelines.processorStats.timePerDocHeader": "ドキュメントごとの時間", "xpack.ml.trainedModels.modelsList.pipelines.processorStats.typeHeader": "プロセッサータイプ", "xpack.ml.trainedModels.modelsList.recommendedDownloadContent": "クラスターのハードウェア構成に応じた推奨モデルバージョン", - "xpack.ml.trainedModels.modelsList.recommendedDownloadLabel": "(推奨)", "xpack.ml.trainedModels.modelsList.selectableMessage": "モデルを選択", "xpack.ml.trainedModels.modelsList.selectedModelsMessage": "{modelsCount, plural, other {#個のモデル}}が選択されました", "xpack.ml.trainedModels.modelsList.startDeployment.cancelButton": "キャンセル", @@ -32770,14 +32739,9 @@ "xpack.securitySolution.alertCountByRuleByStatus.ruleName": "kibana.alert.rule.name", "xpack.securitySolution.alertCountByRuleByStatus.status": "ステータス", "xpack.securitySolution.alertCountByRuleByStatus.tooltipTitle": "ルール名", - "xpack.securitySolution.alertDetails.enrichmentQueryEndDate": "終了日", - "xpack.securitySolution.alertDetails.enrichmentQueryStartDate": "開始日", - "xpack.securitySolution.alertDetails.investigationTimeQueryTitle": "Threat Intelligenceで拡張", - "xpack.securitySolution.alertDetails.noEnrichmentsFoundDescription": "このアラートには脅威情報は含まれていません。", "xpack.securitySolution.alertDetails.overview.hostRiskDataTitle": "{riskEntity}リスクデータ", "xpack.securitySolution.alertDetails.overview.insights.suppressedAlertsCountTechnicalPreview": "テクニカルプレビュー", "xpack.securitySolution.alertDetails.overview.investigationGuide": "調査ガイド", - "xpack.securitySolution.alertDetails.refresh": "更新", "xpack.securitySolution.alertDetails.summary.readLess": "表示を減らす", "xpack.securitySolution.alertDetails.summary.readMore": "続きを読む", "xpack.securitySolution.alerts.badge.readOnly.tooltip": "アラートを更新できません", @@ -33226,8 +33190,6 @@ "xpack.securitySolution.components.chartSelect.chartsOption": "チャート", "xpack.securitySolution.components.chartSelect.chartsOptionTitle": "まとめ", "xpack.securitySolution.components.chartSelect.legendTitle": "タブを選択", - "xpack.securitySolution.components.chartSelect.selectAChartAriaLabel": "アラートを選択", - "xpack.securitySolution.components.chartSelect.tableOption": "表", "xpack.securitySolution.components.chartSelect.tableOptionTitle": "カウント", "xpack.securitySolution.components.chartSelect.treemapOption": "ツリーマップ", "xpack.securitySolution.components.chartSelect.trendOption": "傾向", @@ -33516,12 +33478,6 @@ "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.sourceLabel": "ソース", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.userNameLabel": "ユーザー", "xpack.securitySolution.detectionEngine.alerts.alertsByType.alertRuleChartTitle": "名前別アラート", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.alertTypeChartTitle": "タイプ別アラート", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.detection": "検知", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.detections": "検出", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.prevention": "防御", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.preventions": "防御", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.typeColumn": "型", "xpack.securitySolution.detectionEngine.alerts.chartsTitle": "チャート", "xpack.securitySolution.detectionEngine.alerts.closedAlertFailedToastMessage": "アラートをクローズできませんでした。", "xpack.securitySolution.detectionEngine.alerts.closedAlertsTitle": "対応済", @@ -33558,7 +33514,6 @@ "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showBuildingBlockTitle": "基本アラートを含める", "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showOnlyThreatIndicatorAlerts": "脅威インジケーターアラートのみを表示", "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersTitle": "追加のフィルター", - "xpack.securitySolution.detectionEngine.alerts.utilityBar.takeActionTitle": "アクションを実行", "xpack.securitySolution.detectionEngine.alertTitle": "アラート", "xpack.securitySolution.detectionEngine.body.forEachAlert.message": "ルール{ruleName}はアラート{alertId}を生成しました", "xpack.securitySolution.detectionEngine.body.summary.message": "ルール{ruleName}は{signalsCount}件のアラートを生成しました", @@ -35806,14 +35761,7 @@ "xpack.securitySolution.event.summary.threat_indicator.modal.close": "閉じる", "xpack.securitySolution.event.summary.threat_indicator.showMatches": "すべての{count}件のインジケーター一致アラートを表示", "xpack.securitySolution.eventDetails.alertReason": "アラートの理由", - "xpack.securitySolution.eventDetails.ctiSummary.feedNamePreposition": "開始", - "xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTitle": "脅威一致が検出されました", - "xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipContent": "利用可能な脅威インジケーターの一致を表示します。", - "xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTitle": "脅威インテリジェンスで強化", - "xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipContent": "アラートに関する追加の脅威インテリジェンスを表示します。デフォルトでは過去30日分が照会されます。", "xpack.securitySolution.eventDetails.description": "説明", - "xpack.securitySolution.eventDetails.field": "フィールド", - "xpack.securitySolution.eventDetails.filter.placeholder": "フィールド、値、または説明でフィルター...", "xpack.securitySolution.eventDetails.multiFieldBadge": "複数フィールド", "xpack.securitySolution.eventDetails.multiFieldTooltipContent": "複数フィールドにはフィールドごとに複数の値を入力できます", "xpack.securitySolution.eventDetails.osqueryView": "Osquery結果", @@ -35825,7 +35773,6 @@ "xpack.securitySolution.eventDetails.summaryView": "まとめ", "xpack.securitySolution.eventDetails.table": "表", "xpack.securitySolution.eventDetails.table.actions": "アクション", - "xpack.securitySolution.eventDetails.value": "値", "xpack.securitySolution.eventFilter.flyoutForm.creationSuccessToastTitle": "\"{name}\"がイベントフィルターリストに追加されました。", "xpack.securitySolution.eventFilter.form.description.placeholder": "説明", "xpack.securitySolution.eventFilter.form.name.error": "名前を空にすることはできません", @@ -36070,18 +36017,6 @@ "xpack.securitySolution.fieldBrowser.removeButtonDescription": "ランタイムフィールドを削除", "xpack.securitySolution.fieldBrowser.runtimeLabel": "ランタイム", "xpack.securitySolution.fieldBrowser.runtimeTitle": "ランタイムフィールド", - "xpack.securitySolution.fieldNameIcons.booleanAriaLabel": "ブールフィールド", - "xpack.securitySolution.fieldNameIcons.conflictFieldAriaLabel": "矛盾フィールド", - "xpack.securitySolution.fieldNameIcons.dateFieldAriaLabel": "日付フィールド", - "xpack.securitySolution.fieldNameIcons.geoPointFieldAriaLabel": "地理ポイントフィールド", - "xpack.securitySolution.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報シェイプフィールド", - "xpack.securitySolution.fieldNameIcons.ipAddressFieldAriaLabel": "IPアドレスフィールド", - "xpack.securitySolution.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3フィールド", - "xpack.securitySolution.fieldNameIcons.nestedFieldAriaLabel": "入れ子フィールド", - "xpack.securitySolution.fieldNameIcons.numberFieldAriaLabel": "数値フィールド", - "xpack.securitySolution.fieldNameIcons.sourceFieldAriaLabel": "ソースフィールド", - "xpack.securitySolution.fieldNameIcons.stringFieldAriaLabel": "文字列フィールド", - "xpack.securitySolution.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", "xpack.securitySolution.fieldRenderers.moreLabel": "詳細", "xpack.securitySolution.filtersGroup.assignees.buttonTitle": "担当者", "xpack.securitySolution.filtersGroup.assignees.popoverTooltip": "担当者でフィルター", @@ -36563,7 +36498,6 @@ "xpack.securitySolution.inspect.modal.somethingWentWrongDescription": "申し訳ございませんが、何か問題が発生しました。", "xpack.securitySolution.inspectDescription": "検査", "xpack.securitySolution.inspectPatternDifferent": "この要素には、データビュー設定とは別の固有のインデックスパターンがあります。", - "xpack.securitySolution.investigationEnrichment.requestError": "脅威インテリジェンスの要求中にエラーが発生しました", "xpack.securitySolution.ja3.fingerprint.ja3.fingerprintLabel": "ja3", "xpack.securitySolution.kpiHosts.hosts.title": "ホスト", "xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel": "Dest.", @@ -37422,9 +37356,6 @@ "xpack.securitySolution.ruleExceptions.allExceptionItems.noSearchResultsPromptTitle": "検索条件と一致する結果がありません。", "xpack.securitySolution.ruleExceptions.allExceptionItems.paginationAriaLabel": "例外アイテムの表のページ制御", "xpack.securitySolution.ruleExceptions.allExceptionItems.searchPlaceholder": "シンプルなクエリ構文(name:\"my list\"など)を使用して例外をフィルタリング", - "xpack.securitySolution.ruleExceptions.editException.cancel": "キャンセル", - "xpack.securitySolution.ruleExceptions.editException.editEndpointExceptionTitle": "エンドポイント例外の編集", - "xpack.securitySolution.ruleExceptions.editException.editExceptionTitle": "ルール例外を編集", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastErrorTitle": "例外の更新エラー", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastSuccessText": "{numItems, plural, other {例外}} - {exceptionItemName} - {numItems, plural, other {が}}更新されました。", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastSuccessTitle": "ルール例外が更新されました", @@ -39566,8 +39497,6 @@ "xpack.stackAlerts.esQuery.ui.selectQueryFormType.cancelSelectionAriaLabel": "選択をキャンセル", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeDescription": "ES|QLを使用して、テキストベースのクエリを定義します。", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeLabel": "ES|QL", - "xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalDescription": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticはすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。", - "xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalLabel": "テクニカルプレビュー", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeDescription": "KQLまたはLuceneを使用して、テキストベースのクエリを定義します。", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeLabel": "KQLまたはLucene", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.queryDslFormTypeDescription": "ElasticsearchクエリDSLを使用して、クエリを定義します。", @@ -42850,7 +42779,6 @@ "xpack.triggersActionsUI.updateApiKeyConfirmModal.failureMessage": "API {idsToUpdate, plural, other {キー}}を更新できませんでした", "xpack.triggersActionsUI.updateApiKeyConfirmModal.title": "APIキーの更新", "xpack.triggersActionsUI.urlSyncedAlertsSearchBar.invalidQueryTitle": "無効なクエリ文字列", - "xpack.triggersActionsUI.useAlertDataView.useAlertDataMessage": "アラートデータビューを読み込めません", "xpack.triggersActionsUI.useRuleAADFields.errorMessage": "ルールタイプごとにアラートフィールドを読み込めません", "xpack.upgradeAssistant.app.deniedPrivilegeDescription": "アップグレードアシスタントを使用して、廃止予定の問題を解決するには、すべてのKibanaスペースを管理するためのアクセス権が必要です。", "xpack.upgradeAssistant.app.deniedPrivilegeTitle": "Kibana管理者ロールが必要です", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4051e74ceb544..e314413a565e8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -104,7 +104,7 @@ "alertsUIShared.components.alertLifecycleStatusBadge.flappingLabel": "摆动", "alertsUIShared.components.alertLifecycleStatusBadge.recoveredLabel": "已恢复", "alertsUIShared.components.alertLifecycleStatusBadge.untrackedLabel": "已取消跟踪", - "alertsUIShared.hooks.useAlertDataView.useAlertDataMessage": "无法加载告警数据视图", + "alertsUIShared.hooks.useAlertDataView.fetchErrorMessage": "无法加载告警数据视图", "alertsUIShared.hooks.useLoadRuleTypesQuery.unableToLoadRuleTypesMessage": "无法加载规则类型", "alertsUIShared.hooks.useRuleAADFields.errorMessage": "无法按规则类型加载告警字段", "alertsUIShared.maintenanceWindowCallout.fetchError": "无法检查维护窗口是否处于活动状态", @@ -836,7 +836,6 @@ "core.euiProgress.valueText": "{value}%", "core.euiQuickSelect.applyButton": "应用", "core.euiQuickSelect.fullDescription": "当前设置为 {timeTense} {timeValue} {timeUnit}。", - "core.euiQuickSelect.legendText": "快选时间范围", "core.euiQuickSelect.nextLabel": "下一时间窗口", "core.euiQuickSelect.previousLabel": "上一时间窗口", "core.euiQuickSelect.quickSelectTitle": "快速选择", @@ -848,7 +847,6 @@ "core.euiRecentlyUsed.legend": "最近使用的日期范围", "core.euiRefreshInterval.fullDescriptionOff": "刷新已关闭,时间间隔设置为 {optionValue} {optionText}。", "core.euiRefreshInterval.fullDescriptionOn": "刷新已打开,时间间隔设置为 {optionValue} {optionText}。", - "core.euiRefreshInterval.legend": "刷新频率", "core.euiRelativeTab.dateInputError": "必须为有效范围", "core.euiRelativeTab.fullDescription": "单位可更改。当前设置为 {unit}。", "core.euiRelativeTab.numberInputError": "必须 >= 0", @@ -2410,6 +2408,11 @@ "discover.embeddable.search.displayName": "搜索", "discover.errorCalloutShowErrorMessage": "查看详情", "discover.esqlMode.selectedColumnsCallout": "正在显示 {selectedColumnsNumber} 个字段,共 {esqlQueryColumnsNumber} 个。从可用字段列表中添加更多字段。", + "discover.esqlToDataViewTransitionModal.closeButtonLabel": "切换而不保存", + "discover.esqlToDataViewTransitionModal.dismissButtonLabel": "不再显示此警告", + "discover.esqlToDataViewTransitionModal.saveButtonLabel": "保存并切换", + "discover.esqlToDataViewTransitionModal.title": "将移除您的查询", + "discover.esqlToDataviewTransitionModalBody": "切换数据视图会移除当前的 ES|QL 查询。保存此搜索以确保不会丢失工作。", "discover.fieldChooser.availableFieldsTooltip": "适用于在表中显示的字段。", "discover.fieldChooser.discoverField.addFieldTooltip": "将字段添加为列", "discover.fieldChooser.discoverField.removeFieldTooltip": "从表中移除字段", @@ -5167,10 +5170,7 @@ "kbn-esql-validation-autocomplete.esql.validation.expectedConstantValue": "[{fn}] 的参数必须为常数,收到的是 [{given}]", "kbn-esql-validation-autocomplete.esql.validation.metadataBracketsDeprecation": "需要从 FROM METADATA 声明中移除方括号“[]”", "kbn-esql-validation-autocomplete.esql.validation.missingFunction": "未知函数 [{name}]", - "kbn-esql-validation-autocomplete.esql.validation.noCombinationOfAggAndNonAggValues": "无法在 [STATS] 中组合聚合与非聚合值,找到了 [{expression}]", "kbn-esql-validation-autocomplete.esql.validation.noNestedArgumentSupport": "聚合函数的参数必须为属性、文本或非聚合函数;找到了 [{argType}] 类型的 [{name}]", - "kbn-esql-validation-autocomplete.esql.validation.statsNoAggFunction": "[STATS] 中至少需要一个聚合函数,找到了 [{expression}]", - "kbn-esql-validation-autocomplete.esql.validation.statsNoArguments": "[STATS] 中至少需要一个聚合或分组表达式", "kbn-esql-validation-autocomplete.esql.validation.typeOverwrite": "类型为 {fieldType} 的列 [{field}] 已重写为新类型:{newType}", "kbn-esql-validation-autocomplete.esql.validation.unknowAggregateFunction": "应为聚合函数或组,但收到的是 [{type}] 类型的 [{value}]", "kbn-esql-validation-autocomplete.esql.validation.unknownColumn": "未知列 [{name}]", @@ -5842,8 +5842,6 @@ "searchApiPanels.welcomeBanner.selectClient.callout.description": "借助 Console,您可以立即开始使用我们的 REST API。无需进行安装。", "searchApiPanels.welcomeBanner.selectClient.callout.link": "立即试用 Console", "searchApiPanels.welcomeBanner.selectClient.callout.title": "立即在 Console 中试用", - "searchApiPanels.welcomeBanner.selectClient.description": "Elastic 以几种流行语言构建和维护客户端,我们的社区也做出了许多贡献。选择您常用的语言客户端或深入分析 {console} 以开始使用。", - "searchApiPanels.welcomeBanner.selectClient.description.console.link": "控制台", "searchApiPanels.welcomeBanner.selectClient.elasticsearchClientDocLink": "Elasticsearch 客户端 ", "searchApiPanels.welcomeBanner.selectClient.heading": "选择一个", "searchApiPanels.welcomeBanner.selectClient.title": "选择客户端", @@ -6646,14 +6644,11 @@ "textBasedEditor.query.textBasedLanguagesEditor.errorCount": "{count} 个{count, plural, other {错误}}", "textBasedEditor.query.textBasedLanguagesEditor.errorsTitle": "错误", "textBasedEditor.query.textBasedLanguagesEditor.esql": "ES|QL", - "textBasedEditor.query.textBasedLanguagesEditor.expandTooltip": "展开查询编辑器", "textBasedEditor.query.textBasedLanguagesEditor.feedback": "反馈", "textBasedEditor.query.textBasedLanguagesEditor.functions": "函数", "textBasedEditor.query.textBasedLanguagesEditor.functionsDocumentationESQLDescription": "ROW、EVAL 和 WHERE 支持的函数。", "textBasedEditor.query.textBasedLanguagesEditor.lineCount": "{count} {count, plural, other {行}}", "textBasedEditor.query.textBasedLanguagesEditor.lineNumber": "第 {lineNumber} 行", - "textBasedEditor.query.textBasedLanguagesEditor.MinimizeEditor": "最小化编辑器", - "textBasedEditor.query.textBasedLanguagesEditor.minimizeTooltip": "压缩查询编辑器", "textBasedEditor.query.textBasedLanguagesEditor.operators": "运算符", "textBasedEditor.query.textBasedLanguagesEditor.operatorsDocumentationESQLDescription": "ES|QL 支持以下运算符:", "textBasedEditor.query.textBasedLanguagesEditor.processingCommands": "处理命令", @@ -7238,12 +7233,6 @@ "unifiedSearch.query.queryBar.indexPattern.findFilterSet": "查找查询", "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "管理此数据视图", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "临时", - "unifiedSearch.query.queryBar.indexPattern.textBasedLangSwitchWarning": "切换数据视图会移除当前的 {textBasedLanguage} 查询。保存此搜索以确保不会丢失工作。", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalBody": "切换数据视图会移除当前的 {language} 查询。保存此搜索以确保不会丢失工作。", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalCloseButton": "切换而不保存", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalDismissButton": "不再显示此警告", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalSaveButton": "保存并切换", - "unifiedSearch.query.queryBar.indexPattern.textBasedLanguagesTransitionModalTitle": "将移除您的查询", "unifiedSearch.query.queryBar.kqlLanguageName": "KQL", "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "文档", "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "不再显示", @@ -7254,7 +7243,6 @@ "unifiedSearch.query.queryBar.searchInputPlaceholder": "使用 {language} 语法筛选数据", "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "筛选您的数据", "unifiedSearch.query.queryBar.syntaxOptionsTitle": "语法选项", - "unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "尝试 ES|QL", "unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "{language} 查询的日期范围选择要求数据集中存在 @timestamp 字段。", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "所有时间", "unifiedSearch.queryBarTopRow.submitButton.cancel": "取消", @@ -13239,7 +13227,6 @@ "xpack.csp.emptyState.resetFiltersButton": "重置筛选", "xpack.csp.emptyState.title": "没有任何结果匹配您的搜索条件", "xpack.csp.enableBenchmarkRuleButton": "启用规则", - "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "正在显示第 {pageStart}-{pageEnd} 个(共 {total} 个){type}", "xpack.csp.findings.distributionBar.totalFailedLabel": "失败的结果", "xpack.csp.findings.distributionBar.totalPassedLabel": "通过的结果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "检索搜索结果时遇到问题", @@ -13259,7 +13246,6 @@ "xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle": "理由", "xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle": "补救", "xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle": "规则名称", - "xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTooltip": "管理规则", "xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle": "规则标签", "xpack.csp.findings.findingsFlyout.overviewTabTitle": "概览", "xpack.csp.findings.findingsFlyout.paginationLabel": "正在查找导航", @@ -13271,7 +13257,6 @@ "xpack.csp.findings.findingsFlyout.ruleTab.disabledRuleText": "已禁用", "xpack.csp.findings.findingsFlyout.ruleTab.frameworkSourcesTitle": "框架源", "xpack.csp.findings.findingsFlyout.ruleTab.nameTitle": "名称", - "xpack.csp.findings.findingsFlyout.ruleTab.nameTooltip": "管理规则", "xpack.csp.findings.findingsFlyout.ruleTab.profileApplicabilityTitle": "配置文件适用性", "xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle": "参考", "xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle": "标签", @@ -13280,9 +13265,6 @@ "xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "包含用于订阅的凭据和密钥的 JSON 文件的路径", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "包含用于订阅的凭据和密钥的 JSON Blob", "xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "凭据", - "xpack.csp.findings.groupBySelector.groupByLabel": "分组依据", - "xpack.csp.findings.groupBySelector.groupByNoneLabel": "无", - "xpack.csp.findings.groupBySelector.groupByResourceIdLabel": "资源", "xpack.csp.findings.grouping.cloudAccount.nullGroupTitle": "无云帐户", "xpack.csp.findings.grouping.default.nullGroupTitle": "无分组", "xpack.csp.findings.grouping.kubernetes.nullGroupTitle": "无 Kubernetes 集群", @@ -13472,9 +13454,6 @@ "xpack.csp.vulnerabilities.grouping.nullGroupUnit": "漏洞", "xpack.csp.vulnerabilities.grouping.resource.nullGroupTitle": "无资源", "xpack.csp.vulnerabilities.grouping.severity": "严重性", - "xpack.csp.vulnerabilities.searchBar.placeholder": "搜索漏洞(例如,vulnerability.severity:“CRITICAL”)", - "xpack.csp.vulnerabilities.table.filterIn": "筛选范围", - "xpack.csp.vulnerabilities.table.filterOut": "筛除", "xpack.csp.vulnerabilities.unit": "{totalCount, plural, other {个漏洞}}", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle": "软件包", "xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.resourceId": "资源 ID", @@ -13499,8 +13478,6 @@ "xpack.csp.vulnerabilityDashboard.trendGraphChart.accountsDropDown.prepend.accountsTitle": "帐户", "xpack.csp.vulnerabilityDashboard.trendGraphChart.trendBySeverityTitle": "趋势(按严重性)", "xpack.csp.vulnerabilityDashboard.viewAllButton.buttonTitle": "查看全部", - "xpack.csp.vulnerabilityTable.column.sortAscending": "低 -> 严重", - "xpack.csp.vulnerabilityTable.column.sortDescending": "严重 -> 低", "xpack.csp.vulnerabilityTable.panel.buttonText": "查看所有漏洞", "xpack.csp.vulnMgmtIntegration.awsOption.nameTitle": "Amazon Web Services", "xpack.csp.vulnMgmtIntegration.azureOption.nameTitle": "Azure", @@ -22378,7 +22355,6 @@ "xpack.infra.waffle.maxGroupByTooltip": "一次只能选择两个分组", "xpack.infra.waffle.metriclabel": "指标", "xpack.infra.waffle.metricOptions.countText": "计数", - "xpack.infra.waffle.metricOptions.cpuUsageText": "CPU 使用", "xpack.infra.waffle.metricOptions.diskIOReadBytes": "磁盘读取数", "xpack.infra.waffle.metricOptions.diskIOWriteBytes": "磁盘写入数", "xpack.infra.waffle.metricOptions.hostLogRateText": "日志速率", @@ -24371,13 +24347,8 @@ "xpack.lists.services.items.fileUploadFromFileSystem": "从 {fileName} 的文件系统上传的文件", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.available": "堆栈跟踪可用", "xpack.logsExplorer.dataTable.controlColumn.actions.button.stacktrace.notAvailable": "堆栈跟踪不可用", - "xpack.logsExplorer.dataTable.header.actions.tooltip.expand": "展开日志详情", - "xpack.logsExplorer.dataTable.header.actions.tooltip.paragraph": "提供可操作信息的字段,例如:", - "xpack.logsExplorer.dataTable.header.actions.tooltip.stacktrace": "基于以下项访问可用堆栈跟踪:", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "显示该文档的 {logLevel} 和 {message} 字段。", "xpack.logsExplorer.dataTable.header.content.tooltip.paragraph2": "消息字段为空时,将显示以下项之一:", - "xpack.logsExplorer.dataTable.header.popover.actions": "操作", - "xpack.logsExplorer.dataTable.header.popover.actions.lowercase": "操作", "xpack.logsExplorer.dataTable.header.popover.content": "内容", "xpack.logsExplorer.dataTable.header.popover.resource": "资源", "xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "提供有关文档来源信息的字段,例如:", @@ -28354,7 +28325,6 @@ "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "模型有关联的管道", "xpack.ml.trainedModels.modelsList.downloadFailed": "无法下载“{modelId}”", "xpack.ml.trainedModels.modelsList.downloadStatusCheckErrorMessage": "无法检查下载状态", - "xpack.ml.trainedModels.modelsList.downloadSuccess": "已成功启动“{modelId}”模型下载。", "xpack.ml.trainedModels.modelsList.e5Title": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1Description": "E5 (EmbEddings from bidirEctional Encoder rEpresentations)", "xpack.ml.trainedModels.modelsList.e5v1x86Description": "针对 linux-x86_64 进行了优化的 E5 (EmbEddings from bidirEctional Encoder rEpresentations)", @@ -28412,7 +28382,6 @@ "xpack.ml.trainedModels.modelsList.pipelines.processorStats.timePerDocHeader": "每个文档的时间", "xpack.ml.trainedModels.modelsList.pipelines.processorStats.typeHeader": "处理器类型", "xpack.ml.trainedModels.modelsList.recommendedDownloadContent": "为您集群的硬件配置推荐的模型版本", - "xpack.ml.trainedModels.modelsList.recommendedDownloadLabel": "(推荐)", "xpack.ml.trainedModels.modelsList.selectableMessage": "选择模型", "xpack.ml.trainedModels.modelsList.selectedModelsMessage": "{modelsCount, plural, other {# 个模型}}已选择", "xpack.ml.trainedModels.modelsList.startDeployment.cancelButton": "取消", @@ -32937,14 +32906,9 @@ "xpack.securitySolution.alertCountByRuleByStatus.ruleName": "kibana.alert.rule.name", "xpack.securitySolution.alertCountByRuleByStatus.status": "状态", "xpack.securitySolution.alertCountByRuleByStatus.tooltipTitle": "规则名称", - "xpack.securitySolution.alertDetails.enrichmentQueryEndDate": "结束日期", - "xpack.securitySolution.alertDetails.enrichmentQueryStartDate": "开始日期", - "xpack.securitySolution.alertDetails.investigationTimeQueryTitle": "使用威胁情报扩充", - "xpack.securitySolution.alertDetails.noEnrichmentsFoundDescription": "此告警没有威胁情报。", "xpack.securitySolution.alertDetails.overview.hostRiskDataTitle": "{riskEntity}风险数据", "xpack.securitySolution.alertDetails.overview.insights.suppressedAlertsCountTechnicalPreview": "技术预览", "xpack.securitySolution.alertDetails.overview.investigationGuide": "调查指南", - "xpack.securitySolution.alertDetails.refresh": "刷新", "xpack.securitySolution.alertDetails.summary.readLess": "阅读更少内容", "xpack.securitySolution.alertDetails.summary.readMore": "阅读更多内容", "xpack.securitySolution.alerts.badge.readOnly.tooltip": "无法更新告警", @@ -33393,8 +33357,6 @@ "xpack.securitySolution.components.chartSelect.chartsOption": "图表", "xpack.securitySolution.components.chartSelect.chartsOptionTitle": "摘要", "xpack.securitySolution.components.chartSelect.legendTitle": "选择选项卡", - "xpack.securitySolution.components.chartSelect.selectAChartAriaLabel": "选择图表", - "xpack.securitySolution.components.chartSelect.tableOption": "表", "xpack.securitySolution.components.chartSelect.tableOptionTitle": "计数", "xpack.securitySolution.components.chartSelect.treemapOption": "树状图", "xpack.securitySolution.components.chartSelect.trendOption": "趋势", @@ -33684,12 +33646,6 @@ "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.sourceLabel": "源", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.userNameLabel": "user", "xpack.securitySolution.detectionEngine.alerts.alertsByType.alertRuleChartTitle": "按名称排列的告警", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.alertTypeChartTitle": "按类型排列的告警", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.detection": "检测", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.detections": "检测", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.prevention": "防护", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.preventions": "防护", - "xpack.securitySolution.detectionEngine.alerts.alertsByType.typeColumn": "类型", "xpack.securitySolution.detectionEngine.alerts.chartsTitle": "图表", "xpack.securitySolution.detectionEngine.alerts.closedAlertFailedToastMessage": "无法关闭告警。", "xpack.securitySolution.detectionEngine.alerts.closedAlertsTitle": "已关闭", @@ -33726,7 +33682,6 @@ "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showBuildingBlockTitle": "包括构建块告警", "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showOnlyThreatIndicatorAlerts": "仅显示威胁指标告警", "xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersTitle": "其他筛选", - "xpack.securitySolution.detectionEngine.alerts.utilityBar.takeActionTitle": "采取操作", "xpack.securitySolution.detectionEngine.alertTitle": "告警", "xpack.securitySolution.detectionEngine.body.forEachAlert.message": "规则 {ruleName} 生成了告警 {alertId}", "xpack.securitySolution.detectionEngine.body.summary.message": "规则 {ruleName} 生成了 {signalsCount} 个告警", @@ -35976,14 +35931,7 @@ "xpack.securitySolution.event.summary.threat_indicator.modal.close": "关闭", "xpack.securitySolution.event.summary.threat_indicator.showMatches": "显示所有 {count} 个指标匹配告警", "xpack.securitySolution.eventDetails.alertReason": "告警原因", - "xpack.securitySolution.eventDetails.ctiSummary.feedNamePreposition": "来自", - "xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTitle": "检测到威胁匹配", - "xpack.securitySolution.eventDetails.ctiSummary.indicatorEnrichmentTooltipContent": "显示可用的威胁指标匹配。", - "xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTitle": "已使用威胁情报扩充", - "xpack.securitySolution.eventDetails.ctiSummary.investigationEnrichmentTooltipContent": "显示该告警的其他威胁情报。默认会查询过去 30 天的数据。", "xpack.securitySolution.eventDetails.description": "描述", - "xpack.securitySolution.eventDetails.field": "字段", - "xpack.securitySolution.eventDetails.filter.placeholder": "按字段、值或描述筛选......", "xpack.securitySolution.eventDetails.multiFieldBadge": "多字段", "xpack.securitySolution.eventDetails.multiFieldTooltipContent": "多字段的每个字段可以有多个值", "xpack.securitySolution.eventDetails.osqueryView": "Osquery 结果", @@ -35995,7 +35943,6 @@ "xpack.securitySolution.eventDetails.summaryView": "摘要", "xpack.securitySolution.eventDetails.table": "表", "xpack.securitySolution.eventDetails.table.actions": "操作", - "xpack.securitySolution.eventDetails.value": "值", "xpack.securitySolution.eventFilter.flyoutForm.creationSuccessToastTitle": "“{name}”已添加到事件筛选列表。", "xpack.securitySolution.eventFilter.form.description.placeholder": "描述", "xpack.securitySolution.eventFilter.form.name.error": "名称不能为空", @@ -36240,18 +36187,6 @@ "xpack.securitySolution.fieldBrowser.removeButtonDescription": "删除运行时字段", "xpack.securitySolution.fieldBrowser.runtimeLabel": "运行时", "xpack.securitySolution.fieldBrowser.runtimeTitle": "运行时字段", - "xpack.securitySolution.fieldNameIcons.booleanAriaLabel": "布尔值字段", - "xpack.securitySolution.fieldNameIcons.conflictFieldAriaLabel": "冲突字段", - "xpack.securitySolution.fieldNameIcons.dateFieldAriaLabel": "日期字段", - "xpack.securitySolution.fieldNameIcons.geoPointFieldAriaLabel": "地理点字段", - "xpack.securitySolution.fieldNameIcons.geoShapeFieldAriaLabel": "几何形状字段", - "xpack.securitySolution.fieldNameIcons.ipAddressFieldAriaLabel": "IP 地址字段", - "xpack.securitySolution.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3 字段", - "xpack.securitySolution.fieldNameIcons.nestedFieldAriaLabel": "嵌套字段", - "xpack.securitySolution.fieldNameIcons.numberFieldAriaLabel": "数字字段", - "xpack.securitySolution.fieldNameIcons.sourceFieldAriaLabel": "源字段", - "xpack.securitySolution.fieldNameIcons.stringFieldAriaLabel": "字符串字段", - "xpack.securitySolution.fieldNameIcons.unknownFieldAriaLabel": "未知字段", "xpack.securitySolution.fieldRenderers.moreLabel": "更多", "xpack.securitySolution.filtersGroup.assignees.buttonTitle": "被分配人", "xpack.securitySolution.filtersGroup.assignees.popoverTooltip": "按被分配人筛选", @@ -36732,7 +36667,6 @@ "xpack.securitySolution.inspect.modal.somethingWentWrongDescription": "抱歉,出现问题。", "xpack.securitySolution.inspectDescription": "检查", "xpack.securitySolution.inspectPatternDifferent": "此元素具有与数据视图设置不同的唯一索引模式。", - "xpack.securitySolution.investigationEnrichment.requestError": "请求威胁情报时发生错误", "xpack.securitySolution.ja3.fingerprint.ja3.fingerprintLabel": "ja3", "xpack.securitySolution.kpiHosts.hosts.title": "主机", "xpack.securitySolution.kpiHosts.uniqueIps.destinationChartLabel": "目标", @@ -37592,9 +37526,6 @@ "xpack.securitySolution.ruleExceptions.allExceptionItems.noSearchResultsPromptTitle": "没有任何结果匹配您的搜索条件", "xpack.securitySolution.ruleExceptions.allExceptionItems.paginationAriaLabel": "例外项表分页", "xpack.securitySolution.ruleExceptions.allExceptionItems.searchPlaceholder": "使用简单的查询语法筛选例外,例如 name:\"my list\"", - "xpack.securitySolution.ruleExceptions.editException.cancel": "取消", - "xpack.securitySolution.ruleExceptions.editException.editEndpointExceptionTitle": "编辑终端例外", - "xpack.securitySolution.ruleExceptions.editException.editExceptionTitle": "编辑规则例外", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastErrorTitle": "更新例外时出错", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastSuccessText": "{numItems, plural, other {例外}} - {exceptionItemName} - {numItems, plural, other {已}}更新。", "xpack.securitySolution.ruleExceptions.editException.editRuleExceptionToastSuccessTitle": "已更新规则例外", @@ -39751,8 +39682,6 @@ "xpack.stackAlerts.esQuery.ui.selectQueryFormType.cancelSelectionAriaLabel": "取消选择", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeDescription": "使用 ES|QL 定义基于文本的查询。", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.esqlFormTypeLabel": "ES|QL", - "xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalDescription": "此功能处于技术预览状态,在未来版本中可能会更改或完全移除。Elastic 将努力修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。", - "xpack.stackAlerts.esQuery.ui.selectQueryFormType.experimentalLabel": "技术预览", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeDescription": "使用 KQL 或 Lucene 定义基于文本的查询。", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeLabel": "KQL 或 Lucene", "xpack.stackAlerts.esQuery.ui.selectQueryFormType.queryDslFormTypeDescription": "使用 Elasticsearch 查询 DSL 定义查询。", @@ -43040,7 +42969,6 @@ "xpack.triggersActionsUI.updateApiKeyConfirmModal.failureMessage": "无法更新 API {idsToUpdate, plural, other {密钥}}", "xpack.triggersActionsUI.updateApiKeyConfirmModal.title": "更新 API 密钥", "xpack.triggersActionsUI.urlSyncedAlertsSearchBar.invalidQueryTitle": "字符串查询无效", - "xpack.triggersActionsUI.useAlertDataView.useAlertDataMessage": "无法加载告警数据视图", "xpack.triggersActionsUI.useRuleAADFields.errorMessage": "无法按规则类型加载告警字段", "xpack.upgradeAssistant.app.deniedPrivilegeDescription": "要使用升级助手并解决弃用问题,必须具有管理所有 Kibana 工作区的访问权限。", "xpack.upgradeAssistant.app.deniedPrivilegeTitle": "需要 Kibana 管理员角色", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.test.tsx deleted file mode 100644 index b12d0454b997c..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.test.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AlertConsumers } from '@kbn/rule-data-utils'; -import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; -import type { ValidFeatureId } from '@kbn/rule-data-utils'; -import { renderHook } from '@testing-library/react-hooks/dom'; -import { useAlertDataViews } from './use_alert_data_view'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import React from 'react'; -import { waitFor } from '@testing-library/react'; - -const mockUseKibanaReturnValue = createStartServicesMock(); - -jest.mock('@kbn/kibana-react-plugin/public', () => ({ - __esModule: true, - useKibana: jest.fn(() => ({ - services: mockUseKibanaReturnValue, - })), -})); - -jest.mock('../lib/rule_api/alert_index', () => ({ - fetchAlertIndexNames: jest.fn(), -})); - -const { fetchAlertIndexNames } = jest.requireMock('../lib/rule_api/alert_index'); - -jest.mock('../lib/rule_api/alert_fields', () => ({ - fetchAlertFields: jest.fn(), -})); -const { fetchAlertFields } = jest.requireMock('../lib/rule_api/alert_fields'); - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - cacheTime: 0, - }, - }, -}); -const wrapper = ({ children }: { children: Node }) => ( - <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> -); - -describe('useAlertDataView', () => { - const observabilityAlertFeatureIds: ValidFeatureId[] = [ - AlertConsumers.APM, - AlertConsumers.INFRASTRUCTURE, - AlertConsumers.LOGS, - AlertConsumers.UPTIME, - ]; - - beforeEach(() => { - fetchAlertIndexNames.mockResolvedValue([ - '.alerts-observability.uptime.alerts-*', - '.alerts-observability.metrics.alerts-*', - '.alerts-observability.logs.alerts-*', - '.alerts-observability.apm.alerts-*', - ]); - fetchAlertFields.mockResolvedValue([{ data: ' fields' }]); - }); - - afterEach(() => { - queryClient.clear(); - jest.clearAllMocks(); - }); - - it('initially is loading and does not have data', async () => { - const mockedAsyncDataView = { - loading: true, - dataview: undefined, - }; - - const { result } = renderHook(() => useAlertDataViews(observabilityAlertFeatureIds), { - wrapper, - }); - - await waitFor(() => expect(result.current).toEqual(mockedAsyncDataView)); - }); - - it('fetch index names + fields for the provided o11y featureIds', async () => { - renderHook(() => useAlertDataViews(observabilityAlertFeatureIds), { - wrapper, - }); - - await waitFor(() => expect(fetchAlertIndexNames).toHaveBeenCalledTimes(1)); - expect(fetchAlertFields).toHaveBeenCalledTimes(1); - }); - - it('only fetch index names for security featureId', async () => { - renderHook(() => useAlertDataViews([AlertConsumers.SIEM]), { - wrapper, - }); - - await waitFor(() => expect(fetchAlertIndexNames).toHaveBeenCalledTimes(1)); - expect(fetchAlertFields).toHaveBeenCalledTimes(0); - }); - - it('Do not fetch anything if security and o11y featureIds are mixed together', async () => { - const { result } = renderHook( - () => useAlertDataViews([AlertConsumers.SIEM, AlertConsumers.LOGS]), - { - wrapper, - } - ); - - await waitFor(() => - expect(result.current).toEqual({ - loading: false, - dataview: undefined, - }) - ); - expect(fetchAlertIndexNames).toHaveBeenCalledTimes(0); - expect(fetchAlertFields).toHaveBeenCalledTimes(0); - }); - - it('if fetch throws error return no data', async () => { - fetchAlertIndexNames.mockRejectedValue('error'); - - const { result } = renderHook(() => useAlertDataViews(observabilityAlertFeatureIds), { - wrapper, - }); - - await waitFor(() => - expect(result.current).toEqual({ - loading: false, - dataview: undefined, - }) - ); - }); -}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.ts deleted file mode 100644 index 322bab6c88301..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_alert_data_view.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 { i18n } from '@kbn/i18n'; -import { DataView } from '@kbn/data-views-plugin/common'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils'; -import { useEffect, useMemo, useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; -import { TriggersAndActionsUiServices } from '../..'; -import { fetchAlertIndexNames } from '../lib/rule_api/alert_index'; -import { fetchAlertFields } from '../lib/rule_api/alert_fields'; - -export interface UserAlertDataViews { - dataViews?: DataView[]; - loading: boolean; -} - -export function useAlertDataViews(featureIds: ValidFeatureId[]): UserAlertDataViews { - const { - http, - data: dataService, - notifications: { toasts }, - } = useKibana<TriggersAndActionsUiServices>().services; - const [dataViews, setDataViews] = useState<DataView[] | undefined>(undefined); - const features = featureIds.sort().join(','); - const isOnlySecurity = featureIds.length === 1 && featureIds.includes(AlertConsumers.SIEM); - - const hasSecurityAndO11yFeatureIds = - featureIds.length > 1 && featureIds.includes(AlertConsumers.SIEM); - - const hasNoSecuritySolution = - featureIds.length > 0 && !isOnlySecurity && !hasSecurityAndO11yFeatureIds; - - const queryIndexNameFn = () => { - return fetchAlertIndexNames({ http, features }); - }; - - const queryAlertFieldsFn = () => { - return fetchAlertFields({ http, featureIds }); - }; - - const onErrorFn = () => { - toasts.addDanger( - i18n.translate('xpack.triggersActionsUI.useAlertDataView.useAlertDataMessage', { - defaultMessage: 'Unable to load alert data view', - }) - ); - }; - - const { - data: indexNames, - isSuccess: isIndexNameSuccess, - isInitialLoading: isIndexNameInitialLoading, - isLoading: isIndexNameLoading, - } = useQuery({ - queryKey: ['loadAlertIndexNames', features], - queryFn: queryIndexNameFn, - onError: onErrorFn, - refetchOnWindowFocus: false, - enabled: featureIds.length > 0 && !hasSecurityAndO11yFeatureIds, - }); - - const { - data: alertFields, - isSuccess: isAlertFieldsSuccess, - isInitialLoading: isAlertFieldsInitialLoading, - isLoading: isAlertFieldsLoading, - } = useQuery({ - queryKey: ['loadAlertFields', features], - queryFn: queryAlertFieldsFn, - onError: onErrorFn, - refetchOnWindowFocus: false, - enabled: hasNoSecuritySolution, - }); - - useEffect(() => { - return () => { - dataViews?.map((dv) => { - dataService.dataViews.clearInstanceCache(dv.id); - }); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataViews]); - - // FUTURE ENGINEER this useEffect is for security solution user since - // we are using the user privilege to access the security alert index - useEffect(() => { - async function createDataView() { - const localDataview = await dataService.dataViews.create({ - title: (indexNames ?? []).join(','), - allowNoIndex: true, - }); - setDataViews([localDataview]); - } - - if (isOnlySecurity && isIndexNameSuccess) { - createDataView(); - } - }, [dataService.dataViews, indexNames, isIndexNameSuccess, isOnlySecurity]); - - // FUTURE ENGINEER this useEffect is for o11y and stack solution user since - // we are using the kibana user privilege to access the alert index - useEffect(() => { - if ( - indexNames && - alertFields && - !isOnlySecurity && - isAlertFieldsSuccess && - isIndexNameSuccess - ) { - setDataViews([ - { - title: (indexNames ?? []).join(','), - fieldFormatMap: {}, - fields: (alertFields ?? [])?.map((field) => { - return { - ...field, - ...(field.esTypes && field.esTypes.includes('flattened') ? { type: 'string' } : {}), - }; - }), - }, - ] as unknown as DataView[]); - } - }, [ - alertFields, - dataService.dataViews, - indexNames, - isIndexNameSuccess, - isOnlySecurity, - isAlertFieldsSuccess, - ]); - - return useMemo( - () => ({ - dataViews, - loading: - featureIds.length === 0 || hasSecurityAndO11yFeatureIds - ? false - : isOnlySecurity - ? isIndexNameInitialLoading || isIndexNameLoading - : isIndexNameInitialLoading || - isIndexNameLoading || - isAlertFieldsInitialLoading || - isAlertFieldsLoading, - }), - [ - dataViews, - featureIds.length, - hasSecurityAndO11yFeatureIds, - isOnlySecurity, - isIndexNameInitialLoading, - isIndexNameLoading, - isAlertFieldsInitialLoading, - isAlertFieldsLoading, - ] - ); -} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx index 2f4f0492ad73a..3896e5d0e938a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx @@ -9,25 +9,26 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { compareFilters, Query, TimeRange } from '@kbn/es-query'; import { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; -import { AlertConsumers } from '@kbn/rule-data-utils'; +import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils'; import { EuiContextMenuPanelDescriptor, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; +import { useAlertsDataView } from '@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view'; import { isQuickFiltersGroup, QuickFiltersMenuItem } from './quick_filters'; import { NO_INDEX_PATTERNS } from './constants'; import { SEARCH_BAR_PLACEHOLDER } from './translations'; import { AlertsSearchBarProps, QueryLanguageType } from './types'; -import { useAlertDataViews } from '../../hooks/use_alert_data_view'; import { TriggersAndActionsUiServices } from '../../..'; import { useRuleAADFields } from '../../hooks/use_rule_aad_fields'; import { useLoadRuleTypesQuery } from '../../hooks/use_load_rule_types_query'; const SA_ALERTS = { type: 'alerts', fields: {} } as SuggestionsAbstraction; +const EMPTY_FEATURE_IDS: ValidFeatureId[] = []; // TODO Share buildEsQuery to be used between AlertsSearchBar and AlertsStateTable component https://github.com/elastic/kibana/issues/144615 // Also TODO: Replace all references to this component with the one from alerts-ui-shared export function AlertsSearchBar({ appName, disableQueryLanguageSwitcher = false, - featureIds, + featureIds = EMPTY_FEATURE_IDS, ruleTypeId, query, filters, @@ -46,17 +47,32 @@ export function AlertsSearchBar({ ...props }: AlertsSearchBarProps) { const { + http, + dataViews: dataViewsService, + notifications: { toasts }, unifiedSearch: { ui: { SearchBar }, }, } = useKibana<TriggersAndActionsUiServices>().services; const [queryLanguage, setQueryLanguage] = useState<QueryLanguageType>('kuery'); - const { dataViews, loading } = useAlertDataViews(featureIds ?? []); + const { dataView } = useAlertsDataView({ + featureIds, + http, + dataViewsService, + toasts, + }); const { aadFields, loading: fieldsLoading } = useRuleAADFields(ruleTypeId); - const indexPatterns = - ruleTypeId && aadFields?.length ? [{ title: ruleTypeId, fields: aadFields }] : dataViews; + const indexPatterns = useMemo(() => { + if (ruleTypeId && aadFields?.length) { + return [{ title: ruleTypeId, fields: aadFields }]; + } + if (dataView) { + return [dataView]; + } + return null; + }, [aadFields, dataView, ruleTypeId]); const ruleType = useLoadRuleTypesQuery({ filteredRuleTypes: ruleTypeId !== undefined ? [ruleTypeId] : [], @@ -157,7 +173,7 @@ export function AlertsSearchBar({ appName={appName} disableQueryLanguageSwitcher={disableQueryLanguageSwitcher} // @ts-expect-error - DataView fields prop and SearchBar indexPatterns props are overly broad - indexPatterns={loading || fieldsLoading ? NO_INDEX_PATTERNS : indexPatterns} + indexPatterns={!indexPatterns || fieldsLoading ? NO_INDEX_PATTERNS : indexPatterns} placeholder={placeholder} query={{ query: query ?? '', language: queryLanguage }} filters={filters} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx index 09de049798a12..84b6be1a91560 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx @@ -40,7 +40,7 @@ import { getMaintenanceWindowMockMap } from './maintenance_windows/index.mock'; import { AlertTableConfigRegistry } from '../../alert_table_config_registry'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { fetchAlertsFields } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerts_fields'; -import { useSearchAlertsQuery } from '@kbn/alerts-ui-shared/src/common/hooks'; +import { useSearchAlertsQuery } from '@kbn/alerts-ui-shared/src/common/hooks/use_search_alerts_query'; jest.mock('@kbn/kibana-utils-plugin/public'); jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_search_alerts_query'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx index 2ce8ed3e28e6e..c2b147f2821e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.tsx @@ -30,7 +30,7 @@ import type { SortCombinations, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { QueryClientProvider } from '@tanstack/react-query'; -import { useSearchAlertsQuery } from '@kbn/alerts-ui-shared/src/common/hooks'; +import { useSearchAlertsQuery } from '@kbn/alerts-ui-shared/src/common/hooks/use_search_alerts_query'; import { DEFAULT_ALERTS_PAGE_SIZE } from '@kbn/alerts-ui-shared/src/common/constants'; import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; import deepEqual from 'fast-deep-equal'; @@ -296,7 +296,7 @@ const AlertsTableStateWithQueryProvider = memo( storage, id, defaultColumns: columnConfigByClient, - browserFields: propBrowserFields, + alertsFields: propBrowserFields, }); const [queryParams, setQueryParams] = useState({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx index 97d7ba7c2545e..ced47444f3a8f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.test.tsx @@ -116,8 +116,9 @@ describe('useColumns', () => { ]; beforeEach(() => { - setItemStorageMock.mockClear(); + jest.clearAllMocks(); storage = { current: new Storage(mockStorage) }; + queryClient.clear(); }); test('onColumnResize', async () => { @@ -152,6 +153,28 @@ describe('useColumns', () => { }); }); + test("does not fetch alerts fields if they're overridden through the alertsFields prop", () => { + const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); + const alertsFields = { + testField: { name: 'testField', type: 'string', searchable: true, aggregatable: true }, + }; + const { result } = renderHook<UseColumnsArgs, UseColumnsResp>( + () => + useColumns({ + alertsFields, + defaultColumns, + featureIds, + id, + storageAlertsTable: localStorageAlertsTable, + storage, + }), + { wrapper } + ); + + expect(mockFetchAlertsFields).not.toHaveBeenCalled(); + expect(result.current.browserFields).toEqual(alertsFields); + }); + describe('visibleColumns', () => { test('hide all columns with onChangeVisibleColumns', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); @@ -204,7 +227,7 @@ describe('useColumns', () => { expect(result.current.columns).toEqual(defaultColumns); }); - test('should populate visiblecolumns correctly', async () => { + test('should populate visibleColumns correctly', async () => { const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns); const { result } = renderHook<UseColumnsArgs, UseColumnsResp>( () => @@ -221,7 +244,7 @@ describe('useColumns', () => { expect(result.current.visibleColumns).toMatchObject(defaultColumns.map((col) => col.id)); }); - test('should change visiblecolumns if provided defaultColumns change', async () => { + test('should change visibleColumns if provided defaultColumns change', async () => { let localDefaultColumns = [...defaultColumns]; let localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(localDefaultColumns); const { result, rerender } = renderHook<UseColumnsArgs, UseColumnsResp>( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts index a78b92b6f7599..8846470a2d263 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts @@ -8,7 +8,7 @@ import { EuiDataGridColumn, EuiDataGridOnColumnResizeData } from '@elastic/eui'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { BrowserField, BrowserFields } from '@kbn/alerting-types'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { isEmpty } from 'lodash'; import { useFetchAlertsFieldsQuery } from '@kbn/alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query'; @@ -23,7 +23,11 @@ export interface UseColumnsArgs { storage: React.MutableRefObject<IStorageWrapper>; id: string; defaultColumns: EuiDataGridColumn[]; - browserFields?: BrowserFields; + /** + * If this is provided, it will be used to populate the columns instead of fetching the fields + * from the alerting APIs + */ + alertsFields?: BrowserFields; } export interface UseColumnsResp { @@ -137,8 +141,8 @@ const persist = ({ visibleColumns, }: { id: string; - storageAlertsTable: React.MutableRefObject<AlertsTableStorage>; - storage: React.MutableRefObject<IStorageWrapper>; + storageAlertsTable: MutableRefObject<AlertsTableStorage>; + storage: MutableRefObject<IStorageWrapper>; columns: EuiDataGridColumn[]; visibleColumns: string[]; }) => { @@ -156,25 +160,31 @@ export const useColumns = ({ storage, id, defaultColumns, - browserFields, + alertsFields, }: UseColumnsArgs): UseColumnsResp => { const { http } = useKibana().services; - const { isLoading: isBrowserFieldDataLoading, data } = useFetchAlertsFieldsQuery( + const fieldsQuery = useFetchAlertsFieldsQuery( { http, featureIds, }, { + enabled: !alertsFields, context: AlertsQueryContext, } ); + const selectedAlertsFields = useMemo<BrowserFields>( + () => alertsFields ?? fieldsQuery.data?.browserFields ?? {}, + [alertsFields, fieldsQuery.data?.browserFields] + ); + const [columns, setColumns] = useState<EuiDataGridColumn[]>(() => { let cols = storageAlertsTable.current.columns; // before restoring from storage, enrich the column data - if (browserFields && defaultColumns) { - cols = populateColumns(cols, browserFields, defaultColumns); + if (alertsFields && defaultColumns) { + cols = populateColumns(cols, alertsFields, defaultColumns); } else if (cols && cols.length === 0) { cols = defaultColumns; } @@ -206,7 +216,7 @@ export const useColumns = ({ ); useEffect(() => { - // if defaultColumns have changed, + // If defaultColumns have changed, // get the latest columns provided by client and if (didDefaultColumnChange && defaultColumnsRef.current) { defaultColumnsRef.current = defaultColumns; @@ -220,13 +230,19 @@ export const useColumns = ({ }, [didDefaultColumnChange, storageAlertsTable, defaultColumns, visibleColumns]); useEffect(() => { - if (isEmpty(data.browserFields) || isColumnsPopulated) return; + if (fieldsQuery.data) { + if (isEmpty(fieldsQuery.data.browserFields) || isColumnsPopulated) return; - const populatedColumns = populateColumns(columns, data.browserFields, defaultColumns); + const populatedColumns = populateColumns( + columns, + fieldsQuery.data.browserFields, + defaultColumns + ); - setColumnsPopulated(true); - setColumns(populatedColumns); - }, [data.browserFields, defaultColumns, isBrowserFieldDataLoading, isColumnsPopulated, columns]); + setColumnsPopulated(true); + setColumns(populatedColumns); + } + }, [defaultColumns, fieldsQuery.isLoading, isColumnsPopulated, columns, fieldsQuery.data]); const setColumnsAndSave = useCallback( (newColumns: EuiDataGridColumn[], newVisibleColumns: string[]) => { @@ -244,7 +260,7 @@ export const useColumns = ({ const onToggleColumn = useCallback( (columnId: string): void => { - const column = euiColumnFactory(columnId, data.browserFields, defaultColumns); + const column = euiColumnFactory(columnId, selectedAlertsFields, defaultColumns); const newColumns = toggleColumn({ column, @@ -260,19 +276,19 @@ export const useColumns = ({ setVisibleColumns(newVisibleColumns); setColumnsAndSave(newColumns, newVisibleColumns); }, - [data.browserFields, columns, defaultColumns, setColumnsAndSave, visibleColumns] + [selectedAlertsFields, columns, defaultColumns, setColumnsAndSave, visibleColumns] ); const onResetColumns = useCallback(() => { const populatedDefaultColumns = populateColumns( defaultColumns, - data.browserFields, + selectedAlertsFields, defaultColumns ); const newVisibleColumns = populatedDefaultColumns.map((pdc) => pdc.id); setVisibleColumns(newVisibleColumns); setColumnsAndSave(populatedDefaultColumns, newVisibleColumns); - }, [data.browserFields, defaultColumns, setColumnsAndSave]); + }, [selectedAlertsFields, defaultColumns, setColumnsAndSave]); const onColumnResize = useCallback( ({ columnId, width }: EuiDataGridOnColumnResizeData) => { @@ -294,8 +310,8 @@ export const useColumns = ({ () => ({ columns, visibleColumns, - isBrowserFieldDataLoading, - browserFields: browserFields ?? data.browserFields, + isBrowserFieldDataLoading: fieldsQuery.isLoading, + browserFields: selectedAlertsFields, onToggleColumn, onResetColumns, onChangeVisibleColumns: setColumnsByColumnIds, @@ -305,9 +321,8 @@ export const useColumns = ({ [ columns, visibleColumns, - isBrowserFieldDataLoading, - browserFields, - data.browserFields, + fieldsQuery.isLoading, + selectedAlertsFields, onToggleColumn, onResetColumns, setColumnsByColumnIds, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index b5d12cd6b9caa..615efb5ed74b6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -163,33 +163,43 @@ describe('rule_details', () => { paddingSize="s" panelRef={null} > + <p + className="euiCallOutHeader__title" + > + <EuiIcon + aria-hidden="true" + color="inherit" + css="unknown styles" + size="m" + type="error" + /> + Cannot run rule + </p> + <EuiSpacer + size="s" + /> <EuiText color="default" size="xs" > - <p> - <EuiIcon - color="danger" - type="warning" - /> -   - <b> - Cannot run rule - </b> - ,  + <EuiText + size="xs" + > test -   - <EuiLink - color="primary" - href="/app/management/stack/license_management" - target="_blank" - > - <MemoizedFormattedMessage - defaultMessage="Manage license" - id="xpack.triggersActionsUI.sections.ruleDetails.manageLicensePlanBannerLinkTitle" - /> - </EuiLink> - </p> + </EuiText> + <EuiSpacer + size="s" + /> + <EuiLink + color="primary" + href="/app/management/stack/license_management" + target="_blank" + > + <MemoizedFormattedMessage + defaultMessage="Manage license" + id="xpack.triggersActionsUI.sections.ruleDetails.manageLicensePlanBannerLinkTitle" + /> + </EuiLink> </EuiText> </EuiPanel> `); 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 a9e0be99d42ba..8b2ee15db87d1 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 @@ -465,24 +465,25 @@ export const RuleDetails: React.FunctionComponent<RuleDetailsProps> = ({ rule.executionStatus.error?.reason === RuleExecutionStatusErrorReasons.License ? ( <EuiFlexGroup> <EuiFlexItem> - <EuiCallOut color="danger" data-test-subj="ruleErrorBanner" size="s" iconType="rule"> - <p> - <EuiIcon color="danger" type="warning" /> -   - <b>{getRuleStatusErrorReasonText()}</b>,  - {rule.executionStatus.error?.message} -   - <EuiLink - href={`${http.basePath.get()}/app/management/stack/license_management`} - color="primary" - target="_blank" - > - <FormattedMessage - id="xpack.triggersActionsUI.sections.ruleDetails.manageLicensePlanBannerLinkTitle" - defaultMessage="Manage license" - /> - </EuiLink> - </p> + <EuiCallOut + color="danger" + data-test-subj="ruleErrorBanner" + size="s" + iconType="error" + title={getRuleStatusErrorReasonText()} + > + <EuiText size="xs">{rule.executionStatus.error?.message}</EuiText> + <EuiSpacer size="s" /> + <EuiLink + href={`${http.basePath.get()}/app/management/stack/license_management`} + color="primary" + target="_blank" + > + <FormattedMessage + id="xpack.triggersActionsUI.sections.ruleDetails.manageLicensePlanBannerLinkTitle" + defaultMessage="Manage license" + /> + </EuiLink> </EuiCallOut> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx index 409f3cf39400a..592518f33edd2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx @@ -270,7 +270,7 @@ export const RuleEdit = < <EuiCallOut size="s" color="danger" - iconType="rule" + iconType="error" data-test-subj="hasActionsDisabled" title={i18n.translate( 'xpack.triggersActionsUI.sections.ruleEdit.disabledActionsWarningTitle', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx index 2a82af3eba4c2..ee3ee18152e47 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_table.tsx @@ -623,7 +623,7 @@ export const RulesListTable = (props: RulesListTableProps) => { <EuiIconTip data-test-subj="ruleDurationWarning" anchorClassName="ruleDurationWarningIcon" - type="rule" + type="warning" color="warning" content={i18n.translate( 'xpack.triggersActionsUI.sections.rulesList.ruleTypeExcessDurationMessage', diff --git a/x-pack/scripts/synthetics_private_location.js b/x-pack/scripts/synthetics_private_location.js new file mode 100644 index 0000000000000..2352c8238cc40 --- /dev/null +++ b/x-pack/scripts/synthetics_private_location.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +require('../../src/setup_node_env'); +require('@kbn/synthetics-private-location').cli(); 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 90653108aa500..4c693cfaa1b8d 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 @@ -7,6 +7,7 @@ import { ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; import { AlertsFilter } from '@kbn/alerting-plugin/common/rule'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { Space, User } from '../types'; import { ObjectRemover } from './object_remover'; import { getUrlPrefix } from './space_test_utils'; @@ -15,7 +16,7 @@ import { getTestRuleData } from './get_test_rule_data'; export interface AlertUtilsOpts { user?: User; space: Space; - supertestWithoutAuth: any; + supertestWithoutAuth: SupertestWithoutAuthProviderType; indexRecordActionId?: string; objectRemover?: ObjectRemover; } @@ -57,7 +58,7 @@ export class AlertUtils { private referenceCounter = 1; private readonly user?: User; private readonly space: Space; - private readonly supertestWithoutAuth: any; + private readonly supertestWithoutAuth: SupertestWithoutAuthProviderType; private readonly indexRecordActionId?: string; private readonly objectRemover?: ObjectRemover; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts index e7461476a2996..8c11b69db03be 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { Agent as SuperTestAgent } from 'supertest'; import { chunk, omit } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { SuperuserAtSpace1, UserAtSpaceScenarios } from '../../../scenarios'; import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -17,7 +18,7 @@ const findTestUtils = ( describeType: 'internal' | 'public', objectRemover: ObjectRemover, supertest: SuperTestAgent, - supertestWithoutAuth: any + supertestWithoutAuth: SupertestWithoutAuthProviderType ) => { describe(describeType, () => { afterEach(async () => { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_with_post.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_with_post.ts index b407ee072a78b..f221b5869dd6b 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_with_post.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find_with_post.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { Agent as SuperTestAgent } from 'supertest'; import { chunk, omit } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { UserAtSpaceScenarios } from '../../../scenarios'; import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -17,7 +18,7 @@ const findTestUtils = ( describeType: 'internal' | 'public', objectRemover: ObjectRemover, supertest: SuperTestAgent, - supertestWithoutAuth: any + supertestWithoutAuth: SupertestWithoutAuthProviderType ) => { describe(describeType, () => { afterEach(async () => { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts index fa362249229e3..78c662d98a541 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import { Agent as SuperTestAgent } from 'supertest'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { SuperuserAtSpace1, UserAtSpaceScenarios } from '../../../scenarios'; import { getUrlPrefix, @@ -20,7 +21,7 @@ const getTestUtils = ( describeType: 'internal' | 'public', objectRemover: ObjectRemover, supertest: SuperTestAgent, - supertestWithoutAuth: any + supertestWithoutAuth: SupertestWithoutAuthProviderType ) => { describe(describeType, () => { afterEach(() => objectRemover.removeAll()); diff --git a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts index b1a9fe2effb49..1b7cd6924661c 100644 --- a/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts +++ b/x-pack/test/api_integration/apis/aiops/log_rate_analysis_full_analysis.ts @@ -76,7 +76,10 @@ export default ({ getService }: FtrProviderContext) => { const histogramActions = getHistogramActions(data); const histograms = histogramActions.flatMap((d) => d.payload); // for each significant term we should get a histogram - expect(histogramActions.length).to.be(significantItems.length); + expect(histogramActions.length).to.eql( + testData.expected.histogramActionsLength, + `Expected histogram actions length to be ${testData.expected.histogramActionsLength}, got ${histogramActions.length}` + ); // each histogram should have a length of 20 items. histograms.forEach((h, index) => { expect(h.histogram.length).to.eql( diff --git a/x-pack/test/api_integration/apis/aiops/test_data.ts b/x-pack/test/api_integration/apis/aiops/test_data.ts index c26aad813992a..37fe9ca625716 100644 --- a/x-pack/test/api_integration/apis/aiops/test_data.ts +++ b/x-pack/test/api_integration/apis/aiops/test_data.ts @@ -117,6 +117,7 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa }, ], groups: [], + histogramActionsLength: 1, histogramLength: 20, fieldCandidates: { isECS: false, @@ -147,6 +148,7 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa noIndexActionsLength: 3, significantItems: artificialLogSignificantTerms, groups: artificialLogsSignificantItemGroups, + histogramActionsLength: 1, histogramLength: 20, fieldCandidates: expectedArtificialLogsFieldCandidates, }, @@ -171,6 +173,7 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa noIndexActionsLength: 3, significantItems: topTerms, groups: topTermsGroups, + histogramActionsLength: 1, histogramLength: 20, fieldCandidates: expectedArtificialLogsFieldCandidates, }, @@ -195,6 +198,7 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa noIndexActionsLength: 3, significantItems: topTerms, groups: topTermsGroups, + histogramActionsLength: 1, histogramLength: 20, fieldCandidates: expectedArtificialLogsFieldCandidates, }, @@ -219,6 +223,7 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa noIndexActionsLength: 3, significantItems: [...artificialLogSignificantTerms, ...artificialLogSignificantLogPatterns], groups: artificialLogsSignificantItemGroupsTextfield, + histogramActionsLength: 2, histogramLength: 20, fieldCandidates: expectedArtificialLogsFieldCandidatesWithTextfield, }, @@ -243,6 +248,7 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa noIndexActionsLength: 3, significantItems: artificialLogSignificantTerms, groups: artificialLogsSignificantItemGroups, + histogramActionsLength: 1, histogramLength: 20, fieldCandidates: expectedArtificialLogsFieldCandidates, }, @@ -267,6 +273,7 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa noIndexActionsLength: 3, significantItems: [...artificialLogSignificantTerms, ...artificialLogSignificantLogPatterns], groups: artificialLogsSignificantItemGroupsTextfield, + histogramActionsLength: 2, histogramLength: 20, fieldCandidates: expectedArtificialLogsFieldCandidatesWithTextfield, }, @@ -291,7 +298,8 @@ export const getLogRateAnalysisTestData = <T extends ApiVersion>(): Array<TestDa noIndexActionsLength: 3, groups: frequentItemSetsLargeArraysGroups, significantItems: frequentItemSetsLargeArraysSignificantItems, - histogramLength: 1, + histogramActionsLength: 2, + histogramLength: 20, fieldCandidates: { isECS: false, keywordFieldCandidates: ['items'], diff --git a/x-pack/test/api_integration/apis/aiops/types.ts b/x-pack/test/api_integration/apis/aiops/types.ts index 8e9540fedbed2..615bad6bc71b5 100644 --- a/x-pack/test/api_integration/apis/aiops/types.ts +++ b/x-pack/test/api_integration/apis/aiops/types.ts @@ -24,6 +24,7 @@ export interface TestData<T extends ApiVersion> { noIndexActionsLength: number; significantItems: SignificantItem[]; groups: SignificantItemGroup[]; + histogramActionsLength: number; histogramLength: number; fieldCandidates: FetchFieldCandidatesResponse; }; diff --git a/x-pack/test/api_integration/apis/entity_manager/enablement.ts b/x-pack/test/api_integration/apis/entity_manager/enablement.ts index a84a293e36caf..0745a526c443d 100644 --- a/x-pack/test/api_integration/apis/entity_manager/enablement.ts +++ b/x-pack/test/api_integration/apis/entity_manager/enablement.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { ERROR_USER_NOT_AUTHORIZED } from '@kbn/entityManager-plugin/common/errors'; import { builtInDefinitions } from '@kbn/entityManager-plugin/server/lib/entities/built_in'; import { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server/lib/entities/types'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -18,13 +17,14 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const enablementRequest = - (method: 'get' | 'put' | 'delete') => async (auth: Auth, query?: { [key: string]: any }) => { + (method: 'get' | 'put' | 'delete') => + async (auth: Auth, expectedCode: number, query: { [key: string]: any } = {}) => { const response = await supertest[method]('/internal/entities/managed/enablement') .auth(auth.username, auth.password) .query(query) .set('kbn-xsrf', 'xxx') .send() - .expect(200); + .expect(expectedCode); return response.body; }; @@ -45,7 +45,7 @@ export default function ({ getService }: FtrProviderContext) { describe('with authorized user', () => { it('should enable and disable entity discovery', async () => { - const enableResponse = await enableEntityDiscovery(authorizedUser); + const enableResponse = await enableEntityDiscovery(authorizedUser, 200); expect(enableResponse.success).to.eql(true, "authorized user can't enable EEM"); let definitionsResponse = await getInstalledDefinitions(supertest, authorizedUser); @@ -64,19 +64,21 @@ export default function ({ getService }: FtrProviderContext) { }) ).to.eql(true, 'all builtin definitions are not installed/running'); - let stateResponse = await entityDiscoveryState(authorizedUser); + let stateResponse = await entityDiscoveryState(authorizedUser, 200); expect(stateResponse.enabled).to.eql( true, `EEM is not enabled; response: ${JSON.stringify(stateResponse)}` ); - const disableResponse = await disableEntityDiscovery(authorizedUser, { deleteData: false }); + const disableResponse = await disableEntityDiscovery(authorizedUser, 200, { + deleteData: false, + }); expect(disableResponse.success).to.eql( true, `authorized user failed to disable EEM; response: ${JSON.stringify(disableResponse)}` ); - stateResponse = await entityDiscoveryState(authorizedUser); + stateResponse = await entityDiscoveryState(authorizedUser, 200); expect(stateResponse.enabled).to.eql(false, 'EEM is not disabled'); definitionsResponse = await getInstalledDefinitions(supertest, authorizedUser); @@ -86,11 +88,9 @@ export default function ({ getService }: FtrProviderContext) { describe('with unauthorized user', () => { it('should fail to enable entity discovery', async () => { - const enableResponse = await enableEntityDiscovery(unauthorizedUser); - expect(enableResponse.success).to.eql(false, 'unauthorized user can enable EEM'); - expect(enableResponse.reason).to.eql(ERROR_USER_NOT_AUTHORIZED); + await enableEntityDiscovery(unauthorizedUser, 403); - const stateResponse = await entityDiscoveryState(unauthorizedUser); + const stateResponse = await entityDiscoveryState(unauthorizedUser, 200); expect(stateResponse.enabled).to.eql(false, 'EEM is enabled'); const definitionsResponse = await getInstalledDefinitions(supertest, unauthorizedUser); @@ -98,14 +98,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should fail to disable entity discovery', async () => { - const enableResponse = await enableEntityDiscovery(authorizedUser); + const enableResponse = await enableEntityDiscovery(authorizedUser, 200); expect(enableResponse.success).to.eql(true, "authorized user can't enable EEM"); - let disableResponse = await disableEntityDiscovery(unauthorizedUser); - expect(disableResponse.success).to.eql(false, 'unauthorized user can disable EEM'); - expect(disableResponse.reason).to.eql(ERROR_USER_NOT_AUTHORIZED); + let disableResponse = await disableEntityDiscovery(unauthorizedUser, 403); - disableResponse = await disableEntityDiscovery(authorizedUser); + disableResponse = await disableEntityDiscovery(authorizedUser, 200); expect(disableResponse.success).to.eql(true, "authorized user can't disable EEM"); }); }); diff --git a/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts b/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts index 61570f83aed46..45630dac148d8 100644 --- a/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts +++ b/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; import { CSV_QUOTE_VALUES_SETTING } from '@kbn/share-plugin/common/constants'; import { ELASTIC_HTTP_VERSION_HEADER, @@ -15,7 +14,7 @@ import { import { FtrProviderContext } from '../../../ftr_provider_context'; export default function featureControlsTests({ getService }: FtrProviderContext) { - const supertest: SuperTest<any> = getService('supertestWithoutAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const security = getService('security'); const spaces = getService('spaces'); const deployment = getService('deployment'); @@ -55,7 +54,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) async function saveAdvancedSetting(username: string, password: string, spaceId?: string) { const basePath = spaceId ? `/s/${spaceId}` : ''; - return await supertest + return await supertestWithoutAuth .post(`${basePath}/internal/kibana/settings`) .auth(username, password) .set('kbn-xsrf', 'foo') @@ -67,7 +66,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) async function saveTelemetrySetting(username: string, password: string, spaceId?: string) { const basePath = spaceId ? `/s/${spaceId}` : ''; - return await supertest + return await supertestWithoutAuth .post(`${basePath}/internal/telemetry/optIn`) .auth(username, password) .set('kbn-xsrf', 'foo') diff --git a/x-pack/test/api_integration/apis/management/index_management/inference_endpoints.ts b/x-pack/test/api_integration/apis/management/index_management/inference_endpoints.ts index 9dab57c649f84..be28c5b43f7f2 100644 --- a/x-pack/test/api_integration/apis/management/index_management/inference_endpoints.ts +++ b/x-pack/test/api_integration/apis/management/index_management/inference_endpoints.ts @@ -56,7 +56,7 @@ export default function ({ getService }: FtrProviderContext) { inferenceEndpoints.some( (endpoint: InferenceAPIConfigResponse) => endpoint.model_id === inferenceId ) - ).to.be(true); + ).to.eql(true, `${inferenceId} not found in the GET _inference/_all response`); }); it('can delete inference endpoint', async () => { log.debug(`Deleting inference endpoint`); diff --git a/x-pack/test/api_integration/apis/metrics_ui/infra.ts b/x-pack/test/api_integration/apis/metrics_ui/infra.ts index f70cd61dc7635..641f61c9126fe 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/infra.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/infra.ts @@ -30,6 +30,9 @@ export default function ({ getService }: FtrProviderContext) { { type: 'cpu', }, + { + type: 'cpuTotal', + }, { type: 'diskSpaceUsage', }, @@ -95,6 +98,7 @@ export default function ({ getService }: FtrProviderContext) { ], metrics: [ { name: 'cpu', value: 0.44708333333333333 }, + { name: 'cpuTotal', value: 0 }, { name: 'diskSpaceUsage', value: 0 }, { name: 'memory', value: 0.4563333333333333 }, { name: 'memoryFree', value: 8573890560 }, @@ -155,7 +159,7 @@ export default function ({ getService }: FtrProviderContext) { ...basePayload, metrics: [ { - type: 'cpu', + type: 'cpuTotal', }, ], query: { bool: { filter: [{ term: { 'host.os.name': 'CentOS Linux' } }] } }, @@ -164,8 +168,8 @@ export default function ({ getService }: FtrProviderContext) { const names = (response.body as GetInfraMetricsResponsePayload).nodes.map((p) => p.name); expect(names).eql([ - 'gke-observability-8--observability-8--bc1afd95-ngmh', 'gke-observability-8--observability-8--bc1afd95-f0zc', + 'gke-observability-8--observability-8--bc1afd95-ngmh', 'gke-observability-8--observability-8--bc1afd95-nhhw', ]); }); @@ -175,7 +179,7 @@ export default function ({ getService }: FtrProviderContext) { ...basePayload, metrics: [ { - type: 'cpu', + type: 'cpuTotal', }, ], query: { bool: { filter: [{ term: { 'host.os.name': 'Ubuntu' } }] } }, @@ -192,7 +196,7 @@ export default function ({ getService }: FtrProviderContext) { ...basePayload, metrics: [ { - type: 'cpu', + type: 'cpuTotal', }, ], query: { @@ -207,9 +211,8 @@ export default function ({ getService }: FtrProviderContext) { const names = (response.body as GetInfraMetricsResponsePayload).nodes.map((p) => p.name); expect(names).eql([ - 'gke-observability-8--observability-8--bc1afd95-ngmh', 'gke-observability-8--observability-8--bc1afd95-f0zc', - , + 'gke-observability-8--observability-8--bc1afd95-ngmh', ]); }); @@ -246,7 +249,7 @@ export default function ({ getService }: FtrProviderContext) { const response = await makeRequest({ invalidBody, expectedHTTPCode: 400 }); expect(normalizeNewLine(response.body.message)).to.be( - '[request body]: Failed to validate: in metrics/0/type: "any" does not match expected type "cpu" | "normalizedLoad1m" | "diskSpaceUsage" | "memory" | "memoryFree" | "rx" | "tx"' + '[request body]: Failed to validate: in metrics/0/type: "any" does not match expected type "cpu" | "cpuTotal" | "normalizedLoad1m" | "diskSpaceUsage" | "memory" | "memoryFree" | "rx" | "tx" | "rxV2" | "txV2"' ); }); diff --git a/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts b/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts index 7954a7b31e900..4e229c133b4fd 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/model_downloads.ts @@ -51,6 +51,7 @@ export default ({ getService }: FtrProviderContext) => { { modelName: 'elser', hidden: true, + supported: false, version: 1, config: { input: { @@ -72,6 +73,7 @@ export default ({ getService }: FtrProviderContext) => { description: 'Elastic Learned Sparse EncodeR v2', type: ['elastic', 'pytorch', 'text_expansion'], model_id: '.elser_model_2', + supported: true, ...(isIntelBased ? { default: true } : { recommended: true }), }, { @@ -87,7 +89,7 @@ export default ({ getService }: FtrProviderContext) => { description: 'Elastic Learned Sparse EncodeR v2, optimized for linux-x86_64', type: ['elastic', 'pytorch', 'text_expansion'], model_id: '.elser_model_2_linux-x86_64', - ...(isIntelBased ? { recommended: true } : {}), + ...(isIntelBased ? { recommended: true, supported: true } : { supported: false }), }, { modelName: 'e5', @@ -102,6 +104,7 @@ export default ({ getService }: FtrProviderContext) => { licenseUrl: 'https://huggingface.co/elastic/multilingual-e5-small', type: ['pytorch', 'text_embedding'], model_id: '.multilingual-e5-small', + supported: true, ...(isIntelBased ? { default: true } : { recommended: true }), }, { @@ -120,7 +123,7 @@ export default ({ getService }: FtrProviderContext) => { licenseUrl: 'https://huggingface.co/elastic/multilingual-e5-small_linux-x86_64', type: ['pytorch', 'text_embedding'], model_id: '.multilingual-e5-small_linux-x86_64', - ...(isIntelBased ? { recommended: true } : {}), + ...(isIntelBased ? { recommended: true, supported: true } : { supported: false }), }, ]); }); diff --git a/x-pack/test/api_integration/apis/ml/trained_models/put_model.ts b/x-pack/test/api_integration/apis/ml/trained_models/put_model.ts index 20df947f2b8c8..9893e19c3c8c4 100644 --- a/x-pack/test/api_integration/apis/ml/trained_models/put_model.ts +++ b/x-pack/test/api_integration/apis/ml/trained_models/put_model.ts @@ -15,7 +15,8 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); const ml = getService('ml'); - describe('PUT trained_models', () => { + // FLAKY: https://github.com/elastic/kibana/issues/189637 + describe.skip('PUT trained_models', () => { before(async () => { await ml.api.initSavedObjects(); await ml.testResources.setKibanaTimeZoneToUTC(); diff --git a/x-pack/test/api_integration/apis/painless_lab/config.ts b/x-pack/test/api_integration/apis/painless_lab/config.ts deleted file mode 100644 index 5f335f116fefe..0000000000000 --- a/x-pack/test/api_integration/apis/painless_lab/config.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrConfigProviderContext } from '@kbn/test'; - -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts')); - - return { - ...baseIntegrationTestsConfig.getAll(), - testFiles: [require.resolve('.')], - }; -} diff --git a/x-pack/test/api_integration/apis/painless_lab/index.ts b/x-pack/test/api_integration/apis/painless_lab/index.ts deleted file mode 100644 index 63744b77312d7..0000000000000 --- a/x-pack/test/api_integration/apis/painless_lab/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; 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('Painless Lab', () => { - loadTestFile(require.resolve('./painless_lab')); - }); -} diff --git a/x-pack/test/api_integration/apis/painless_lab/painless_lab.ts b/x-pack/test/api_integration/apis/painless_lab/painless_lab.ts deleted file mode 100644 index b594220634d1d..0000000000000 --- a/x-pack/test/api_integration/apis/painless_lab/painless_lab.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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -const API_BASE_PATH = '/api/painless_lab'; - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - describe('Painless Lab', function () { - describe('Execute', () => { - it('should execute a valid painless script', async () => { - const script = - '"{\\n \\"script\\": {\\n \\"source\\": \\"return true;\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; - - const { body } = await supertest - .post(`${API_BASE_PATH}/execute`) - .set('kbn-xsrf', 'xxx') - .set('Content-Type', 'application/json;charset=UTF-8') - .send(script) - .expect(200); - - expect(body).to.eql({ - result: 'true', - }); - }); - - it('should return error response for invalid painless script', async () => { - const invalidScript = - '"{\\n \\"script\\": {\\n \\"source\\": \\"foobar\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; - - const { body } = await supertest - .post(`${API_BASE_PATH}/execute`) - .set('kbn-xsrf', 'xxx') - .set('Content-Type', 'application/json;charset=UTF-8') - .send(invalidScript) - .expect(200); - - expect(body.error).to.not.be(undefined); - expect(body.error.reason).to.eql('compile error'); - }); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/security/index.ts b/x-pack/test/api_integration/apis/security/index.ts index 0e3f85d22a4ad..949862992a804 100644 --- a/x-pack/test/api_integration/apis/security/index.ts +++ b/x-pack/test/api_integration/apis/security/index.ts @@ -21,5 +21,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./roles')); loadTestFile(require.resolve('./users')); loadTestFile(require.resolve('./privileges')); + loadTestFile(require.resolve('./roles_bulk')); }); } diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index d78b33631c6e4..4cbaeca03373c 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -68,6 +68,7 @@ export default function ({ getService }: FtrProviderContext) { 'process_operations_all', 'file_operations_all', 'execute_operations_all', + 'scan_operations_all', ], uptime: ['all', 'read', 'minimal_all', 'minimal_read', 'elastic_managed_locations_enabled'], securitySolutionAssistant: [ 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 a4e21fed4d92d..5fb3c715805f1 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -145,6 +145,7 @@ export default function ({ getService }: FtrProviderContext) { 'trusted_applications_read', 'file_operations_all', 'execute_operations_all', + 'scan_operations_all', ], uptime: [ 'all', diff --git a/x-pack/test/api_integration/apis/security/roles_bulk.ts b/x-pack/test/api_integration/apis/security/roles_bulk.ts new file mode 100644 index 0000000000000..52c6f9f21ab29 --- /dev/null +++ b/x-pack/test/api_integration/apis/security/roles_bulk.ts @@ -0,0 +1,425 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 }: FtrProviderContext) { + const es = getService('es'); + const supertest = getService('supertest'); + const config = getService('config'); + const basic = config.get('esTestCluster.license') === 'basic'; + + describe('Roles Bulk', () => { + after(async () => { + await supertest.delete('/api/security/role/bulk_role_1').set('kbn-xsrf', 'xxx').expect(204); + await supertest.delete('/api/security/role/bulk_role_2').set('kbn-xsrf', 'xxx').expect(204); + await supertest + .delete('/api/security/role/bulk_role_valid') + .set('kbn-xsrf', 'xxx') + .expect(204); + await supertest + .delete('/api/security/role/bulk_role_with_privilege_1') + .set('kbn-xsrf', 'xxx') + .expect(204); + await supertest + .delete('/api/security/role/bulk_role_with_privilege_2') + .set('kbn-xsrf', 'xxx') + .expect(204); + await supertest + .delete('/api/security/role/bulk_role_to_update_1') + .set('kbn-xsrf', 'xxx') + .expect(204); + await supertest + .delete('/api/security/role/bulk_role_to_update_2') + .set('kbn-xsrf', 'xxx') + .expect(204); + + const emptyRoles = await es.security.getRole( + { name: 'bulk_role_1,bulk_role_2' }, + { ignore: [404] } + ); + expect(emptyRoles).to.eql({}); + const rolesWithPrivileges = await es.security.getRole( + { name: 'bulk_role_with_privilege_1,bulk_role_with_privilege_2,bulk_role_valid' }, + { ignore: [404] } + ); + expect(rolesWithPrivileges).to.eql({}); + + const rolesToUpdate = await es.security.getRole( + { name: 'bulk_role_to_update_1,bulk_role_to_update_2' }, + { ignore: [404] } + ); + expect(rolesToUpdate).to.eql({}); + }); + + describe('Create Roles', () => { + it('should allow us to create empty roles', async () => { + await supertest + .post('/api/security/roles') + .set('kbn-xsrf', 'xxx') + .send({ + roles: { + bulk_role_1: {}, + bulk_role_2: {}, + }, + }) + .expect(200) + .then((response) => { + expect(response.body).to.eql({ created: ['bulk_role_1', 'bulk_role_2'] }); + }); + }); + + it('should create roles with kibana and elasticsearch privileges', async () => { + await supertest + .post('/api/security/roles') + .set('kbn-xsrf', 'xxx') + .send({ + roles: { + bulk_role_with_privilege_1: { + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + run_as: ['watcher_user'], + }, + kibana: [ + { + base: ['read'], + }, + { + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'], + }, + spaces: ['marketing', 'sales'], + }, + ], + }, + bulk_role_with_privilege_2: { + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + }, + ], + run_as: ['watcher_user'], + }, + kibana: [ + { + base: ['read'], + }, + { + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'], + }, + spaces: ['marketing', 'sales'], + }, + ], + }, + }, + }) + .expect(200); + + const role = await es.security.getRole({ name: 'bulk_role_with_privilege_1' }); + expect(role).to.eql({ + bulk_role_with_privilege_1: { + metadata: {}, + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], + resources: ['space:marketing', 'space:sales'], + }, + ], + run_as: ['watcher_user'], + transient_metadata: { + enabled: true, + }, + }, + }); + }); + + it(`should ${basic ? 'not' : ''} create a role with kibana and FLS/DLS elasticsearch + privileges on ${basic ? 'basic' : 'trial'} licenses`, async () => { + await supertest + .post('/api/security/roles') + .set('kbn-xsrf', 'xxx') + .send({ + roles: { + role_with_privileges_dls_fls: { + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + field_security: { + grant: ['*'], + except: ['geo.*'], + }, + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + query: `{ "match": { "geo.src": "CN" } }`, + }, + ], + run_as: ['watcher_user'], + }, + }, + }, + }) + .expect(200) + .then((response) => { + const { errors, created } = response.body; + if (basic) { + expect(created).to.be(undefined); + expect(errors).to.have.property('role_with_privileges_dls_fls'); + expect(errors.role_with_privileges_dls_fls.type).to.be('security_exception'); + expect(errors.role_with_privileges_dls_fls.reason).to.contain( + `current license is non-compliant for [field and document level security]` + ); + } else { + expect(created).to.eql(['role_with_privileges_dls_fls']); + expect(errors).to.be(undefined); + } + }); + }); + + it('should return noop if roles exist and did not change', async () => { + await supertest + .post('/api/security/roles') + .set('kbn-xsrf', 'xxx') + .send({ + roles: { + bulk_role_1: {}, + bulk_role_2: {}, + }, + }) + .expect(200) + .then((response) => { + expect(response.body).to.eql({ noop: ['bulk_role_1', 'bulk_role_2'] }); + }); + }); + + it('should return validation errors for roles that failed', async () => { + await supertest + .post('/api/security/roles') + .set('kbn-xsrf', 'xxx') + .send({ + roles: { + bulk_role_es_invalid: { + elasticsearch: { + cluster: ['bla'], + }, + }, + bulk_role_kibana_invalid: { + kibana: [ + { + spaces: ['bar-space'], + base: [], + feature: { + fleetv2: ['all', 'read'], + }, + }, + ], + }, + bulk_role_valid: { + elasticsearch: { + cluster: ['all'], + }, + }, + }, + }) + .expect(200) + .then((response) => { + const { created, errors } = response.body; + expect(created).to.eql(['bulk_role_valid']); + expect(errors).to.have.property('bulk_role_es_invalid'); + expect(errors).to.have.property('bulk_role_kibana_invalid'); + }); + }); + }); + + describe('Update Roles', () => { + it('should update roles with elasticsearch, kibana and other applications privileges', async () => { + await es.security.putRole({ + name: 'bulk_role_to_update_1', + body: { + cluster: ['monitor'], + indices: [ + { + names: ['beats-*'], + privileges: ['write'], + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read'], + resources: ['*'], + }, + { + application: 'logstash-default', + privileges: ['logstash-privilege'], + resources: ['*'], + }, + ], + run_as: ['reporting_user'], + metadata: { + bar: 'old-metadata', + }, + }, + }); + await es.security.putRole({ name: 'bulk_role_to_update_2', body: {} }); + + await supertest + .post('/api/security/roles') + .set('kbn-xsrf', 'xxx') + .send({ + roles: { + bulk_role_to_update_1: { + metadata: { + foo: 'test-metadata', + }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: true, + }, + ], + run_as: ['watcher_user'], + }, + kibana: [ + { + feature: { + dashboard: ['read'], + dev_tools: ['all'], + }, + spaces: ['*'], + }, + { + base: ['all'], + spaces: ['marketing', 'sales'], + }, + ], + }, + bulk_role_to_update_2: { + kibana: [ + { + feature: { + dashboard: ['read'], + dev_tools: ['all'], + }, + spaces: ['*'], + }, + { + base: ['all'], + spaces: ['observability', 'sales'], + }, + ], + }, + }, + }) + .expect(200) + .then((response) => { + expect(response.body).to.eql({ + updated: ['bulk_role_to_update_1', 'bulk_role_to_update_2'], + }); + }); + + const role = await es.security.getRole({ + name: 'bulk_role_to_update_1,bulk_role_to_update_2', + }); + + expect(role).to.eql({ + bulk_role_to_update_1: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: true, + }, + ], + metadata: { + bar: 'old-metadata', + foo: 'test-metadata', + }, + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_dev_tools.all'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['space_all'], + resources: ['space:marketing', 'space:sales'], + }, + { + application: 'logstash-default', + privileges: ['logstash-privilege'], + resources: ['*'], + }, + ], + run_as: ['watcher_user'], + transient_metadata: { + enabled: true, + }, + }, + bulk_role_to_update_2: { + cluster: [], + indices: [], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['feature_dashboard.read', 'feature_dev_tools.all'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['space_all'], + resources: ['space:observability', 'space:sales'], + }, + ], + run_as: [], + metadata: {}, + transient_metadata: { + enabled: true, + }, + }, + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/security/security_basic.ts b/x-pack/test/api_integration/apis/security/security_basic.ts index 709f738b4e9bb..c0815f0aefe69 100644 --- a/x-pack/test/api_integration/apis/security/security_basic.ts +++ b/x-pack/test/api_integration/apis/security/security_basic.ts @@ -21,5 +21,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./roles')); loadTestFile(require.resolve('./users')); loadTestFile(require.resolve('./privileges_basic')); + loadTestFile(require.resolve('./roles_bulk')); }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/README.md b/x-pack/test/api_integration/deployment_agnostic/README.md new file mode 100644 index 0000000000000..eff834b7b1db9 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/README.md @@ -0,0 +1,183 @@ +# Deployment-Agnostic Tests Guidelines + +## Definition +A deployment-agnostic API integration test is a test suite that fulfills the following criteria: + +**Functionality**: It tests Kibana APIs that are logically identical in both stateful and serverless environments for the same roles. + +**Design**: The test design is clean and does not require additional logic to execute in either stateful or serverless environments. + +## Tests Design Requirements +A deployment-agnostic test is contained within a single test file and always utilizes the [DeploymentAgnosticFtrProviderContext](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts) to load compatible FTR services. A compatible FTR service must support: + +- **Serverless**: Both local environments and MKI (Managed Kubernetes Infrastructure). +- **Stateful**: Both local environments and Cloud deployments. + +To achieve this, services cannot use `supertest`, which employs an operator user for serverless and a system index superuser for stateful setups. Instead, services should use a combination of `supertestWithoutAuth` and `samlAuth` to generate an API key for user roles and make API calls. For example, see the [data_view_api.ts](https://github.com/elastic/kibana/blob/main/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts) service. + +### How It Works +Most existing stateful tests use basic authentication for API testing. In contrast, serverless tests use SAML authentication with project-specific role mapping. + +Since both Elastic Cloud (ESS) and Serverless rely on SAML authentication by default, and stateful deployments also support SAML, *deployment-agnostic tests configure Elasticsearch and Kibana with SAML authentication to use the same authentication approach in all cases*. For roles, stateful deployments define 'viewer', 'editor', and 'admin' roles with serverless-alike permissions. + +### When to Create Separate Tests +While the deployment-agnostic testing approach is beneficial, it should not compromise the quality and simplicity of the tests. Here are some scenarios where separate test files are recommended: + +- **Role-Specific Logic**: If API access or logic depends on roles that differ across deployments. +- **Environment Constraints**: If a test can only run locally and not on MKI or Cloud deployments. +- **Complex Logic**: If the test logic requires splitting across multiple locations. + +## File Structure +We recommend following this structure to simplify maintenance and allow other teams to reuse code (e.g., FTR services) created by different teams: + +``` +x-pack/test/<my_own_api_integration_folder> +├─ deployment_agnostic +│ ├─ apis +│ │ ├─ <api_1> +│ │ │ ├─ <test_1_1> +│ │ │ ├─ <test_1_2> +│ │ ├─ <api_2> +│ │ │ ├─ <test_2_1> +│ │ │ ├─ <test_2_2> +│ ├─ services +│ │ ├─ index.ts // only services from 'x-pack/test/api_integration/deployment_agnostic/services' +│ │ ├─ <deployment_agnostic_service_1>.ts +│ │ ├─ <deployment_agnostic_service_2>.ts +│ ├─ ftr_provider_context.d.ts // with types of services from './services' +├─ stateful.index.ts +├─ stateful.config.ts +├─ <serverless_project>.index.ts // e.g., oblt.index.ts +├─ <serverless_project>.serverless.config.ts // e.g., oblt.serverless.config.ts +``` + +## Step-by-Step Guide +1. Define Deployment-Agnostic Services + +Under `x-pack/test/<my_own_api_integration_folder>/deployment_agnostic/services`, create `index.ts` and load default services like `samlAuth` and `superuserWithoutAuth`: + +```ts +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { services as deploymentAgnosticServices } from './../../api_integration/deployment_agnostic/services'; + +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; + +export const services = { + ...deploymentAgnosticServices, + // create a new deployment-agnostic service and load here +}; +``` + +We suggest adding new services to `x-pack/test/api_integration/deployment_agnostic/services` so other teams can benefit from them. + +2. Create `DeploymentAgnosticFtrProviderContext` with Services Defined in Step 2 + +Create `ftr_provider_context.d.ts` and export `DeploymentAgnosticFtrProviderContext`: +```ts +import { GenericFtrProviderContext } from '@kbn/test'; +import { services } from './services'; + +export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext<typeof services, {}>; +``` + +3. Add Tests + +Add test files to `x-pack/test/<my_own_api_integration_folder>/deployment_agnostic/apis/<my_api>`: + +test example +```ts +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('compression', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + describe('against an application page', () => { + it(`uses compression when there isn't a referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + }); + }); +} +``` +Load all test files in `index.ts` under the same folder. + +4. Add Tests Entry File and FTR Config File for **Stateful** Deployment + +Create `stateful.index.ts` tests entry file and load tests: + +```ts +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('apis', () => { + loadTestFile(require.resolve('./apis/<my_api>')); + }); +} +``` + +Create `stateful.config.ts` and link tests entry file: + +```ts +import { createStatefulTestConfig } from './../../api_integration/deployment_agnostic/default_configs/stateful.config.base'; + +export default createStatefulTestConfig({ + testFiles: [require.resolve('./stateful.index.ts')], + junit: { + reportName: 'Stateful - Deployment-agnostic API Integration Tests', + }, + // extra arguments + esServerArgs: [], + kbnServerArgs: [], +}); +``` +5. Add Tests Entry File and FTR Config File for Specific **Serverless** Project + +Example for Observability project: + +oblt.index.ts +```ts +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Observability - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/<my_api>')); + }); +} +``` + +oblt.serverless.config.ts +```ts +import { createServerlessTestConfig } from './../../api_integration/deployment_agnostic/default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'oblt', + testFiles: [require.resolve('./oblt.index.ts')], + junit: { + reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests', + }, +}); +``` + +ES and Kibana project-specific arguments are defined and loaded from `serverless.config.base`. These arguments are copied from the Elasticsearch and Kibana controller repositories. + +Note: The FTR (Functional Test Runner) does not have the capability to provision custom ES/Kibana server arguments into the serverless project. Any custom arguments listed explicitly in this config file will apply **only to a local environment**. + +6. Add FTR Configs Path to FTR Manifest Files Located in `.buildkite/` diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/console/index.ts new file mode 100644 index 0000000000000..4558f6818542f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/console/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 { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('console', () => { + loadTestFile(require.resolve('./spec_definitions')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts b/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.ts new file mode 100644 index 0000000000000..a2c8115e4ea0d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/console/spec_definitions.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 { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('GET /api/console/api_server', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + it('returns autocomplete definitions', async () => { + const { body } = await supertestWithoutAuth + .get('/api/console/api_server') + .set(roleAuthc.apiKeyHeader) + .set(internalHeaders) + .set('kbn-xsrf', 'true') + .expect(200); + expect(body.es).to.be.ok(); + const { + es: { name, globals, endpoints }, + } = body; + expect(name).to.be.ok(); + expect(Object.keys(globals).length).to.be.above(0); + expect(Object.keys(endpoints).length).to.be.above(0); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts b/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.ts new file mode 100644 index 0000000000000..d1aa1cfb45153 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/core/compression.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 { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('compression', () => { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + describe('against an application page', () => { + it(`uses compression when there isn't a referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + + it(`uses compression when there is a whitelisted referer`, async () => { + const response = await supertestWithoutAuth + .get('/app/kibana') + .set('accept-encoding', 'gzip') + .set(internalHeaders) + .set('referer', 'https://some-host.com') + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/core/index.ts new file mode 100644 index 0000000000000..93e40d3d5b914 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/core/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 { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('core', () => { + loadTestFile(require.resolve('./compression')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/index.ts new file mode 100644 index 0000000000000..ff59037fc1f06 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/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 { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Painless Lab', () => { + loadTestFile(require.resolve('./painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.ts new file mode 100644 index 0000000000000..7e7047ac9cb57 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/painless_lab/painless_lab.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 expect from '@kbn/expect'; +import { RoleCredentials, InternalRequestHeader } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../../ftr_provider_context'; + +const API_BASE_PATH = '/api/painless_lab'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const samlAuth = getService('samlAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + let internalHeaders: InternalRequestHeader; + + describe('Painless Lab Routes', function () { + before(async () => { + roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin'); + internalHeaders = samlAuth.getInternalRequestHeader(); + }); + after(async () => { + await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + describe('Execute', () => { + it('should execute a valid painless script', async () => { + const script = + '"{\\n \\"script\\": {\\n \\"source\\": \\"return true;\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; + + const { body } = await supertestWithoutAuth + .post(`${API_BASE_PATH}/execute`) + .set(internalHeaders) + .set(roleAuthc.apiKeyHeader) + .set('Content-Type', 'application/json;charset=UTF-8') + .send(script) + .expect(200); + + expect(body).to.eql({ + result: 'true', + }); + }); + + it('should return error response for invalid painless script', async () => { + const invalidScript = + '"{\\n \\"script\\": {\\n \\"source\\": \\"foobar\\",\\n \\"params\\": {\\n \\"string_parameter\\": \\"string value\\",\\n \\"number_parameter\\": 1.5,\\n \\"boolean_parameter\\": true\\n}\\n }\\n}"'; + + const { body } = await supertestWithoutAuth + .post(`${API_BASE_PATH}/execute`) + .set(internalHeaders) + .set('Content-Type', 'application/json;charset=UTF-8') + .set(roleAuthc.apiKeyHeader) + .send(invalidScript) + .expect(200); + + expect(body.error).to.not.be(undefined); + expect(body.error.reason).to.eql('compile error'); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts new file mode 100644 index 0000000000000..606a71835b317 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/serverless.config.base.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrConfigProviderContext, Config } from '@kbn/test'; + +import { ServerlessProjectType } from '@kbn/es'; +import { services } from '../services'; + +interface CreateTestConfigOptions { + serverlessProject: ServerlessProjectType; + esServerArgs?: string[]; + kbnServerArgs?: string[]; + testFiles: string[]; + junit: { reportName: string }; + suiteTags?: { include?: string[]; exclude?: string[] }; +} + +// include settings from elasticsearch controller +// https://github.com/elastic/elasticsearch-controller/blob/main/helm/values.yaml +const esServerArgsFromController = { + es: [], + oblt: [ + 'xpack.apm_data.enabled=true', + // for ML, data frame analytics are not part of this project type + 'xpack.ml.dfa.enabled=false', + ], + security: [ + 'xpack.security.authc.api_key.cache.max_keys=70000', + 'data_streams.lifecycle.retention.factory_default=365d', + 'data_streams.lifecycle.retention.factory_max=365d', + ], +}; + +// include settings from kibana controller +// https://github.com/elastic/kibana-controller/blob/main/internal/controllers/kibana/config/config_settings.go +const kbnServerArgsFromController = { + es: [ + // useful for testing (also enabled in MKI QA) + '--coreApp.allowDynamicConfigOverrides=true', + ], + oblt: [ + '--coreApp.allowDynamicConfigOverrides=true', + // defined in MKI control plane + '--xpack.uptime.service.manifestUrl=mockDevUrl', + ], + security: [ + '--coreApp.allowDynamicConfigOverrides=true', + // disable fleet task that writes to metrics.fleet_server.* data streams, impacting functional tests + `--xpack.task_manager.unsafe.exclude_task_types=${JSON.stringify(['Fleet-Metrics-Task'])}`, + ], +}; + +export function createServerlessTestConfig(options: CreateTestConfigOptions) { + return async ({ readConfigFile }: FtrConfigProviderContext): Promise<Config> => { + const svlSharedConfig = await readConfigFile( + require.resolve('@kbn/test-suites-serverless/shared/config.base') + ); + + return { + ...svlSharedConfig.getAll(), + + services: { + ...services, + }, + esTestCluster: { + ...svlSharedConfig.get('esTestCluster'), + serverArgs: [ + ...svlSharedConfig.get('esTestCluster.serverArgs'), + ...esServerArgsFromController[options.serverlessProject], + ...(options.esServerArgs ?? []), + ], + }, + kbnTestServer: { + ...svlSharedConfig.get('kbnTestServer'), + serverArgs: [ + ...svlSharedConfig.get('kbnTestServer.serverArgs'), + ...kbnServerArgsFromController[options.serverlessProject], + `--serverless=${options.serverlessProject}`, + ...(options.kbnServerArgs || []), + ], + }, + testFiles: options.testFiles, + junit: options.junit, + suiteTags: options.suiteTags, + }; + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.ts new file mode 100644 index 0000000000000..c784ff071895b --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/default_configs/stateful.config.base.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 { + MOCK_IDP_REALM_NAME, + MOCK_IDP_ENTITY_ID, + MOCK_IDP_ATTRIBUTE_PRINCIPAL, + MOCK_IDP_ATTRIBUTE_ROLES, + MOCK_IDP_ATTRIBUTE_EMAIL, + MOCK_IDP_ATTRIBUTE_NAME, +} from '@kbn/mock-idp-utils'; +import { + esTestConfig, + kbnTestConfig, + systemIndicesSuperuser, + FtrConfigProviderContext, +} from '@kbn/test'; +import { services } from '../services'; + +interface CreateTestConfigOptions { + esServerArgs?: string[]; + kbnServerArgs?: string[]; + testFiles: string[]; + junit: { reportName: string }; + suiteTags?: { include?: string[]; exclude?: string[] }; +} + +export function createStatefulTestConfig(options: CreateTestConfigOptions) { + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const xPackAPITestsConfig = await readConfigFile(require.resolve('../../config.ts')); + + // TODO: move to kbn-es because currently metadata file has hardcoded entityID and Location + const idpPath = require.resolve( + '@kbn/security-api-integration-helpers/saml/idp_metadata_mock_idp.xml' + ); + + const servers = { + kibana: { + ...kbnTestConfig.getUrlParts(systemIndicesSuperuser), + protocol: process.env.TEST_CLOUD ? 'https' : 'http', + }, + elasticsearch: { + ...esTestConfig.getUrlParts(), + protocol: process.env.TEST_CLOUD ? 'https' : 'http', + }, + }; + + const kbnUrl = `${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`; + + return { + servers, + testFiles: options.testFiles, + security: { disableTestUser: true }, + services, + junit: options.junit, + suiteTags: options.suiteTags, + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...xPackAPITestsConfig.get('esTestCluster.serverArgs'), + ...(options.esServerArgs ?? []), + 'xpack.security.authc.token.enabled=true', + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.order=0`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.metadata.path=${idpPath}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.idp.entity_id=${MOCK_IDP_ENTITY_ID}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.entity_id=${kbnUrl}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.acs=${kbnUrl}/api/security/saml/callback`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.sp.logout=${kbnUrl}/logout`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.principal=${MOCK_IDP_ATTRIBUTE_PRINCIPAL}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.groups=${MOCK_IDP_ATTRIBUTE_ROLES}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.name=${MOCK_IDP_ATTRIBUTE_NAME}`, + `xpack.security.authc.realms.saml.${MOCK_IDP_REALM_NAME}.attributes.mail=${MOCK_IDP_ATTRIBUTE_EMAIL}`, + ], + }, + + kbnTestServer: { + ...xPackAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), + ...(options.kbnServerArgs || []), + '--xpack.security.authc.selector.enabled=false', + `--xpack.security.authc.providers=${JSON.stringify({ + saml: { 'cloud-saml-kibana': { order: 0, realm: MOCK_IDP_REALM_NAME } }, + basic: { 'cloud-basic': { order: 1 } }, + })}`, + `--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`, + ], + }, + }; + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts b/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..81df490d79428 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; + +import { services } from './services'; + +export type DeploymentAgnosticFtrProviderContext = GenericFtrProviderContext<typeof services, {}>; diff --git a/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts b/x-pack/test/api_integration/deployment_agnostic/oblt.index.ts new file mode 100644 index 0000000000000..d81415e0554dd --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/oblt.index.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 { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Observability - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.ts new file mode 100644 index 0000000000000..52e1ba2d431ac --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/oblt.serverless.config.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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'oblt', + testFiles: [require.resolve('./oblt.index.ts')], + junit: { + reportName: 'Serverless Observability - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/search.index.ts b/x-pack/test/api_integration/deployment_agnostic/search.index.ts new file mode 100644 index 0000000000000..740520f032f0f --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/search.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 { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Serverless Search - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.ts new file mode 100644 index 0000000000000..5e90b71d69550 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/search.serverless.config.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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'es', + testFiles: [require.resolve('./search.index.ts')], + junit: { + reportName: 'Serverless Search - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/security.index.ts b/x-pack/test/api_integration/deployment_agnostic/security.index.ts new file mode 100644 index 0000000000000..9a9db972bcd5d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/security.index.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 { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('Security Search - Deployment-agnostic api integration tests', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts b/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.ts new file mode 100644 index 0000000000000..d3a30cc95d820 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/security.serverless.config.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 { createServerlessTestConfig } from './default_configs/serverless.config.base'; + +export default createServerlessTestConfig({ + serverlessProject: 'security', + testFiles: [require.resolve('./security.index.ts')], + junit: { + reportName: 'Serverless Security - Deployment-agnostic API Integration Tests', + }, +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.ts new file mode 100644 index 0000000000000..c22db40882b60 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/data_view_api.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 { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; + +export function DataViewApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const samlAuth = getService('samlAuth'); + + return { + async create({ + roleAuthc, + id, + name, + title, + }: { + roleAuthc: RoleCredentials; + id: string; + name: string; + title: string; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/content_management/rpc/create`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set(samlAuth.getCommonRequestHeader()) + .send({ + contentTypeId: 'index-pattern', + data: { + fieldAttrs: '{}', + title, + timeFieldName: '@timestamp', + sourceFilters: '[]', + fields: '[]', + fieldFormatMap: '{}', + typeMeta: '{}', + runtimeFieldMap: '{}', + name, + }, + options: { id }, + version: 1, + }); + return body; + }, + + async delete({ roleAuthc, id }: { roleAuthc: RoleCredentials; id: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/content_management/rpc/create`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set(samlAuth.getCommonRequestHeader()) + .send({ + contentTypeId: 'index-pattern', + id, + options: { force: true }, + version: 1, + }); + return body; + }, + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts b/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.ts new file mode 100644 index 0000000000000..38222c096bed0 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/deployment_agnostic_services.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 _ from 'lodash'; +import { services as apiIntegrationServices } from '../../services'; + +/** + * Load only services that support both stateful & serverless deployments (including Cloud/MKI), + * e.g. `randomness` or `retry` are deployment agnostic + */ +export const deploymentAgnosticServices = _.pick(apiIntegrationServices, [ + 'supertest', // TODO: review its behaviour + 'es', + 'esArchiver', + 'esSupertest', // TODO: review its behaviour + 'indexPatterns', + 'ingestPipelines', + 'kibanaServer', + // 'ml', depends on 'esDeleteAllIndices', can we make it deployment agnostic? + 'randomness', + 'retry', + 'security', + 'usageAPI', +]); diff --git a/x-pack/test/api_integration/deployment_agnostic/services/index.ts b/x-pack/test/api_integration/deployment_agnostic/services/index.ts new file mode 100644 index 0000000000000..c1e70466f1b3c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/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 { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { deploymentAgnosticServices } from './deployment_agnostic_services'; +import { DataViewApiProvider } from './data_view_api'; +import { SloApiProvider } from './slo_api'; + +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; + +export const services = { + ...deploymentAgnosticServices, + supertestWithoutAuth: commonFunctionalServices.supertestWithoutAuth, + samlAuth: commonFunctionalServices.samlAuth, + dataViewApi: DataViewApiProvider, + sloApi: SloApiProvider, + // create a new deployment-agnostic service and load here +}; diff --git a/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts new file mode 100644 index 0000000000000..4c83a536ffb36 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/services/slo_api.ts @@ -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 { + fetchHistoricalSummaryParamsSchema, + FetchHistoricalSummaryResponse, +} from '@kbn/slo-schema'; +import * as t from 'io-ts'; +import { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; + +type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M'; + +interface Duration { + value: number; + unit: DurationUnit; +} + +interface WindowSchema { + id: string; + burnRateThreshold: number; + maxBurnRateThreshold: number; + longWindow: Duration; + shortWindow: Duration; + actionGroup: string; +} + +interface Dependency { + ruleId: string; + actionGroupsToSuppressOn: string[]; +} + +export interface SloBurnRateRuleParams { + sloId: string; + windows: WindowSchema[]; + dependencies?: Dependency[]; +} + +interface SloParams { + id?: string; + name: string; + description: string; + indicator: { + type: 'sli.kql.custom'; + params: { + index: string; + good: string; + total: string; + timestampField: string; + }; + }; + timeWindow: { + duration: string; + type: string; + }; + budgetingMethod: string; + objective: { + target: number; + }; + groupBy: string; +} + +type FetchHistoricalSummaryParams = t.OutputOf< + typeof fetchHistoricalSummaryParamsSchema.props.body +>; + +interface SloRequestParams { + id: string; + roleAuthc: RoleCredentials; +} + +export function SloApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) { + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const samlAuth = getService('samlAuth'); + const retry = getService('retry'); + const config = getService('config'); + const retryTimeout = config.get('timeouts.try'); + const requestTimeout = 30 * 1000; + + return { + async create(slo: SloParams, roleAuthc: RoleCredentials) { + const { body } = await supertestWithoutAuth + .post(`/api/observability/slos`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send(slo); + return body; + }, + + async delete({ id, roleAuthc }: SloRequestParams) { + const response = await supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()); + return response; + }, + + async fetchHistoricalSummary( + params: FetchHistoricalSummaryParams, + roleAuthc: RoleCredentials + ): Promise<FetchHistoricalSummaryResponse> { + const { body } = await supertestWithoutAuth + .post(`/internal/observability/slos/_historical_summary`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send(params); + return body; + }, + + async waitForSloToBeDeleted({ id, roleAuthc }: SloRequestParams) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .timeout(requestTimeout); + if (!response.ok) { + throw new Error(`SLO with id '${id}' was not deleted`); + } + return response; + }); + }, + + async waitForSloCreated({ id, roleAuthc }: SloRequestParams) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertestWithoutAuth + .get(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .timeout(requestTimeout); + if (response.body.id === undefined) { + throw new Error(`No SLO with id '${id}' found`); + } + return response.body; + }); + }, + + async waitForSloSummaryTempIndexToExist(index: string) { + return await retry.tryForTime(retryTimeout, async () => { + const indexExists = await es.indices.exists({ index, allow_no_indices: false }); + if (!indexExists) { + throw new Error(`SLO summary index '${index}' should exist`); + } + return indexExists; + }); + }, + + async getSloData({ sloId, indexName }: { sloId: string; indexName: string }) { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': sloId } }], + }, + }, + }, + }); + return response; + }, + async waitForSloData({ id, indexName }: { id: string; indexName: string }) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': id } }], + }, + }, + }, + }); + if (response.hits.hits.length === 0) { + throw new Error(`No hits found at index '${indexName}' for slo id='${id}'`); + } + return response; + }); + }, + async deleteAllSLOs(roleAuthc: RoleCredentials) { + const response = await supertestWithoutAuth + .get(`/api/observability/slos/_definitions`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send() + .expect(200); + await Promise.all( + response.body.results.map(({ id }: { id: string }) => { + return supertestWithoutAuth + .delete(`/api/observability/slos/${id}`) + .set(roleAuthc.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send() + .expect(204); + }) + ); + }, + }; +} diff --git a/x-pack/test/api_integration/deployment_agnostic/stateful.config.ts b/x-pack/test/api_integration/deployment_agnostic/stateful.config.ts new file mode 100644 index 0000000000000..b4c22e735925c --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/stateful.config.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 { createStatefulTestConfig } from './default_configs/stateful.config.base'; + +export default createStatefulTestConfig({ + testFiles: [require.resolve('./stateful.index.ts')], + junit: { + reportName: 'Stateful - Deployment-agnostic API Integration Tests', + }, + // extra arguments + esServerArgs: [], + kbnServerArgs: [], +}); diff --git a/x-pack/test/api_integration/deployment_agnostic/stateful.index.ts b/x-pack/test/api_integration/deployment_agnostic/stateful.index.ts new file mode 100644 index 0000000000000..3e46c4d5b0472 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/stateful.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. + */ + +import { DeploymentAgnosticFtrProviderContext } from './ftr_provider_context'; + +export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) { + describe('apis', () => { + loadTestFile(require.resolve('./apis/console')); + loadTestFile(require.resolve('./apis/core')); + loadTestFile(require.resolve('./apis/painless_lab')); + }); +} diff --git a/x-pack/test/api_integration/services/index.ts b/x-pack/test/api_integration/services/index.ts index d3dd76767f489..cc2b3f77ca0fa 100644 --- a/x-pack/test/api_integration/services/index.ts +++ b/x-pack/test/api_integration/services/index.ts @@ -10,8 +10,6 @@ import { services as commonServices } from '../../common/services'; // @ts-ignore not ts yet import { EsSupertestWithoutAuthProvider } from './es_supertest_without_auth'; -// @ts-ignore not ts yet -import { SupertestWithoutAuthProvider } from './supertest_without_auth'; import { UsageAPIProvider } from './usage_api'; @@ -36,7 +34,6 @@ export const services = { dataViewApi: DataViewApiProvider, esSupertestWithoutAuth: EsSupertestWithoutAuthProvider, infraOpsSourceConfiguration: InfraOpsSourceConfigurationProvider, - supertestWithoutAuth: SupertestWithoutAuthProvider, usageAPI: UsageAPIProvider, ml: MachineLearningProvider, ingestManager: IngestManagerProvider, diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index bd66aa39f2f2a..ef706d2aca2cd 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -27,33 +27,57 @@ import { BulkDeleteRulesPostRequestBodyInput } from '@kbn/security-solution-plug import { BulkPatchRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.gen'; import { BulkUpdateRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.gen'; import { BulkUpsertAssetCriticalityRecordsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.gen'; +import { CleanDraftTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/clean_draft_timelines/clean_draft_timelines_route.gen'; import { CreateAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen'; import { CreateAssetCriticalityRecordRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/create_asset_criticality.gen'; import { CreateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.gen'; +import { CreateTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/create_timelines/create_timelines_route.gen'; import { CreateUpdateProtectionUpdatesNoteRequestParamsInput, CreateUpdateProtectionUpdatesNoteRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen'; import { DeleteAssetCriticalityRecordRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen'; +import { DeleteNoteRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_note/delete_note_route.gen'; import { DeleteRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.gen'; +import { DeleteTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/delete_timelines/delete_timelines_route.gen'; import { DeprecatedTriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen'; -import { EndpointIsolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/isolate_route.gen'; -import { EndpointUnisolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/unisolate_route.gen'; +import { EndpointExecuteActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/execute/execute.gen'; +import { EndpointFileDownloadRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/file_download/file_download.gen'; +import { EndpointFileInfoRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/file_info/file_info.gen'; +import { EndpointGetActionsDetailsRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/details/details.gen'; +import { EndpointGetActionsListRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/list/list.gen'; +import { EndpointGetActionsStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/status/status.gen'; +import { EndpointGetFileActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/get_file/get_file.gen'; +import { EndpointGetProcessesActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/running_procs/running_procs.gen'; +import { EndpointIsolateActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/isolate/isolate.gen'; +import { EndpointIsolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/isolate/deprecated_isolate.gen'; +import { EndpointKillProcessActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/kill_process/kill_process.gen'; +import { EndpointScanActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/scan/scan.gen'; +import { EndpointSuspendProcessActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/suspend_process/suspend_process.gen'; +import { EndpointUnisolateActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/unisolate/unisolate.gen'; +import { EndpointUnisolateRedirectRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/unisolate/deprecated_unisolate.gen'; +import { EndpointUploadActionRequestBodyInput } from '@kbn/security-solution-plugin/common/api/endpoint/actions/response_actions/upload/upload.gen'; import { ExportRulesRequestQueryInput, ExportRulesRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen'; +import { + ExportTimelinesRequestQueryInput, + ExportTimelinesRequestBodyInput, +} from '@kbn/security-solution-plugin/common/api/timeline/export_timelines/export_timelines_route.gen'; import { FinalizeAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.gen'; import { FindAssetCriticalityRecordsRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/list_asset_criticality.gen'; import { FindRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen'; -import { GetAgentPolicySummaryRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy.gen'; -import { GetAlertsMigrationStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen'; +import { GetAgentPolicySummaryRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/deprecated_agent_policy_summary.gen'; import { GetAssetCriticalityRecordRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/get_asset_criticality.gen'; +import { GetDraftTimelinesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_draft_timelines/get_draft_timelines_route.gen'; +import { GetEndpointMetadataListRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/metadata/get_metadata.gen'; import { GetEndpointSuggestionsRequestParamsInput, GetEndpointSuggestionsRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/endpoint/suggestions/get_suggestions.gen'; -import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy.gen'; +import { GetNotesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_notes/get_notes_route.gen'; +import { GetPolicyResponseRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy_response.gen'; import { GetProtectionUpdatesNoteRequestParamsInput } from '@kbn/security-solution-plugin/common/api/endpoint/protection_updates_note/protection_updates_note.gen'; import { GetRuleExecutionEventsRequestQueryInput, @@ -63,22 +87,29 @@ import { GetRuleExecutionResultsRequestQueryInput, GetRuleExecutionResultsRequestParamsInput, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen'; +import { GetTimelineRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_timeline/get_timeline_route.gen'; +import { GetTimelinesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/get_timelines/get_timelines_route.gen'; import { ImportRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen'; -import { InternalCreateAssetCriticalityRecordRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/internal_create_asset_criticality.gen'; -import { InternalDeleteAssetCriticalityRecordRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/internal_delete_asset_criticality.gen'; -import { InternalGetAssetCriticalityRecordRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/asset_criticality/internal_get_asset_criticality.gen'; -import { ManageAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen'; +import { ImportTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/import_timelines/import_timelines_route.gen'; +import { InstallPrepackedTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen'; import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen'; +import { PatchTimelineRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/patch_timelines/patch_timeline_route.gen'; import { PerformBulkActionRequestQueryInput, PerformBulkActionRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen'; +import { PersistFavoriteRouteRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/persist_favorite/persist_favorite_route.gen'; +import { PersistNoteRouteRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/persist_note/persist_note_route.gen'; +import { PersistPinnedEventRouteRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/pinned_events/pinned_events_route.gen'; import { PreviewRiskScoreRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/preview_route.gen'; +import { ReadAlertsMigrationStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/read_signals_migration_status/read_signals_migration_status.gen'; import { ReadRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/read_rule/read_rule_route.gen'; +import { ResolveTimelineRequestQueryInput } from '@kbn/security-solution-plugin/common/api/timeline/resolve_timeline/resolve_timeline_route.gen'; import { RulePreviewRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_preview/rule_preview.gen'; import { SearchAlertsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals/query_signals/query_signals_route.gen'; import { SetAlertAssigneesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_assignees/set_alert_assignees_route.gen'; import { SetAlertsStatusRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.gen'; +import { SetAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen'; import { SuggestUserProfilesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/users/suggest_user_profiles_route.gen'; import { TriggerRiskScoreCalculationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/risk_engine/entity_calculation_route.gen'; import { UpdateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.gen'; @@ -106,6 +137,23 @@ after 30 days. It also deletes other artifacts specific to the migration impleme .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + assetCriticalityGetPrivileges() { + return supertest + .get('/internal/asset_criticality/privileges') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Ensures that the packages needed for prebuilt detection rules to work are installed and up to date + */ + bootstrapPrebuiltRules() { + return supertest + .post('/internal/detection_engine/prebuilt_rules/_bootstrap') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, /** * Create new detection rules in bulk. */ @@ -172,6 +220,18 @@ after 30 days. It also deletes other artifacts specific to the migration impleme .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Retrieves a clean draft timeline. If a draft timeline does not exist, it is created and returned. + + */ + cleanDraftTimelines(props: CleanDraftTimelinesProps) { + return supertest + .post('/api/timeline/_draft') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, createAlertsIndex() { return supertest .post('/api/detection_engine/index') @@ -211,6 +271,14 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + createTimelines(props: CreateTimelinesProps) { + return supertest + .post('/api/timeline') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, createUpdateProtectionUpdatesNote(props: CreateUpdateProtectionUpdatesNoteProps) { return supertest .post( @@ -236,6 +304,14 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + deleteNote(props: DeleteNoteProps) { + return supertest + .delete('/api/note') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Delete a detection rule using the `rule_id` or `id` field. */ @@ -247,6 +323,14 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + deleteTimelines(props: DeleteTimelinesProps) { + return supertest + .delete('/api/timeline') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Calculates and persists Risk Scores for an entity, returning the calculated risk score. */ @@ -272,6 +356,114 @@ Migrations are initiated per index. While the process is neither destructive nor .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + /** + * Execute a given command on an endpoint + */ + endpointExecuteAction(props: EndpointExecuteActionProps) { + return supertest + .post('/api/endpoint/action/execute') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Download a file from an endpoint + */ + endpointFileDownload(props: EndpointFileDownloadProps) { + return supertest + .get( + replaceParams( + '/api/endpoint/action/{action_id}/file/{file_id}/download`', + props.params + ) + ) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get file info + */ + endpointFileInfo(props: EndpointFileInfoProps) { + return supertest + .get(replaceParams('/api/endpoint/action/{action_id}/file/{file_id}`', props.params)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get action details + */ + endpointGetActionsDetails(props: EndpointGetActionsDetailsProps) { + return supertest + .get(replaceParams('/api/endpoint/action/{action_id}', props.params)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get a list of action requests and their responses + */ + endpointGetActionsList(props: EndpointGetActionsListProps) { + return supertest + .get('/api/endpoint/action') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + endpointGetActionsState() { + return supertest + .get('/api/endpoint/action/state') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Get action status + */ + endpointGetActionsStatus(props: EndpointGetActionsStatusProps) { + return supertest + .get('/api/endpoint/action_status') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + /** + * Get a file from an endpoint + */ + endpointGetFileAction(props: EndpointGetFileActionProps) { + return supertest + .post('/api/endpoint/action/get_file') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Get list of running processes on an endpoint + */ + endpointGetProcessesAction(props: EndpointGetProcessesActionProps) { + return supertest + .post('/api/endpoint/action/running_procs') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Isolate an endpoint + */ + endpointIsolateAction(props: EndpointIsolateActionProps) { + return supertest + .post('/api/endpoint/action/isolate') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, endpointIsolateRedirect(props: EndpointIsolateRedirectProps) { return supertest .post('/api/endpoint/isolate') @@ -280,6 +472,50 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Kill a running process on an endpoint + */ + endpointKillProcessAction(props: EndpointKillProcessActionProps) { + return supertest + .post('/api/endpoint/action/kill_process') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Scan a file or directory + */ + endpointScanAction(props: EndpointScanActionProps) { + return supertest + .post('/api/endpoint/action/scan') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Suspend a running process on an endpoint + */ + endpointSuspendProcessAction(props: EndpointSuspendProcessActionProps) { + return supertest + .post('/api/endpoint/action/suspend_process') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + /** + * Release an endpoint + */ + endpointUnisolateAction(props: EndpointUnisolateActionProps) { + return supertest + .post('/api/endpoint/action/unisolate') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, endpointUnisolateRedirect(props: EndpointUnisolateRedirectProps) { return supertest .post('/api/endpoint/unisolate') @@ -288,6 +524,17 @@ Migrations are initiated per index. While the process is neither destructive nor .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * Upload a file to an endpoint + */ + endpointUploadAction(props: EndpointUploadActionProps) { + return supertest + .post('/api/endpoint/action/upload') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Export detection rules to an `.ndjson` file. The following configuration items are also included in the `.ndjson` file: - Actions @@ -305,6 +552,15 @@ Migrations are initiated per index. While the process is neither destructive nor .send(props.body as object) .query(props.query); }, + exportTimelines(props: ExportTimelinesProps) { + return supertest + .post('/api/timeline/_export') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object) + .query(props.query); + }, /** * Finalize successful migrations of detection alerts. This replaces the original index's alias with the successfully migrated index's alias. The endpoint is idempotent; therefore, it can safely be used to poll a given migration and, upon completion, @@ -346,24 +602,6 @@ finalize it. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, - getAlertsIndex() { - return supertest - .get('/api/detection_engine/index') - .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); - }, - /** - * Retrieve indices that contain detection alerts of a particular age, along with migration information for each of those indices. - */ - getAlertsMigrationStatus(props: GetAlertsMigrationStatusProps) { - return supertest - .post('/api/detection_engine/signals/migration_status') - .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .query(props.query); - }, getAssetCriticalityRecord(props: GetAssetCriticalityRecordProps) { return supertest .get('/api/asset_criticality') @@ -379,45 +617,48 @@ finalize it. .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, - getEndpointSuggestions(props: GetEndpointSuggestionsProps) { + getDraftTimelines(props: GetDraftTimelinesProps) { return supertest - .post(replaceParams('/api/endpoint/suggestions/{suggestion_type}', props.params)) + .get('/api/timeline/_draft') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send(props.body as object); + .query(props.query); }, - getPolicyResponse(props: GetPolicyResponseProps) { + getEndpointMetadataList(props: GetEndpointMetadataListProps) { return supertest - .get('/api/endpoint/policy_response') + .get('/api/endpoint/metadata') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + getEndpointSuggestions(props: GetEndpointSuggestionsProps) { + return supertest + .post(replaceParams('/api/endpoint/suggestions/{suggestion_type}', props.params)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** - * Retrieve the status of all Elastic prebuilt detection rules and Timelines. + * Gets notes */ - getPrebuiltRulesAndTimelinesStatus() { + getNotes(props: GetNotesProps) { return supertest - .get('/api/detection_engine/rules/prepackaged/_status') + .get('/api/note') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); }, - /** - * Retrieves whether or not the user is authenticated, and the user's Kibana -space and index privileges, which determine if the user can create an -index for the Elastic Security alerts generated by -detection engine rules. - - */ - getPrivileges() { + getPolicyResponse(props: GetPolicyResponseProps) { return supertest - .get('/api/detection_engine/privileges') + .get('/api/endpoint/policy_response') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); }, getProtectionUpdatesNote(props: GetProtectionUpdatesNoteProps) { return supertest @@ -458,6 +699,22 @@ detection engine rules. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + getTimeline(props: GetTimelineProps) { + return supertest + .get('/api/timeline') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + getTimelines(props: GetTimelinesProps) { + return supertest + .get('/api/timelines') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, /** * Import detection rules from an `.ndjson` file, including actions and exception lists. The request must include: - The `Content-Type: multipart/form-data` HTTP header. @@ -472,6 +729,14 @@ detection engine rules. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + importTimelines(props: ImportTimelinesProps) { + return supertest + .post('/api/timeline/_import') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine */ @@ -492,30 +757,14 @@ detection engine rules. .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, - internalCreateAssetCriticalityRecord(props: InternalCreateAssetCriticalityRecordProps) { + installPrepackedTimelines(props: InstallPrepackedTimelinesProps) { return supertest - .post('/internal/asset_criticality') + .post('/api/timeline/_prepackaged') .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, - internalDeleteAssetCriticalityRecord(props: InternalDeleteAssetCriticalityRecordProps) { - return supertest - .delete('/internal/asset_criticality') - .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .query(props.query); - }, - internalGetAssetCriticalityRecord(props: InternalGetAssetCriticalityRecordProps) { - return supertest - .get('/internal/asset_criticality') - .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .query(props.query); - }, internalUploadAssetCriticalityRecords() { return supertest .post('/internal/asset_criticality/upload_csv') @@ -524,25 +773,22 @@ detection engine rules. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, /** - * And tags to detection alerts, and remove them from alerts. -> info -> You cannot add and remove the same alert tag in the same request. - - */ - manageAlertTags(props: ManageAlertTagsProps) { + * Update specific fields of an existing detection rule using the `rule_id` or `id` field. + */ + patchRule(props: PatchRuleProps) { return supertest - .post('/api/detection_engine/signals/tags') + .patch('/api/detection_engine/rules') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, /** - * Update specific fields of an existing detection rule using the `rule_id` or `id` field. + * Updates an existing timeline. This API is used to update the title, description, date range, pinned events, pinned queries, and/or pinned saved queries of an existing timeline. */ - patchRule(props: PatchRuleProps) { + patchTimeline(props: PatchTimelineProps) { return supertest - .patch('/api/detection_engine/rules') + .patch('/api/timeline') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') @@ -560,6 +806,30 @@ detection engine rules. .send(props.body as object) .query(props.query); }, + persistFavoriteRoute(props: PersistFavoriteRouteProps) { + return supertest + .patch('/api/timeline/_favorite') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + persistNoteRoute(props: PersistNoteRouteProps) { + return supertest + .patch('/api/note') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, + persistPinnedEventRoute(props: PersistPinnedEventRouteProps) { + return supertest + .patch('/api/pinned_event') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Calculates and returns a list of Risk Scores, sorted by identifier_type and risk score. */ @@ -571,6 +841,48 @@ detection engine rules. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + readAlertsIndex() { + return supertest + .get('/api/detection_engine/index') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Retrieve indices that contain detection alerts of a particular age, along with migration information for each of those indices. + */ + readAlertsMigrationStatus(props: ReadAlertsMigrationStatusProps) { + return supertest + .post('/api/detection_engine/signals/migration_status') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + /** + * Retrieve the status of all Elastic prebuilt detection rules and Timelines. + */ + readPrebuiltRulesAndTimelinesStatus() { + return supertest + .get('/api/detection_engine/rules/prepackaged/_status') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, + /** + * Retrieves whether or not the user is authenticated, and the user's Kibana +space and index privileges, which determine if the user can create an +index for the Elastic Security alerts generated by +detection engine rules. + + */ + readPrivileges() { + return supertest + .get('/api/detection_engine/privileges') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, readRiskEngineSettings() { return supertest .get('/internal/risk_score/engine/settings') @@ -599,6 +911,21 @@ detection engine rules. .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + resolveTimeline(props: ResolveTimelineProps) { + return supertest + .get('/api/timeline/resolve') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, + riskEngineGetPrivileges() { + return supertest + .get('/internal/risk_engine/privileges') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, rulePreview(props: RulePreviewProps) { return supertest .post('/api/detection_engine/rules/preview') @@ -643,6 +970,20 @@ detection engine rules. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + /** + * And tags to detection alerts, and remove them from alerts. +> info +> You cannot add and remove the same alert tag in the same request. + + */ + setAlertTags(props: SetAlertTagsProps) { + return supertest + .post('/api/detection_engine/signals/tags') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Suggests user profiles. */ @@ -710,6 +1051,9 @@ export interface BulkUpdateRulesProps { export interface BulkUpsertAssetCriticalityRecordsProps { body: BulkUpsertAssetCriticalityRecordsRequestBodyInput; } +export interface CleanDraftTimelinesProps { + body: CleanDraftTimelinesRequestBodyInput; +} export interface CreateAlertsMigrationProps { body: CreateAlertsMigrationRequestBodyInput; } @@ -719,6 +1063,9 @@ export interface CreateAssetCriticalityRecordProps { export interface CreateRuleProps { body: CreateRuleRequestBodyInput; } +export interface CreateTimelinesProps { + body: CreateTimelinesRequestBodyInput; +} export interface CreateUpdateProtectionUpdatesNoteProps { params: CreateUpdateProtectionUpdatesNoteRequestParamsInput; body: CreateUpdateProtectionUpdatesNoteRequestBodyInput; @@ -726,22 +1073,74 @@ export interface CreateUpdateProtectionUpdatesNoteProps { export interface DeleteAssetCriticalityRecordProps { query: DeleteAssetCriticalityRecordRequestQueryInput; } +export interface DeleteNoteProps { + body: DeleteNoteRequestBodyInput; +} export interface DeleteRuleProps { query: DeleteRuleRequestQueryInput; } +export interface DeleteTimelinesProps { + body: DeleteTimelinesRequestBodyInput; +} export interface DeprecatedTriggerRiskScoreCalculationProps { body: DeprecatedTriggerRiskScoreCalculationRequestBodyInput; } +export interface EndpointExecuteActionProps { + body: EndpointExecuteActionRequestBodyInput; +} +export interface EndpointFileDownloadProps { + params: EndpointFileDownloadRequestParamsInput; +} +export interface EndpointFileInfoProps { + params: EndpointFileInfoRequestParamsInput; +} +export interface EndpointGetActionsDetailsProps { + params: EndpointGetActionsDetailsRequestParamsInput; +} +export interface EndpointGetActionsListProps { + query: EndpointGetActionsListRequestQueryInput; +} +export interface EndpointGetActionsStatusProps { + query: EndpointGetActionsStatusRequestQueryInput; +} +export interface EndpointGetFileActionProps { + body: EndpointGetFileActionRequestBodyInput; +} +export interface EndpointGetProcessesActionProps { + body: EndpointGetProcessesActionRequestBodyInput; +} +export interface EndpointIsolateActionProps { + body: EndpointIsolateActionRequestBodyInput; +} export interface EndpointIsolateRedirectProps { body: EndpointIsolateRedirectRequestBodyInput; } +export interface EndpointKillProcessActionProps { + body: EndpointKillProcessActionRequestBodyInput; +} +export interface EndpointScanActionProps { + body: EndpointScanActionRequestBodyInput; +} +export interface EndpointSuspendProcessActionProps { + body: EndpointSuspendProcessActionRequestBodyInput; +} +export interface EndpointUnisolateActionProps { + body: EndpointUnisolateActionRequestBodyInput; +} export interface EndpointUnisolateRedirectProps { body: EndpointUnisolateRedirectRequestBodyInput; } +export interface EndpointUploadActionProps { + body: EndpointUploadActionRequestBodyInput; +} export interface ExportRulesProps { query: ExportRulesRequestQueryInput; body: ExportRulesRequestBodyInput; } +export interface ExportTimelinesProps { + query: ExportTimelinesRequestQueryInput; + body: ExportTimelinesRequestBodyInput; +} export interface FinalizeAlertsMigrationProps { body: FinalizeAlertsMigrationRequestBodyInput; } @@ -754,16 +1153,22 @@ export interface FindRulesProps { export interface GetAgentPolicySummaryProps { query: GetAgentPolicySummaryRequestQueryInput; } -export interface GetAlertsMigrationStatusProps { - query: GetAlertsMigrationStatusRequestQueryInput; -} export interface GetAssetCriticalityRecordProps { query: GetAssetCriticalityRecordRequestQueryInput; } +export interface GetDraftTimelinesProps { + query: GetDraftTimelinesRequestQueryInput; +} +export interface GetEndpointMetadataListProps { + query: GetEndpointMetadataListRequestQueryInput; +} export interface GetEndpointSuggestionsProps { params: GetEndpointSuggestionsRequestParamsInput; body: GetEndpointSuggestionsRequestBodyInput; } +export interface GetNotesProps { + query: GetNotesRequestQueryInput; +} export interface GetPolicyResponseProps { query: GetPolicyResponseRequestQueryInput; } @@ -778,34 +1183,52 @@ export interface GetRuleExecutionResultsProps { query: GetRuleExecutionResultsRequestQueryInput; params: GetRuleExecutionResultsRequestParamsInput; } -export interface ImportRulesProps { - query: ImportRulesRequestQueryInput; +export interface GetTimelineProps { + query: GetTimelineRequestQueryInput; } -export interface InternalCreateAssetCriticalityRecordProps { - body: InternalCreateAssetCriticalityRecordRequestBodyInput; +export interface GetTimelinesProps { + query: GetTimelinesRequestQueryInput; } -export interface InternalDeleteAssetCriticalityRecordProps { - query: InternalDeleteAssetCriticalityRecordRequestQueryInput; +export interface ImportRulesProps { + query: ImportRulesRequestQueryInput; } -export interface InternalGetAssetCriticalityRecordProps { - query: InternalGetAssetCriticalityRecordRequestQueryInput; +export interface ImportTimelinesProps { + body: ImportTimelinesRequestBodyInput; } -export interface ManageAlertTagsProps { - body: ManageAlertTagsRequestBodyInput; +export interface InstallPrepackedTimelinesProps { + body: InstallPrepackedTimelinesRequestBodyInput; } export interface PatchRuleProps { body: PatchRuleRequestBodyInput; } +export interface PatchTimelineProps { + body: PatchTimelineRequestBodyInput; +} export interface PerformBulkActionProps { query: PerformBulkActionRequestQueryInput; body: PerformBulkActionRequestBodyInput; } +export interface PersistFavoriteRouteProps { + body: PersistFavoriteRouteRequestBodyInput; +} +export interface PersistNoteRouteProps { + body: PersistNoteRouteRequestBodyInput; +} +export interface PersistPinnedEventRouteProps { + body: PersistPinnedEventRouteRequestBodyInput; +} export interface PreviewRiskScoreProps { body: PreviewRiskScoreRequestBodyInput; } +export interface ReadAlertsMigrationStatusProps { + query: ReadAlertsMigrationStatusRequestQueryInput; +} export interface ReadRuleProps { query: ReadRuleRequestQueryInput; } +export interface ResolveTimelineProps { + query: ResolveTimelineRequestQueryInput; +} export interface RulePreviewProps { body: RulePreviewRequestBodyInput; } @@ -818,6 +1241,9 @@ export interface SetAlertAssigneesProps { export interface SetAlertsStatusProps { body: SetAlertsStatusRequestBodyInput; } +export interface SetAlertTagsProps { + body: SetAlertTagsRequestBodyInput; +} export interface SuggestUserProfilesProps { query: SuggestUserProfilesRequestQueryInput; } diff --git a/x-pack/test/api_integration/services/security_solution_exceptions_api.gen.ts b/x-pack/test/api_integration/services/security_solution_exceptions_api.gen.ts index 31c880d97c8f1..e7426a737b39c 100644 --- a/x-pack/test/api_integration/services/security_solution_exceptions_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_exceptions_api.gen.ts @@ -31,12 +31,12 @@ import { DeleteExceptionListRequestQueryInput } from '@kbn/securitysolution-exce import { DeleteExceptionListItemRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/delete_exception_list_item/delete_exception_list_item.gen'; import { DuplicateExceptionListRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/duplicate_exception_list/duplicate_exception_list.gen'; import { ExportExceptionListRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/export_exception_list/export_exception_list.gen'; -import { FindExceptionListItemsRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/find_exception_list_item/find_exception_list_item.gen'; -import { FindExceptionListsRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/find_exception_list/find_exception_list.gen'; -import { GetExceptionListRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/read_exception_list/read_exception_list.gen'; -import { GetExceptionListItemRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.gen'; -import { GetExceptionListSummaryRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/summary_exception_list/summary_exception_list.gen'; +import { FindExceptionListItemsRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/find_exception_list_items/find_exception_list_items.gen'; +import { FindExceptionListsRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/find_exception_lists/find_exception_lists.gen'; import { ImportExceptionListRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/import_exceptions/import_exceptions.gen'; +import { ReadExceptionListRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/read_exception_list/read_exception_list.gen'; +import { ReadExceptionListItemRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/read_exception_list_item/read_exception_list_item.gen'; +import { ReadExceptionListSummaryRequestQueryInput } from '@kbn/securitysolution-exceptions-common/api/read_exception_list_summary/read_exception_list_summary.gen'; import { UpdateExceptionListRequestBodyInput } from '@kbn/securitysolution-exceptions-common/api/update_exception_list/update_exception_list.gen'; import { UpdateExceptionListItemRequestBodyInput } from '@kbn/securitysolution-exceptions-common/api/update_exception_list_item/update_exception_list_item.gen'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -128,36 +128,36 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, - getExceptionList(props: GetExceptionListProps) { + /** + * Imports an exception list and associated items + */ + importExceptionList(props: ImportExceptionListProps) { return supertest - .get('/api/exception_lists') + .post('/api/exception_lists/_import') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, - getExceptionListItem(props: GetExceptionListItemProps) { + readExceptionList(props: ReadExceptionListProps) { return supertest - .get('/api/exception_lists/items') + .get('/api/exception_lists') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, - getExceptionListSummary(props: GetExceptionListSummaryProps) { + readExceptionListItem(props: ReadExceptionListItemProps) { return supertest - .get('/api/exception_lists/summary') + .get('/api/exception_lists/items') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, - /** - * Imports an exception list and associated items - */ - importExceptionList(props: ImportExceptionListProps) { + readExceptionListSummary(props: ReadExceptionListSummaryProps) { return supertest - .post('/api/exception_lists/_import') + .get('/api/exception_lists/summary') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') @@ -213,17 +213,17 @@ export interface FindExceptionListItemsProps { export interface FindExceptionListsProps { query: FindExceptionListsRequestQueryInput; } -export interface GetExceptionListProps { - query: GetExceptionListRequestQueryInput; +export interface ImportExceptionListProps { + query: ImportExceptionListRequestQueryInput; } -export interface GetExceptionListItemProps { - query: GetExceptionListItemRequestQueryInput; +export interface ReadExceptionListProps { + query: ReadExceptionListRequestQueryInput; } -export interface GetExceptionListSummaryProps { - query: GetExceptionListSummaryRequestQueryInput; +export interface ReadExceptionListItemProps { + query: ReadExceptionListItemRequestQueryInput; } -export interface ImportExceptionListProps { - query: ImportExceptionListRequestQueryInput; +export interface ReadExceptionListSummaryProps { + query: ReadExceptionListSummaryRequestQueryInput; } export interface UpdateExceptionListProps { body: UpdateExceptionListRequestBodyInput; diff --git a/x-pack/test/api_integration/services/security_solution_lists_api.gen.ts b/x-pack/test/api_integration/services/security_solution_lists_api.gen.ts index 9800f0b802873..4ebd688bf296a 100644 --- a/x-pack/test/api_integration/services/security_solution_lists_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_lists_api.gen.ts @@ -23,14 +23,14 @@ import { CreateListRequestBodyInput } from '@kbn/securitysolution-lists-common/a import { CreateListItemRequestBodyInput } from '@kbn/securitysolution-lists-common/api/create_list_item/create_list_item.gen'; import { DeleteListRequestQueryInput } from '@kbn/securitysolution-lists-common/api/delete_list/delete_list.gen'; import { DeleteListItemRequestQueryInput } from '@kbn/securitysolution-lists-common/api/delete_list_item/delete_list_item.gen'; -import { ExportListItemsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/export_list_item/export_list_item.gen'; -import { FindListItemsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/find_list_item/find_list_item.gen'; -import { FindListsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/find_list/find_list.gen'; -import { GetListRequestQueryInput } from '@kbn/securitysolution-lists-common/api/read_list/read_list.gen'; -import { GetListItemRequestQueryInput } from '@kbn/securitysolution-lists-common/api/read_list_item/read_list_item.gen'; -import { ImportListItemsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/import_list_item/import_list_item.gen'; +import { ExportListItemsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/export_list_items/export_list_items.gen'; +import { FindListItemsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/find_list_items/find_list_items.gen'; +import { FindListsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/find_lists/find_lists.gen'; +import { ImportListItemsRequestQueryInput } from '@kbn/securitysolution-lists-common/api/import_list_items/import_list_items.gen'; import { PatchListRequestBodyInput } from '@kbn/securitysolution-lists-common/api/patch_list/patch_list.gen'; import { PatchListItemRequestBodyInput } from '@kbn/securitysolution-lists-common/api/patch_list_item/patch_list_item.gen'; +import { ReadListRequestQueryInput } from '@kbn/securitysolution-lists-common/api/read_list/read_list.gen'; +import { ReadListItemRequestQueryInput } from '@kbn/securitysolution-lists-common/api/read_list_item/read_list_item.gen'; import { UpdateListRequestBodyInput } from '@kbn/securitysolution-lists-common/api/update_list/update_list.gen'; import { UpdateListItemRequestBodyInput } from '@kbn/securitysolution-lists-common/api/update_list_item/update_list_item.gen'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -112,65 +112,65 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, - getList(props: GetListProps) { + /** + * Imports a list of items from a `.txt` or `.csv` file. The maximum file size is 9 million bytes. + +You can import items to a new or existing list. + + */ + importListItems(props: ImportListItemsProps) { return supertest - .get('/api/lists') + .post('/api/lists/items/_import') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, - getListIndex() { + patchList(props: PatchListProps) { return supertest - .get('/api/lists/index') + .patch('/api/lists') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); }, - getListItem(props: GetListItemProps) { + patchListItem(props: PatchListItemProps) { return supertest - .get('/api/lists/items') + .patch('/api/lists/items') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .query(props.query); + .send(props.body as object); }, - getListPrivileges() { + readList(props: ReadListProps) { return supertest - .get('/api/lists/privileges') + .get('/api/lists') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); }, - /** - * Imports a list of items from a `.txt` or `.csv` file. The maximum file size is 9 million bytes. - -You can import items to a new or existing list. - - */ - importListItems(props: ImportListItemsProps) { + readListIndex() { return supertest - .post('/api/lists/items/_import') + .get('/api/lists/index') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .query(props.query); + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, - patchList(props: PatchListProps) { + readListItem(props: ReadListItemProps) { return supertest - .patch('/api/lists') + .get('/api/lists/items') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send(props.body as object); + .query(props.query); }, - patchListItem(props: PatchListItemProps) { + readListPrivileges() { return supertest - .patch('/api/lists/items') + .get('/api/lists/privileges') .set('kbn-xsrf', 'true') .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send(props.body as object); + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, updateList(props: UpdateListProps) { return supertest @@ -212,12 +212,6 @@ export interface FindListItemsProps { export interface FindListsProps { query: FindListsRequestQueryInput; } -export interface GetListProps { - query: GetListRequestQueryInput; -} -export interface GetListItemProps { - query: GetListItemRequestQueryInput; -} export interface ImportListItemsProps { query: ImportListItemsRequestQueryInput; } @@ -227,6 +221,12 @@ export interface PatchListProps { export interface PatchListItemProps { body: PatchListItemRequestBodyInput; } +export interface ReadListProps { + query: ReadListRequestQueryInput; +} +export interface ReadListItemProps { + query: ReadListItemRequestQueryInput; +} export interface UpdateListProps { body: UpdateListRequestBodyInput; } diff --git a/x-pack/test/api_integration/services/supertest_without_auth.js b/x-pack/test/api_integration/services/supertest_without_auth.js deleted file mode 100644 index ea4a5bdf08a94..0000000000000 --- a/x-pack/test/api_integration/services/supertest_without_auth.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { format as formatUrl } from 'url'; - -import supertest from 'supertest'; - -/** - * supertest provider that doesn't include user credentials into base URL that is passed - * to the supertest. It's used to test API behaviour for not yet authenticated user. - */ -export function SupertestWithoutAuthProvider({ getService }) { - const config = getService('config'); - const kibanaServerConfig = config.get('servers.kibana'); - - return supertest( - formatUrl({ - ...kibanaServerConfig, - auth: false, - }) - ); -} diff --git a/x-pack/test/apm_api_integration/tests/entities/services/services_entities_detailed_statistics.spec.ts b/x-pack/test/apm_api_integration/tests/entities/services/services_entities_detailed_statistics.spec.ts index 9e4cc209c6b92..e1bf212c6e9c0 100644 --- a/x-pack/test/apm_api_integration/tests/entities/services/services_entities_detailed_statistics.spec.ts +++ b/x-pack/test/apm_api_integration/tests/entities/services/services_entities_detailed_statistics.spec.ts @@ -5,40 +5,20 @@ * 2.0. */ import expect from '@kbn/expect'; -import { - APIClientRequestParamsOf, - APIReturnType, -} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type'; -import { RollupInterval } from '@kbn/apm-plugin/common/rollup'; -import { apm, log, timerange } from '@kbn/apm-synthtrace-client'; -import { uniq, map } from 'lodash'; +import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -type ServicesEntitiesDetailedStatisticsReturn = - APIReturnType<'POST /internal/apm/entities/services/detailed_statistics'>; - export default function ApiTest({ getService }: FtrProviderContext) { const registry = getService('registry'); const apmApiClient = getService('apmApiClient'); - const synthtrace = getService('apmSynthtraceEsClient'); - const logSynthtrace = getService('logSynthtraceEsClient'); const start = '2024-01-01T00:00:00.000Z'; const end = '2024-01-01T00:59:59.999Z'; const serviceNames = ['my-service', 'synth-go']; - const hostName = 'synth-host'; - const serviceName = 'synth-go'; - - const EXPECTED_TPM = 5; - const EXPECTED_LATENCY = 1000; - const EXPECTED_FAILURE_RATE = 0.25; - const EXPECTED_LOG_RATE = 0.016666666666666666; - const EXPECTED_LOG_ERROR_RATE = 1; - async function getStats( + async function getServiceEntitiesDetailedStats( overrides?: Partial< APIClientRequestParamsOf<'POST /internal/apm/entities/services/detailed_statistics'>['params']['query'] > @@ -51,10 +31,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { end, environment: 'ENVIRONMENT_ALL', kuery: '', - probability: 1, - documentType: ApmDocumentType.TransactionMetric, - rollupInterval: RollupInterval.OneMinute, - bucketSizeInSeconds: 60, ...overrides, }, body: { @@ -63,127 +39,18 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }); - return response.body; + return response; } registry.when( - 'Services entities detailed statistics when data is generated', + 'Services entities detailed statistics when no data is generated', { config: 'basic', archives: [] }, () => { - let servicesEntitiesDetailedStatistics: ServicesEntitiesDetailedStatisticsReturn; - - const instance = apm.service('my-service', 'production', 'java').instance('instance'); - - before(async () => { - const interval = timerange(new Date(start).getTime(), new Date(end).getTime() - 1).interval( - '1m' - ); - - await logSynthtrace.index([ - timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => - log - .create() - .message('This is a log message') - .logLevel('error') - .timestamp(timestamp) - .defaults({ - 'log.file.path': '/my-service.log', - 'service.name': serviceName, - 'host.name': hostName, - 'service.environment': 'test', - }) - ), - timerange(start, end) - .interval('2m') - .rate(1) - .generator((timestamp) => - log - .create() - .message('This is an error log message') - .logLevel('error') - .timestamp(timestamp) - .defaults({ - 'log.file.path': '/my-service.log', - 'service.name': 'my-service', - 'host.name': hostName, - 'service.environment': 'production', - }) - ), - timerange(start, end) - .interval('5m') - .rate(1) - .generator((timestamp) => - log - .create() - .message('This is an info message') - .logLevel('info') - .timestamp(timestamp) - .defaults({ - 'log.file.path': '/my-service.log', - 'service.name': 'my-service', - 'host.name': hostName, - 'service.environment': 'production', - }) - ), - ]); - - await synthtrace.index([ - interval.rate(3).generator((timestamp) => { - return instance - .transaction('GET /api') - .duration(EXPECTED_LATENCY) - .outcome('success') - .timestamp(timestamp); - }), - interval.rate(1).generator((timestamp) => { - return instance - .transaction('GET /api') - .duration(EXPECTED_LATENCY) - .outcome('failure') - .timestamp(timestamp); - }), - interval.rate(1).generator((timestamp) => { - return instance - .transaction('GET /api') - .duration(EXPECTED_LATENCY) - .outcome('unknown') - .timestamp(timestamp); - }), - ]); - }); - - after(() => synthtrace.clean()); - - describe('and transaction metrics are used', () => { - before(async () => { - servicesEntitiesDetailedStatistics = await getStats(); - }); - - it('returns the expected statistics for apm', () => { - const stats = servicesEntitiesDetailedStatistics.currentPeriod.apm['my-service']; - - expect(stats).not.empty(); - expect(uniq(map(stats.throughput, 'y'))).eql([EXPECTED_TPM], 'tpm'); - - expect(uniq(map(stats.latency, 'y'))).eql([EXPECTED_LATENCY * 1000], 'latency'); - - expect(uniq(map(stats.transactionErrorRate, 'y'))).eql( - [EXPECTED_FAILURE_RATE], - 'errorRate' - ); - }); - - it('returns the expected statistics for logs', () => { - const statsLogErrorRate = - servicesEntitiesDetailedStatistics.currentPeriod.logErrorRate[serviceName]; - const statsLogRate = - servicesEntitiesDetailedStatistics.currentPeriod.logRate[serviceName]; - - expect(statsLogErrorRate.every(({ y }) => y === EXPECTED_LOG_ERROR_RATE)).to.be(true); - expect(statsLogRate.every(({ y }) => y === EXPECTED_LOG_RATE)).to.be(true); + describe('Service entities detailed', () => { + it('handles the empty state', async () => { + const response = await getServiceEntitiesDetailedStats(); + expect(response.status).to.be(200); + expect(response.body.currentPeriod).to.empty(); }); }); } diff --git a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts index 54ae003de8698..5b80b5c7bc99d 100644 --- a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts +++ b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts @@ -484,8 +484,8 @@ export default function (providerContext: FtrProviderContext) { }); expect(status).to.be(403); }); - // Blocked by https://github.com/elastic/kibana/issues/188059 - it.skip('users with read privileges on cloud security should be able to mute', async () => { + + it('users with all privileges on cloud security should be able to mute', async () => { const rule1 = await getRandomCspBenchmarkRule(); const rule2 = await getRandomCspBenchmarkRule(); @@ -494,7 +494,7 @@ export default function (providerContext: FtrProviderContext) { .set(ELASTIC_HTTP_VERSION_HEADER, '1') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('kbn-xsrf', 'xxxx') - .auth('role_security_read_user', cspSecurity.getPasswordForUser('role_security_read_user')) + .auth('role_security_all_user', cspSecurity.getPasswordForUser('role_security_all_user')) .send({ action: 'mute', rules: [ diff --git a/x-pack/test/cloud_security_posture_api/routes/helper/user_roles_utilites.ts b/x-pack/test/cloud_security_posture_api/routes/helper/user_roles_utilites.ts index ef66b58d28311..aac6b19ddbef6 100644 --- a/x-pack/test/cloud_security_posture_api/routes/helper/user_roles_utilites.ts +++ b/x-pack/test/cloud_security_posture_api/routes/helper/user_roles_utilites.ts @@ -129,6 +129,24 @@ export function CspSecurityCommonProvider(providerContext: FtrProviderContext) { }, ], }, + { + name: 'role_security_all', + elasticsearch: { + indices: securityUserIndinces, + }, + kibana: [ + { + base: [], + feature: { + siem: ['all'], + fleet: ['all'], + fleetv2: ['all'], + savedObjectsManagement: ['all'], + }, + spaces: ['*'], + }, + ], + }, ]; const users = [ @@ -140,7 +158,7 @@ export function CspSecurityCommonProvider(providerContext: FtrProviderContext) { }, { name: 'role_security_read_user_alerts', - full_name: 'user with 0 security privilege for', + full_name: 'user with 0 security privilege', password: 'csp123', roles: ['role_security_read_alerts'], }, @@ -152,10 +170,16 @@ export function CspSecurityCommonProvider(providerContext: FtrProviderContext) { }, { name: 'role_security_no_read_user_alerts', - full_name: 'user with 0 security privilege for', + full_name: 'user with 0 security privilege', password: 'csp123', roles: ['role_security_no_read_alerts'], }, + { + name: 'role_security_all_user', + full_name: 'user with all security privilege', + password: 'csp123', + roles: ['role_security_all'], + }, ]; return { diff --git a/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts b/x-pack/test/cloud_security_posture_functional/agentless/create_agent.ts new file mode 100644 index 0000000000000..aa1a079262cba --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/agentless/create_agent.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 { CLOUD_CREDENTIALS_PACKAGE_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; +import * as http from 'http'; +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../ftr_provider_context'; +import { setupMockServer } from './mock_agentless_api'; +// eslint-disable-next-line import/no-default-export +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const mockAgentlessApiService = setupMockServer(); + const pageObjects = getPageObjects([ + 'common', + 'cspSecurity', + 'security', + 'header', + 'cisAddIntegration', + ]); + + const CIS_AWS_OPTION_TEST_ID = 'cisAwsTestId'; + + const AWS_SINGLE_ACCOUNT_TEST_ID = 'awsSingleTestId'; + + describe('Agentless cloud', function () { + let cisIntegration: typeof pageObjects.cisAddIntegration; + let mockApiServer: http.Server; + + before(async () => { + cisIntegration = pageObjects.cisAddIntegration; + mockApiServer = await mockAgentlessApiService.listen(8089); // Start the usage api mock server on port 8081 + }); + + after(async () => { + await await pageObjects.cspSecurity.logout(); + mockApiServer.close(); + }); + + it(`should create agentless-agent`, async () => { + const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; + await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( + CLOUD_CREDENTIALS_PACKAGE_VERSION + ); + + await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); + await cisIntegration.clickOptionButton(AWS_SINGLE_ACCOUNT_TEST_ID); + + await cisIntegration.inputIntegrationName(integrationPolicyName); + + await cisIntegration.selectSetupTechnology('agentless'); + await cisIntegration.selectAwsCredentials('direct'); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + await cisIntegration.clickSaveButton(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await cisIntegration.navigateToIntegrationCspList(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be( + integrationPolicyName + ); + expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be( + `Agentless policy for ${integrationPolicyName}` + ); + }); + + it(`should create default agent-based agent`, async () => { + const integrationPolicyName = `cloud_security_posture-${new Date().toISOString()}`; + + await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( + CLOUD_CREDENTIALS_PACKAGE_VERSION + ); + + await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); + await cisIntegration.clickOptionButton(AWS_SINGLE_ACCOUNT_TEST_ID); + + await cisIntegration.inputIntegrationName(integrationPolicyName); + + await cisIntegration.clickSaveButton(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const agentPolicyName = await cisIntegration.getAgentBasedPolicyValue(); + + await cisIntegration.navigateToIntegrationCspList(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + expect(await cisIntegration.getFirstCspmIntegrationPageIntegration()).to.be( + integrationPolicyName + ); + expect(await cisIntegration.getFirstCspmIntegrationPageAgent()).to.be(agentPolicyName); + }); + }); +} diff --git a/x-pack/test/cloud_security_posture_functional/agentless/mock_agentless_api.ts b/x-pack/test/cloud_security_posture_functional/agentless/mock_agentless_api.ts new file mode 100644 index 0000000000000..129adde9c0018 --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/agentless/mock_agentless_api.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 { createServer } from '@mswjs/http-middleware'; + +import { http, HttpResponse, StrictResponse } from 'msw'; + +export const setupMockServer = () => { + const server = createServer(deploymentHandler); + return server; +}; + +interface AgentlessApiResponse { + status: number; +} + +const deploymentHandler = http.post( + 'agentless-api/api/v1/ess/deployments', + async ({ request }): Promise<StrictResponse<AgentlessApiResponse>> => { + return HttpResponse.json({ + status: 200, + }); + } +); diff --git a/x-pack/test/cloud_security_posture_functional/cloud_tests/benchmark_sanity.ts b/x-pack/test/cloud_security_posture_functional/cloud_tests/benchmark_sanity.ts index 277c1038e51ae..1dcbbac1d991e 100644 --- a/x-pack/test/cloud_security_posture_functional/cloud_tests/benchmark_sanity.ts +++ b/x-pack/test/cloud_security_posture_functional/cloud_tests/benchmark_sanity.ts @@ -34,14 +34,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const benchmarksRows = await benchmark.benchmarkPage.getBenchmarkTableRows(); for (const row of benchmarksRows) { const benchmarkName = await benchmark.benchmarkPage.getCisNameCellData(row); - const evaluated = await benchmark.benchmarkPage.getEvaluatedCellData(row); - const compliance = await benchmark.benchmarkPage.getComplianceCellData(row); - expect(await evaluated).to.not.contain( - 'Add', + const isEvaluationEmpty = await benchmark.benchmarkPage.isEvaluationEmpty(row); + const isComplianceEmpty = await benchmark.benchmarkPage.isComplianceEmpty(row); + + expect(isEvaluationEmpty).to.eql( + false, `The ${benchmarkName} does not have evaluated data` ); - expect(await compliance).to.not.contain( - 'No', + + expect(isComplianceEmpty).to.eql( + false, `The ${benchmarkName} does not have compliance data` ); } diff --git a/x-pack/test/cloud_security_posture_functional/cloud_tests/dashboard_sanity.ts b/x-pack/test/cloud_security_posture_functional/cloud_tests/dashboard_sanity.ts index 6eaafcb154134..3808c3d843487 100644 --- a/x-pack/test/cloud_security_posture_functional/cloud_tests/dashboard_sanity.ts +++ b/x-pack/test/cloud_security_posture_functional/cloud_tests/dashboard_sanity.ts @@ -73,7 +73,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const findingsLinkCount = await dashboard.getFindingsLinksCount(TAB_TYPES.CLOUD); for (let i = 0; i < findingsLinkCount; i++) { const link = await dashboard.getFindingsLinkAtIndex(TAB_TYPES.CLOUD, i); - // for (const link of findingsLink) { await link.click(); await pageObjects.header.waitUntilLoadingHasFinished(); const groupSelector = await findings.groupSelector(); diff --git a/x-pack/test/cloud_security_posture_functional/config.agentless.ts b/x-pack/test/cloud_security_posture_functional/config.agentless.ts new file mode 100644 index 0000000000000..459c814b6e08c --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/config.agentless.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 type { FtrConfigProviderContext } from '@kbn/test'; +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { pageObjects } from './page_objects'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile(require.resolve('./config.cloud.ts')); + // FTR configuration for cloud testing + return { + ...xpackFunctionalConfig.getAll(), + pageObjects, + junit: { + reportName: 'ESS Security Cloud Security Agentless Creating Agent Functional Tests', + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + `--xpack.fleet.agents.fleet_server.hosts=["https://ftr.kibana:8220"]`, + `--xpack.fleet.internal.fleetServerStandalone=true`, + `--xpack.fleet.enableExperimental.0=agentless`, + `--xpack.fleet.agentless.api.url=http://localhost:8089/agentless-api/api/v1/ess`, + `--xpack.fleet.agentless.api.tls.certificate=${KBN_CERT_PATH}`, + `--xpack.fleet.agentless.api.tls.key=${KBN_KEY_PATH}`, + `--xpack.fleet.agentless.api.tls.ca=${CA_CERT_PATH}`, + `--xpack.cloud.id=something-anything`, + ], + }, + // load tests in the index file + testFiles: [require.resolve('./agentless/create_agent.ts')], + }; +} diff --git a/x-pack/test/cloud_security_posture_functional/data_views/config.ts b/x-pack/test/cloud_security_posture_functional/data_views/config.ts new file mode 100644 index 0000000000000..2b5799cdcd010 --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/data_views/config.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 { resolve } from 'path'; +import type { FtrConfigProviderContext } from '@kbn/test'; +import { pageObjects } from '../page_objects'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile( + require.resolve('../../functional/config.base.js') + ); + + return { + ...xpackFunctionalConfig.getAll(), + pageObjects, + testFiles: [resolve(__dirname, './data_views')], + junit: { + reportName: 'X-Pack Cloud Security Posture Functional Tests', + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + /** + * Package version is fixed (not latest) so FTR won't suddenly break when package is changed. + * + * test a new package: + * 1. build the package and start the registry with elastic-package and uncomment the 'registryUrl' flag below + * 2. locally checkout the kibana version that matches the new package + * 3. update the package version below to use the new package version + * 4. run tests with NODE_EXTRA_CA_CERTS pointing to the elastic-package certificate + * 5. when test pass: + * 1. release a new package to EPR + * 2. merge the updated version number change to kibana + */ + `--xpack.fleet.agents.fleet_server.hosts=["https://ftr.kibana:8220"]`, + `--xpack.fleet.internal.fleetServerStandalone=true`, + ], + }, + }; +} diff --git a/x-pack/test/cloud_security_posture_functional/data_views/data_views.ts b/x-pack/test/cloud_security_posture_functional/data_views/data_views.ts new file mode 100644 index 0000000000000..d6da5ff3cfe8a --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/data_views/data_views.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { DataViewAttributes } from '@kbn/data-views-plugin/common'; +import { CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX } from '@kbn/cloud-security-posture-plugin/common/constants'; +import { KbnClientSavedObjects } from '@kbn/test/src/kbn_client/kbn_client_saved_objects'; +import { FtrProviderContext } from '../ftr_provider_context'; + +const TEST_SPACE = 'space-1'; + +const getDataViewSafe = async ( + soClient: KbnClientSavedObjects, + dataViewId: string, + currentSpaceId: string +): Promise<boolean> => { + try { + await soClient.get<DataViewAttributes>({ + type: 'index-pattern', + id: dataViewId, + space: currentSpaceId, + }); + return true; + } catch (e) { + return false; + } +}; +// eslint-disable-next-line import/no-default-export +export default ({ getService, getPageObjects }: FtrProviderContext) => { + const kibanaServer = getService('kibanaServer'); + const spacesService = getService('spaces'); + + const pageObjects = getPageObjects([ + 'common', + 'findings', + 'cloudPostureDashboard', + 'header', + 'spaceSelector', + 'cspSecurity', + ]); + + // FLAKY: https://github.com/elastic/kibana/issues/189854 + describe.skip('Data Views', async function () { + this.tags(['cloud_security_posture_data_views', 'cloud_security_posture_spaces']); + let cspSecurity = pageObjects.cspSecurity; + let findings: typeof pageObjects.findings; + + before(async () => { + cspSecurity = pageObjects.cspSecurity; + findings = pageObjects.findings; + await cspSecurity.createRoles(); + await cspSecurity.createUsers(); + }); + + afterEach(async () => { + await kibanaServer.savedObjects.clean({ + types: ['index-pattern'], + space: 'default', + }); + await kibanaServer.savedObjects.clean({ + types: ['index-pattern'], + space: TEST_SPACE, + }); + + await spacesService.delete(TEST_SPACE); + }); + + it('Verify data view is created once user reach the findings page - default space', async () => { + const expectedDataViewId = `${CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX}-default`; + const idDataViewExists = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + 'default' + ); + expect(idDataViewExists).to.be(false); + + await findings.navigateToLatestVulnerabilitiesPage(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const idDataViewExistsPostFindingsNavigation = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + 'default' + ); + expect(idDataViewExistsPostFindingsNavigation).to.be(true); + }); + + it('Verify data view is created once user reach the dashboard page - default space', async () => { + const expectedDataViewId = `${CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX}-default`; + const idDataViewExists = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + 'default' + ); + expect(idDataViewExists).to.be(false); + + const cspDashboard = pageObjects.cloudPostureDashboard; + await cspDashboard.navigateToComplianceDashboardPage(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const idDataViewExistsPostFindingsNavigation = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + 'default' + ); + expect(idDataViewExistsPostFindingsNavigation).to.be(true); + }); + + it('Verify data view is created once user reach the findings page - non default space', async () => { + await pageObjects.common.navigateToApp('home'); + await spacesService.create({ id: TEST_SPACE, name: 'space_one', disabledFeatures: [] }); + await pageObjects.spaceSelector.openSpacesNav(); + await pageObjects.spaceSelector.clickSpaceAvatar(TEST_SPACE); + await pageObjects.spaceSelector.expectHomePage(TEST_SPACE); + + const expectedDataViewId = `${CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX}-${TEST_SPACE}`; + const idDataViewExists = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + TEST_SPACE + ); + + expect(idDataViewExists).to.be(false); + + await findings.navigateToLatestFindingsPage(TEST_SPACE); + await pageObjects.header.waitUntilLoadingHasFinished(); + const idDataViewExistsPostFindingsNavigation = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + TEST_SPACE + ); + + expect(idDataViewExistsPostFindingsNavigation).to.be(true); + }); + + it('Verify data view is created once user reach the dashboard page - non default space', async () => { + // await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.common.navigateToApp('home'); + await spacesService.create({ id: TEST_SPACE, name: 'space_one', disabledFeatures: [] }); + await pageObjects.spaceSelector.openSpacesNav(); + await pageObjects.spaceSelector.clickSpaceAvatar(TEST_SPACE); + await pageObjects.spaceSelector.expectHomePage(TEST_SPACE); + const expectedDataViewId = `${CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX}-${TEST_SPACE}`; + const idDataViewExists = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + TEST_SPACE + ); + + expect(idDataViewExists).to.be(false); + + const cspDashboard = pageObjects.cloudPostureDashboard; + await cspDashboard.navigateToComplianceDashboardPage(TEST_SPACE); + await pageObjects.header.waitUntilLoadingHasFinished(); + const idDataViewExistsPostFindingsNavigation = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + TEST_SPACE + ); + + expect(idDataViewExistsPostFindingsNavigation).to.be(true); + }); + + it('Verify data view is created once user with read permissions reach the dashboard page', async () => { + await pageObjects.common.navigateToApp('home'); + await cspSecurity.logout(); + await cspSecurity.login('csp_read_user'); + const expectedDataViewId = `${CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX}-default`; + const idDataViewExists = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + 'default' + ); + + expect(idDataViewExists).to.be(false); + + const cspDashboard = pageObjects.cloudPostureDashboard; + await cspDashboard.navigateToComplianceDashboardPage(); + await pageObjects.header.waitUntilLoadingHasFinished(); + const idDataViewExistsPostFindingsNavigation = await getDataViewSafe( + kibanaServer.savedObjects, + expectedDataViewId, + 'default' + ); + + expect(idDataViewExistsPostFindingsNavigation).to.be(true); + }); + }); +}; diff --git a/x-pack/test/cloud_security_posture_functional/data_views/index.ts b/x-pack/test/cloud_security_posture_functional/data_views/index.ts new file mode 100644 index 0000000000000..f8f5d555e9937 --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/data_views/index.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 { FtrProviderContext } from '../ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Cloud Security Posture', function () { + loadTestFile(require.resolve('./data_views')); + }); +} diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts index 7a15d629d0be4..0c4581f21a829 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts @@ -17,6 +17,10 @@ export function AddCisIntegrationFormPageProvider({ const PageObjects = getPageObjects(['common', 'header']); const browser = getService('browser'); + const SETUP_TECHNOLOGY_SELECTOR = 'setup-technology-selector'; + const SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ = 'setup-technology-selector-accordion'; + const AWS_CREDENTIAL_SELECTOR = 'aws-credentials-type-selector'; + const cisAzure = { getPostInstallArmTemplateModal: async () => { return await testSubjects.find('postInstallAzureArmTemplateModal'); @@ -112,38 +116,77 @@ export function AddCisIntegrationFormPageProvider({ return fieldValue; }; - const navigateToAddIntegrationCspmPage = async () => { + const navigateToAddIntegrationCspmPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'fleet', // Defined in Security Solution plugin 'integrations/cloud_security_posture/add-integration/cspm', - { shouldUseHashForSubUrl: false } + options ); await PageObjects.header.waitUntilLoadingHasFinished(); }; - const navigateToAddIntegrationCspmWithVersionPage = async (packageVersion: string) => { + const navigateToAddIntegrationCspmWithVersionPage = async ( + packageVersion: string, + space?: string + ) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'fleet', `integrations/cloud_security_posture-${packageVersion}/add-integration/cspm`, - { shouldUseHashForSubUrl: false } + options ); await PageObjects.header.waitUntilLoadingHasFinished(); }; - const navigateToAddIntegrationCnvmPage = async () => { + const navigateToAddIntegrationCnvmPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'fleet', // Defined in Security Solution plugin 'integrations/cloud_security_posture/add-integration/vuln_mgmt', - { shouldUseHashForSubUrl: false } + options ); await PageObjects.header.waitUntilLoadingHasFinished(); }; - const navigateToAddIntegrationKspmPage = async () => { + const navigateToAddIntegrationKspmPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'fleet', // Defined in Security Solution plugin 'integrations/cloud_security_posture/add-integration/kspm', - { shouldUseHashForSubUrl: false } + options ); await PageObjects.header.waitUntilLoadingHasFinished(); }; @@ -207,6 +250,25 @@ export function AddCisIntegrationFormPageProvider({ await advancedAccordian.click(); }; + const selectSetupTechnology = async (setupTechnology: 'agentless' | 'agent-based') => { + await clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); + await clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); + + const agentOption = await testSubjects.find( + setupTechnology === 'agentless' + ? 'setup-technology-agentless-option' + : 'setup-technology-agent-based-option' + ); + await agentOption.click(); + }; + const selectAwsCredentials = async (credentialType: 'direct' | 'temporary') => { + await clickOptionButton(AWS_CREDENTIAL_SELECTOR); + await selectValue( + AWS_CREDENTIAL_SELECTOR, + credentialType === 'direct' ? 'direct_access_keys' : 'temporary_keys' + ); + }; + const clickOptionButton = async (text: string) => { const optionToBeClicked = await findOptionInPage(text); await optionToBeClicked.scrollIntoView(); @@ -292,11 +354,34 @@ export function AddCisIntegrationFormPageProvider({ await nameField[0].type(uuidv4()); }; + const inputIntegrationName = async (text: string) => { + const page = await testSubjects.find('createPackagePolicy_page'); + const nameField = await page.findAllByCssSelector('input[id="name"]'); + await nameField[0].clearValueWithKeyboard(); + await nameField[0].type(text); + }; + const getSecretComponentReplaceButton = async (secretButtonSelector: string) => { const secretComponentReplaceButton = await testSubjects.find(secretButtonSelector); return secretComponentReplaceButton; }; + const getFirstCspmIntegrationPageIntegration = async () => { + const integration = await testSubjects.find('integrationNameLink'); + return await integration.getVisibleText(); + }; + + const getFirstCspmIntegrationPageAgent = async () => { + const agent = await testSubjects.find('agentPolicyNameLink'); + // this is assuming that the agent was just created therefor should be the first element + return await agent.getVisibleText(); + }; + + const getAgentBasedPolicyValue = async () => { + const agentName = await testSubjects.find('createAgentPolicyNameField'); + return await agentName.getAttribute('value'); + }; + return { cisAzure, cisAws, @@ -317,6 +402,8 @@ export function AddCisIntegrationFormPageProvider({ getIntegrationFormEditPage, findOptionInPage, clickOptionButton, + selectAwsCredentials, + selectSetupTechnology, clickSaveButton, clickSaveIntegrationButton, clickAccordianButton, @@ -333,5 +420,9 @@ export function AddCisIntegrationFormPageProvider({ getReplaceSecretButton, getSecretComponentReplaceButton, inputUniqueIntegrationName, + inputIntegrationName, + getFirstCspmIntegrationPageIntegration, + getFirstCspmIntegrationPageAgent, + getAgentBasedPolicyValue, }; } diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/benchmark_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/benchmark_page.ts index 25ae1181f38b5..438f79dbc3332 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/benchmark_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/benchmark_page.ts @@ -10,6 +10,7 @@ import { ELASTIC_HTTP_VERSION_HEADER, X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; +import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; import type { FtrProviderContext } from '../ftr_provider_context'; export const CSP_BECNHMARK_TABLE = 'csp_benchmarks_table'; @@ -43,32 +44,66 @@ export function BenchmarkPagePageProvider({ getService, getPageObjects }: FtrPro getBenchmarkTableRows: async () => { const benchmarkTable = await testSubjects.find(CSP_BECNHMARK_TABLE); - return await benchmarkTable.findAllByXpath(`//tbody//tr`); + const tableRows = await benchmarkTable.findAllByXpath(`//tbody//tr`); + return tableRows; }, - getCellData: async (row: any, cellDataTestSubj: string) => { + getCellData: async (row: WebElementWrapper, cellDataTestSubj: string) => { const cell = await row.findByTestSubject(cellDataTestSubj); return await cell.getVisibleText(); }, - getEvaluatedCellData: async (row: any) => { + getEvaluatedCellData: async (row: WebElementWrapper) => { return await benchmarkPage.getCellData(row, 'benchmark-table-column-evaluated'); }, - getComplianceCellData: async (row: any) => { + getComplianceCellData: async (row: WebElementWrapper) => { return await benchmarkPage.getCellData(row, 'benchmark-table-column-compliance'); }, - getCisNameCellData: async (row: any) => { + getCisNameCellData: async (row: WebElementWrapper) => { return await benchmarkPage.getCellData(row, 'benchmark-table-column-cis-name'); }, + + isEvaluationEmpty: async (row: WebElementWrapper) => { + try { + const notEvaluated = await row.findAllByTestSubject('benchmark-not-evaluated-account', 200); + return notEvaluated.length > 0; + } catch (error) { + if (error.name === 'StaleElementReferenceError' || error.name === 'NoSuchElementError') { + return false; + } + throw error; + } + }, + + isComplianceEmpty: async (row: WebElementWrapper) => { + try { + const noCompliance = await row.findAllByTestSubject('benchmark-score-no-findings', 200); + return noCompliance.length > 0; + } catch (error) { + if (error.name === 'StaleElementReferenceError' || error.name === 'NoSuchElementError') { + return false; + } + throw error; + } + }, }; - const navigateToBenchnmarkPage = async () => { + const navigateToBenchnmarkPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'securitySolution', // Defined in Security Solution plugin `cloud_security_posture/benchmarks/`, - { shouldUseHashForSubUrl: false } + options ); await PageObjects.header.waitUntilLoadingHasFinished(); }; diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts index 9e2105cf367f5..8827437a19332 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/csp_dashboard_page.ts @@ -105,7 +105,16 @@ export function CspDashboardPageProvider({ getService, getPageObjects }: FtrProv getFindingsLinks: async (tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES]) => { await dashboard.getDashoard(tab); const pageContainer = await testSubjects.find('pageContainer'); - return await pageContainer.findAllByXpath(`//button[contains(@class, 'euiLink')]`); + return [ + await pageContainer.findByTestSubject('dashboard-summary-passed-findings'), + await pageContainer.findByTestSubject('dashboard-summary-failed-findings'), + ...(await pageContainer.findAllByTestSubject('grouped-findings-evaluation-link')), + ...(await pageContainer.findAllByTestSubject('view-all-failed-findings')), + ...(await pageContainer.findAllByTestSubject('benchmark-section-bench-name')), + ...(await pageContainer.findAllByTestSubject('benchmark-asset-type')), + ...(await pageContainer.findAllByTestSubject('compliance-score-section-passed')), + ...(await pageContainer.findAllByTestSubject('compliance-score-section-failed')), + ]; }, getFindingsLinkAtIndex: async ( @@ -113,12 +122,12 @@ export function CspDashboardPageProvider({ getService, getPageObjects }: FtrProv linkIndex = 0 ) => { const allLinks = await dashboard.getFindingsLinks(tab); - return await allLinks[linkIndex]; + return allLinks[linkIndex]; }, getFindingsLinksCount: async (tab: (typeof TAB_TYPES)[keyof typeof TAB_TYPES]) => { const allLinks = await dashboard.getFindingsLinks(tab); - return await allLinks.length; + return allLinks.length; }, getIntegrationDashboardContainer: () => testSubjects.find('dashboard-container'), @@ -187,11 +196,20 @@ export function CspDashboardPageProvider({ getService, getPageObjects }: FtrProv }, }; - const navigateToComplianceDashboardPage = async () => { + const navigateToComplianceDashboardPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'securitySolution', // Defined in Security Solution plugin 'cloud_security_posture/dashboard', - { shouldUseHashForSubUrl: false } + options ); }; diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts index 17cd9f581c6be..c2cc93ccb1aaf 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/findings_page.ts @@ -235,27 +235,53 @@ export function FindingsPageProvider({ getService, getPageObjects }: FtrProvider }, }); - const navigateToLatestFindingsPage = async () => { + const navigateToLatestFindingsPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'securitySolution', // Defined in Security Solution plugin 'cloud_security_posture/findings/configurations', - { shouldUseHashForSubUrl: false } + options ); }; - const navigateToLatestVulnerabilitiesPage = async () => { + const navigateToLatestVulnerabilitiesPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; await PageObjects.common.navigateToUrl( 'securitySolution', // Defined in Security Solution plugin 'cloud_security_posture/findings/vulnerabilities', - { shouldUseHashForSubUrl: false } + options ); }; - const navigateToMisconfigurations = async () => { + const navigateToMisconfigurations = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'securitySolution', // Defined in Security Solution plugin 'cloud_security_posture/findings/configurations', - { shouldUseHashForSubUrl: false } + options ); }; diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts index 4d0d3034306af..5ed732ba34a90 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts @@ -191,11 +191,24 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider }, }; - const navigateToRulePage = async (benchmarkCisId: string, benchmarkCisVersion: string) => { + const navigateToRulePage = async ( + benchmarkCisId: string, + benchmarkCisVersion: string, + space?: string + ) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'securitySolution', // Defined in Security Solution plugin `cloud_security_posture/benchmarks/${benchmarkCisId}/${benchmarkCisVersion}/rules`, - { shouldUseHashForSubUrl: false } + options ); await PageObjects.header.waitUntilLoadingHasFinished(); }; diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/vulnerability_dashboard_page_object.ts b/x-pack/test/cloud_security_posture_functional/page_objects/vulnerability_dashboard_page_object.ts index 688786972debd..a53dd82f841d5 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/vulnerability_dashboard_page_object.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/vulnerability_dashboard_page_object.ts @@ -37,11 +37,20 @@ export function VulnerabilityDashboardPageProvider({ log.debug('CSP plugin is initialized'); }); - const navigateToVulnerabilityDashboardPage = async () => { + const navigateToVulnerabilityDashboardPage = async (space?: string) => { + const options = space + ? { + basePath: `/s/${space}`, + shouldUseHashForSubUrl: false, + } + : { + shouldUseHashForSubUrl: false, + }; + await PageObjects.common.navigateToUrl( 'securitySolution', // Defined in Security Solution plugin 'cloud_security_posture/vulnerability_dashboard', - { shouldUseHashForSubUrl: false } + options ); }; diff --git a/x-pack/test/cloud_security_posture_functional/pages/findings.ts b/x-pack/test/cloud_security_posture_functional/pages/findings.ts index 3ea3475b85763..35f1f0b5cbff4 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/findings.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/findings.ts @@ -44,6 +44,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'Upper case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': timeFiveHoursAgo, @@ -61,6 +64,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'Another Upper case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': timeFiveHoursAgo, @@ -78,6 +84,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'lower case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': timeFiveHoursAgo, @@ -95,6 +104,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'another lower case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, ]; diff --git a/x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts b/x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts index 1c345b68caf7b..26e146338a6e8 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/findings_alerts.ts @@ -40,6 +40,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'Upper case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -62,6 +65,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'Another Upper case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -84,6 +90,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'lower case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': new Date(Date.now() - 60 * 60 * 1000).toISOString(), @@ -106,12 +115,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: 'process', }, cluster_id: 'another lower case cluster id', + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, ]; const ruleName1 = data[0].rule.name; - describe('Findings Page - Alerts', function () { + // Failing: See https://github.com/elastic/kibana/issues/168991 + describe.skip('Findings Page - Alerts', function () { this.tags(['cloud_security_posture_findings_alerts']); let findings: typeof pageObjects.findings; let latestFindingsTable: typeof findings.latestFindingsTable; @@ -195,7 +208,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.click('csp:toast-success-link'); await pageObjects.header.waitUntilLoadingHasFinished(); const rulePageTitle = await testSubjects.find('header-page-title'); - expect(await rulePageTitle.getVisibleText()).to.be(ruleName1); + // Rule page title is not immediately available, so we need to retry until it is + await retry.try(async () => { + expect(await rulePageTitle.getVisibleText()).to.be(ruleName1); + }); }); }); describe('Rule details', () => { diff --git a/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts b/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts index ff77372b5ed23..acfd5918afe9f 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/findings_grouping.ts @@ -53,6 +53,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, type: 'process', }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': new Date().toISOString(), @@ -75,6 +78,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, type: 'process', }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': new Date().toISOString(), @@ -97,6 +103,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, type: 'process', }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, { '@timestamp': new Date().toISOString(), @@ -119,6 +128,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, type: 'process', }, + data_stream: { + dataset: 'cloud_security_posture.findings', + }, }, ]; diff --git a/x-pack/test/common/services/bsearch_secure.ts b/x-pack/test/common/services/bsearch_secure.ts index 01050d1bb60c0..f454aa3818ea6 100644 --- a/x-pack/test/common/services/bsearch_secure.ts +++ b/x-pack/test/common/services/bsearch_secure.ts @@ -10,10 +10,10 @@ import expect from '@kbn/expect'; import request from 'superagent'; -import type SuperTest from 'supertest'; import type { IEsSearchResponse } from '@kbn/search-types'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { FtrService } from '../ftr_provider_context'; const parseBfetchResponse = (resp: request.Response): Array<Record<string, any>> => { @@ -28,7 +28,7 @@ const getSpaceUrlPrefix = (spaceId?: string): string => { }; interface SendOptions { - supertestWithoutAuth: SuperTest.Agent; + supertestWithoutAuth: SupertestWithoutAuthProviderType; auth: { username: string; password: string }; referer?: string; kibanaVersion?: string; diff --git a/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts b/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts index 5649031185feb..5c0500f89ef51 100644 --- a/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts +++ b/x-pack/test/common/utils/security_solution/detections_response/rules/get_rule_for_alert_testing.ts @@ -30,3 +30,18 @@ export const getRuleForAlertTesting = ( query: '*:*', from: '1900-01-01T00:00:00.000Z', }); + +export const getLuceneRuleForTesting = (): QueryRuleCreateProps => ({ + rule_id: 'lucene-rule-1', + enabled: true, + name: 'Incident 496 test rule', + description: 'Ensures lucene rules generate alerts', + risk_score: 1, + severity: 'high', + type: 'query', + index: ['auditbeat-*'], + query: + '((event.category: (network OR network_traffic) AND type: (tls OR http)) OR event.dataset: (network_traffic.tls OR network_traffic.http)) AND destination.domain:/[a-z]{3}.stage.[0-9]{8}..*/', + language: 'lucene', + from: '1900-01-01T00:00:00.000Z', +}); diff --git a/x-pack/test/defend_workflows_cypress/config.ts b/x-pack/test/defend_workflows_cypress/config.ts index 09c08fca6996c..a8502edcabe24 100644 --- a/x-pack/test/defend_workflows_cypress/config.ts +++ b/x-pack/test/defend_workflows_cypress/config.ts @@ -48,9 +48,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.fleet.agents.elasticsearch.host=http://${hostIp}:${kibanaCommonTestsConfig.get( 'servers.elasticsearch.port' )}`, - `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'endpointResponseActionsEnabled', - ])}`, ], }, }; diff --git a/x-pack/test/encrypted_saved_objects_api_integration/services.ts b/x-pack/test/encrypted_saved_objects_api_integration/services.ts index a04db8ad9da8c..153fd62b1ced5 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/services.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/services.ts @@ -6,9 +6,7 @@ */ import { services as commonServices } from '../common/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; export const services = { ...commonServices, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/fleet_api_integration/apis/epm/final_pipeline.ts b/x-pack/test/fleet_api_integration/apis/epm/final_pipeline.ts index e5cf4c241767a..157e0178f0ca4 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/final_pipeline.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/final_pipeline.ts @@ -34,8 +34,7 @@ export default function (providerContext: FtrProviderContext) { .expect(201); } - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/180071 - describe.skip('fleet_final_pipeline', () => { + describe('fleet_final_pipeline', () => { skipIfNoDockerRegistry(providerContext); before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); @@ -157,9 +156,7 @@ export default function (providerContext: FtrProviderContext) { body: { message: 'message-test-1', event: { - original: { - foo: 'bar', - }, + original: JSON.stringify({ foo: 'bar' }), }, '@timestamp': '2023-01-01T09:00:00', tags: [], @@ -185,9 +182,7 @@ export default function (providerContext: FtrProviderContext) { body: { message: 'message-test-1', event: { - original: { - foo: 'bar', - }, + original: JSON.stringify({ foo: 'bar' }), }, '@timestamp': '2023-01-01T09:00:00', tags: ['preserve_original_event'], @@ -204,7 +199,7 @@ export default function (providerContext: FtrProviderContext) { const event = doc._source.event; - expect(event.original).to.eql({ foo: 'bar' }); + expect(event.original).to.eql(JSON.stringify({ foo: 'bar' })); }); const scenarios = [ 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 bcd4421f493d7..9fbdfd6dfee60 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 @@ -26,12 +26,12 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }); }; - describe('TESTME Installing custom integrations', async () => { + describe('Installing custom integrations', async () => { afterEach(async () => { await uninstallPackage(); }); - it('Correcty installs a custom integration and all of its assets', async () => { + it('Correctly installs a custom integration and all of its assets', async () => { const response = await supertest .post(`/api/fleet/epm/custom_integrations`) .set('kbn-xsrf', 'xxxx') @@ -98,9 +98,17 @@ export default function (providerContext: FtrProviderContext) { expect(installation.attributes.version).to.be(INTEGRATION_VERSION); expect(installation.attributes.install_source).to.be('custom'); expect(installation.attributes.install_status).to.be('installed'); + + for (const indexTemplate of actualIndexTemplates) { + const templateResponse = await esClient.indices.getIndexTemplate({ name: indexTemplate }); + + expect(templateResponse.index_templates[0].index_template.composed_of).to.contain( + 'ecs@mappings' + ); + } }); - it('Correctly sets up index templates to create an ECS compliant mapping', async () => { + it('Includes custom integration metadata', async () => { await supertest .post(`/api/fleet/epm/custom_integrations`) .set('kbn-xsrf', 'xxxx') @@ -118,413 +126,16 @@ export default function (providerContext: FtrProviderContext) { await esClient.index({ index: indexName, document: { - 'cloud.account.id': 'xyz', - 'cloud.availability_zone': 'xyz', - 'cloud.instance.id': 'xyz', - 'cloud.instance.name': 'xyz', - 'cloud.machine.type': 'xyz', - 'cloud.provider': 'xyz', - 'cloud.region': 'xyz', - 'cloud.project.id': 'xyz', - 'cloud.image.id': 'xyz', - 'container.id': 'xyz', - 'container.image.name': 'xyz', - 'container.labels': { foo_id: 'beef42' }, - 'container.name': 'xyz', - 'host.architecture': 'xyz', - 'host.domain': 'xyz', - 'host.hostname': 'xyz', - 'host.id': 'xyz', - 'host.ip': '1.1.1.1', - 'host.mac': 'xyz', - 'host.name': 'xyz', - 'host.os.family': 'xyz', - 'host.os.kernel': 'xyz', - 'host.os.name': 'xyz', - 'host.os.platform': 'xyz', - 'host.os.version': 'xyz', - 'host.os.type': 'xyz', - 'host.os.containerized': true, - 'host.os.build': 'xyz', - 'host.os.codename': 'xyz', - 'input.type': 'xyz', - 'log.offset': 123, - 'data_stream.type': 'logs', + foo: 'bar', }, }); + const response = await esClient.indices.getMapping({ index: indexName }); - expect(Object.values(response)[0].mappings).to.eql({ - subobjects: false, - _meta: { - managed_by: 'fleet', - managed: true, - package: { - name: 'my_nginx', - }, - }, - _data_stream_timestamp: { - enabled: true, - }, - dynamic_templates: [ - { - ecs_timestamp: { - mapping: { - ignore_malformed: false, - type: 'date', - }, - match: '@timestamp', - }, - }, - { - ecs_message_match_only_text: { - mapping: { - type: 'match_only_text', - }, - path_match: ['message', '*.message'], - unmatch_mapping_type: 'object', - }, - }, - { - ecs_non_indexed_keyword: { - mapping: { - doc_values: false, - index: false, - type: 'keyword', - }, - path_match: 'event.original', - }, - }, - { - ecs_non_indexed_long: { - mapping: { - doc_values: false, - index: false, - type: 'long', - }, - path_match: '*.x509.public_key_exponent', - }, - }, - { - ecs_ip: { - path_match: ['ip', '*.ip', '*_ip'], - match_mapping_type: 'string', - mapping: { - type: 'ip', - }, - }, - }, - { - ecs_wildcard: { - path_match: ['*.io.text', '*.message_id', '*registry.data.strings', '*url.path'], - unmatch_mapping_type: 'object', - mapping: { - type: 'wildcard', - }, - }, - }, - { - ecs_path_match_wildcard_and_match_only_text: { - path_match: ['*.body.content', '*url.full', '*url.original'], - unmatch_mapping_type: 'object', - mapping: { - fields: { - text: { - type: 'match_only_text', - }, - }, - type: 'wildcard', - }, - }, - }, - { - ecs_match_wildcard_and_match_only_text: { - match: ['*command_line', '*stack_trace'], - unmatch_mapping_type: 'object', - mapping: { - fields: { - text: { - type: 'match_only_text', - }, - }, - type: 'wildcard', - }, - }, - }, - { - ecs_path_match_keyword_and_match_only_text: { - path_match: [ - '*.title', - '*.executable', - '*.name', - '*.working_directory', - '*.full_name', - '*file.path', - '*file.target_path', - '*os.full', - 'email.subject', - 'vulnerability.description', - 'user_agent.original', - ], - unmatch_mapping_type: 'object', - mapping: { - fields: { - text: { - type: 'match_only_text', - }, - }, - type: 'keyword', - }, - }, - }, - { - ecs_date: { - path_match: [ - '*.timestamp', - '*_timestamp', - '*.not_after', - '*.not_before', - '*.accessed', - 'created', - '*.created', - '*.installed', - '*.creation_date', - '*.ctime', - '*.mtime', - 'ingested', - '*.ingested', - '*.start', - '*.end', - ], - unmatch_mapping_type: 'object', - mapping: { - type: 'date', - }, - }, - }, - { - ecs_path_match_float: { - path_match: ['*.score.*', '*_score*'], - path_unmatch: '*.version', - unmatch_mapping_type: 'object', - mapping: { - type: 'float', - }, - }, - }, - { - ecs_usage_double_scaled_float: { - path_match: '*.usage', - match_mapping_type: ['double', 'long', 'string'], - mapping: { - scaling_factor: 1000, - type: 'scaled_float', - }, - }, - }, - { - ecs_geo_point: { - path_match: '*.geo.location', - mapping: { - type: 'geo_point', - }, - }, - }, - { - ecs_flattened: { - path_match: ['*structured_data', '*exports', '*imports'], - match_mapping_type: 'object', - mapping: { - type: 'flattened', - }, - }, - }, - { - all_strings_to_keywords: { - match_mapping_type: 'string', - mapping: { - ignore_above: 1024, - type: 'keyword', - }, - }, - }, - { - strings_as_keyword: { - match_mapping_type: 'string', - mapping: { - ignore_above: 1024, - type: 'keyword', - }, - }, - }, - ], - date_detection: false, - properties: { - '@timestamp': { - type: 'date', - ignore_malformed: false, - }, - 'cloud.account.id': { - type: 'keyword', - ignore_above: 1024, - }, - 'cloud.availability_zone': { - type: 'keyword', - ignore_above: 1024, - }, - 'cloud.image.id': { - type: 'keyword', - ignore_above: 1024, - }, - 'cloud.instance.id': { - type: 'keyword', - ignore_above: 1024, - }, - 'cloud.instance.name': { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - 'cloud.machine.type': { - type: 'keyword', - ignore_above: 1024, - }, - 'cloud.project.id': { - type: 'keyword', - ignore_above: 1024, - }, - 'cloud.provider': { - type: 'keyword', - ignore_above: 1024, - }, - 'cloud.region': { - type: 'keyword', - ignore_above: 1024, - }, - 'container.id': { - type: 'keyword', - ignore_above: 1024, - }, - 'container.image.name': { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - 'container.labels.foo_id': { - type: 'keyword', - ignore_above: 1024, - }, - 'container.name': { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - 'data_stream.dataset': { - type: 'constant_keyword', - }, - 'data_stream.namespace': { - type: 'constant_keyword', - }, - 'data_stream.type': { - type: 'constant_keyword', - value: 'logs', - }, - 'event.agent_id_status': { - type: 'keyword', - ignore_above: 1024, - }, - 'event.ingested': { - type: 'date', - format: 'strict_date_time_no_millis||strict_date_optional_time||epoch_millis', - ignore_malformed: false, - }, - 'host.architecture': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.domain': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.hostname': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.id': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.ip': { - type: 'ip', - }, - 'host.mac': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.name': { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - 'host.os.build': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.os.codename': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.os.containerized': { - type: 'boolean', - }, - 'host.os.family': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.os.kernel': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.os.name': { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - 'host.os.platform': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.os.type': { - type: 'keyword', - ignore_above: 1024, - }, - 'host.os.version': { - type: 'keyword', - ignore_above: 1024, - }, - 'input.type': { - type: 'keyword', - ignore_above: 1024, - }, - 'log.offset': { - type: 'long', - }, - }, + expect(Object.values(response)[0].mappings._meta).to.eql({ + managed_by: 'fleet', + managed: true, + package: { name: INTEGRATION_NAME }, }); }); diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_hidden_datastreams.ts b/x-pack/test/fleet_api_integration/apis/epm/install_hidden_datastreams.ts index ca971aa006954..45a80bfa88456 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_hidden_datastreams.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_hidden_datastreams.ts @@ -19,8 +19,7 @@ export default function (providerContext: FtrProviderContext) { await supertest.delete(`/api/fleet/epm/packages/${name}/${version}`).set('kbn-xsrf', 'xxxx'); }; - // Failing: See https://github.com/elastic/kibana/issues/184310 - describe.skip('installing with hidden datastream', async () => { + describe('installing with hidden datastream', async () => { skipIfNoDockerRegistry(providerContext); setupFleetAndAgents(providerContext); @@ -28,74 +27,6 @@ export default function (providerContext: FtrProviderContext) { await deletePackage('apm', '8.8.0'); }); - it('should rollover hidden datastreams when failed to update mappings', async function () { - await supertest - .post(`/api/fleet/epm/packages/apm/8.7.0`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); - - const writeDoc = () => - es.index({ - refresh: true, - index: 'metrics-apm.service_summary.10m-default', - document: { - '@timestamp': '2023-05-30T07:50:00.000Z', - agent: { - name: 'go', - }, - data_stream: { - dataset: 'apm.service_summary.10m', - namespace: 'default', - type: 'metrics', - }, - ecs: { - version: '8.6.0-dev', - }, - event: { - agent_id_status: 'missing', - ingested: '2023-05-30T07:57:12Z', - }, - metricset: { - interval: '10m', - name: 'service_summary', - }, - observer: { - hostname: '047e282994fb', - type: 'apm-server', - version: '8.7.0', - }, - processor: { - event: 'metric', - name: 'metric', - }, - service: { - language: { - name: 'go', - }, - name: '___main_elastic_cloud_87_ilm_fix', - }, - }, - }); - - await writeDoc(); - - await supertest - .post(`/api/fleet/epm/packages/apm/8.8.0`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }) - .expect(200); - - // Rollover are lazy need to write a new doc - await writeDoc(); - const ds = await es.indices.get({ - index: 'metrics-apm.service_summary*', - expand_wildcards: ['open', 'hidden'], - }); - // datastream rolled over - expect(Object.keys(ds).length).greaterThan(1); - }); - it('should not rollover datastreams when successfully updated mappings', async function () { await supertest .post(`/api/fleet/epm/packages/apm/8.8.0`) diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts b/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts index b15b84dcfbee9..15fec814a0ec9 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts @@ -148,6 +148,26 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .expect(200); }); + + it('should delete agent policy with package policy if supports_agentless', async function () { + // update existing policy to supports_agentless + await supertest + .put(`/api/fleet/agent_policies/${agentPolicy.id}`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: agentPolicy.name, + namespace: agentPolicy.namespace, + supports_agentless: true, + }) + .expect(200); + + await supertest + .delete(`/api/fleet/package_policies/${packagePolicy.id}`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + await supertest.get(`/api/fleet/agent_policies/${agentPolicy.id}`).expect(404); + }); }); describe('Delete bulk', () => { let agentPolicy: any; diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts new file mode 100644 index 0000000000000..efd73ddb54b0f --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts @@ -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 expect from '@kbn/expect'; +import { AGENT_POLICY_INDEX, CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetActionIndices, cleanFleetIndices, createFleetAgent } from './helpers'; +import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + + // Failing: See https://github.com/elastic/kibana/issues/189805 + describe.skip('actions', async function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertest); + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + beforeEach(async () => { + await cleanFleetActionIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + + let defaultSpacePolicy1: CreateAgentPolicyResponse; + let spaceTest1Policy1: CreateAgentPolicyResponse; + let spaceTest1Policy2: CreateAgentPolicyResponse; + + let defaultSpaceAgent1: string; + let defaultSpaceAgent2: string; + let testSpaceAgent1: string; + let testSpaceAgent2: string; + + before(async () => { + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); + defaultSpacePolicy1 = _defaultSpacePolicy1; + spaceTest1Policy1 = _spaceTest1Policy1; + spaceTest1Policy2 = _spaceTest1Policy2; + + const [_defaultSpaceAgent1, _defaultSpaceAgent2, _testSpaceAgent1, _testSpaceAgent2] = + await Promise.all([ + createFleetAgent(esClient, defaultSpacePolicy1.item.id, 'default'), + createFleetAgent(esClient, defaultSpacePolicy1.item.id), + createFleetAgent(esClient, spaceTest1Policy1.item.id, TEST_SPACE_1), + createFleetAgent(esClient, spaceTest1Policy2.item.id, TEST_SPACE_1), + ]); + defaultSpaceAgent1 = _defaultSpaceAgent1; + defaultSpaceAgent2 = _defaultSpaceAgent2; + testSpaceAgent1 = _testSpaceAgent1; + testSpaceAgent2 = _testSpaceAgent2; + }); + + describe('GET /agents/action_status', () => { + it('should return agent actions in the default space', async () => { + // Create UPDATE_TAGS action for agents in default space + await apiClient.bulkUpdateAgentTags({ + agents: [defaultSpaceAgent1, defaultSpaceAgent2], + tagsToAdd: ['tag1'], + }); + + const actionStatusInDefaultSpace = await apiClient.getActionStatus(); + expect(actionStatusInDefaultSpace.items.length).to.eql(1); + expect(actionStatusInDefaultSpace.items[0]).to.have.keys( + 'type', + 'status', + 'nbAgentsActionCreated', + 'nbAgentsAck', + 'nbAgentsActioned', + 'nbAgentsFailed', + 'latestErrors' + ); + expect(actionStatusInDefaultSpace.items[0].type).to.eql('UPDATE_TAGS'); + expect(actionStatusInDefaultSpace.items[0].nbAgentsActioned).to.eql(2); + expect(actionStatusInDefaultSpace.items[0].status).to.eql('COMPLETE'); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(0); + }); + + it('should return agent actions in a custom space', async () => { + // Create UPDATE_TAGS action for agents in custom space + await apiClient.bulkUpdateAgentTags( + { + agents: [testSpaceAgent1, testSpaceAgent2], + tagsToAdd: ['tag1'], + }, + TEST_SPACE_1 + ); + + const actionStatusInDefaultSpace = await apiClient.getActionStatus(); + expect(actionStatusInDefaultSpace.items.length).to.eql(0); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(1); + expect(actionStatusInCustomSpace.items[0]).to.have.keys( + 'type', + 'status', + 'nbAgentsActionCreated', + 'nbAgentsAck', + 'nbAgentsActioned', + 'nbAgentsFailed', + 'latestErrors' + ); + expect(actionStatusInCustomSpace.items[0].type).to.eql('UPDATE_TAGS'); + expect(actionStatusInCustomSpace.items[0].nbAgentsActioned).to.eql(2); + expect(actionStatusInCustomSpace.items[0].status).to.eql('COMPLETE'); + }); + + it('should return agent policy actions in the default space', async () => { + // Index agent policy document with no namespaces + // TODO: can this be done by updating the agent policy using the API? The .fleet-policies index remains empty... + await esClient.index({ + refresh: 'wait_for', + index: AGENT_POLICY_INDEX, + document: { + revision_idx: 2, + policy_id: defaultSpacePolicy1.item.id, + coordinator_idx: 0, + '@timestamp': '2024-07-31T13:00:00.000Z', + }, + }); + + // Index agent policy document in the default space + // TODO: can this be done by updating the agent policy using the API? The .fleet-policies index remains empty... + await esClient.index({ + refresh: 'wait_for', + index: AGENT_POLICY_INDEX, + document: { + revision_idx: 2, + policy_id: defaultSpacePolicy1.item.id, + coordinator_idx: 0, + '@timestamp': '2024-07-31T13:00:00.000Z', + namespaces: ['default'], + }, + }); + + const actionStatusInDefaultSpace = await apiClient.getActionStatus(); + expect(actionStatusInDefaultSpace.items.length).to.eql(1); + expect(actionStatusInDefaultSpace.items[0]).to.have.keys( + 'type', + 'status', + 'nbAgentsActionCreated', + 'nbAgentsAck', + 'nbAgentsActioned', + 'nbAgentsFailed' + ); + expect(actionStatusInDefaultSpace.items[0].type).to.eql('POLICY_CHANGE'); + expect(actionStatusInDefaultSpace.items[0].nbAgentsActioned).to.eql(2); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(0); + }); + + it('should return agent policy actions in a custom space', async () => { + // Index agent policy document in a custom space + // TODO: can this be done by updating the agent policy using the API? The .fleet-policies index remains empty... + await esClient.index({ + refresh: 'wait_for', + index: AGENT_POLICY_INDEX, + document: { + revision_idx: 2, + policy_id: defaultSpacePolicy1.item.id, + coordinator_idx: 0, + '@timestamp': '2024-07-31T13:00:00.000Z', + namespaces: [TEST_SPACE_1], + }, + }); + + const actionStatusInDefaultSpace = await apiClient.getActionStatus(); + expect(actionStatusInDefaultSpace.items.length).to.eql(0); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(1); + expect(actionStatusInCustomSpace.items[0]).to.have.keys( + 'type', + 'status', + 'nbAgentsActionCreated', + 'nbAgentsAck', + 'nbAgentsActioned', + 'nbAgentsFailed' + ); + expect(actionStatusInCustomSpace.items[0].type).to.eql('POLICY_CHANGE'); + expect(actionStatusInCustomSpace.items[0].nbAgentsActioned).to.eql(2); + }); + }); + + describe('POST /agents/{agentId}/actions', () => { + it('should return 404 if the agent is not in the current space', async () => { + const resInDefaultSpace = await supertest + .post(`/api/fleet/agents/${testSpaceAgent1}/actions`) + .set('kbn-xsrf', 'xxxx') + .send({ action: { type: 'UNENROLL' } }) + .expect(404); + expect(resInDefaultSpace.body.message).to.eql(`${testSpaceAgent1} not found in namespace`); + + const resInCustomSpace = await supertest + .post(`/s/${TEST_SPACE_1}/api/fleet/agents/${defaultSpaceAgent1}/actions`) + .set('kbn-xsrf', 'xxxx') + .send({ action: { type: 'UNENROLL' } }) + .expect(404); + expect(resInCustomSpace.body.message).to.eql( + `${defaultSpaceAgent1} not found in namespace` + ); + }); + + it('should create an action with set namespace in the default space', async () => { + await apiClient.postNewAgentAction(defaultSpaceAgent1); + + const actionStatusInDefaultSpace = await apiClient.getActionStatus(); + expect(actionStatusInDefaultSpace.items.length).to.eql(1); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(0); + }); + + it('should create an action with set namespace in a custom space', async () => { + await apiClient.postNewAgentAction(testSpaceAgent1, TEST_SPACE_1); + + const actionStatusInDefaultSpace = await apiClient.getActionStatus(); + expect(actionStatusInDefaultSpace.items.length).to.eql(0); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(1); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts index 0a4a1035083e6..047d32a854511 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -10,7 +10,7 @@ import { CreateAgentPolicyResponse, GetAgentsResponse } from '@kbn/fleet-plugin/ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { SpaceTestApiClient } from './api_helper'; -import { cleanFleetIndices } from './helpers'; +import { cleanFleetIndices, createFleetAgent } from './helpers'; import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; export default function (providerContext: FtrProviderContext) { @@ -18,31 +18,7 @@ export default function (providerContext: FtrProviderContext) { const supertest = getService('supertest'); const esClient = getService('es'); const kibanaServer = getService('kibanaServer'); - const createFleetAgent = async (agentPolicyId: string, spaceId?: string) => { - const agentResponse = await esClient.index({ - index: '.fleet-agents', - refresh: true, - body: { - access_api_key_id: 'api-key-3', - active: true, - policy_id: agentPolicyId, - policy_revision_idx: 1, - last_checkin_status: 'online', - type: 'PERMANENT', - local_metadata: { - host: { hostname: 'host123' }, - elastic: { agent: { version: '8.15.0' } }, - }, - user_provided_metadata: {}, - enrolled_at: new Date().toISOString(), - last_checkin: new Date().toISOString(), - tags: ['tag1'], - namespaces: spaceId ? [spaceId] : undefined, - }, - }); - return agentResponse._id; - }; describe('agents', async function () { skipIfNoDockerRegistry(providerContext); const apiClient = new SpaceTestApiClient(supertest); @@ -85,12 +61,11 @@ export default function (providerContext: FtrProviderContext) { const [_defaultSpaceAgent1, _defaultSpaceAgent2, _testSpaceAgent1, _testSpaceAgent2] = await Promise.all([ - createFleetAgent(defaultSpacePolicy1.item.id, 'default'), - createFleetAgent(defaultSpacePolicy1.item.id), - createFleetAgent(spaceTest1Policy1.item.id, TEST_SPACE_1), - createFleetAgent(spaceTest1Policy2.item.id, TEST_SPACE_1), + createFleetAgent(esClient, defaultSpacePolicy1.item.id, 'default'), + createFleetAgent(esClient, defaultSpacePolicy1.item.id), + createFleetAgent(esClient, spaceTest1Policy1.item.id, TEST_SPACE_1), + createFleetAgent(esClient, spaceTest1Policy2.item.id, TEST_SPACE_1), ]); - defaultSpaceAgent1 = _defaultSpaceAgent1; defaultSpaceAgent2 = _defaultSpaceAgent2; testSpaceAgent1 = _testSpaceAgent1; @@ -154,7 +129,11 @@ export default function (providerContext: FtrProviderContext) { describe('DELETE /agents/{id}', () => { it('should allow to delete an agent in the same space', async () => { - const testSpaceAgent3 = await createFleetAgent(spaceTest1Policy2.item.id, TEST_SPACE_1); + const testSpaceAgent3 = await createFleetAgent( + esClient, + spaceTest1Policy2.item.id, + TEST_SPACE_1 + ); await apiClient.deleteAgent(testSpaceAgent3, TEST_SPACE_1); }); diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts index 963c1af66d1df..11fd693d9340b 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts @@ -25,6 +25,8 @@ import { GetInfoResponse, GetSpaceSettingsResponse, PutSpaceSettingsRequest, + GetActionStatusResponse, + PostNewAgentActionResponse, } from '@kbn/fleet-plugin/common/types'; import { GetUninstallTokenResponse, @@ -116,7 +118,7 @@ export class SpaceTestApiClient { return res; } - // Enrollmennt API Keys + // Enrollment API Keys async getEnrollmentApiKey( keyId: string, spaceId?: string @@ -302,4 +304,22 @@ export class SpaceTestApiClient { return res; } + // Actions + async getActionStatus(spaceId?: string): Promise<GetActionStatusResponse> { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/agents/action_status`) + .expect(200); + + return res; + } + + async postNewAgentAction(agentId: string, spaceId?: string): Promise<PostNewAgentActionResponse> { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}/actions`) + .set('kbn-xsrf', 'xxxx') + .send({ action: { type: 'UNENROLL' } }) + .expect(200); + + return res; + } } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts index d5eb41afb2104..af648ec765971 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { SpaceTestApiClient } from './api_helper'; -import { cleanFleetIndices } from './helpers'; +import { cleanFleetIndices, createFleetAgent } from './helpers'; import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; export default function (providerContext: FtrProviderContext) { @@ -17,31 +17,7 @@ export default function (providerContext: FtrProviderContext) { const supertest = getService('supertest'); const esClient = getService('es'); const kibanaServer = getService('kibanaServer'); - const createFleetAgent = async (agentPolicyId: string, spaceId?: string) => { - const agentResponse = await esClient.index({ - index: '.fleet-agents', - refresh: true, - body: { - access_api_key_id: 'api-key-3', - active: true, - policy_id: agentPolicyId, - policy_revision_idx: 1, - last_checkin_status: 'online', - type: 'PERMANENT', - local_metadata: { - host: { hostname: 'host123' }, - elastic: { agent: { version: '8.15.0' } }, - }, - user_provided_metadata: {}, - enrolled_at: new Date().toISOString(), - last_checkin: new Date().toISOString(), - tags: ['tag1'], - namespaces: spaceId ? [spaceId] : undefined, - }, - }); - return agentResponse._id; - }; describe('enrollment_settings', async function () { skipIfNoDockerRegistry(providerContext); const apiClient = new SpaceTestApiClient(supertest); @@ -104,7 +80,7 @@ export default function (providerContext: FtrProviderContext) { before(async () => { await apiClient.setup(); const testSpaceFleetServerPolicy = await apiClient.createFleetServerPolicy(TEST_SPACE_1); - await createFleetAgent(testSpaceFleetServerPolicy.item.id, TEST_SPACE_1); + await createFleetAgent(esClient, testSpaceFleetServerPolicy.item.id, TEST_SPACE_1); }); describe('GET /enrollments/settings', () => { @@ -142,7 +118,7 @@ export default function (providerContext: FtrProviderContext) { before(async () => { await apiClient.setup(); const defaultFleetServerPolicy = await apiClient.createFleetServerPolicy(); - await createFleetAgent(defaultFleetServerPolicy.item.id); + await createFleetAgent(esClient, defaultFleetServerPolicy.item.id); }); describe('GET /enrollments/settings', () => { diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts index fe731e323ce12..eacee0b41b6ac 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts @@ -7,19 +7,87 @@ import { Client } from '@elastic/elasticsearch'; +import { + AGENT_ACTIONS_INDEX, + AGENT_ACTIONS_RESULTS_INDEX, + AGENTS_INDEX, + AGENT_POLICY_INDEX, +} from '@kbn/fleet-plugin/common'; +import { ENROLLMENT_API_KEYS_INDEX } from '@kbn/fleet-plugin/common/constants'; + +const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } }; + export async function cleanFleetIndices(esClient: Client) { await Promise.all([ esClient.deleteByQuery({ - index: '.fleet-enrollment-api-keys', + index: ENROLLMENT_API_KEYS_INDEX, q: '*', ignore_unavailable: true, refresh: true, }), esClient.deleteByQuery({ - index: '.fleet-agents', + index: AGENTS_INDEX, q: '*', ignore_unavailable: true, refresh: true, }), ]); } + +export async function cleanFleetActionIndices(esClient: Client) { + try { + await Promise.all([ + esClient.deleteByQuery({ + index: AGENT_POLICY_INDEX, + q: '*', + refresh: true, + }), + esClient.deleteByQuery({ + index: AGENT_ACTIONS_INDEX, + q: '*', + ignore_unavailable: true, + refresh: true, + }), + esClient.deleteByQuery( + { + index: AGENT_ACTIONS_RESULTS_INDEX, + q: '*', + refresh: true, + }, + ES_INDEX_OPTIONS + ), + ]); + } catch (error) { + // swallowing error if does not exist + } +} + +export const createFleetAgent = async ( + esClient: Client, + agentPolicyId: string, + spaceId?: string +) => { + const agentResponse = await esClient.index({ + index: '.fleet-agents', + refresh: true, + body: { + access_api_key_id: 'api-key-3', + active: true, + policy_id: agentPolicyId, + policy_revision_idx: 1, + last_checkin_status: 'online', + type: 'PERMANENT', + local_metadata: { + host: { hostname: 'host123' }, + elastic: { agent: { version: '8.15.0' } }, + }, + user_provided_metadata: {}, + enrolled_at: new Date().toISOString(), + last_checkin: new Date().toISOString(), + tags: ['tag1'], + namespaces: spaceId ? [spaceId] : undefined, + }, + }); + + return agentResponse._id; +}; diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/index.js b/x-pack/test/fleet_api_integration/apis/space_awareness/index.js index fd9bab0e091f2..c684504372736 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/index.js +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/index.js @@ -14,5 +14,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./enrollment_settings')); loadTestFile(require.resolve('./package_install')); loadTestFile(require.resolve('./space_settings')); + loadTestFile(require.resolve('./actions')); }); } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts index 4052e9a8de488..0b20df56042ca 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { SpaceTestApiClient } from './api_helper'; -import { cleanFleetIndices } from './helpers'; +import { cleanFleetIndices, createFleetAgent } from './helpers'; import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; export default function (providerContext: FtrProviderContext) { @@ -21,31 +21,6 @@ export default function (providerContext: FtrProviderContext) { describe('package install', async function () { skipIfNoDockerRegistry(providerContext); const apiClient = new SpaceTestApiClient(supertest); - const createFleetAgent = async (agentPolicyId: string, spaceId?: string) => { - const agentResponse = await esClient.index({ - index: '.fleet-agents', - refresh: true, - body: { - access_api_key_id: 'api-key-3', - active: true, - policy_id: agentPolicyId, - policy_revision_idx: 1, - last_checkin_status: 'online', - type: 'PERMANENT', - local_metadata: { - host: { hostname: 'host123' }, - elastic: { agent: { version: '8.15.0' } }, - }, - user_provided_metadata: {}, - enrolled_at: new Date().toISOString(), - last_checkin: new Date().toISOString(), - tags: ['tag1'], - namespaces: spaceId ? [spaceId] : undefined, - }, - }); - - return agentResponse._id; - }; before(async () => { await kibanaServer.savedObjects.cleanStandardList(); @@ -275,7 +250,7 @@ export default function (providerContext: FtrProviderContext) { inputs: {}, }); - await createFleetAgent(agentPolicyRes.item.id); + await createFleetAgent(esClient, agentPolicyRes.item.id); }); it('should not allow to delete a package with active agents in the same space', async () => { diff --git a/x-pack/test/fleet_api_integration/config.base.ts b/x-pack/test/fleet_api_integration/config.base.ts index b1b2b0dcac0ad..cc689e1f3f536 100644 --- a/x-pack/test/fleet_api_integration/config.base.ts +++ b/x-pack/test/fleet_api_integration/config.base.ts @@ -91,7 +91,13 @@ export default async function ({ readConfigFile, log }: FtrConfigProviderContext 'enableStrictKQLValidation', 'subfeaturePrivileges', 'enablePackagesStateMachine', + 'agentless', ])}`, + `--xpack.cloud.id='123456789'`, + `--xpack.fleet.agentless.api.url=https://api.agentless.url/api/v1/ess`, + `--xpack.fleet.agentless.api.tls.certificate=./config/node.crt`, + `--xpack.fleet.agentless.api.tls.key=./config/node.key`, + `--xpack.fleet.agentless.api.tls.ca=./config/ca.crt`, `--logging.loggers=${JSON.stringify([ ...getKibanaCliLoggers(xPackAPITestsConfig.get('kbnTestServer.serverArgs')), diff --git a/x-pack/test/fleet_api_integration/privileges_helpers.ts b/x-pack/test/fleet_api_integration/privileges_helpers.ts index 7a9ea3753867e..bceaf3ebba09f 100644 --- a/x-pack/test/fleet_api_integration/privileges_helpers.ts +++ b/x-pack/test/fleet_api_integration/privileges_helpers.ts @@ -5,7 +5,8 @@ * 2.0. */ -import supertest, { type SuperTest } from 'supertest'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; +import supertest from 'supertest'; interface PrivilegeTestScenario { user: { @@ -26,7 +27,7 @@ interface PrivilegeTestRoute { export function runPrivilegeTests( routes: PrivilegeTestRoute[], - supertestWithoutAuth: SuperTest<supertest.Test> + supertestWithoutAuth: SupertestWithoutAuthProviderType ) { for (const route of routes) { describe(`${route.method} ${route.path}`, () => { diff --git a/x-pack/test/fleet_functional/apps/home/welcome.ts b/x-pack/test/fleet_functional/apps/home/welcome.ts index 3cc187769d06b..6bffd88168d22 100644 --- a/x-pack/test/fleet_functional/apps/home/welcome.ts +++ b/x-pack/test/fleet_functional/apps/home/welcome.ts @@ -11,11 +11,10 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const esArchiver = getService('esArchiver'); - const PageObjects = getPageObjects(['common', 'home']); + const PageObjects = getPageObjects(['common', 'home', 'header']); const kibanaServer = getService('kibanaServer'); - // FLAKY: https://github.com/elastic/kibana/issues/109017 - describe.skip('Welcome interstitial', () => { + describe('Welcome interstitial', () => { before(async () => { // Need to navigate to page first to clear storage before test can be run await PageObjects.common.navigateToUrl('home', undefined); @@ -33,6 +32,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Reload the home screen and verify the interstitial is displayed await PageObjects.common.navigateToUrl('home', undefined, { disableWelcomePrompt: false }); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.home.isWelcomeInterstitialDisplayed()).to.be(true); }); diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts index e9f245df27514..f2681ef52183d 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis.ts @@ -315,7 +315,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); } - describe('log rate analysis', async function () { + // Failing: See https://github.com/elastic/kibana/issues/176387 + describe.skip('log rate analysis', async function () { for (const testData of logRateAnalysisTestData) { describe(`with '${testData.sourceIndexOrSavedSearch}'`, function () { before(async () => { diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts index bb59bbc7af283..c745800dbdf2e 100644 --- a/x-pack/test/functional/apps/discover/visualize_field.ts +++ b/x-pack/test/functional/apps/discover/visualize_field.ts @@ -167,7 +167,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); expect(await testSubjects.exists('xyVisChart')).to.be(true); expect(await PageObjects.lens.canRemoveDimension('lnsXY_xDimensionPanel')).to.equal(true); @@ -190,7 +189,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -209,7 +207,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -228,7 +225,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('unifiedHistogramSaveVisualization'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -257,7 +253,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await monacoEditor.setCodeEditorValue('from logstash-* | limit 10'); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); // save the visualization await testSubjects.click('unifiedHistogramSaveVisualization'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -308,7 +303,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('unifiedHistogramSaveVisualization'); await PageObjects.header.waitUntilLoadingHasFinished(); let title = await testSubjects.getAttribute('savedObjectTitle', 'value'); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 7751359ac30fd..29cfa9bb37e0b 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -65,9 +65,18 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs') ); - it('renders an empty data prompt', async () => { + it('renders an empty data prompt and redirects to the onboarding page', async () => { await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.getNoMetricsIndicesPrompt(); + await pageObjects.infraHome.noDataPromptExists(); + await pageObjects.infraHome.noDataPromptAddDataClick(); + + await retry.try(async () => { + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`; + const expectedUrlPattern = `${baseUrl}/app/observabilityOnboarding/?category=infra`; + expect(currentUrl).to.equal(expectedUrlPattern); + }); }); // Unskip once asset details error handling has been implemented @@ -164,7 +173,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); [ - { metric: 'cpuUsage', value: '0.8%' }, + { metric: 'cpuUsage', value: 'N/A' }, { metric: 'normalizedLoad1m', value: '1.4%' }, { metric: 'memoryUsage', value: '18.0%' }, { metric: 'diskUsage', value: '35.0%' }, @@ -397,7 +406,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.clearSearchTerm(); }); - it('sort nodes by descending value', async () => { + it.skip('sort nodes by descending value', async () => { await pageObjects.infraHome.goToTime(DATE_WITH_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.sortNodesBy('value'); @@ -414,7 +423,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - it('sort nodes by ascending value', async () => { + it.skip('sort nodes by ascending value', async () => { await pageObjects.infraHome.goToTime(DATE_WITH_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.sortNodesBy('value'); @@ -441,7 +450,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - it('filter nodes by search term', async () => { + it.skip('filter nodes by search term', async () => { await pageObjects.infraHome.goToTime(DATE_WITH_DATA); await pageObjects.infraHome.getWaffleMap(); await pageObjects.infraHome.enterSearchTerm('host.name: "demo-stack-apache-01"'); @@ -454,7 +463,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.clearSearchTerm(); }); - it('change color palette', async () => { + it.skip('change color palette', async () => { await pageObjects.infraHome.openLegendControls(); await pageObjects.infraHome.changePalette('temperature'); await pageObjects.infraHome.applyLegendControls(); @@ -572,16 +581,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { 'system.core.softirq.pct', 'system.core.steal.pct', 'system.cpu.nice.pct', - 'system.cpu.idle.pct', - 'system.cpu.iowait.pct', - 'system.cpu.irq.pct', ]; for (const field of fields) { await pageObjects.infraHome.addCustomMetric(field); } const metricsCount = await pageObjects.infraHome.getMetricsContextMenuItemsCount(); - // there are 6 default metrics in the context menu for hosts + // there are 7 default metrics in the context menu for hosts expect(metricsCount).to.eql(20); await pageObjects.infraHome.ensureCustomMetricAddButtonIsDisabled(); diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts index 8af7d9fc23dc3..a016fbec64ecd 100644 --- a/x-pack/test/functional/apps/infra/hosts_view.ts +++ b/x-pack/test/functional/apps/infra/hosts_view.ts @@ -33,7 +33,7 @@ const tableEntries = [ { alertsCount: 2, title: 'demo-stack-apache-01', - cpuUsage: '1.2%', + cpuUsage: '0%', normalizedLoad: '0.5%', memoryUsage: '18.4%', memoryFree: '3.2 GB', @@ -44,7 +44,7 @@ const tableEntries = [ { alertsCount: 2, title: 'demo-stack-mysql-01', - cpuUsage: '0.9%', + cpuUsage: '0%', normalizedLoad: '0%', memoryUsage: '18.2%', memoryFree: '3.2 GB', @@ -55,7 +55,7 @@ const tableEntries = [ { alertsCount: 2, title: 'demo-stack-redis-01', - cpuUsage: '0.8%', + cpuUsage: '0%', normalizedLoad: '0%', memoryUsage: '15.9%', memoryFree: '3.3 GB', @@ -65,19 +65,19 @@ const tableEntries = [ }, { alertsCount: 0, - title: 'demo-stack-nginx-01', - cpuUsage: '0.8%', - normalizedLoad: '1.4%', - memoryUsage: '18%', - memoryFree: '3.2 GB', - diskSpaceUsage: '35%', + title: 'demo-stack-client-01', + cpuUsage: '0%', + normalizedLoad: '0.1%', + memoryUsage: '13.8%', + memoryFree: '3.3 GB', + diskSpaceUsage: '33.8%', rx: '0 bit/s', tx: '0 bit/s', }, { alertsCount: 0, title: 'demo-stack-haproxy-01', - cpuUsage: '0.8%', + cpuUsage: '0%', normalizedLoad: '0%', memoryUsage: '16.5%', memoryFree: '3.2 GB', @@ -87,12 +87,12 @@ const tableEntries = [ }, { alertsCount: 0, - title: 'demo-stack-client-01', - cpuUsage: '0.5%', - normalizedLoad: '0.1%', - memoryUsage: '13.8%', - memoryFree: '3.3 GB', - diskSpaceUsage: '33.8%', + title: 'demo-stack-nginx-01', + cpuUsage: '0%', + normalizedLoad: '1.4%', + memoryUsage: '18%', + memoryFree: '3.2 GB', + diskSpaceUsage: '35%', rx: '0 bit/s', tx: '0 bit/s', }, @@ -109,6 +109,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const testSubjects = getService('testSubjects'); const apmSynthtraceKibanaClient = getService('apmSynthtraceKibanaClient'); + const pageObjects = getPageObjects([ 'assetDetails', 'common', @@ -197,670 +198,695 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ); describe('Hosts View', function () { - let synthtraceApmClient: ApmSynthtraceEsClient; - before(async () => { - const version = (await apmSynthtraceKibanaClient.installApmPackage()).version; - synthtraceApmClient = await getApmSynthtraceEsClient({ - client: esClient, - packageVersion: version, + describe('#Onboarding', function () { + before(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); }); - const services = generateAddServicesToExistingHost({ - from: DATES.metricsAndLogs.hosts.processesDataStartDate, - to: DATES.metricsAndLogs.hosts.processesDataEndDate, - hostName: 'Jennys-MBP.fritz.box', - servicesPerHost: 3, + it('should show hosts no data page and redirect onboarding page', async () => { + await pageObjects.infraHome.noDataPromptExists(); + await pageObjects.infraHome.noDataPromptAddDataClick(); + + await retry.try(async () => { + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`; + const expectedUrlPattern = `${baseUrl}/app/observabilityOnboarding/?category=logs`; + expect(currentUrl).to.equal(expectedUrlPattern); + }); }); + }); - await browser.setWindowSize(1600, 1200); + describe('#With data', function () { + let synthtraceApmClient: ApmSynthtraceEsClient; + before(async () => { + const version = (await apmSynthtraceKibanaClient.installApmPackage()).version; + synthtraceApmClient = await getApmSynthtraceEsClient({ + client: esClient, + packageVersion: version, + }); - return Promise.all([ - synthtraceApmClient.index(services), - esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), - esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), - esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), - ]); - }); + const services = generateAddServicesToExistingHost({ + from: DATES.metricsAndLogs.hosts.processesDataStartDate, + to: DATES.metricsAndLogs.hosts.processesDataEndDate, + hostName: 'Jennys-MBP.fritz.box', + servicesPerHost: 3, + }); - after(async () => { - return Promise.all([ - apmSynthtraceKibanaClient.uninstallApmPackage(), - synthtraceApmClient.clean(), - esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'), - esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'), - esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), - browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY), - ]); - }); + await browser.setWindowSize(1600, 1200); - it('should be accessible from the Inventory page', async () => { - await pageObjects.common.navigateToApp('infraOps'); + return Promise.all([ + synthtraceApmClient.index(services), + esArchiver.load('x-pack/test/functional/es_archives/infra/alerts'), + esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), + esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), + ]); + }); - await pageObjects.infraHome.clickDismissKubernetesTourButton(); - await pageObjects.infraHostsView.getBetaBadgeExists(); - await pageObjects.infraHostsView.clickTryHostViewBadge(); + after(async () => { + return Promise.all([ + apmSynthtraceKibanaClient.uninstallApmPackage(), + synthtraceApmClient.clean(), + esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts'), + esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'), + esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_hosts_processes'), + browser.removeLocalStorageItem(HOSTS_LINK_LOCAL_STORAGE_KEY), + ]); + }); - const pageUrl = await browser.getCurrentUrl(); + it('should be accessible from the Inventory page', async () => { + await pageObjects.common.navigateToApp('infraOps'); - expect(pageUrl).to.contain(HOSTS_VIEW_PATH); - }); + await pageObjects.infraHome.clickDismissKubernetesTourButton(); + await pageObjects.infraHostsView.getBetaBadgeExists(); + await pageObjects.infraHostsView.clickTryHostViewBadge(); - describe('#Single Host Flyout', () => { - before(async () => { - await setHostViewEnabled(true); - await setCustomDashboardsEnabled(true); - await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); - await pageObjects.header.waitUntilLoadingHasFinished(); + const pageUrl = await browser.getCurrentUrl(); + + expect(pageUrl).to.contain(HOSTS_VIEW_PATH); }); - describe('Tabs', () => { + describe('#Single Host Flyout', () => { before(async () => { - await pageObjects.timePicker.setAbsoluteRange( - START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), - END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) - ); + await setHostViewEnabled(true); + await setCustomDashboardsEnabled(true); + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); - await waitForPageToLoad(); + describe('Tabs', () => { + before(async () => { + await pageObjects.timePicker.setAbsoluteRange( + START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT), + END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) + ); - await pageObjects.infraHostsView.clickTableOpenFlyoutButton(); - }); + await waitForPageToLoad(); - after(async () => { - await retry.try(async () => { - await pageObjects.infraHome.clickCloseFlyoutButton(); + await pageObjects.infraHostsView.clickTableOpenFlyoutButton(); }); - }); - describe('Overview Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickOverviewTab(); + after(async () => { + await retry.try(async () => { + await pageObjects.infraHome.clickCloseFlyoutButton(); + }); }); - [ - { metric: 'cpuUsage', value: '13.9%' }, - { metric: 'normalizedLoad1m', value: '18.8%' }, - { metric: 'memoryUsage', value: '94.9%' }, - { metric: 'diskUsage', value: 'N/A' }, - ].forEach(({ metric, value }) => { - it(`${metric} tile should show ${value}`, async () => { - await retry.try(async () => { - const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( - metric - ); - expect(tileValue).to.eql(value); + describe('Overview Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickOverviewTab(); + }); + + [ + { metric: 'cpuUsage', value: '13.9%' }, + { metric: 'normalizedLoad1m', value: '18.8%' }, + { metric: 'memoryUsage', value: '94.9%' }, + { metric: 'diskUsage', value: 'N/A' }, + ].forEach(({ metric, value }) => { + it(`${metric} tile should show ${value}`, async () => { + await retry.try(async () => { + const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( + metric + ); + expect(tileValue).to.eql(value); + }); }); }); - }); - [ - { metric: 'cpu', chartsCount: 2 }, - { metric: 'memory', chartsCount: 1 }, - { metric: 'disk', chartsCount: 2 }, - { metric: 'network', chartsCount: 1 }, - ].forEach(({ metric, chartsCount }) => { - it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { - const hosts = await pageObjects.assetDetails.getOverviewTabHostMetricCharts(metric); - expect(hosts.length).to.equal(chartsCount); + [ + { metric: 'cpu', chartsCount: 2 }, + { metric: 'memory', chartsCount: 1 }, + { metric: 'disk', chartsCount: 2 }, + { metric: 'network', chartsCount: 1 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { + const hosts = await pageObjects.assetDetails.getOverviewTabHostMetricCharts(metric); + expect(hosts.length).to.equal(chartsCount); + }); }); - }); - it('should show all section as collapsible', async () => { - await pageObjects.assetDetails.metadataSectionCollapsibleExist(); - await pageObjects.assetDetails.alertsSectionCollapsibleExist(); - await pageObjects.assetDetails.metricsSectionCollapsibleExist(); - await pageObjects.assetDetails.servicesSectionCollapsibleExist(); - }); + it('should show all section as collapsible', async () => { + await pageObjects.assetDetails.metadataSectionCollapsibleExist(); + await pageObjects.assetDetails.alertsSectionCollapsibleExist(); + await pageObjects.assetDetails.metricsSectionCollapsibleExist(); + await pageObjects.assetDetails.servicesSectionCollapsibleExist(); + }); - it('should show alerts', async () => { - await pageObjects.header.waitUntilLoadingHasFinished(); - await pageObjects.assetDetails.overviewAlertsTitleExists(); - }); + it('should show alerts', async () => { + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.assetDetails.overviewAlertsTitleExists(); + }); - it('should show 3 services each with an icon, service name, and url', async () => { - await pageObjects.assetDetails.servicesSectionCollapsibleExist(); + it('should show 3 services each with an icon, service name, and url', async () => { + await pageObjects.assetDetails.servicesSectionCollapsibleExist(); - const services = - await pageObjects.assetDetails.getAssetDetailsServicesWithIconsAndNames(); + const services = + await pageObjects.assetDetails.getAssetDetailsServicesWithIconsAndNames(); - expect(services.length).to.equal(3); + expect(services.length).to.equal(3); - const currentUrl = await browser.getCurrentUrl(); - const parsedUrl = new URL(currentUrl); - const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`; + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + const baseUrl = `${parsedUrl.protocol}//${parsedUrl.host}`; - services.forEach((service, index) => { - expect(service.serviceName).to.equal(`service-${index}`); - expect(service.iconSrc).to.not.be.empty(); - const expectedUrlPattern = `${baseUrl}/app/apm/services/service-${index}/overview?rangeFrom=${DATES.metricsAndLogs.hosts.processesDataStartDate}&rangeTo=${DATES.metricsAndLogs.hosts.processesDataEndDate}`; - expect(service.serviceUrl).to.equal(expectedUrlPattern); + services.forEach((service, index) => { + expect(service.serviceName).to.equal(`service-${index}`); + expect(service.iconSrc).to.not.be.empty(); + const expectedUrlPattern = `${baseUrl}/app/apm/services/service-${index}/overview?rangeFrom=${DATES.metricsAndLogs.hosts.processesDataStartDate}&rangeTo=${DATES.metricsAndLogs.hosts.processesDataEndDate}`; + expect(service.serviceUrl).to.equal(expectedUrlPattern); + }); }); }); - }); - describe('Metadata Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickMetadataTab(); - }); - - it('should show metadata table', async () => { - await pageObjects.assetDetails.metadataTableExists(); - }); - - it('should render metadata tab, add and remove filter', async () => { - // Add Filter - await pageObjects.assetDetails.clickAddMetadataFilter(); - await pageObjects.header.waitUntilLoadingHasFinished(); + describe('Metadata Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetadataTab(); + }); - const addedFilter = await pageObjects.assetDetails.getMetadataAppliedFilter(); - expect(addedFilter).to.contain('host.architecture: arm64'); - const removeFilterExists = await pageObjects.assetDetails.metadataRemoveFilterExists(); - expect(removeFilterExists).to.be(true); + it('should show metadata table', async () => { + await pageObjects.assetDetails.metadataTableExists(); + }); - // Remove filter - await pageObjects.assetDetails.clickRemoveMetadataFilter(); - await pageObjects.header.waitUntilLoadingHasFinished(); - const removeFilterShouldNotExist = - await pageObjects.assetDetails.metadataRemovePinExists(); - expect(removeFilterShouldNotExist).to.be(false); + it('should render metadata tab, add and remove filter', async () => { + // Add Filter + await pageObjects.assetDetails.clickAddMetadataFilter(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const addedFilter = await pageObjects.assetDetails.getMetadataAppliedFilter(); + expect(addedFilter).to.contain('host.architecture: arm64'); + const removeFilterExists = + await pageObjects.assetDetails.metadataRemoveFilterExists(); + expect(removeFilterExists).to.be(true); + + // Remove filter + await pageObjects.assetDetails.clickRemoveMetadataFilter(); + await pageObjects.header.waitUntilLoadingHasFinished(); + const removeFilterShouldNotExist = + await pageObjects.assetDetails.metadataRemovePinExists(); + expect(removeFilterShouldNotExist).to.be(false); + }); }); - }); - describe('Metrics Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickMetricsTab(); - }); + describe('Metrics Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetricsTab(); + }); - it('should show metrics content', async () => { - await pageObjects.assetDetails.metricsChartsContentExists(); + it('should show metrics content', async () => { + await pageObjects.assetDetails.metricsChartsContentExists(); + }); }); - }); - describe('Processes Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickProcessesTab(); - }); + describe('Processes Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickProcessesTab(); + }); - it('should show processes table', async () => { - await pageObjects.assetDetails.processesTableExists(); + it('should show processes table', async () => { + await pageObjects.assetDetails.processesTableExists(); + }); }); - }); - describe('Logs Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickLogsTab(); - }); + describe('Logs Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickLogsTab(); + }); - it('should render logs tab', async () => { - await pageObjects.assetDetails.logsExists(); + it('should render logs tab', async () => { + await pageObjects.assetDetails.logsExists(); + }); }); - }); - describe('Dashboards Tab', () => { - before(async () => { - await pageObjects.assetDetails.clickDashboardsTab(); - }); + describe('Dashboards Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickDashboardsTab(); + }); - it('should render dashboards tab splash screen with option to add dashboard', async () => { - await pageObjects.assetDetails.addDashboardExists(); + it('should render dashboards tab splash screen with option to add dashboard', async () => { + await pageObjects.assetDetails.addDashboardExists(); + }); }); - }); - describe('Flyout links', () => { - it('should navigate to Host Details page after click', async () => { - await pageObjects.assetDetails.clickOpenAsPageLink(); - const dateRange = await pageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); - expect(dateRange.start).to.equal(START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT)); - expect(dateRange.end).to.equal(END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT)); + describe('Flyout links', () => { + it('should navigate to Host Details page after click', async () => { + await pageObjects.assetDetails.clickOpenAsPageLink(); + const dateRange = await pageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(dateRange.start).to.equal( + START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT) + ); + expect(dateRange.end).to.equal(END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT)); - await returnTo(HOSTS_VIEW_PATH); + await returnTo(HOSTS_VIEW_PATH); + }); }); }); }); - }); - describe('#Page Content', () => { - before(async () => { - await setHostViewEnabled(true); - await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); - await pageObjects.header.waitUntilLoadingHasFinished(); - await pageObjects.timePicker.setAbsoluteRange( - START_DATE.format(DATE_PICKER_FORMAT), - END_DATE.format(DATE_PICKER_FORMAT) - ); + describe('#Page Content', () => { + before(async () => { + await setHostViewEnabled(true); + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_DATE.format(DATE_PICKER_FORMAT), + END_DATE.format(DATE_PICKER_FORMAT) + ); - await waitForPageToLoad(); - }); + await waitForPageToLoad(); + }); - it('should render the correct page title', async () => { - const documentTitle = await browser.getTitle(); - expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic'); - }); + it('should render the correct page title', async () => { + const documentTitle = await browser.getTitle(); + expect(documentTitle).to.contain('Hosts - Infrastructure - Observability - Elastic'); + }); - it('should render the title beta badge', async () => { - await pageObjects.infraHostsView.getBetaBadgeExists(); - }); + it('should render the title beta badge', async () => { + await pageObjects.infraHostsView.getBetaBadgeExists(); + }); - describe('Hosts table', async () => { - let hostRows: WebElementWrapper[] = []; + describe('Hosts table', async () => { + let hostRows: WebElementWrapper[] = []; - before(async () => { - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - }); + before(async () => { + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + }); - it('should render a table with 6 hosts', async () => { - expect(hostRows.length).to.equal(6); - }); + it('should render a table with 6 hosts', async () => { + expect(hostRows.length).to.equal(6); + }); - it('should render the computed metrics for each host entry', async () => { - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position])); + it('should render the computed metrics for each host entry', async () => { + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(tableEntries[i]); + } }); - }); - it('should select and filter hosts inside the table', async () => { - const selectHostsButtonExistsOnLoad = - await pageObjects.infraHostsView.selectedHostsButtonExist(); - expect(selectHostsButtonExistsOnLoad).to.be(false); + it('should select and filter hosts inside the table', async () => { + const selectHostsButtonExistsOnLoad = + await pageObjects.infraHostsView.selectedHostsButtonExist(); + expect(selectHostsButtonExistsOnLoad).to.be(false); - await pageObjects.infraHostsView.clickHostCheckbox('demo-stack-client-01', '-'); - await pageObjects.infraHostsView.clickHostCheckbox('demo-stack-apache-01', '-'); + await pageObjects.infraHostsView.clickHostCheckbox('demo-stack-client-01', '-'); + await pageObjects.infraHostsView.clickHostCheckbox('demo-stack-apache-01', '-'); - const selectHostsButtonExistsOnSelection = - await pageObjects.infraHostsView.selectedHostsButtonExist(); - expect(selectHostsButtonExistsOnSelection).to.be(true); + const selectHostsButtonExistsOnSelection = + await pageObjects.infraHostsView.selectedHostsButtonExist(); + expect(selectHostsButtonExistsOnSelection).to.be(true); - await pageObjects.infraHostsView.clickSelectedHostsButton(); - await pageObjects.infraHostsView.clickSelectedHostsAddFilterButton(); + await pageObjects.infraHostsView.clickSelectedHostsButton(); + await pageObjects.infraHostsView.clickSelectedHostsAddFilterButton(); - await pageObjects.header.waitUntilLoadingHasFinished(); - const hostRowsAfterFilter = await pageObjects.infraHostsView.getHostsTableData(); - expect(hostRowsAfterFilter.length).to.equal(2); + await pageObjects.header.waitUntilLoadingHasFinished(); + const hostRowsAfterFilter = await pageObjects.infraHostsView.getHostsTableData(); + expect(hostRowsAfterFilter.length).to.equal(2); - const deleteFilterButton = await find.byCssSelector( - `[title="Delete host.name: demo-stack-client-01 OR host.name: demo-stack-apache-01"]` - ); - await deleteFilterButton.click(); - await pageObjects.header.waitUntilLoadingHasFinished(); - const hostRowsAfterRemovingFilter = await pageObjects.infraHostsView.getHostsTableData(); - expect(hostRowsAfterRemovingFilter.length).to.equal(6); + const deleteFilterButton = await find.byCssSelector( + `[title="Delete host.name: demo-stack-client-01 OR host.name: demo-stack-apache-01"]` + ); + await deleteFilterButton.click(); + await pageObjects.header.waitUntilLoadingHasFinished(); + const hostRowsAfterRemovingFilter = + await pageObjects.infraHostsView.getHostsTableData(); + expect(hostRowsAfterRemovingFilter.length).to.equal(6); + }); }); - }); - describe('Host details page navigation', () => { - after(async () => { - await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); - await pageObjects.header.waitUntilLoadingHasFinished(); - await pageObjects.timePicker.setAbsoluteRange( - START_DATE.format(DATE_PICKER_FORMAT), - END_DATE.format(DATE_PICKER_FORMAT) - ); + describe('Host details page navigation', () => { + after(async () => { + await pageObjects.common.navigateToApp(HOSTS_VIEW_PATH); + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.timePicker.setAbsoluteRange( + START_DATE.format(DATE_PICKER_FORMAT), + END_DATE.format(DATE_PICKER_FORMAT) + ); - await waitForPageToLoad(); - }); + await waitForPageToLoad(); + }); - it('should maintain the selected date range when navigating to the individual host details', async () => { - const start = START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); - const end = END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); + it('should maintain the selected date range when navigating to the individual host details', async () => { + const start = START_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); + const end = END_HOST_PROCESSES_DATE.format(DATE_PICKER_FORMAT); - await pageObjects.timePicker.setAbsoluteRange(start, end); + await pageObjects.timePicker.setAbsoluteRange(start, end); - await waitForPageToLoad(); + await waitForPageToLoad(); - const hostDetailLinks = await pageObjects.infraHostsView.getAllHostDetailLinks(); - expect(hostDetailLinks.length).not.to.equal(0); + const hostDetailLinks = await pageObjects.infraHostsView.getAllHostDetailLinks(); + expect(hostDetailLinks.length).not.to.equal(0); - await hostDetailLinks[0].click(); + await hostDetailLinks[0].click(); - expect(await pageObjects.timePicker.timePickerExists()).to.be(true); + expect(await pageObjects.timePicker.timePickerExists()).to.be(true); - const datePickerValue = await pageObjects.timePicker.getTimeConfig(); - expect(datePickerValue.start).to.equal(start); - expect(datePickerValue.end).to.equal(end); + const datePickerValue = await pageObjects.timePicker.getTimeConfig(); + expect(datePickerValue.start).to.equal(start); + expect(datePickerValue.end).to.equal(end); + }); }); - }); - describe('KPIs', () => { - [ - { metric: 'hostsCount', value: '6' }, - { metric: 'cpuUsage', value: '0.8%' }, - { metric: 'normalizedLoad1m', value: '0.3%' }, - { metric: 'memoryUsage', value: '16.8%' }, - { metric: 'diskUsage', value: '35.7%' }, - ].forEach(({ metric, value }) => { - it(`${metric} tile should show ${value}`, async () => { - await retry.try(async () => { - const tileValue = - metric === 'hostsCount' - ? await pageObjects.infraHostsView.getKPITileValue(metric) - : await pageObjects.assetDetails.getAssetDetailsKPITileValue(metric); + describe('KPIs', () => { + [ + { metric: 'hostsCount', value: '6' }, + { metric: 'cpuUsage', value: 'N/A' }, + { metric: 'normalizedLoad1m', value: '0.3%' }, + { metric: 'memoryUsage', value: '16.8%' }, + { metric: 'diskUsage', value: '35.7%' }, + ].forEach(({ metric, value }) => { + it(`${metric} tile should show ${value}`, async () => { + await retry.try(async () => { + const tileValue = + metric === 'hostsCount' + ? await pageObjects.infraHostsView.getKPITileValue(metric) + : await pageObjects.assetDetails.getAssetDetailsKPITileValue(metric); - expect(tileValue).to.eql(value); + expect(tileValue).to.eql(value); + }); }); }); }); - }); - describe('Metrics Tab', () => { - before(async () => { - await browser.scrollTop(); - await pageObjects.infraHostsView.visitMetricsTab(); - }); + describe('Metrics Tab', () => { + before(async () => { + await browser.scrollTop(); + await pageObjects.infraHostsView.visitMetricsTab(); + }); - after(async () => { - await browser.scrollTop(); - }); + after(async () => { + await browser.scrollTop(); + }); - it('should load 11 lens metric charts', async () => { - const metricCharts = await pageObjects.infraHostsView.getAllMetricsCharts(); - expect(metricCharts.length).to.equal(11); - }); + it('should load 11 lens metric charts', async () => { + const metricCharts = await pageObjects.infraHostsView.getAllMetricsCharts(); + expect(metricCharts.length).to.equal(11); + }); - it('should have an option to open the chart in lens', async () => { - await retry.try(async () => { - await pageObjects.infraHostsView.clickAndValidateMetricChartActionOptions(); + it('should have an option to open the chart in lens', async () => { + await retry.try(async () => { + await pageObjects.infraHostsView.clickAndValidateMetricChartActionOptions(); + await browser.pressKeys(browser.keys.ESCAPE); + }); }); }); - }); - describe('Logs Tab', () => { - before(async () => { - await browser.scrollTop(); - await pageObjects.infraHostsView.visitLogsTab(); - }); + describe('Logs Tab', () => { + before(async () => { + await browser.scrollTop(); + await pageObjects.infraHostsView.visitLogsTab(); + }); - after(async () => { - await browser.scrollTop(); - }); + after(async () => { + await browser.scrollTop(); + }); - it('should load the Logs tab section when clicking on it', async () => { - await testSubjects.existOrFail('hostsView-logs'); - }); + it('should load the Logs tab section when clicking on it', async () => { + await testSubjects.existOrFail('hostsView-logs'); + }); - it('should load the Logs tab with the right columns', async () => { - await retry.try(async () => { - const columnLabels = await pageObjects.infraHostsView.getLogsTableColumnHeaders(); + it('should load the Logs tab with the right columns', async () => { + await retry.try(async () => { + const columnLabels = await pageObjects.infraHostsView.getLogsTableColumnHeaders(); - expect(columnLabels).to.eql(['Timestamp', 'host.name', 'Message']); + expect(columnLabels).to.eql(['Timestamp', 'host.name', 'Message']); + }); }); }); - }); - describe('Alerts Tab', () => { - const ACTIVE_ALERTS = 6; - const RECOVERED_ALERTS = 4; - const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; - const COLUMNS = 11; + describe('Alerts Tab', () => { + const ACTIVE_ALERTS = 6; + const RECOVERED_ALERTS = 4; + const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; + const COLUMNS = 11; - before(async () => { - await browser.scrollTop(); - await pageObjects.infraHostsView.visitAlertTab(); - }); + before(async () => { + await browser.scrollTop(); + await pageObjects.infraHostsView.visitAlertTab(); + }); - after(async () => { - await browser.scrollTop(); - }); + after(async () => { + await browser.scrollTop(); + }); - it('should correctly load the Alerts tab section when clicking on it', async () => { - testSubjects.existOrFail('hostsView-alerts'); - }); + it('should correctly load the Alerts tab section when clicking on it', async () => { + testSubjects.existOrFail('hostsView-alerts'); + }); - it('should correctly render a badge with the active alerts count', async () => { - const alertsCount = await pageObjects.infraHostsView.getAlertsCount(); + it('should correctly render a badge with the active alerts count', async () => { + const alertsCount = await pageObjects.infraHostsView.getAlertsCount(); - expect(alertsCount).to.be('6'); - }); + expect(alertsCount).to.be('6'); + }); - describe('#FilterButtonGroup', () => { - it('can be filtered to only show "all" alerts using the filter button', async () => { - await pageObjects.infraHostsView.setAlertStatusFilter(); - await retry.try(async () => { - const tableRows = await observability.alerts.common.getTableCellsInRows(); - expect(tableRows.length).to.be(ALL_ALERTS); + describe('#FilterButtonGroup', () => { + it('can be filtered to only show "all" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ALL_ALERTS); + }); }); - }); - it('can be filtered to only show "active" alerts using the filter button', async () => { - await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_ACTIVE); - await retry.try(async () => { - const tableRows = await observability.alerts.common.getTableCellsInRows(); - expect(tableRows.length).to.be(ACTIVE_ALERTS); + it('can be filtered to only show "active" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_ACTIVE); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(ACTIVE_ALERTS); + }); }); - }); - it('can be filtered to only show "recovered" alerts using the filter button', async () => { - await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_RECOVERED); - await retry.try(async () => { - const tableRows = await observability.alerts.common.getTableCellsInRows(); - expect(tableRows.length).to.be(RECOVERED_ALERTS); + it('can be filtered to only show "recovered" alerts using the filter button', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(ALERT_STATUS_RECOVERED); + await retry.try(async () => { + const tableRows = await observability.alerts.common.getTableCellsInRows(); + expect(tableRows.length).to.be(RECOVERED_ALERTS); + }); }); }); - }); - describe('#AlertsTable', () => { - it('should correctly render', async () => { - await observability.alerts.common.getTableOrFail(); - }); + describe('#AlertsTable', () => { + it('should correctly render', async () => { + await observability.alerts.common.getTableOrFail(); + }); - it('should renders the correct number of cells', async () => { - await pageObjects.infraHostsView.setAlertStatusFilter(); - await retry.try(async () => { - const cells = await observability.alerts.common.getTableCells(); - expect(cells.length).to.be(ALL_ALERTS * COLUMNS); + it('should renders the correct number of cells', async () => { + await pageObjects.infraHostsView.setAlertStatusFilter(); + await retry.try(async () => { + const cells = await observability.alerts.common.getTableCells(); + expect(cells.length).to.be(ALL_ALERTS * COLUMNS); + }); }); }); }); - }); - describe('Search Query', () => { - const filtererEntries = tableEntries.slice(0, 3); + describe('Search Query', () => { + const filtererEntries = tableEntries.slice(0, 3); - const query = filtererEntries.map((entry) => `host.name :"${entry.title}"`).join(' or '); + const query = filtererEntries.map((entry) => `host.name :"${entry.title}"`).join(' or '); - before(async () => { - await browser.scrollTop(); - await pageObjects.infraHostsView.submitQuery(query); - await await waitForPageToLoad(); - }); + before(async () => { + await browser.scrollTop(); + await pageObjects.infraHostsView.submitQuery(query); + await await waitForPageToLoad(); + }); - after(async () => { - await browser.scrollTop(); - await pageObjects.infraHostsView.submitQuery(''); - }); + after(async () => { + await browser.scrollTop(); + await pageObjects.infraHostsView.submitQuery(''); + }); - it('should filter the table content on a search submit', async () => { - const hostRows = await pageObjects.infraHostsView.getHostsTableData(); + it('should filter the table content on a search submit', async () => { + const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - expect(hostRows.length).to.equal(3); + expect(hostRows.length).to.equal(3); - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(filtererEntries[position])); + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(filtererEntries[i]); + } }); - }); - it('should update the KPIs content on a search submit', async () => { - await Promise.all( - [ - { metric: 'hostsCount', value: '3' }, - { metric: 'cpuUsage', value: '0.9%' }, - { metric: 'normalizedLoad1m', value: '0.2%' }, - { metric: 'memoryUsage', value: '17.5%' }, - { metric: 'diskUsage', value: '35.7%' }, - ].map(async ({ metric, value }) => { - await retry.try(async () => { - const tileValue = - metric === 'hostsCount' - ? await pageObjects.infraHostsView.getKPITileValue(metric) - : await pageObjects.assetDetails.getAssetDetailsKPITileValue(metric); - expect(tileValue).to.eql(value); - }); - }) - ); - }); + it('should update the KPIs content on a search submit', async () => { + await Promise.all( + [ + { metric: 'hostsCount', value: '3' }, + { metric: 'cpuUsage', value: 'N/A' }, + { metric: 'normalizedLoad1m', value: '0.2%' }, + { metric: 'memoryUsage', value: '17.5%' }, + { metric: 'diskUsage', value: '35.7%' }, + ].map(async ({ metric, value }) => { + await retry.try(async () => { + const tileValue = + metric === 'hostsCount' + ? await pageObjects.infraHostsView.getKPITileValue(metric) + : await pageObjects.assetDetails.getAssetDetailsKPITileValue(metric); + expect(tileValue).to.eql(value); + }); + }) + ); + }); - it('should update the alerts count on a search submit', async () => { - const alertsCount = await pageObjects.infraHostsView.getAlertsCount(); + it('should update the alerts count on a search submit', async () => { + const alertsCount = await pageObjects.infraHostsView.getAlertsCount(); - expect(alertsCount).to.be('6'); - }); + expect(alertsCount).to.be('6'); + }); - it('should update the alerts table content on a search submit', async () => { - const ACTIVE_ALERTS = 6; - const RECOVERED_ALERTS = 4; - const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; - const COLUMNS = 11; + it('should update the alerts table content on a search submit', async () => { + const ACTIVE_ALERTS = 6; + const RECOVERED_ALERTS = 4; + const ALL_ALERTS = ACTIVE_ALERTS + RECOVERED_ALERTS; + const COLUMNS = 11; - await pageObjects.infraHostsView.visitAlertTab(); + await pageObjects.infraHostsView.visitAlertTab(); - await pageObjects.infraHostsView.setAlertStatusFilter(); - await retry.try(async () => { - const cells = await observability.alerts.common.getTableCells(); - expect(cells.length).to.be(ALL_ALERTS * COLUMNS); + await pageObjects.infraHostsView.setAlertStatusFilter(); + await retry.try(async () => { + const cells = await observability.alerts.common.getTableCells(); + expect(cells.length).to.be(ALL_ALERTS * COLUMNS); + }); }); - }); - it('should show an error message when an invalid KQL is submitted', async () => { - await pageObjects.infraHostsView.submitQuery('cloud.provider="gcp" A'); - await testSubjects.existOrFail('hostsViewErrorCallout'); - }); + it('should show an error message when an invalid KQL is submitted', async () => { + await pageObjects.infraHostsView.submitQuery('cloud.provider="gcp" A'); + await testSubjects.existOrFail('hostsViewErrorCallout'); + }); - it('should show no data message in the table content', async () => { - await pageObjects.infraHostsView.submitQuery('host.name : "foo"'); + it('should show no data message in the table content', async () => { + await pageObjects.infraHostsView.submitQuery('host.name : "foo"'); - await waitForPageToLoad(); + await waitForPageToLoad(); - await retry.try(async () => { - await testSubjects.exists('hostsViewTableNoData'); + await retry.try(async () => { + await testSubjects.exists('hostsViewTableNoData'); + }); }); }); - }); - describe('Pagination and Sorting', () => { - before(async () => { - await browser.scrollTop(); - }); + describe('Pagination and Sorting', () => { + before(async () => { + await browser.scrollTop(); + }); - after(async () => { - await browser.scrollTop(); - }); + after(async () => { + await browser.scrollTop(); + }); - beforeEach(async () => { - await retry.try(async () => { - await pageObjects.infraHostsView.changePageSize(5); + beforeEach(async () => { + await retry.try(async () => { + await pageObjects.infraHostsView.changePageSize(5); + }); }); - }); - it('should show 5 rows on the first page', async () => { - const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position])); + it('should show 5 rows on the first page', async () => { + const hostRows = await pageObjects.infraHostsView.getHostsTableData(); + + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(tableEntries[i]); + } }); - }); - it('should paginate to the last page', async () => { - await pageObjects.infraHostsView.paginateTo(2); - const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - hostRows.forEach((row) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[5])); + it('should paginate to the last page', async () => { + await pageObjects.infraHostsView.paginateTo(2); + const hostRows = await pageObjects.infraHostsView.getHostsTableData(); + + expect(hostRows.length).to.equal(1); + + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostRowData).to.eql(tableEntries[5]); }); - }); - it('should show all hosts on the same page', async () => { - await pageObjects.infraHostsView.changePageSize(10); - const hostRows = await pageObjects.infraHostsView.getHostsTableData(); - hostRows.forEach((row, position) => { - pageObjects.infraHostsView - .getHostsRowData(row) - .then((hostRowData) => expect(hostRowData).to.eql(tableEntries[position])); + it('should show all hosts on the same page', async () => { + await pageObjects.infraHostsView.changePageSize(10); + const hostRows = await pageObjects.infraHostsView.getHostsTableData(); + + for (let i = 0; i < hostRows.length; i++) { + const hostRowData = await pageObjects.infraHostsView.getHostsRowData(hostRows[i]); + expect(hostRowData).to.eql(tableEntries[i]); + } }); - }); - it('should sort by a numeric field asc', async () => { - await pageObjects.infraHostsView.sortByCpuUsage(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[5]); + it('should sort by a numeric field asc', async () => { + await pageObjects.infraHostsView.sortByMemoryUsage(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(tableEntries[3]); - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[0]); - }); + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(tableEntries[0]); + }); - it('should sort by a numeric field desc', async () => { - await pageObjects.infraHostsView.sortByCpuUsage(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[0]); + it('should sort by a numeric field desc', async () => { + await pageObjects.infraHostsView.sortByMemoryUsage(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(tableEntries[0]); - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[5]); - }); + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(tableEntries[3]); + }); - it('should sort by text field asc', async () => { - await pageObjects.infraHostsView.sortByTitle(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[0]); + it('should sort by text field asc', async () => { + await pageObjects.infraHostsView.sortByTitle(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(tableEntries[0]); - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[2]); - }); + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(tableEntries[2]); + }); - it('should sort by text field desc', async () => { - await pageObjects.infraHostsView.sortByTitle(); - let hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataFirtPage).to.eql(tableEntries[2]); + it('should sort by text field desc', async () => { + await pageObjects.infraHostsView.sortByTitle(); + let hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataFirtPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataFirtPage).to.eql(tableEntries[2]); - await pageObjects.infraHostsView.paginateTo(2); - hostRows = await pageObjects.infraHostsView.getHostsTableData(); - const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); - expect(hostDataLastPage).to.eql(tableEntries[0]); + await pageObjects.infraHostsView.paginateTo(2); + hostRows = await pageObjects.infraHostsView.getHostsTableData(); + const hostDataLastPage = await pageObjects.infraHostsView.getHostsRowData(hostRows[0]); + expect(hostDataLastPage).to.eql(tableEntries[0]); + }); }); }); - }); - describe('#Permissions: Read Only User - Single Host Flyout', () => { - describe('Dashboards Tab', () => { - before(async () => { - await setCustomDashboardsEnabled(true); - await loginWithReadOnlyUserAndNavigateToHostsFlyout(); - await pageObjects.assetDetails.clickDashboardsTab(); - }); + describe('#Permissions: Read Only User - Single Host Flyout', () => { + describe('Dashboards Tab', () => { + before(async () => { + await setCustomDashboardsEnabled(true); + await loginWithReadOnlyUserAndNavigateToHostsFlyout(); + await pageObjects.assetDetails.clickDashboardsTab(); + }); - after(async () => { - await retry.try(async () => { - await pageObjects.infraHome.clickCloseFlyoutButton(); + after(async () => { + await retry.try(async () => { + await pageObjects.infraHome.clickCloseFlyoutButton(); + }); + await logoutAndDeleteReadOnlyUser(); }); - await logoutAndDeleteReadOnlyUser(); - }); - it('should render dashboards tab splash screen with disabled option to add dashboard', async () => { - await pageObjects.assetDetails.addDashboardExists(); - const elementToHover = await pageObjects.assetDetails.getAddDashboardButton(); - await retry.try(async () => { - await elementToHover.moveMouseTo(); - await testSubjects.existOrFail('infraCannotAddDashboardTooltip'); + it('should render dashboards tab splash screen with disabled option to add dashboard', async () => { + await pageObjects.assetDetails.addDashboardExists(); + const elementToHover = await pageObjects.assetDetails.getAddDashboardButton(); + await retry.try(async () => { + await elementToHover.moveMouseTo(); + await testSubjects.existOrFail('infraCannotAddDashboardTooltip'); + }); }); - }); - it('should not render dashboards tab if the feature is disabled', async () => { - await setCustomDashboardsEnabled(false); - await pageObjects.assetDetails.clickOverviewTab(); - await browser.refresh(); - await !pageObjects.assetDetails.dashboardsTabExists(); + it('should not render dashboards tab if the feature is disabled', async () => { + await setCustomDashboardsEnabled(false); + await pageObjects.assetDetails.clickOverviewTab(); + await browser.refresh(); + await !pageObjects.assetDetails.dashboardsTabExists(); + }); }); }); }); diff --git a/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts index 358095e4350b8..22ed2bd035ee1 100644 --- a/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs/logs_source_configuration.ts @@ -76,6 +76,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.header.waitUntilLoadingHasFinished(); + await infraSourceConfigurationForm.selectIndicesPanel(); + const nameInput = await infraSourceConfigurationForm.getNameInput(); await nameInput.clearValueWithKeyboard({ charByChar: true }); await nameInput.type('Modified Source'); diff --git a/x-pack/test/functional/apps/infra/metrics_source_configuration.ts b/x-pack/test/functional/apps/infra/metrics_source_configuration.ts index f8ba7904280b9..177e675785a41 100644 --- a/x-pack/test/functional/apps/infra/metrics_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/metrics_source_configuration.ts @@ -86,7 +86,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('renders the no indices screen when no indices match the pattern', async () => { await pageObjects.common.navigateToApp('infraOps'); - await pageObjects.infraHome.getNoMetricsIndicesPrompt(); + await pageObjects.infraHome.noDataPromptExists(); }); it('can change the metric indices to a remote cluster when connection does not exist', async () => { diff --git a/x-pack/test/functional/apps/infra/node_details.ts b/x-pack/test/functional/apps/infra/node_details.ts index 0db50b877b92a..fbc442b5079c0 100644 --- a/x-pack/test/functional/apps/infra/node_details.ts +++ b/x-pack/test/functional/apps/infra/node_details.ts @@ -32,6 +32,7 @@ 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 ); @@ -47,7 +48,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const infraSynthtraceKibanaClient = getService('infraSynthtraceKibanaClient'); - const infraSourceConfigurationForm = getService('infraSourceConfigurationForm'); const esClient = getService('es'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); @@ -103,42 +103,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }; describe('Node Details', () => { - describe('#Missing fields', function () { - before(async () => { - await pageObjects.common.navigateToUrlWithBrowserHistory('infraOps', '/settings'); - - const metricIndicesInput = await infraSourceConfigurationForm.getMetricIndicesInput(); - await metricIndicesInput.clearValueWithKeyboard({ charByChar: true }); - await metricIndicesInput.type('metrics-apm*'); - - await infraSourceConfigurationForm.saveInfraSettings(); - await pageObjects.infraHome.waitForLoading(); - await pageObjects.infraHome.getInfraMissingMetricsIndicesCallout(); - - await navigateToNodeDetails('Jennys-MBP.fritz.box', 'Jennys-MBP.fritz.box', 'host'); - await pageObjects.header.waitUntilLoadingHasFinished(); - }); - - after(async () => { - await pageObjects.common.navigateToUrlWithBrowserHistory('infraOps', '/settings'); - - const metricIndicesInput = await infraSourceConfigurationForm.getMetricIndicesInput(); - await metricIndicesInput.clearValueWithKeyboard({ charByChar: true }); - await metricIndicesInput.type('metrics-*,metricbeat-*'); - - await infraSourceConfigurationForm.saveInfraSettings(); - - await pageObjects.infraHome.waitForLoading(); - await pageObjects.infraHome.getInfraMissingRemoteClusterIndicesCallout(); - }); - - describe('KPIs', () => { - it('should render custom badge message', async () => { - await pageObjects.assetDetails.getAssetDetailsKPIMissingFieldMessageExists('cpuUsage'); - }); - }); - }); - describe('#With Asset Details', () => { before(async () => { await Promise.all([ @@ -594,7 +558,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); [ - { metric: 'cpuUsage', value: '99.6%' }, + { metric: 'cpuUsage', value: '100.0%' }, { metric: 'normalizedLoad1m', value: '1,300.3%' }, { metric: 'memoryUsage', value: '42.2%' }, { metric: 'diskUsage', value: '36.0%' }, diff --git a/x-pack/test/functional/apps/lens/group2/fields_list.ts b/x-pack/test/functional/apps/lens/group2/fields_list.ts index 14151f481913f..dff265f0c4a40 100644 --- a/x-pack/test/functional/apps/lens/group2/fields_list.ts +++ b/x-pack/test/functional/apps/lens/group2/fields_list.ts @@ -47,6 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await fieldEditor.typeScript("emit('abc')"); await fieldEditor.save(); await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.missingOrFail('fieldEditor'); }); }); it('should show all fields as available', async () => { diff --git a/x-pack/test/functional/apps/managed_content/managed_content.ts b/x-pack/test/functional/apps/managed_content/managed_content.ts index 1bee3acd761bb..f5e5f528ba51c 100644 --- a/x-pack/test/functional/apps/managed_content/managed_content.ts +++ b/x-pack/test/functional/apps/managed_content/managed_content.ts @@ -26,8 +26,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const listingTable = getService('listingTable'); const log = getService('log'); - // Failing: See https://github.com/elastic/kibana/issues/177551 - describe.skip('Managed Content', () => { + describe('Managed Content', () => { before(async () => { esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/managed_content'); @@ -55,7 +54,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('lens', async () => { await PageObjects.common.navigateToActualUrl( 'lens', - 'edit/managed-36db-4a3b-a4ba-7a64ab8f130b' + '/edit/managed-36db-4a3b-a4ba-7a64ab8f130b' ); await PageObjects.lens.waitForVisualization('xyVisChart'); @@ -64,7 +63,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToActualUrl( 'lens', - 'edit/unmanaged-36db-4a3b-a4ba-7a64ab8f130b' + '/edit/unmanaged-36db-4a3b-a4ba-7a64ab8f130b' ); await PageObjects.lens.waitForVisualization('xyVisChart'); @@ -72,11 +71,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await expectManagedContentSignifiers(false, 'lnsApp_saveButton'); }); - // FLAKY: https://github.com/elastic/kibana/issues/178920 - it.skip('discover', async () => { + it('discover', async () => { await PageObjects.common.navigateToActualUrl( 'discover', - 'view/managed-3d62-4113-ac7c-de2e20a68fbc' + '/view/managed-3d62-4113-ac7c-de2e20a68fbc' ); await PageObjects.discover.waitForDiscoverAppOnScreen(); @@ -84,7 +82,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToActualUrl( 'discover', - 'view/unmanaged-3d62-4113-ac7c-de2e20a68fbc' + '/view/unmanaged-3d62-4113-ac7c-de2e20a68fbc' ); await PageObjects.discover.waitForDiscoverAppOnScreen(); @@ -94,7 +92,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('visualize', async () => { await PageObjects.common.navigateToActualUrl( 'visualize', - 'edit/managed-feb9-4ba6-9538-1b8f67fb4f57' + '/edit/managed-feb9-4ba6-9538-1b8f67fb4f57' ); await PageObjects.visChart.waitForVisualization(); @@ -102,7 +100,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToActualUrl( 'visualize', - 'edit/unmanaged-feb9-4ba6-9538-1b8f67fb4f57' + '/edit/unmanaged-feb9-4ba6-9538-1b8f67fb4f57' ); await PageObjects.visChart.waitForVisualization(); @@ -112,7 +110,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('maps', async () => { await PageObjects.common.navigateToActualUrl( 'maps', - 'map/managed-d7ab-46eb-a807-8fed28ed8566' + '/map/managed-d7ab-46eb-a807-8fed28ed8566' ); await PageObjects.maps.waitForLayerAddPanelClosed(); @@ -120,7 +118,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToActualUrl( 'maps', - 'map/unmanaged-d7ab-46eb-a807-8fed28ed8566' + '/map/unmanaged-d7ab-46eb-a807-8fed28ed8566' ); await PageObjects.maps.waitForLayerAddPanelClosed(); @@ -147,20 +145,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('managed panels in dashboards', () => { + // unskip with https://github.com/elastic/kibana/issues/190138 fix + describe.skip('managed panels in dashboards', () => { it('inlines panels when managed dashboard cloned', async () => { await PageObjects.common.navigateToActualUrl( 'dashboard', - 'view/c44c86f9-b105-4a9c-9a24-449a58a827f3', - // for some reason the URL didn't always match the expected, so I turned off this check - // URL doesn't matter as long as we get the dashboard app - { ensureCurrentUrl: false } + '/view/c44c86f9-b105-4a9c-9a24-449a58a827f3' ); await PageObjects.dashboard.waitForRenderComplete(); await PageObjects.dashboard.duplicateDashboard(); - await PageObjects.dashboard.waitForRenderComplete(); await testSubjects.missingOrFail('embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION'); @@ -177,7 +172,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { { name: 'Managed map', type: 'map' }, { name: 'Managed saved search', type: 'search' }, ]); - await testSubjects.missingOrFail('embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION'); await dashboardAddPanel.addEmbeddables([ diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts index 3a60e8fca97c2..89eb1b9361272 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts @@ -42,7 +42,8 @@ export default function ({ getService }: FtrProviderContext) { describe('forecasts', function () { this.tags(['ml']); - describe('with single metric job', function () { + // FLAKY: https://github.com/elastic/kibana/issues/164381 + describe.skip('with single metric job', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); await ml.testResources.createDataViewIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts index b944ebd9b8306..3c87d53031aa4 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts @@ -85,7 +85,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render content virtual column properly', async () => { it('should render log level and log message when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('A sample log')).to.be(true); @@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render log message when present and skip log level when missing', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(false); expect(cellValue.includes('A sample log')).to.be(true); @@ -103,7 +103,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from error object when top level message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(2, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('error.message')).to.be(true); @@ -113,7 +113,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from event.original when top level message and error.message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(3, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('event.original')).to.be(true); @@ -123,7 +123,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the whole JSON when neither message, error.message and event.original are present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); @@ -137,7 +137,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with no message field should open JSON Viewer', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(4, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); }); }); @@ -145,7 +145,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with message field should open regular popover', async () => { await navigateToLogsExplorer(); await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(3, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); await testSubjects.existOrFail('euiDataGridExpansionPopover'); }); }); @@ -154,7 +154,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render resource virtual column properly', async () => { it('should render service name and host name when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('synth-service')).to.be(true); expect(cellValue.includes('synth-host')).to.be(true); @@ -168,7 +168,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render a popover with cell actions when a chip on content column is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); await logLevelChip.click(); // Check Filter In button is present @@ -182,7 +182,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; @@ -203,7 +203,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; @@ -222,7 +222,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where service.name value is selected', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); diff --git a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts b/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts index 21dd8a6772bb7..58b123d08cdaf 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render control column with proper header', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', 'actions']); + expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); }); }); @@ -61,7 +61,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the degraded icon in the leading control column if degraded doc exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 2); const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); expect(degradedButton).to.not.be.empty(); }); @@ -69,7 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); + const cellElement = await dataGrid.getCellElement(0, 2); const degradedDisableButton = await cellElement.findByTestSubject( 'docTableDegradedDocDoesNotExist' ); @@ -79,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 1); + const cellElement = await dataGrid.getCellElement(4, 3); const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); expect(stacktraceButton).to.not.be.empty(); }); @@ -87,7 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 3); const stacktraceButton = await cellElement.findByTestSubject( 'docTableStacktraceDoesNotExist' ); diff --git a/x-pack/test/functional/apps/spaces/enter_space.ts b/x-pack/test/functional/apps/spaces/enter_space.ts index 85cf3b39cf09f..4cff39e05b413 100644 --- a/x-pack/test/functional/apps/spaces/enter_space.ts +++ b/x-pack/test/functional/apps/spaces/enter_space.ts @@ -110,10 +110,37 @@ export default function enterSpaceFunctionalTests({ const anchorElement = await PageObjects.spaceSelector.getSpaceCardAnchor(spaceId); const path = await anchorElement.getAttribute('href'); - const pathWithNextRoute = `${path}?next=/app/management/kibana/objects?initialQuery=type:(visualization)#/view`; + const pathWithNextRoute = `${path}?next=/app/management/kibana/objects?initialQuery=type:(visualization)#/view/uuid`; await browser.navigateTo(pathWithNextRoute); + await PageObjects.spaceSelector.expectRoute( + spaceId, + '/app/management/kibana/objects?initialQuery=type%3A(visualization)#/view/uuid' + ); + }); + + it('allows user to navigate to different space with default route preserving url hash and search', async () => { + const spaceId = 'another-space'; + + await kibanaServer.uiSettings.replace( + { + defaultRoute: '/app/management/kibana/objects?initialQuery=type:(visualization)#/view', + buildNum: 8467, + 'dateFormat:tz': 'UTC', + }, + { space: 'another-space' } + ); + + await PageObjects.security.login(undefined, undefined, { + expectSpaceSelector: true, + }); + + const anchorElement = await PageObjects.spaceSelector.getSpaceCardAnchor(spaceId); + const path = await anchorElement.getAttribute('href'); + + await browser.navigateTo(path!); + await PageObjects.spaceSelector.expectRoute( spaceId, '/app/management/kibana/objects?initialQuery=type%3A(visualization)#/view' @@ -142,6 +169,15 @@ export default function enterSpaceFunctionalTests({ it('falls back to the default home page if provided next route is malformed', async () => { const spaceId = 'another-space'; + await kibanaServer.uiSettings.replace( + { + defaultRoute: '/app/canvas', + buildNum: 8467, + 'dateFormat:tz': 'UTC', + }, + { space: 'another-space' } + ); + await PageObjects.security.login(undefined, undefined, { expectSpaceSelector: true, }); diff --git a/x-pack/test/functional/apps/spaces/index.ts b/x-pack/test/functional/apps/spaces/index.ts index aa034f4be012b..3fe77a1a4528b 100644 --- a/x-pack/test/functional/apps/spaces/index.ts +++ b/x-pack/test/functional/apps/spaces/index.ts @@ -14,5 +14,6 @@ export default function spacesApp({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./spaces_selection')); loadTestFile(require.resolve('./enter_space')); loadTestFile(require.resolve('./create_edit_space')); + loadTestFile(require.resolve('./spaces_grid')); }); } diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts index 0d286f5ffa621..f99c2daf38960 100644 --- a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts +++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts @@ -7,6 +7,11 @@ import { FtrConfigProviderContext } from '@kbn/test'; +/** + * NOTE: The solution view is currently only available in the cloud environment. + * This test suite fakes a cloud environement by setting the cloud.id and cloud.base_url + */ + export default async function ({ readConfigFile }: FtrConfigProviderContext) { const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); @@ -17,10 +22,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.get('kbnTestServer'), serverArgs: [ ...functionalConfig.get('kbnTestServer.serverArgs'), - '--xpack.cloud_integrations.experiments.enabled=true', - '--xpack.cloud_integrations.experiments.launch_darkly.sdk_key=a_string', - '--xpack.cloud_integrations.experiments.launch_darkly.client_id=a_string', - '--xpack.cloud_integrations.experiments.flag_overrides.solutionNavEnabled=true', // Note: the base64 string in the cloud.id config contains the ES endpoint required in the functional tests '--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=', '--xpack.cloud.base_url=https://cloud.elastic.co', diff --git a/x-pack/test/functional/apps/spaces/spaces_grid.ts b/x-pack/test/functional/apps/spaces/spaces_grid.ts new file mode 100644 index 0000000000000..62363802db98a --- /dev/null +++ b/x-pack/test/functional/apps/spaces/spaces_grid.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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function enterSpaceFunctionalTests({ + getService, + getPageObjects, +}: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['security', 'spaceSelector', 'common']); + const spacesService = getService('spaces'); + const testSubjects = getService('testSubjects'); + + const anotherSpace = { + id: 'space2', + name: 'space2', + disabledFeatures: [], + }; + + describe('Spaces grid', function () { + before(async () => { + await spacesService.create(anotherSpace); + + await PageObjects.common.navigateToApp('spacesManagement'); + await testSubjects.existOrFail('spaces-grid-page'); + }); + + after(async () => { + await spacesService.delete('another-space'); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('can switch to a space from the row in the grid', async () => { + // use the "current" badge confirm that Default is the current space + await testSubjects.existOrFail('spacesListCurrentBadge-default'); + // click the switch button of "another space" + await PageObjects.spaceSelector.clickSwitchSpaceButton('space2'); + // use the "current" badge confirm that "Another Space" is now the current space + await testSubjects.existOrFail('spacesListCurrentBadge-space2'); + }); + }); +} diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 834aee5fbaa8c..4fdb988fef098 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -146,6 +146,9 @@ export default async function ({ readConfigFile }) { snapshotRestore: { pathname: '/app/management/data/snapshot_restore', }, + spacesManagement: { + pathname: '/app/management/kibana/spaces', + }, remoteClusters: { pathname: '/app/management/data/remote_clusters', }, @@ -182,6 +185,15 @@ export default async function ({ readConfigFile }) { maintenanceWindows: { pathname: '/app/management/insightsAndAlerting/maintenanceWindows', }, + obsAIAssistant: { + pathname: '/app/observabilityAIAssistant', + }, + aiAssistantManagementSelection: { + pathname: '/app/management/kibana/aiAssistantManagementSelection', + }, + obsAIAssistantManagement: { + pathname: '/app/management/kibana/observabilityAiAssistantManagement', + }, }, suiteTags: { diff --git a/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz b/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz index 00c6963b16937..b400db7f4540d 100644 Binary files a/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz and b/x-pack/test/functional/es_archives/auditbeat/hosts/data.json.gz differ diff --git a/x-pack/test/functional/page_objects/dataset_quality.ts b/x-pack/test/functional/page_objects/dataset_quality.ts index 7f918ce3ba76f..b2d07161ddbaa 100644 --- a/x-pack/test/functional/page_objects/dataset_quality.ts +++ b/x-pack/test/functional/page_objects/dataset_quality.ts @@ -79,7 +79,7 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv datasetSearchInput: '[placeholder="Filter data sets"]', showFullDatasetNamesSwitch: 'button[aria-label="Show full data set names"]', showInactiveDatasetsNamesSwitch: 'button[aria-label="Show inactive data sets"]', - superDatePickerApplyButton: '.euiQuickSelect__applyButton', + superDatePickerApplyButton: '[data-test-subj="superDatePickerQuickSelectApplyButton"]', }; const testSubjectSelectors = { 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 07efff0fc4d72..ec7908d916e9b 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -94,7 +94,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide async clickOnFirstNode() { const firstNode = await this.getFirstNode(); - firstNode.click(); + return firstNode.click(); }, async clickOnGoToNodeDetails() { @@ -279,8 +279,12 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide await testSubjects.click('createSavedViewButton'); }, - async getNoMetricsIndicesPrompt() { - return testSubjects.find('noDataPage'); + async noDataPromptExists() { + return testSubjects.existOrFail('noDataPage'); + }, + + async noDataPromptAddDataClick() { + return testSubjects.click('noDataDefaultFooterAction'); }, async getNoMetricsDataPrompt() { diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts index 18407b72da412..d92cf51892a5f 100644 --- a/x-pack/test/functional/page_objects/infra_hosts_view.ts +++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts @@ -35,11 +35,11 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { // Inventory UI async clickTryHostViewLink() { - return await testSubjects.click('inventory-hostsView-link'); + return testSubjects.click('inventory-hostsView-link'); }, async clickTryHostViewBadge() { - return await testSubjects.click('inventory-hostsView-link-badge'); + return testSubjects.click('inventory-hostsView-link-badge'); }, // Table @@ -129,8 +129,6 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { const button = await element.findByTestSubject('embeddablePanelToggleMenuIcon'); await button.click(); await testSubjects.existOrFail('embeddablePanelAction-openInLens'); - // forces the modal to close - await element.click(); }, // KPIs @@ -249,17 +247,17 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) { }, // Sorting - getCpuUsageHeader() { - return testSubjects.find('tableHeaderCell_cpu_3'); + getMemoryHeader() { + return testSubjects.find('tableHeaderCell_memory_5'); }, getTitleHeader() { return testSubjects.find('tableHeaderCell_title_2'); }, - async sortByCpuUsage() { - const diskLatency = await this.getCpuUsageHeader(); - const button = await testSubjects.findDescendant('tableHeaderSortButton', diskLatency); + async sortByMemoryUsage() { + const memory = await this.getMemoryHeader(); + const button = await testSubjects.findDescendant('tableHeaderSortButton', memory); await button.click(); }, diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index b52ccbdd456f7..cd9a3ed385b73 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -1035,15 +1035,8 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont /** * Changes the index pattern in the data panel */ - async switchDataPanelIndexPattern( - dataViewTitle: string, - transitionFromTextBasedLanguages?: boolean - ) { - await PageObjects.unifiedSearch.switchDataView( - 'lns-dataView-switch-link', - dataViewTitle, - transitionFromTextBasedLanguages - ); + async switchDataPanelIndexPattern(dataViewTitle: string) { + await PageObjects.unifiedSearch.switchDataView('lns-dataView-switch-link', dataViewTitle); await PageObjects.header.waitUntilLoadingHasFinished(); }, diff --git a/x-pack/test/functional/page_objects/search_playground_page.ts b/x-pack/test/functional/page_objects/search_playground_page.ts index 8e59c7cc3e37a..d03457298a7c7 100644 --- a/x-pack/test/functional/page_objects/search_playground_page.ts +++ b/x-pack/test/functional/page_objects/search_playground_page.ts @@ -47,6 +47,12 @@ export function SearchPlaygroundPageProvider({ getService }: FtrProviderContext) }, }); }, + + async expectInSession(key: string, value: string | undefined): Promise<void> { + const session = (await browser.getLocalStorageItem(SESSION_KEY)) || '{}'; + const state = JSON.parse(session); + expect(state[key]).to.be(value); + }, }, PlaygroundStartChatPage: { async expectPlaygroundStartChatPageComponentsToExist() { @@ -151,6 +157,20 @@ export function SearchPlaygroundPageProvider({ getService }: FtrProviderContext) await testSubjects.existOrFail('summarizationPanel'); }, + async updatePrompt(prompt: string) { + await testSubjects.setValue('instructionsPrompt', prompt); + }, + + async updateQuestion(question: string) { + await testSubjects.setValue('questionInput', question); + }, + + async expectQuestionInputToBeEmpty() { + const questionInput = await testSubjects.find('questionInput'); + const question = await questionInput.getAttribute('value'); + expect(question).to.be.empty(); + }, + async sendQuestion() { await testSubjects.setValue('questionInput', 'test question'); await testSubjects.click('sendQuestionButton'); diff --git a/x-pack/test/functional/page_objects/space_selector_page.ts b/x-pack/test/functional/page_objects/space_selector_page.ts index a9bea52bcd2e8..ea9a7948410f3 100644 --- a/x-pack/test/functional/page_objects/space_selector_page.ts +++ b/x-pack/test/functional/page_objects/space_selector_page.ts @@ -212,7 +212,21 @@ export class SpaceSelectorPageObject extends FtrService { await this.testSubjects.setValue('descriptionSpaceText', descriptionSpace); } + async clickSwitchSpaceButton(spaceName: string) { + const collapsedButtonSelector = '[data-test-subj=euiCollapsedItemActionsButton]'; + // open context menu + await this.find.clickByCssSelector(`#${spaceName}-actions ${collapsedButtonSelector}`); + // click context menu item + await this.find.clickByCssSelector( + `.euiContextMenuItem[data-test-subj="${spaceName}-switchSpace"]` // can not use testSubj: multiple elements exist with the same data-test-subj + ); + } + async clickOnDeleteSpaceButton(spaceName: string) { + const collapsedButtonSelector = '[data-test-subj=euiCollapsedItemActionsButton]'; + // open context menu + await this.find.clickByCssSelector(`#${spaceName}-actions ${collapsedButtonSelector}`); + // click context menu item await this.testSubjects.click(`${spaceName}-deleteSpace`); } diff --git a/x-pack/test/functional/services/infra_source_configuration_form.ts b/x-pack/test/functional/services/infra_source_configuration_form.ts index da39347c36389..c8d28e0e3656b 100644 --- a/x-pack/test/functional/services/infra_source_configuration_form.ts +++ b/x-pack/test/functional/services/infra_source_configuration_form.ts @@ -30,7 +30,9 @@ export function InfraSourceConfigurationFormProvider({ async getMetricIndicesInput(): Promise<WebElementWrapper> { return await testSubjects.findDescendant('~metricIndicesInput', await this.getForm()); }, - + async selectIndicesPanel(): Promise<void> { + return await testSubjects.click('logIndicesCheckableCard'); + }, /** * Logs */ diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index a26ac26906468..8b2c46d474c0c 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -1473,11 +1473,13 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { } }, - async stopTrainedModelDeploymentES(deploymentId: string) { + async stopTrainedModelDeploymentES(deploymentId: string, force: boolean = false) { log.debug(`Stopping trained model deployment with id "${deploymentId}"`); - const { body, status } = await esSupertest.post( - `/_ml/trained_models/${deploymentId}/deployment/_stop` - ); + const url = `/_ml/trained_models/${deploymentId}/deployment/_stop${ + force ? '?force=true' : '' + }`; + + const { body, status } = await esSupertest.post(url); this.assertResponseStatusCode(200, status, body); log.debug('> Trained model deployment stopped'); @@ -1570,8 +1572,13 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { ); }, - async importTrainedModel(modelId: string, modelName: SupportedTrainedModelNamesType) { - await this.createTrainedModel(modelId, this.getTrainedModelConfig(modelName)); + async importTrainedModel( + modelId: string, + modelName: SupportedTrainedModelNamesType, + config?: PutTrainedModelConfig + ) { + const trainedModelConfig = config ?? this.getTrainedModelConfig(modelName); + await this.createTrainedModel(modelId, trainedModelConfig); await this.createTrainedModelVocabularyES(modelId, this.getTrainedModelVocabulary(modelName)); await this.uploadTrainedModelDefinitionES( modelId, diff --git a/x-pack/test/functional/services/transform/date_picker.ts b/x-pack/test/functional/services/transform/date_picker.ts index 8752a398c2276..3da0c88fce6b7 100644 --- a/x-pack/test/functional/services/transform/date_picker.ts +++ b/x-pack/test/functional/services/transform/date_picker.ts @@ -37,7 +37,9 @@ export function TransformDatePickerProvider({ getService, getPageObjects }: FtrP await find.selectValue(`[aria-label*="Time unit"]`, timeUnit); // Apply - const applyButton = await quickMenuElement.findByClassName('euiQuickSelect__applyButton'); + const applyButton = await quickMenuElement.findByTestSubject( + 'superDatePickerQuickSelectApplyButton' + ); const actualApplyButtonText = await applyButton.getVisibleText(); expect(actualApplyButtonText).to.be('Apply'); diff --git a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts index 300aa0fb0a8a1..314bc202a6268 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts @@ -324,7 +324,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await titleElem.getAttribute('value')).to.equal(dataView); }; - describe('Search source Alert', () => { + // FLAKY: https://github.com/elastic/kibana/issues/190090 + describe.skip('Search source Alert', () => { before(async () => { await security.testUser.setRoles(['discover_alert']); diff --git a/x-pack/test/kibana.jsonc b/x-pack/test/kibana.jsonc index 28ec73724560e..609e591270853 100644 --- a/x-pack/test/kibana.jsonc +++ b/x-pack/test/kibana.jsonc @@ -1,6 +1,6 @@ { "type": "test-helper", "id": "@kbn/test-suites-xpack", - "owner": "@elastic/appex-qa", + "owner": [], "devOnly": true } diff --git a/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts b/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts index 75f7bb628b4be..74f0016f009f7 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/configs/index.ts @@ -8,6 +8,7 @@ import { mapValues } from 'lodash'; import path from 'path'; import { createTestConfig, CreateTestConfig } from '../common/config'; +import { SUPPORTED_TRAINED_MODELS } from '../../functional/services/ml/api'; export const observabilityAIAssistantDebugLogger = { name: 'plugins.observabilityAIAssistant', @@ -30,6 +31,7 @@ export const observabilityAIAssistantFtrConfigs = { __dirname, '../../../../test/analytics/plugins/analytics_ftr_helpers' ), + 'xpack.observabilityAIAssistant.modelId': SUPPORTED_TRAINED_MODELS.TINY_ELSER.name, }, }, }; diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/helpers.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/helpers.ts new file mode 100644 index 0000000000000..1d9a9170e56ea --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/helpers.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 { MachineLearningProvider } from '../../../api_integration/services/ml'; +import { SUPPORTED_TRAINED_MODELS } from '../../../functional/services/ml/api'; + +export const TINY_ELSER = { + ...SUPPORTED_TRAINED_MODELS.TINY_ELSER, + id: SUPPORTED_TRAINED_MODELS.TINY_ELSER.name, +}; + +export async function createKnowledgeBaseModel(ml: ReturnType<typeof MachineLearningProvider>) { + const config = { + ...ml.api.getTrainedModelConfig(TINY_ELSER.name), + input: { + field_names: ['text_field'], + }, + }; + await ml.api.importTrainedModel(TINY_ELSER.name, TINY_ELSER.id, config); + await ml.api.assureMlStatsIndexExists(); +} +export async function deleteKnowledgeBaseModel(ml: ReturnType<typeof MachineLearningProvider>) { + await ml.api.stopTrainedModelDeploymentES(TINY_ELSER.id, true); + await ml.api.deleteTrainedModelES(TINY_ELSER.id); + await ml.api.cleanMlIndices(); + await ml.testResources.cleanMLSavedObjects(); +} diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts new file mode 100644 index 0000000000000..74ba11ce2eb9e --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts @@ -0,0 +1,257 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { createKnowledgeBaseModel, deleteKnowledgeBaseModel } from './helpers'; + +interface KnowledgeBaseEntry { + id: string; + text: string; +} + +export default function ApiTest({ getService }: FtrProviderContext) { + const ml = getService('ml'); + const es = getService('es'); + + const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); + const KB_INDEX = '.kibana-observability-ai-assistant-kb-*'; + + describe('Knowledge base', () => { + before(async () => { + await createKnowledgeBaseModel(ml); + }); + + after(async () => { + await deleteKnowledgeBaseModel(ml); + }); + + it('returns 200 on knowledge base setup', async () => { + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + }) + .expect(200); + expect(res.body).to.eql({}); + }); + describe('when managing a single entry', () => { + const knowledgeBaseEntry = { + id: 'my-doc-id-1', + text: 'My content', + }; + it('returns 200 on create', async () => { + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/entries/save', + params: { body: knowledgeBaseEntry }, + }) + .expect(200); + const res = await observabilityAIAssistantAPIClient.editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { + query: '', + sortBy: 'doc_id', + sortDirection: 'asc', + }, + }, + }); + const entry = res.body.entries[0]; + expect(entry.id).to.equal(knowledgeBaseEntry.id); + expect(entry.text).to.equal(knowledgeBaseEntry.text); + }); + + it('returns 200 on get entries and entry exists', async () => { + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { + query: '', + sortBy: 'doc_id', + sortDirection: 'asc', + }, + }, + }) + .expect(200); + const entry = res.body.entries[0]; + expect(entry.id).to.equal(knowledgeBaseEntry.id); + expect(entry.text).to.equal(knowledgeBaseEntry.text); + }); + + it('returns 200 on delete', async () => { + const entryId = 'my-doc-id-1'; + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', + params: { + path: { entryId }, + }, + }) + .expect(200); + + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { + query: '', + sortBy: 'doc_id', + sortDirection: 'asc', + }, + }, + }) + .expect(200); + expect( + res.body.entries.filter((entry: KnowledgeBaseEntry) => entry.id.startsWith('my-doc-id')) + .length + ).to.eql(0); + }); + + it('returns 500 on delete not found', async () => { + const entryId = 'my-doc-id-1'; + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', + params: { + path: { entryId }, + }, + }) + .expect(500); + }); + }); + describe('when managing multiple entries', () => { + before(async () => { + es.deleteByQuery({ + index: KB_INDEX, + conflicts: 'proceed', + query: { match_all: {} }, + }); + }); + afterEach(async () => { + es.deleteByQuery({ + index: KB_INDEX, + conflicts: 'proceed', + query: { match_all: {} }, + }); + }); + const knowledgeBaseEntries: KnowledgeBaseEntry[] = [ + { + id: 'my_doc_a', + text: 'My content a', + }, + { + id: 'my_doc_b', + text: 'My content b', + }, + { + id: 'my_doc_c', + text: 'My content c', + }, + ]; + it('returns 200 on create', async () => { + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import', + params: { body: { entries: knowledgeBaseEntries } }, + }) + .expect(200); + + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { + query: '', + sortBy: 'doc_id', + sortDirection: 'asc', + }, + }, + }) + .expect(200); + expect( + res.body.entries.filter((entry: KnowledgeBaseEntry) => entry.id.startsWith('my_doc')) + .length + ).to.eql(3); + }); + + it('allows sorting', async () => { + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import', + params: { body: { entries: knowledgeBaseEntries } }, + }) + .expect(200); + + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { + query: '', + sortBy: 'doc_id', + sortDirection: 'desc', + }, + }, + }) + .expect(200); + + const entries = res.body.entries.filter((entry: KnowledgeBaseEntry) => + entry.id.startsWith('my_doc') + ); + expect(entries[0].id).to.eql('my_doc_c'); + expect(entries[1].id).to.eql('my_doc_b'); + expect(entries[2].id).to.eql('my_doc_a'); + + // asc + const resAsc = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { + query: '', + sortBy: 'doc_id', + sortDirection: 'asc', + }, + }, + }) + .expect(200); + + const entriesAsc = resAsc.body.entries.filter((entry: KnowledgeBaseEntry) => + entry.id.startsWith('my_doc') + ); + expect(entriesAsc[0].id).to.eql('my_doc_a'); + expect(entriesAsc[1].id).to.eql('my_doc_b'); + expect(entriesAsc[2].id).to.eql('my_doc_c'); + }); + it('allows searching', async () => { + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/entries/import', + params: { body: { entries: knowledgeBaseEntries } }, + }) + .expect(200); + + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { + query: 'my_doc_a', + sortBy: 'doc_id', + sortDirection: 'asc', + }, + }, + }) + .expect(200); + + expect(res.body.entries.length).to.eql(1); + expect(res.body.entries[0].id).to.eql('my_doc_a'); + }); + }); + }); +} diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.todo.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.todo.ts deleted file mode 100644 index dc1c34fbdd4c2..0000000000000 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.todo.ts +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 getPort from 'get-port'; -import http, { Server } from 'http'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -/* - This test is disabled because the Knowledge base requires a trained model (ELSER) - which is not available in FTR tests. - - When a comparable, less expensive trained model is available, this test should be re-enabled. -*/ - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const KNOWLEDGE_BASE_API_URL = `/internal/observability_ai_assistant/kb`; - - describe('Knowledge base', () => { - const requestHandler = ( - request: http.IncomingMessage, - response: http.ServerResponse<http.IncomingMessage> & { req: http.IncomingMessage } - ) => {}; - - let server: Server; - - before(async () => { - const port = await getPort({ port: getPort.makeRange(9000, 9100) }); - - server = http - .createServer((request, response) => { - requestHandler(request, response); - }) - .listen(port); - }); - - after(() => { - server.close(); - }); - - it('should be possible to set up the knowledge base', async () => { - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/setup`) - .set('kbn-xsrf', 'foo') - .expect(200) - .then((response) => { - expect(response.body).to.eql({ entries: [] }); - }); - }); - - describe('when creating a single entry', () => { - it('returns a 200 when using the right payload', async () => { - const knowledgeBaseEntry = { - id: 'my-doc-id-1', - text: 'My content', - }; - - await supertest - .post(`${KNOWLEDGE_BASE_API_URL}/entries/save`) - .set('kbn-xsrf', 'foo') - .send(knowledgeBaseEntry) - .expect(200); - - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/entries?query=&sortBy=doc_id&sortDirection=asc`) - .set('kbn-xsrf', 'foo') - .expect(200) - .then((response) => { - expect(response.body).to.eql({ entries: [knowledgeBaseEntry] }); - }); - }); - - it('returns a 500 when using the wrong payload', async () => { - const knowledgeBaseEntry = { - foo: 'my-doc-id-1', - }; - - await supertest - .post(`${KNOWLEDGE_BASE_API_URL}/entries/save`) - .set('kbn-xsrf', 'foo') - .send(knowledgeBaseEntry) - .expect(500); - }); - }); - - describe('when importing multiple entries', () => { - it('returns a 200 when using the right payload', async () => { - const knowledgeBaseEntries = [ - { - id: 'my-doc-id-2', - text: 'My content 2', - }, - { - id: 'my-doc-id-3', - text: 'My content 3', - }, - ]; - - await supertest - .post(`${KNOWLEDGE_BASE_API_URL}/entries/import`) - .set('kbn-xsrf', 'foo') - .send(knowledgeBaseEntries) - .expect(200); - - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/entries?query=&sortBy=doc_id&sortDirection=asc`) - .set('kbn-xsrf', 'foo') - .expect(200) - .then((response) => { - expect(response.body).to.eql({ entries: knowledgeBaseEntries }); - }); - }); - - it('returns a 500 when using the wrong payload', async () => { - const knowledgeBaseEntry = { - foo: 'my-doc-id-1', - }; - - await supertest - .post(`${KNOWLEDGE_BASE_API_URL}/entries/import`) - .set('kbn-xsrf', 'foo') - .send(knowledgeBaseEntry) - .expect(500); - }); - }); - - describe('when deleting an entry', () => { - it('returns a 200 when the item is found and the item is deleted', async () => { - await supertest - .delete(`${KNOWLEDGE_BASE_API_URL}/entries/delete/my-doc-id-2`) - .set('kbn-xsrf', 'foo') - .expect(200); - - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/entries?query=&sortBy=doc_id&sortDirection=asc`) - .set('kbn-xsrf', 'foo') - .expect(200) - .then((response) => { - expect(response.body).to.eql({ - entries: [ - { - id: 'my-doc-id-1', - text: 'My content 1', - confidence: 'high', - }, - { - id: 'my-doc-id-3', - text: 'My content 3', - }, - ], - }); - }); - }); - - it('returns a 500 when the item is not found ', async () => { - return await supertest - .delete(`${KNOWLEDGE_BASE_API_URL}/entries/delete/my-doc-id-2`) - .set('kbn-xsrf', 'foo') - .expect(500); - }); - }); - - describe('when retrieving entries', () => { - it('returns a 200 when calling get entries with the right parameters', async () => { - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/entries?query=&sortBy=doc_id&sortDirection=asc`) - .set('kbn-xsrf', 'foo') - .expect(200) - .then((response) => { - expect(response.body).to.eql({ entries: [] }); - }); - }); - - it('allows sorting', async () => { - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/entries?query=&sortBy=doc_id&sortDirection=desc`) - .set('kbn-xsrf', 'foo') - .expect(200) - .then((response) => { - expect(response.body).to.eql({ - entries: [ - { - id: 'my-doc-id-3', - text: 'My content 3', - }, - { - id: 'my-doc-id-1', - text: 'My content 1', - }, - ], - }); - }); - }); - - it('allows searching', async () => { - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/entries?query=my-doc-3&sortBy=doc_id&sortDirection=asc`) - .set('kbn-xsrf', 'foo') - .expect(200) - .then((response) => { - expect(response.body).to.eql({ - entries: [ - { - id: 'my-doc-id-3', - text: 'My content 3', - }, - ], - }); - }); - }); - - it('returns a 500 when calling get entries with the wrong parameters', async () => { - return supertest - .get(`${KNOWLEDGE_BASE_API_URL}/entries`) - .set('kbn-xsrf', 'foo') - .expect(500); - }); - }); - }); -} diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts new file mode 100644 index 0000000000000..9099eff540d35 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteKnowledgeBaseModel, createKnowledgeBaseModel } from './helpers'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const ml = getService('ml'); + const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); + + describe('/internal/observability_ai_assistant/kb/setup', () => { + it('returns empty object when successful', async () => { + await createKnowledgeBaseModel(ml); + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + }) + .expect(200); + expect(res.body).to.eql({}); + await deleteKnowledgeBaseModel(ml); + }); + + it('returns bad request if model cannot be installed', async () => { + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + }) + .expect(400); + }); + }); +} diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts new file mode 100644 index 0000000000000..62d2ab7cae785 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteKnowledgeBaseModel, createKnowledgeBaseModel, TINY_ELSER } from './helpers'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const ml = getService('ml'); + const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); + + describe('/internal/observability_ai_assistant/kb/status', () => { + before(async () => { + await createKnowledgeBaseModel(ml); + await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + }) + .expect(200); + }); + + after(async () => { + await deleteKnowledgeBaseModel(ml); + }); + + it('returns correct status after knowledge base is setup', async () => { + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/status', + }) + .expect(200); + expect(res.body.deployment_state).to.eql('started'); + expect(res.body.model_name).to.eql(TINY_ELSER.id); + }); + + it('returns correct status after elser is stopped', async () => { + await ml.api.stopTrainedModelDeploymentES(TINY_ELSER.id, true); + + const res = await observabilityAIAssistantAPIClient + .editorUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/status', + }) + .expect(200); + + expect(res.body).to.eql({ + ready: false, + model_name: TINY_ELSER.id, + }); + }); + }); +} diff --git a/x-pack/test/observability_ai_assistant_functional/common/connectors.ts b/x-pack/test/observability_ai_assistant_functional/common/connectors.ts new file mode 100644 index 0000000000000..0930c1e4ff7c4 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/common/connectors.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 { Agent as SuperTestAgent } from 'supertest'; +import { LlmProxy } from '../../observability_ai_assistant_api_integration/common/create_llm_proxy'; +export async function createConnector(proxy: LlmProxy, supertest: SuperTestAgent) { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'foo', + config: { + apiProvider: 'OpenAI', + apiUrl: `http://localhost:${proxy.getPort()}`, + defaultModel: 'gpt-4', + }, + secrets: { apiKey: 'myApiKey' }, + connector_type_id: '.gen-ai', + }) + .expect(200); +} + +export async function deleteConnectors(supertest: SuperTestAgent) { + const connectors = await supertest.get('/api/actions/connectors').expect(200); + const promises = connectors.body.map((connector: { id: string }) => { + return supertest + .delete(`/api/actions/connector/${connector.id}`) + .set('kbn-xsrf', 'foo') + .expect(204); + }); + + return Promise.all(promises); +} diff --git a/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts b/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts index d0a45f61e17be..a5d2802dfbcc5 100644 --- a/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts +++ b/x-pack/test/observability_ai_assistant_functional/common/ui/index.ts @@ -33,6 +33,8 @@ const pages = { retryButton: 'observabilityAiAssistantWelcomeMessageSetUpKnowledgeBaseButton', conversationLink: 'observabilityAiAssistantConversationsLink', positiveFeedbackButton: 'observabilityAiAssistantFeedbackButtonsPositiveButton', + connectorsErrorMsg: 'observabilityAiAssistantConnectorsError', + conversationsPage: 'observabilityAiAssistantConversationsPage', }, createConnectorFlyout: { flyout: 'create-connector-flyout', @@ -48,6 +50,18 @@ const pages = { button: 'obsAiAssistantInsightButton', text: 'obsAiAssistantInsightResponse', }, + links: { + solutionMenuLink: 'observability-nav-observabilityAIAssistant-ai_assistant', + globalHeaderButton: 'observabilityAiAssistantAppNavControlButton', + }, + settings: { + settingsPage: 'aiAssistantSettingsPage', + managementLink: 'aiAssistantManagementSelection', + logsIndexPatternInput: + 'management-settings-editField-observability:aiAssistantLogsIndexPattern', + saveButton: 'observabilityAiAssistantManagementBottomBarActionsButton', + aiAssistantCard: 'aiAssistantSelectionPageObservabilityCard', + }, }; export async function ObservabilityAIAssistantUIProvider({ diff --git a/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts index 41ad6f793ee93..7355b508ce5a0 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/contextual_insights/index.spec.ts @@ -14,6 +14,7 @@ import { LlmProxy, } from '../../../observability_ai_assistant_api_integration/common/create_llm_proxy'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { deleteConnectors, createConnector } from '../../common/connectors'; export default function ApiTest({ getService, getPageObjects }: FtrProviderContext) { const ui = getService('observabilityAIAssistantUI'); @@ -60,35 +61,6 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte await apmSynthtraceEsClient.index(documents); } - async function createConnector(proxy: LlmProxy) { - await supertest - .post('/api/actions/connector') - .set('kbn-xsrf', 'foo') - .send({ - name: 'foo', - config: { - apiProvider: 'OpenAI', - apiUrl: `http://localhost:${proxy.getPort()}`, - defaultModel: 'gpt-4', - }, - secrets: { apiKey: 'myApiKey' }, - connector_type_id: '.gen-ai', - }) - .expect(200); - } - - async function deleteConnectors() { - const connectors = await supertest.get('/api/actions/connectors').expect(200); - const promises = connectors.body.map((connector: { id: string }) => { - return supertest - .delete(`/api/actions/connector/${connector.id}`) - .set('kbn-xsrf', 'foo') - .expect(204); - }); - - return Promise.all(promises); - } - async function navigateToError() { await common.navigateToUrl('apm', 'services/opbeans-go/errors/some-expection-key', { shouldUseHashForSubUrl: false, @@ -111,7 +83,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte describe('Contextual insights for APM errors', () => { before(async () => { await Promise.all([ - deleteConnectors(), // cleanup previous connectors + deleteConnectors(supertest), // cleanup previous connectors apmSynthtraceEsClient.clean(), // cleanup previous synthtrace data ]); @@ -123,7 +95,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte after(async () => { await Promise.all([ - deleteConnectors(), // cleanup previous connectors + deleteConnectors(supertest), // cleanup previous connectors apmSynthtraceEsClient.clean(), // cleanup synthtrace data ui.auth.logout(), // logout ]); @@ -141,7 +113,7 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte before(async () => { proxy = await createLlmProxy(log); - await createConnector(proxy); + await createConnector(proxy, supertest); }); after(async () => { diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/assistant_security.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/assistant_security.spec.ts new file mode 100644 index 0000000000000..1108c7251b89d --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/assistant_security.spec.ts @@ -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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { + createLlmProxy, + LlmProxy, +} from '../../../observability_ai_assistant_api_integration/common/create_llm_proxy'; +import { createConnector, deleteConnectors } from '../../common/connectors'; +import { createAndLoginUserWithCustomRole, deleteAndLogoutUser } from './helpers'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const log = getService('log'); + const supertest = getService('supertest'); + const PageObjects = getPageObjects(['common', 'error', 'navigationalSearch', 'security']); + const ui = getService('observabilityAIAssistantUI'); + const testSubjects = getService('testSubjects'); + + describe('ai assistant privileges', () => { + describe('all privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // need some obs app or obs menu wont show where we can click on AI Assistant + infrastructure: ['all'], + observabilityAIAssistant: ['all'], + // requires connectors to chat + actions: ['read'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant link in solution nav', async () => { + // navigate to an observability app so the left side o11y menu shows up + await PageObjects.common.navigateToUrl('infraOps', '', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.existOrFail(ui.pages.links.solutionMenuLink); + }); + it('shows AI Assistant button in global nav', async () => { + await testSubjects.existOrFail(ui.pages.links.globalHeaderButton); + }); + it('shows AI Assistant conversations link in search', async () => { + await PageObjects.navigationalSearch.searchFor('observability ai assistant'); + const results = await PageObjects.navigationalSearch.getDisplayedResults(); + expect(results[0].label).to.eql('Observability AI Assistant / Conversations'); + }); + describe('with no connector setup', () => { + before(async () => { + await deleteConnectors(supertest); + }); + it('loads conversations UI with setup connector message', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.conversations.setupGenAiConnectorsButtonSelector); + }); + }); + describe('with connector setup', () => { + let proxy: LlmProxy; + + before(async () => { + await deleteConnectors(supertest); + proxy = await createLlmProxy(log); + await createConnector(proxy, supertest); + }); + + after(async () => { + proxy.close(); + await deleteConnectors(supertest); + }); + it('loads conversations UI with ability to chat', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + const chatInputElement = await testSubjects.find(ui.pages.conversations.chatInput); + await testSubjects.existOrFail(ui.pages.conversations.chatInput); + const isDisabled = await chatInputElement.getAttribute('disabled'); + expect(isDisabled).to.be(null); + }); + }); + }); + describe('no actions privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // need some obs app or obs menu wont show where we can click on AI Assistant + infrastructure: ['all'], + observabilityAIAssistant: ['all'], + }); + }); + it('loads conversations UI with connector error message', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.conversations.connectorsErrorMsg); + }); + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + }); + describe('no privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // need some obs app or obs menu wont show where we can click on AI Assistant + infrastructure: ['all'], + }); + }); + it('shows no AI Assistant link in solution nav', async () => { + // navigate to an observability app so the left side o11y menu shows up + await PageObjects.common.navigateToUrl('infraOps', '', { + ensureCurrentUrl: true, + shouldLoginIfPrompted: false, + }); + await testSubjects.missingOrFail(ui.pages.links.solutionMenuLink); + }); + it('shows no AI Assistant button in global nav', async () => { + await testSubjects.missingOrFail(ui.pages.links.globalHeaderButton); + }); + it('shows no AI Assistant conversations link in global search', async () => { + await PageObjects.navigationalSearch.searchFor('observability ai assistant'); + const results = await PageObjects.navigationalSearch.getDisplayedResults(); + expect(results.length).to.eql(0); + }); + it('cannot navigate to AI Assistant page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistant', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.conversations.conversationsPage); + }); + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + }); + }); +} diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/helpers.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/helpers.ts new file mode 100644 index 0000000000000..206b5d0df78f7 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/helpers.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InheritedFtrProviderContext } from '../../ftr_provider_context'; + +const AI_ASSISTANT_ROLE_NAME = 'ai_assistant_role'; +const AI_ASSISTANT_USER_NAME = 'ai_assistant_user'; +const AI_ASSISTANT_USER_PASSWORD = `${AI_ASSISTANT_USER_NAME}-password`; + +export const createAndLoginUserWithCustomRole = async ( + getPageObjects: InheritedFtrProviderContext['getPageObjects'], + getService: InheritedFtrProviderContext['getService'], + featurePrivileges: { [key: string]: string[] } +) => { + const security = getService('security'); + const PageObjects = getPageObjects(['security']); + + const kibanaPrivileges = [ + { + feature: featurePrivileges, + spaces: ['*'], + }, + ]; + + await security.role.create(AI_ASSISTANT_ROLE_NAME, { + kibana: kibanaPrivileges, + }); + + await security.user.create(AI_ASSISTANT_USER_NAME, { + password: AI_ASSISTANT_USER_PASSWORD, + roles: [AI_ASSISTANT_ROLE_NAME], + full_name: 'test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login(AI_ASSISTANT_USER_NAME, AI_ASSISTANT_USER_PASSWORD, { + expectSpaceSelector: false, + }); +}; + +export const deleteAndLogoutUser = async ( + getService: InheritedFtrProviderContext['getService'], + getPageObjects: InheritedFtrProviderContext['getPageObjects'] +) => { + const security = getService('security'); + const PageObjects = getPageObjects(['security']); + + await PageObjects.security.forceLogout(); + await Promise.all([ + security.role.delete(AI_ASSISTANT_ROLE_NAME), + security.user.delete(AI_ASSISTANT_USER_NAME), + ]); +}; diff --git a/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts new file mode 100644 index 0000000000000..cea40d3ad10ce --- /dev/null +++ b/x-pack/test/observability_ai_assistant_functional/tests/feature_controls/settings_security.spec.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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'; +import { interceptRequest } from '../../common/intercept_request'; +import { createAndLoginUserWithCustomRole, deleteAndLogoutUser } from './helpers'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const browser = getService('browser'); + const PageObjects = getPageObjects(['common', 'error', 'navigationalSearch', 'security']); + const ui = getService('observabilityAIAssistantUI'); + const testSubjects = getService('testSubjects'); + const driver = getService('__webdriver__'); + const retry = getService('retry'); + const toasts = getService('toasts'); + + describe('ai assistant management privileges', () => { + describe('all privileges', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // we need all these privileges to view and modify Obs AI Assistant settings view + observabilityAIAssistant: ['all'], + // aiAssistantManagementSelection determines link visibility in stack management and navigating to the page + // but not whether you can read/write the settings + aiAssistantManagementSelection: ['all'], + // advancedSettings determines whether user can read/write the settings + advancedSettings: ['all'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.managementLink); + }); + + it('allows access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + }); + it('allows access to obs ai assistant settings view', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.settingsPage); + }); + it('allows updating of an advanced setting', async () => { + const testLogsIndexPattern = 'my-logs-index-pattern'; + const logsIndexPatternInput = await testSubjects.find( + ui.pages.settings.logsIndexPatternInput + ); + await logsIndexPatternInput.clearValue(); + await logsIndexPatternInput.type(testLogsIndexPattern); + const saveButton = await testSubjects.find(ui.pages.settings.saveButton); + await saveButton.click(); + await browser.refresh(); + const logsIndexPatternInputValue = await logsIndexPatternInput.getAttribute('value'); + expect(logsIndexPatternInputValue).to.be(testLogsIndexPattern); + // reset the value + await logsIndexPatternInput.clearValue(); + await logsIndexPatternInput.type('logs-*'); + await saveButton.click(); + }); + it('displays failure toast on failed request', async () => { + const logsIndexPatternInput = await testSubjects.find( + ui.pages.settings.logsIndexPatternInput + ); + // Wait until the input has the default value 'logs-*' to prevent flakiness + await retry.waitFor('input field to have default value', async () => { + const value = await logsIndexPatternInput.getAttribute('value'); + return value === 'logs-*'; + }); + await logsIndexPatternInput.clearValue(); + await logsIndexPatternInput.type('test'); + + await interceptRequest( + driver.driver, + '*kibana\\/settings*', + (responseFactory) => { + return responseFactory.fail(); + }, + async () => { + await testSubjects.click(ui.pages.settings.saveButton); + } + ); + + await retry.waitFor('Error saving settings toast', async () => { + const count = await toasts.getCount(); + return count > 0; + }); + }); + }); + describe('with advancedSettings read privilege', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + observabilityAIAssistant: ['all'], + aiAssistantManagementSelection: ['all'], + advancedSettings: ['read'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.managementLink); + }); + + it('allows access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.aiAssistantCard); + }); + it('allows access to obs ai assistant settings page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.settingsPage); + }); + it('has disabled inputs', async () => { + const logsIndexPatternInput = await testSubjects.find( + ui.pages.settings.logsIndexPatternInput + ); + expect(await logsIndexPatternInput.getAttribute('disabled')).to.be('true'); + }); + }); + describe('observabilityAIAssistant privilege with no aiAssistantManagementSelection privilege', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + // we need at least one feature available to login + observabilityAIAssistant: ['all'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('does not show AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.managementLink); + }); + + it('does not allow access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.aiAssistantCard); + }); + it('allows access to obs ai assistant settings page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.settingsPage); + }); + }); + describe('aiAssistantManagementSelection privilege with no observabilityAIAssistant privilege', () => { + before(async () => { + await createAndLoginUserWithCustomRole(getPageObjects, getService, { + aiAssistantManagementSelection: ['all'], + advancedSettings: ['all'], + }); + }); + + after(async () => { + await deleteAndLogoutUser(getService, getPageObjects); + }); + + it('shows AI Assistant settings link in solution nav', async () => { + await PageObjects.common.navigateToUrl('management', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.managementLink); + }); + + it('allows access to ai assistant settings landing page', async () => { + await PageObjects.common.navigateToUrl('aiAssistantManagementSelection', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.existOrFail(ui.pages.settings.aiAssistantCard); + }); + it('does not allow access to obs ai assistant settings page', async () => { + await PageObjects.common.navigateToUrl('obsAIAssistantManagement', '', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); + await testSubjects.missingOrFail(ui.pages.settings.settingsPage); + }); + }); + }); +} diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts index 066a004df3814..44d2257f8a957 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/health_route.ts @@ -140,7 +140,12 @@ export default function ({ getService }: FtrProviderContext) { }, }, request_capacity: 1000, - max_workers: 10, + capacity: { + config: 10, + as_workers: 10, + as_cost: 20, + }, + claim_strategy: 'default', }); }); diff --git a/x-pack/test/saved_object_api_integration/common/services/index.ts b/x-pack/test/saved_object_api_integration/common/services/index.ts index eac92136695f8..98129d9cc5da3 100644 --- a/x-pack/test/saved_object_api_integration/common/services/index.ts +++ b/x-pack/test/saved_object_api_integration/common/services/index.ts @@ -5,17 +5,10 @@ * 2.0. */ -import { services as kibanaApiIntegrationServices } from '@kbn/test-suites-src/api_integration/services'; -import { services as kibanaFunctionalServices } from '@kbn/test-suites-src/functional/services'; import { services as commonServices } from '../../../common/services'; import { services as apiIntegrationServices } from '../../../api_integration/services'; export const services = { ...commonServices, esSupertestWithoutAuth: apiIntegrationServices.esSupertestWithoutAuth, - supertest: kibanaApiIntegrationServices.supertest, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, - retry: kibanaApiIntegrationServices.retry, - esArchiver: kibanaFunctionalServices.esArchiver, - kibanaServer: kibanaFunctionalServices.kibanaServer, }; diff --git a/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts index 5a4a71c6abb2d..67abb9d5f4e08 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/dashboard_integration.ts @@ -75,7 +75,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('creating', () => { + // FLAKY: https://github.com/elastic/kibana/issues/160583 + describe.skip('creating', () => { beforeEach(async () => { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.gotoDashboardLandingPage(); diff --git a/x-pack/test/scalability/config.ts b/x-pack/test/scalability/config.ts index ebfa91d4ce1e2..3315276a688ca 100644 --- a/x-pack/test/scalability/config.ts +++ b/x-pack/test/scalability/config.ts @@ -11,8 +11,8 @@ import path from 'path'; // @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { REPO_ROOT } from '@kbn/repo-info'; import { createFlagError } from '@kbn/dev-cli-errors'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { v4 as uuidV4 } from 'uuid'; +import { services } from './services'; import { ScalabilityTestRunner } from './runner'; import { FtrProviderContext } from './ftr_provider_context'; import { ScalabilityJourney } from './types'; @@ -49,7 +49,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...baseConfig, - services: commonFunctionalServices, + services, pageObjects: {}, testRunner: (context: FtrProviderContext) => diff --git a/x-pack/test/scalability/ftr_provider_context.ts b/x-pack/test/scalability/ftr_provider_context.ts index 19c510a9ec8e7..82dba4367104b 100644 --- a/x-pack/test/scalability/ftr_provider_context.ts +++ b/x-pack/test/scalability/ftr_provider_context.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; +import { services } from './services'; -export type FtrProviderContext = GenericFtrProviderContext<typeof commonFunctionalServices, {}>; +export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>; export class FtrService extends GenericFtrService<FtrProviderContext> {} diff --git a/x-pack/test/scalability/services.ts b/x-pack/test/scalability/services.ts new file mode 100644 index 0000000000000..5708fb19f7e57 --- /dev/null +++ b/x-pack/test/scalability/services.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 { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; + +const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices; +export const services = { + es, + esArchiver, + kibanaServer, + retry, + supertestWithoutAuth, +}; diff --git a/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml b/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml new file mode 100644 index 0000000000000..1049f4392d9ba --- /dev/null +++ b/x-pack/test/security_api_integration/packages/helpers/saml/idp_metadata_mock_idp.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" + entityID="urn:mock-idp"> + <md:IDPSSODescriptor WantAuthnRequestsSigned="false" + protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + <md:KeyDescriptor use="signing"> + <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> + <ds:X509Data> + <!-- This certificate is extracted from KBN_CERT_PATH in @kbn/dev-utils and should always be in sync with it --> + <ds:X509Certificate>MIIDYjCCAkqgAwIBAgIUZ2p8K7GMXGk6xwCS9S91BUl1JnAwDQYJKoZIhvcNAQEL +BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l +cmF0ZWQgQ0EwIBcNMjMwOTIzMTUyMDE0WhgPMjA3MzA5MTAxNTIwMTRaMBExDzAN +BgNVBAMTBmtpYmFuYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOU +r52dbZ5dY0BoP2p7CEnOpG+qHTNrOAqZO/OJfniPMtpGmwAMl3WZDca6u2XkV2KE +qQyevQ2ADk6G3o8S2RU8mO/+UweuCDF7LHuSdxEGTpucidZErmVhEGUOFosL5UeB +AtIDWxvWwgK+W9Yzt5IEN2HzNCZ6h0dOSk2r9EjVMG5yF4Q6kuqOYxBT7jxoaOtO +OCrgBRummtUga4T13WZ/ZIyyHpXj2+JD4YEmrDyoTa7NLaphv0hnVhHXYoYBI/c6 +2SwwAoBlmtDmlinwSACQ3o/8eLWk0tqkIP14rc3oFh3m7D2c3c2m2HXuyoSDMfGW +beG2IE1Q3idcGmeG3qsCAwEAAaOBjDCBiTAdBgNVHQ4EFgQUMOUM7w5jmIozDvnq +RpM779m5GigwHwYDVR0jBBgwFoAUMEwqwI5b0MYpNxwaHJ9Tw1Lp3p4wPAYDVR0R +BDUwM4IUaG9zdC5kb2NrZXIuaW50ZXJuYWyCCWxvY2FsaG9zdIIEZXMwM4IEZXMw +MoIEZXMwMTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCxqvQYXSKqgpdl +SP4gXgwipAnYsoW9qkgWQODTvSBEzUdOWme0d3j7i2l6Ur/nVSv5YjkqAv1hf/yJ +Hrk9h+j29ZO/aQ/KDh5i/gTEUnPw3Bxbw47dfn23tjMWO7NCU1fr5HNztRsa/gQr +e9s07g25u/gTfTi9Fyu0lcRe3bXOLS/mFVcuC5oxuS65R9OlbIsiORkZ2EfwuNUf +wAAYOGPIjM2VlQCvBitefsd/SzRKHdxSPy6KSjkO6MGEGo87fr7B7Nx1qp1DVrK7 +q9XeP1Cuygjg9WTcnsvWvNw8CssyuFM6X/3tGjpPasXwLvNUoG2AairK2AYTWhvS +foE31cFg</ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + </md:KeyDescriptor> + <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + Location="http://localhost:5620/mock_idp/logout"/> + <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + Location="http://localhost:5620/mock_idp/logout"/> + <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + Location="http://localhost:5620/mock_idp/login"/> + <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + Location="http://localhost:5620/mock_idp/login"/> + </md:IDPSSODescriptor> +</md:EntityDescriptor> diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 8186cdbded722..5f7efaefd6242 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -19,6 +19,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const es = getService('es'); + const esSupertest = getService('esSupertest'); const esDeleteAllIndices = getService('esDeleteAllIndices'); const config = getService('config'); const randomness = getService('randomness'); @@ -72,9 +73,19 @@ export default function ({ getService }: FtrProviderContext) { return cookie; } + async function addESDebugLoggingSettings() { + const addLogging = { + persistent: { + 'logger.org.elasticsearch.xpack.security.authc': 'debug', + }, + }; + await esSupertest.put('/_cluster/settings').send(addLogging).expect(200); + } + describe('Session Lifespan cleanup', () => { beforeEach(async () => { await es.cluster.health({ index: '.kibana_security_session*', wait_for_status: 'green' }); + await addESDebugLoggingSettings(); await esDeleteAllIndices('.kibana_security_session*'); }); diff --git a/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts b/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts index 5e55f216eb800..fc808b4c33cbd 100644 --- a/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_functional/tests/login_selector/basic_functionality.ts @@ -132,11 +132,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { full_name: 'Guest', }); await PageObjects.security.loginSelector.login('anonymous', 'anonymous1'); - await security.user.delete('anonymous_user'); // We need to make sure that both path and hash are respected. const currentURL = parse(await browser.getCurrentUrl()); expect(currentURL.pathname).to.eql('/app/management/security/users'); + + await security.user.delete('anonymous_user'); }); it('can login after `Unauthorized` error after request authentication preserving original URL', async () => { diff --git a/x-pack/test/security_solution_api_integration/config/ess/services_edr_workflows.ts b/x-pack/test/security_solution_api_integration/config/ess/services_edr_workflows.ts index eba99b3d6f6f5..6baf6c0108492 100644 --- a/x-pack/test/security_solution_api_integration/config/ess/services_edr_workflows.ts +++ b/x-pack/test/security_solution_api_integration/config/ess/services_edr_workflows.ts @@ -16,6 +16,7 @@ import { SecuritySolutionEndpointDataStreamHelpers, SecuritySolutionEndpointRegistryHelpers, } from '../../../common/services/security_solution'; +import { SecuritySolutionESSUtils } from '../services/security_solution_ess_utils'; export const services = { ...xPackAPIServices, @@ -26,4 +27,5 @@ export const services = { rolesUsersProvider: RolesUsersProvider, endpointDataStreamHelpers: SecuritySolutionEndpointDataStreamHelpers, endpointRegistryHelpers: SecuritySolutionEndpointRegistryHelpers, + securitySolutionUtils: SecuritySolutionESSUtils, }; diff --git a/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts b/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts index 9123c039c25cc..6c50ff3500050 100644 --- a/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts +++ b/x-pack/test/security_solution_api_integration/config/serverless/services_edr_workflows.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { - KibanaSupertestWithCertProvider, - KibanaSupertestWithCertWithoutAuthProvider, -} from '../../../security_solution_endpoint/services/supertest_with_cert'; +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api'; import { services as essServices } from '../ess/services_edr_workflows'; +import { SecuritySolutionServerlessSuperTest } from '../services/security_solution_serverless_supertest'; +import { SecuritySolutionServerlessUtils } from '../services/security_solution_serverless_utils'; export const svlServices = { ...essServices, - - supertest: KibanaSupertestWithCertProvider, - supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider, + supertest: SecuritySolutionServerlessSuperTest, + securitySolutionUtils: SecuritySolutionServerlessUtils, + svlUserManager: commonFunctionalServices.samlAuth, + svlCommonApi: SvlCommonApiServiceProvider, }; diff --git a/x-pack/test/security_solution_api_integration/config/services/security_solution_ess_utils.ts b/x-pack/test/security_solution_api_integration/config/services/security_solution_ess_utils.ts index 4a69d1db5e253..158ef1e3756b3 100644 --- a/x-pack/test/security_solution_api_integration/config/services/security_solution_ess_utils.ts +++ b/x-pack/test/security_solution_api_integration/config/services/security_solution_ess_utils.ts @@ -5,18 +5,32 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; -import { SecuritySolutionUtils } from './types'; +import { format as formatUrl } from 'url'; +import supertest from 'supertest'; +import { FtrProviderContextWithSpaces } from '../../ftr_provider_context_with_spaces'; +import { SecuritySolutionESSUtilsInterface } from './types'; export function SecuritySolutionESSUtils({ getService, -}: FtrProviderContext): SecuritySolutionUtils { +}: FtrProviderContextWithSpaces): SecuritySolutionESSUtilsInterface { const config = getService('config'); - const supertest = getService('supertest'); + const bsearch = getService('bsearch'); + const supertestWithoutAuth = getService('supertest'); return { getUsername: (_role?: string) => Promise.resolve(config.get('servers.kibana.username') as string), - createSuperTest: (_role?: string) => Promise.resolve(supertest), + createBsearch: (_role?: string) => Promise.resolve(bsearch), + createSuperTest: async (role?: string, password: string = 'changeme') => { + if (!role) { + return supertestWithoutAuth; + } + const kbnUrl = formatUrl({ + ...config.get('servers.kibana'), + auth: false, + }); + + return supertest.agent(kbnUrl).auth(role, password); + }, }; } diff --git a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_bsearch_creator.ts b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_bsearch_creator.ts new file mode 100644 index 0000000000000..3a0f7391c1ff7 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_bsearch_creator.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 async function SecuritySolutionServerlessBsearchCreator({ getService }: FtrProviderContext) { + const { createBsearch } = getService('securitySolutionUtils'); + + return await createBsearch('admin'); +} diff --git a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts index 848d963a68c76..15b2699acbedb 100644 --- a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts +++ b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts @@ -7,18 +7,22 @@ import supertest from 'supertest'; import { format as formatUrl } from 'url'; +import { IEsSearchResponse } from '@kbn/search-types'; import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { SecuritySolutionUtils } from './types'; +import type { SendOptions } from '@kbn/test-suites-src/common/services/bsearch'; +import type { SendOptions as SecureBsearchSendOptions } from '@kbn/test-suites-serverless/shared/services/bsearch_secure'; +import type { FtrProviderContext } from '../../ftr_provider_context'; +import type { SecuritySolutionUtilsInterface } from './types'; export function SecuritySolutionServerlessUtils({ getService, -}: FtrProviderContext): SecuritySolutionUtils { +}: FtrProviderContext): SecuritySolutionUtilsInterface { const svlUserManager = getService('svlUserManager'); const lifecycle = getService('lifecycle'); const svlCommonApi = getService('svlCommonApi'); const config = getService('config'); const log = getService('log'); + const SecureBsearch = getService('secureBsearch'); const rolesCredentials = new Map<string, RoleCredentials>(); const commonRequestHeader = svlCommonApi.getCommonRequestHeader(); @@ -26,9 +30,10 @@ export function SecuritySolutionServerlessUtils({ ...config.get('servers.kibana'), auth: false, }); - const agentWithCommonHeaders = supertest.agent(kbnUrl).set(commonRequestHeader); async function invalidateApiKey(credentials: RoleCredentials) { + // load service to call it outside mocha context + await svlUserManager.init(); await svlUserManager.invalidateM2mApiKeyWithRoleScope(credentials); } @@ -48,8 +53,21 @@ export function SecuritySolutionServerlessUtils({ }); }); + const createSuperTest = async (role = 'admin') => { + cleanCredentials(role); + // load service to call it outside mocha context + await svlUserManager.init(); + const credentials = await svlUserManager.createM2mApiKeyWithRoleScope(role); + rolesCredentials.set(role, credentials); + + const agentWithCommonHeaders = supertest.agent(kbnUrl).set(commonRequestHeader); + return agentWithCommonHeaders.set(credentials.apiKeyHeader); + }; + return { getUsername: async (role = 'admin') => { + // load service to call it outside mocha context + await svlUserManager.init(); const { username } = await svlUserManager.getUserData(role); return username; @@ -57,12 +75,32 @@ export function SecuritySolutionServerlessUtils({ /** * Only one API key for each role can be active at a time. */ - createSuperTest: async (role = 'admin') => { - cleanCredentials(role); - const credentials = await svlUserManager.createM2mApiKeyWithRoleScope(role); - rolesCredentials.set(role, credentials); + createSuperTest, + + createBsearch: async (role = 'admin') => { + const apiKeyHeader = rolesCredentials.get(role)?.apiKeyHeader; + + if (!apiKeyHeader) { + log.error(`API key for role [${role}] is not available, SecureBsearch cannot be created`); + } + + const send = <T extends IEsSearchResponse>(sendOptions: SendOptions): Promise<T> => { + const { supertest: _, ...rest } = sendOptions; + const serverlessSendOptions: SecureBsearchSendOptions = { + ...rest, + // We need super test WITHOUT auth to make the request here, as we are setting the auth header in bsearch `apiKeyHeader` + supertestWithoutAuth: supertest.agent(kbnUrl), + apiKeyHeader: apiKeyHeader ?? { Authorization: '' }, + internalOrigin: 'Kibana', + }; + + log.debug( + `Sending request to SecureBsearch with options: ${JSON.stringify(serverlessSendOptions)}` + ); + return SecureBsearch.send(serverlessSendOptions); + }; - return agentWithCommonHeaders.set(credentials.apiKeyHeader); + return { ...SecureBsearch, send }; }, }; } diff --git a/x-pack/test/security_solution_api_integration/config/services/types.ts b/x-pack/test/security_solution_api_integration/config/services/types.ts index b0a22e8f3a12e..72397582dad00 100644 --- a/x-pack/test/security_solution_api_integration/config/services/types.ts +++ b/x-pack/test/security_solution_api_integration/config/services/types.ts @@ -6,8 +6,23 @@ */ import TestAgent from 'supertest/lib/agent'; +import type { IEsSearchResponse } from '@kbn/search-types'; -export interface SecuritySolutionUtils { +import type { BsearchSecureService } from '@kbn/test-suites-serverless/shared/services/bsearch_secure'; +import type { BsearchService, SendOptions } from '@kbn/test-suites-src/common/services/bsearch'; + +export interface SecuritySolutionServerlessBsearch extends Omit<BsearchSecureService, 'send'> { + send: <T extends IEsSearchResponse>(options: SendOptions) => Promise<T>; +} + +export interface SecuritySolutionUtilsInterface { getUsername: (role?: string) => Promise<string>; createSuperTest: (role?: string) => Promise<TestAgent<any>>; + createBsearch: (role?: string) => Promise<SecuritySolutionServerlessBsearch>; +} + +export interface SecuritySolutionESSUtilsInterface { + getUsername: (role?: string) => Promise<string>; + createBsearch: (role?: string) => Promise<BsearchService>; + createSuperTest: (role?: string, password?: string) => Promise<TestAgent<any>>; } diff --git a/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz b/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz index 00c6963b16937..1bb35f37877b6 100644 Binary files a/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz and b/x-pack/test/security_solution_api_integration/es_archive/serverless/auditbeat/hosts/data.json.gz differ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/rule_exceptions_execution.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/rule_exceptions_execution.ts index 126e76c8df7c1..550bb16d1dfe8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/rule_exceptions_execution.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/exceptions/workflows/basic_license_essentials_tier/rule_exceptions_execution.ts @@ -67,8 +67,7 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.unload(path); }); - // FLAKY: https://github.com/elastic/kibana/issues/181887 - describe.skip('creating rules with exceptions', () => { + describe('creating rules with exceptions', () => { beforeEach(async () => { await createAlertsIndex(supertest, log); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts index 2b66bc6ca49bb..0fd9938dd1999 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/custom_query.ts @@ -69,6 +69,7 @@ import { deleteAllRules, deleteAllAlerts, getRuleForAlertTesting, + getLuceneRuleForTesting, } from '../../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; @@ -117,7 +118,10 @@ export default ({ getService }: FtrProviderContext) => { after(async () => { await esArchiver.unload(auditbeatPath); await esArchiver.unload('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); - await deleteAllAlerts(supertest, log, es, ['.preview.alerts-security.alerts-*']); + await deleteAllAlerts(supertest, log, es, [ + '.preview.alerts-security.alerts-*', + '.alerts-security.alerts-*', + ]); await deleteAllRules(supertest, log); }); @@ -2750,5 +2754,18 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('with a Lucene query rule', () => { + it('should run successfully and generate an alert that matches the lucene query', async () => { + const luceneQueryRule = getLuceneRuleForTesting(); + const { previewId } = await previewRule({ supertest, rule: luceneQueryRule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBeGreaterThan(0); + expect(previewAlerts[0]?._source?.destination).toEqual( + expect.objectContaining({ domain: 'aaa.stage.11111111.hello' }) + ); + expect(previewAlerts[0]?._source?.['event.dataset']).toEqual('network_traffic.tls'); + }); + }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts index ae0682e6479ba..26764650287fc 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql_alert_suppression.ts @@ -64,6 +64,7 @@ export default ({ getService }: FtrProviderContext) => { log, }); + // NOTE: Add to second quality gate after feature is GA describe('@ess @serverless Alert Suppression for EQL rules', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts index 62e8fbfeffe56..30da3e2c762f4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql_suppression.ts @@ -64,6 +64,7 @@ export default ({ getService }: FtrProviderContext) => { const getNonAggRuleQueryWithMetadata = (id: string) => `from ecs_compliant metadata _id, _index, _version ${internalIdPipe(id)}`; + // NOTE: Add to second quality gate after feature is GA describe('@ess @serverless ES|QL rule type, alert suppression', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts index 1ca09bcc8de46..0f6b0f94bdf8e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/indicator_match_alert_suppression.ts @@ -158,7 +158,8 @@ export default ({ getService }: FtrProviderContext) => { }, ]; - describe('@ess @serverless @serverlessQA Indicator match type rules, alert suppression', () => { + // NOTE: Add to second quality gate after feature is GA + describe('@ess @serverless Indicator match type rules, alert suppression', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts index be6464baff393..3db6319842828 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts @@ -86,7 +86,8 @@ export default ({ getService }: FtrProviderContext) => { rule_id: 'ml-rule-id', }; - describe('@ess @serverless @serverlessQA Machine learning type rules', () => { + // Note: This suite of tests can be a candidate for the Kibana QA quality gate once the tests are passing consistenly on the periodic pipeline. + describe('@ess @serverless Machine learning type rules', () => { before(async () => { // Order is critical here: auditbeat data must be loaded before attempting to start the ML job, // as the job looks for certain indices on start diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts index 72818eb423b6a..7f0327c3a644e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning_alert_suppression.ts @@ -88,6 +88,7 @@ export default ({ getService }: FtrProviderContext) => { 'user.name': ['root'], }; + // NOTE: Add to second quality gate after feature is GA describe('@ess @serverless Machine Learning Detection Rule - Alert Suppression', () => { describe('with an active ML Job', () => { before(async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts index ab741069b4ac7..285bb81c6ac93 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts @@ -56,7 +56,8 @@ export default ({ getService }: FtrProviderContext) => { const historicalWindowStart = '2019-10-13T05:00:04.000Z'; - describe('@ess @serverless @serverlessQA New terms type rules, alert suppression', () => { + // NOTE: Add to second quality gate after feature is GA + describe('@ess @serverless New terms type rules, alert suppression', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts index c1681d65c05f5..0ea928d67b491 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts @@ -133,7 +133,7 @@ export default ({ getService }: FtrProviderContext) => { field: 'host.id', value: 1, // This value generates 7 alerts with the current esArchive }, - max_signals: 7, + max_signals: 8, }; const { logs } = await previewRule({ supertest, rule }); expect(logs[0].warnings).not.toContain(getMaxAlertsWarning()); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts index c3cf5a54b145d..c391afd0b9c12 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts @@ -51,7 +51,8 @@ export default ({ getService }: FtrProviderContext) => { const dataPathBuilder = new EsArchivePathBuilder(isServerless); const path = dataPathBuilder.getPath('auditbeat/hosts'); - describe('@ess @serverless @serverlessQA Threshold type rules, alert suppression', () => { + // NOTE: Add to second quality gate after feature is GA + describe('@ess @serverless Threshold type rules, alert suppression', () => { const { indexListOfDocuments, indexGeneratedDocuments } = dataGeneratorFactory({ es, index: 'ecs_compliant', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts index 9afade19216a1..5f86637a1b121 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/keyword_family/keyword.ts @@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const es = getService('es'); - describe('Rule detects against a keyword of event.dataset', () => { + describe('@ess @serverless @serverlessQA Rule detects against a keyword of event.dataset', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/rule_keyword_family/keyword'); }); @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllRules(supertest, log); }); - describe('@ess @serverless @serverlessQA "kql" rule type', () => { + describe('"kql" rule type', () => { it('should detect the "dataset_name_1" from "event.dataset"', async () => { const rule: QueryRuleCreateProps = { ...getRuleForAlertTesting(['keyword']), diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/bootstrap_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/bootstrap_prebuilt_rules.ts new file mode 100644 index 0000000000000..97287ef0cdb93 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/bootstrap_prebuilt_rules.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 { + ENDPOINT_PACKAGE_NAME, + PREBUILT_RULES_PACKAGE_NAME, +} from '@kbn/security-solution-plugin/common/detection_engine/constants'; +import expect from 'expect'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deletePrebuiltRulesFleetPackage } from '../../../../utils'; +import { deleteEndpointFleetPackage } from '../../../../utils/rules/prebuilt_rules/delete_endpoint_fleet_package'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); + + describe('@ess @serverless @skipInServerlessMKI Bootstrap Prebuilt Rules', () => { + beforeEach(async () => { + await deletePrebuiltRulesFleetPackage(supertest); + await deleteEndpointFleetPackage(supertest); + }); + + it('should install fleet packages required for detection engine to function', async () => { + const { body } = await securitySolutionApi.bootstrapPrebuiltRules().expect(200); + + expect(body).toMatchObject({ + packages: expect.arrayContaining([ + expect.objectContaining({ + name: PREBUILT_RULES_PACKAGE_NAME, + status: 'installed', + }), + expect.objectContaining({ + name: ENDPOINT_PACKAGE_NAME, + status: 'installed', + }), + ]), + }); + }); + + it('should skip installing fleet packages if they are already installed', async () => { + // Install the packages + await securitySolutionApi.bootstrapPrebuiltRules().expect(200); + // Try to install the packages again + const { body } = await securitySolutionApi.bootstrapPrebuiltRules().expect(200); + + expect(body).toMatchObject({ + packages: expect.arrayContaining([ + expect.objectContaining({ + name: PREBUILT_RULES_PACKAGE_NAME, + status: 'already_installed', + }), + expect.objectContaining({ + name: ENDPOINT_PACKAGE_NAME, + status: 'already_installed', + }), + ]), + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts index 4d17ef3f63f72..4c8efcfa751e0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext): void => { describe('Rules Management - Prebuilt Rules - Prebuilt Rules Management', function () { + loadTestFile(require.resolve('./bootstrap_prebuilt_rules')); loadTestFile(require.resolve('./get_prebuilt_rules_status')); loadTestFile(require.resolve('./get_prebuilt_timelines_status')); loadTestFile(require.resolve('./install_prebuilt_rules')); @@ -20,6 +21,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.single_line_string_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.scalar_array_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.multi_line_string_fields')); + loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.data_source_fields')); loadTestFile(require.resolve('./upgrade_review_prebuilt_rules.stats')); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts new file mode 100644 index 0000000000000..f3b009a8ade47 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.data_source_fields.ts @@ -0,0 +1,972 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + AllFieldsDiff, + DataSourceType, + RuleUpdateProps, + ThreeWayDiffConflict, + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getPrebuiltRuleMock } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + deleteAllTimelines, + deleteAllPrebuiltRuleAssets, + createRuleAssetSavedObject, + installPrebuiltRules, + createPrebuiltRuleAssetSavedObjects, + reviewPrebuiltRulesToUpgrade, + createHistoricalPrebuiltRuleAssetSavedObjects, + updateRule, + patchRule, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + describe(`data_source fields`, () => { + const getIndexRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 1, + index: ['one', 'two', 'three'], + }), + ]; + + const getDataViewIdRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 1, + data_view_id: 'A', + }), + ]; + + describe("when rule field doesn't have an update and has no custom value - scenario AAA", () => { + describe('when all versions are index patterns', () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + index: ['one', 'three', 'two'], + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligable for update but data_source field is NOT returned + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when all versions are data view id', () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + data_view_id: 'A', + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligable for update but data_source field is NOT returned + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + }); + + describe("when rule field doesn't have an update but has a custom value - scenario ABA", () => { + describe('when current version is index pattern type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + data_view_id: 'A', + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that data_source diff field is returned but field does not have an update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when current version is data view id type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + data_view_id: 'B', + } as RuleUpdateProps); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + index: ['one', 'two', 'three'], + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that data_source diff field is returned but field does not have an update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + target_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when current version is undefined', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, do NOT update the related data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + index: ['one', 'two', 'three'], + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that data_source diff field is returned but field does not have an update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: undefined, + target_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + merged_version: undefined, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + }); + + describe('when rule field has an update but does not have a custom value - scenario AAB', () => { + describe('when target version is index pattern type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'four'], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + target_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when target version is data view id type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'B', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + target_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.NONE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + }); + + describe('when rule field has an update and a custom value that are the same - scenario ABB', () => { + describe('when current and target version are index pattern type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'four'], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains data_source field + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when current and target version are data view id type', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + data_view_id: 'B', + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'B', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains data_source field + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + target_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NONE, + has_update: false, + has_base_version: true, + }); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(0); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + }); + + describe('when rule field has an update and a custom value that are different - scenario ABC', () => { + describe('when just current and target versions are index patterns', () => { + it('should show a solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'one', 'two', 'three'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'five'], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'three', 'five'], + type: DataSourceType.index_patterns, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when all versions are index patterns', () => { + it('should show a solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getIndexRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a multi line string field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + }); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'five'], + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + index_patterns: ['one', 'two', 'three'], + type: DataSourceType.index_patterns, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'four', 'five'], + type: DataSourceType.index_patterns, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Merged, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when all versions are data view id types', () => { + it('should show a non-solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: undefined, + data_view_id: 'B', + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'C', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + target_version: { + data_view_id: 'C', + type: DataSourceType.data_view, + }, + merged_version: { + data_view_id: 'B', + type: DataSourceType.data_view, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + }); + }); + + describe('when current and target versions are different data types', () => { + it('should show a non-solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'C', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + data_view_id: 'C', + type: DataSourceType.data_view, + }, + merged_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + }); + }); + + describe('when current version is undefined', () => { + it('should show a non-solvable conflict in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + getDataViewIdRuleAssetSavedObjects() + ); + await installPrebuiltRules(es, supertest); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + data_view_id: 'C', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + base_version: { + data_view_id: 'A', + type: DataSourceType.data_view, + }, + current_version: undefined, + target_version: { + data_view_id: 'C', + type: DataSourceType.data_view, + }, + merged_version: undefined, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + conflict: ThreeWayDiffConflict.NON_SOLVABLE, + has_update: true, + has_base_version: true, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(1); + }); + }); + }); + + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same - scenario -AA', () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createPrebuiltRuleAssetSavedObjects(es, getIndexRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Increment the version of the installed rule, but keep data_source field unchanged + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'three'], // unchanged + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // but does NOT contain data_source field (tags is not present, since scenario -AA is not included in response) + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toBeUndefined(); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered conflict + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + + describe('when rule field has an update and a custom value that are different - scenario -AB', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createPrebuiltRuleAssetSavedObjects(es, getIndexRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Customize a data_source field on the installed rule + await updateRule(supertest, { + ...getPrebuiltRuleMock(), + rule_id: 'rule-1', + index: ['one', 'two', 'four'], + data_view_id: undefined, + } as RuleUpdateProps); + + // Increment the version of the installed rule, update a data_source field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + index: ['one', 'two', 'five'], + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and data_source field update does not have a conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + const fieldDiffObject = reviewResponse.rules[0].diff.fields as AllFieldsDiff; + expect(fieldDiffObject.data_source).toEqual({ + current_version: { + index_patterns: ['one', 'two', 'four'], + type: DataSourceType.index_patterns, + }, + target_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + merged_version: { + index_patterns: ['one', 'two', 'five'], + type: DataSourceType.index_patterns, + }, + diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + conflict: ThreeWayDiffConflict.SOLVABLE, + has_update: true, + has_base_version: false, + }); + + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // version + tags + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(0); + + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); + expect(reviewResponse.stats.num_rules_with_non_solvable_conflicts).toBe(0); + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts index d0f633707fa62..517ffee8ec1ff 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules.ts @@ -1088,6 +1088,15 @@ export default ({ getService }: FtrProviderContext) => { notifications_enabled: 0, notifications_disabled: 0, legacy_investigation_fields: 0, + alert_suppression: { + enabled: 0, + disabled: 0, + suppressed_per_time_period: 0, + suppressed_per_rule_execution: 0, + suppressed_fields_count: { one: 0, two: 0, three: 0 }, + suppresses_missing_fields: 0, + does_not_suppress_missing_fields: 0, + }, }); }); }); @@ -1119,6 +1128,10 @@ export default ({ getService }: FtrProviderContext) => { has_legacy_notification: false, has_notification: false, has_legacy_investigation_field: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + alert_suppression_fields_count: 0, }); }); }); @@ -1157,6 +1170,10 @@ export default ({ getService }: FtrProviderContext) => { has_notification: true, has_legacy_notification: false, has_legacy_investigation_field: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + alert_suppression_fields_count: 0, }); expect( stats.detection_rules.detection_rule_usage.elastic_total.notifications_disabled @@ -1210,6 +1227,10 @@ export default ({ getService }: FtrProviderContext) => { has_notification: true, has_legacy_notification: false, has_legacy_investigation_field: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + alert_suppression_fields_count: 0, }); expect( stats.detection_rules.detection_rule_usage.elastic_total.notifications_disabled @@ -1263,6 +1284,10 @@ export default ({ getService }: FtrProviderContext) => { has_notification: false, has_legacy_notification: true, has_legacy_investigation_field: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + alert_suppression_fields_count: 0, }); expect( stats.detection_rules.detection_rule_usage.elastic_total.notifications_disabled @@ -1316,6 +1341,10 @@ export default ({ getService }: FtrProviderContext) => { has_notification: false, has_legacy_notification: true, has_legacy_investigation_field: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + alert_suppression_fields_count: 0, }); expect( stats.detection_rules.detection_rule_usage.elastic_total.notifications_disabled diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts index e34ee9c2005ef..ead375adb2049 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/telemetry/trial_license_complete_tier/usage_collector/detection_rules_legacy_action.ts @@ -438,6 +438,10 @@ export default ({ getService }: FtrProviderContext) => { has_notification: false, has_legacy_notification: true, has_legacy_investigation_field: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + alert_suppression_fields_count: 0, }); expect( stats.detection_rules.detection_rule_usage.elastic_total.notifications_disabled @@ -491,6 +495,10 @@ export default ({ getService }: FtrProviderContext) => { has_notification: false, has_legacy_notification: true, has_legacy_investigation_field: false, + has_alert_suppression_per_rule_execution: false, + has_alert_suppression_per_time_period: false, + has_alert_suppression_missing_fields_strategy_do_not_suppress: false, + alert_suppression_fields_count: 0, }); expect( stats.detection_rules.detection_rule_usage.elastic_total.notifications_disabled diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts index f826629741179..8ec09bdb5b193 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts @@ -6,7 +6,7 @@ */ import { AlertTagIds } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { ManageAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { SetAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine'; export const setAlertTags = ({ tagsToAdd, @@ -16,7 +16,7 @@ export const setAlertTags = ({ tagsToAdd: string[]; tagsToRemove: string[]; ids: AlertTagIds; -}): ManageAlertTagsRequestBodyInput => ({ +}): SetAlertTagsRequestBodyInput => ({ tags: { tags_to_add: tagsToAdd, tags_to_remove: tagsToRemove, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.ts new file mode 100644 index 0000000000000..e53e24f98de3b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_endpoint_fleet_package.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 { epmRouteService } from '@kbn/fleet-plugin/common'; +import { ENDPOINT_PACKAGE_NAME } from '@kbn/security-solution-plugin/common/detection_engine/constants'; +import type SuperTest from 'supertest'; + +/** + * Delete the endpoint package using fleet API. + * + * @param supertest Supertest instance + */ +export async function deleteEndpointFleetPackage(supertest: SuperTest.Agent) { + const resp = await supertest + .get(epmRouteService.getInfoPath(ENDPOINT_PACKAGE_NAME)) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(); + + if (resp.status === 200 && resp.body.response.status === 'installed') { + await supertest + .delete(epmRouteService.getRemovePath(ENDPOINT_PACKAGE_NAME, resp.body.response.version)) + .set('kbn-xsrf', 'true') + .send({ force: true }); + } +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts index a3468ccb32b5a..930c9d39757f4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/delete_prebuilt_rules_fleet_package.ts @@ -6,6 +6,7 @@ */ import { epmRouteService } from '@kbn/fleet-plugin/common'; +import { PREBUILT_RULES_PACKAGE_NAME } from '@kbn/security-solution-plugin/common/detection_engine/constants'; import type SuperTest from 'supertest'; /** @@ -15,7 +16,7 @@ import type SuperTest from 'supertest'; */ export async function deletePrebuiltRulesFleetPackage(supertest: SuperTest.Agent) { const resp = await supertest - .get(epmRouteService.getInfoPath('security_detection_engine')) + .get(epmRouteService.getInfoPath(PREBUILT_RULES_PACKAGE_NAME)) .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31') .send(); @@ -23,7 +24,7 @@ export async function deletePrebuiltRulesFleetPackage(supertest: SuperTest.Agent if (resp.status === 200 && resp.body.response.status === 'installed') { await supertest .delete( - epmRouteService.getRemovePath('security_detection_engine', resp.body.response.version) + epmRouteService.getRemovePath(PREBUILT_RULES_PACKAGE_NAME, resp.body.response.version) ) .set('kbn-xsrf', 'true') .send({ force: true }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts index 420f8a7aca1ce..7ef37d867b1df 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/get_prebuilt_rules_and_timelines_status.ts @@ -6,7 +6,7 @@ */ import { - GetPrebuiltRulesAndTimelinesStatusResponse, + ReadPrebuiltRulesAndTimelinesStatusResponse, PREBUILT_RULES_STATUS_URL, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; import type SuperTest from 'supertest'; @@ -22,7 +22,7 @@ import { refreshSavedObjectIndices } from '../../refresh_index'; export const getPrebuiltRulesAndTimelinesStatus = async ( es: Client, supertest: SuperTest.Agent -): Promise<GetPrebuiltRulesAndTimelinesStatusResponse> => { +): Promise<ReadPrebuiltRulesAndTimelinesStatusResponse> => { await refreshSavedObjectIndices(es); const response = await supertest diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/blocklists.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/blocklists.ts index e483166e7f47c..1641f9821c754 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/blocklists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/blocklists.ts @@ -13,23 +13,29 @@ import { GLOBAL_ARTIFACT_TAG, } from '@kbn/security-solution-plugin/common/endpoint/service/artifacts'; import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; import { PolicyTestResourceInfo } from '../../../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../../../security_solution_endpoint/services/endpoint_artifacts'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); + const utils = getService('securitySolutionUtils'); // @skipInServerlessMKI due to authentication issues - we should migrate from Basic to Bearer token when available // @skipInServerlessMKI - if you are removing this annotation, make sure to add the test suite to the MKI pipeline in .buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml describe('@ess @serverless @skipInServerlessMKI Endpoint artifacts (via lists plugin): Blocklists', function () { let fleetEndpointPolicy: PolicyTestResourceInfo; + let t1AnalystSupertest: TestAgent; + let endpointPolicyManagerSupertest: TestAgent; + before(async () => { + t1AnalystSupertest = await utils.createSuperTest(ROLE.t1_analyst); + endpointPolicyManagerSupertest = await utils.createSuperTest(ROLE.endpoint_policy_manager); + // Create an endpoint policy in fleet we can work with fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); }); @@ -60,7 +66,7 @@ export default function ({ getService }: FtrProviderContext) { let blocklistData: ArtifactTestData; type BlocklistApiCallsInterface<BodyReturnType = unknown> = Array<{ - method: keyof Pick<typeof supertest, 'post' | 'put' | 'get' | 'delete' | 'patch'>; + method: keyof Pick<TestAgent, 'post' | 'put' | 'get' | 'delete' | 'patch'>; info?: string; path: string; // The body just needs to have the properties we care about in the tests. This should cover most @@ -156,8 +162,7 @@ export default function ({ getService }: FtrProviderContext) { const body = blocklistApiCall.getBody(); body.entries[0].field = 'some.invalid.field'; - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -177,8 +182,7 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -198,8 +202,7 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -229,8 +232,7 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -263,8 +265,7 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -277,8 +278,7 @@ export default function ({ getService }: FtrProviderContext) { body.os_types = ['linux', 'windows']; - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -292,7 +292,7 @@ export default function ({ getService }: FtrProviderContext) { body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`]; // Using superuser here as we need custom license for this action - await supertest[blocklistApiCall.method](blocklistApiCall.path) + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -302,8 +302,7 @@ export default function ({ getService }: FtrProviderContext) { } for (const blocklistApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { it(`should not error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody() as object) .expect(200); @@ -313,10 +312,13 @@ export default function ({ getService }: FtrProviderContext) { // no such role in serverless describe('@skipInServerless and user has authorization to read blocklist', function () { + let artifactReadSupertest: TestAgent; + before(async () => { + artifactReadSupertest = await utils.createSuperTest(ROLE.artifact_read_privileges); + }); for (const blocklistApiCall of [...blocklistApiCalls, ...needsWritePrivilege]) { it(`should error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.artifact_read_privileges, 'changeme') + await artifactReadSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody() as object) .expect(403); @@ -325,8 +327,7 @@ export default function ({ getService }: FtrProviderContext) { for (const blocklistApiCall of needsReadPrivilege) { it(`should not error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.artifact_read_privileges, 'changeme') + await artifactReadSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody() as object) .expect(200); @@ -341,8 +342,7 @@ export default function ({ getService }: FtrProviderContext) { ...needsReadPrivilege, ]) { it(`should error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { - await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLE.t1_analyst, 'changeme') + await t1AnalystSupertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody() as object) .expect(403); diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/event_filters.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/event_filters.ts index d49af143a7e35..f5c416038b956 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/event_filters.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/event_filters.ts @@ -14,23 +14,29 @@ import { getImportExceptionsListSchemaMock, toNdJsonString, } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { PolicyTestResourceInfo } from '../../../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../../../security_solution_endpoint/services/endpoint_artifacts'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); + const utils = getService('securitySolutionUtils'); // @skipInServerlessMKI due to authentication issues - we should migrate from Basic to Bearer token when available // @skipInServerlessMKI - if you are removing this annotation, make sure to add the test suite to the MKI pipeline in .buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml describe('@ess @serverless @skipInServerlessMKI Endpoint artifacts (via lists plugin): Event Filters', function () { let fleetEndpointPolicy: PolicyTestResourceInfo; + let t1AnalystSupertest: TestAgent; + let endpointPolicyManagerSupertest: TestAgent; + before(async () => { + t1AnalystSupertest = await utils.createSuperTest(ROLE.t1_analyst); + endpointPolicyManagerSupertest = await utils.createSuperTest(ROLE.endpoint_policy_manager); + // Create an endpoint policy in fleet we can work with fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); }); @@ -68,7 +74,7 @@ export default function ({ getService }: FtrProviderContext) { >; type EventFilterApiCallsInterface<BodyGetter = UnknownBodyGetter> = Array<{ - method: keyof Pick<typeof supertest, 'post' | 'put' | 'get' | 'delete' | 'patch'>; + method: keyof Pick<TestAgent, 'post' | 'put' | 'get' | 'delete' | 'patch'>; info?: string; path: string; // The body just needs to have the properties we care about in the tests. This should cover most @@ -161,7 +167,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 400 for import of endpoint exceptions', async () => { - await supertest + await endpointPolicyManagerSupertest .post(`${EXCEPTION_LIST_URL}/_import?overwrite=false`) .set('kbn-xsrf', 'true') .attach( @@ -183,8 +189,7 @@ export default function ({ getService }: FtrProviderContext) { it(`should error on [${eventFilterApiCall.method}] if more than one OS is set`, async () => { const body = eventFilterApiCall.getBody({ os_types: ['linux', 'windows'] }); - await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[eventFilterApiCall.method](eventFilterApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -198,8 +203,7 @@ export default function ({ getService }: FtrProviderContext) { }); // Using superuser there as we need custom license for this action - await supertest[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[eventFilterApiCall.method](eventFilterApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -211,20 +215,19 @@ export default function ({ getService }: FtrProviderContext) { const body = eventFilterApiCall.getBody({}); // Using superuser here as we need custom license for this action - await supertest[eventFilterApiCall.method](eventFilterApiCall.path) + await endpointPolicyManagerSupertest[eventFilterApiCall.method](eventFilterApiCall.path) .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(200); const deleteUrl = `${EXCEPTION_LIST_ITEM_URL}?item_id=${body.item_id}&namespace_type=${body.namespace_type}`; - await supertest.delete(deleteUrl).set('kbn-xsrf', 'true'); + await endpointPolicyManagerSupertest.delete(deleteUrl).set('kbn-xsrf', 'true'); }); } for (const eventFilterApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { it(`should not error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { - await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[eventFilterApiCall.method](eventFilterApiCall.path) .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody() as object) .expect(200); @@ -233,10 +236,13 @@ export default function ({ getService }: FtrProviderContext) { }); describe('@skipInServerless and user has authorization to read event filters', function () { + let hunterSupertest: TestAgent; + before(async () => { + hunterSupertest = await utils.createSuperTest(ROLE.hunter); + }); for (const eventFilterApiCall of [...eventFilterCalls, ...needsWritePrivilege]) { it(`should error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { - await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.hunter, 'changeme') + await hunterSupertest[eventFilterApiCall.method](eventFilterApiCall.path) .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody() as object) .expect(403); @@ -245,8 +251,7 @@ export default function ({ getService }: FtrProviderContext) { for (const eventFilterApiCall of needsReadPrivilege) { it(`should not error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { - await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.hunter, 'changeme') + await hunterSupertest[eventFilterApiCall.method](eventFilterApiCall.path) .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody() as object) .expect(200); @@ -261,8 +266,7 @@ export default function ({ getService }: FtrProviderContext) { ...needsReadPrivilege, ]) { it(`should error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { - await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) - .auth(ROLE.t1_analyst, 'changeme') + await t1AnalystSupertest[eventFilterApiCall.method](eventFilterApiCall.path) .set('kbn-xsrf', 'true') .send(eventFilterApiCall.getBody() as object) .expect(403); diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/host_isolation_exceptions.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/host_isolation_exceptions.ts index df6b9450e4686..f9e61f1aa9d94 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/host_isolation_exceptions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/host_isolation_exceptions.ts @@ -17,16 +17,16 @@ import { toNdJsonString, } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator'; +import TestAgent from 'supertest/lib/agent'; import { PolicyTestResourceInfo } from '../../../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../../../security_solution_endpoint/services/endpoint_artifacts'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); + const utils = getService('securitySolutionUtils'); // @skipInServerlessMKI due to authentication issues - we should migrate from Basic to Bearer token when available // @skipInServerlessMKI - if you are removing this annotation, make sure to add the test suite to the MKI pipeline in .buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml @@ -59,7 +59,7 @@ export default function ({ getService }: FtrProviderContext) { 'item_id' | 'namespace_type' | 'os_types' | 'tags' | 'entries' >; type HostIsolationExceptionApiCallsInterface<BodyGetter = UnknownBodyGetter> = Array<{ - method: keyof Pick<typeof supertest, 'post' | 'put' | 'get' | 'delete' | 'patch'>; + method: keyof Pick<TestAgent, 'post' | 'put' | 'get' | 'delete' | 'patch'>; info?: string; path: string; // The body just needs to have the properties we care about in the tests. This should cover most @@ -138,8 +138,13 @@ export default function ({ getService }: FtrProviderContext) { }, ]; + let t1AnalystSupertest: TestAgent; + let endpointPolicyManagerSupertest: TestAgent; + before(async () => { - // Create an endpoint policy in fleet we can work with + t1AnalystSupertest = await utils.createSuperTest(ROLE.t1_analyst); + endpointPolicyManagerSupertest = await utils.createSuperTest(ROLE.endpoint_policy_manager); + fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); }); @@ -164,7 +169,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 400 for import of endpoint exceptions', async () => { - await supertest + await endpointPolicyManagerSupertest .post(`${EXCEPTION_LIST_URL}/_import?overwrite=false`) .set('kbn-xsrf', 'true') .attach( @@ -190,10 +195,9 @@ export default function ({ getService }: FtrProviderContext) { body.entries[0].field = 'some.invalid.field'; - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await endpointPolicyManagerSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -206,10 +210,9 @@ export default function ({ getService }: FtrProviderContext) { body.entries.push({ ...body.entries[0] }); - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await endpointPolicyManagerSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -229,10 +232,9 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await endpointPolicyManagerSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -245,10 +247,9 @@ export default function ({ getService }: FtrProviderContext) { body.os_types = ['linux', 'windows']; - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await endpointPolicyManagerSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -262,7 +263,9 @@ export default function ({ getService }: FtrProviderContext) { body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`]; // Using superuser here as we need custom license for this action - await supertest[hostIsolationExceptionApiCall.method](hostIsolationExceptionApiCall.path) + await endpointPolicyManagerSupertest[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -272,10 +275,9 @@ export default function ({ getService }: FtrProviderContext) { } for (const hostIsolationExceptionApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { it(`should not error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await endpointPolicyManagerSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.endpoint_policy_manager, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody() as object) .expect(200); @@ -285,15 +287,19 @@ export default function ({ getService }: FtrProviderContext) { // no such role in serverless describe('@skipInServerless and user has authorization to read host isolation exceptions', function () { + let hunterSupertest: TestAgent; + before(async () => { + hunterSupertest = await utils.createSuperTest(ROLE.hunter); + }); + for (const hostIsolationExceptionApiCall of [ ...hostIsolationExceptionCalls, ...needsWritePrivilege, ]) { it(`should error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await hunterSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody() as object) .expect(403); @@ -302,10 +308,9 @@ export default function ({ getService }: FtrProviderContext) { for (const hostIsolationExceptionApiCall of needsReadPrivilege) { it(`should not error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await hunterSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.hunter, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody() as object) .expect(200); @@ -320,10 +325,9 @@ export default function ({ getService }: FtrProviderContext) { ...needsReadPrivilege, ]) { it(`should error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { - await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + await t1AnalystSupertest[hostIsolationExceptionApiCall.method]( hostIsolationExceptionApiCall.path ) - .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .send(hostIsolationExceptionApiCall.getBody() as object) .expect(403); diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/index.ts index 7320e49902011..d243d3d87555e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/trusted_apps.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/trusted_apps.ts index e63b37f78bce7..cb310acf47aa4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/trusted_apps.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/trusted_apps.ts @@ -13,23 +13,28 @@ import { GLOBAL_ARTIFACT_TAG, } from '@kbn/security-solution-plugin/common/endpoint/service/artifacts'; import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/common/endpoint/data_generators/exceptions_list_item_generator'; +import TestAgent from 'supertest/lib/agent'; import { PolicyTestResourceInfo } from '../../../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../../../security_solution_endpoint/services/endpoint_artifacts'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); + const utils = getService('securitySolutionUtils'); // @skipInServerlessMKI due to authentication issues - we should migrate from Basic to Bearer token when available // @skipInServerlessMKI - if you are removing this annotation, make sure to add the test suite to the MKI pipeline in .buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml describe('@ess @serverless @skipInServerlessMKI Endpoint artifacts (via lists plugin): Trusted Applications', function () { let fleetEndpointPolicy: PolicyTestResourceInfo; + let t1AnalystSupertest: TestAgent; + let endpointPolicyManagerSupertest: TestAgent; before(async () => { + t1AnalystSupertest = await utils.createSuperTest(ROLE.t1_analyst); + endpointPolicyManagerSupertest = await utils.createSuperTest(ROLE.endpoint_policy_manager); + fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); }); @@ -59,7 +64,7 @@ export default function ({ getService }: FtrProviderContext) { let trustedAppData: ArtifactTestData; type TrustedAppApiCallsInterface<BodyReturnType = unknown> = Array<{ - method: keyof Pick<typeof supertest, 'post' | 'put' | 'get' | 'delete' | 'patch'>; + method: keyof Pick<TestAgent, 'post' | 'put' | 'get' | 'delete' | 'patch'>; info?: string; path: string; // The body just needs to have the properties we care about in the tests. This should cover most @@ -156,8 +161,7 @@ export default function ({ getService }: FtrProviderContext) { body.entries[0].field = 'some.invalid.field'; - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -170,8 +174,7 @@ export default function ({ getService }: FtrProviderContext) { body.entries.push({ ...body.entries[0] }); - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -191,8 +194,7 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -225,8 +227,7 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -239,8 +240,7 @@ export default function ({ getService }: FtrProviderContext) { body.os_types = ['linux', 'windows']; - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -254,8 +254,7 @@ export default function ({ getService }: FtrProviderContext) { body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`]; // Using superuser here as we need custom license for this action - await supertest[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -265,8 +264,7 @@ export default function ({ getService }: FtrProviderContext) { } for (const trustedAppApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.endpoint_policy_manager, 'changeme') + await endpointPolicyManagerSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody() as object) .expect(200); @@ -276,10 +274,14 @@ export default function ({ getService }: FtrProviderContext) { // no such role in serverless describe('@skipInServerless and user has authorization to read trusted apps', function () { + let hunterSupertest: TestAgent; + before(async () => { + hunterSupertest = await utils.createSuperTest(ROLE.hunter); + }); + for (const trustedAppApiCall of [...trustedAppApiCalls, ...needsWritePrivilege]) { it(`should error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.hunter, 'changeme') + await hunterSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody() as object) .expect(403); @@ -288,8 +290,7 @@ export default function ({ getService }: FtrProviderContext) { for (const trustedAppApiCall of needsReadPrivilege) { it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.hunter, 'changeme') + await hunterSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody() as object) .expect(200); @@ -304,8 +305,7 @@ export default function ({ getService }: FtrProviderContext) { ...needsReadPrivilege, ]) { it(`should error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { - await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLE.t1_analyst, 'changeme') + await t1AnalystSupertest[trustedAppApiCall.method](trustedAppApiCall.path) .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody() as object) .expect(403); diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/endpoint_authz.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/endpoint_authz.ts index 3057e4ce5f9f7..5805dbef73e2e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/endpoint_authz.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/endpoint_authz.ts @@ -24,16 +24,16 @@ import { UNISOLATE_HOST_ROUTE_V2, } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; export default function ({ getService }: FtrProviderContext) { - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const supertest = getService('supertest'); const endpointTestResources = getService('endpointTestResources'); + const utils = getService('securitySolutionUtils'); interface ApiCallsInterface { - method: keyof Pick<typeof supertest, 'post' | 'get'>; + method: keyof Pick<TestAgent, 'post' | 'get'>; path: string; version?: string; body: Record<string, unknown> | (() => Record<string, unknown>) | undefined; @@ -160,7 +160,18 @@ export default function ({ getService }: FtrProviderContext) { return typeof apiCall.body === 'function' ? apiCall.body() : apiCall.body; } + let adminSupertest: TestAgent; + let t1AnalystSupertest: TestAgent; + let endpointOperationsAnalystSupertest: TestAgent; + let platformEnginnerSupertest: TestAgent; before(async () => { + adminSupertest = await utils.createSuperTest(); + t1AnalystSupertest = await utils.createSuperTest(ROLE.t1_analyst); + endpointOperationsAnalystSupertest = await utils.createSuperTest( + ROLE.endpoint_operations_analyst + ); + platformEnginnerSupertest = await utils.createSuperTest(ROLE.platform_engineer); + indexedData = await endpointTestResources.loadEndpointData(); agentId = indexedData.hosts[0].agent.id; actionId = indexedData.actions[0].action_id; @@ -180,8 +191,7 @@ export default function ({ getService }: FtrProviderContext) { it(`should return 403 when [${apiListItem.method.toUpperCase()} ${ apiListItem.path }]`, async () => { - await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.t1_analyst, 'changeme') + await t1AnalystSupertest[apiListItem.method](replacePathIds(apiListItem.path)) .set('kbn-xsrf', 'xxx') .set(apiListItem.version ? 'Elastic-Api-Version' : 'foo', '2023-10-31') .send(getBodyPayload(apiListItem)) @@ -198,8 +208,7 @@ export default function ({ getService }: FtrProviderContext) { it(`should return 200 when [${apiListItem.method.toUpperCase()} ${ apiListItem.path }]`, async () => { - await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.t1_analyst, 'changeme') + await t1AnalystSupertest[apiListItem.method](replacePathIds(apiListItem.path)) .set('kbn-xsrf', 'xxx') .send(getBodyPayload(apiListItem)) .expect(200); @@ -216,8 +225,7 @@ export default function ({ getService }: FtrProviderContext) { it(`should return 403 when [${apiListItem.method.toUpperCase()} ${ apiListItem.path }]`, async () => { - await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.platform_engineer, 'changeme') + await platformEnginnerSupertest[apiListItem.method](replacePathIds(apiListItem.path)) .set('kbn-xsrf', 'xxx') .send(getBodyPayload(apiListItem)) .expect(403, { @@ -236,8 +244,7 @@ export default function ({ getService }: FtrProviderContext) { it(`should return 200 when [${apiListItem.method.toUpperCase()} ${ apiListItem.path }]`, async () => { - await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.platform_engineer, 'changeme') + await platformEnginnerSupertest[apiListItem.method](replacePathIds(apiListItem.path)) .set('kbn-xsrf', 'xxx') .send(getBodyPayload(apiListItem)) .expect(200); @@ -250,8 +257,9 @@ export default function ({ getService }: FtrProviderContext) { it(`should return 403 when [${apiListItem.method.toUpperCase()} ${ apiListItem.path }]`, async () => { - await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.endpoint_operations_analyst, 'changeme') + await endpointOperationsAnalystSupertest[apiListItem.method]( + replacePathIds(apiListItem.path) + ) .set('kbn-xsrf', 'xxx') .send(getBodyPayload(apiListItem)) .expect(403, { @@ -272,8 +280,9 @@ export default function ({ getService }: FtrProviderContext) { it(`should return 200 when [${apiListItem.method.toUpperCase()} ${ apiListItem.path }]`, async () => { - await supertestWithoutAuth[apiListItem.method](replacePathIds(apiListItem.path)) - .auth(ROLE.endpoint_operations_analyst, 'changeme') + await endpointOperationsAnalystSupertest[apiListItem.method]( + replacePathIds(apiListItem.path) + ) .set('kbn-xsrf', 'xxx') .send(getBodyPayload(apiListItem)) .expect(200); @@ -289,12 +298,22 @@ export default function ({ getService }: FtrProviderContext) { ...canWriteProcessOperationsApiList, ...canWriteExecuteOperationsApiList, ...canWriteFileOperationsApiList, - ...superuserApiList, ]) { it(`should return 200 when [${apiListItem.method.toUpperCase()} ${ apiListItem.path }]`, async () => { - await supertest[apiListItem.method](replacePathIds(apiListItem.path)) + await adminSupertest[apiListItem.method](replacePathIds(apiListItem.path)) + .set('kbn-xsrf', 'xxx') + .send(getBodyPayload(apiListItem)) + .expect(200); + }); + } + for (const apiListItem of [...superuserApiList]) { + // Admin user has no access to these APIs + it(`@skipInServerless should return 200 when [${apiListItem.method.toUpperCase()} ${ + apiListItem.path + }]`, async () => { + await adminSupertest[apiListItem.method](replacePathIds(apiListItem.path)) .set('kbn-xsrf', 'xxx') .send(getBodyPayload(apiListItem)) .expect(200); diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/index.ts index de537ba971ec6..388785ad08b70 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/authentication/trial_license_complete_tier/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/index.ts index 71ee3b8ebc337..e856bf92c8a95 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/metadata.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/metadata.ts index bd494997b8681..8dac3bfe66784 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/metadata.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/metadata/trial_license_complete_tier/metadata.ts @@ -30,6 +30,7 @@ import { MetadataListResponse, } from '@kbn/security-solution-plugin/common/endpoint/types'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { generateAgentDocs, @@ -37,14 +38,22 @@ import { } from '../../../../config/services/security_solution_edr_workflows_metadata'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); const endpointTestResources = getService('endpointTestResources'); const log = getService('log'); const endpointDataStreamHelpers = getService('endpointDataStreamHelpers'); + const utils = getService('securitySolutionUtils'); // @skipInServerlessMKI - this test uses internal index manipulation in before/after hooks // @skipInServerlessMKI - if you are removing this annotation, make sure to add the test suite to the MKI pipeline in .buildkite/pipelines/security_solution_quality_gate/mki_periodic/mki_periodic_defend_workflows.yml describe('@ess @serverless @skipInServerlessMKI test metadata apis', function () { + let adminSupertest: TestAgent; + let t1AnalystSupertest: TestAgent; + + before(async () => { + adminSupertest = await utils.createSuperTest(); + t1AnalystSupertest = await utils.createSuperTest('t1_analyst'); + }); + describe('list endpoints GET route', () => { const numberOfHostsInFixture = 2; let agent1Timestamp: number; @@ -99,7 +108,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return one entry for each host with default paging', async () => { - const res = await supertest + const res = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -116,7 +125,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('metadata api should return page based on paging properties passed', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -132,7 +141,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('metadata api should return accurate total metadata if page index produces no result', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -148,7 +157,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('metadata api should return 400 when pagingProperties is below boundaries.', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -161,7 +170,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('metadata api should return page based on filters passed.', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -177,7 +186,7 @@ export default function ({ getService }: FtrProviderContext) { it('metadata api should return page based on filters and paging passed.', async () => { const notIncludedIp = '10.101.149.26'; - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -200,7 +209,7 @@ export default function ({ getService }: FtrProviderContext) { it('metadata api should return page based on host.os.Ext.variant filter.', async () => { const variantValue = 'Windows Pro'; - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -220,7 +229,7 @@ export default function ({ getService }: FtrProviderContext) { it('metadata api should return the latest event for all the events for an endpoint', async () => { const targetEndpointIp = '10.101.149.26'; - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -239,7 +248,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('metadata api should return the latest event for all the events where policy status is not success', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -259,7 +268,7 @@ export default function ({ getService }: FtrProviderContext) { it('metadata api should return the endpoint based on the elastic agent id, and status should be healthy', async () => { const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf'; const targetElasticAgentId = '023fa40c-411d-4188-a941-4147bfadd095'; - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -281,7 +290,7 @@ export default function ({ getService }: FtrProviderContext) { it('metadata api should return the endpoint based on the agent hostname', async () => { const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf'; const targetAgentHostname = 'Example-host-name-XYZ'; - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -300,7 +309,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('metadata api should return all hosts when filter is empty string', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -313,7 +322,7 @@ export default function ({ getService }: FtrProviderContext) { describe('`last_checkin` runtime field', () => { it('should sort based on `last_checkin` - because it is a runtime field', async () => { - const { body: bodyAsc }: { body: MetadataListResponse } = await supertest + const { body: bodyAsc }: { body: MetadataListResponse } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -326,7 +335,7 @@ export default function ({ getService }: FtrProviderContext) { expect(bodyAsc.data[0].last_checkin).to.eql(new Date(agent1Timestamp).toISOString()); expect(bodyAsc.data[1].last_checkin).to.eql(new Date(agent2Timestamp).toISOString()); - const { body: bodyDesc }: { body: MetadataListResponse } = await supertest + const { body: bodyDesc }: { body: MetadataListResponse } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -343,7 +352,7 @@ export default function ({ getService }: FtrProviderContext) { describe('sorting', () => { it('metadata api should return 400 with not supported sorting field', async () => { - await supertest + await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -354,7 +363,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('metadata api should sort by enrollment date by default', async () => { - const { body }: { body: MetadataListResponse } = await supertest + const { body }: { body: MetadataListResponse } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -368,7 +377,7 @@ export default function ({ getService }: FtrProviderContext) { it(`metadata api should be able to sort by ${field}`, async () => { let body: MetadataListResponse; - ({ body } = await supertest + ({ body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -381,7 +390,7 @@ export default function ({ getService }: FtrProviderContext) { expect(body.sortDirection).to.eql('asc'); expect(body.sortField).to.eql(field); - ({ body } = await supertest + ({ body } = await adminSupertest .get(HOST_METADATA_LIST_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -416,7 +425,7 @@ export default function ({ getService }: FtrProviderContext) { const config = getService('config'); const ca = config.get('servers.kibana').certificateAuthorities; - await getService('supertestWithoutAuth') + await t1AnalystSupertest .get(METADATA_TRANSFORMS_STATUS_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -428,7 +437,7 @@ export default function ({ getService }: FtrProviderContext) { await endpointDataStreamHelpers.stopTransform(getService, `${currentTransformName}*`); await endpointDataStreamHelpers.stopTransform(getService, `${unitedTransformName}*`); - const { body } = await supertest + const { body } = await adminSupertest .get(METADATA_TRANSFORMS_STATUS_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') @@ -456,7 +465,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('correctly returns started transform stats', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(METADATA_TRANSFORMS_STATUS_ROUTE) .set('kbn-xsrf', 'xxx') .set('Elastic-Api-Version', '2023-10-31') diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/package/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/package/trial_license_complete_tier/index.ts index a2332c1724d11..9a823c041a17b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/package/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/package/trial_license_complete_tier/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/index.ts index 495f784a2ee26..605c3279bff05 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/policy_response.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/policy_response.ts index 61adaf8573ca1..f9629f3a71d1b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/policy_response.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/policy_response/trial_license_complete_tier/policy_response.ts @@ -6,14 +6,21 @@ */ import expect from '@kbn/expect'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); const endpointDataStreamHelpers = getService('endpointDataStreamHelpers'); + const utils = getService('securitySolutionUtils'); describe('@ess @serverless Endpoint policy api', function () { + let adminSupertest: TestAgent; + + before(async () => { + adminSupertest = await utils.createSuperTest(); + }); + describe('GET /api/endpoint/policy_response', () => { before( async () => @@ -28,7 +35,7 @@ export default function ({ getService }: FtrProviderContext) { it('should return one policy response for an id', async () => { const expectedAgentId = 'a10ac658-a3bc-4ac6-944a-68d9bd1c5a5e'; - const { body } = await supertest + const { body } = await adminSupertest .get(`/api/endpoint/policy_response?agentId=${expectedAgentId}`) .send() .expect(200); @@ -38,7 +45,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return not found if host has no policy response', async () => { - const { body } = await supertest + const { body } = await adminSupertest .get(`/api/endpoint/policy_response?agentId=bad_id`) .send() .expect(404); diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity.ts index 1f79ced2ab20c..680a439e326f5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity.ts @@ -8,13 +8,20 @@ import expect from '@kbn/expect'; import { eventsIndexPattern } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { ResolverEntityIndex } from '@kbn/security-solution-plugin/common/endpoint/types'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); describe('@ess @serverless Resolver tests for the entity route', function () { + let adminSupertest: TestAgent; + + before(async () => { + adminSupertest = await utils.createSuperTest(); + }); + describe('winlogbeat tests', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/endpoint/resolver/winlogbeat'); @@ -27,7 +34,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns a winlogbeat sysmon event when the event matches the schema correctly', async () => { // this id is from the es archive const _id = 'sysmon-event'; - const { body }: { body: ResolverEntityIndex } = await supertest + const { body }: { body: ResolverEntityIndex } = await adminSupertest .get( `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=winlogbeat-7.11.0-default` ) @@ -50,7 +57,7 @@ export default function ({ getService }: FtrProviderContext) { it('does not return a powershell event that has event.module set to powershell', async () => { // this id is from the es archive const _id = 'powershell-event'; - const { body }: { body: ResolverEntityIndex } = await supertest + const { body }: { body: ResolverEntityIndex } = await adminSupertest .get( `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=winlogbeat-7.11.0-default` ) @@ -73,7 +80,7 @@ export default function ({ getService }: FtrProviderContext) { it('returns an event even if it does not have a mapping for entity_id', async () => { // this id is from the es archive const _id = 'fa7eb1546f44fd47d8868be8d74e0082e19f22df493c67a7725457978eb648ab'; - const { body }: { body: ResolverEntityIndex } = await supertest.get( + const { body }: { body: ResolverEntityIndex } = await adminSupertest.get( `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=.siem-signals-default` ); expect(body).eql([ @@ -96,7 +103,7 @@ export default function ({ getService }: FtrProviderContext) { it('does not return an event when it does not have the entity_id field in the document', async () => { // this id is from the es archive const _id = 'no-entity-id-field'; - const { body }: { body: ResolverEntityIndex } = await supertest.get( + const { body }: { body: ResolverEntityIndex } = await adminSupertest.get( `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=.siem-signals-default` ); expect(body).to.be.empty(); @@ -105,7 +112,7 @@ export default function ({ getService }: FtrProviderContext) { it('does not return an event when it does not have the process field in the document', async () => { // this id is from the es archive const _id = 'no-process-field'; - const { body }: { body: ResolverEntityIndex } = await supertest.get( + const { body }: { body: ResolverEntityIndex } = await adminSupertest.get( `/api/endpoint/resolver/entity?_id=${_id}&indices=${eventsIndexPattern}&indices=.siem-signals-default` ); expect(body).to.be.empty(); diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity_id.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity_id.ts index 3e65a0899cd2a..05e7c72c98d4b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity_id.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/entity_id.ts @@ -19,6 +19,7 @@ import { EndpointDocGenerator, Event, } from '@kbn/security-solution-plugin/common/endpoint/generate_data'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { createAncestryArray, schemaWithAncestry, HEADERS } from './common'; import { @@ -27,9 +28,9 @@ import { } from '../../../../config/services/security_solution_edr_workflows_resolver'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); const resolver = getService('resolverGenerator'); const generator = new EndpointDocGenerator('resolver'); + const utils = getService('securitySolutionUtils'); const setEntityIDEmptyString = (event: Event) => { if (event.process?.entity_id) { @@ -38,6 +39,12 @@ export default function ({ getService }: FtrProviderContext) { }; describe('@ess @serverless Resolver handling of entity ids', function () { + let adminSupertest: TestAgent; + + before(async () => { + adminSupertest = await utils.createSuperTest(); + }); + describe('entity api', () => { let origin: Event; let genData: InsertedEvents; @@ -55,7 +62,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('excludes events that have an empty entity_id field', async () => { - const { body }: { body: ResolverEntityIndex } = await supertest + const { body }: { body: ResolverEntityIndex } = await adminSupertest .get( // using the same indices value here twice to force the query parameter to be an array // for some reason using supertest's query() function doesn't construct a parsable array @@ -104,7 +111,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('does not find children without a process entity_id', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -177,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { } return nodes.find((node) => node.id === id) !== undefined; }; - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/events.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/events.ts index 0d938b5936430..91345c22ac82b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/events.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/events.ts @@ -18,6 +18,7 @@ import { Tree, RelatedEventCategory, } from '@kbn/security-solution-plugin/common/endpoint/generate_data'; +import TestAgent from 'supertest/lib/agent'; import { GeneratedTrees, Options, @@ -26,8 +27,8 @@ import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflo import { compareArrays, HEADERS } from './common'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); const resolver = getService('resolverGenerator'); + const utils = getService('securitySolutionUtils'); const relatedEventsToGen = [ { category: RelatedEventCategory.Driver, count: 2 }, @@ -54,7 +55,11 @@ export default function ({ getService }: FtrProviderContext) { describe('@ess @serverless event route', function () { let entityIDFilterArray: JsonObject[] | undefined; let entityIDFilter: string | undefined; + let adminSupertest: TestAgent; + before(async () => { + adminSupertest = await utils.createSuperTest(); + resolverTrees = await resolver.createTrees(treeOptions); // we only requested a single alert so there's only 1 tree tree = resolverTrees.trees[0]; @@ -78,7 +83,7 @@ export default function ({ getService }: FtrProviderContext) { filter: [{ term: { 'event.id': tree.origin.relatedEvents[0]?.event?.id } }], }, }); - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -101,7 +106,7 @@ export default function ({ getService }: FtrProviderContext) { filter: [{ term: { 'process.entity_id': '5555' } }], }, }); - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -118,7 +123,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return related events for the root node', async () => { - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -144,7 +149,7 @@ export default function ({ getService }: FtrProviderContext) { ], }, }); - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -165,7 +170,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return paginated results for the root node', async () => { - let { body }: { body: ResolverPaginatedEvents } = await supertest + let { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events?limit=2`) .set(HEADERS) .send({ @@ -181,7 +186,7 @@ export default function ({ getService }: FtrProviderContext) { compareArrays(tree.origin.relatedEvents, body.events); expect(body.nextEvent).not.to.eql(null); - ({ body } = await supertest + ({ body } = await adminSupertest .post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`) .set(HEADERS) .send({ @@ -197,7 +202,7 @@ export default function ({ getService }: FtrProviderContext) { compareArrays(tree.origin.relatedEvents, body.events); expect(body.nextEvent).to.not.eql(null); - ({ body } = await supertest + ({ body } = await adminSupertest .post(`/api/endpoint/resolver/events?limit=2&afterEvent=${body.nextEvent}`) .set(HEADERS) .send({ @@ -214,7 +219,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return the first page of information when the cursor is invalid', async () => { - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events?afterEvent=blah`) .set(HEADERS) .send({ @@ -232,7 +237,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should sort the events in descending order', async () => { - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -259,7 +264,7 @@ export default function ({ getService }: FtrProviderContext) { timestampAsDateSafeVersion(tree.origin.relatedEvents[0])?.toISOString() ?? new Date(0).toISOString(); const to = from; - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -277,7 +282,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should not find events when using an incorrect index pattern', async () => { - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -296,7 +301,7 @@ export default function ({ getService }: FtrProviderContext) { it('should retrieve lifecycle events for multiple ids', async () => { const originParentID = parentEntityIDSafeVersion(tree.origin.lifecycle[0]) ?? ''; expect(originParentID).to.not.be(''); - const { body }: { body: ResolverPaginatedEvents } = await supertest + const { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .set(HEADERS) .send({ @@ -323,7 +328,7 @@ export default function ({ getService }: FtrProviderContext) { it('should paginate lifecycle events for multiple ids', async () => { const originParentID = parentEntityIDSafeVersion(tree.origin.lifecycle[0]) ?? ''; expect(originParentID).to.not.be(''); - let { body }: { body: ResolverPaginatedEvents } = await supertest + let { body }: { body: ResolverPaginatedEvents } = await adminSupertest .post(`/api/endpoint/resolver/events`) .query({ limit: 2 }) .set(HEADERS) @@ -346,7 +351,7 @@ export default function ({ getService }: FtrProviderContext) { expect(body.events.length).to.eql(2); expect(body.nextEvent).not.to.eql(null); - ({ body } = await supertest + ({ body } = await adminSupertest .post(`/api/endpoint/resolver/events`) .query({ limit: 3, afterEvent: body.nextEvent }) .set(HEADERS) diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/index.ts index 6528ba3ecb293..ec7e5dbdb916d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/tree.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/tree.ts index 0d28ab42a14ff..8d7aa70f679bd 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/tree.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/resolver/trial_license_complete_tier/tree.ts @@ -16,6 +16,7 @@ import { Tree, RelatedEventCategory, } from '@kbn/security-solution-plugin/common/endpoint/generate_data'; +import TestAgent from 'supertest/lib/agent'; import { GeneratedTrees, Options, @@ -30,8 +31,8 @@ import { } from './common'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); const resolver = getService('resolverGenerator'); + const utils = getService('securitySolutionUtils'); const relatedEventsToGen = [ { category: RelatedEventCategory.Driver, count: 2 }, @@ -56,7 +57,11 @@ export default function ({ getService }: FtrProviderContext) { ancestryArraySize: 2, }; describe('@ess @serverless Resolver tree', function () { + let adminSupertest: TestAgent; + before(async () => { + adminSupertest = await utils.createSuperTest(); + resolverTrees = await resolver.createTrees(treeOptions); // we need tree2 for comparison tests [tree, tree2] = resolverTrees.trees; @@ -67,7 +72,7 @@ export default function ({ getService }: FtrProviderContext) { describe('ancestry events', () => { it('should return the correct ancestor nodes for the tree', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -93,7 +98,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should handle an invalid id', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -114,7 +119,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return a subset of the ancestors', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -141,7 +146,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return ancestors without the ancestry array', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -170,7 +175,7 @@ export default function ({ getService }: FtrProviderContext) { const from = new Date( timestampSafeVersion(tree.origin.lifecycle[0]) ?? new Date() ).toISOString(); - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -198,7 +203,7 @@ export default function ({ getService }: FtrProviderContext) { it('should support returning multiple ancestor trees when multiple nodes are requested', async () => { // There should be 2 levels of descendants under the origin, grab the bottom one, and the first node's id const bottomMostDescendant = Array.from(tree.childrenLevels[1].values())[0].id; - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -235,7 +240,7 @@ export default function ({ getService }: FtrProviderContext) { const level0Nodes = Array.from(tree.childrenLevels[0].values()); const leftNode = level0Nodes[0].id; const rightNode = level0Nodes[2].id; - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -267,7 +272,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should not return any nodes when the search index does not have any data', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -290,7 +295,7 @@ export default function ({ getService }: FtrProviderContext) { describe('descendant events', () => { it('returns all descendants for the origin without using the ancestry field', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -320,7 +325,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('returns all descendants for the origin using the ancestry field', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -351,7 +356,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('should handle an invalid id', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -375,7 +380,7 @@ export default function ({ getService }: FtrProviderContext) { // this gets a node should have 3 children which were created in succession so that the timestamps // are ordered correctly to be retrieved in a single call const childID = Array.from(tree.childrenLevels[0].values())[0].id; - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -409,7 +414,7 @@ export default function ({ getService }: FtrProviderContext) { const level0Nodes = Array.from(tree.childrenLevels[0].values()); const leftNodeID = level0Nodes[0].id; const rightNodeID = level0Nodes[2].id; - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -443,7 +448,7 @@ export default function ({ getService }: FtrProviderContext) { const originGrandparent = parentEntityIDSafeVersion(tree.ancestry.get(originParent)!.lifecycle[0]) ?? ''; expect(originGrandparent).to.not.be(''); - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -481,7 +486,7 @@ export default function ({ getService }: FtrProviderContext) { const originGrandparent = parentEntityIDSafeVersion(tree.ancestry.get(originParent)!.lifecycle[0]) ?? ''; expect(originGrandparent).to.not.be(''); - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -518,7 +523,7 @@ export default function ({ getService }: FtrProviderContext) { const end = new Date( timestampSafeVersion(level0Node.lifecycle[0]) ?? new Date() ).toISOString(); - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -548,7 +553,7 @@ export default function ({ getService }: FtrProviderContext) { describe('ancestry and descendants', () => { it('returns all descendants and ancestors without the ancestry field and they should have the name field', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -587,7 +592,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('returns all descendants and ancestors without the ancestry field', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -626,7 +631,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('returns all descendants and ancestors with the ancestry field', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -665,7 +670,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('returns an empty response when limits are zero', async () => { - const { body }: { body: ResolverNode[] } = await supertest + const { body }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -698,7 +703,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('different agent.ids', () => { it('should return correct nodes for tree1 and tree2 based on agent.id', async () => { - const { body: body1 }: { body: ResolverNode[] } = await supertest + const { body: body1 }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -728,7 +733,7 @@ export default function ({ getService }: FtrProviderContext) { relatedEventsCategories: relatedEventsToGen, }); - const { body: body2 }: { body: ResolverNode[] } = await supertest + const { body: body2 }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -764,7 +769,7 @@ export default function ({ getService }: FtrProviderContext) { it('should handle process.entity_id collisions correctly', async () => { const duplicateEntityId = tree.origin.id; - const { body: body1 }: { body: ResolverNode[] } = await supertest + const { body: body1 }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ @@ -782,7 +787,7 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(200); - const { body: body2 }: { body: ResolverNode[] } = await supertest + const { body: body2 }: { body: ResolverNode[] } = await adminSupertest .post('/api/endpoint/resolver/tree') .set(HEADERS) .send({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/agent_type_support.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/agent_type_support.ts index 75b0d5a68a3a9..c006ecb88ad84 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/agent_type_support.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/agent_type_support.ts @@ -6,15 +6,21 @@ */ import { ISOLATE_HOST_ROUTE_V2 } from '@kbn/security-solution-plugin/common/endpoint/constants'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - describe('@ess @serverless Response Actions support for sentinelOne agentType', function () { + const utils = getService('securitySolutionUtils'); + + let adminSupertest: TestAgent; + before(async () => { + adminSupertest = await utils.createSuperTest(); + }); + describe('and the "responseActionsSentinelOneV1Enabled" feature flag is enabled', () => { it('should not return feature disabled error, but a connector not found error', async () => { - await supertest + await adminSupertest .post(ISOLATE_HOST_ROUTE_V2) .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/execute.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/execute.ts index 6e50f67e3510d..13afbf6e4551c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/execute.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/execute.ts @@ -13,22 +13,30 @@ import { import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; import { ActionDetails } from '@kbn/security-solution-plugin/common/endpoint/types'; import { getFileDownloadId } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/get_file_download_id'; +import TestAgent from 'supertest/lib/agent'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; export default function ({ getService }: FtrProviderContext) { - const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointTestResources = getService('endpointTestResources'); const rolesUsersProvider = getService('rolesUsersProvider'); + const utils = getService('securitySolutionUtils'); // @skipInServerlessMKI - this test uses internal index manipulation in before/after hooks describe('@ess @serverless @skipInServerlessMKI Endpoint `execute` response action', function () { let indexedData: IndexedHostsAndAlertsResponse; let agentId = ''; + let t1AnalystSupertest: TestAgent; + let endpointOperationsAnalystSupertest: TestAgent; before(async () => { indexedData = await endpointTestResources.loadEndpointData(); agentId = indexedData.hosts[0].agent.id; + + t1AnalystSupertest = await utils.createSuperTest(ROLE.t1_analyst); + endpointOperationsAnalystSupertest = await utils.createSuperTest( + ROLE.endpoint_operations_analyst + ); }); after(async () => { @@ -38,9 +46,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should not allow `execute` action without required privilege', async () => { - await supertestWithoutAuth + await t1AnalystSupertest .post(EXECUTE_ROUTE) - .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la' } }) @@ -53,9 +60,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should error on invalid endpoint id', async () => { - await supertestWithoutAuth + await endpointOperationsAnalystSupertest .post(EXECUTE_ROUTE) - .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [' '], parameters: { command: 'ls -la' } }) @@ -67,9 +73,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should error on missing endpoint id', async () => { - await supertestWithoutAuth + await endpointOperationsAnalystSupertest .post(EXECUTE_ROUTE) - .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ parameters: { command: 'ls -la' } }) @@ -82,9 +87,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should error on invalid `command` parameter', async () => { - await supertestWithoutAuth + await endpointOperationsAnalystSupertest .post(EXECUTE_ROUTE) - .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: ' ' } }) @@ -96,9 +100,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should error on missing `command` parameter', async () => { - await supertestWithoutAuth + await endpointOperationsAnalystSupertest .post(EXECUTE_ROUTE) - .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId] }) @@ -111,9 +114,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should error on invalid `timeout` parameter', async () => { - await supertestWithoutAuth + await endpointOperationsAnalystSupertest .post(EXECUTE_ROUTE) - .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 'too' } }) @@ -128,7 +130,7 @@ export default function ({ getService }: FtrProviderContext) { it('should succeed with valid endpoint id and command', async () => { const { body: { data }, - } = await supertestWithoutAuth + } = await endpointOperationsAnalystSupertest .post(EXECUTE_ROUTE) .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') @@ -144,9 +146,8 @@ export default function ({ getService }: FtrProviderContext) { it('should succeed with valid endpoint id, command and an optional timeout', async () => { const { body: { data }, - } = await supertestWithoutAuth + } = await endpointOperationsAnalystSupertest .post(EXECUTE_ROUTE) - .auth(ROLE.endpoint_operations_analyst, 'changeme') .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 2000 } }) @@ -163,6 +164,7 @@ export default function ({ getService }: FtrProviderContext) { const username = 'execute_limited'; const password = 'changeme'; let fileInfoApiRoutePath: string = ''; + let customUsernameSupertest: TestAgent; before(async () => { await rolesUsersProvider.createRole({ @@ -173,11 +175,12 @@ export default function ({ getService }: FtrProviderContext) { }); await rolesUsersProvider.createUser({ name: username, password, roles: [username] }); + customUsernameSupertest = await utils.createSuperTest(username); + const { body: { data }, - } = await supertestWithoutAuth + } = await customUsernameSupertest .post(EXECUTE_ROUTE) - .auth(username, password) .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la' } }) @@ -197,9 +200,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should have access to file info api', async () => { - await supertestWithoutAuth + await customUsernameSupertest .get(fileInfoApiRoutePath) - .auth(username, password) .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') // We expect 404 because the indexes with the file info don't exist. @@ -208,9 +210,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should have access to file download api', async () => { - await supertestWithoutAuth + await customUsernameSupertest .get(`${fileInfoApiRoutePath}/download`) - .auth(username, password) .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') // We expect 404 because the indexes with the file info don't exist. diff --git a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/index.ts index c26d7dd125f39..ad4d08718c1ca 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/edr_workflows/response_actions/trial_license_complete_tier/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../../../ftr_provider_context_edr_workflows'; import { ROLE } from '../../../../config/services/security_solution_edr_workflows_roles_users'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts index b0eee842b47f6..29bf401412af4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts @@ -442,7 +442,7 @@ export default ({ getService }: FtrProviderContext) => { }); describe('delete', () => { - it('should correctly delete asset criticality', async () => { + it('should correctly delete asset criticality if it exists', async () => { const assetCriticality = { id_field: 'host.name', id_value: 'delete-me', @@ -451,7 +451,10 @@ export default ({ getService }: FtrProviderContext) => { await assetCriticalityRoutes.upsert(assetCriticality); - await assetCriticalityRoutes.delete('host.name', 'delete-me'); + const res = await assetCriticalityRoutes.delete('host.name', 'delete-me'); + + expect(res.body.deleted).to.eql(true); + expect(_.omit(res.body.record, '@timestamp')).to.eql(assetCriticality); const doc = await getAssetCriticalityDoc({ idField: 'host.name', idValue: 'delete-me', @@ -461,6 +464,13 @@ export default ({ getService }: FtrProviderContext) => { expect(doc).to.eql(undefined); }); + it('should not return 404 if the asset criticality does not exist', async () => { + const res = await assetCriticalityRoutes.delete('host.name', 'doesnt-exist'); + + expect(res.body.deleted).to.eql(false); + expect(res.body.record).to.eql(undefined); + }); + it('should return 403 if the advanced setting is disabled', async () => { await disableAssetCriticalityAdvancedSetting(kibanaServer, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/index.ts index 4ccce93c790f9..c9d97546e22c8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/index.ts @@ -18,5 +18,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./asset_criticality')); loadTestFile(require.resolve('./asset_criticality_privileges')); loadTestFile(require.resolve('./asset_criticality_csv_upload')); + loadTestFile(require.resolve('./risk_score_entity_calculation')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts index 581af2aec6012..e35ea16c87f1a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/risk_score_entity_calculation.ts @@ -76,7 +76,8 @@ export default ({ getService }: FtrProviderContext): void => { }); }; - describe('@ess @serverless @serverlessQA Risk Scoring Entity Calculation API', () => { + describe('@ess @serverless @serverlessQA Risk Scoring Entity Calculation API', function () { + this.tags(['esGate']); before(async () => { enableAssetCriticalityAdvancedSetting(kibanaServer, log); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts index 11343e077eeaf..4c541d48b436b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts @@ -29,6 +29,7 @@ import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import querystring from 'querystring'; import { KbnClient } from '@kbn/test'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { routeWithNamespace, waitFor } from '../../../../common/utils/security_solution'; export const getAssetCriticalityIndex = (namespace?: string) => @@ -202,7 +203,7 @@ export const assetCriticalityRouteHelpersFactory = ( }); export const assetCriticalityRouteHelpersFactoryNoAuth = ( - supertestWithoutAuth: SuperTest.Agent, + supertestWithoutAuth: SupertestWithoutAuthProviderType, namespace?: string ) => ({ privilegesForUser: async ({ username, password }: { username: string; password: string }) => diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts index 4c39cdce1a48a..198ffcfc0f54d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts @@ -26,6 +26,7 @@ import { import { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; import { removeLegacyTransforms } from '@kbn/security-solution-plugin/server/lib/entity_analytics/utils/transforms'; import { EntityRiskScoreRecord } from '@kbn/security-solution-plugin/common/api/entity_analytics/common'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { createRule, waitForAlertsToBePresent, @@ -579,7 +580,7 @@ interface Credentials { } export const riskEngineRouteHelpersFactoryNoAuth = ( - supertestWithoutAuth: SuperTest.Agent, + supertestWithoutAuth: SupertestWithoutAuthProviderType, namespace?: string ) => ({ privilegesForUser: async ({ username, password }: Credentials) => diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/ess.config.ts index fffc126c3692c..763f554aaf708 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/ess.config.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...functionalConfig.getAll(), - testFiles: [require.resolve('../ess')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Hosts Integration Tests - ESS Env - Trial License', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/serverless.config.ts index da6d987c970d1..778e6b3c1cc18 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/configs/serverless.config.ts @@ -16,7 +16,7 @@ export default createTestConfig({ { product_line: 'cloud', product_tier: 'complete' }, ])}`, ], - testFiles: [require.resolve('../serverless')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Hosts Integration Tests - Serverless Env - Complete Tier', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/host_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/host_details.ts deleted file mode 100644 index d02e686be56d0..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/host_details.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 expect from '@kbn/expect'; -import { - HostDetailsStrategyResponse, - HostsQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; -import { hostDetailsFilebeatExpectedResult } from '../mocks/host_details'; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('Host Details', () => { - describe('With filebeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') - ); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - - it('Make sure that we get HostDetails data', async () => { - const { hostDetails } = await bsearch.send<HostDetailsStrategyResponse>({ - supertest, - options: { - factoryQueryType: HostsQueries.details, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['filebeat-*'], - hostName: 'raspberrypi', - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(hostDetails).to.eql(hostDetailsFilebeatExpectedResult.hostDetails); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/hosts.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/hosts.ts deleted file mode 100644 index ed4de3ad0546a..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/hosts.ts +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - HostsQueries, - Direction, - HostsFields, - HostsStrategyResponse, - HostDetailsStrategyResponse, - FirstLastSeenQuery, - FirstLastSeenStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; - -// typical values that have to change after an update from "scripts/es_archiver" -const HOST_NAME = 'Ubuntu'; -const TOTAL_COUNT = 7; -const EDGE_LENGTH = 1; -const CURSOR_ID = '2ab45fc1c41e4c84bbd02202a7e5761f'; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const bsearch = getService('bsearch'); - - describe('hosts', () => { - before(async () => await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts')); - - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts') - ); - - it('Make sure that we get Hosts Table data', async () => { - const hosts = await bsearch.send<HostsStrategyResponse>({ - supertest, - options: { - factoryQueryType: HostsQueries.hosts, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-*'], - sort: { - field: HostsFields.lastSeen, - direction: Direction.asc, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(hosts.edges.length).to.be(EDGE_LENGTH); - expect(hosts.totalCount).to.be(TOTAL_COUNT); - expect(hosts.pageInfo.fakeTotalCount).to.equal(3); - }); - - it('Make sure that pagination is working in Hosts Table query', async () => { - const hosts = await bsearch.send<HostsStrategyResponse>({ - supertest, - options: { - factoryQueryType: HostsQueries.hosts, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - sort: { - field: HostsFields.lastSeen, - direction: Direction.asc, - }, - defaultIndex: ['auditbeat-*'], - pagination: { - activePage: 2, - cursorStart: 1, - fakePossibleCount: 5, - querySize: 2, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(hosts.edges.length).to.be(EDGE_LENGTH); - expect(hosts.totalCount).to.be(TOTAL_COUNT); - expect(hosts.edges[0].node.host?.os?.name).to.eql([HOST_NAME]); - }); - - it('Make sure that we get Host details data', async () => { - const { hostDetails } = await bsearch.send<HostDetailsStrategyResponse>({ - supertest, - options: { - factoryQueryType: HostsQueries.details, - hostName: 'zeek-sensor-san-francisco', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(hostDetails).to.eql({ - _id: 'zeek-sensor-san-francisco', - host: { - architecture: ['x86_64'], - id: [CURSOR_ID], - name: ['zeek-sensor-san-francisco'], - os: { - family: ['debian'], - name: [HOST_NAME], - platform: ['ubuntu'], - version: ['18.04.2 LTS (Bionic Beaver)'], - }, - }, - cloud: { - instance: { - id: ['132972452'], - }, - provider: ['digitalocean'], - region: ['sfo2'], - }, - }); - }); - - it('Make sure that we get First Seen for a Host', async () => { - const firstLastSeenHost = await bsearch.send<FirstLastSeenStrategyResponse>({ - supertest, - options: { - factoryQueryType: FirstLastSeenQuery, - defaultIndex: ['auditbeat-*'], - field: 'host.name', - value: 'zeek-sensor-san-francisco', - order: 'asc', - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(firstLastSeenHost.firstSeen).to.eql('2019-02-19T19:36:23.561Z'); - }); - - it('Make sure that we get Last Seen for a Host', async () => { - const firstLastSeenHost = await bsearch.send<FirstLastSeenStrategyResponse>({ - supertest, - options: { - factoryQueryType: FirstLastSeenQuery, - defaultIndex: ['auditbeat-*'], - field: 'host.name', - value: 'zeek-sensor-san-francisco', - order: 'desc', - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(firstLastSeenHost.lastSeen).to.eql('2019-02-19T20:42:33.561Z'); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/index.ts deleted file mode 100644 index e77610753708f..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/index.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 { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { - describe('@ess SecuritySolution Explore Hosts', () => { - loadTestFile(require.resolve('./hosts')); - loadTestFile(require.resolve('./host_details')); - loadTestFile(require.resolve('./uncommon_processes')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/uncommon_processes.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/uncommon_processes.ts deleted file mode 100644 index f6e675889c07c..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/ess/uncommon_processes.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - HostsQueries, - HostsUncommonProcessesStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; - -// typical values that have to change after an update from "scripts/es_archiver" -const TOTAL_COUNT = 3; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('hosts', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/uncommon_processes'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/uncommon_processes'); - }); - - it('should return an edge of length 1 when given a pagination of length 1', async () => { - const response = await bsearch.send<HostsUncommonProcessesStrategyResponse>({ - supertest, - options: { - factoryQueryType: HostsQueries.uncommonProcesses, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - defaultIndex: ['auditbeat-uncommon-processes'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(response.edges.length).to.be(1); - }); - - describe('when given a pagination of length 2', () => { - it('should return an edge of length 2 ', async () => { - const response = await bsearch.send<HostsUncommonProcessesStrategyResponse>({ - supertest, - options: { - factoryQueryType: HostsQueries.uncommonProcesses, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 2, - }, - defaultIndex: ['auditbeat-uncommon-processes'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(response.edges.length).to.be(2); - }); - }); - - describe('when given a pagination of length 1', () => { - let response: HostsUncommonProcessesStrategyResponse | null = null; - before(async () => { - response = await bsearch.send<HostsUncommonProcessesStrategyResponse>({ - supertest, - options: { - factoryQueryType: HostsQueries.uncommonProcesses, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - defaultIndex: ['auditbeat-uncommon-processes'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - }); - - it('should return an edge of length 1 ', () => { - expect(response?.edges.length).to.be(1); - }); - - it('should return a total count of elements', () => { - expect(response?.totalCount).to.be(TOTAL_COUNT); - }); - - it('should return a single data set with pagination of 1', () => { - const expected = { - _id: 'HCFxB2kBR346wHgnL4ik', - instances: 1, - process: { - name: ['kworker/u2:0'], - }, - user: { - id: ['0'], - name: ['root'], - }, - hosts: [ - { - id: ['zeek-sensor-san-francisco'], - name: ['zeek-sensor-san-francisco'], - }, - ], - }; - expect(response?.edges[0].node).to.eql(expected); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/host_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/host_details.ts deleted file mode 100644 index 6611e07e652d8..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/host_details.ts +++ /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 expect from '@kbn/expect'; -import { - HostDetailsStrategyResponse, - HostsQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { hostDetailsFilebeatExpectedResult } from '../mocks/host_details'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - describe('Host Details', () => { - describe('With filebeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - - it('Make sure that we get HostDetails data', async () => { - const { hostDetails } = await secureBsearch.send<HostDetailsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: HostsQueries.details, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['filebeat-*'], - hostName: 'raspberrypi', - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(hostDetails).to.eql(hostDetailsFilebeatExpectedResult.hostDetails); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/hosts.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/hosts.ts deleted file mode 100644 index 84add1afdca25..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/hosts.ts +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - HostsQueries, - Direction, - HostsFields, - HostsStrategyResponse, - HostDetailsStrategyResponse, - FirstLastSeenQuery, - FirstLastSeenStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; - -// typical values that have to change after an update from "scripts/es_archiver" -const HOST_NAME = 'Ubuntu'; -const TOTAL_COUNT = 7; -const EDGE_LENGTH = 1; -const CURSOR_ID = '2ab45fc1c41e4c84bbd02202a7e5761f'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - describe('hosts', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Make sure that we get Hosts Table data', async () => { - const hosts = await secureBsearch.send<HostsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: HostsQueries.hosts, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-*'], - sort: { - field: HostsFields.lastSeen, - direction: Direction.asc, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(hosts.edges.length).to.be(EDGE_LENGTH); - expect(hosts.totalCount).to.be(TOTAL_COUNT); - expect(hosts.pageInfo.fakeTotalCount).to.equal(3); - }); - - it('Make sure that pagination is working in Hosts Table query', async () => { - const hosts = await secureBsearch.send<HostsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: HostsQueries.hosts, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - sort: { - field: HostsFields.lastSeen, - direction: Direction.asc, - }, - defaultIndex: ['auditbeat-*'], - pagination: { - activePage: 2, - cursorStart: 1, - fakePossibleCount: 5, - querySize: 2, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(hosts.edges.length).to.be(EDGE_LENGTH); - expect(hosts.totalCount).to.be(TOTAL_COUNT); - expect(hosts.edges[0].node.host?.os?.name).to.eql([HOST_NAME]); - }); - - it('Make sure that we get Host details data', async () => { - const { hostDetails } = await secureBsearch.send<HostDetailsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: HostsQueries.details, - hostName: 'zeek-sensor-san-francisco', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(hostDetails).to.eql({ - _id: 'zeek-sensor-san-francisco', - host: { - architecture: ['x86_64'], - id: [CURSOR_ID], - name: ['zeek-sensor-san-francisco'], - os: { - family: ['debian'], - name: [HOST_NAME], - platform: ['ubuntu'], - version: ['18.04.2 LTS (Bionic Beaver)'], - }, - }, - cloud: { - instance: { - id: ['132972452'], - }, - provider: ['digitalocean'], - region: ['sfo2'], - }, - }); - }); - - it('Make sure that we get First Seen for a Host', async () => { - const firstLastSeenHost = await secureBsearch.send<FirstLastSeenStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: FirstLastSeenQuery, - defaultIndex: ['auditbeat-*'], - field: 'host.name', - value: 'zeek-sensor-san-francisco', - order: 'asc', - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(firstLastSeenHost.firstSeen).to.eql('2019-02-19T19:36:23.561Z'); - }); - - it('Make sure that we get Last Seen for a Host', async () => { - const firstLastSeenHost = await secureBsearch.send<FirstLastSeenStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: FirstLastSeenQuery, - defaultIndex: ['auditbeat-*'], - field: 'host.name', - value: 'zeek-sensor-san-francisco', - order: 'desc', - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(firstLastSeenHost.lastSeen).to.eql('2019-02-19T20:42:33.561Z'); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/index.ts deleted file mode 100644 index ca77384e41b3b..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/index.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 { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ loadTestFile, getService }: FtrProviderContextWithSpaces) { - describe('@serverless SecuritySolution Explore Hosts', () => { - loadTestFile(require.resolve('./hosts')); - loadTestFile(require.resolve('./host_details')); - loadTestFile(require.resolve('./uncommon_processes')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/uncommon_processes.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/uncommon_processes.ts deleted file mode 100644 index 659ff13d8ec92..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/serverless/uncommon_processes.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - HostsQueries, - HostsUncommonProcessesStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -const FROM = '2019-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; - -// typical values that have to change after an update from "scripts/es_archiver" -const TOTAL_COUNT = 3; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - describe('hosts', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/uncommon_processes'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/uncommon_processes'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('should return an edge of length 1 when given a pagination of length 1', async () => { - const response = await secureBsearch.send<HostsUncommonProcessesStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: HostsQueries.uncommonProcesses, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - defaultIndex: ['auditbeat-uncommon-processes'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(response.edges.length).to.be(1); - }); - - describe('when given a pagination of length 2', () => { - it('should return an edge of length 2 ', async () => { - const response = await secureBsearch.send<HostsUncommonProcessesStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: HostsQueries.uncommonProcesses, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 2, - }, - defaultIndex: ['auditbeat-uncommon-processes'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(response.edges.length).to.be(2); - }); - }); - - describe('when given a pagination of length 1', () => { - let response: HostsUncommonProcessesStrategyResponse | null = null; - before(async () => { - response = await secureBsearch.send<HostsUncommonProcessesStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: HostsQueries.uncommonProcesses, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - defaultIndex: ['auditbeat-uncommon-processes'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - }); - - it('should return an edge of length 1 ', () => { - expect(response?.edges.length).to.be(1); - }); - - it('should return a total count of elements', () => { - expect(response?.totalCount).to.be(TOTAL_COUNT); - }); - - it('should return a single data set with pagination of 1', () => { - const expected = { - _id: 'HCFxB2kBR346wHgnL4ik', - instances: 1, - process: { - name: ['kworker/u2:0'], - }, - user: { - id: ['0'], - name: ['root'], - }, - hosts: [ - { - id: ['zeek-sensor-san-francisco'], - name: ['zeek-sensor-san-francisco'], - }, - ], - }; - expect(response?.edges[0].node).to.eql(expected); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/host_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/host_details.ts new file mode 100644 index 0000000000000..1c74a987e4fec --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/host_details.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + HostDetailsStrategyResponse, + HostsQueries, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; +import { hostDetailsFilebeatExpectedResult } from '../mocks/host_details'; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Host Details', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('With filebeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + + it('Make sure that we get HostDetails data', async () => { + const { hostDetails } = await bsearch.send<HostDetailsStrategyResponse>({ + supertest, + options: { + factoryQueryType: HostsQueries.details, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['filebeat-*'], + hostName: 'raspberrypi', + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(hostDetails).to.eql(hostDetailsFilebeatExpectedResult.hostDetails); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/hosts.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/hosts.ts new file mode 100644 index 0000000000000..a39da25c81aa3 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/hosts.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + HostsQueries, + Direction, + HostsFields, + HostsStrategyResponse, + HostDetailsStrategyResponse, + FirstLastSeenQuery, + FirstLastSeenStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; + +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +const FROM = '2000-01-01T00:00:00.000Z'; +const TO = '3000-01-01T00:00:00.000Z'; + +// typical values that have to change after an update from "scripts/es_archiver" +const HOST_NAME = 'Ubuntu'; +const TOTAL_COUNT = 7; +const EDGE_LENGTH = 1; +const CURSOR_ID = '2ab45fc1c41e4c84bbd02202a7e5761f'; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('hosts', async () => { + let supertest: TestAgent; + let bsearch: BsearchService; + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts') + ); + + it('Make sure that we get Hosts Table data', async () => { + const hosts = await bsearch.send<HostsStrategyResponse>({ + supertest, + options: { + factoryQueryType: HostsQueries.hosts, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['auditbeat-*'], + sort: { + field: HostsFields.lastSeen, + direction: Direction.asc, + }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(hosts.edges.length).to.be(EDGE_LENGTH); + expect(hosts.totalCount).to.be(TOTAL_COUNT); + expect(hosts.pageInfo.fakeTotalCount).to.equal(3); + }); + + it('Make sure that pagination is working in Hosts Table query', async () => { + const hosts = await bsearch.send<HostsStrategyResponse>({ + supertest, + options: { + factoryQueryType: HostsQueries.hosts, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + sort: { + field: HostsFields.lastSeen, + direction: Direction.asc, + }, + defaultIndex: ['auditbeat-*'], + pagination: { + activePage: 2, + cursorStart: 1, + fakePossibleCount: 5, + querySize: 2, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(hosts.edges.length).to.be(EDGE_LENGTH); + expect(hosts.totalCount).to.be(TOTAL_COUNT); + expect(hosts.edges[0].node.host?.os?.name).to.eql([HOST_NAME]); + }); + + it('Make sure that we get Host details data', async () => { + const { hostDetails } = await bsearch.send<HostDetailsStrategyResponse>({ + supertest, + options: { + factoryQueryType: HostsQueries.details, + hostName: 'zeek-sensor-san-francisco', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['auditbeat-*'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(hostDetails).to.eql({ + _id: 'zeek-sensor-san-francisco', + host: { + architecture: ['x86_64'], + id: [CURSOR_ID], + name: ['zeek-sensor-san-francisco'], + os: { + family: ['debian'], + name: [HOST_NAME], + platform: ['ubuntu'], + version: ['18.04.2 LTS (Bionic Beaver)'], + }, + }, + cloud: { + instance: { + id: ['132972452'], + }, + provider: ['digitalocean'], + region: ['sfo2'], + }, + }); + }); + + it('Make sure that we get First Seen for a Host', async () => { + const firstLastSeenHost = await bsearch.send<FirstLastSeenStrategyResponse>({ + supertest, + options: { + factoryQueryType: FirstLastSeenQuery, + defaultIndex: ['auditbeat-*'], + field: 'host.name', + value: 'zeek-sensor-san-francisco', + order: 'asc', + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(firstLastSeenHost.firstSeen).to.eql('2019-02-19T19:36:23.561Z'); + }); + + it('Make sure that we get Last Seen for a Host', async () => { + const firstLastSeenHost = await bsearch.send<FirstLastSeenStrategyResponse>({ + supertest, + options: { + factoryQueryType: FirstLastSeenQuery, + defaultIndex: ['auditbeat-*'], + field: 'host.name', + value: 'zeek-sensor-san-francisco', + order: 'desc', + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(firstLastSeenHost.lastSeen).to.eql('2019-02-19T20:42:33.561Z'); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/index.ts new file mode 100644 index 0000000000000..ce291ab55d85e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/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. + */ + +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { + describe('@ess @serverless SecuritySolution Explore Hosts', () => { + loadTestFile(require.resolve('./hosts')); + loadTestFile(require.resolve('./host_details')); + loadTestFile(require.resolve('./uncommon_processes')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/uncommon_processes.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/uncommon_processes.ts new file mode 100644 index 0000000000000..19710d4eedf45 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/uncommon_processes.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { + HostsQueries, + HostsUncommonProcessesStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +const FROM = '2000-01-01T00:00:00.000Z'; +const TO = '3000-01-01T00:00:00.000Z'; + +// typical values that have to change after an update from "scripts/es_archiver" +const TOTAL_COUNT = 3; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('hosts', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/uncommon_processes'); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/uncommon_processes'); + }); + + it('should return an edge of length 1 when given a pagination of length 1', async () => { + const response = await bsearch.send<HostsUncommonProcessesStrategyResponse>({ + supertest, + options: { + factoryQueryType: HostsQueries.uncommonProcesses, + sourceId: 'default', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, + }, + defaultIndex: ['auditbeat-uncommon-processes'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(response.edges.length).to.be(1); + }); + + describe('when given a pagination of length 2', () => { + it('should return an edge of length 2 ', async () => { + const response = await bsearch.send<HostsUncommonProcessesStrategyResponse>({ + supertest, + options: { + factoryQueryType: HostsQueries.uncommonProcesses, + sourceId: 'default', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 2, + }, + defaultIndex: ['auditbeat-uncommon-processes'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(response.edges.length).to.be(2); + }); + }); + + describe('when given a pagination of length 1', () => { + let response: HostsUncommonProcessesStrategyResponse | null = null; + before(async () => { + response = await bsearch.send<HostsUncommonProcessesStrategyResponse>({ + supertest, + options: { + factoryQueryType: HostsQueries.uncommonProcesses, + sourceId: 'default', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, + }, + defaultIndex: ['auditbeat-uncommon-processes'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + }); + + it('should return an edge of length 1 ', () => { + expect(response?.edges.length).to.be(1); + }); + + it('should return a total count of elements', () => { + expect(response?.totalCount).to.be(TOTAL_COUNT); + }); + + it('should return a single data set with pagination of 1', () => { + const expected = { + _id: 'HCFxB2kBR346wHgnL4ik', + instances: 1, + process: { + name: ['kworker/u2:0'], + }, + user: { + id: ['0'], + name: ['root'], + }, + hosts: [ + { + id: ['zeek-sensor-san-francisco'], + name: ['zeek-sensor-san-francisco'], + }, + ], + }; + expect(response?.edges[0].node).to.eql(expected); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/ess.config.ts index 6439b163d054a..92a4153dc07e0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/ess.config.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...functionalConfig.getAll(), - testFiles: [require.resolve('../ess')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Network Integration Tests - ESS Env - Trial License', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/serverless.config.ts index 50aa606520261..24c6dbc4b94e9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/configs/serverless.config.ts @@ -16,7 +16,7 @@ export default createTestConfig({ { product_line: 'cloud', product_tier: 'complete' }, ])}`, ], - testFiles: [require.resolve('../serverless')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Network Integration Tests - Serverless Env - Complete Tier', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/index.ts deleted file mode 100644 index 4c67d06c62852..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { - describe('@ess SecuritySolution Explore Network', () => { - loadTestFile(require.resolve('./network_details')); - loadTestFile(require.resolve('./network_dns')); - loadTestFile(require.resolve('./network_top_n_flow')); - loadTestFile(require.resolve('./tls')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_details.ts deleted file mode 100644 index fa5811a524c80..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_details.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { - NetworkDetailsStrategyResponse, - NetworkQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('Network details', () => { - describe('With filebeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') - ); - - it('Make sure that we get Network details data', async () => { - const body = await bsearch.send<NetworkDetailsStrategyResponse>({ - supertest, - options: { - ip: '151.205.0.17', - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.details, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(body.networkDetails.source?.geo.continent_name).to.eql(['North America']); - expect(body.networkDetails.source?.geo.location?.lat!).to.eql([37.751]); - expect(body.networkDetails.host?.os?.platform).to.eql(['raspbian']); - }); - }); - - describe('With packetbeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/default') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/default') - ); - - it('Make sure that we get Network details data', async () => { - const body = await bsearch.send<NetworkDetailsStrategyResponse>({ - supertest, - options: { - ip: '185.53.91.88', - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.details, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(body.networkDetails.host?.id).to.eql(['2ce8b1e7d69e4a1d9c6bcddc473da9d9']); - expect(body.networkDetails.host?.name).to.eql(['zeek-sensor-amsterdam']); - expect(body.networkDetails.host?.os?.platform!).to.eql(['ubuntu']); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_dns.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_dns.ts deleted file mode 100644 index 8b6915294eb37..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_dns.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 expect from '@kbn/expect'; -import { - NetworkQueries, - NetworkDnsEdges, - Direction, - NetworkDnsFields, - NetworkDnsStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('Network DNS', () => { - describe('With packetbeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/dns') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/dns') - ); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - - it('Make sure that we get Dns data and sorting by uniqueDomains ascending', async () => { - const networkDns = await bsearch.send<NetworkDnsStrategyResponse>({ - supertest, - options: { - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.dns, - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - isPtrIncluded: false, - pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 30, querySize: 10 }, - sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.asc }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkDns.edges.length).to.be(10); - expect(networkDns.totalCount).to.be(44); - expect(networkDns.edges.map((i: NetworkDnsEdges) => i.node.dnsName).join(',')).to.be( - 'aaplimg.com,adgrx.com,akadns.net,akamaiedge.net,amazonaws.com,cbsistatic.com,cdn-apple.com,connman.net,d1oxlq5h9kq8q5.cloudfront.net,d3epxf4t8a32oh.cloudfront.net' - ); - expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); - }); - - it('Make sure that we get Dns data and sorting by uniqueDomains descending', async () => { - const networkDns = await bsearch.send<NetworkDnsStrategyResponse>({ - supertest, - options: { - ip: '151.205.0.17', - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.dns, - inspect: false, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.desc }, - stackByField: 'dns.question.registered_domain', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkDns.edges.length).to.be(10); - expect(networkDns.totalCount).to.be(44); - expect(networkDns.edges.map((i: NetworkDnsEdges) => i.node.dnsName).join(',')).to.be( - 'nflxvideo.net,apple.com,netflix.com,samsungcloudsolution.com,samsungqbe.com,samsungelectronics.com,internetat.tv,samsungcloudsolution.net,samsungosp.com,cbsnews.com' - ); - expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_top_n_flow.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_top_n_flow.ts deleted file mode 100644 index 7dd225c0cbf80..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/network_top_n_flow.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - NetworkQueries, - NetworkTopNFlowEdges, - Direction, - FlowTargetSourceDest, - NetworkTopTablesFields, - NetworkTopNFlowStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -const EDGE_LENGTH = 10; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('Network Top N Flow', () => { - describe('With filebeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') - ); - - const FROM = '2019-02-09T01:57:24.870Z'; - const TO = '2019-02-12T01:57:24.870Z'; - - it('should get Source NetworkTopNFlow data with bytes_in descending sort', async () => { - const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ - supertest, - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.topNFlow, - flowTarget: FlowTargetSourceDest.source, - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 0, - querySize: 10, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect( - networkTopNFlow.edges.map((i: NetworkTopNFlowEdges) => i.node.source!.ip).join(',') - ).to.be( - '10.100.7.196,10.100.7.199,10.100.7.197,10.100.7.198,3.82.33.170,17.249.172.100,10.100.4.1,8.248.209.244,8.248.211.247,8.248.213.244' - ); - expect(networkTopNFlow.edges[0].node.destination).to.be(undefined); - expect(networkTopNFlow.edges[0].node.source!.flows).to.be(498); - expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(132); - }); - - it('should get Source NetworkTopNFlow data with bytes_in ascending sort ', async () => { - const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ - supertest, - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.topNFlow, - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - flowTarget: FlowTargetSourceDest.source, - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.asc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 0, - querySize: 10, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect( - networkTopNFlow.edges.map((i: NetworkTopNFlowEdges) => i.node.source!.ip).join(',') - ).to.be( - '8.248.209.244,8.248.211.247,8.248.213.244,8.248.223.246,8.250.107.245,8.250.121.236,8.250.125.244,8.253.38.231,8.253.157.112,8.253.157.240' - ); - expect(networkTopNFlow.edges[0].node.destination).to.be(undefined); - expect(networkTopNFlow.edges[0].node.source!.flows).to.be(12); - expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(1); - }); - - it('should get Destination NetworkTopNFlow data', async () => { - const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ - supertest, - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: 'topNFlow', - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, - flowTarget: FlowTargetSourceDest.destination, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 0, - querySize: 10, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect(networkTopNFlow.edges[0].node.destination!.flows).to.be(19); - expect(networkTopNFlow.edges[0].node.destination!.source_ips).to.be(1); - expect(networkTopNFlow.edges[0].node.source).to.be(undefined); - }); - - it('should paginate NetworkTopNFlow query', async () => { - const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ - supertest, - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: 'topNFlow', - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, - flowTarget: FlowTargetSourceDest.source, - pagination: { - activePage: 1, - cursorStart: 10, - fakePossibleCount: 0, - querySize: 20, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect(networkTopNFlow.edges[0].node.source!.ip).to.be('8.248.223.246'); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/tls.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/tls.ts deleted file mode 100644 index 52da3a9b46781..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/ess/tls.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 expect from '@kbn/expect'; -import { - NetworkQueries, - Direction, - NetworkTlsFields, - FlowTarget, - NetworkTlsStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; -const SOURCE_IP = '10.128.0.35'; -const DESTINATION_IP = '74.125.129.95'; - -const expectedResult = { - _id: '16989191B1A93ECECD5FE9E63EBD4B5C3B606D26', - subjects: ['CN=edgecert.googleapis.com,O=Google LLC,L=Mountain View,ST=California,C=US'], - issuers: ['CN=GTS CA 1O1,O=Google Trust Services,C=US'], - ja3: ['bd12d76eb0b6787e6a78a14d2ff96c2b'], - notAfter: ['2020-05-06T11:52:15.000Z'], -}; - -const expectedOverviewDestinationResult = { - edges: [ - { - cursor: { - tiebreaker: null, - value: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - }, - node: { - _id: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - subjects: [ - 'CN=*.cdn.mozilla.net,OU=Cloud Services,O=Mozilla Corporation,L=Mountain View,ST=California,C=US', - ], - issuers: ['CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US'], - ja3: ['b20b44b18b853ef29ab773e921b03422'], - notAfter: ['2020-12-09T12:00:00.000Z'], - }, - }, - ], - pageInfo: { - activePage: 0, - fakeTotalCount: 3, - showMorePagesIndicator: false, - }, - totalCount: 3, -}; - -const expectedOverviewSourceResult = { - edges: [ - { - cursor: { - tiebreaker: null, - value: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - }, - node: { - _id: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - subjects: [ - 'CN=*.cdn.mozilla.net,OU=Cloud Services,O=Mozilla Corporation,L=Mountain View,ST=California,C=US', - ], - issuers: ['CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US'], - ja3: ['b20b44b18b853ef29ab773e921b03422'], - notAfter: ['2020-12-09T12:00:00.000Z'], - }, - }, - ], - pageInfo: { - activePage: 0, - fakeTotalCount: 3, - showMorePagesIndicator: false, - }, - totalCount: 3, -}; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const bsearch = getService('bsearch'); - - describe('Tls Test with Packetbeat', () => { - describe('Tls Test', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/tls') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/tls') - ); - - it('Ensure data is returned for FlowTarget.Source', async () => { - const tls = await bsearch.send<NetworkTlsStrategyResponse>({ - supertest, - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: SOURCE_IP, - flowTarget: FlowTarget.source, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(tls.edges.length).to.be(1); - expect(tls.totalCount).to.be(1); - expect(tls.edges[0].node).to.eql(expectedResult); - }); - - it('Ensure data is returned for FlowTarget.Destination', async () => { - const tls = await bsearch.send<NetworkTlsStrategyResponse>({ - supertest, - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: DESTINATION_IP, - flowTarget: FlowTarget.destination, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(tls.edges.length).to.be(1); - expect(tls.totalCount).to.be(1); - expect(tls.edges[0].node).to.eql(expectedResult); - }); - }); - - describe('Tls Overview Test', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/tls') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/tls') - ); - - it('Ensure data is returned for FlowTarget.Source', async () => { - const tls = await bsearch.send<NetworkTlsStrategyResponse>({ - supertest, - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: '', - flowTarget: FlowTarget.source, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(tls.pageInfo).to.eql(expectedOverviewSourceResult.pageInfo); - expect(tls.edges[0]).to.eql(expectedOverviewSourceResult.edges[0]); - }); - - it('Ensure data is returned for FlowTarget.Destination', async () => { - const tls = await bsearch.send<NetworkTlsStrategyResponse>({ - supertest, - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: '', - flowTarget: FlowTarget.destination, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(tls.pageInfo).to.eql(expectedOverviewDestinationResult.pageInfo); - expect(tls.edges[0]).to.eql(expectedOverviewDestinationResult.edges[0]); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/index.ts deleted file mode 100644 index c045e15ca1a1d..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('@serverless SecuritySolution Explore Network', () => { - loadTestFile(require.resolve('./network_details')); - loadTestFile(require.resolve('./network_dns')); - loadTestFile(require.resolve('./network_top_n_flow')); - loadTestFile(require.resolve('./tls')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_details.ts deleted file mode 100644 index 251d1d7a1e390..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_details.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 expect from '@kbn/expect'; -import { - NetworkDetailsStrategyResponse, - NetworkQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - describe('Network details', () => { - describe('With filebeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Make sure that we get Network details data', async () => { - const body = await secureBsearch.send<NetworkDetailsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - ip: '151.205.0.17', - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.details, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(body.networkDetails.source?.geo.continent_name).to.eql(['North America']); - expect(body.networkDetails.source?.geo.location?.lat!).to.eql([37.751]); - expect(body.networkDetails.host?.os?.platform).to.eql(['raspbian']); - }); - }); - - describe('With packetbeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/default'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/default'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Make sure that we get Network details data', async () => { - const body = await secureBsearch.send<NetworkDetailsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - ip: '185.53.91.88', - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.details, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(body.networkDetails.host?.id).to.eql(['2ce8b1e7d69e4a1d9c6bcddc473da9d9']); - expect(body.networkDetails.host?.name).to.eql(['zeek-sensor-amsterdam']); - expect(body.networkDetails.host?.os?.platform!).to.eql(['ubuntu']); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_dns.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_dns.ts deleted file mode 100644 index 617febc72a4e1..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_dns.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - NetworkQueries, - NetworkDnsEdges, - Direction, - NetworkDnsFields, - NetworkDnsStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - - describe('Network DNS', () => { - describe('With packetbeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/dns'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/dns'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - - it('Make sure that we get Dns data and sorting by uniqueDomains ascending', async () => { - const networkDns = await secureBsearch.send<NetworkDnsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - - internalOrigin: 'Kibana', - options: { - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.dns, - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - isPtrIncluded: false, - pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 30, querySize: 10 }, - sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.asc }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkDns.edges.length).to.be(10); - expect(networkDns.totalCount).to.be(44); - expect(networkDns.edges.map((i: NetworkDnsEdges) => i.node.dnsName).join(',')).to.be( - 'aaplimg.com,adgrx.com,akadns.net,akamaiedge.net,amazonaws.com,cbsistatic.com,cdn-apple.com,connman.net,d1oxlq5h9kq8q5.cloudfront.net,d3epxf4t8a32oh.cloudfront.net' - ); - expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); - }); - - it('Make sure that we get Dns data and sorting by uniqueDomains descending', async () => { - const networkDns = await secureBsearch.send<NetworkDnsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - - internalOrigin: 'Kibana', - options: { - ip: '151.205.0.17', - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.dns, - inspect: false, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.desc }, - stackByField: 'dns.question.registered_domain', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkDns.edges.length).to.be(10); - expect(networkDns.totalCount).to.be(44); - expect(networkDns.edges.map((i: NetworkDnsEdges) => i.node.dnsName).join(',')).to.be( - 'nflxvideo.net,apple.com,netflix.com,samsungcloudsolution.com,samsungqbe.com,samsungelectronics.com,internetat.tv,samsungcloudsolution.net,samsungosp.com,cbsnews.com' - ); - expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_top_n_flow.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_top_n_flow.ts deleted file mode 100644 index 63e4d1041d148..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/network_top_n_flow.ts +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - NetworkQueries, - NetworkTopNFlowEdges, - Direction, - FlowTargetSourceDest, - NetworkTopTablesFields, - NetworkTopNFlowStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -const EDGE_LENGTH = 10; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - describe('Network Top N Flow', () => { - describe('With filebeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - const FROM = '2019-02-09T01:57:24.870Z'; - const TO = '2019-02-12T01:57:24.870Z'; - - it('should get Source NetworkTopNFlow data with bytes_in descending sort', async () => { - const networkTopNFlow = await secureBsearch.send<NetworkTopNFlowStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.topNFlow, - flowTarget: FlowTargetSourceDest.source, - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 0, - querySize: 10, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect( - networkTopNFlow.edges.map((i: NetworkTopNFlowEdges) => i.node.source!.ip).join(',') - ).to.be( - '10.100.7.196,10.100.7.199,10.100.7.197,10.100.7.198,3.82.33.170,17.249.172.100,10.100.4.1,8.248.209.244,8.248.211.247,8.248.213.244' - ); - expect(networkTopNFlow.edges[0].node.destination).to.be(undefined); - expect(networkTopNFlow.edges[0].node.source!.flows).to.be(498); - expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(132); - }); - - it('should get Source NetworkTopNFlow data with bytes_in ascending sort ', async () => { - const networkTopNFlow = await secureBsearch.send<NetworkTopNFlowStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.topNFlow, - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - flowTarget: FlowTargetSourceDest.source, - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.asc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 0, - querySize: 10, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect( - networkTopNFlow.edges.map((i: NetworkTopNFlowEdges) => i.node.source!.ip).join(',') - ).to.be( - '8.248.209.244,8.248.211.247,8.248.213.244,8.248.223.246,8.250.107.245,8.250.121.236,8.250.125.244,8.253.38.231,8.253.157.112,8.253.157.240' - ); - expect(networkTopNFlow.edges[0].node.destination).to.be(undefined); - expect(networkTopNFlow.edges[0].node.source!.flows).to.be(12); - expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(1); - }); - - it('should get Destination NetworkTopNFlow data', async () => { - const networkTopNFlow = await secureBsearch.send<NetworkTopNFlowStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: 'topNFlow', - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, - flowTarget: FlowTargetSourceDest.destination, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 0, - querySize: 10, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect(networkTopNFlow.edges[0].node.destination!.flows).to.be(19); - expect(networkTopNFlow.edges[0].node.destination!.source_ips).to.be(1); - expect(networkTopNFlow.edges[0].node.source).to.be(undefined); - }); - - it('should paginate NetworkTopNFlow query', async () => { - const networkTopNFlow = await secureBsearch.send<NetworkTopNFlowStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: 'topNFlow', - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', - sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, - flowTarget: FlowTargetSourceDest.source, - pagination: { - activePage: 1, - cursorStart: 10, - fakePossibleCount: 0, - querySize: 20, - }, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); - expect(networkTopNFlow.edges[0].node.source!.ip).to.be('8.248.223.246'); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/tls.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/tls.ts deleted file mode 100644 index b92ac100b417c..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/serverless/tls.ts +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - NetworkQueries, - Direction, - NetworkTlsFields, - FlowTarget, - NetworkTlsStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; -const SOURCE_IP = '10.128.0.35'; -const DESTINATION_IP = '74.125.129.95'; - -const expectedResult = { - _id: '16989191B1A93ECECD5FE9E63EBD4B5C3B606D26', - subjects: ['CN=edgecert.googleapis.com,O=Google LLC,L=Mountain View,ST=California,C=US'], - issuers: ['CN=GTS CA 1O1,O=Google Trust Services,C=US'], - ja3: ['bd12d76eb0b6787e6a78a14d2ff96c2b'], - notAfter: ['2020-05-06T11:52:15.000Z'], -}; - -const expectedOverviewDestinationResult = { - edges: [ - { - cursor: { - tiebreaker: null, - value: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - }, - node: { - _id: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - subjects: [ - 'CN=*.cdn.mozilla.net,OU=Cloud Services,O=Mozilla Corporation,L=Mountain View,ST=California,C=US', - ], - issuers: ['CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US'], - ja3: ['b20b44b18b853ef29ab773e921b03422'], - notAfter: ['2020-12-09T12:00:00.000Z'], - }, - }, - ], - pageInfo: { - activePage: 0, - fakeTotalCount: 3, - showMorePagesIndicator: false, - }, - totalCount: 3, -}; - -const expectedOverviewSourceResult = { - edges: [ - { - cursor: { - tiebreaker: null, - value: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - }, - node: { - _id: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', - subjects: [ - 'CN=*.cdn.mozilla.net,OU=Cloud Services,O=Mozilla Corporation,L=Mountain View,ST=California,C=US', - ], - issuers: ['CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US'], - ja3: ['b20b44b18b853ef29ab773e921b03422'], - notAfter: ['2020-12-09T12:00:00.000Z'], - }, - }, - ], - pageInfo: { - activePage: 0, - fakeTotalCount: 3, - showMorePagesIndicator: false, - }, - totalCount: 3, -}; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const secureBsearch = getService('secureBsearch'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - - describe('Tls Test with Packetbeat', () => { - describe('Tls Test', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/tls'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/tls'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Ensure data is returned for FlowTarget.Source', async () => { - const tls = await secureBsearch.send<NetworkTlsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: SOURCE_IP, - flowTarget: FlowTarget.source, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(tls.edges.length).to.be(1); - expect(tls.totalCount).to.be(1); - expect(tls.edges[0].node).to.eql(expectedResult); - }); - - it('Ensure data is returned for FlowTarget.Destination', async () => { - const tls = await secureBsearch.send<NetworkTlsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: DESTINATION_IP, - flowTarget: FlowTarget.destination, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(tls.edges.length).to.be(1); - expect(tls.totalCount).to.be(1); - expect(tls.edges[0].node).to.eql(expectedResult); - }); - }); - - describe('Tls Overview Test', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/tls'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/tls'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Ensure data is returned for FlowTarget.Source', async () => { - const tls = await secureBsearch.send<NetworkTlsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: '', - flowTarget: FlowTarget.source, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(tls.pageInfo).to.eql(expectedOverviewSourceResult.pageInfo); - expect(tls.edges[0]).to.eql(expectedOverviewSourceResult.edges[0]); - }); - - it('Ensure data is returned for FlowTarget.Destination', async () => { - const tls = await secureBsearch.send<NetworkTlsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: NetworkQueries.tls, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - ip: '', - flowTarget: FlowTarget.destination, - sort: { field: NetworkTlsFields._id, direction: Direction.desc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - defaultIndex: ['packetbeat-*'], - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(tls.pageInfo).to.eql(expectedOverviewDestinationResult.pageInfo); - expect(tls.edges[0]).to.eql(expectedOverviewDestinationResult.edges[0]); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/index.ts new file mode 100644 index 0000000000000..a5d905911f2b0 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { + describe('@ess @serverless SecuritySolution Explore Network', () => { + loadTestFile(require.resolve('./network_details')); + loadTestFile(require.resolve('./network_dns')); + loadTestFile(require.resolve('./network_top_n_flow')); + loadTestFile(require.resolve('./tls')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_details.ts new file mode 100644 index 0000000000000..5e9040424713b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_details.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + NetworkDetailsStrategyResponse, + NetworkQueries, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; + +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Network details', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('With filebeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') + ); + + it('Make sure that we get Network details data', async () => { + const body = await bsearch.send<NetworkDetailsStrategyResponse>({ + supertest, + options: { + ip: '151.205.0.17', + defaultIndex: ['filebeat-*'], + factoryQueryType: NetworkQueries.details, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(body.networkDetails.source?.geo.continent_name).to.eql(['North America']); + expect(body.networkDetails.source?.geo.location?.lat!).to.eql([37.751]); + expect(body.networkDetails.host?.os?.platform).to.eql(['raspbian']); + }); + }); + + describe('With packetbeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/default'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/default') + ); + + it('Make sure that we get Network details data', async () => { + const body = await bsearch.send<NetworkDetailsStrategyResponse>({ + supertest, + options: { + ip: '185.53.91.88', + defaultIndex: ['packetbeat-*'], + factoryQueryType: NetworkQueries.details, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(body.networkDetails.host?.id).to.eql(['2ce8b1e7d69e4a1d9c6bcddc473da9d9']); + expect(body.networkDetails.host?.name).to.eql(['zeek-sensor-amsterdam']); + expect(body.networkDetails.host?.os?.platform!).to.eql(['ubuntu']); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_dns.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_dns.ts new file mode 100644 index 0000000000000..7254dc6e99a5e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_dns.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + NetworkQueries, + NetworkDnsEdges, + Direction, + NetworkDnsFields, + NetworkDnsStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Network DNS', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('With packetbeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/dns'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/dns') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + + it('Make sure that we get Dns data and sorting by uniqueDomains ascending', async () => { + const networkDns = await bsearch.send<NetworkDnsStrategyResponse>({ + supertest, + options: { + defaultIndex: ['packetbeat-*'], + factoryQueryType: NetworkQueries.dns, + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', + isPtrIncluded: false, + pagination: { activePage: 0, cursorStart: 0, fakePossibleCount: 30, querySize: 10 }, + sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.asc }, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(networkDns.edges.length).to.be(10); + expect(networkDns.totalCount).to.be(44); + expect(networkDns.edges.map((i: NetworkDnsEdges) => i.node.dnsName).join(',')).to.be( + 'aaplimg.com,adgrx.com,akadns.net,akamaiedge.net,amazonaws.com,cbsistatic.com,cdn-apple.com,connman.net,d1oxlq5h9kq8q5.cloudfront.net,d3epxf4t8a32oh.cloudfront.net' + ); + expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); + }); + + it('Make sure that we get Dns data and sorting by uniqueDomains descending', async () => { + const networkDns = await bsearch.send<NetworkDnsStrategyResponse>({ + supertest, + options: { + ip: '151.205.0.17', + defaultIndex: ['packetbeat-*'], + factoryQueryType: NetworkQueries.dns, + inspect: false, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + sort: { field: NetworkDnsFields.uniqueDomains, direction: Direction.desc }, + stackByField: 'dns.question.registered_domain', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(networkDns.edges.length).to.be(10); + expect(networkDns.totalCount).to.be(44); + expect(networkDns.edges.map((i: NetworkDnsEdges) => i.node.dnsName).join(',')).to.be( + 'nflxvideo.net,apple.com,netflix.com,samsungcloudsolution.com,samsungqbe.com,samsungelectronics.com,internetat.tv,samsungcloudsolution.net,samsungosp.com,cbsnews.com' + ); + expect(networkDns.pageInfo.fakeTotalCount).to.equal(30); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_top_n_flow.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_top_n_flow.ts new file mode 100644 index 0000000000000..2306861471073 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_top_n_flow.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 expect from '@kbn/expect'; +import { + NetworkQueries, + NetworkTopNFlowEdges, + Direction, + FlowTargetSourceDest, + NetworkTopTablesFields, + NetworkTopNFlowStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; + +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +const EDGE_LENGTH = 10; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Network Top N Flow', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('With filebeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') + ); + + const FROM = '2019-02-09T01:57:24.870Z'; + const TO = '2019-02-12T01:57:24.870Z'; + + it('should get Source NetworkTopNFlow data with bytes_in descending sort', async () => { + const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ + supertest, + options: { + defaultIndex: ['filebeat-*'], + factoryQueryType: NetworkQueries.topNFlow, + flowTarget: FlowTargetSourceDest.source, + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 0, + querySize: 10, + }, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); + expect( + networkTopNFlow.edges.map((i: NetworkTopNFlowEdges) => i.node.source!.ip).join(',') + ).to.be( + '10.100.7.196,10.100.7.199,10.100.7.197,10.100.7.198,3.82.33.170,17.249.172.100,10.100.4.1,8.248.209.244,8.248.211.247,8.248.213.244' + ); + expect(networkTopNFlow.edges[0].node.destination).to.be(undefined); + expect(networkTopNFlow.edges[0].node.source!.flows).to.be(498); + expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(132); + }); + + it('should get Source NetworkTopNFlow data with bytes_in ascending sort ', async () => { + const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ + supertest, + options: { + defaultIndex: ['filebeat-*'], + factoryQueryType: NetworkQueries.topNFlow, + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', + flowTarget: FlowTargetSourceDest.source, + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.asc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 0, + querySize: 10, + }, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); + expect( + networkTopNFlow.edges.map((i: NetworkTopNFlowEdges) => i.node.source!.ip).join(',') + ).to.be( + '8.248.209.244,8.248.211.247,8.248.213.244,8.248.223.246,8.250.107.245,8.250.121.236,8.250.125.244,8.253.38.231,8.253.157.112,8.253.157.240' + ); + expect(networkTopNFlow.edges[0].node.destination).to.be(undefined); + expect(networkTopNFlow.edges[0].node.source!.flows).to.be(12); + expect(networkTopNFlow.edges[0].node.source!.destination_ips).to.be(1); + }); + + it('should get Destination NetworkTopNFlow data', async () => { + const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ + supertest, + options: { + defaultIndex: ['filebeat-*'], + factoryQueryType: 'topNFlow', + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, + flowTarget: FlowTargetSourceDest.destination, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 0, + querySize: 10, + }, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); + expect(networkTopNFlow.edges[0].node.destination!.flows).to.be(19); + expect(networkTopNFlow.edges[0].node.destination!.source_ips).to.be(1); + expect(networkTopNFlow.edges[0].node.source).to.be(undefined); + }); + + it('should paginate NetworkTopNFlow query', async () => { + const networkTopNFlow = await bsearch.send<NetworkTopNFlowStrategyResponse>({ + supertest, + options: { + defaultIndex: ['filebeat-*'], + factoryQueryType: 'topNFlow', + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}}],"should":[],"must_not":[]}}', + sort: { field: NetworkTopTablesFields.bytes_in, direction: Direction.desc }, + flowTarget: FlowTargetSourceDest.source, + pagination: { + activePage: 1, + cursorStart: 10, + fakePossibleCount: 0, + querySize: 20, + }, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(networkTopNFlow.edges.length).to.be(EDGE_LENGTH); + expect(networkTopNFlow.edges[0].node.source!.ip).to.be('8.248.223.246'); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/tls.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/tls.ts new file mode 100644 index 0000000000000..4c555ca0d6555 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/tls.ts @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + NetworkQueries, + Direction, + NetworkTlsFields, + FlowTarget, + NetworkTlsStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; + +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +const FROM = '2000-01-01T00:00:00.000Z'; +const TO = '3000-01-01T00:00:00.000Z'; +const SOURCE_IP = '10.128.0.35'; +const DESTINATION_IP = '74.125.129.95'; + +const expectedResult = { + _id: '16989191B1A93ECECD5FE9E63EBD4B5C3B606D26', + subjects: ['CN=edgecert.googleapis.com,O=Google LLC,L=Mountain View,ST=California,C=US'], + issuers: ['CN=GTS CA 1O1,O=Google Trust Services,C=US'], + ja3: ['bd12d76eb0b6787e6a78a14d2ff96c2b'], + notAfter: ['2020-05-06T11:52:15.000Z'], +}; + +const expectedOverviewDestinationResult = { + edges: [ + { + cursor: { + tiebreaker: null, + value: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', + }, + node: { + _id: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', + subjects: [ + 'CN=*.cdn.mozilla.net,OU=Cloud Services,O=Mozilla Corporation,L=Mountain View,ST=California,C=US', + ], + issuers: ['CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US'], + ja3: ['b20b44b18b853ef29ab773e921b03422'], + notAfter: ['2020-12-09T12:00:00.000Z'], + }, + }, + ], + pageInfo: { + activePage: 0, + fakeTotalCount: 3, + showMorePagesIndicator: false, + }, + totalCount: 3, +}; + +const expectedOverviewSourceResult = { + edges: [ + { + cursor: { + tiebreaker: null, + value: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', + }, + node: { + _id: 'EB4E81DD7C55BA9715652ECF5647FB8877E55A8F', + subjects: [ + 'CN=*.cdn.mozilla.net,OU=Cloud Services,O=Mozilla Corporation,L=Mountain View,ST=California,C=US', + ], + issuers: ['CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US'], + ja3: ['b20b44b18b853ef29ab773e921b03422'], + notAfter: ['2020-12-09T12:00:00.000Z'], + }, + }, + ], + pageInfo: { + activePage: 0, + fakeTotalCount: 3, + showMorePagesIndicator: false, + }, + totalCount: 3, +}; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Tls Test with Packetbeat', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('Tls Test', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/tls'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/tls') + ); + + it('Ensure data is returned for FlowTarget.Source', async () => { + const tls = await bsearch.send<NetworkTlsStrategyResponse>({ + supertest, + options: { + factoryQueryType: NetworkQueries.tls, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + ip: SOURCE_IP, + flowTarget: FlowTarget.source, + sort: { field: NetworkTlsFields._id, direction: Direction.desc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + defaultIndex: ['packetbeat-*'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(tls.edges.length).to.be(1); + expect(tls.totalCount).to.be(1); + expect(tls.edges[0].node).to.eql(expectedResult); + }); + + it('Ensure data is returned for FlowTarget.Destination', async () => { + const tls = await bsearch.send<NetworkTlsStrategyResponse>({ + supertest, + options: { + factoryQueryType: NetworkQueries.tls, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + ip: DESTINATION_IP, + flowTarget: FlowTarget.destination, + sort: { field: NetworkTlsFields._id, direction: Direction.desc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + defaultIndex: ['packetbeat-*'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(tls.edges.length).to.be(1); + expect(tls.totalCount).to.be(1); + expect(tls.edges[0].node).to.eql(expectedResult); + }); + }); + + describe('Tls Overview Test', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/tls'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/tls') + ); + + it('Ensure data is returned for FlowTarget.Source', async () => { + const tls = await bsearch.send<NetworkTlsStrategyResponse>({ + supertest, + options: { + factoryQueryType: NetworkQueries.tls, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + ip: '', + flowTarget: FlowTarget.source, + sort: { field: NetworkTlsFields._id, direction: Direction.desc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + defaultIndex: ['packetbeat-*'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(tls.pageInfo).to.eql(expectedOverviewSourceResult.pageInfo); + expect(tls.edges[0]).to.eql(expectedOverviewSourceResult.edges[0]); + }); + + it('Ensure data is returned for FlowTarget.Destination', async () => { + const tls = await bsearch.send<NetworkTlsStrategyResponse>({ + supertest, + options: { + factoryQueryType: NetworkQueries.tls, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + ip: '', + flowTarget: FlowTarget.destination, + sort: { field: NetworkTlsFields._id, direction: Direction.desc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + defaultIndex: ['packetbeat-*'], + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(tls.pageInfo).to.eql(expectedOverviewDestinationResult.pageInfo); + expect(tls.edges[0]).to.eql(expectedOverviewDestinationResult.edges[0]); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/ess.config.ts index c94840cb0709d..d582e84921cd1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/ess.config.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...functionalConfig.getAll(), - testFiles: [require.resolve('../ess')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Overview Integration Tests - ESS Env - Trial License', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/serverless.config.ts index 91dc13bc76281..c2a2e06d1f9e3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/configs/serverless.config.ts @@ -16,7 +16,7 @@ export default createTestConfig({ { product_line: 'cloud', product_tier: 'complete' }, ])}`, ], - testFiles: [require.resolve('../serverless')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Overview Integration Tests - Serverless Env - Complete Tier', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/index.ts deleted file mode 100644 index 1967dec3cd9d8..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/index.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. - */ - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { - describe('@ess SecuritySolution Explore Overview', () => { - loadTestFile(require.resolve('./overview_host')); - loadTestFile(require.resolve('./overview_network')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/overview_host.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/overview_host.ts deleted file mode 100644 index a528a02eae718..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/overview_host.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; - -import { - HostsQueries, - HostsOverviewStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('Overview Host', () => { - describe('With auditbeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/overview') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/overview') - ); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - const expectedResult = { - auditbeatAuditd: 2194, - auditbeatFIM: 4, - auditbeatLogin: 2810, - auditbeatPackage: 3, - auditbeatProcess: 7, - auditbeatUser: 6, - endgameDns: 1, - endgameFile: 2, - endgameImageLoad: 1, - endgameNetwork: 4, - endgameProcess: 2, - endgameRegistry: 1, - endgameSecurity: 4, - filebeatSystemModule: 0, - winlogbeatSecurity: 0, - winlogbeatMWSysmonOperational: 0, - }; - - it('Make sure that we get OverviewHost data', async () => { - const { overviewHost } = await bsearch.send<HostsOverviewStrategyResponse>({ - supertest, - options: { - defaultIndex: ['auditbeat-*'], - factoryQueryType: HostsQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewHost).to.eql(expectedResult); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/overview_network.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/overview_network.ts deleted file mode 100644 index 76c2c0b45efe1..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/ess/overview_network.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - NetworkOverviewStrategyResponse, - NetworkQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('Overview Network', () => { - describe('With filebeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') - ); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - - const expectedResult = { - auditbeatSocket: 0, - filebeatCisco: 0, - filebeatNetflow: 1273, - filebeatPanw: 0, - filebeatSuricata: 4547, - filebeatZeek: 0, - packetbeatDNS: 0, - packetbeatFlow: 0, - packetbeatTLS: 0, - }; - - it('Make sure that we get OverviewNetwork data', async () => { - const { overviewNetwork } = await bsearch.send<NetworkOverviewStrategyResponse>({ - supertest, - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewNetwork).to.eql(expectedResult); - }); - }); - - describe('With packetbeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/overview') - ); - after( - async () => - await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/overview') - ); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - const expectedResult = { - auditbeatSocket: 0, - filebeatCisco: 0, - filebeatNetflow: 0, - filebeatPanw: 0, - filebeatSuricata: 0, - filebeatZeek: 0, - packetbeatDNS: 44, - packetbeatFlow: 588, - packetbeatTLS: 0, - }; - - it('Make sure that we get OverviewNetwork data', async () => { - const { overviewNetwork } = await bsearch.send<NetworkOverviewStrategyResponse>({ - supertest, - options: { - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewNetwork).to.eql(expectedResult); - }); - }); - - describe('With auditbeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/overview') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/overview') - ); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - const expectedResult = { - auditbeatSocket: 45, - filebeatCisco: 0, - filebeatNetflow: 0, - filebeatPanw: 0, - filebeatSuricata: 0, - filebeatZeek: 0, - packetbeatDNS: 0, - packetbeatFlow: 0, - packetbeatTLS: 0, - }; - - it('Make sure that we get OverviewNetwork data', async () => { - const { overviewNetwork } = await bsearch.send<NetworkOverviewStrategyResponse>({ - supertest, - options: { - defaultIndex: ['auditbeat-*'], - factoryQueryType: NetworkQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewNetwork).to.eql(expectedResult); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/index.ts deleted file mode 100644 index dee32eba5b386..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/index.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. - */ - -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ loadTestFile, getService }: FtrProviderContext) { - describe('@serverless SecuritySolution Explore Overview', () => { - loadTestFile(require.resolve('./overview_host')); - loadTestFile(require.resolve('./overview_network')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/overview_host.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/overview_host.ts deleted file mode 100644 index 0d8ae5ee2a655..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/overview_host.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; - -import { - HostsQueries, - HostsOverviewStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - describe('Overview Host', () => { - describe('With auditbeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/overview'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/overview'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - const expectedResult = { - auditbeatAuditd: 2194, - auditbeatFIM: 4, - auditbeatLogin: 2810, - auditbeatPackage: 3, - auditbeatProcess: 7, - auditbeatUser: 6, - endgameDns: 1, - endgameFile: 2, - endgameImageLoad: 1, - endgameNetwork: 4, - endgameProcess: 2, - endgameRegistry: 1, - endgameSecurity: 4, - filebeatSystemModule: 0, - winlogbeatSecurity: 0, - winlogbeatMWSysmonOperational: 0, - }; - - it('Make sure that we get OverviewHost data', async () => { - const { overviewHost } = await secureBsearch.send<HostsOverviewStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['auditbeat-*'], - factoryQueryType: HostsQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewHost).to.eql(expectedResult); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/overview_network.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/overview_network.ts deleted file mode 100644 index ff4958aa69168..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/serverless/overview_network.ts +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - NetworkOverviewStrategyResponse, - NetworkQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - - describe('Overview Network', () => { - describe('With filebeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - - const expectedResult = { - auditbeatSocket: 0, - filebeatCisco: 0, - filebeatNetflow: 1273, - filebeatPanw: 0, - filebeatSuricata: 4547, - filebeatZeek: 0, - packetbeatDNS: 0, - packetbeatFlow: 0, - packetbeatTLS: 0, - }; - - it('Make sure that we get OverviewNetwork data', async () => { - const { overviewNetwork } = await secureBsearch.send<NetworkOverviewStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['filebeat-*'], - factoryQueryType: NetworkQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewNetwork).to.eql(expectedResult); - }); - }); - - describe('With packetbeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/overview'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/overview'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - const expectedResult = { - auditbeatSocket: 0, - filebeatCisco: 0, - filebeatNetflow: 0, - filebeatPanw: 0, - filebeatSuricata: 0, - filebeatZeek: 0, - packetbeatDNS: 44, - packetbeatFlow: 588, - packetbeatTLS: 0, - }; - - it('Make sure that we get OverviewNetwork data', async () => { - const { overviewNetwork } = await secureBsearch.send<NetworkOverviewStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['packetbeat-*'], - factoryQueryType: NetworkQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewNetwork).to.eql(expectedResult); - }); - }); - - describe('With auditbeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/overview'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/overview'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - const FROM = '2000-01-01T00:00:00.000Z'; - const TO = '3000-01-01T00:00:00.000Z'; - const expectedResult = { - auditbeatSocket: 45, - filebeatCisco: 0, - filebeatNetflow: 0, - filebeatPanw: 0, - filebeatSuricata: 0, - filebeatZeek: 0, - packetbeatDNS: 0, - packetbeatFlow: 0, - packetbeatTLS: 0, - }; - - it('Make sure that we get OverviewNetwork data', async () => { - const { overviewNetwork } = await secureBsearch.send<NetworkOverviewStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - defaultIndex: ['auditbeat-*'], - factoryQueryType: NetworkQueries.overview, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(overviewNetwork).to.eql(expectedResult); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/index.ts new file mode 100644 index 0000000000000..583f366adeba3 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/index.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 { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { + describe('@ess @serverless SecuritySolution Explore Overview', () => { + loadTestFile(require.resolve('./overview_host')); + loadTestFile(require.resolve('./overview_network')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_host.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_host.ts new file mode 100644 index 0000000000000..d99fbd296ba3e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_host.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 expect from '@kbn/expect'; + +import { + HostsQueries, + HostsOverviewStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Overview Host', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('With auditbeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/overview'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/overview') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + const expectedResult = { + auditbeatAuditd: 2194, + auditbeatFIM: 4, + auditbeatLogin: 2810, + auditbeatPackage: 3, + auditbeatProcess: 7, + auditbeatUser: 6, + endgameDns: 1, + endgameFile: 2, + endgameImageLoad: 1, + endgameNetwork: 4, + endgameProcess: 2, + endgameRegistry: 1, + endgameSecurity: 4, + filebeatSystemModule: 0, + winlogbeatSecurity: 0, + winlogbeatMWSysmonOperational: 0, + }; + + it('Make sure that we get OverviewHost data', async () => { + const { overviewHost } = await bsearch.send<HostsOverviewStrategyResponse>({ + supertest, + options: { + defaultIndex: ['auditbeat-*'], + factoryQueryType: HostsQueries.overview, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(overviewHost).to.eql(expectedResult); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_network.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_network.ts new file mode 100644 index 0000000000000..952e3eed8f8af --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_network.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 expect from '@kbn/expect'; +import { + NetworkOverviewStrategyResponse, + NetworkQueries, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Overview Network', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('With filebeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + + const expectedResult = { + auditbeatSocket: 0, + filebeatCisco: 0, + filebeatNetflow: 1273, + filebeatPanw: 0, + filebeatSuricata: 4547, + filebeatZeek: 0, + packetbeatDNS: 0, + packetbeatFlow: 0, + packetbeatTLS: 0, + }; + + it('Make sure that we get OverviewNetwork data', async () => { + const { overviewNetwork } = await bsearch.send<NetworkOverviewStrategyResponse>({ + supertest, + options: { + defaultIndex: ['filebeat-*'], + factoryQueryType: NetworkQueries.overview, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(overviewNetwork).to.eql(expectedResult); + }); + }); + + describe('With packetbeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/packetbeat/overview'); + }); + after( + async () => + await esArchiver.unload('x-pack/test/functional/es_archives/packetbeat/overview') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + const expectedResult = { + auditbeatSocket: 0, + filebeatCisco: 0, + filebeatNetflow: 0, + filebeatPanw: 0, + filebeatSuricata: 0, + filebeatZeek: 0, + packetbeatDNS: 44, + packetbeatFlow: 588, + packetbeatTLS: 0, + }; + + it('Make sure that we get OverviewNetwork data', async () => { + const { overviewNetwork } = await bsearch.send<NetworkOverviewStrategyResponse>({ + supertest, + options: { + defaultIndex: ['packetbeat-*'], + factoryQueryType: NetworkQueries.overview, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(overviewNetwork).to.eql(expectedResult); + }); + }); + + describe('With auditbeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/overview'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/overview') + ); + + const FROM = '2000-01-01T00:00:00.000Z'; + const TO = '3000-01-01T00:00:00.000Z'; + const expectedResult = { + auditbeatSocket: 45, + filebeatCisco: 0, + filebeatNetflow: 0, + filebeatPanw: 0, + filebeatSuricata: 0, + filebeatZeek: 0, + packetbeatDNS: 0, + packetbeatFlow: 0, + packetbeatTLS: 0, + }; + + it('Make sure that we get OverviewNetwork data', async () => { + const { overviewNetwork } = await bsearch.send<NetworkOverviewStrategyResponse>({ + supertest, + options: { + defaultIndex: ['auditbeat-*'], + factoryQueryType: NetworkQueries.overview, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(overviewNetwork).to.eql(expectedResult); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/ess.config.ts index fc5c840bfc2af..c7fd559a7f1dd 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/ess.config.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...functionalConfig.getAll(), - testFiles: [require.resolve('../ess')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Users Integration Tests - ESS Env - Trial License', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/serverless.config.ts index 18415c5fbbbc2..27d6c1fb70567 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/configs/serverless.config.ts @@ -16,7 +16,7 @@ export default createTestConfig({ { product_line: 'cloud', product_tier: 'complete' }, ])}`, ], - testFiles: [require.resolve('../serverless')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Explore - Users Integration Tests - Serverless Env - Complete Tier', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/authentications.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/authentications.ts deleted file mode 100644 index 95c9bf4d6a037..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/authentications.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. - */ - -import expect from '@kbn/expect'; -import { - AuthStackByField, - Direction, - UserAuthenticationsStrategyResponse, - UsersQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import type { UserAuthenticationsRequestOptions } from '@kbn/security-solution-plugin/common/api/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; - -// typical values that have to change after an update from "scripts/es_archiver" -const HOST_NAME = 'zeek-newyork-sha-aa8df15'; -const LAST_SUCCESS_SOURCE_IP = '8.42.77.171'; -const TOTAL_COUNT = 3; -const EDGE_LENGTH = 1; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('authentications', () => { - before(async () => await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts')); - - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts') - ); - - it('Make sure that we get Authentication data', async () => { - const requestOptions: UserAuthenticationsRequestOptions = { - factoryQueryType: UsersQueries.authentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - defaultIndex: ['auditbeat-*'], - stackByField: AuthStackByField.userName, - sort: { field: 'timestamp', direction: Direction.asc }, - filterQuery: '', - }; - - const authentications = await bsearch.send<UserAuthenticationsStrategyResponse>({ - supertest, - options: requestOptions, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(authentications.edges.length).to.be(EDGE_LENGTH); - expect(authentications.totalCount).to.be(TOTAL_COUNT); - expect(authentications.pageInfo.fakeTotalCount).to.equal(3); - }); - - it('Make sure that pagination is working in Authentications query', async () => { - const requestOptions: UserAuthenticationsRequestOptions = { - factoryQueryType: UsersQueries.authentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 2, - cursorStart: 1, - fakePossibleCount: 5, - querySize: 2, - }, - defaultIndex: ['auditbeat-*'], - stackByField: AuthStackByField.userName, - sort: { field: 'timestamp', direction: Direction.asc }, - filterQuery: '', - }; - - const authentications = await bsearch.send<UserAuthenticationsStrategyResponse>({ - supertest, - options: requestOptions, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(authentications.edges.length).to.be(EDGE_LENGTH); - expect(authentications.totalCount).to.be(TOTAL_COUNT); - expect(authentications.edges[0].node.lastSuccess?.source?.ip).to.eql([ - LAST_SUCCESS_SOURCE_IP, - ]); - expect(authentications.edges[0].node.lastSuccess?.host?.name).to.eql([HOST_NAME]); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/users.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/users.ts deleted file mode 100644 index c204ea64bd1c2..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/users.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 expect from '@kbn/expect'; -import { - NetworkQueries, - Direction, - NetworkUsersFields, - FlowTarget, - NetworkUsersStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; -const IP = '0.0.0.0'; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - describe('Users', () => { - describe('With auditbeat', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/users') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/users') - ); - - it('Ensure data is returned from auditbeat', async () => { - const users = await bsearch.send<NetworkUsersStrategyResponse>({ - supertest, - options: { - factoryQueryType: NetworkQueries.users, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-users'], - ip: IP, - flowTarget: FlowTarget.destination, - sort: { field: NetworkUsersFields.name, direction: Direction.asc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(users.edges.length).to.be(1); - expect(users.totalCount).to.be(1); - expect(users.edges[0].node.user?.id).to.eql(['0']); - expect(users.edges[0].node.user?.name).to.be('root'); - expect(users.edges[0].node.user?.groupId).to.eql(['0']); - expect(users.edges[0].node.user?.groupName).to.eql(['root']); - expect(users.edges[0].node.user?.count).to.be(1); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/authentications.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/authentications.ts deleted file mode 100644 index f54b976583fca..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/authentications.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { - AuthStackByField, - Direction, - UserAuthenticationsStrategyResponse, - UsersQueries, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import type { UserAuthenticationsRequestOptions } from '@kbn/security-solution-plugin/common/api/search_strategy'; - -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; - -// typical values that have to change after an update from "scripts/es_archiver" -const HOST_NAME = 'zeek-newyork-sha-aa8df15'; -const LAST_SUCCESS_SOURCE_IP = '8.42.77.171'; -const TOTAL_COUNT = 3; -const EDGE_LENGTH = 1; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - describe('authentications', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Make sure that we get Authentication data', async () => { - const requestOptions: UserAuthenticationsRequestOptions = { - factoryQueryType: UsersQueries.authentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 3, - querySize: 1, - }, - defaultIndex: ['auditbeat-*'], - stackByField: AuthStackByField.userName, - sort: { field: 'timestamp', direction: Direction.asc }, - filterQuery: '', - }; - - const authentications = await secureBsearch.send<UserAuthenticationsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: requestOptions, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(authentications.edges.length).to.be(EDGE_LENGTH); - expect(authentications.totalCount).to.be(TOTAL_COUNT); - expect(authentications.pageInfo.fakeTotalCount).to.equal(3); - }); - - it('Make sure that pagination is working in Authentications query', async () => { - const requestOptions: UserAuthenticationsRequestOptions = { - factoryQueryType: UsersQueries.authentications, - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - pagination: { - activePage: 2, - cursorStart: 1, - fakePossibleCount: 5, - querySize: 2, - }, - defaultIndex: ['auditbeat-*'], - stackByField: AuthStackByField.userName, - sort: { field: 'timestamp', direction: Direction.asc }, - filterQuery: '', - }; - - const authentications = await secureBsearch.send<UserAuthenticationsStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: requestOptions, - strategy: 'securitySolutionSearchStrategy', - }); - - expect(authentications.edges.length).to.be(EDGE_LENGTH); - expect(authentications.totalCount).to.be(TOTAL_COUNT); - expect(authentications.edges[0].node.lastSuccess?.source?.ip).to.eql([ - LAST_SUCCESS_SOURCE_IP, - ]); - expect(authentications.edges[0].node.lastSuccess?.host?.name).to.eql([HOST_NAME]); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/index.ts deleted file mode 100644 index aabf2a0fd3022..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/index.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. - */ - -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('@serverless SecuritySolution Explore Users', () => { - loadTestFile(require.resolve('./authentications')); - loadTestFile(require.resolve('./users')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/users.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/users.ts deleted file mode 100644 index 167ac1dbddae2..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/serverless/users.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. - */ - -import expect from '@kbn/expect'; -import { - NetworkQueries, - Direction, - NetworkUsersFields, - FlowTarget, - NetworkUsersStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -const FROM = '2000-01-01T00:00:00.000Z'; -const TO = '3000-01-01T00:00:00.000Z'; -const IP = '0.0.0.0'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - - describe('Users', () => { - describe('With auditbeat', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/users'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/users'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Ensure data is returned from auditbeat', async () => { - const users = await secureBsearch.send<NetworkUsersStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: NetworkQueries.users, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-users'], - ip: IP, - flowTarget: FlowTarget.destination, - sort: { field: NetworkUsersFields.name, direction: Direction.asc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - inspect: false, - }, - strategy: 'securitySolutionSearchStrategy', - }); - expect(users.edges.length).to.be(1); - expect(users.totalCount).to.be(1); - expect(users.edges[0].node.user?.id).to.eql(['0']); - expect(users.edges[0].node.user?.name).to.be('root'); - expect(users.edges[0].node.user?.groupId).to.eql(['0']); - expect(users.edges[0].node.user?.groupName).to.eql(['root']); - expect(users.edges[0].node.user?.count).to.be(1); - }); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/authentications.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/authentications.ts new file mode 100644 index 0000000000000..d7329a597e2e0 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/authentications.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { + AuthStackByField, + Direction, + UserAuthenticationsStrategyResponse, + UsersQueries, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import type { UserAuthenticationsRequestOptions } from '@kbn/security-solution-plugin/common/api/search_strategy'; +import TestAgent from 'supertest/lib/agent'; + +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +const FROM = '2000-01-01T00:00:00.000Z'; +const TO = '3000-01-01T00:00:00.000Z'; + +// typical values that have to change after an update from "scripts/es_archiver" +const HOST_NAME = 'zeek-newyork-sha-aa8df15'; +const LAST_SUCCESS_SOURCE_IP = '8.42.77.171'; +const TOTAL_COUNT = 3; +const EDGE_LENGTH = 1; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('authentications', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts') + ); + + it('Make sure that we get Authentication data', async () => { + const requestOptions: UserAuthenticationsRequestOptions = { + factoryQueryType: UsersQueries.authentications, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 3, + querySize: 1, + }, + defaultIndex: ['auditbeat-*'], + stackByField: AuthStackByField.userName, + sort: { field: 'timestamp', direction: Direction.asc }, + filterQuery: '', + }; + + const authentications = await bsearch.send<UserAuthenticationsStrategyResponse>({ + supertest, + options: requestOptions, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(authentications.edges.length).to.be(EDGE_LENGTH); + expect(authentications.totalCount).to.be(TOTAL_COUNT); + expect(authentications.pageInfo.fakeTotalCount).to.equal(3); + }); + + it('Make sure that pagination is working in Authentications query', async () => { + const requestOptions: UserAuthenticationsRequestOptions = { + factoryQueryType: UsersQueries.authentications, + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + pagination: { + activePage: 2, + cursorStart: 1, + fakePossibleCount: 5, + querySize: 2, + }, + defaultIndex: ['auditbeat-*'], + stackByField: AuthStackByField.userName, + sort: { field: 'timestamp', direction: Direction.asc }, + filterQuery: '', + }; + + const authentications = await bsearch.send<UserAuthenticationsStrategyResponse>({ + supertest, + options: requestOptions, + strategy: 'securitySolutionSearchStrategy', + }); + + expect(authentications.edges.length).to.be(EDGE_LENGTH); + expect(authentications.totalCount).to.be(TOTAL_COUNT); + expect(authentications.edges[0].node.lastSuccess?.source?.ip).to.eql([ + LAST_SUCCESS_SOURCE_IP, + ]); + expect(authentications.edges[0].node.lastSuccess?.host?.name).to.eql([HOST_NAME]); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/index.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/index.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/ess/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/index.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/users.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/users.ts new file mode 100644 index 0000000000000..65b44bf4cbc5e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/users.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 expect from '@kbn/expect'; +import { + NetworkQueries, + Direction, + NetworkUsersFields, + FlowTarget, + NetworkUsersStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; + +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +const FROM = '2000-01-01T00:00:00.000Z'; +const TO = '3000-01-01T00:00:00.000Z'; +const IP = '0.0.0.0'; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Users', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + describe('With auditbeat', () => { + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/users'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/users') + ); + + it('Ensure data is returned from auditbeat', async () => { + const users = await bsearch.send<NetworkUsersStrategyResponse>({ + supertest, + options: { + factoryQueryType: NetworkQueries.users, + sourceId: 'default', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['auditbeat-users'], + ip: IP, + flowTarget: FlowTarget.destination, + sort: { field: NetworkUsersFields.name, direction: Direction.asc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + inspect: false, + }, + strategy: 'securitySolutionSearchStrategy', + }); + expect(users.edges.length).to.be(1); + expect(users.totalCount).to.be(1); + expect(users.edges[0].node.user?.id).to.eql(['0']); + expect(users.edges[0].node.user?.name).to.be('root'); + expect(users.edges[0].node.user?.groupId).to.eql(['0']); + expect(users.edges[0].node.user?.groupName).to.eql(['root']); + expect(users.edges[0].node.user?.count).to.be(1); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts index 1f1a4bb821082..9f40373976c28 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/helpers.ts @@ -7,7 +7,7 @@ import type SuperTest from 'supertest'; import { v4 as uuidv4 } from 'uuid'; -import { TimelineType } from '@kbn/security-solution-plugin/common/api/timeline'; +import { TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline'; export const createBasicTimeline = async (supertest: SuperTest.Agent, titleToSaved: string) => await supertest @@ -35,6 +35,6 @@ export const createBasicTimelineTemplate = async ( title: titleToSaved, templateTimelineId: uuidv4(), templateTimelineVersion: 1, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts index 21270c6d36289..6895d79c5aa8e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/saved_objects/trial_license_complete_tier/timeline.ts @@ -6,7 +6,10 @@ */ import expect from '@kbn/expect'; -import { TimelineResult, TimelineType } from '@kbn/security-solution-plugin/common/api/timeline'; +import { + TimelineResult, + TimelineTypeEnum, +} from '@kbn/security-solution-plugin/common/api/timeline'; import { FtrProviderContext } from '../../../../../api_integration/ftr_provider_context'; import { createBasicTimeline } from './helpers'; @@ -207,7 +210,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: savedObjectId, templateTimelineId: null, templateTimelineVersion: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); @@ -216,7 +219,7 @@ export default function ({ getService }: FtrProviderContext) { expect(responseToTest.body.data!.persistFavorite.templateTimelineId).to.be.eql(null); expect(responseToTest.body.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineType.default + TimelineTypeEnum.default ); }); @@ -235,7 +238,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: savedObjectId, templateTimelineId: templateTimelineIdFromStore, templateTimelineVersion: templateTimelineVersionFromStore, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }); expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); expect(responseToTest.body.data!.persistFavorite.favorite.length).to.be(1); @@ -247,7 +250,7 @@ export default function ({ getService }: FtrProviderContext) { templateTimelineVersionFromStore ); expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineType.template + TimelineTypeEnum.template ); }); @@ -261,7 +264,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: savedObjectId, templateTimelineId: null, templateTimelineVersion: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); const responseToTest = await supertest @@ -271,7 +274,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: savedObjectId, templateTimelineId: null, templateTimelineVersion: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); @@ -280,7 +283,7 @@ export default function ({ getService }: FtrProviderContext) { expect(responseToTest.body.data!.persistFavorite.templateTimelineId).to.be.eql(null); expect(responseToTest.body.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineType.default + TimelineTypeEnum.default ); }); @@ -296,7 +299,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: savedObjectId, templateTimelineId: templateTimelineIdFromStore, templateTimelineVersion: templateTimelineVersionFromStore, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }); const responseToTest = await supertest @@ -306,7 +309,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: savedObjectId, templateTimelineId: templateTimelineIdFromStore, templateTimelineVersion: templateTimelineVersionFromStore, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }); expect(responseToTest.body.data!.persistFavorite.savedObjectId).to.be(savedObjectId); @@ -319,7 +322,7 @@ export default function ({ getService }: FtrProviderContext) { templateTimelineVersionFromStore ); expect(responseToTest.body.data!.persistFavorite.timelineType).to.be.eql( - TimelineType.template + TimelineTypeEnum.template ); }); @@ -331,7 +334,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: null, templateTimelineId: null, templateTimelineVersion: null, - timelineType: TimelineType.default, + timelineType: TimelineTypeEnum.default, }); expect(response.body.data!.persistFavorite.savedObjectId).to.not.be.empty(); @@ -339,7 +342,9 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.data!.persistFavorite.version).to.not.be.empty(); expect(response.body.data!.persistFavorite.templateTimelineId).to.be.eql(null); expect(response.body.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); - expect(response.body.data!.persistFavorite.timelineType).to.be.eql(TimelineType.default); + expect(response.body.data!.persistFavorite.timelineType).to.be.eql( + TimelineTypeEnum.default + ); }); it('to a timeline template without a timelineId', async () => { @@ -353,7 +358,7 @@ export default function ({ getService }: FtrProviderContext) { timelineId: null, templateTimelineId: templateTimelineIdFromStore, templateTimelineVersion: templateTimelineVersionFromStore, - timelineType: TimelineType.template, + timelineType: TimelineTypeEnum.template, }); expect(response.body.data!.persistFavorite.savedObjectId).to.not.be.empty(); @@ -365,7 +370,9 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.data!.persistFavorite.templateTimelineVersion).to.be.eql( templateTimelineVersionFromStore ); - expect(response.body.data!.persistFavorite.timelineType).to.be.eql(TimelineType.template); + expect(response.body.data!.persistFavorite.timelineType).to.be.eql( + TimelineTypeEnum.template + ); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts index cbe1173e3b244..279b9a1a2ed57 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.basic.config.ts @@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, ], }, - testFiles: [require.resolve('../ess/basic')], + testFiles: [require.resolve('../tests/basic')], junit: { reportName: 'Timeline Integration Tests - ESS Env - Basic License', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts index 5d8538c1a7ae0..ad8b3a9ddcd39 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/configs/ess.trial.config.ts @@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, ], }, - testFiles: [require.resolve('../ess/trial')], + testFiles: [require.resolve('../tests/trial')], junit: { reportName: 'Timeline Integration Tests - ESS Env - Trial License', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/events.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/events.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/events.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/import_timelines.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/import_timelines.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/import_timelines.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/import_timelines.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/index.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/index.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/install_prepackaged_timelines.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/basic/install_prepackaged_timelines.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/basic/install_prepackaged_timelines.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/trial/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/events.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/trial/events.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/events.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/trial/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/index.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/ess/trial/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/security_and_spaces/tests/trial/index.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts index 21efaf9559cf6..8fd44a2a2c5d9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/ess.config.ts @@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([])}`, ], }, - testFiles: [require.resolve('../ess')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Timeline Integration Tests - ESS Env - Trial License', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts index 2c92778eae3aa..0a2827db15d66 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/configs/serverless.config.ts @@ -16,7 +16,7 @@ export default createTestConfig({ { product_line: 'cloud', product_tier: 'complete' }, ])}`, ], - testFiles: [require.resolve('../serverless')], + testFiles: [require.resolve('../tests')], junit: { reportName: 'Timeline Integration Tests - Serverless Env - Complete Tier', }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/events.ts deleted file mode 100644 index dd89246000443..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/events.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { JsonObject } from '@kbn/utility-types'; - -import { - Direction, - TimelineEventsQueries, - TimelineEventsAllStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; -import { getFieldsToRequest, getFilterValue } from '../../../../utils'; - -const TO = '3000-01-01T00:00:00.000Z'; -const FROM = '2000-01-01T00:00:00.000Z'; -// typical values that have to change after an update from "scripts/es_archiver" -const DATA_COUNT = 7; -const HOST_NAME = 'suricata-sensor-amsterdam'; -const TOTAL_COUNT = 96; -const EDGE_LENGTH = 25; -const ACTIVE_PAGE = 0; -const PAGE_SIZE = 25; -const LIMITED_PAGE_SIZE = 2; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const bsearch = getService('bsearch'); - const supertest = getService('supertest'); - - const getPostBody = (): JsonObject => ({ - defaultIndex: ['auditbeat-*'], - factoryQueryType: TimelineEventsQueries.all, - entityType: 'events', - fieldRequested: getFieldsToRequest(), - fields: [], - filterQuery: getFilterValue(HOST_NAME, FROM, TO), - pagination: { - activePage: 0, - querySize: 25, - }, - language: 'kuery', - sort: [ - { - field: '@timestamp', - direction: Direction.desc, - esTypes: ['date'], - }, - ], - timerange: { - from: FROM, - to: TO, - interval: '12h', - }, - }); - - describe('Timeline', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - it('returns Timeline data', async () => { - const timeline = await bsearch.send<TimelineEventsAllStrategyResponse>({ - supertest, - options: { - ...getPostBody(), - }, - strategy: 'timelineSearchStrategy', - }); - expect(timeline.edges.length).to.be(EDGE_LENGTH); - expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); - expect(timeline.totalCount).to.be(TOTAL_COUNT); - expect(timeline.pageInfo.activePage).to.equal(ACTIVE_PAGE); - expect(timeline.pageInfo.querySize).to.equal(PAGE_SIZE); - }); - - it('returns paginated Timeline query', async () => { - const timeline = await bsearch.send<TimelineEventsAllStrategyResponse>({ - supertest, - options: { - ...getPostBody(), - pagination: { - activePage: 0, - querySize: LIMITED_PAGE_SIZE, - }, - }, - strategy: 'timelineSearchStrategy', - }); - expect(timeline.edges.length).to.be(LIMITED_PAGE_SIZE); - expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); - expect(timeline.totalCount).to.be(TOTAL_COUNT); - expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); - expect(timeline.edges[0]!.node.ecs.host!.name).to.eql([HOST_NAME]); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/index.ts deleted file mode 100644 index 7cd50e294c1c6..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - -export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { - describe('@ess SecuritySolution Timeline', () => { - loadTestFile(require.resolve('./events')); - loadTestFile(require.resolve('./timeline_details')); - loadTestFile(require.resolve('./timeline')); - loadTestFile(require.resolve('./timeline_migrations')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline_details.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline_details.ts deleted file mode 100644 index cd918c8909441..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline_details.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { sortBy } from 'lodash'; -import { - TimelineEventsQueries, - TimelineEventsDetailsStrategyResponse, - TimelineKpiStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; -import { timelineDetailsFilebeatExpectedResults as EXPECTED_DATA } from '../mocks/timeline_details'; - -// typical values that have to change after an update from "scripts/es_archiver" -const INDEX_NAME = 'filebeat-7.0.0-iot-2019.06'; -const ID = 'QRhG1WgBqd-n62SwZYDT'; - -const EXPECTED_KPI_COUNTS = { - destinationIpCount: 154, - hostCount: 1, - processCount: 0, - sourceIpCount: 121, - userCount: 0, -}; - -export default function ({ getService }: FtrProviderContextWithSpaces) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const bsearch = getService('bsearch'); - - describe('Timeline Details', () => { - before( - async () => await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default') - ); - after( - async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') - ); - - it('Make sure that we get Event Details data', async () => { - const { data: detailsData } = await bsearch.send<TimelineEventsDetailsStrategyResponse>({ - supertest, - options: { - factoryQueryType: TimelineEventsQueries.details, - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - }, - strategy: 'timelineSearchStrategy', - }); - expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field')); - }); - - it('Make sure that we get kpi data', async () => { - const { destinationIpCount, hostCount, processCount, sourceIpCount, userCount } = - await bsearch.send<TimelineKpiStrategyResponse>({ - supertest, - options: { - factoryQueryType: TimelineEventsQueries.kpi, - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - }, - strategy: 'timelineSearchStrategy', - }); - expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( - EXPECTED_KPI_COUNTS - ); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/events.ts deleted file mode 100644 index 0069a80697084..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/events.ts +++ /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 expect from '@kbn/expect'; -import { JsonObject } from '@kbn/utility-types'; - -import { - Direction, - TimelineEventsQueries, - TimelineEventsAllStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { getFieldsToRequest, getFilterValue } from '../../../../utils'; - -const TO = '3000-01-01T00:00:00.000Z'; -const FROM = '2000-01-01T00:00:00.000Z'; -// typical values that have to change after an update from "scripts/es_archiver" -const DATA_COUNT = 7; -const HOST_NAME = 'suricata-sensor-amsterdam'; -const TOTAL_COUNT = 96; -const EDGE_LENGTH = 25; -const ACTIVE_PAGE = 0; -const PAGE_SIZE = 25; -const LIMITED_PAGE_SIZE = 2; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - const getPostBody = (): JsonObject => ({ - defaultIndex: ['auditbeat-*'], - factoryQueryType: TimelineEventsQueries.all, - entityType: 'events', - fieldRequested: getFieldsToRequest(), - fields: [], - filterQuery: getFilterValue(HOST_NAME, FROM, TO), - pagination: { - activePage: 0, - querySize: 25, - }, - language: 'kuery', - sort: [ - { - field: '@timestamp', - direction: Direction.desc, - esTypes: ['date'], - }, - ], - timerange: { - from: FROM, - to: TO, - interval: '12h', - }, - }); - - describe('Timeline', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('returns Timeline data', async () => { - const timeline = await secureBsearch.send<TimelineEventsAllStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - ...getPostBody(), - }, - strategy: 'timelineSearchStrategy', - }); - - expect(timeline.edges.length).to.be(EDGE_LENGTH); - expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); - expect(timeline.totalCount).to.be(TOTAL_COUNT); - expect(timeline.pageInfo.activePage).to.equal(ACTIVE_PAGE); - expect(timeline.pageInfo.querySize).to.equal(PAGE_SIZE); - }); - - it('returns paginated Timeline query', async () => { - const timeline = await secureBsearch.send<TimelineEventsAllStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - ...getPostBody(), - pagination: { - activePage: 0, - querySize: LIMITED_PAGE_SIZE, - }, - }, - strategy: 'timelineSearchStrategy', - }); - expect(timeline.edges.length).to.be(LIMITED_PAGE_SIZE); - expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); - expect(timeline.totalCount).to.be(TOTAL_COUNT); - expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); - expect(timeline.edges[0]!.node.ecs.host!.name).to.eql([HOST_NAME]); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/index.ts deleted file mode 100644 index a914f99212376..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/index.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. - */ - -import { FtrProviderContext } from '../../../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('@serverless SecuritySolution Timeline', () => { - loadTestFile(require.resolve('./events')); - loadTestFile(require.resolve('./timeline_details')); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/timeline_details.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/timeline_details.ts deleted file mode 100644 index b1ee8c5daef37..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/serverless/timeline_details.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { sortBy } from 'lodash'; -import { - TimelineEventsQueries, - TimelineEventsDetailsStrategyResponse, - TimelineKpiStrategyResponse, -} from '@kbn/security-solution-plugin/common/search_strategy'; - -import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import { FtrProviderContext } from '../../../../../ftr_provider_context'; -import { timelineDetailsFilebeatExpectedResults as EXPECTED_DATA } from '../mocks/timeline_details'; - -// typical values that have to change after an update from "scripts/es_archiver" -const INDEX_NAME = 'filebeat-7.0.0-iot-2019.06'; -const ID = 'QRhG1WgBqd-n62SwZYDT'; - -const EXPECTED_KPI_COUNTS = { - destinationIpCount: 154, - hostCount: 1, - processCount: 0, - sourceIpCount: 121, - userCount: 0, -}; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const secureBsearch = getService('secureBsearch'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc: RoleCredentials; - - describe('Timeline Details', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default'); - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - - it('Make sure that we get Event Details data', async () => { - const { data: detailsData } = await secureBsearch.send<TimelineEventsDetailsStrategyResponse>( - { - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: TimelineEventsQueries.details, - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - }, - strategy: 'timelineSearchStrategy', - } - ); - expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field')); - }); - - it('Make sure that we get kpi data', async () => { - const { destinationIpCount, hostCount, processCount, sourceIpCount, userCount } = - await secureBsearch.send<TimelineKpiStrategyResponse>({ - supertestWithoutAuth, - apiKeyHeader: roleAuthc.apiKeyHeader, - internalOrigin: 'Kibana', - options: { - factoryQueryType: TimelineEventsQueries.kpi, - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - }, - strategy: 'timelineSearchStrategy', - }); - expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( - EXPECTED_KPI_COUNTS - ); - }); - }); -} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts new file mode 100644 index 0000000000000..c66978bbe1b42 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.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 expect from '@kbn/expect'; +import { JsonObject } from '@kbn/utility-types'; + +import { + Direction, + TimelineEventsQueries, + TimelineEventsAllStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; +import { getFieldsToRequest, getFilterValue } from '../../../../utils'; + +const TO = '3000-01-01T00:00:00.000Z'; +const FROM = '2000-01-01T00:00:00.000Z'; +// typical values that have to change after an update from "scripts/es_archiver" +const DATA_COUNT = 7; +const HOST_NAME = 'suricata-sensor-amsterdam'; +const TOTAL_COUNT = 96; +const EDGE_LENGTH = 25; +const ACTIVE_PAGE = 0; +const PAGE_SIZE = 25; +const LIMITED_PAGE_SIZE = 2; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + const getPostBody = (): JsonObject => ({ + defaultIndex: ['auditbeat-*'], + factoryQueryType: TimelineEventsQueries.all, + entityType: 'events', + fieldRequested: getFieldsToRequest(), + fields: [], + filterQuery: getFilterValue(HOST_NAME, FROM, TO), + pagination: { + activePage: 0, + querySize: 25, + }, + language: 'kuery', + sort: [ + { + field: '@timestamp', + direction: Direction.desc, + esTypes: ['date'], + }, + ], + timerange: { + from: FROM, + to: TO, + interval: '12h', + }, + }); + + describe('Timeline', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + it('returns Timeline data', async () => { + const timeline = await bsearch.send<TimelineEventsAllStrategyResponse>({ + supertest, + options: { + ...getPostBody(), + }, + strategy: 'timelineSearchStrategy', + }); + expect(timeline.edges.length).to.be(EDGE_LENGTH); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.totalCount).to.be(TOTAL_COUNT); + expect(timeline.pageInfo.activePage).to.equal(ACTIVE_PAGE); + expect(timeline.pageInfo.querySize).to.equal(PAGE_SIZE); + }); + + it('returns paginated Timeline query', async () => { + const timeline = await bsearch.send<TimelineEventsAllStrategyResponse>({ + supertest, + options: { + ...getPostBody(), + pagination: { + activePage: 0, + querySize: LIMITED_PAGE_SIZE, + }, + }, + strategy: 'timelineSearchStrategy', + }); + expect(timeline.edges.length).to.be(LIMITED_PAGE_SIZE); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.totalCount).to.be(TOTAL_COUNT); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.edges[0]!.node.ecs.host!.name).to.eql([HOST_NAME]); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/index.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/index.ts new file mode 100644 index 0000000000000..ebf592d01282b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/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 { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; + +export default function ({ loadTestFile }: FtrProviderContextWithSpaces) { + // Failed in serverless: https://github.com/elastic/kibana/issues/183645 + describe('@ess @serverless @skipInServerless SecuritySolution Timeline', () => { + loadTestFile(require.resolve('./events')); + loadTestFile(require.resolve('./timeline_details')); + loadTestFile(require.resolve('./timeline')); + loadTestFile(require.resolve('./timeline_migrations')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline.ts similarity index 95% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline.ts index 80eefe0b5f6b5..8a37ad6e9cac9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { SavedTimeline, TimelineType } from '@kbn/security-solution-plugin/common/api/timeline'; +import { SavedTimeline, TimelineTypeEnum } from '@kbn/security-solution-plugin/common/api/timeline'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; import { @@ -55,7 +55,9 @@ export default function ({ getService }: FtrProviderContextWithSpaces) { const templates: SavedTimeline[] = resp.body.timeline; expect(templates.length).to.greaterThan(0); - expect(templates.filter((t) => t.timelineType === TimelineType.default).length).to.equal(0); + expect( + templates.filter((t) => t.timelineType === TimelineTypeEnum.default).length + ).to.equal(0); }); }); describe('resolve timeline', () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts new file mode 100644 index 0000000000000..1e3119260455d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { sortBy } from 'lodash'; +import { + TimelineEventsQueries, + TimelineEventsDetailsStrategyResponse, + TimelineKpiStrategyResponse, +} from '@kbn/security-solution-plugin/common/search_strategy'; +import TestAgent from 'supertest/lib/agent'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; +import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; +import { timelineDetailsFilebeatExpectedResults as EXPECTED_DATA } from '../mocks/timeline_details'; + +// typical values that have to change after an update from "scripts/es_archiver" +const INDEX_NAME = 'filebeat-7.0.0-iot-2019.06'; +const ID = 'QRhG1WgBqd-n62SwZYDT'; + +const EXPECTED_KPI_COUNTS = { + destinationIpCount: 154, + hostCount: 1, + processCount: 0, + sourceIpCount: 121, + userCount: 0, +}; + +export default function ({ getService }: FtrProviderContextWithSpaces) { + const esArchiver = getService('esArchiver'); + const utils = getService('securitySolutionUtils'); + + describe('Timeline Details', () => { + let supertest: TestAgent; + let bsearch: BsearchService; + before(async () => { + supertest = await utils.createSuperTest(); + bsearch = await utils.createBsearch(); + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'); + }); + after( + async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default') + ); + + it('Make sure that we get Event Details data', async () => { + const { data: detailsData } = await bsearch.send<TimelineEventsDetailsStrategyResponse>({ + supertest, + options: { + factoryQueryType: TimelineEventsQueries.details, + indexName: INDEX_NAME, + inspect: false, + eventId: ID, + }, + strategy: 'timelineSearchStrategy', + }); + expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field')); + }); + + it('Make sure that we get kpi data', async () => { + const { destinationIpCount, hostCount, processCount, sourceIpCount, userCount } = + await bsearch.send<TimelineKpiStrategyResponse>({ + supertest, + options: { + factoryQueryType: TimelineEventsQueries.kpi, + indexName: INDEX_NAME, + inspect: false, + eventId: ID, + }, + strategy: 'timelineSearchStrategy', + }); + expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( + EXPECTED_KPI_COUNTS + ); + }); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_migrations.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/ess/timeline_migrations.ts rename to x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_migrations.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/create_exception_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/create_exception_list_items.ts index b5a4dc3449eb1..b9a602c9fbaa3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/create_exception_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/create_exception_list_items.ts @@ -28,7 +28,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless create_exception_list_items', () => { + describe('@ess @serverless @serverlessQA create_exception_list_items', () => { describe('validation errors', () => { it('should give a 404 error that the exception list must exist first before being able to add a list item to the exception list', async () => { const { body } = await supertest diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/delete_exception_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/delete_exception_list_items.ts index 89ff41806c97f..aa172f44bbbe6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/delete_exception_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/delete_exception_list_items.ts @@ -28,7 +28,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless delete_exception_list_items', () => { + describe('@ess @serverless @serverlessQA delete_exception_list_items', () => { describe('delete exception list items', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/find_exception_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/find_exception_list_items.ts index bd9068d989c19..3753c63ff7693 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/find_exception_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/find_exception_list_items.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext): void => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless find_exception_list_items', () => { + describe('@ess @serverless @serverlessQA find_exception_list_items', () => { describe('find exception list items', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/read_exception_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/read_exception_list_items.ts index d1689dcaf04b7..811796dbc5e2f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/read_exception_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/read_exception_list_items.ts @@ -27,7 +27,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless read_exception_list_items', () => { + describe('@ess @serverless @serverlessQA read_exception_list_items', () => { describe('reading exception list items', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/update_exception_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/update_exception_list_items.ts index 46766b1ace80f..9eae3406c7aa4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/update_exception_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/items/update_exception_list_items.ts @@ -32,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless update_exception_list_items', () => { + describe('@ess @serverless @serverlessQA update_exception_list_items', () => { describe('update exception list items', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/create_exception_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/create_exception_lists.ts index 0a1ecfa0b0d0a..3c1c32e9046a4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/create_exception_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/create_exception_lists.ts @@ -23,7 +23,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless create_exception_lists', () => { + describe('@ess @serverless @serverlessQA create_exception_lists', () => { describe('creating exception lists', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/delete_exception_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/delete_exception_lists.ts index 98b387b1dc9bb..480a8acb638ae 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/delete_exception_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/delete_exception_lists.ts @@ -24,7 +24,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless delete_exception_lists', () => { + describe('@ess @serverless @serverlessQA delete_exception_lists', () => { describe('delete exception lists', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts index 927cf87b93d5a..6501ac284b7ff 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/duplicate_exception_list.ts @@ -25,7 +25,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless duplicate_exception_lists', () => { + describe('@ess @serverless @serverlessQA duplicate_exception_lists', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/export_exception_list.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/export_exception_list.ts index 726ec6269508f..f405dd38bfa96 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/export_exception_list.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/export_exception_list.ts @@ -23,7 +23,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); - describe('@ess @serverless export_exception_list_route', () => { + describe('@ess @serverless @serverlessQA export_exception_list_route', () => { describe('exporting exception lists', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/find_exception_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/find_exception_lists.ts index 31ece2f7a1a58..37de79ed5ee1a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/find_exception_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/find_exception_lists.ts @@ -17,7 +17,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); - describe('@ess @serverless find_exception_lists', () => { + describe('@ess @serverless @serverlessQA find_exception_lists', () => { describe('find exception lists', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/get_exception_filter.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/get_exception_filter.ts index 633262ca1baa4..3c5c3ed813e65 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/get_exception_filter.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/get_exception_filter.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); - describe('@ess @serverless get_exception_filter', () => { + describe('@ess @serverless @serverlessQA get_exception_filter', () => { describe('get exception filter', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/import_exceptions.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/import_exceptions.ts index bc191618914d2..00e4da4f25dd6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/import_exceptions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/import_exceptions.ts @@ -22,7 +22,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); - describe('@ess @serverless import_exceptions', () => { + describe('@ess @serverless @serverlessQA import_exceptions', () => { beforeEach(async () => { await deleteAllExceptions(supertest, log); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/read_exception_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/read_exception_lists.ts index 89239b1a93c25..0d23afde9734b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/read_exception_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/read_exception_lists.ts @@ -24,7 +24,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless read_exception_lists', () => { + describe('@ess @serverless @serverlessQA read_exception_lists', () => { describe('reading exception lists', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/summary_exception_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/summary_exception_lists.ts index 2c0c9780714f7..07562ddc32d9f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/summary_exception_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/summary_exception_lists.ts @@ -23,7 +23,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); - describe('@ess @serverless summary_exception_lists', () => { + describe('@ess @serverless @serverlessQA summary_exception_lists', () => { describe('summary exception lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/update_exception_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/update_exception_lists.ts index f399bcde87426..b52f62a25a156 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/update_exception_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/exception_lists_items/trial_license_complete_tier/lists/update_exception_lists.ts @@ -25,7 +25,7 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless update_exception_lists', () => { + describe('@ess @serverless @serverlessQA update_exception_lists', () => { describe('update exception lists', () => { afterEach(async () => { await deleteAllExceptions(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/create_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/create_list_items.ts index 9ab6eb9361148..989e2521d3f27 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/create_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/create_list_items.ts @@ -29,11 +29,11 @@ export default ({ getService }: FtrProviderContext) => { const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless create_list_items', () => { + describe('@ess @serverless @serverlessQA create_list_items', () => { let supertest: TestAgent; before(async () => { - supertest = await utils.createSuperTest('admin'); + supertest = await utils.createSuperTest(); }); describe('validation errors', () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/delete_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/delete_list_items.ts index 1f224e2e2db74..6068845d37929 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/delete_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/delete_list_items.ts @@ -12,6 +12,7 @@ import { getListItemResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-pl import { getCreateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_item_schema.mock'; import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -20,11 +21,16 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless delete_list_items', () => { + describe('@ess @serverless @serverlessQA delete_list_items', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('deleting list items', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/export_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/export_list_items.ts index b9c99a49af931..1fc6116b915e6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/export_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/export_list_items.ts @@ -12,15 +12,22 @@ import { getCreateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/sch import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { LIST_ID, NAME } from '@kbn/lists-plugin/common/constants.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, binaryToString } from '../../../utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + describe('@ess @serverless @serverlessQA export_list_items', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); - describe('@ess @serverless export_list_items', () => { describe('exporting lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts index d7817ea86cd23..8779ef9915eb4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts @@ -12,15 +12,22 @@ import { LIST_ITEM_ID, LIST_ID } from '@kbn/lists-plugin/common/constants.mock'; import { getCreateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_item_schema.mock'; import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex } from '../../../utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + describe('@ess @serverless @serverlessQA find_list_items', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); - describe('@ess @serverless find_list_items', () => { describe('find list items', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/import_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/import_list_items.ts index 3e2949568d7b9..4ea906eeb2502 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/import_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/import_list_items.ts @@ -12,6 +12,7 @@ import { getListItemResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-pl import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_schema.mock'; import { getImportListItemAsBuffer } from '@kbn/lists-plugin/common/schemas/request/import_list_item_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -22,11 +23,16 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless import_list_items', () => { + describe('@ess @serverless @serverlessQA import_list_items', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('importing list items without an index', () => { it('should not import a list item if the index does not exist yet', async () => { const { body } = await supertest diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/patch_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/patch_list_items.ts index ac25a46b458b8..701f437518130 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/patch_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/patch_list_items.ts @@ -18,6 +18,7 @@ import { getCreateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/sch import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getUpdateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_list_item_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -26,12 +27,17 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const retry = getService('retry'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless patch_list_items', () => { + describe('@ess @serverless @serverlessQA patch_list_items', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('patch list items', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/read_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/read_list_items.ts index 6b57358f68057..8864951d37501 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/read_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/read_list_items.ts @@ -12,6 +12,7 @@ import { getListItemResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-pl import { getCreateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_item_schema.mock'; import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -21,11 +22,16 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless read_list_items', () => { + describe('@ess @serverless @serverlessQA read_list_items', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('reading list items', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/update_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/update_list_items.ts index 073127cebfb3b..dcb562938fcac 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/update_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/update_list_items.ts @@ -18,6 +18,7 @@ import { getCreateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/sch import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getUpdateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_list_item_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -26,12 +27,17 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const retry = getService('retry'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless update_list_items', () => { + describe('@ess @serverless @serverlessQA update_list_items', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('update list items', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists.ts index d8ee7bcb7bd8a..e3d6a377f35c2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists.ts @@ -14,6 +14,7 @@ import { } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -23,11 +24,16 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless create_lists', () => { + describe('@ess @serverless @serverlessQA create_lists', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('creating lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists_index.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists_index.ts index bb9167f4ba8a2..634894e727619 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists_index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/create_lists_index.ts @@ -9,15 +9,22 @@ import expect from '@kbn/expect'; import { LIST_INDEX } from '@kbn/securitysolution-list-constants'; +import TestAgent from 'supertest/lib/agent'; import { deleteListsIndex } from '../../../utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); + const utils = getService('securitySolutionUtils'); + + describe('@ess @serverless @serverlessQA create_list_index_route', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); - describe('@ess @serverless create_list_index_route', () => { beforeEach(async () => { await deleteListsIndex(supertest, log); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/delete_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/delete_lists.ts index bcb02e613defc..b622192754d94 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/delete_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/delete_lists.ts @@ -22,6 +22,7 @@ import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { getCreateExceptionListItemMinimalSchemaMockWithoutId } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; import { DETECTION_TYPE, LIST_ID } from '@kbn/lists-plugin/common/constants.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteAllExceptions, @@ -31,11 +32,16 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless delete_lists', () => { + describe('@ess @serverless @serverlessQA delete_lists', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('deleting lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists.ts index f28f5252e3679..4c1bc4fc93e59 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists.ts @@ -11,6 +11,7 @@ import { LIST_URL } from '@kbn/securitysolution-list-constants'; import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -20,11 +21,16 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless find_lists', () => { + describe('@ess @serverless @serverlessQA find_lists', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('find lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists_by_size.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists_by_size.ts index f4aacaa2f8b77..87552111269a2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists_by_size.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/find_lists_by_size.ts @@ -14,6 +14,7 @@ import { LIST_URL, INTERNAL_FIND_LISTS_BY_SIZE } from '@kbn/securitysolution-lis import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -23,11 +24,15 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless find_lists_by_size', () => { + describe('@ess @serverless @serverlessQA find_lists_by_size', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); describe('find lists by size', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/patch_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/patch_lists.ts index 51d9a2a9b5dab..db6f0c73b8ba7 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/patch_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/patch_lists.ts @@ -13,6 +13,7 @@ import { LIST_URL } from '@kbn/securitysolution-list-constants'; import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_schema.mock'; import { getUpdateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -21,12 +22,17 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const retry = getService('retry'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless patch_lists', () => { + describe('@ess @serverless @serverlessQA patch_lists', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('patch lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/read_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/read_lists.ts index 6750acd2f4a5a..02b0056e351ca 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/read_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/read_lists.ts @@ -14,6 +14,7 @@ import { } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -22,11 +23,16 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless read_lists', () => { + describe('@ess @serverless @serverlessQA read_lists', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('reading lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/update_lists.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/update_lists.ts index 21d1db4ff3e94..9ebc487b32855 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/update_lists.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/lists/update_lists.ts @@ -13,6 +13,7 @@ import { LIST_URL } from '@kbn/securitysolution-list-constants'; import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; import { getListResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_schema.mock'; import { getUpdateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/update_list_schema.mock'; +import TestAgent from 'supertest/lib/agent'; import { createListsIndex, deleteListsIndex, @@ -22,12 +23,17 @@ import { import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); const log = getService('log'); const retry = getService('retry'); const utils = getService('securitySolutionUtils'); - describe('@ess @serverless update_lists', () => { + describe('@ess @serverless @serverlessQA update_lists', () => { + let supertest: TestAgent; + + before(async () => { + supertest = await utils.createSuperTest(); + }); + describe('update lists', () => { beforeEach(async () => { await createListsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index 58651125c5068..2f420920027d5 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -8,11 +8,12 @@ "**/*", "../../../typings/**/*", "../../../packages/kbn-test/types/ftr_globals/**/*", - ], + ], "exclude": [ "target/**/*" ], "kbn_references": [ + { "path": "../security_solution_endpoint/tsconfig.json" }, "@kbn/test-suites-serverless", { "path": "../../test_serverless/api_integration/**/*" }, { "path": "../../test_serverless/shared/**/*" }, @@ -46,6 +47,7 @@ "@kbn/utility-types", "@kbn/timelines-plugin", "@kbn/dev-cli-runner", + "@kbn/search-types", "@kbn/security-plugin", "@kbn/test-suites-src", ] diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/enrichments/alert_threat_enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/enrichments/alert_threat_enrichments.cy.ts index 71dd0387a6451..bf0bbea5ad6d9 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/enrichments/alert_threat_enrichments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/detection_alerts/enrichments/alert_threat_enrichments.cy.ts @@ -13,7 +13,7 @@ import { THREAT_DETAILS_VIEW, INDICATOR_MATCH_ENRICHMENT_SECTION, INVESTIGATION_TIME_ENRICHMENT_SECTION, - THREAT_DETAILS_ACCORDION, + ENRICHMENT_SECTION_BUTTON_CONTENT, } from '../../../../../screens/alerts_details'; import { TIMELINE_FIELD } from '../../../../../screens/rule_details'; import { expandFirstAlert, setEnrichmentDates } from '../../../../../tasks/alerts'; @@ -163,36 +163,39 @@ describe('Threat Match Enrichment', { tags: ['@ess', '@serverless', '@skipInServ }); it('Displays matched fields from both indicator match rules and investigation time enrichments on Threat Intel tab', () => { + expandFirstAlert(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openThreatIntelligenceTab(); + setEnrichmentDates('08/05/2018 10:00 AM'); + const indicatorMatchRuleEnrichment = { field: 'myhash.mysha256', value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3', feedName: 'AbuseCH malware', }; + cy.get(`${INDICATOR_MATCH_ENRICHMENT_SECTION}`).within(() => { + cy.get(`${ENRICHMENT_SECTION_BUTTON_CONTENT}`) + .should('exist') + .should( + 'have.text', + `${indicatorMatchRuleEnrichment.field} ${indicatorMatchRuleEnrichment.value} from ${indicatorMatchRuleEnrichment.feedName}` + ); + }); + const investigationTimeEnrichment = { field: 'source.ip', value: '192.168.1.1', feedName: 'feed_name', }; - - expandFirstAlert(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInsightsTab(); - openThreatIntelligenceTab(); - setEnrichmentDates('08/05/2018 10:00 AM'); - - cy.get(`${INDICATOR_MATCH_ENRICHMENT_SECTION} ${THREAT_DETAILS_ACCORDION}`) - .should('exist') - .should( - 'have.text', - `${indicatorMatchRuleEnrichment.field} ${indicatorMatchRuleEnrichment.value} from ${indicatorMatchRuleEnrichment.feedName}` - ); - - cy.get(`${INVESTIGATION_TIME_ENRICHMENT_SECTION} ${THREAT_DETAILS_ACCORDION}`) - .should('exist') - .should( - 'have.text', - `${investigationTimeEnrichment.field} ${investigationTimeEnrichment.value} from ${investigationTimeEnrichment.feedName}` - ); + cy.get(`${INVESTIGATION_TIME_ENRICHMENT_SECTION}`).within(() => { + cy.get(`${ENRICHMENT_SECTION_BUTTON_CONTENT}`) + .should('exist') + .should( + 'have.text', + `${investigationTimeEnrichment.field} ${investigationTimeEnrichment.value} from ${investigationTimeEnrichment.feedName}` + ); + }); }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts index b58f3719ef3f8..348133b1d2802 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/esql_rule.cy.ts @@ -28,7 +28,6 @@ import { getDetails, goBackToRulesTable } from '../../../../tasks/rule_details'; import { expectNumberOfRules } from '../../../../tasks/alerts_detection_rules'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { - expandEsqlQueryBar, fillAboutRuleAndContinue, fillDefineEsqlRuleAndContinue, fillScheduleRuleAndContinue, @@ -87,7 +86,6 @@ describe( it('creates an ES|QL rule', function () { selectEsqlRuleType(); - expandEsqlQueryBar(); fillDefineEsqlRuleAndContinue(rule); fillAboutRuleAndContinue(rule); @@ -109,7 +107,6 @@ describe( // this test case is important, since field shown in rule override component are coming from ES|QL query, not data view fields API it('creates an ES|QL rule and overrides its name', function () { selectEsqlRuleType(); - expandEsqlQueryBar(); fillDefineEsqlRuleAndContinue(rule); fillAboutSpecificEsqlRuleAndContinue({ ...rule, rule_name_override: 'test_id' }); @@ -130,7 +127,6 @@ describe( }); it('shows error when ES|QL query is empty', function () { selectEsqlRuleType(); - expandEsqlQueryBar(); getDefineContinueButton().click(); cy.get(ESQL_QUERY_BAR).contains('ES|QL query is required'); @@ -138,7 +134,6 @@ describe( it('proceeds further once invalid query is fixed', function () { selectEsqlRuleType(); - expandEsqlQueryBar(); getDefineContinueButton().click(); cy.get(ESQL_QUERY_BAR).contains('required'); @@ -153,7 +148,6 @@ describe( it('shows error when non-aggregating ES|QL query does not have metadata operator', function () { const invalidNonAggregatingQuery = 'from auditbeat* | limit 5'; selectEsqlRuleType(); - expandEsqlQueryBar(); fillEsqlQueryBar(invalidNonAggregatingQuery); getDefineContinueButton().click(); @@ -167,7 +161,6 @@ describe( 'from auditbeat* metadata _id, _version, _index | keep agent.* | limit 5'; selectEsqlRuleType(); - expandEsqlQueryBar(); fillEsqlQueryBar(invalidNonAggregatingQuery); getDefineContinueButton().click(); @@ -182,12 +175,22 @@ describe( visit(CREATE_RULE_URL); selectEsqlRuleType(); - expandEsqlQueryBar(); fillEsqlQueryBar(invalidEsqlQuery); getDefineContinueButton().click(); cy.get(ESQL_QUERY_BAR).contains('Error validating ES|QL'); }); + + it('shows syntax error when query is syntactically invalid - prioritizing it over missing metadata operator error', function () { + const invalidNonAggregatingQuery = 'from auditbeat* | limit 5 test'; + selectEsqlRuleType(); + fillEsqlQueryBar(invalidNonAggregatingQuery); + getDefineContinueButton().click(); + + cy.get(ESQL_QUERY_BAR).contains( + `Error validating ES|QL: "SyntaxError: extraneous input 'test' expecting <EOF>"` + ); + }); }); describe('ES|QL investigation fields', () => { @@ -207,7 +210,6 @@ describe( workaroundForResizeObserver(); selectEsqlRuleType(); - expandEsqlQueryBar(); fillEsqlQueryBar(queryWithCustomFields); getDefineContinueButton().click(); @@ -242,7 +244,6 @@ describe( workaroundForResizeObserver(); selectEsqlRuleType(); - expandEsqlQueryBar(); interceptEsqlQueryFieldsRequest(queryWithCustomFields, 'esqlSuppressionFieldsRequest'); fillEsqlQueryBar(queryWithCustomFields); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts index 95a7f8466156a..43655b1358b29 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/esql_rule.cy.ts @@ -30,7 +30,6 @@ import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management'; import { getDetails } from '../../../../tasks/rule_details'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { - expandEsqlQueryBar, fillEsqlQueryBar, fillOverrideEsqlRuleName, goToAboutStepTab, @@ -68,7 +67,6 @@ describe( }); it('edits ES|QL rule and checks details page', () => { - expandEsqlQueryBar(); // ensure once edit form opened, correct query is displayed in ES|QL input cy.get(ESQL_QUERY_BAR).contains(rule.query); @@ -94,7 +92,6 @@ describe( }); it('adds ES|QL override rule name on edit', () => { - expandEsqlQueryBar(); // ensure once edit form opened, correct query is displayed in ES|QL input cy.get(ESQL_QUERY_BAR).contains(rule.query); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts index c1db8eb2b44d1..509ba40e57fc3 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/alerts_charts.cy.ts @@ -6,13 +6,30 @@ */ import { getNewRule } from '../../../objects/rule'; -import { ALERTS_COUNT } from '../../../screens/alerts'; +import { + ALERT_SUMMARY_CHARTS, + SELECT_OVERVIEW_CHARTS, + SELECT_HISTOGRAM, + SELECT_COUNTS_TABLE, + SELECT_TREEMAP, + ALERT_SUMMARY_SEVERITY_DONUT_CHART, + ALERT_SUMMARY_RULES_TABLE, + ALERT_SUMMARY_PROGRESS_BAR_CHARTS, + ALERTS_HISTOGRAM, + ALERT_COUNT_TABLE, + ALERT_TREEMAP, + ALERTS_COUNT, + ALERT_SUMMARY_CHARTS_COLLAPSED, +} from '../../../screens/alerts'; import { clickAlertsHistogramLegend, clickAlertsHistogramLegendAddToTimeline, clickAlertsHistogramLegendFilterFor, clickAlertsHistogramLegendFilterOut, selectAlertsHistogram, + selectAlertsCountTable, + selectAlertsTreemap, + toggleKPICharts, } from '../../../tasks/alerts'; import { createRule } from '../../../tasks/api_calls/rules'; import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; @@ -25,45 +42,100 @@ import { } from '../../../screens/search_bar'; import { TOASTER } from '../../../screens/alerts_detection_rules'; -describe('Histogram legend hover actions', { tags: ['@ess', '@serverless'] }, () => { +describe('KPI visualizations in Alerts Page', { tags: ['@ess', '@serverless'] }, () => { const ruleConfigs = getNewRule(); - beforeEach(() => { deleteAlertsAndRules(); login(); createRule(getNewRule({ rule_id: 'new custom rule' })); visitWithTimeRange(ALERTS_URL); - selectAlertsHistogram(); }); - it('should should add a filter in to KQL bar', () => { - const expectedNumberOfAlerts = 1; - clickAlertsHistogramLegend(); - clickAlertsHistogramLegendFilterFor(ruleConfigs.name); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( - 'have.text', - `kibana.alert.rule.name: ${ruleConfigs.name}` - ); - cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alert`); - }); + context('KPI viz navigation', () => { + it('should navigate through clicking chart names', () => { + cy.log('should display summary charts by default'); + + cy.get(ALERT_SUMMARY_CHARTS).should('exist'); + cy.get(ALERT_SUMMARY_CHARTS_COLLAPSED).should('not.exist'); + cy.get(SELECT_OVERVIEW_CHARTS).should('have.class', 'euiButtonGroupButton-isSelected'); + + cy.get(ALERT_SUMMARY_SEVERITY_DONUT_CHART).should('exist'); + cy.get(ALERT_SUMMARY_RULES_TABLE).should('exist'); + cy.get(ALERT_SUMMARY_PROGRESS_BAR_CHARTS).should('exist'); + + cy.log('should display histogram charts when clicking trend button'); + + selectAlertsHistogram(); + cy.get(SELECT_HISTOGRAM).should('have.class', 'euiButtonGroupButton-isSelected'); + cy.get(ALERT_SUMMARY_CHARTS).should('not.exist'); + cy.get(ALERTS_HISTOGRAM).should('exist'); + + cy.log('should display table charts when clicking table button'); + + selectAlertsCountTable(); + cy.get(ALERT_COUNT_TABLE).should('exist'); + cy.get(SELECT_COUNTS_TABLE).should('have.class', 'euiButtonGroupButton-isSelected'); + + cy.log('should display treemap charts when clicking treemap button'); - it('should add a filter out to KQL bar', () => { - clickAlertsHistogramLegend(); - clickAlertsHistogramLegendFilterOut(ruleConfigs.name); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( - 'have.text', - `NOT kibana.alert.rule.name: ${ruleConfigs.name}` - ); - cy.get(ALERTS_COUNT).should('not.exist'); - - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_DELETE).click(); - cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('not.exist'); + selectAlertsTreemap(); + cy.get(ALERT_TREEMAP).should('exist'); + cy.get(SELECT_TREEMAP).should('have.class', 'euiButtonGroupButton-isSelected'); + }); + + it('should display/hide collapsed chart when clicking on the toggle', () => { + cy.log('should display summary charts by default'); + + cy.get(ALERT_SUMMARY_CHARTS_COLLAPSED).should('not.exist'); + cy.get(ALERT_SUMMARY_SEVERITY_DONUT_CHART).should('exist'); + + cy.log('should display collapsed summary when clicking toggle button'); + + toggleKPICharts(); + cy.get(ALERT_SUMMARY_CHARTS_COLLAPSED).should('exist'); + cy.get(ALERT_SUMMARY_SEVERITY_DONUT_CHART).should('not.exist'); + + cy.log('should display summary when clicking toggle button again'); + + toggleKPICharts(); + cy.get(ALERT_SUMMARY_CHARTS_COLLAPSED).should('not.exist'); + cy.get(ALERT_SUMMARY_SEVERITY_DONUT_CHART).should('exist'); + }); }); - it('should add To Timeline', () => { - clickAlertsHistogramLegend(); - clickAlertsHistogramLegendAddToTimeline(ruleConfigs.name); + context('Histogram legend hover actions', () => { + it('should should add a filter in to KQL bar', () => { + selectAlertsHistogram(); + const expectedNumberOfAlerts = 1; + clickAlertsHistogramLegend(); + clickAlertsHistogramLegendFilterFor(ruleConfigs.name); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( + 'have.text', + `kibana.alert.rule.name: ${ruleConfigs.name}` + ); + cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alert`); + }); + + it('should add a filter out to KQL bar', () => { + selectAlertsHistogram(); + clickAlertsHistogramLegend(); + clickAlertsHistogramLegendFilterOut(ruleConfigs.name); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should( + 'have.text', + `NOT kibana.alert.rule.name: ${ruleConfigs.name}` + ); + cy.get(ALERTS_COUNT).should('not.exist'); + + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM_DELETE).click(); + cy.get(GLOBAL_SEARCH_BAR_FILTER_ITEM).should('not.exist'); + }); + + it('should add To Timeline', () => { + selectAlertsHistogram(); + clickAlertsHistogramLegend(); + clickAlertsHistogramLegendAddToTimeline(ruleConfigs.name); - cy.get(TOASTER).should('have.text', `Added ${ruleConfigs.name} to timeline`); + cy.get(TOASTER).should('have.text', `Added ${ruleConfigs.name} to timeline`); + }); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts index fe8a63efb1102..faf268f476652 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/changing_alert_status.cy.ts @@ -26,7 +26,7 @@ import { goToOpenedAlerts, openAlerts, openFirstAlert, - selectCountTable, + selectAlertsCountTable, waitForPageFilters, sumAlertCountFromAlertCountTable, parseAlertsCountToInt, @@ -61,7 +61,7 @@ describe.skip('Changing alert status', { tags: ['@ess', '@skipInServerless'] }, closeAlerts(); waitForAlerts(); waitForPageFilters(); - selectCountTable(); + selectAlertsCountTable(); }); it.skip('Open one alert when more than one closed alerts are selected', () => { @@ -103,7 +103,7 @@ describe.skip('Changing alert status', { tags: ['@ess', '@skipInServerless'] }, goToOpenedAlerts(); waitForAlerts(); - selectCountTable(); + selectAlertsCountTable(); cy.get(ALERTS_COUNT).should( 'have.text', @@ -126,7 +126,7 @@ describe.skip('Changing alert status', { tags: ['@ess', '@skipInServerless'] }, createRule(getNewRule()); visit(ALERTS_URL); waitForAlertsToPopulate(); - selectCountTable(); + selectAlertsCountTable(); }); it('Mark one alert as acknowledged when more than one open alerts are selected', () => { cy.get(ALERTS_COUNT) @@ -167,7 +167,7 @@ describe.skip('Changing alert status', { tags: ['@ess', '@skipInServerless'] }, createRule(getNewRule({ rule_id: '1', max_signals: 100 })); visit(ALERTS_URL); waitForAlertsToPopulate(); - selectCountTable(); + selectAlertsCountTable(); }); it.skip('Closes and opens alerts', () => { const numberOfAlertsToBeClosed = 3; @@ -319,7 +319,7 @@ describe.skip('Changing alert status', { tags: ['@ess', '@skipInServerless'] }, createRule(getNewRule()); visit(ALERTS_URL); waitForAlertsToPopulate(); - selectCountTable(); + selectAlertsCountTable(); }); it('Mark one alert as acknowledged when more than one open alerts are selected', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts index aeba2d5128e2f..051f7860c8dbe 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts @@ -29,7 +29,6 @@ import { markAcknowledgedFirstAlert, openPageFilterPopover, resetFilters, - selectCountTable, selectPageFilterValue, togglePageFilterPopover, visitAlertsPageWithCustomFilters, @@ -236,7 +235,6 @@ describe.skip(`Detections : Page Filters`, { tags: ['@ess', '@serverless'] }, () }, () => { // mark status of one alert to be acknowledged - selectCountTable(); cy.get(ALERTS_COUNT) .invoke('text') .then((noOfAlerts) => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts index fbb434c4d7263..bb4010a6db4f6 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/sourcerer/sourcerer_timeline.cy.ts @@ -31,7 +31,7 @@ import { saveSourcerer, } from '../../../tasks/sourcerer'; import { openTimelineUsingToggle } from '../../../tasks/security_main'; -import { waitForFleetSetup } from '../../../tasks/fleet_integrations'; +import { waitForRulesBootstrap } from '../../../tasks/fleet_integrations'; import { SOURCERER } from '../../../screens/sourcerer'; import { createTimeline, deleteTimelines } from '../../../tasks/api_calls/timelines'; import { getTimelineModifiedSourcerer } from '../../../objects/timeline'; @@ -42,7 +42,7 @@ const dataViews = ['logs-*', 'metrics-*', '.kibana-event-log-*']; describe('Timeline scope', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { before(() => { - waitForFleetSetup(); + waitForRulesBootstrap(); }); beforeEach(() => { diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index a348cc98d12ec..35c46e9f97557 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -50,7 +50,7 @@ export const EMPTY_ALERT_TABLE = '[data-test-subj="alertsStateTableEmptyState"]' export const EXPAND_ALERT_BTN = '[data-test-subj="expand-event"]'; -export const TAKE_ACTION_BTN = '[data-test-subj="take-action-dropdown-btn"]'; +export const TAKE_ACTION_BTN = '[data-test-subj="securitySolutionFlyoutFooterDropdownButton"]'; export const TAKE_ACTION_MENU = '[data-test-subj="takeActionPanelMenu"]'; @@ -72,8 +72,6 @@ export const MESSAGE = '[data-test-subj="formatted-field-message"]'; export const SELECTED_ALERTS = '[data-test-subj="selectedShowBulkActionsButton"]'; -export const SELECT_AGGREGATION_CHART = '[data-test-subj="chart-select-table"]'; - export const SEND_ALERT_TO_TIMELINE_BTN = '[data-test-subj="send-alert-to-timeline-button"]'; export const OPEN_ANALYZER_BTN = '[data-test-subj="view-in-analyzer"]'; @@ -131,6 +129,33 @@ export const ACTIONS_EXPAND_BUTTON = '[data-test-subj="euiDataGridCellExpandButt export const SHOW_TOP_N_HEADER = '[data-test-subj="topN-container"] [data-test-subj="header-section-title"]'; +export const SESSION_VIEWER_BUTTON = '[data-test-subj="session-view-button"]'; + +export const OVERLAY_CONTAINER = '[data-test-subj="overlayContainer"]'; + +/** + * Alerts KPIs + */ + +export const ALERT_CHARTS_TOGGLE_BUTTON = getDataTestSubjectSelector('query-toggle-header'); + +export const SELECT_OVERVIEW_CHARTS = getDataTestSubjectSelector('chart-select-charts'); + +export const ALERT_SUMMARY_CHARTS = getDataTestSubjectSelector('alerts-charts-panel'); + +export const ALERT_SUMMARY_SEVERITY_DONUT_CHART = + getDataTestSubjectSelector('severity-level-donut'); + +export const ALERT_SUMMARY_RULES_TABLE = getDataTestSubjectSelector('alerts-by-rule-panel'); + +export const ALERT_SUMMARY_PROGRESS_BAR_CHARTS = getDataTestSubjectSelector( + 'alerts-progress-bar-panel' +); + +export const ALERT_SUMMARY_CHARTS_COLLAPSED = getDataTestSubjectSelector('chart-collapse'); + +export const ALERTS_HISTOGRAM = getDataTestSubjectSelector('alerts-histogram-panel'); + export const ALERTS_HISTOGRAM_LEGEND = '[data-test-subj="alerts-histogram-panel"] .echLegendItem__action'; @@ -146,12 +171,13 @@ export const LEGEND_ACTIONS = { COPY: (ruleName: string) => `[data-test-subj="legend-${ruleName}-embeddable_copyToClipboard"]`, }; -export const SESSION_VIEWER_BUTTON = '[data-test-subj="session-view-button"]'; +export const ALERT_COUNT_TABLE = getDataTestSubjectSelector('alertsCountPanel'); -export const OVERLAY_CONTAINER = '[data-test-subj="overlayContainer"]'; +export const SELECT_COUNTS_TABLE = '[data-test-subj="chart-select-table"]'; -export const ALERT_SUMMARY_SEVERITY_DONUT_CHART = - getDataTestSubjectSelector('severity-level-donut'); +export const SELECT_TREEMAP = getDataTestSubjectSelector('chart-select-treemap'); + +export const ALERT_TREEMAP = getDataTestSubjectSelector('treemapPanel'); export const ALERT_TAGGING_CONTEXT_MENU_ITEM = '[data-test-subj="alert-tags-context-menu-item"]'; @@ -225,7 +251,8 @@ export const FILTER_BY_ASSIGNEES_BUTTON = '[data-test-subj="filter-popover-butto export const ALERT_DETAILS_ASSIGN_BUTTON = '[data-test-subj="securitySolutionFlyoutHeaderAssigneesAddButton"]'; -export const ALERT_DETAILS_TAKE_ACTION_BUTTON = '[data-test-subj="take-action-dropdown-btn"]'; +export const ALERT_DETAILS_TAKE_ACTION_BUTTON = + '[data-test-subj="securitySolutionFlyoutFooterDropdownButton"]'; export const USER_COLUMN = '[data-gridcell-column-id="user.name"]'; export const TOOLTIP = '[data-test-subj="message-tool-tip"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts index 6f9ee7b4aea77..bbae387497af7 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts @@ -5,11 +5,8 @@ * 2.0. */ -export const ALERT_FLYOUT = '[data-test-subj="timeline:details-panel:flyout"]'; - -export const CELL_TEXT = '.euiText'; - -export const ENRICHMENT_QUERY_RANGE_PICKER = '[data-test-subj="enrichment-query-range-picker"]'; +export const ENRICHMENT_QUERY_RANGE_PICKER = + '[data-test-subj="securitySolutionFlyoutThreatIntelligenceDetailsEnrichmentRangePicker"]'; export const ENRICHMENT_QUERY_START_INPUT = '.start-picker'; @@ -17,42 +14,34 @@ export const ENRICHMENT_QUERY_END_INPUT = '.end-picker'; export const FILTER_INPUT = '[data-test-subj="eventDetails"] .euiFieldSearch'; -export const INDICATOR_MATCH_ENRICHMENT_SECTION = '[data-test-subj="threat-match-detected"]'; +export const INDICATOR_MATCH_ENRICHMENT_SECTION = + '[data-test-subj="securitySolutionFlyoutThreatIntelligenceDetailsThreatMatchDetected"]'; export const INVESTIGATION_TIME_ENRICHMENT_SECTION = - '[data-test-subj="enriched-with-threat-intel"]'; + '[data-test-subj="securitySolutionFlyoutThreatIntelligenceDetailsEnrichedWithThreatIntel"]'; -export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]'; +export const ENRICHMENT_SECTION_BUTTON_CONTENT = + '[data-test-subj="securitySolutionFlyoutThreatIntelligenceDetailsEnrichmentButtonContent"]'; -export const JSON_TEXT = '[data-test-subj="kibanaCodeEditor"]'; - -export const OVERVIEW_RULE = '[data-test-subj="eventDetails"] [data-test-subj="ruleName"]'; +export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]'; export const OVERVIEW_STATUS = '[data-test-subj="eventDetails"] [data-test-subj="alertStatus"]'; export const EVENT_DETAILS_ALERT_STATUS_POPOVER = '[data-test-subj="event-details-alertStatusPopover"]'; -export const SUMMARY_VIEW = '[data-test-subj="summary-view"]'; - export const TABLE_CELL = '.euiTableRowCell'; export const CELL_EXPAND_VALUE = '[data-test-subj="euiDataGridCellExpandButton"]'; export const TABLE_TAB = '[data-test-subj="tableTab"]'; -export const TABLE_CONTAINER = '[data-test-subj="event-fields-browser"]'; - export const TABLE_ROWS = '.euiTableRow'; -export const THREAT_DETAILS_ACCORDION = '.euiAccordion__triggerWrapper'; - -export const THREAT_DETAILS_VIEW = '[data-test-subj="threat-details-view-0"]'; +export const THREAT_DETAILS_VIEW = + '[data-test-subj="securitySolutionFlyoutThreatIntelligenceDetailsEnrichmentAccordionTable-0"]'; -export const THREAT_INTEL_TAB = '[data-test-subj="threatIntelTab"]'; - -export const UPDATE_ENRICHMENT_RANGE_BUTTON = '[data-test-subj="enrichment-button"]'; +export const UPDATE_ENRICHMENT_RANGE_BUTTON = + '[data-test-subj="securitySolutionFlyoutThreatIntelligenceDetailsEnrichmentRefreshButton"]'; export const ENRICHED_DATA_ROW = `[data-test-subj='EnrichedDataRow']`; - -export const COPY_ALERT_FLYOUT_LINK = `[data-test-subj="copy-alert-flyout-link"]`; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts index b0af7275ae808..46b0b3a68734a 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts @@ -261,9 +261,6 @@ export const ESQL_QUERY_BAR_INPUT_AREA = export const ESQL_QUERY_BAR = '[data-test-subj="detectionEngineStepDefineRuleEsqlQueryBar"]'; -export const ESQL_QUERY_BAR_EXPAND_BTN = - '[data-test-subj="detectionEngineStepDefineRuleEsqlQueryBar"] [data-test-subj="TextBasedLangEditor-expand"]'; - export const NEW_TERMS_INPUT_AREA = '[data-test-subj="newTermsInput"]'; export const NEW_TERMS_HISTORY_SIZE = 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 8b2d9ccac1bfa..114cb9a4db0c4 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/discover.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/discover.ts @@ -15,7 +15,7 @@ export const DISCOVER_DATA_VIEW_SWITCHER = { INPUT: getDataTestSubjectSelector('indexPattern-switcher--input'), GET_DATA_VIEW: (title: string) => `.euiSelectableListItem[role=option][title^="${title}"]`, CREATE_NEW: getDataTestSubjectSelector('dataview-create-new'), - TEXT_BASE_LANG_SWICTHER: getDataTestSubjectSelector('select-text-based-language-panel'), + TEXT_BASE_LANG_SWITCHER: getDataTestSubjectSelector('select-text-based-language-btn'), }; export const DISCOVER_DATA_VIEW_EDITOR_FLYOUT = { @@ -32,7 +32,6 @@ export const DISCOVER_ESQL_INPUT = `${DISCOVER_CONTAINER} ${getDataTestSubjectSe export const DISCOVER_ESQL_INPUT_TEXT_CONTAINER = `${DISCOVER_ESQL_INPUT} .view-lines`; -export const DISCOVER_ESQL_INPUT_EXPAND = getDataTestSubjectSelector('TextBasedLangEditor-expand'); export const DISCOVER_ESQL_EDITABLE_INPUT = `${DISCOVER_ESQL_INPUT} textarea`; export const DISCOVER_ADD_FILTER = `${DISCOVER_CONTAINER} ${getDataTestSubjectSelector( 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 4044c284ee054..41cd41eb83890 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 @@ -55,10 +55,10 @@ export const DOCUMENT_DETAILS_FLYOUT_HEADER_ASSIGNEES = getDataTestSubjectSelect /* Footer */ export const DOCUMENT_DETAILS_FLYOUT_FOOTER = getDataTestSubjectSelector( - 'side-panel-flyout-footer' + 'securitySolutionFlyoutFooter' ); export const DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON = getDataTestSubjectSelector( - 'take-action-dropdown-btn' + 'securitySolutionFlyoutFooterDropdownButton' ); export const DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON_DROPDOWN = getDataTestSubjectSelector('takeActionPanelMenu'); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_table_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_table_tab.ts index 3a0eec9391437..24065985f377f 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_table_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_table_tab.ts @@ -5,22 +5,23 @@ * 2.0. */ -import { getClassSelector, getDataTestSubjectSelector } from '../../helpers/common'; +import { getDataTestSubjectSelector } from '../../helpers/common'; -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER = getClassSelector('euiFieldSearch'); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_FILTER = getDataTestSubjectSelector( + 'securitySolutionFlyoutDocumentTableSearchInput' +); export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW = getDataTestSubjectSelector( - 'event-fields-table-row-@timestamp' + 'flyout-table-row-@timestamp' ); export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_CELL = getDataTestSubjectSelector('event-field-@timestamp'); -export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW = getDataTestSubjectSelector( - 'event-fields-table-row-_id' -); +export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW = + getDataTestSubjectSelector('flyout-table-row-_id'); export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_HOST_OS_BUILD_ROW = getDataTestSubjectSelector( - 'event-fields-table-row-host.os.build' + 'flyout-table-row-host.os.build' ); export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_THREAT_ENRICHMENTS = getDataTestSubjectSelector( - 'event-fields-table-row-threat.enrichments' + 'flyout-table-row-threat.enrichments' ); const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_ACTIONS = 'actionItem-security-detailsFlyout-cellActions-'; diff --git a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts index b6701a8b4b553..1b5c2adcae455 100644 --- a/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts +++ b/x-pack/test/security_solution_cypress/cypress/support/saml_auth.ts @@ -9,6 +9,8 @@ import { ToolingLog } from '@kbn/tooling-log'; import { SecurityRoleName } from '@kbn/security-solution-plugin/common/test'; import { HostOptions, SamlSessionManager } from '@kbn/test'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; import { DEFAULT_SERVERLESS_ROLE } from '../env_var_names_constants'; export const samlAuthentication = async ( @@ -31,30 +33,27 @@ export const samlAuthentication = async ( // If config.env.PROXY_ORG is set, it means that proxy service is used to create projects. Define the proxy org filename to override the roles. const rolesFilename = config.env.PROXY_ORG ? `${config.env.PROXY_ORG}.json` : undefined; + const cloudUsersFilePath = resolve(REPO_ROOT, '.ftr', rolesFilename ?? 'role_users.json'); on('task', { getSessionCookie: async (role: string | SecurityRoleName): Promise<string> => { - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath, + }); return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role); }, getFullname: async ( role: string | SecurityRoleName = DEFAULT_SERVERLESS_ROLE ): Promise<string> => { - const sessionManager = new SamlSessionManager( - { - hostOptions, - log, - isCloud: config.env.CLOUD_SERVERLESS, - }, - rolesFilename - ); + const sessionManager = new SamlSessionManager({ + hostOptions, + log, + isCloud: config.env.CLOUD_SERVERLESS, + cloudUsersFilePath, + }); const { full_name: fullName } = await sessionManager.getUserData(role); return fullName; }, diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index 2a3038b090240..6adcd97bd8c28 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -18,7 +18,9 @@ import { MARK_ALERT_ACKNOWLEDGED_BTN, OPEN_ALERT_BTN, SEND_ALERT_TO_TIMELINE_BTN, - SELECT_AGGREGATION_CHART, + ALERT_CHARTS_TOGGLE_BUTTON, + SELECT_COUNTS_TABLE, + SELECT_TREEMAP, TAKE_ACTION_POPOVER_BTN, TIMELINE_CONTEXT_MENU_BTN, CLOSE_FLYOUT, @@ -60,7 +62,6 @@ import { ENRICHMENT_QUERY_END_INPUT, ENRICHMENT_QUERY_RANGE_PICKER, ENRICHMENT_QUERY_START_INPUT, - THREAT_INTEL_TAB, CELL_EXPAND_VALUE, } from '../screens/alerts_details'; import { FIELD_INPUT } from '../screens/exceptions'; @@ -154,8 +155,6 @@ export const hideMessageTooltip = () => { export const closeAlertFlyout = () => cy.get(CLOSE_FLYOUT).click(); -export const viewThreatIntelTab = () => cy.get(THREAT_INTEL_TAB).click(); - export const setEnrichmentDates = (from?: string, to?: string) => { cy.get(ENRICHMENT_QUERY_RANGE_PICKER).within(() => { if (from) { @@ -267,14 +266,22 @@ export const openAlerts = () => { cy.get(OPEN_ALERT_BTN).click(); }; -export const selectCountTable = () => { - cy.get(SELECT_AGGREGATION_CHART).click({ force: true }); +export const toggleKPICharts = () => { + cy.get(ALERT_CHARTS_TOGGLE_BUTTON).click(); +}; + +export const selectAlertsCountTable = () => { + cy.get(SELECT_COUNTS_TABLE).click(); }; export const selectAlertsHistogram = () => { cy.get(SELECT_HISTOGRAM).click(); }; +export const selectAlertsTreemap = () => { + cy.get(SELECT_TREEMAP).click(); +}; + export const goToAcknowledgedAlerts = () => { /* * below line commented because alertPageFiltersEnabled feature flag diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts index 273491ebd2efe..f4273cad22299 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/prebuilt_rules.ts @@ -8,6 +8,7 @@ import { PerformRuleInstallationResponseBody, PERFORM_RULE_INSTALLATION_URL, + BOOTSTRAP_PREBUILT_RULES_URL, } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common/detection_engine/constants'; import type { PrePackagedRulesStatusResponse } from '@kbn/security-solution-plugin/public/detection_engine/rule_management/logic/types'; @@ -203,8 +204,7 @@ export const getRuleAssets = (index: string | undefined = '.kibana_security_solu /* during e2e tests, and allow for manual installation of mock rules instead. */ export const preventPrebuiltRulesPackageInstallation = () => { cy.log('Prevent prebuilt rules package installation'); - cy.intercept('POST', '/api/fleet/epm/packages/_bulk*', {}); - cy.intercept('POST', '/api/fleet/epm/packages/security_detection_engine/*', {}); + cy.intercept('POST', BOOTSTRAP_PREBUILT_RULES_URL, {}); }; /** diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts index bf0b4676bf117..4251ef8ee0ec8 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts @@ -59,7 +59,6 @@ import { EQL_TYPE, ESQL_TYPE, ESQL_QUERY_BAR, - ESQL_QUERY_BAR_EXPAND_BTN, ESQL_QUERY_BAR_INPUT_AREA, FALSE_POSITIVES_INPUT, IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK, @@ -632,14 +631,6 @@ export const fillEsqlQueryBar = (query: string) => { typeEsqlQueryBar(query); }; -/** - * expands query bar, so query is not obscured on narrow screens - * and validation message is not covered by input menu tooltip - */ -export const expandEsqlQueryBar = () => { - cy.get(ESQL_QUERY_BAR_EXPAND_BTN).click(); -}; - export const fillDefineEsqlRuleAndContinue = (rule: EsqlRuleCreateProps) => { cy.get(ESQL_QUERY_BAR).contains('ES|QL query'); fillEsqlQueryBar(rule.query); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts b/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts index e888f75149b2d..1034922383a40 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/discover.ts @@ -15,7 +15,6 @@ import { DISCOVER_DATA_VIEW_EDITOR_FLYOUT, DISCOVER_FIELD_LIST_LOADING, DISCOVER_ESQL_EDITABLE_INPUT, - DISCOVER_ESQL_INPUT_EXPAND, } from '../screens/discover'; import { GET_LOCAL_SEARCH_BAR_SUBMIT_BUTTON } from '../screens/search_bar'; import { goToEsqlTab } from './timeline'; @@ -28,8 +27,7 @@ export const switchDataViewTo = (dataviewName: string) => { }; export const switchDataViewToESQL = () => { - openDataViewSwitcher(); - cy.get(DISCOVER_DATA_VIEW_SWITCHER.TEXT_BASE_LANG_SWICTHER).trigger('click'); + cy.get(DISCOVER_DATA_VIEW_SWITCHER.TEXT_BASE_LANG_SWITCHER).trigger('click'); cy.get(DISCOVER_DATA_VIEW_SWITCHER.BTN).should('contain.text', 'ES|QL'); }; @@ -56,8 +54,6 @@ export const selectCurrentDiscoverEsqlQuery = ( goToEsqlTab(); // eslint-disable-next-line cypress/no-force cy.get(discoverEsqlInput).click({ force: true }); - // eslint-disable-next-line cypress/no-force - cy.get(DISCOVER_ESQL_INPUT_EXPAND).click({ force: true }); fillEsqlQueryBar(Cypress.platform === 'darwin' ? '{cmd+a}' : '{ctrl+a}'); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts index 7105ebc4df70f..f0b59a39b8478 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts @@ -6,6 +6,7 @@ */ import { + BOOTSTRAP_PREBUILT_RULES_URL, GET_ALL_INTEGRATIONS_URL, Integration, } from '@kbn/security-solution-plugin/common/api/detection_engine'; @@ -21,10 +22,10 @@ export const mockFleetIntegrations = (integrations: Integration[] = []) => { }).as('integrations'); }; -export const waitForFleetSetup = () => { - cy.intercept('POST', '/api/fleet/epm/packages/_bulk?prerelease=true').as('fleetSetup'); +export const waitForRulesBootstrap = () => { + cy.intercept('POST', BOOTSTRAP_PREBUILT_RULES_URL).as('rulesBootstrap'); cy.clearLocalStorage(); login(); visitGetStartedPage(); - cy.wait('@fleetSetup'); + cy.wait('@rulesBootstrap'); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tsconfig.json b/x-pack/test/security_solution_cypress/cypress/tsconfig.json index d07d03536f7f4..b7223115bbcb6 100644 --- a/x-pack/test/security_solution_cypress/cypress/tsconfig.json +++ b/x-pack/test/security_solution_cypress/cypress/tsconfig.json @@ -42,5 +42,6 @@ "@kbn/actions-plugin", "@kbn/alerts-ui-shared", "@kbn/securitysolution-endpoint-exceptions-common", + "@kbn/repo-info", ] } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts index d051500293682..16fb1bb48808a 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_solution_integrations.ts @@ -106,7 +106,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should show Isolation action in alert details', async () => { await pageObjects.timeline.showEventDetails(); - await testSubjects.click('take-action-dropdown-btn'); + await testSubjects.click('securitySolutionFlyoutFooterDropdownButton'); await testSubjects.clickWhenNotDisabled('isolate-host-action-item'); await testSubjects.existOrFail('endpointHostIsolationForm'); await testSubjects.click('hostIsolateCancelButton'); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 032865c4ad1ed..7302050adeede 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -6,14 +6,13 @@ */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../configs/ftr_provider_context'; export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService, getPageObjects } = providerContext; - // Flaky: https://github.com/elastic/kibana/issues/186089 - describe('@skipInServerless endpoint', function () { + describe('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); @@ -38,7 +37,7 @@ export default function (providerContext: FtrProviderContext) { if (await isServerlessKibanaFlavor(kbnClient)) { log.info('login for serverless environment'); const pageObjects = getPageObjects(['svlCommonPage']); - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('endpoint_operations_analyst'); } }); loadTestFile(require.resolve('./endpoint_list')); diff --git a/x-pack/test/security_solution_endpoint/apps/integrations/index.ts b/x-pack/test/security_solution_endpoint/apps/integrations/index.ts index 7f5cfa4641471..7bf73a60499d2 100644 --- a/x-pack/test/security_solution_endpoint/apps/integrations/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/integrations/index.ts @@ -6,13 +6,12 @@ */ import { getRegistryUrl as getRegistryUrlFromIngest } from '@kbn/fleet-plugin/server'; -import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrProviderContext } from '../../configs/ftr_provider_context'; export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService, getPageObjects } = providerContext; - // Flaky: https://github.com/elastic/kibana/issues/186086 describe('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); @@ -37,7 +36,7 @@ export default function (providerContext: FtrProviderContext) { if (await isServerlessKibanaFlavor(kbnClient)) { log.info('login for serverless environment'); const pageObjects = getPageObjects(['svlCommonPage']); - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('admin'); } }); loadTestFile(require.resolve('./policy_list')); diff --git a/x-pack/test/security_solution_endpoint/configs/config.base.ts b/x-pack/test/security_solution_endpoint/configs/config.base.ts index 684dbf0e9510b..a4d1d121374d9 100644 --- a/x-pack/test/security_solution_endpoint/configs/config.base.ts +++ b/x-pack/test/security_solution_endpoint/configs/config.base.ts @@ -8,8 +8,9 @@ import { Config } from '@kbn/test'; import { FtrConfigProviderContext } from '@kbn/test'; import { SecuritySolutionEndpointRegistryHelpers } from '../../common/services/security_solution'; -import { pageObjects } from '../page_objects'; import type { TargetTags } from '../target_tags'; +import { PageObjects } from '../page_objects'; +import { Services } from '../services'; export const SUITE_TAGS: Record< 'ess' | 'serverless', @@ -33,6 +34,7 @@ export const generateConfig = async ({ kbnServerArgs = [], target, services, + pageObjects, }: { ftrConfigProviderContext: FtrConfigProviderContext; baseConfig: Config; @@ -40,7 +42,8 @@ export const generateConfig = async ({ junitReportName: string; kbnServerArgs?: string[]; target: keyof typeof SUITE_TAGS; - services: any; + services: Services; + pageObjects: PageObjects; }): Promise<Config> => { const { readConfigFile } = ftrConfigProviderContext; // services are not ready yet, so we need to import them here diff --git a/x-pack/test/security_solution_endpoint/configs/endpoint.config.ts b/x-pack/test/security_solution_endpoint/configs/endpoint.config.ts index b7fed05f12656..cad89b4b3dcd9 100644 --- a/x-pack/test/security_solution_endpoint/configs/endpoint.config.ts +++ b/x-pack/test/security_solution_endpoint/configs/endpoint.config.ts @@ -9,6 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test'; import { resolve } from 'path'; import { generateConfig } from './config.base'; import { services } from '../services'; +import { pageObjects } from '../page_objects'; // eslint-disable-next-line import/no-default-export export default async function (ftrConfigProviderContext: FtrConfigProviderContext) { @@ -25,5 +26,6 @@ export default async function (ftrConfigProviderContext: FtrConfigProviderContex junitReportName: 'X-Pack Endpoint Functional Tests on ESS', target: 'ess', services, + pageObjects, }); } diff --git a/x-pack/test/security_solution_endpoint/configs/ftr_provider_context.d.ts b/x-pack/test/security_solution_endpoint/configs/ftr_provider_context.d.ts index 464db920156b4..1166169b76ba2 100644 --- a/x-pack/test/security_solution_endpoint/configs/ftr_provider_context.d.ts +++ b/x-pack/test/security_solution_endpoint/configs/ftr_provider_context.d.ts @@ -7,7 +7,10 @@ import { GenericFtrProviderContext } from '@kbn/test'; -import { pageObjects } from '../page_objects'; -import { services } from '../services'; +import { pageObjects, svlPageObjects } from '../page_objects'; +import { services, svlServices } from '../services'; -export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>; +export type FtrProviderContext = GenericFtrProviderContext< + typeof services & typeof svlServices, + typeof pageObjects & typeof svlPageObjects +>; diff --git a/x-pack/test/security_solution_endpoint/configs/integrations.config.ts b/x-pack/test/security_solution_endpoint/configs/integrations.config.ts index bed24d7741724..9a94b101dccf1 100644 --- a/x-pack/test/security_solution_endpoint/configs/integrations.config.ts +++ b/x-pack/test/security_solution_endpoint/configs/integrations.config.ts @@ -9,6 +9,7 @@ import { resolve } from 'path'; import { FtrConfigProviderContext } from '@kbn/test'; import { generateConfig } from './config.base'; import { services } from '../services'; +import { pageObjects } from '../page_objects'; // eslint-disable-next-line import/no-default-export export default async function (ftrConfigProviderContext: FtrConfigProviderContext) { @@ -29,5 +30,6 @@ export default async function (ftrConfigProviderContext: FtrConfigProviderContex '--xpack.securitySolution.packagerTaskInterval=5s', ], services, + pageObjects, }); } diff --git a/x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts b/x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts index 4867cab58725d..3df139dc9dd9e 100644 --- a/x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts +++ b/x-pack/test/security_solution_endpoint/configs/serverless.endpoint.config.ts @@ -9,6 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test'; import { resolve } from 'path'; import { generateConfig } from './config.base'; import { svlServices } from '../services'; +import { svlPageObjects } from '../page_objects'; // eslint-disable-next-line import/no-default-export export default async function (ftrConfigProviderContext: FtrConfigProviderContext) { @@ -26,5 +27,6 @@ export default async function (ftrConfigProviderContext: FtrConfigProviderContex kbnServerArgs: ['--serverless=security'], target: 'serverless', services: svlServices, + pageObjects: svlPageObjects, }); } diff --git a/x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts b/x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts index 7489c969d1984..2822d16a979e3 100644 --- a/x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts +++ b/x-pack/test/security_solution_endpoint/configs/serverless.integrations.config.ts @@ -9,6 +9,7 @@ import { resolve } from 'path'; import { FtrConfigProviderContext } from '@kbn/test'; import { generateConfig } from './config.base'; import { svlServices } from '../services'; +import { svlPageObjects } from '../page_objects'; // eslint-disable-next-line import/no-default-export export default async function (ftrConfigProviderContext: FtrConfigProviderContext) { @@ -30,5 +31,6 @@ export default async function (ftrConfigProviderContext: FtrConfigProviderContex '--xpack.securitySolution.packagerTaskInterval=5s', ], services: svlServices, + pageObjects: svlPageObjects, }); } diff --git a/x-pack/test/security_solution_endpoint/page_objects/index.ts b/x-pack/test/security_solution_endpoint/page_objects/index.ts index d7fb55608226f..977076cc71044 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/index.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SvlCommonPageProvider } from '@kbn/test-suites-serverless/functional/page_objects/svl_common_page'; import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects'; import { EndpointPageProvider } from './endpoint_page'; import { EndpointPageUtils } from './page_utils'; @@ -17,7 +18,6 @@ import { EndpointPolicyPageProvider } from './policy_page'; import { TrustedAppsPageProvider } from './trusted_apps_page'; import { FleetIntegrations } from './fleet_integrations_page'; import { ArtifactEntriesListPageProvider } from './artifact_entries_list_page'; -import { SvlCommonPageProvider } from './svl_common_page'; export const pageObjects = { ...xpackFunctionalPageObjects, @@ -33,6 +33,12 @@ export const pageObjects = { trustedApps: TrustedAppsPageProvider, artifactEntriesList: ArtifactEntriesListPageProvider, fleetIntegrations: FleetIntegrations, +}; + +export const svlPageObjects = { + ...pageObjects, svlCommonPage: SvlCommonPageProvider, }; + +export type PageObjects = typeof pageObjects | typeof svlPageObjects; diff --git a/x-pack/test/security_solution_endpoint/page_objects/svl_common_page.ts b/x-pack/test/security_solution_endpoint/page_objects/svl_common_page.ts deleted file mode 100644 index c08f0b22b2078..0000000000000 --- a/x-pack/test/security_solution_endpoint/page_objects/svl_common_page.ts +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 '../configs/ftr_provider_context'; - -/** copied from `x-pack/test_serverless/functional/page_objects` in order to be able to login when testing against serverless, - * without importing from a different project - */ -export function SvlCommonPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const config = getService('config'); - const pageObjects = getPageObjects(['security', 'common']); - const retry = getService('retry'); - const deployment = getService('deployment'); - const log = getService('log'); - const browser = getService('browser'); - - const delay = (ms: number) => - new Promise((resolve) => { - setTimeout(resolve, ms); - }); - - return { - async navigateToLoginForm() { - const url = deployment.getHostPort() + '/login'; - await browser.get(url); - // ensure welcome screen won't be shown. This is relevant for environments which don't allow - // to use the yml setting, e.g. cloud - await browser.setLocalStorageItem('home:welcome:show', 'false'); - - log.debug('Waiting for Login Form to appear.'); - await retry.waitForWithTimeout('login form', 10_000, async () => { - return await pageObjects.security.isLoginFormVisible(); - }); - }, - - async login() { - await pageObjects.security.forceLogout({ waitForLoginPage: false }); - - // adding sleep to settle down logout - await pageObjects.common.sleep(2500); - - await retry.waitForWithTimeout( - 'Waiting for successful authentication', - 90_000, - async () => { - if (!(await testSubjects.exists('loginUsername', { timeout: 1000 }))) { - await this.navigateToLoginForm(); - - await testSubjects.setValue('loginUsername', config.get('servers.kibana.username')); - await testSubjects.setValue('loginPassword', config.get('servers.kibana.password')); - await testSubjects.click('loginSubmit'); - } - - if (await testSubjects.exists('userMenuButton', { timeout: 10_000 })) { - log.debug('userMenuButton is found, logged in passed'); - return true; - } else { - throw new Error(`Failed to login to Kibana via UI`); - } - }, - async () => { - // Sometimes authentication fails and user is redirected to Cloud login page - // [plugins.security.authentication] Authentication attempt failed: UNEXPECTED_SESSION_ERROR - const currentUrl = await browser.getCurrentUrl(); - if (currentUrl.startsWith('https://cloud.elastic.co')) { - log.debug( - 'Probably authentication attempt failed, we are at Cloud login page. Retrying from scratch' - ); - } else { - const authError = await testSubjects.exists('promptPage', { timeout: 2500 }); - if (authError) { - log.debug('Probably SAML callback page, doing logout again'); - await pageObjects.security.forceLogout({ waitForLoginPage: false }); - } else { - const isOnLoginPage = await testSubjects.exists('loginUsername', { timeout: 1000 }); - if (isOnLoginPage) { - log.debug( - 'Probably ES user profile activation failed, waiting 2 seconds and pressing Login button again' - ); - await delay(2000); - await testSubjects.click('loginSubmit'); - } else { - log.debug('New behaviour, trying to navigate and login again'); - } - } - } - } - ); - log.debug('Logged in successfully'); - }, - - async forceLogout() { - await pageObjects.security.forceLogout({ waitForLoginPage: false }); - log.debug('Logged out successfully'); - }, - - async assertProjectHeaderExists() { - await testSubjects.existOrFail('kibanaProjectHeader'); - }, - - async clickUserAvatar() { - testSubjects.click('userMenuAvatar'); - }, - - async assertUserAvatarExists() { - await testSubjects.existOrFail('userMenuAvatar'); - }, - - async assertUserMenuExists() { - await testSubjects.existOrFail('userMenu'); - }, - }; -} diff --git a/x-pack/test/security_solution_endpoint/services/endpoint.ts b/x-pack/test/security_solution_endpoint/services/endpoint.ts index c2df3a155377d..1c93bbe0f2be7 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint.ts @@ -7,18 +7,17 @@ /* eslint-disable max-classes-per-file */ -import { errors } from '@elastic/elasticsearch'; -import { Client } from '@elastic/elasticsearch'; +import { Client, errors } from '@elastic/elasticsearch'; import { AGENTS_INDEX } from '@kbn/fleet-plugin/common'; import { - metadataCurrentIndexPattern, - metadataTransformPrefix, + HOST_METADATA_GET_ROUTE, METADATA_CURRENT_TRANSFORM_V2, + METADATA_DATASTREAM, METADATA_UNITED_INDEX, METADATA_UNITED_TRANSFORM, METADATA_UNITED_TRANSFORM_V2, - HOST_METADATA_GET_ROUTE, - METADATA_DATASTREAM, + metadataCurrentIndexPattern, + metadataTransformPrefix, } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { deleteIndexedHostsAndAlerts, @@ -38,20 +37,33 @@ import { merge } from 'lodash'; // @ts-expect-error we have to check types with "allowJs: false" for now, causing this import to fail import { kibanaPackageJson } from '@kbn/repo-info'; import seedrandom from 'seedrandom'; +import { fetchFleetLatestAvailableAgentVersion } from '@kbn/security-solution-plugin/common/endpoint/utils/fetch_fleet_version'; +import { KbnClient } from '@kbn/test'; +import { isServerlessKibanaFlavor } from '@kbn/security-solution-plugin/common/endpoint/utils/kibana_status'; import { FtrService } from '../../functional/ftr_provider_context'; // Document Generator override that uses a custom Endpoint Metadata generator and sets the // `agent.version` to the current version -const CurrentKibanaVersionDocGenerator = class extends EndpointDocGenerator { - constructor(seedValue: string | seedrandom.prng) { - const MetadataGenerator = class extends EndpointMetadataGenerator { - protected randomVersion(): string { - return kibanaPackageJson.version; - } - }; - super(seedValue, MetadataGenerator); +const createDocGeneratorClass = async (kbnClient: KbnClient, isServerless: boolean) => { + let version = kibanaPackageJson.version; + if (isServerless) { + version = await fetchFleetLatestAvailableAgentVersion(kbnClient); } + // TS doesn't like the `version` let being used in the class definition + const capturedVersion = version; + + return class extends EndpointDocGenerator { + constructor(seedValue: string | seedrandom.prng) { + const MetadataGenerator = class extends EndpointMetadataGenerator { + protected randomVersion(): string { + return capturedVersion; + } + }; + + super(seedValue, MetadataGenerator); + } + }; }; export class EndpointTestResources extends FtrService { @@ -93,7 +105,7 @@ export class EndpointTestResources extends FtrService { * @param [options.numHostDocs=1] Number of Document to be loaded per Endpoint Host (Endpoint hosts index uses a append-only index) * @param [options.alertsPerHost=1] Number of Alerts and Events to be loaded per Endpoint Host * @param [options.enableFleetIntegration=true] When set to `true`, Fleet data will also be loaded (ex. Integration Policies, Agent Policies, "fake" Agents) - * @param [options.generatorSeed='seed`] The seed to be used by the data generator. Important in order to ensure the same data is generated on very run. + * @param [options.generatorSeed='seed'] The seed to be used by the data generator. Important in order to ensure the same data is generated on very run. * @param [options.waitUntilTransformed=true] If set to `true`, the data loading process will wait until the endpoint hosts metadata is processed by the transform * @param [options.waitTimeout=120000] If waitUntilTransformed=true, number of ms to wait until timeout * @param [options.customIndexFn] If provided, will use this function to generate and index data instead @@ -140,6 +152,12 @@ export class EndpointTestResources extends FtrService { await this.stopTransform(unitedTransformName); } + const isServerless = await isServerlessKibanaFlavor(this.kbnClient); + const CurrentKibanaVersionDocGenerator = await createDocGeneratorClass( + this.kbnClient, + isServerless + ); + // load data into the system const indexedData = customIndexFn ? await customIndexFn() @@ -308,15 +326,13 @@ export class EndpointTestResources extends FtrService { * @param endpointAgentId */ async fetchEndpointMetadata(endpointAgentId: string): Promise<HostInfo> { - const metadata = this.supertest + return this.supertest .get(HOST_METADATA_GET_ROUTE.replace('{id}', endpointAgentId)) .set('kbn-xsrf', 'true') .set('Elastic-Api-Version', '2023-10-31') .send() .expect(200) .then((response) => response.body as HostInfo); - - return metadata; } /** diff --git a/x-pack/test/security_solution_endpoint/services/index.ts b/x-pack/test/security_solution_endpoint/services/index.ts index bf1c3a7c85c25..666378e4492c5 100644 --- a/x-pack/test/security_solution_endpoint/services/index.ts +++ b/x-pack/test/security_solution_endpoint/services/index.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { SvlCommonApiServiceProvider } from '@kbn/test-suites-serverless/shared/services/svl_common_api'; import { services as xPackFunctionalServices } from '../../functional/services'; import { IngestManagerProvider } from '../../common/services/ingest_manager'; import { EndpointTelemetryTestResourcesProvider } from './endpoint_telemetry'; @@ -39,4 +41,9 @@ export const svlServices = { supertest: KibanaSupertestWithCertProvider, supertestWithoutAuth: KibanaSupertestWithCertWithoutAuthProvider, + + svlCommonApi: SvlCommonApiServiceProvider, + svlUserManager: commonFunctionalServices.samlAuth, }; + +export type Services = typeof services | typeof svlServices; diff --git a/x-pack/test/security_solution_endpoint/tsconfig.json b/x-pack/test/security_solution_endpoint/tsconfig.json new file mode 100644 index 0000000000000..e4ce04de12a59 --- /dev/null +++ b/x-pack/test/security_solution_endpoint/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "node", + "@kbn/ambient-ftr-types" + ], + "allowJs": false + }, + "include": [ + "**/*", + "../../../typings/**/*", + "../../../packages/kbn-test/types/ftr_globals/**/*" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/test-suites-serverless", + "@kbn/expect", + "@kbn/security-solution-plugin", + "@kbn/repo-info", + "@kbn/securitysolution-list-constants", + "@kbn/fleet-plugin", + "@kbn/securitysolution-io-ts-list-types", + "@kbn/ftr-common-functional-ui-services", + "@kbn/test", + "@kbn/test-subj-selector", + "@kbn/ftr-common-functional-services", + ] +} diff --git a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts index e45fa7f7a5eb7..a8eb7cbcfbce1 100644 --- a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts +++ b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts @@ -196,7 +196,7 @@ export class DetectionsPageObject extends FtrService { */ async openResponseConsoleFromAlertDetails(): Promise<void> { await this.testSubjects.existOrFail('eventDetails'); - await this.testSubjects.click('take-action-dropdown-btn'); + await this.testSubjects.click('securitySolutionFlyoutFooterDropdownButton'); await this.testSubjects.clickWhenNotDisabled('endpointResponseActions-action-item'); await this.testSubjects.existOrFail('consolePageOverlay'); } diff --git a/x-pack/test/spaces_api_integration/common/config.ts b/x-pack/test/spaces_api_integration/common/config.ts index af659dbd9741c..f1e08457f692b 100644 --- a/x-pack/test/spaces_api_integration/common/config.ts +++ b/x-pack/test/spaces_api_integration/common/config.ts @@ -70,6 +70,10 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ...disabledPlugins .filter((k) => k !== 'security') .map((key) => `--xpack.${key}.enabled=false`), + // Note: we fake a cloud deployment as the solution view is only available in cloud + '--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=', + '--xpack.cloud.base_url=https://cloud.elastic.co', + '--xpack.cloud.deployment_url=/deployments/deploymentId', ], }, }; diff --git a/x-pack/test/spaces_api_integration/common/services.ts b/x-pack/test/spaces_api_integration/common/services.ts index aa150c7a22603..8b24d9b23e675 100644 --- a/x-pack/test/spaces_api_integration/common/services.ts +++ b/x-pack/test/spaces_api_integration/common/services.ts @@ -11,5 +11,4 @@ import { services as apiIntegrationServices } from '../../api_integration/servic export const services = { ...commonServices, usageAPI: apiIntegrationServices.usageAPI, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, }; diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index 2325e0259c8dd..bc465e237d550 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -12,6 +12,7 @@ import { SavedObjectsImportFailure, SavedObjectsImportAmbiguousConflictError, } from '@kbn/core/server'; +import { SuperTest } from 'supertest'; import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader'; @@ -98,7 +99,9 @@ interface Aggs extends estypes.AggregationsMultiBucketAggregateBase { } export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { const testDataLoader = getTestDataLoader(context); - const supertestWithoutAuth = context.getService('supertestWithoutAuth'); + const supertestWithoutAuth = context.getService( + 'supertestWithoutAuth' + ) as unknown as SuperTest<any>; const es = context.getService('es'); const collectSpaceContents = async () => { diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index 0a71beb9bffa0..93e76ab36b06b 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { SavedObject } from '@kbn/core/server'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; import { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces'; +import { SuperTest } from 'supertest'; import { getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; import type { FtrProviderContext } from '../ftr_provider_context'; @@ -69,7 +70,9 @@ const getDestinationSpace = (originSpaceId?: string) => { export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { const testDataLoader = getTestDataLoader(context); const supertestWithAuth = context.getService('supertest'); - const supertestWithoutAuth = context.getService('supertestWithoutAuth'); + const supertestWithoutAuth = context.getService( + 'supertestWithoutAuth' + ) as unknown as SuperTest<any>; const getVisualizationAtSpace = async (spaceId: string): Promise<SavedObject<any>> => { return supertestWithAuth diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts index c7baeff3087ad..3195f35c734cb 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/create.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { createTestSuiteFactory } from '../../common/suites/create'; @@ -22,7 +23,7 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext expectConflictResponse, expectRbacForbiddenResponse, expectSolutionSpecifiedResult, - } = createTestSuiteFactory(esArchiver, supertestWithoutAuth); + } = createTestSuiteFactory(esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('create', () => { [ diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts index 2ba0e4d77bfbe..60bcb91125858 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/delete.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { deleteTestSuiteFactory } from '../../common/suites/delete'; @@ -22,7 +23,7 @@ export default function deleteSpaceTestSuite({ getService }: FtrProviderContext) expectEmptyResult, expectNotFound, expectReservedSpaceResult, - } = deleteTestSuiteFactory(es, esArchiver, supertestWithoutAuth); + } = deleteTestSuiteFactory(es, esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('delete', () => { [ diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts index dccb7d5beb7df..122a9218555fe 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { getTestSuiteFactory } from '../../common/suites/get'; @@ -21,7 +22,7 @@ export default function getSpaceTestSuite({ getService }: FtrProviderContext) { createExpectNotFoundResult, createExpectRbacForbidden, nonExistantSpaceId, - } = getTestSuiteFactory(esArchiver, supertestWithoutAuth); + } = getTestSuiteFactory(esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('get', () => { [ diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts index 494f632ae4b1c..992ab6c7028a6 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { getAllTestSuiteFactory } from '../../common/suites/get_all'; @@ -16,7 +17,7 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext const esArchiver = getService('esArchiver'); const { getAllTest, createExpectResults, createExpectAllPurposesResults, expectRbacForbidden } = - getAllTestSuiteFactory(esArchiver, supertestWithoutAuth); + getAllTestSuiteFactory(esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); // these are used to determine expected results for tests where the `include_authorized_purposes` option is enabled const authorizedAll = { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts index 9bd80f4edd0d4..40ddc6549edcc 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/update.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { AUTHENTICATION } from '../../common/lib/authentication'; import { SPACES } from '../../common/lib/spaces'; import { updateTestSuiteFactory } from '../../common/suites/update'; @@ -21,7 +22,7 @@ export default function updateSpaceTestSuite({ getService }: FtrProviderContext) expectAlreadyExistsResult, expectDefaultSpaceResult, expectRbacForbidden, - } = updateTestSuiteFactory(esArchiver, supertestWithoutAuth); + } = updateTestSuiteFactory(esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('update', () => { [ diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts index 000d6b8f2ebe7..eea6204267bdd 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/create.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { SPACES } from '../../common/lib/spaces'; import { createTestSuiteFactory } from '../../common/suites/create'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -20,7 +21,7 @@ export default function createSpacesOnlySuite({ getService }: FtrProviderContext expectConflictResponse, expectReservedSpecifiedResult, expectSolutionSpecifiedResult, - } = createTestSuiteFactory(esArchiver, supertestWithoutAuth); + } = createTestSuiteFactory(esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('create', () => { [ diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts index 8621fe74d492d..f066018744818 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/delete.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { SPACES } from '../../common/lib/spaces'; import { deleteTestSuiteFactory } from '../../common/suites/delete'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -16,7 +17,7 @@ export default function deleteSpaceTestSuite({ getService }: FtrProviderContext) const es = getService('es'); const { deleteTest, expectEmptyResult, expectReservedSpaceResult, expectNotFound } = - deleteTestSuiteFactory(es, esArchiver, supertestWithoutAuth); + deleteTestSuiteFactory(es, esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('delete', () => { [ diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts index dbcf52776e5cc..e7f9acc06b655 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/get.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { SPACES } from '../../common/lib/spaces'; import { getTestSuiteFactory } from '../../common/suites/get'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -15,7 +16,7 @@ export default function getSpaceTestSuite({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const { getTest, createExpectResults, createExpectNotFoundResult, nonExistantSpaceId } = - getTestSuiteFactory(esArchiver, supertestWithoutAuth); + getTestSuiteFactory(esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('get', () => { // valid spaces diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts index 3fb4e5fd17458..46ddb59461945 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { SPACES } from '../../common/lib/spaces'; import { getAllTestSuiteFactory } from '../../common/suites/get_all'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -16,7 +17,7 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext const { getAllTest, createExpectResults } = getAllTestSuiteFactory( esArchiver, - supertestWithoutAuth + supertestWithoutAuth as unknown as SuperTest<any> ); describe('get all', () => { diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts index b473e02140dcb..40e4df87e74e9 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/update.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { SuperTest } from 'supertest'; import { SPACES } from '../../common/lib/spaces'; import { updateTestSuiteFactory } from '../../common/suites/update'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -15,7 +16,7 @@ export default function updateSpaceTestSuite({ getService }: FtrProviderContext) const esArchiver = getService('esArchiver'); const { updateTest, expectAlreadyExistsResult, expectDefaultSpaceResult, expectNotFound } = - updateTestSuiteFactory(esArchiver, supertestWithoutAuth); + updateTestSuiteFactory(esArchiver, supertestWithoutAuth as unknown as SuperTest<any>); describe('update', () => { [ diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts index 066a004df3814..7b536025715f0 100644 --- a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts @@ -129,7 +129,7 @@ export default function ({ getService }: FtrProviderContext) { const health = await getHealth(); expect(health.status).to.eql('OK'); expect(health.stats.configuration.value).to.eql({ - poll_interval: 3000, + poll_interval: 500, monitored_aggregated_stats_refresh_rate: monitoredAggregatedStatsRefreshRate, monitored_stats_running_average_window: 50, monitored_task_execution_thresholds: { @@ -140,7 +140,12 @@ export default function ({ getService }: FtrProviderContext) { }, }, request_capacity: 1000, - max_workers: 10, + capacity: { + config: 10, + as_workers: 10, + as_cost: 20, + }, + claim_strategy: 'unsafe_mget', }); }); diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/metrics_route.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/metrics_route.ts index 4fad194abf368..3e37c488e5190 100644 --- a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/metrics_route.ts +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/metrics_route.ts @@ -52,7 +52,10 @@ export default function ({ getService }: FtrProviderContext) { // counters are reset every 30 seconds, so wait until the start of a // fresh counter cycle to make sure values are incrementing const initialMetrics = ( - await getMetrics(false, (metrics) => metrics?.metrics?.task_claim?.value.total === 1) + await getMetrics( + false, + (metrics) => (metrics?.metrics?.task_claim?.value.total || 0) >= 1 + ) ).metrics; expect(initialMetrics).not.to.be(null); expect(initialMetrics?.task_claim).not.to.be(null); @@ -92,7 +95,7 @@ export default function ({ getService }: FtrProviderContext) { const initialMetrics = ( await getMetrics( false, - (metrics) => metrics?.metrics?.task_claim?.value.total === initialCounterValue + (metrics) => (metrics?.metrics?.task_claim?.value.total || 0) >= initialCounterValue ) ).metrics; expect(initialMetrics).not.to.be(null); @@ -101,7 +104,10 @@ export default function ({ getService }: FtrProviderContext) { // retry until counter value resets const resetMetrics = ( - await getMetrics(false, (m: NodeMetrics) => m?.metrics?.task_claim?.value.total === 1) + await getMetrics( + false, + (m: NodeMetrics) => (m?.metrics?.task_claim?.value.total || 0) >= 1 + ) ).metrics; expect(resetMetrics).not.to.be(null); expect(resetMetrics?.task_claim).not.to.be(null); @@ -113,7 +119,7 @@ export default function ({ getService }: FtrProviderContext) { const initialMetrics = ( await getMetrics( false, - (metrics) => metrics?.metrics?.task_claim?.value.total === initialCounterValue + (metrics) => (metrics?.metrics?.task_claim?.value.total || 0) >= initialCounterValue ) ).metrics; expect(initialMetrics).not.to.be(null); @@ -133,8 +139,8 @@ export default function ({ getService }: FtrProviderContext) { expect(metrics?.task_claim).not.to.be(null); expect(metrics?.task_claim?.value).not.to.be(null); - expect(metrics?.task_claim?.value.success).to.equal(1); - expect(metrics?.task_claim?.value.total).to.equal(1); + expect(metrics?.task_claim?.value.success).to.be.greaterThan(0); + expect(metrics?.task_claim?.value.total).to.be.greaterThan(0); previousTaskClaimTimestamp = metrics?.task_claim?.timestamp!; diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_partitions.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_partitions.ts index bede1c52625b3..5a4f956655ab3 100644 --- a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_partitions.ts +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_partitions.ts @@ -10,6 +10,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; import { taskMappings as TaskManagerMapping } from '@kbn/task-manager-plugin/server/saved_objects/mappings'; import { asyncForEach } from '@kbn/std'; +import { setTimeout as setTimeoutAsync } from 'timers/promises'; import { FtrProviderContext } from '../../ftr_provider_context'; const { properties: taskManagerIndexMapping } = TaskManagerMapping; @@ -154,13 +155,17 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should tasks with partitions assigned to this kibana node', async () => { + it('should run tasks with partitions assigned to this kibana node', async () => { const partitions: Record<string, number> = { '0': 127, '1': 147, '2': 23, }; + // wait for the pod partitions cache to update before scheduling tasks + await updateKibanaNodes(); + await setTimeoutAsync(10000); + const tasksToSchedule = []; for (let i = 0; i < 3; i++) { tasksToSchedule.push( diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 7fb0a50ef0f09..974a206d71e3f 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -24,7 +24,8 @@ "*/plugins/**/*", "*/packages/**/*", "*/*/packages/**/*", - "security_solution_api_integration/**/*" + "security_solution_api_integration/**/*", + "security_solution_endpoint/**/*" ], "kbn_references": [ "@kbn/test-suites-src", @@ -166,7 +167,6 @@ "@kbn/alerting-state-types", "@kbn/reporting-server", "@kbn/data-quality-plugin", - "@kbn/securitysolution-io-ts-list-types", "@kbn/ml-trained-models-utils", "@kbn/openapi-common", "@kbn/securitysolution-lists-common", @@ -176,6 +176,7 @@ "@kbn/osquery-plugin", "@kbn/entities-schema", "@kbn/actions-simulators-plugin", - "@kbn/cases-api-integration-test-plugin" + "@kbn/cases-api-integration-test-plugin", + "@kbn/mock-idp-utils" ] } 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 e2664e9d7104d..f7a909c688d0e 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 @@ -11,7 +11,7 @@ import type { Agent as SuperTestAgent } from 'supertest'; import { InternalRequestHeader, RoleCredentials, - SupertestWithoutAuthType, + SupertestWithoutAuthProviderType, } from '../../../../../shared/services'; interface CreateEsQueryRuleParams { @@ -40,7 +40,7 @@ export async function createIndexConnector({ name, indexName, }: { - supertestWithoutAuth: SupertestWithoutAuthType; + supertestWithoutAuth: SupertestWithoutAuthProviderType; roleAuthc: RoleCredentials; internalReqHeader: InternalRequestHeader; name: string; @@ -68,7 +68,7 @@ export async function createSlackConnector({ internalReqHeader, name, }: { - supertestWithoutAuth: SupertestWithoutAuthType; + supertestWithoutAuth: SupertestWithoutAuthProviderType; roleAuthc: RoleCredentials; internalReqHeader: InternalRequestHeader; name: string; @@ -103,7 +103,7 @@ export async function createEsQueryRule({ notifyWhen, enabled = true, }: { - supertestWithoutAuth: SupertestWithoutAuthType; + supertestWithoutAuth: SupertestWithoutAuthProviderType; roleAuthc: RoleCredentials; internalReqHeader: InternalRequestHeader; ruleTypeId: string; @@ -360,7 +360,7 @@ export async function runRule({ internalReqHeader, ruleId, }: { - supertestWithoutAuth: SupertestWithoutAuthType; + supertestWithoutAuth: SupertestWithoutAuthProviderType; roleAuthc: RoleCredentials; internalReqHeader: InternalRequestHeader; ruleId: string; 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 66f69a3801bd0..c7f2ac357e4a2 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 @@ -12,7 +12,7 @@ import type { SearchResponse, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { runRule } from './alerting_api_helper'; -import type { SupertestWithoutAuthType } from '../../../../../shared/services'; +import type { SupertestWithoutAuthProviderType } from '../../../../../shared/services'; import { RoleCredentials } from '../../../../../shared/services'; import { InternalRequestHeader } from '../../../../../shared/services'; @@ -376,7 +376,7 @@ export async function waitForNumRuleRuns({ esClient, testStart, }: { - supertestWithoutAuth: SupertestWithoutAuthType; + supertestWithoutAuth: SupertestWithoutAuthProviderType; roleAuthc: RoleCredentials; internalReqHeader: InternalRequestHeader; numOfRuns: number; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts index 64adeea57a9be..72640103c0ef9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts @@ -7,8 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { RoleCredentials } from '../../../../shared/services'; -import { InternalRequestHeader } from '../../../../shared/services/svl_common_api'; +import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; export default ({ getService }: FtrProviderContext) => { const svlCommonApi = getService('svlCommonApi'); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/console/proxy_route.ts b/x-pack/test_serverless/api_integration/test_suites/common/console/proxy_route.ts index 499ff182bf560..4c774d566bac7 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/console/proxy_route.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/console/proxy_route.ts @@ -25,14 +25,12 @@ export default function ({ getService }: FtrProviderContext) { describe('system indices behavior', () => { it('does not forward x-elastic-product-origin', async () => { // If we pass the header and we still get the warning back, we assume that the header was not forwarded. - return await supertestWithoutAuth + const response = await supertestWithoutAuth .post('/api/console/proxy?method=GET&path=/.kibana/_settings') .set('kbn-xsrf', 'true') .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .then((response) => { - expect(response.header).to.have.property('warning'); - }); + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('warning'); }); }); }); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/core/compression.ts b/x-pack/test_serverless/api_integration/test_suites/common/core/compression.ts index 6141cfeb0ad80..72f09e634ddf8 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/core/compression.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/core/compression.ts @@ -17,26 +17,22 @@ export default function ({ getService }: FtrProviderContext) { const compressionSuite = (url: string) => { it(`uses compression when there isn't a referer`, async () => { - await supertestWithoutAuth + const response = await supertestWithoutAuth .get(url) .set('accept-encoding', 'gzip') .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); }); it(`uses compression when there is a whitelisted referer`, async () => { - await supertestWithoutAuth + const response = await supertestWithoutAuth .get(url) .set('accept-encoding', 'gzip') .set(svlCommonApi.getInternalRequestHeader()) .set('referer', 'https://some-host.com') - .set(roleAuthc.apiKeyHeader) - .then((response) => { - expect(response.header).to.have.property('content-encoding', 'gzip'); - }); + .set(roleAuthc.apiKeyHeader); + expect(response.header).to.have.property('content-encoding', 'gzip'); }); }; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/core/translations.ts b/x-pack/test_serverless/api_integration/test_suites/common/core/translations.ts index 0ce90d73db1f9..bd348d27915c1 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/core/translations.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/core/translations.ts @@ -23,33 +23,26 @@ export default function ({ getService }: FtrProviderContext) { await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); it(`returns the translations with the correct headers`, async () => { - await supertestWithoutAuth + const response = await supertestWithoutAuth .get('/translations/en.json') .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .then((response) => { - expect(response.body.locale).to.eql('en'); + .set(roleAuthc.apiKeyHeader); + expect(response.body.locale).to.eql('en'); - expect(response.header).to.have.property( - 'content-type', - 'application/json; charset=utf-8' - ); - expect(response.header).to.have.property( - 'cache-control', - 'public, max-age=31536000, immutable' - ); - expect(response.header).not.to.have.property('etag'); - }); + expect(response.header).to.have.property('content-type', 'application/json; charset=utf-8'); + expect(response.header).to.have.property( + 'cache-control', + 'public, max-age=31536000, immutable' + ); + expect(response.header).not.to.have.property('etag'); }); it(`returns a 404 when not using the correct locale`, async () => { - await supertestWithoutAuth + const response = await supertestWithoutAuth .get('/translations/foo.json') .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .then((response) => { - expect(response.status).to.eql(404); - }); + .set(roleAuthc.apiKeyHeader); + expect(response.status).to.eql(404); }); }); } 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 009d9f6b7e971..885af133a4794 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 @@ -31,60 +31,59 @@ export default function ({ getService }: FtrProviderContext) { await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); - it('flags fields with mismatched types as conflicting', () => - supertestWithoutAuth + it('flags fields with mismatched types as conflicting', async () => { + const resp = await supertestWithoutAuth .get(FIELDS_FOR_WILDCARD_PATH) .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION_INTERNAL) .set(internalReqHeader) .set(roleAuthc.apiKeyHeader) .query({ pattern: 'logs-2017.01.*' }) - .expect(200) - .then((resp) => { - expect(resp.body).to.eql({ - fields: [ - { - name: '@timestamp', - type: 'date', - esTypes: ['date'], - aggregatable: true, - searchable: true, - readFromDocValues: true, - metadata_field: false, - }, - { - name: 'number_conflict', - type: 'number', - esTypes: ['float', 'integer'], - aggregatable: true, - searchable: true, - readFromDocValues: true, - metadata_field: false, - }, - { - name: 'string_conflict', - type: 'string', - esTypes: ['keyword', 'text'], - aggregatable: true, - searchable: true, - readFromDocValues: true, - metadata_field: false, - }, - { - name: 'success', - type: 'conflict', - esTypes: ['keyword', 'boolean'], - aggregatable: true, - searchable: true, - readFromDocValues: false, - conflictDescriptions: { - boolean: ['logs-2017.01.02'], - keyword: ['logs-2017.01.01'], - }, - metadata_field: false, - }, - ], - indices: ['logs-2017.01.01', 'logs-2017.01.02'], - }); - })); + .expect(200); + expect(resp.body).to.eql({ + fields: [ + { + name: '@timestamp', + type: 'date', + esTypes: ['date'], + aggregatable: true, + searchable: true, + readFromDocValues: true, + metadata_field: false, + }, + { + name: 'number_conflict', + type: 'number', + esTypes: ['float', 'integer'], + aggregatable: true, + searchable: true, + readFromDocValues: true, + metadata_field: false, + }, + { + name: 'string_conflict', + type: 'string', + esTypes: ['keyword', 'text'], + aggregatable: true, + searchable: true, + readFromDocValues: true, + metadata_field: false, + }, + { + name: 'success', + type: 'conflict', + esTypes: ['keyword', 'boolean'], + aggregatable: true, + searchable: true, + readFromDocValues: false, + conflictDescriptions: { + boolean: ['logs-2017.01.02'], + keyword: ['logs-2017.01.01'], + }, + metadata_field: false, + }, + ], + indices: ['logs-2017.01.01', 'logs-2017.01.02'], + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts index 1c1a63223cb72..c2b21e5c9cd83 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts @@ -67,7 +67,7 @@ export default function ({ getService }: FtrProviderContext) { inferenceEndpoints.some( (endpoint: InferenceAPIConfigResponse) => endpoint.model_id === inferenceId ) - ).to.be(true); + ).to.eql(true, `${inferenceId} not found in the GET _inference/_all response`); }); it('can delete inference endpoint', async () => { log.debug(`Deleting inference endpoint`); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts b/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts index c9e3300a2b68c..a45addf4a32ad 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry/kql_telemetry.ts @@ -81,40 +81,32 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should report success when opt *in* is incremented successfully', () => { - return ( - supertestWithoutAuth - .post('/internal/kql_opt_in_stats') - .set('content-type', 'application/json') - .set(ELASTIC_HTTP_VERSION_HEADER, KQL_TELEMETRY_ROUTE_LATEST_VERSION) - // TODO: API requests in Serverless require internal request headers - .set(svlCommonApi.getInternalRequestHeader()) - .send({ opt_in: true }) - .expect('Content-Type', /json/) - .set(roleAuthc.apiKeyHeader) - .expect(200) - .then(({ body }) => { - expect(body.success).to.be(true); - }) - ); + it('should report success when opt *in* is incremented successfully', async () => { + const { body } = await supertestWithoutAuth + .post('/internal/kql_opt_in_stats') + .set('content-type', 'application/json') + .set(ELASTIC_HTTP_VERSION_HEADER, KQL_TELEMETRY_ROUTE_LATEST_VERSION) + // TODO: API requests in Serverless require internal request headers + .set(svlCommonApi.getInternalRequestHeader()) + .send({ opt_in: true }) + .expect('Content-Type', /json/) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(body.success).to.be(true); }); - it('should report success when opt *out* is incremented successfully', () => { - return ( - supertestWithoutAuth - .post('/internal/kql_opt_in_stats') - .set('content-type', 'application/json') - .set(ELASTIC_HTTP_VERSION_HEADER, KQL_TELEMETRY_ROUTE_LATEST_VERSION) - // TODO: API requests in Serverless require internal request headers - .set(svlCommonApi.getInternalRequestHeader()) - .send({ opt_in: false }) - .expect('Content-Type', /json/) - .set(roleAuthc.apiKeyHeader) - .expect(200) - .then(({ body }) => { - expect(body.success).to.be(true); - }) - ); + it('should report success when opt *out* is incremented successfully', async () => { + const { body } = await supertestWithoutAuth + .post('/internal/kql_opt_in_stats') + .set('content-type', 'application/json') + .set(ELASTIC_HTTP_VERSION_HEADER, KQL_TELEMETRY_ROUTE_LATEST_VERSION) + // TODO: API requests in Serverless require internal request headers + .set(svlCommonApi.getInternalRequestHeader()) + .send({ opt_in: false }) + .expect('Content-Type', /json/) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(body.success).to.be(true); }); it('should only accept literal boolean values for the opt_in POST body param', function () { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts index 797ad2ef8e911..562e98d33866a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/response_headers.ts @@ -17,13 +17,10 @@ export default function ({ getService }: FtrProviderContext) { let roleAuthc: RoleCredentials; describe('security/response_headers', function () { - // fails on MKI, see https://github.com/elastic/kibana/issues/188714 - this.tags(['failsOnMKI']); - const baseCSP = `script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; frame-ancestors 'self'`; const defaultCOOP = 'same-origin'; const defaultPermissionsPolicy = - 'camera=(), display-capture=(), fullscreen=(self), geolocation=(), microphone=(), web-share=()'; + 'camera=(), display-capture=(), fullscreen=(self), geolocation=(), microphone=(), web-share=();report-to=violations-endpoint'; const defaultStrictTransportSecurity = 'max-age=31536000; includeSubDomains'; const defaultReferrerPolicy = 'strict-origin-when-cross-origin'; const defaultXContentTypeOptions = 'nosniff'; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_delete.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_delete.ts index b8d5ad868d0d5..33ef0942bb61f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_delete.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_delete.ts @@ -59,41 +59,38 @@ export default function ({ getService }: FtrProviderContext) { }); } - it('should return 200 for an existing object', async () => - await supertestWithoutAuth + it('should return 200 for an existing object', async () => { + const { body } = await supertestWithoutAuth .post(endpoint) .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) .send([validObject]) - .expect(200) - .then(({ body }) => { - expect(body).to.have.length(1); - expectSuccess(0, body); - })); + .expect(200); + expect(body).to.have.length(1); + expectSuccess(0, body); + }); - it('should return error for invalid object type', async () => - await supertestWithoutAuth + it('should return error for invalid object type', async () => { + const { body } = await supertestWithoutAuth .post(endpoint) .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) .send([invalidObject]) - .expect(200) - .then(({ body }) => { - expect(body).to.have.length(1); - expectBadRequest(0, body); - })); + .expect(200); + expect(body).to.have.length(1); + expectBadRequest(0, body); + }); - it('should return mix of successes and errors', async () => - await supertestWithoutAuth + it('should return mix of successes and errors', async () => { + const { body } = await supertestWithoutAuth .post(endpoint) .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) .send([validObject, invalidObject]) - .expect(200) - .then(({ body }) => { - expect(body).to.have.length(2); - expectSuccess(0, body); - expectBadRequest(1, body); - })); + .expect(200); + expect(body).to.have.length(2); + expectSuccess(0, body); + expectBadRequest(1, body); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_get.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_get.ts index 14a7d8a2efaf9..a215c72c41e1f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_get.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_get.ts @@ -61,42 +61,39 @@ export default function ({ getService }: FtrProviderContext) { }); } - it('should return 200 for object that exists and inject metadata', async () => - await supertestWithoutAuth + it('should return 200 for object that exists and inject metadata', async () => { + const { body } = await supertestWithoutAuth .post(URL) .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) .send([validObject]) - .expect(200) - .then(({ body }) => { - expect(body).to.have.length(1); - expectSuccess(0, body); - })); + .expect(200); + expect(body).to.have.length(1); + expectSuccess(0, body); + }); - it('should return error for invalid object type', async () => - await supertestWithoutAuth + it('should return error for invalid object type', async () => { + const { body } = await supertestWithoutAuth .post(URL) .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) .send([invalidObject]) - .expect(200) - .then(({ body }) => { - expect(body).to.have.length(1); - expectBadRequest(0, body); - })); + .expect(200); + expect(body).to.have.length(1); + expectBadRequest(0, body); + }); - it('should return mix of successes and errors', async () => - await supertestWithoutAuth + it('should return mix of successes and errors', async () => { + const { body } = await supertestWithoutAuth .post(URL) .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) .send([validObject, invalidObject]) - .expect(200) - .then(({ body }) => { - expect(body).to.have.length(2); - expectSuccess(0, body); - expectBadRequest(1, body); - })); + .expect(200); + expect(body).to.have.length(2); + expectSuccess(0, body); + expectBadRequest(1, body); + }); }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/find.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/find.ts index 888c92ec30f87..8ac839ad1284c 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/find.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/find.ts @@ -132,7 +132,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('search for multiple references with OR operator', async () => { - await supertestWithoutAuth + const response = await supertestWithoutAuth .get('/api/kibana/management/saved_objects/_find') .query({ type: 'visualization', @@ -143,14 +143,10 @@ export default function ({ getService }: FtrProviderContext) { hasReferenceOperator: 'OR', }) .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .then((response) => { - expect(response.status).to.eql(200); - expect(response.body.saved_objects.length).not.to.be(null); - expect(response.body.saved_objects.map((obj: any) => obj.id).length).to.be.greaterThan( - 0 - ); - }); + .set(roleAuthc.apiKeyHeader); + expect(response.status).to.eql(200); + expect(response.body.saved_objects.length).not.to.be(null); + expect(response.body.saved_objects.map((obj: any) => obj.id).length).to.be.greaterThan(0); }); it('search for multiple references with AND operator', async () => { @@ -221,88 +217,84 @@ export default function ({ getService }: FtrProviderContext) { await kibanaServer.savedObjects.cleanStandardList(); }); - it('should inject meta attributes for searches', async () => - await supertestWithoutAuth + it('should inject meta attributes for searches', async () => { + const response = await supertestWithoutAuth .get('/api/kibana/management/saved_objects/_find?type=search') .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) - .expect(200) - .then((response) => { - expect(response.body.saved_objects).to.have.length(1); - expect(response.body.saved_objects[0].meta).to.eql({ - icon: 'discoverApp', - title: 'OneRecord', - hiddenType: false, - inAppUrl: { - path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', - uiCapabilitiesPath: 'discover.show', - }, - namespaceType: 'multiple-isolated', - }); - })); + .expect(200); + expect(response.body.saved_objects).to.have.length(1); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'discoverApp', + title: 'OneRecord', + hiddenType: false, + inAppUrl: { + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'discover.show', + }, + namespaceType: 'multiple-isolated', + }); + }); - it('should inject meta attributes for dashboards', async () => - await supertestWithoutAuth + it('should inject meta attributes for dashboards', async () => { + const response = await supertestWithoutAuth .get('/api/kibana/management/saved_objects/_find?type=dashboard') .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) - .expect(200) - .then((response) => { - expect(response.body.saved_objects).to.have.length(1); - expect(response.body.saved_objects[0].meta).to.eql({ - icon: 'dashboardApp', - title: 'Dashboard', - hiddenType: false, - inAppUrl: { - path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', - uiCapabilitiesPath: 'dashboard.show', - }, - namespaceType: 'multiple-isolated', - }); - })); + .expect(200); + expect(response.body.saved_objects).to.have.length(1); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'dashboardApp', + title: 'Dashboard', + hiddenType: false, + inAppUrl: { + path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'dashboard.show', + }, + namespaceType: 'multiple-isolated', + }); + }); - it('should inject meta attributes for visualizations', async () => - await supertestWithoutAuth + it('should inject meta attributes for visualizations', async () => { + const response = await supertestWithoutAuth .get('/api/kibana/management/saved_objects/_find?type=visualization') .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) - .expect(200) - .then((response) => { - expect(response.body.saved_objects).to.have.length(2); - expect(response.body.saved_objects[0].meta).to.eql({ - icon: 'visualizeApp', - title: 'VisualizationFromSavedSearch', - namespaceType: 'multiple-isolated', - hiddenType: false, - }); - expect(response.body.saved_objects[1].meta).to.eql({ - icon: 'visualizeApp', - title: 'Visualization', - namespaceType: 'multiple-isolated', - hiddenType: false, - }); - })); + .expect(200); + expect(response.body.saved_objects).to.have.length(2); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'visualizeApp', + title: 'VisualizationFromSavedSearch', + namespaceType: 'multiple-isolated', + hiddenType: false, + }); + expect(response.body.saved_objects[1].meta).to.eql({ + icon: 'visualizeApp', + title: 'Visualization', + namespaceType: 'multiple-isolated', + hiddenType: false, + }); + }); - it('should inject meta attributes for index patterns', async () => - await supertestWithoutAuth + it('should inject meta attributes for index patterns', async () => { + const response = await supertestWithoutAuth .get('/api/kibana/management/saved_objects/_find?type=index-pattern') .set(svlCommonApi.getInternalRequestHeader()) .set(roleAuthc.apiKeyHeader) - .expect(200) - .then((response) => { - expect(response.body.saved_objects).to.have.length(1); - expect(response.body.saved_objects[0].meta).to.eql({ - icon: 'indexPatternApp', - title: 'saved_objects*', - hiddenType: false, - editUrl: '/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', - inAppUrl: { - path: '/app/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', - uiCapabilitiesPath: 'management.kibana.indexPatterns', - }, - namespaceType: 'multiple', - }); - })); + .expect(200); + expect(response.body.saved_objects).to.have.length(1); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'indexPatternApp', + title: 'saved_objects*', + hiddenType: false, + editUrl: '/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + inAppUrl: { + path: '/app/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + namespaceType: 'multiple', + }); + }); }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/index.js b/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/index.js deleted file mode 100644 index e6dae2f948174..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/index.js +++ /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. - */ - -export default function ({ loadTestFile }) { - // TODO: The `scripts` folder was renamed to `scripts_tests` because the folder - // name `scripts` triggers the `eslint@kbn/imports/no_boundary_crossing` rule - describe('scripts', function () { - this.tags(['esGate']); - - loadTestFile(require.resolve('./languages')); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/index.ts new file mode 100644 index 0000000000000..29776682a2a35 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/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) { + // TODO: The `scripts` folder was renamed to `scripts_tests` because the folder + // name `scripts` triggers the `eslint@kbn/imports/no_boundary_crossing` rule + describe('scripts', function () { + this.tags(['esGate']); + + loadTestFile(require.resolve('./languages')); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/languages.js b/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/languages.js deleted file mode 100644 index dda9266bc28e7..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/languages.js +++ /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 expect from '@kbn/expect'; - -import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { SCRIPT_LANGUAGES_ROUTE_LATEST_VERSION } from '@kbn/data-plugin/common/constants'; - -export default function ({ getService }) { - const svlCommonApi = getService('svlCommonApi'); - const svlUserManager = getService('svlUserManager'); - let roleAuthc; - const supertestWithoutAuth = getService('supertestWithoutAuth'); - - describe('Script Languages API', function getLanguages() { - before(async () => { - roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - }); - after(async () => { - await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); - }); - it('should return 200 with an array of languages', () => - supertestWithoutAuth - .get('/internal/scripts/languages') - .set(ELASTIC_HTTP_VERSION_HEADER, SCRIPT_LANGUAGES_ROUTE_LATEST_VERSION) - // TODO: API requests in Serverless require internal request headers - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .expect(200) - .then((response) => { - expect(response.body).to.be.an('array'); - })); - - // eslint-disable-next-line jest/no-disabled-tests - it.skip('should only return langs enabled for inline scripting', () => - supertestWithoutAuth - .get('/internal/scripts/languages') - .set(ELASTIC_HTTP_VERSION_HEADER, SCRIPT_LANGUAGES_ROUTE_LATEST_VERSION) - // TODO: API requests in Serverless require internal request headers - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .expect(200) - .then((response) => { - expect(response.body).to.contain('expression'); - expect(response.body).to.contain('painless'); - expect(response.body).to.not.contain('groovy'); - })); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/languages.ts b/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/languages.ts new file mode 100644 index 0000000000000..9a2131b5defa4 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests/languages.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 expect from '@kbn/expect'; + +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { SCRIPT_LANGUAGES_ROUTE_LATEST_VERSION } from '@kbn/data-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials } from '../../../../shared/services'; + +export default function ({ getService }: FtrProviderContext) { + const svlCommonApi = getService('svlCommonApi'); + const svlUserManager = getService('svlUserManager'); + let roleAuthc: RoleCredentials; + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('Script Languages API', function getLanguages() { + before(async () => { + roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); + }); + after(async () => { + await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + it('should return 200 with an array of languages', async () => { + const response = await supertestWithoutAuth + .get('/internal/scripts/languages') + .set(ELASTIC_HTTP_VERSION_HEADER, SCRIPT_LANGUAGES_ROUTE_LATEST_VERSION) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(response.body).to.be.an('array'); + }); + + it.skip('should only return langs enabled for inline scripting', async () => { + const response = await supertestWithoutAuth + .get('/internal/scripts/languages') + .set(ELASTIC_HTTP_VERSION_HEADER, SCRIPT_LANGUAGES_ROUTE_LATEST_VERSION) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(response.body).to.contain('expression'); + expect(response.body).to.contain('painless'); + expect(response.body).to.not.contain('groovy'); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts b/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts index 2bf5eda659da4..6518dcd30f147 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/telemetry/telemetry_config.ts @@ -68,6 +68,21 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext) journeyName: 'my-ftr-test', }, }); + + // Sends "null" to remove the label + await supertestWithoutAuth + .put('/internal/core/_settings') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .set('elastic-api-version', '1') + .send({ 'telemetry.labels.journeyName': null }) + .expect(200, { ok: true }); + + await supertestWithoutAuth + .get('/api/telemetry/v2/config') + .set(svlCommonApi.getCommonRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200, initialConfig); }); }); } diff --git a/x-pack/test_serverless/functional/page_objects/svl_management_page.ts b/x-pack/test_serverless/functional/page_objects/svl_management_page.ts index fe78ec9bc07d9..e77e77c4fa76c 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_management_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_management_page.ts @@ -48,5 +48,16 @@ export function SvlManagementPageProvider({ getService }: FtrProviderContext) { async clickIngestPipelinesManagementCard() { await testSubjects.click('app-card-ingest_pipelines'); }, + + // Spaces card + async assertSpacesManagementCardExists() { + await testSubjects.existOrFail('app-card-spaces'); + }, + async assertSpacesManagementCardDoesNotExist() { + await testSubjects.missingOrFail('app-card-spaces'); + }, + async clickSpacesManagementCard() { + await testSubjects.click('app-card-spaces'); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/common/console/console.ts b/x-pack/test_serverless/functional/test_suites/common/console/console.ts index bd5d7b224fba3..b7eec68c637af 100644 --- a/x-pack/test_serverless/functional/test_suites/common/console/console.ts +++ b/x-pack/test_serverless/functional/test_suites/common/console/console.ts @@ -6,34 +6,9 @@ */ import expect from '@kbn/expect'; +import { DEFAULT_INPUT_VALUE } from '@kbn/console-plugin/common/constants'; import { FtrProviderContext } from '../../../ftr_provider_context'; -const DEFAULT_REQUEST = ` -# Welcome to the Dev Tools Console! -# -# You can use Console to explore the Elasticsearch API. See the \n Elasticsearch API reference to learn more: -# https://www.elastic.co/guide/en/elasticsearch/reference/current/rest\n -apis.html -# -# Here are a few examples to get you started. - - -# Create an index -PUT /my-index - - -# Add a document to my-index -POST /my-index/_doc -{ - "id": "park_rocky-mountain", - "title": "Rocky Mountain", - "description": "Bisected north to south by the Continental Divide, \n this portion of the Rockies has ecosystems varying from over 150 \n riparian lakes to montane and subalpine forests to treeless \n alpine tundra." -} - - -# Perform a search in my-index -GET /my-index/_search?q="rocky mountain" -`.trim(); - export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const log = getService('log'); @@ -56,18 +31,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show the default request', async () => { await retry.try(async () => { - const actualRequest = await PageObjects.console.getRequest(); + const actualRequest = await PageObjects.console.monaco.getEditorText(); log.debug(actualRequest); - expect(actualRequest.trim()).to.eql(DEFAULT_REQUEST); + expect(actualRequest.replace(/\s/g, '')).to.eql(DEFAULT_INPUT_VALUE.replace(/\s/g, '')); }); }); it('default request response should include `"timed_out" : false`', async () => { const expectedResponseContains = `"timed_out": false`; - await PageObjects.console.selectAllRequests(); + await PageObjects.console.monaco.selectAllRequests(); await PageObjects.console.clickPlay(); await retry.try(async () => { - const actualResponse = await PageObjects.console.getResponse(); + const actualResponse = await PageObjects.console.monaco.getOutputText(); log.debug(actualResponse); expect(actualResponse).to.contain(expectedResponseContains); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts index 61a20883bbad8..e03a6d2e081a4 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts @@ -33,8 +33,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -50,8 +50,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-logs | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -71,8 +71,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); @@ -87,8 +87,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-logs | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); @@ -103,7 +103,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { describe('cell renderers', () => { it('should not render custom @timestamp or log.level', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -115,7 +117,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not render custom @timestamp but should render custom log.level', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs'); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); @@ -131,7 +135,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('doc viewer extension', () => { it('should not render custom doc viewer view', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); @@ -142,7 +148,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render custom doc viewer view', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle({ rowIndex: 0 }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_root_profile.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_root_profile.ts index 8ad7d97b13da6..e7eb75384d67c 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_root_profile.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_root_profile.ts @@ -16,8 +16,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('root profile', () => { before(async () => { - await PageObjects.svlCommonPage.loginAsViewer(); + await PageObjects.svlCommonPage.loginAsAdmin(); }); + describe('ES|QL mode', () => { describe('cell renderers', () => { it('should not render custom @timestamp', async () => { @@ -25,8 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); @@ -38,7 +39,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { describe('cell renderers', () => { it('should not render custom @timestamp', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await PageObjects.discover.waitUntilSearchingHasFinished(); const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts index 2a81561199f47..7bce934099e18 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_cell_renderers.ts @@ -10,7 +10,7 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList']); + const PageObjects = getPageObjects(['common', 'discover', 'unifiedFieldList', 'svlCommonPage']); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const dataGrid = getService('dataGrid'); @@ -19,6 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('extension getCellRenderers', () => { before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); }); @@ -34,8 +35,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null', }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); @@ -54,8 +55,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { esql: 'from my-example* | sort @timestamp desc | where `log.level` is not null', }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); @@ -67,7 +68,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { it('should render log.level badge cell', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs,logstash*'); await queryBar.setQuery('log.level:*'); await queryBar.submitQuery(); @@ -82,7 +85,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it("should not render log.level badge cell if it's not a logs data source", async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-*'); await queryBar.setQuery('log.level:*'); await queryBar.submitQuery(); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_default_app_state.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_default_app_state.ts new file mode 100644 index 0000000000000..c99f556b2f52d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_default_app_state.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage', 'unifiedFieldList']); + const dataViews = getService('dataViews'); + const dataGrid = getService('dataGrid'); + const queryBar = getService('queryBar'); + const monacoEditor = getService('monacoEditor'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + + describe('extension getDefaultAppState', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); + }); + + afterEach(async () => { + await kibanaServer.uiSettings.unset('defaultColumns'); + }); + + describe('ES|QL mode', () => { + it('should render default columns and row height', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-logs', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + const rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should render default columns and row height when switching index patterns', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-*', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + let rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(3); + await monacoEditor.setCodeEditorValue('from my-example-logs'); + await queryBar.clickQuerySubmitButton(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should reset default columns and row height when clicking "New"', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-logs', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level'); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('message'); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + await dataGrid.changeRowHeightValue('Single'); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Single'); + await testSubjects.click('discoverNewButton'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should merge and dedup configured default columns with default profile columns', async () => { + await kibanaServer.uiSettings.update({ + defaultColumns: ['bad_column', 'data_stream.type', 'message'], + }); + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { + esql: 'from my-example-logs', + }, + }); + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']); + }); + }); + + // FLAKY: https://github.com/elastic/kibana/issues/189994 + describe.skip('data view mode', () => { + it('should render default columns and row height', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + const rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should render default columns and row height when switching data views', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + let rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(3); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should reset default columns and row height when clicking "New"', async () => { + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('log.level'); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('message'); + let columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'Document']); + await dataGrid.clickGridSettings(); + await dataGrid.changeRowHeightValue('Single'); + let rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Single'); + await testSubjects.click('discoverNewButton'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message']); + await dataGrid.clickGridSettings(); + rowHeightValue = await dataGrid.getCurrentRowHeightValue(); + expect(rowHeightValue).to.be('Custom'); + const rowHeightNumber = await dataGrid.getCustomRowHeightNumber(); + expect(rowHeightNumber).to.be(5); + }); + + it('should merge and dedup configured default columns with default profile columns', async () => { + await kibanaServer.uiSettings.update({ + defaultColumns: ['bad_column', 'data_stream.type', 'message'], + }); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const columns = await PageObjects.discover.getColumnHeaders(); + expect(columns).to.eql(['@timestamp', 'log.level', 'message', 'data_stream.type']); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts index 61a9684481ee2..52b514a6673b6 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_doc_viewer.ts @@ -18,14 +18,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await PageObjects.svlCommonPage.loginAsAdmin(); }); + describe('ES|QL mode', () => { it('should render logs overview tab for logs data source', async () => { const state = kbnRison.encode({ dataSource: { type: 'esql' }, query: { esql: 'from my-example-logs | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); @@ -40,8 +41,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example-metrics | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); @@ -52,7 +53,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('data view mode', () => { it('should render logs overview tab for logs data source', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-logs'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); @@ -63,7 +66,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should not render logs overview tab for non-logs data source', async () => { - await PageObjects.common.navigateToApp('discover'); + await PageObjects.common.navigateToActualUrl('discover', undefined, { + ensureCurrentUrl: false, + }); await dataViews.switchTo('my-example-metrics'); await PageObjects.discover.waitUntilSearchingHasFinished(); await dataGrid.clickRowToggle(); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.ts new file mode 100644 index 0000000000000..c91dae10bc4ea --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_additional_leading_controls.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 kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'discover', 'svlCommonPage']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('extension getRowAdditionalLeadingControls', () => { + before(async () => { + await PageObjects.svlCommonPage.loginAsAdmin(); + }); + describe('ES|QL mode', () => { + it('should render logs controls for logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-metrics | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + + describe('data view mode', () => { + it('should render logs controls for logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.existOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.existOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + + it('should not render logs controls for non-logs data source', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-metrics'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await testSubjects.missingOrFail('exampleLogsControl_visBarVerticalStacked'); + await testSubjects.missingOrFail('unifiedDataTable_additionalRowControl_menuControl'); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts index 27c27360a28b7..c7b402665e689 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/extensions/_get_row_indicator_provider.ts @@ -37,8 +37,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from logstash* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.timePicker.setDefaultAbsoluteRange(); @@ -57,8 +57,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { dataSource: { type: 'esql' }, query: { esql: 'from my-example* | sort @timestamp desc' }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); // my-example* has a log.level field, but it's not matching the logs profile, so the color indicator should not be rendered @@ -73,8 +73,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { esql: 'from my-example-logs,logstash* | sort @timestamp desc | where `log.level` is not null', }, }); - await PageObjects.common.navigateToApp('discover', { - hash: `/?_a=${state}`, + await PageObjects.common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, }); await PageObjects.discover.waitUntilSearchingHasFinished(); // in this case it's matching the logs data source profile and has a log.level field, so the color indicator should be rendered diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts index 298dad659cf43..d0e23c825870b 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts @@ -38,7 +38,9 @@ export default function ({ getService, getPageObjects, loadTestFile }: FtrProvid loadTestFile(require.resolve('./_root_profile')); loadTestFile(require.resolve('./_data_source_profile')); loadTestFile(require.resolve('./extensions/_get_row_indicator_provider')); + loadTestFile(require.resolve('./extensions/_get_row_additional_leading_controls')); loadTestFile(require.resolve('./extensions/_get_doc_viewer')); loadTestFile(require.resolve('./extensions/_get_cell_renderers')); + loadTestFile(require.resolve('./extensions/_get_default_app_state')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts index 703ff8b6b605e..0d1e2b3de6a02 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/esql/_esql_view.ts @@ -198,8 +198,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const cell = await dataGrid.getCellElementExcludingControlColumns(0, 1); expect(await cell.getVisibleText()).to.be(' - '); expect(await dataGrid.getHeaders()).to.eql([ - 'Control column', 'Select column', + 'Control column', 'Numberbytes', 'machine.ram_range', ]); @@ -260,9 +260,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectTextBaseLang(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.selectIndexPattern('logstash-*', false); + await testSubjects.click('switch-to-dataviews'); await retry.try(async () => { - await testSubjects.existOrFail('unifiedSearch_switch_modal'); + await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); }); @@ -273,19 +273,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.selectIndexPattern('logstash-*', false); + await testSubjects.click('switch-to-dataviews'); await retry.try(async () => { - await testSubjects.existOrFail('unifiedSearch_switch_modal'); + await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); await find.clickByCssSelector( - '[data-test-subj="unifiedSearch_switch_modal"] .euiModal__closeIcon' + '[data-test-subj="discover-esql-to-dataview-modal"] .euiModal__closeIcon' ); await retry.try(async () => { - await testSubjects.missingOrFail('unifiedSearch_switch_modal'); + await testSubjects.missingOrFail('discover-esql-to-dataview-modal'); }); await PageObjects.discover.saveSearch('esql_test'); - await PageObjects.discover.selectIndexPattern('logstash-*'); - await testSubjects.missingOrFail('unifiedSearch_switch_modal'); + await testSubjects.click('switch-to-dataviews'); + await testSubjects.missingOrFail('discover-esql-to-dataview-modal'); }); it('should show switch modal when switching to a data view while a saved search with unsaved changes is open', async () => { @@ -298,9 +298,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('querySubmitButton'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await PageObjects.discover.selectIndexPattern('logstash-*', false); + await testSubjects.click('switch-to-dataviews'); await retry.try(async () => { - await testSubjects.existOrFail('unifiedSearch_switch_modal'); + await testSubjects.existOrFail('discover-esql-to-dataview-modal'); }); }); }); @@ -346,7 +346,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); const historyItems = await esql.getHistoryItems(); log.debug(historyItems); @@ -369,7 +368,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); const historyItems = await esql.getHistoryItems(); log.debug(historyItems); @@ -386,7 +384,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); // click a history item await esql.clickHistoryItem(1); @@ -412,7 +409,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.unifiedFieldList.waitUntilSidebarHasLoaded(); - await testSubjects.click('TextBasedLangEditor-expand'); await testSubjects.click('TextBasedLangEditor-toggle-query-history-button'); await testSubjects.click('TextBasedLangEditor-queryHistory-runQuery-button'); const historyItem = await esql.getHistoryItem(0); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts index a5274115cfbaf..531202481ba84 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group1/_discover.ts @@ -221,8 +221,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'America/Phoenix' }); await PageObjects.common.navigateToApp('discover'); await PageObjects.header.awaitKibanaChrome(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); await queryBar.clearQuery(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); log.debug( 'check that the newest doc timestamp is now -7 hours from the UTC time in the first test' diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts index a9d88803d0b4b..723acdaf9c3ed 100644 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts +++ b/x-pack/test_serverless/functional/test_suites/common/platform_security/navigation/management_nav_cards.ts @@ -5,6 +5,9 @@ * 2.0. */ +// Note: this suite is currently only called from the feature flags test config: +// x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts + import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -59,6 +62,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // `--xpack.cloud.organization_url: '/account/members'`, expect(url).to.contain('/account/members'); }); + + it('displays the spaces management card, and will navigate to the spaces management UI', async () => { + await pageObjects.svlManagementPage.assertSpacesManagementCardExists(); + await pageObjects.svlManagementPage.clickSpacesManagementCard(); + + const url = await browser.getCurrentUrl(); + expect(url).to.contain('/management/kibana/spaces'); + }); }); describe('as viewer', function () { @@ -96,6 +107,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(url).to.contain('/account/members'); }); + it('should not display the spaces management card', async () => { + await retry.waitFor('page to be visible', async () => { + return await testSubjects.exists('cards-navigation-page'); + }); + await pageObjects.svlManagementPage.assertSpacesManagementCardDoesNotExist(); + }); + describe('API keys management card - search solution', function () { this.tags(['skipSvlOblt', 'skipSvlSec']); diff --git a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts index 80de679ca7b49..fa7de0335ff8d 100644 --- a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts +++ b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/export_transform.ts @@ -42,7 +42,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('allows to mutate the objects during an export', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -50,24 +50,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-transform'], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ - { - id: 'type_1-obj_1', - enabled: false, - }, - { - id: 'type_1-obj_2', - enabled: false, - }, - ]); - }); + .expect(200); + + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => ({ id: obj.id, enabled: obj.attributes.enabled }))).to.eql([ + { + id: 'type_1-obj_1', + enabled: false, + }, + { + id: 'type_1-obj_2', + enabled: false, + }, + ]); }); it('allows to add additional objects to an export', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -80,15 +79,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql(['type_2-obj_1', 'type_dep-obj_1']); }); it('allows to add additional objects to an export when exporting by type', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -96,20 +93,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-add'], excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.map((obj) => obj.id)).to.eql([ - 'type_2-obj_1', - 'type_2-obj_2', - 'type_dep-obj_1', - 'type_dep-obj_2', - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_2-obj_1', + 'type_2-obj_2', + 'type_dep-obj_1', + 'type_dep-obj_2', + ]); }); it('returns a 400 when the type causes a transform error', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -117,21 +112,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-transform-error'], excludeExportDetails: true, }) - .expect(400) - .then((resp) => { - const { attributes, ...error } = resp.body; - expect(error).to.eql({ - error: 'Bad Request', - message: 'Error transforming objects to export', - statusCode: 400, - }); - expect(attributes.cause).to.eql('Error during transform'); - expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); - }); + .expect(400); + const { attributes, ...error } = resp.body; + expect(error).to.eql({ + error: 'Bad Request', + message: 'Error transforming objects to export', + statusCode: 400, + }); + expect(attributes.cause).to.eql('Error during transform'); + expect(attributes.objects.map((obj: any) => obj.id)).to.eql(['type_4-obj_1']); }); it('returns a 400 when the type causes an invalid transform', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -139,17 +132,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { type: ['test-export-invalid-transform'], excludeExportDetails: true, }) - .expect(400) - .then((resp) => { - expect(resp.body).to.eql({ - error: 'Bad Request', - message: 'Invalid transform performed on objects to export', - statusCode: 400, - attributes: { - objectKeys: ['test-export-invalid-transform|type_3-obj_1'], - }, - }); - }); + .expect(400); + expect(resp.body).to.eql({ + error: 'Bad Request', + message: 'Invalid transform performed on objects to export', + statusCode: 400, + attributes: { + objectKeys: ['test-export-invalid-transform|type_3-obj_1'], + }, + }); }); }); @@ -172,7 +163,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('execute export transforms for reference objects', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -186,21 +177,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text).sort((obj1, obj2) => - obj1.id.localeCompare(obj2.id) - ); - expect(objects.map((obj) => obj.id)).to.eql([ - 'type_1-obj_1', - 'type_1-obj_2', - 'type_2-obj_1', - 'type_dep-obj_1', - ]); + .expect(200); + const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id)); + expect(objects.map((obj) => obj.id)).to.eql([ + 'type_1-obj_1', + 'type_1-obj_2', + 'type_2-obj_1', + 'type_dep-obj_1', + ]); - expect(objects[0].attributes.enabled).to.eql(false); - expect(objects[1].attributes.enabled).to.eql(false); - }); + expect(objects[0].attributes.enabled).to.eql(false); + expect(objects[1].attributes.enabled).to.eql(false); }); }); @@ -223,7 +210,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should only export objects returning `true` for `isExportable`', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -237,21 +224,17 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: true, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text).sort((obj1, obj2) => - obj1.id.localeCompare(obj2.id) - ); - expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([ - 'test-is-exportable:1', - 'test-is-exportable:3', - 'test-is-exportable:5', - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text).sort((obj1, obj2) => obj1.id.localeCompare(obj2.id)); + expect(objects.map((obj) => `${obj.type}:${obj.id}`)).to.eql([ + 'test-is-exportable:1', + 'test-is-exportable:3', + 'test-is-exportable:5', + ]); }); it('lists objects that got filtered', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -265,31 +248,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: false, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - const exportDetails = objects[ - objects.length - 1 - ] as unknown as SavedObjectsExportResultDetails; + .expect(200); + const objects = parseNdJson(resp.text); + const exportDetails = objects[ + objects.length - 1 + ] as unknown as SavedObjectsExportResultDetails; - expect(exportDetails.excludedObjectsCount).to.eql(2); - expect(exportDetails.excludedObjects).to.eql([ - { - type: 'test-is-exportable', - id: '2', - reason: 'excluded', - }, - { - type: 'test-is-exportable', - id: '4', - reason: 'excluded', - }, - ]); - }); + expect(exportDetails.excludedObjectsCount).to.eql(2); + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: '2', + reason: 'excluded', + }, + { + type: 'test-is-exportable', + id: '4', + reason: 'excluded', + }, + ]); }); it('excludes objects if `isExportable` throws', async () => { - await supertest + const resp = await supertest .post('/api/saved_objects/_export') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) @@ -307,24 +288,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { includeReferencesDeep: true, excludeExportDetails: false, }) - .expect(200) - .then((resp) => { - const objects = parseNdJson(resp.text); - expect(objects.length).to.eql(2); - expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql([ - 'test-is-exportable:5', - ]); - const exportDetails = objects[ - objects.length - 1 - ] as unknown as SavedObjectsExportResultDetails; - expect(exportDetails.excludedObjects).to.eql([ - { - type: 'test-is-exportable', - id: 'error', - reason: 'predicate_error', - }, - ]); - }); + .expect(200); + const objects = parseNdJson(resp.text); + expect(objects.length).to.eql(2); + expect([objects[0]].map((obj) => `${obj.type}:${obj.id}`)).to.eql(['test-is-exportable:5']); + const exportDetails = objects[ + objects.length - 1 + ] as unknown as SavedObjectsExportResultDetails; + expect(exportDetails.excludedObjects).to.eql([ + { + type: 'test-is-exportable', + id: 'error', + reason: 'predicate_error', + }, + ]); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts index 986479518bfd4..c792d0f7fdbc1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts +++ b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/find.ts @@ -44,37 +44,35 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await kibanaServer.savedObjects.cleanStandardList(); }); - it('returns saved objects with importableAndExportable types', async () => - await supertest + it('returns saved objects with importableAndExportable types', async () => { + const resp = await supertest .get('/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable') .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) - .expect(200) - .then((resp) => { - expect( - resp.body.saved_objects.map((so: { id: string; type: string }) => ({ - id: so.id, - type: so.type, - })) - ).to.eql([ - { - type: 'test-hidden-importable-exportable', - id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab', - }, - ]); - })); + .expect(200); + expect( + resp.body.saved_objects.map((so: { id: string; type: string }) => ({ + id: so.id, + type: so.type, + })) + ).to.eql([ + { + type: 'test-hidden-importable-exportable', + id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab', + }, + ]); + }); - it('returns empty response for non importableAndExportable types', async () => - await supertest + it('returns empty response for non importableAndExportable types', async () => { + const resp = await supertest .get( '/api/kibana/management/saved_objects/_find?type=test-hidden-non-importable-exportable' ) .set(svlCommonApi.getCommonRequestHeader()) .set(svlCommonApi.getInternalRequestHeader()) - .expect(200) - .then((resp) => { - expect(resp.body.saved_objects).to.eql([]); - })); + .expect(200); + expect(resp.body.saved_objects).to.eql([]); + }); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/hidden_from_http_apis.ts b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/hidden_from_http_apis.ts index d5edfb0e73bf2..6857dd73a2c4f 100644 --- a/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/hidden_from_http_apis.ts +++ b/x-pack/test_serverless/functional/test_suites/common/saved_objects_management/hidden_from_http_apis.ts @@ -11,6 +11,11 @@ import type { Response } from 'supertest'; import { SavedObject } from '@kbn/core/types'; import { FtrProviderContext } from '../../../ftr_provider_context'; +interface MinimalSO { + id: string; + type: string; +} + function parseNdJson(input: string): Array<SavedObject<any>> { return input.split('\n').map((str) => JSON.parse(str)); } @@ -118,10 +123,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { .expect(200) .then((resp) => { expect( - resp.body.saved_objects.map((so: { id: string; type: string }) => ({ - id: so.id, - type: so.type, - })) + resp.body.saved_objects + .map((so: MinimalSO) => ({ + id: so.id, + type: so.type, + })) + .sort((a: MinimalSO, b: MinimalSO) => (a.id > b.id ? 1 : -1)) ).to.eql([ { id: 'hidden-from-http-apis-1', diff --git a/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection_enabled.ts b/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection_enabled.ts new file mode 100644 index 0000000000000..e998b25d24a11 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/spaces/spaces_selection_enabled.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. + */ + +// Note: this suite is currently only called from the feature flags test config: +// x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts +// This file can take the place of the spaces_selection test file when spaces +// are enabled permanently in serverless. + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObject, getService }: FtrProviderContext) { + const svlCommonPage = getPageObject('svlCommonPage'); + const svlCommonNavigation = getService('svlCommonNavigation'); + const testSubjects = getService('testSubjects'); + + describe('space selection', function () { + describe('as Viewer', function () { + before(async () => { + await svlCommonPage.loginAsViewer(); + }); + + it('displays the space selection menu in header', async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + + await testSubjects.existOrFail('spacesNavSelector'); + }); + + it(`does not display the manage button in the space selection menu`, async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + await testSubjects.click('spacesNavSelector'); + await testSubjects.missingOrFail('manageSpaces'); + }); + }); + + describe('as Admin', function () { + before(async () => { + await svlCommonPage.loginAsAdmin(); + }); + + it('displays the space selection menu in header', async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + await testSubjects.existOrFail('spacesNavSelector'); + }); + + it(`displays the manage button in the space selection menu`, async () => { + await svlCommonNavigation.navigateToKibanaHome(); + await svlCommonPage.assertProjectHeaderExists(); + await testSubjects.click('spacesNavSelector'); + await testSubjects.existOrFail('manageSpaces'); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts index ffbc3365ba930..f28a8bdb40377 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/config.feature_flags.ts @@ -24,6 +24,7 @@ export default createTestConfig({ '--xpack.security.roleManagementEnabled=true', `--xpack.cloud.base_url='https://cloud.elastic.co'`, `--xpack.cloud.organization_url='/account/members'`, + `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities ], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], diff --git a/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts index ef9523962e4af..24a7c41caeaee 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/index.feature_flags.ts @@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./infra')); loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts')); loadTestFile(require.resolve('../common/platform_security/roles.ts')); + loadTestFile(require.resolve('../common/spaces/spaces_selection_enabled.ts')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts index 8e7294d35f1b8..6fcfea151db12 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts @@ -86,7 +86,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render content virtual column properly', async () => { it('should render log level and log message when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('A sample log')).to.be(true); @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render log message when present and skip log level when missing', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(false); expect(cellValue.includes('A sample log')).to.be(true); @@ -104,7 +104,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from error object when top level message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(2, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('error.message')).to.be(true); @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render message from event.original when top level message and error.message not present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(3, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); expect(cellValue.includes('event.original')).to.be(true); @@ -124,7 +124,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the whole JSON when neither message, error.message and event.original are present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('info')).to.be(true); @@ -138,7 +138,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with no message field should open JSON Viewer', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(4, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); }); }); @@ -146,7 +146,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('on cell expansion with message field should open regular popover', async () => { await navigateToLogsExplorer(); await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButton(3, 4); + await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); await testSubjects.existOrFail('euiDataGridExpansionPopover'); }); }); @@ -155,7 +155,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('render resource virtual column properly', async () => { it('should render service name and host name when present', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const cellValue = await cellElement.getVisibleText(); expect(cellValue.includes('synth-service')).to.be(true); expect(cellValue.includes('synth-host')).to.be(true); @@ -169,7 +169,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should render a popover with cell actions when a chip on content column is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); await logLevelChip.click(); // Check Filter In button is present @@ -183,7 +183,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; @@ -204,7 +204,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 4); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; @@ -223,7 +223,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the table filtered where service.name value is selected', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 3); + const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); const serviceNameChip = await cellElement.findByTestSubject( 'dataTablePopoverChip_service.name' ); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts index fea974fd0096e..c1f4692f19fdf 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts @@ -48,7 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render control column with proper header', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', 'actions']); + expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); }); }); @@ -60,27 +60,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should render the malformed icon in the leading control column if malformed doc exists', async () => { + it('should render the degraded icon in the leading control column if degraded doc exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); - const malformedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); - expect(malformedButton).to.not.be.empty(); + const cellElement = await dataGrid.getCellElement(1, 2); + const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); + expect(degradedButton).to.not.be.empty(); }); }); - it('should render the disabled malformed icon in the leading control column when malformed doc does not exists', async () => { + it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); - const malformedDisableButton = await cellElement.findByTestSubject( + const cellElement = await dataGrid.getCellElement(0, 2); + const degradedDisableButton = await cellElement.findByTestSubject( 'docTableDegradedDocDoesNotExist' ); - expect(malformedDisableButton).to.not.be.empty(); + expect(degradedDisableButton).to.not.be.empty(); }); }); it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 1); + const cellElement = await dataGrid.getCellElement(4, 3); const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); expect(stacktraceButton).to.not.be.empty(); }); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 1); + const cellElement = await dataGrid.getCellElement(1, 3); const stacktraceButton = await cellElement.findByTestSubject( 'docTableStacktraceDoesNotExist' ); @@ -116,10 +116,7 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { }) ); - const malformedDocs = timerange( - moment(to).subtract(2, 'second'), - moment(to).subtract(1, 'second') - ) + const degradedDocs = timerange(moment(to).subtract(2, 'second'), moment(to).subtract(1, 'second')) .interval('1m') .rate(1) .generator((timestamp) => @@ -128,7 +125,7 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { .map(() => { return log .create() - .message('A malformed doc') + .message('A degraded doc') .logLevel(MORE_THAN_1024_CHARS) .timestamp(timestamp) .defaults({ 'service.name': 'synth-service' }); @@ -186,5 +183,5 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { }) ); - return [logs, malformedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; + return [logs, degradedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; } diff --git a/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts index c4b29fdfb443d..ee652f0f7eb2d 100644 --- a/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/search/config.feature_flags.ts @@ -24,6 +24,7 @@ export default createTestConfig({ `--xpack.cloud.base_url='https://cloud.elastic.co'`, `--xpack.cloud.organization_url='/account/members'`, `--xpack.security.roleManagementEnabled=true`, + `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities ], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], diff --git a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts index 2126b0c7d9839..acf3e6f98b7db 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.feature_flags.ts @@ -12,5 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { // add tests that require feature flags, defined in config.feature_flags.ts loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts')); loadTestFile(require.resolve('../common/platform_security/roles.ts')); + loadTestFile(require.resolve('../common/spaces/spaces_selection_enabled.ts')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts index e5ef4f8d04af6..476b47b060b2c 100644 --- a/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts +++ b/x-pack/test_serverless/functional/test_suites/search/search_playground/playground_overview.ts @@ -192,6 +192,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'You are a fireman in london that helps answering question-answering tasks.' ); }); + + it("saves a session to localstorage when it's updated", async () => { + await pageObjects.searchPlayground.session.setSession(); + await browser.refresh(); + await pageObjects.searchPlayground.PlaygroundChatPage.navigateToChatPage(); + await pageObjects.searchPlayground.PlaygroundChatPage.updatePrompt("You're a doctor"); + await pageObjects.searchPlayground.PlaygroundChatPage.updateQuestion('i have back pain'); + await pageObjects.searchPlayground.session.expectInSession('prompt', "You're a doctor"); + await pageObjects.searchPlayground.session.expectInSession('question', undefined); + }); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts index beddcaddf7208..84e80f154bee9 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.feature_flags.ts @@ -22,6 +22,7 @@ export default createTestConfig({ `--xpack.security.roleManagementEnabled=true`, `--xpack.cloud.base_url='https://cloud.elastic.co'`, `--xpack.cloud.organization_url='/account/members'`, + `--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities ], // load tests in the index file testFiles: [require.resolve('./index.feature_flags.ts')], diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts index efb1c1da15d05..882c348b9b7e6 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_aws.ts @@ -5,17 +5,13 @@ * 2.0. */ const CIS_AWS_OPTION_TEST_ID = 'cisAwsTestId'; -const AWS_CREDENTIAL_SELECTOR = 'aws-credentials-type-selector'; -const SETUP_TECHNOLOGY_SELECTOR = 'setup-technology-selector'; -const SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ = 'setup-technology-selector-accordion'; - const AWS_SINGLE_ACCOUNT_TEST_ID = 'awsSingleTestId'; import { CLOUD_CREDENTIALS_PACKAGE_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../../../../ftr_provider_context'; -export default function ({ getPageObjects, getService }: FtrProviderContext) { +export default function ({ getPageObjects }: FtrProviderContext) { const pageObjects = getPageObjects([ 'settings', 'common', @@ -45,11 +41,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); await cisIntegration.clickOptionButton(AWS_SINGLE_ACCOUNT_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agentless'); - await cisIntegration.clickOptionButton(AWS_CREDENTIAL_SELECTOR); - await cisIntegration.selectValue(AWS_CREDENTIAL_SELECTOR, 'direct_access_keys'); + + await cisIntegration.selectSetupTechnology('agentless'); + await cisIntegration.selectAwsCredentials('direct'); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -63,11 +57,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); await cisIntegration.clickOptionButton(AWS_SINGLE_ACCOUNT_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agent-based'); - await cisIntegration.clickOptionButton(AWS_CREDENTIAL_SELECTOR); - await cisIntegration.selectValue(AWS_CREDENTIAL_SELECTOR, 'temporary_keys'); + await cisIntegration.selectSetupTechnology('agentless'); + + await cisIntegration.selectAwsCredentials('temporary'); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -76,17 +68,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); describe('Agentless CIS_AWS ORG Account Launch Cloud formation', () => { - it(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`, async () => { + // tech debt: this test is failing because the credentials select is not working as expected + // https://github.com/orgs/elastic/projects/705/views/92?sliceBy%5Bvalue%5D=Agentless+-+API+-+ESS&pane=issue&itemId=73261952 + it.skip(`should show CIS_AWS Launch Cloud formation button when credentials selector is direct access keys and package version is ${CLOUD_CREDENTIALS_PACKAGE_VERSION}`, async () => { await cisIntegration.navigateToAddIntegrationCspmWithVersionPage( CLOUD_CREDENTIALS_PACKAGE_VERSION ); await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agentless'); - await cisIntegration.clickOptionButton(AWS_CREDENTIAL_SELECTOR); - await cisIntegration.selectValue(AWS_CREDENTIAL_SELECTOR, 'direct_access_keys'); + + await cisIntegration.selectSetupTechnology('agentless'); + + await cisIntegration.selectAwsCredentials('direct'); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -97,11 +90,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.navigateToAddIntegrationCspmWithVersionPage(previousPackageVersion); await cisIntegration.clickOptionButton(CIS_AWS_OPTION_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agentless'); - await cisIntegration.clickOptionButton(AWS_CREDENTIAL_SELECTOR); - await cisIntegration.selectValue(AWS_CREDENTIAL_SELECTOR, 'temporary_keys'); + await cisIntegration.selectSetupTechnology('agentless'); + + await cisIntegration.selectAwsCredentials('temporary'); await pageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts index 271639e7c552c..4c87e8fa23b60 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/agentless/cis_integration_gcp.ts @@ -6,8 +6,6 @@ */ const CIS_GCP_OPTION_TEST_ID = 'cisGcpTestId'; const GCP_SINGLE_ACCOUNT_TEST_ID = 'gcpSingleAccountTestId'; -const SETUP_TECHNOLOGY_SELECTOR = 'setup-technology-selector'; -const SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ = 'setup-technology-selector-accordion'; import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../../../../ftr_provider_context'; @@ -22,6 +20,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { this.tags(['skipMKI', 'cloud_security_posture_cis_integration']); let cisIntegration: typeof pageObjects.cisAddIntegration; let cisIntegrationGcp: typeof pageObjects.cisAddIntegration.cisGcp; + before(async () => { await pageObjects.svlCommonPage.loginAsAdmin(); cisIntegration = pageObjects.cisAddIntegration; @@ -36,9 +35,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.clickOptionButton(CIS_GCP_OPTION_TEST_ID); await cisIntegration.clickOptionButton(GCP_SINGLE_ACCOUNT_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agentless'); + + await cisIntegration.selectSetupTechnology('agentless'); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -50,9 +48,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.clickOptionButton(CIS_GCP_OPTION_TEST_ID); await cisIntegration.clickOptionButton(GCP_SINGLE_ACCOUNT_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agentless'); + await cisIntegration.selectSetupTechnology('agentless'); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -67,9 +63,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await cisIntegration.clickOptionButton(CIS_GCP_OPTION_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agentless'); + await cisIntegration.selectSetupTechnology('agentless'); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -80,9 +74,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await cisIntegration.navigateToAddIntegrationCspmWithVersionPage(previousPackageVersion); await cisIntegration.clickOptionButton(CIS_GCP_OPTION_TEST_ID); - await cisIntegration.clickAccordianButton(SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ); - await cisIntegration.clickOptionButton(SETUP_TECHNOLOGY_SELECTOR); - await cisIntegration.selectValue(SETUP_TECHNOLOGY_SELECTOR, 'agentless'); + await cisIntegration.selectSetupTechnology('agentless'); await pageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts b/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts index 7a0c4fbe8bd0e..a260942f1a4fc 100644 --- a/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts +++ b/x-pack/test_serverless/functional/test_suites/security/index.feature_flags.ts @@ -12,5 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { // add tests that require feature flags, defined in config.feature_flags.ts loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts')); loadTestFile(require.resolve('../common/platform_security/roles.ts')); + loadTestFile(require.resolve('../common/spaces/spaces_selection_enabled.ts')); }); } diff --git a/x-pack/test_serverless/kibana.jsonc b/x-pack/test_serverless/kibana.jsonc index 360a8efe48141..2ad994687ff92 100644 --- a/x-pack/test_serverless/kibana.jsonc +++ b/x-pack/test_serverless/kibana.jsonc @@ -1,6 +1,6 @@ { "type": "test-helper", "id": "@kbn/test-suites-serverless", - "owner": "@elastic/appex-qa", + "owner": [], "devOnly": true } diff --git a/x-pack/test_serverless/shared/config.base.ts b/x-pack/test_serverless/shared/config.base.ts index 6d9e134baef6f..099bc6455db3d 100644 --- a/x-pack/test_serverless/shared/config.base.ts +++ b/x-pack/test_serverless/shared/config.base.ts @@ -139,6 +139,9 @@ export default async () => { })}`, '--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', `--server.publicBaseUrl=${servers.kibana.protocol}://${servers.kibana.hostname}:${servers.kibana.port}`, + // configure security reponse header report-to settings to mimic MKI configuration + `--csp.report_to=${JSON.stringify(['violations-endpoint'])}`, + `--permissionsPolicy.report_to=${JSON.stringify(['violations-endpoint'])}`, ], }, diff --git a/x-pack/test_serverless/shared/services/bsearch_secure.ts b/x-pack/test_serverless/shared/services/bsearch_secure.ts index 14a373ab99686..7ebe89bed8247 100644 --- a/x-pack/test_serverless/shared/services/bsearch_secure.ts +++ b/x-pack/test_serverless/shared/services/bsearch_secure.ts @@ -11,10 +11,10 @@ import expect from '@kbn/expect'; import { GenericFtrService } from '@kbn/test'; import request from 'superagent'; -import type SuperTest from 'supertest'; import type { IEsSearchResponse } from '@kbn/search-types'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; +import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { FtrProviderContext } from '../../functional/ftr_provider_context'; const parseBfetchResponse = (resp: request.Response): Array<Record<string, any>> => { @@ -24,8 +24,8 @@ const parseBfetchResponse = (resp: request.Response): Array<Record<string, any>> .map((item) => JSON.parse(item)); }; -interface SendOptions { - supertestWithoutAuth: SuperTest.Agent; +export interface SendOptions { + supertestWithoutAuth: SupertestWithoutAuthProviderType; apiKeyHeader: { Authorization: string }; referer?: string; kibanaVersion?: string; diff --git a/x-pack/test_serverless/shared/services/index.ts b/x-pack/test_serverless/shared/services/index.ts index 1241c0c9aea37..631d047d8fcfb 100644 --- a/x-pack/test_serverless/shared/services/index.ts +++ b/x-pack/test_serverless/shared/services/index.ts @@ -5,21 +5,25 @@ * 2.0. */ -import { SupertestProvider, SupertestWithoutAuthProvider } from './supertest'; +import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { SupertestProvider } from './supertest'; import { SvlCommonApiServiceProvider } from './svl_common_api'; import { SvlReportingServiceProvider } from './svl_reporting'; -import { SvlUserManagerProvider } from './svl_user_manager'; import { DataViewApiProvider } from './data_view_api'; -export type { RoleCredentials } from './svl_user_manager'; -export type { InternalRequestHeader } from './svl_common_api'; -export type { SupertestWithoutAuthType } from './supertest'; +export type { + InternalRequestHeader, + RoleCredentials, + SupertestWithoutAuthProviderType, +} from '@kbn/ftr-common-functional-services'; + +const SupertestWithoutAuthProvider = commonFunctionalServices.supertestWithoutAuth; export const services = { supertest: SupertestProvider, supertestWithoutAuth: SupertestWithoutAuthProvider, svlCommonApi: SvlCommonApiServiceProvider, svlReportingApi: SvlReportingServiceProvider, - svlUserManager: SvlUserManagerProvider, + svlUserManager: commonFunctionalServices.samlAuth, dataViewApi: DataViewApiProvider, }; diff --git a/x-pack/test_serverless/shared/services/supertest.ts b/x-pack/test_serverless/shared/services/supertest.ts index 7159a85377626..f76f7f7714400 100644 --- a/x-pack/test_serverless/shared/services/supertest.ts +++ b/x-pack/test_serverless/shared/services/supertest.ts @@ -7,9 +7,7 @@ import { format as formatUrl } from 'url'; import supertest from 'supertest'; -import { ProvidedType } from '@kbn/test'; import { FtrProviderContext } from '../../functional/ftr_provider_context'; -export type SupertestWithoutAuthType = ProvidedType<typeof SupertestWithoutAuthProvider>; /** * Returns supertest.SuperTest<supertest.Test> instance that will not persist cookie between API requests. */ @@ -19,20 +17,3 @@ export function SupertestProvider({ getService }: FtrProviderContext) { return supertest(kbnUrl); } - -/** - * Returns supertest.SuperTest<supertest.Test> instance that will not persist cookie between API requests. - * If you need to pass certificate, do the following: - * await supertestWithoutAuth - * .get('/abc') - * .ca(CA_CERT) - */ -export function SupertestWithoutAuthProvider({ getService }: FtrProviderContext) { - const config = getService('config'); - const kbnUrl = formatUrl({ - ...config.get('servers.kibana'), - auth: false, - }); - - return supertest(kbnUrl); -} diff --git a/x-pack/test_serverless/shared/services/svl_common_api.ts b/x-pack/test_serverless/shared/services/svl_common_api.ts index 99ffe486dd4d7..f207c0ed3bb0e 100644 --- a/x-pack/test_serverless/shared/services/svl_common_api.ts +++ b/x-pack/test_serverless/shared/services/svl_common_api.ts @@ -22,10 +22,11 @@ export type InternalRequestHeader = typeof INTERNAL_REQUEST_HEADERS; export function SvlCommonApiServiceProvider({}: FtrProviderContext) { return { + // call it from 'samlAuth' service when tests are migrated to deployment-agnostic getCommonRequestHeader() { return COMMON_REQUEST_HEADERS; }, - + // call it from 'samlAuth' service when tests are migrated to deployment-agnostic getInternalRequestHeader(): InternalRequestHeader { return INTERNAL_REQUEST_HEADERS; }, diff --git a/x-pack/test_serverless/shared/services/svl_reporting.ts b/x-pack/test_serverless/shared/services/svl_reporting.ts index cd231ee8e77e1..9d3d7941ec503 100644 --- a/x-pack/test_serverless/shared/services/svl_reporting.ts +++ b/x-pack/test_serverless/shared/services/svl_reporting.ts @@ -11,8 +11,8 @@ import type { ReportingJobResponse } from '@kbn/reporting-plugin/server/types'; import { REPORTING_DATA_STREAM_WILDCARD_WITH_LEGACY } from '@kbn/reporting-server'; import rison from '@kbn/rison'; import { FtrProviderContext } from '../../functional/ftr_provider_context'; -import { RoleCredentials } from './svl_user_manager'; -import { InternalRequestHeader } from './svl_common_api'; +import { RoleCredentials } from '.'; +import { InternalRequestHeader } from '.'; const API_HEADER: [string, string] = ['kbn-xsrf', 'reporting']; diff --git a/x-pack/test_serverless/shared/services/svl_user_manager.ts b/x-pack/test_serverless/shared/services/svl_user_manager.ts deleted file mode 100644 index 2bb8cbc5fab40..0000000000000 --- a/x-pack/test_serverless/shared/services/svl_user_manager.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ServerlessProjectType, SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; -import { SamlSessionManager } from '@kbn/test'; -import { readRolesDescriptorsFromResource } from '@kbn/es'; -import { resolve } from 'path'; -import { Role } from '@kbn/test/src/auth/types'; -import { isServerlessProjectType } from '@kbn/es/src/utils'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../functional/ftr_provider_context'; - -export interface RoleCredentials { - apiKey: { id: string; name: string }; - apiKeyHeader: { Authorization: string }; - cookieHeader: { Cookie: string }; -} - -export function SvlUserManagerProvider({ getService }: FtrProviderContext) { - const config = getService('config'); - const log = getService('log'); - const svlCommonApi = getService('svlCommonApi'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const isCloud = !!process.env.TEST_CLOUD; - const kbnServerArgs = config.get('kbnTestServer.serverArgs') as string[]; - const projectType = kbnServerArgs - .filter((arg) => arg.startsWith('--serverless')) - .reduce((acc, arg) => { - const match = arg.match(/--serverless[=\s](\w+)/); - return acc + (match ? match[1] : ''); - }, '') as ServerlessProjectType; - - if (!isServerlessProjectType(projectType)) { - throw new Error(`Unsupported serverless projectType: ${projectType}`); - } - - const supportedRoleDescriptors = readRolesDescriptorsFromResource( - resolve(SERVERLESS_ROLES_ROOT_PATH, projectType, 'roles.yml') - ); - - const supportedRoles = Object.keys(supportedRoleDescriptors); - - const defaultRolesToMap = new Map<string, Role>([ - ['es', 'developer'], - ['security', 'editor'], - ['oblt', 'editor'], - ]); - - const getDefaultRole = () => { - if (defaultRolesToMap.has(projectType)) { - return defaultRolesToMap.get(projectType)!; - } else { - throw new Error(`Default role is not defined for ${projectType} project`); - } - }; - - const customRolesFileName: string | undefined = process.env.ROLES_FILENAME_OVERRIDE; - // Sharing the instance within FTR config run means cookies are persistent for each role between tests. - const sessionManager = new SamlSessionManager( - { - hostOptions: { - protocol: config.get('servers.kibana.protocol'), - hostname: config.get('servers.kibana.hostname'), - port: isCloud ? undefined : config.get('servers.kibana.port'), - username: config.get('servers.kibana.username'), - password: config.get('servers.kibana.password'), - }, - log, - isCloud, - supportedRoles, - }, - customRolesFileName - ); - - const DEFAULT_ROLE = getDefaultRole(); - - return { - async getInteractiveUserSessionCookieWithRoleScope(role: string) { - return sessionManager.getInteractiveUserSessionCookieWithRoleScope(role); - }, - async getM2MApiCredentialsWithRoleScope(role: string) { - return sessionManager.getApiCredentialsForRole(role); - }, - async getEmail(role: string) { - return sessionManager.getEmail(role); - }, - - async getUserData(role: string) { - return sessionManager.getUserData(role); - }, - async createM2mApiKeyWithDefaultRoleScope() { - log.debug(`Creating api key for default role: [${this.DEFAULT_ROLE}]`); - return this.createM2mApiKeyWithRoleScope(this.DEFAULT_ROLE); - }, - async createM2mApiKeyWithRoleScope(role: string): Promise<RoleCredentials> { - // Get admin credentials in order to create the API key - const adminCookieHeader = await this.getM2MApiCredentialsWithRoleScope('admin'); - - // Get the role descrtiptor for the role - let roleDescriptors = {}; - if (role !== 'admin') { - const roleDescriptor = supportedRoleDescriptors[role]; - if (!roleDescriptor) { - throw new Error(`Cannot create API key for non-existent role "${role}"`); - } - log.debug( - `Creating api key for ${role} role with the following privileges ${JSON.stringify( - roleDescriptor - )}` - ); - roleDescriptors = { - [role]: roleDescriptor, - }; - } - - const { body, status } = await supertestWithoutAuth - .post('/internal/security/api_key') - .set(svlCommonApi.getInternalRequestHeader()) - .set(adminCookieHeader) - .send({ - name: 'myTestApiKey', - metadata: {}, - role_descriptors: roleDescriptors, - }); - expect(status).to.be(200); - - const apiKey = body; - const apiKeyHeader = { Authorization: 'ApiKey ' + apiKey.encoded }; - - log.debug(`Created api key for role: [${role}]`); - return { apiKey, apiKeyHeader, cookieHeader: adminCookieHeader }; - }, - async invalidateM2mApiKeyWithRoleScope(roleCredentials: RoleCredentials) { - const requestBody = { - apiKeys: [ - { - id: roleCredentials.apiKey.id, - name: roleCredentials.apiKey.name, - }, - ], - isAdmin: true, - }; - - const { status } = await supertestWithoutAuth - .post('/internal/security/api_key/invalidate') - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleCredentials.cookieHeader) - .send(requestBody); - - expect(status).to.be(200); - }, - DEFAULT_ROLE, - }; -} diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index 3ad0d679d126a..b1f73748b5e0d 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -100,5 +100,6 @@ "@kbn/observability-ai-assistant-plugin", "@kbn/ml-trained-models-utils", "@kbn/test-suites-src", + "@kbn/console-plugin", ] } diff --git a/yarn.lock b/yarn.lock index 2a6fc9cb5f857..5f7cee38ed2a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1700,6 +1700,21 @@ dependencies: tslib "^1.9.3" +"@elastic/ebt@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@elastic/ebt/-/ebt-1.0.0.tgz#49a3772363c0e6e180e451a4aa24e219add4a6f3" + integrity sha512-/M3/bW8Kk4osYWM7xx5R0ePH1xr1fLER36bkw/HY8vHlHv+WCHnyLhL1yqQdCrq+eVunAPcoGawHXgi52SV05A== + dependencies: + "@babel/runtime" "^7.24.7" + fp-ts "^2.3.1" + io-ts "^2.0.5" + lodash.get "^4.4.2" + lodash.has "^4.5.2" + moment "^2.29.4" + node-fetch "^2.6.7" + rxjs "^7.5.5" + typescript "^5.5.4" + "@elastic/ecs-helpers@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@elastic/ecs-helpers/-/ecs-helpers-2.1.1.tgz#8a375b307c33a959938d9ae8f9abb466eb9fb3bf" @@ -1752,10 +1767,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@95.4.0": - version "95.4.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-95.4.0.tgz#57ac111c2a3e8125db244928a95787bbb2f318c8" - integrity sha512-UXmn+xgJj4pwq6MV7xMjM79GN+taTv7LBfrtRfurGwhkmvXJrOjMXWq+mfR8rU44lOS2R4AwzH5gcCBjAafLsA== +"@elastic/eui@95.6.0": + version "95.6.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-95.6.0.tgz#432845333bd0d64aa816222d192db4715cc75987" + integrity sha512-OnErgFixCcYcDpH3iu1fIG5FxoJ+mmHtKOVI/aqeC0beEeJJSZd8uqcLh4iJf9tfCEb+ApykTIuRGi6XTcpvjw== dependencies: "@hello-pangea/dnd" "^16.6.0" "@types/lodash" "^4.14.202" @@ -3437,19 +3452,19 @@ version "0.0.0" uid "" -"@kbn/apm-utils@link:packages/kbn-apm-utils": +"@kbn/apm-types@link:packages/kbn-apm-types": version "0.0.0" uid "" -"@kbn/app-link-test-plugin@link:test/plugin_functional/plugins/app_link_test": +"@kbn/apm-utils@link:packages/kbn-apm-utils": version "0.0.0" uid "" -"@kbn/application-usage-test-plugin@link:x-pack/test/usage_collection/plugins/application_usage_test": +"@kbn/app-link-test-plugin@link:test/plugin_functional/plugins/app_link_test": version "0.0.0" uid "" -"@kbn/assets-data-access-plugin@link:x-pack/plugins/observability_solution/assets_data_access": +"@kbn/application-usage-test-plugin@link:x-pack/test/usage_collection/plugins/application_usage_test": version "0.0.0" uid "" @@ -4761,10 +4776,6 @@ version "0.0.0" uid "" -"@kbn/ebt@link:packages/analytics/ebt": - version "0.0.0" - uid "" - "@kbn/ecs-data-quality-dashboard-plugin@link:x-pack/plugins/ecs_data_quality_dashboard": version "0.0.0" uid "" @@ -4821,6 +4832,10 @@ version "0.0.0" uid "" +"@kbn/entities-data-access-plugin@link:x-pack/plugins/observability_solution/entities_data_access": + version "0.0.0" + uid "" + "@kbn/entities-schema@link:x-pack/packages/kbn-entities-schema": version "0.0.0" uid "" @@ -5245,6 +5260,10 @@ version "0.0.0" uid "" +"@kbn/inference-plugin@link:x-pack/plugins/inference": + version "0.0.0" + uid "" + "@kbn/inference_integration_flyout@link:x-pack/packages/ml/inference_integration_flyout": version "0.0.0" uid "" @@ -6349,6 +6368,10 @@ version "0.0.0" uid "" +"@kbn/server-route-repository-client@link:packages/kbn-server-route-repository-client": + version "0.0.0" + uid "" + "@kbn/server-route-repository-utils@link:packages/kbn-server-route-repository-utils": version "0.0.0" uid "" @@ -6697,6 +6720,10 @@ version "0.0.0" uid "" +"@kbn/synthetics-private-location@link:x-pack/packages/kbn-synthetics-private-location": + version "0.0.0" + uid "" + "@kbn/task-manager-fixture-plugin@link:x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture": version "0.0.0" uid "" @@ -7110,16 +7137,16 @@ zod "^3.22.3" zod-to-json-schema "^3.22.5" -"@langchain/core@>0.1.0 <0.3.0", "@langchain/core@>=0.2.11 <0.3.0", "@langchain/core@>=0.2.16 <0.3.0", "@langchain/core@>=0.2.5 <0.3.0", "@langchain/core@^0.2.17", "@langchain/core@~0.2.11": - version "0.2.17" - resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.2.17.tgz#dfd44a2ccf79cef88ba765741a1c277bc22e483f" - integrity sha512-WnFiZ7R/ZUVeHO2IgcSL7Tu+CjApa26Iy99THJP5fax/NF8UQCc/ZRcw2Sb/RUuRPVm6ALDass0fSQE1L9YNJg== +"@langchain/core@>0.1.0 <0.3.0", "@langchain/core@>=0.2.11 <0.3.0", "@langchain/core@>=0.2.16 <0.3.0", "@langchain/core@>=0.2.18 <0.3.0", "@langchain/core@>=0.2.5 <0.3.0", "@langchain/core@^0.2.18", "@langchain/core@~0.2.11": + version "0.2.18" + resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.2.18.tgz#1ac4f307fa217ab3555c9634147a6c4ad9826092" + integrity sha512-ru542BwNcsnDfjTeDbIkFIchwa54ctHZR+kVrC8U9NPS9/36iM8p8ruprOV7Zccj/oxtLE5UpEhV+9MZhVcFlA== dependencies: ansi-styles "^5.0.0" camelcase "6" decamelize "1.2.0" js-tiktoken "^1.0.12" - langsmith "~0.1.30" + langsmith "~0.1.39" ml-distance "^4.0.0" mustache "^4.2.0" p-queue "^6.6.2" @@ -7137,12 +7164,12 @@ "@langchain/core" ">=0.2.16 <0.3.0" zod-to-json-schema "^3.22.4" -"@langchain/langgraph@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.0.29.tgz#eda31d101e7a75981e0929661c41ab2461ff8640" - integrity sha512-BSFFJarkXqrMdH9yH6AIiBCw4ww0VsXXpBwqaw+9/7iulW0pBFRSkWXHjEYnmsdCRgyIxoP8vYQAQ8Jtu3qzZA== +"@langchain/langgraph@^0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.0.31.tgz#4585fc9b4e9ad9677e97fd8debcfec2ae43a5fb4" + integrity sha512-f5QMSLy/RnLktsqnNm2mq8gp1xplHwQf87XIPVO0IYuumOJiafx5lE7ahPO+fVmCzAz6LxcsVocvD0JqxXR/2w== dependencies: - "@langchain/core" ">=0.2.16 <0.3.0" + "@langchain/core" ">=0.2.18 <0.3.0" uuid "^10.0.0" zod "^3.23.8" @@ -7185,10 +7212,10 @@ "@launchdarkly/js-sdk-common" "2.5.0" semver "7.5.4" -"@launchdarkly/node-server-sdk@^9.4.7": - version "9.4.7" - resolved "https://registry.yarnpkg.com/@launchdarkly/node-server-sdk/-/node-server-sdk-9.4.7.tgz#d8f923943b1750efa4c0f80baad079103ac5f700" - integrity sha512-9blKdS4c/ZGR0XjB8EqifDTdk08WBX/KZ91lZIc3ND3WA9D3qagysynaVkpBo3FiymHyCYyvwq4ZjiSMOIkrPw== +"@launchdarkly/node-server-sdk@^9.5.0": + version "9.5.0" + resolved "https://registry.yarnpkg.com/@launchdarkly/node-server-sdk/-/node-server-sdk-9.5.0.tgz#171c235a16836611e3c6d8d95945afa24832fc10" + integrity sha512-nNH011mToDKEUz5Nrux3bdatgmHj+SQGUCCD1+R5wgzpdvwZxhi7w1W+9Wl6QrqB+LcpBfKp2+vMQH/4KLsDXg== dependencies: "@launchdarkly/js-server-sdk-common" "2.4.4" https-proxy-agent "^5.0.1" @@ -8187,19 +8214,19 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@puppeteer/browsers@2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-2.2.3.tgz#ad6b79129c50825e77ddaba082680f4dad0b674e" - integrity sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ== +"@puppeteer/browsers@2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-2.2.4.tgz#4307245d881aa5a79743050be66568bad0f6ffbb" + integrity sha512-BdG2qiI1dn89OTUUsx2GZSpUzW+DRffR1wlMJyKxVHYrhnKoELSDxDd+2XImUkuWPEKk76H5FcM/gPFrEK1Tfw== dependencies: - debug "4.3.4" - extract-zip "2.0.1" - progress "2.0.3" - proxy-agent "6.4.0" - semver "7.6.0" - tar-fs "3.0.5" - unbzip2-stream "1.4.3" - yargs "17.7.2" + debug "^4.3.5" + extract-zip "^2.0.1" + progress "^2.0.3" + proxy-agent "^6.4.0" + semver "^7.6.2" + tar-fs "^3.0.6" + unbzip2-stream "^1.4.3" + yargs "^17.7.2" "@redocly/ajv@^8.11.0": version "8.11.0" @@ -8211,12 +8238,12 @@ require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/cli@^1.18.1": - version "1.18.1" - resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.18.1.tgz#a811d33be83885cdae973bcdf30455b8bb2bffc1" - integrity sha512-+bRKj46R9wvTzMdnoYfMueJ9/ek0NprEsQNowV7XcHgOXifeFFikRtBFcpkwqCNxaQ/nWAJn4LHZaFcssbcHow== +"@redocly/cli@^1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@redocly/cli/-/cli-1.19.0.tgz#fa7ac88c50b8850c1358bb68a22a3330efe0e0b0" + integrity sha512-ev6J0eD+quprvW9PVCl9JmRFZbj6cuK+mnYPAjcrPvesy2RF752fflcpgQjGnyFaGb1Cj+DiwDi3dYr3EAp04A== dependencies: - "@redocly/openapi-core" "1.18.1" + "@redocly/openapi-core" "1.19.0" abort-controller "^3.0.0" chokidar "^3.5.1" colorette "^1.2.0" @@ -8241,10 +8268,10 @@ resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.7.0.tgz#e8d06dc1f2d9cb9a4b5c5ce09afbf8536b32161c" integrity sha512-6GKxTo/9df0654Mtivvr4lQnMOp+pRj9neVywmI5+BwfZLTtkJnj2qB3D6d8FHTr4apsNOf6zTa5FojX0Evh4g== -"@redocly/openapi-core@1.18.1", "@redocly/openapi-core@^1.4.0": - version "1.18.1" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.18.1.tgz#b23cdc647db3ecb787995e9f78b569c13c33fb0d" - integrity sha512-y2ZR3aaVF80XRVoFP0Dp2z5DeCOilPTuS7V4HnHIYZdBTfsqzjkO169h5JqAaifnaLsLBhe3YArdgLb7W7wW6Q== +"@redocly/openapi-core@1.19.0", "@redocly/openapi-core@^1.4.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.19.0.tgz#8c6db2f0286b7776d79e392335f89f702ea19432" + integrity sha512-ezK6qr80sXvjDgHNrk/zmRs9vwpIAeHa0T/qmo96S+ib4ThQ5a8f3qjwEqxMeVxkxCTbkaY9sYSJKOxv4ejg5w== dependencies: "@redocly/ajv" "^8.11.0" "@redocly/config" "^0.7.0" @@ -11154,12 +11181,12 @@ "@types/node" "*" "@types/ws" "*" -"@types/semver@^7", "@types/semver@^7.3.12": +"@types/semver@^7.3.12": version "7.5.3" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== -"@types/semver@^7.5.1": +"@types/semver@^7.5.1", "@types/semver@^7.5.8": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== @@ -12210,10 +12237,12 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.2: dependencies: type-fest "^0.21.3" -ansi-escapes@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz#76c54ce9b081dad39acec4b5d53377913825fb0f" - integrity sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig== +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" ansi-gray@^0.1.1: version "0.1.1" @@ -14113,14 +14142,14 @@ chromedriver@^126.0.3: proxy-from-env "^1.1.0" tcp-port-used "^1.0.2" -chromium-bidi@0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.5.19.tgz#e4f4951b7d9b20d668d6b387839f7b7bf2d69ef4" - integrity sha512-UA6zL77b7RYCjJkZBsZ0wlvCTD+jTjllZ8f6wdO4buevXgTZYjV+XLB9CiEa2OuuTGGTLnI7eN9I60YxuALGQg== +chromium-bidi@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/chromium-bidi/-/chromium-bidi-0.6.1.tgz#533612dd166b7b36a8ba8b90685ad2fa0c98d064" + integrity sha512-kSxJRj0VgtUKz6nmzc2JPfyfJGzwzt65u7PqhPHtgGQUZLF5oG+ST6l6e5ONfStUMAlhSutFCjaGKllXZa16jA== dependencies: mitt "3.0.1" urlpattern-polyfill "10.0.0" - zod "3.22.4" + zod "3.23.8" ci-info@^2.0.0: version "2.0.0" @@ -14208,12 +14237,12 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" - integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== dependencies: - restore-cursor "^4.0.0" + restore-cursor "^5.0.0" cli-progress@^3.12.0: version "3.12.0" @@ -14802,16 +14831,6 @@ core-util-is@1.0.2, core-util-is@^1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" - integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== - dependencies: - env-paths "^2.2.1" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - cosmiconfig@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" @@ -14834,6 +14853,16 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" + integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== + dependencies: + env-paths "^2.2.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + cp-file@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-7.0.0.tgz#b9454cfd07fe3b974ab9ea0e5f29655791a9b8cd" @@ -15658,10 +15687,10 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5: + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" @@ -15672,6 +15701,13 @@ debug@4.3.1: dependencies: ms "2.1.2" +debug@4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.0.0, debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -16112,10 +16148,10 @@ detective@^5.0.2: defined "^1.0.0" minimist "^1.1.1" -devtools-protocol@0.0.1273771: - version "0.0.1273771" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1273771.tgz#46aeb5db41417e2c2ad3d8367c598c975290b1a5" - integrity sha512-QDbb27xcTVReQQW/GHJsdQqGKwYBE7re7gxehj467kKP2DKuYBUj6i2k5LRiAC66J1yZG/9gsxooz/s9pcm0Og== +devtools-protocol@0.0.1299070: + version "0.0.1299070" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz#b3e4cf0b678a46f0f907ae6e07e03ad3a53c00df" + integrity sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg== dezalgo@^1.0.0, dezalgo@^1.0.4: version "1.0.4" @@ -16496,10 +16532,10 @@ elastic-apm-node@3.46.0: traverse "^0.6.6" unicode-byte-truncate "^1.0.0" -elastic-apm-node@^4.7.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.7.1.tgz#39c30172db7d4d2e9af4dd87fc35f5f5171788b3" - integrity sha512-2OWhcjKD6O+S9ZuvywFiqXM7ifHnSUxaOTmCzprxEf/VfBonEBEfyyQ5Omymtm4xQ5Mqh18xKRUBNT74Xup0VQ== +elastic-apm-node@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.7.2.tgz#880b3df8e2266aac70f6370f916b0e66d5063455" + integrity sha512-9jsvAeHU6wztM+qUWJvgJCgdCVUI1sfg6a9quXmgkcjUJmRDJG0trfTScELZrfK5VJBQ88LVl05Q0nJW2j6TsA== dependencies: "@elastic/ecs-pino-format" "^1.5.0" "@opentelemetry/api" "^1.4.1" @@ -16688,6 +16724,11 @@ envinfo@^7.7.3: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + enzyme-shallow-equal@^1.0.0, enzyme-shallow-equal@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e" @@ -17111,6 +17152,15 @@ eslint-plugin-cypress@^2.15.1: dependencies: globals "^13.20.0" +eslint-plugin-depend@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-depend/-/eslint-plugin-depend-0.9.0.tgz#966af1287abc6e73b3467b4c4472b1ed8ef950b3" + integrity sha512-LApHjbTFvzuZR2hpHt67ehawywGpmLAQV0Th65oiTssmXk2UcbXdsr0+hCQFdV+C30HMZHSwofL0BSzLveNACA== + dependencies: + fd-package-json "^1.2.0" + module-replacements "^2.1.0" + semver "^7.6.0" + eslint-plugin-es@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz#98cb1bc8ab0aa807977855e11ad9d1c9422d014b" @@ -17891,6 +17941,13 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" +fd-package-json@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fd-package-json/-/fd-package-json-1.2.0.tgz#4f218bb8ff65c21011d1f4f17cb3d0c9e72f8da7" + integrity sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA== + dependencies: + walk-up-path "^3.0.1" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -20230,12 +20287,12 @@ is-ci@^3.0.0: dependencies: ci-info "^3.2.0" -is-core-module@^2.11.0, is-core-module@^2.12.0, is-core-module@^2.12.1, is-core-module@^2.9.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== +is-core-module@^2.11.0, is-core-module@^2.12.1, is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: - has "^1.0.3" + hasown "^2.0.0" is-data-descriptor@^0.1.4: version "0.1.4" @@ -21911,10 +21968,10 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== -langchain@0.2.3, langchain@^0.2.10: - version "0.2.10" - resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.2.10.tgz#35b74038e54650efbd9fe7d9d59765fe2790bb47" - integrity sha512-i0fC+RlX/6w6HKPWL3N5zrhrkijvpe2Xu4t/qbWzq4uFf8WBfPwmNFom3RtO2RatuPnHLm8mViU6nw8YBDiVwA== +langchain@0.2.3, langchain@^0.2.11: + version "0.2.11" + resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.2.11.tgz#d97e5bbd57e8954f21356fd85603aa39e3efe03f" + integrity sha512-6FQWKNAXuTmwuhHHMOmurLo8pydSRu5C/FwCYvYbR4ulCLqcsj+jre/kfXvA5BdHOZHNo6oQn0/5kxDNnhxMUA== dependencies: "@langchain/core" ">=0.2.11 <0.3.0" "@langchain/openai" ">=0.1.0 <0.3.0" @@ -21938,10 +21995,10 @@ langchainhub@~0.0.8: resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.8.tgz#fd4b96dc795e22e36c1a20bad31b61b0c33d3110" integrity sha512-Woyb8YDHgqqTOZvWIbm2CaFDGfZ4NTSyXV687AG4vXEfoNo7cGQp7nhl7wL3ehenKWmNEmcxCLgOZzW8jE6lOQ== -langsmith@^0.1.37, langsmith@~0.1.30: - version "0.1.37" - resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.37.tgz#7e7bf6fce3eab2a9e95221d5879820ec29d0aa60" - integrity sha512-8JgWykdJywdKWs+WeefOEf4Gkz3YdNkvG5u5JPbgXuodTUwuHPwjmblsldt1OGKkPp7iCWfdtCdnc9z9MYC/Dw== +langsmith@^0.1.39, langsmith@~0.1.30, langsmith@~0.1.39: + version "0.1.39" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.39.tgz#cc99f1828a9c0f5ba24bec6b0121edc44e8d282d" + integrity sha512-K2/qbc96JhrZbSL74RbZ0DBOpTB9Mxicu8RQrZ88Xsp1bH2O3+y5EdcvC0g/1YzQWQhcQ4peknCA24c3VTNiNA== dependencies: "@types/uuid" "^9.0.1" commander "^10.0.1" @@ -22134,15 +22191,15 @@ listr2@^3.8.3: through "^2.3.8" wrap-ansi "^7.0.0" -listr2@^8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.3.tgz#c494bb89b34329cf900e4e0ae8aeef9081d7d7a5" - integrity sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw== +listr2@^8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.4.tgz#486b51cbdb41889108cb7e2c90eeb44519f5a77f" + integrity sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g== dependencies: cli-truncate "^4.0.0" colorette "^2.0.20" eventemitter3 "^5.0.1" - log-update "^6.0.0" + log-update "^6.1.0" rfdc "^1.4.1" wrap-ansi "^9.0.0" @@ -22280,6 +22337,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.has@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" + integrity sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -22400,14 +22462,14 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -log-update@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.0.0.tgz#0ddeb7ac6ad658c944c1de902993fce7c33f5e59" - integrity sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw== +log-update@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" + integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== dependencies: - ansi-escapes "^6.2.0" - cli-cursor "^4.0.0" - slice-ansi "^7.0.0" + ansi-escapes "^7.0.0" + cli-cursor "^5.0.0" + slice-ansi "^7.1.0" strip-ansi "^7.1.0" wrap-ansi "^9.0.0" @@ -23132,6 +23194,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -23491,6 +23558,11 @@ module-lookup-amd@^8.0.5: requirejs "^2.3.6" requirejs-config-file "^4.0.0" +module-replacements@^2.1.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/module-replacements/-/module-replacements-2.3.2.tgz#285600e9ccf15c37d2a37c9a5690e4a6754a5941" + integrity sha512-dK8FQwOCU0rKuqtS2kWIU6It0XrE8qYZWtDT6fK4BQPtcspQ9EjWo2V0hob2d6G1wvQ21q+b1HFHOp3E/0cQTg== + moment-duration-format@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-2.3.2.tgz#5fa2b19b941b8d277122ff3f87a12895ec0d6212" @@ -23656,10 +23728,10 @@ msgpackr@^1.9.9: optionalDependencies: msgpackr-extract "^3.0.2" -msw@^2.3.4: - version "2.3.4" - resolved "https://registry.yarnpkg.com/msw/-/msw-2.3.4.tgz#3caac6c8e1ceb8040f512c32f9b8a6e850739c03" - integrity sha512-sHMlwrajgmZSA2l1o7qRSe+azm/I+x9lvVVcOxAzi4vCtH8uVPJk1K5BQYDkzGl+tt0RvM9huEXXdeGrgcc79g== +msw@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.3.5.tgz#424ad91b20a548d6b77fc26aca0c789e5cbc4764" + integrity sha512-+GUI4gX5YC5Bv33epBrD+BGdmDvBg2XGruiWnI3GbIbRmMMBeZ5gs3mJ51OWSGHgJKztZ8AtZeYMMNMVrje2/Q== dependencies: "@bundled-es-modules/cookie" "^2.0.0" "@bundled-es-modules/statuses" "^1.0.1" @@ -24486,6 +24558,13 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + dependencies: + mimic-function "^5.0.0" + open@^7.0.3: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" @@ -25905,16 +25984,16 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + proj4@2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/proj4/-/proj4-2.6.2.tgz#4665d7cbc30fd356375007c2fed53b07dbda1d67" @@ -26037,7 +26116,7 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-agent@6.4.0, proxy-agent@^6.4.0: +proxy-agent@^6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.4.0.tgz#b4e2dd51dee2b377748aef8d45604c2d7608652d" integrity sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ== @@ -26148,37 +26227,32 @@ pupa@^3.1.0: dependencies: escape-goat "^4.0.0" -puppeteer-core@22.8.1: - version "22.8.1" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-22.8.1.tgz#757ec8983ca38486dad8e5464e744f4b8aff5a13" - integrity sha512-m1F6ZSTw1xrJ6xD4B+HonkSNVQmMrRMaqca/ivRcZYJ6jqzOnfEh3QgO9HpNPj6heiAZ2+4IPAU3jdZaTIDnSA== +puppeteer-core@22.13.1: + version "22.13.1" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-22.13.1.tgz#3ba03e5ebd98bbbd86e465864cf00314e07309de" + integrity sha512-NmhnASYp51QPRCAf9n0OPxuPMmzkKd8+2sB9Q+BjwwCG25gz6iuNc3LQDWa+cH2tyivmJppLhNNFt6Q3HmoOpw== dependencies: - "@puppeteer/browsers" "2.2.3" - chromium-bidi "0.5.19" - debug "4.3.4" - devtools-protocol "0.0.1273771" - ws "8.17.0" + "@puppeteer/browsers" "2.2.4" + chromium-bidi "0.6.1" + debug "^4.3.5" + devtools-protocol "0.0.1299070" + ws "^8.18.0" -puppeteer@22.8.1: - version "22.8.1" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-22.8.1.tgz#d0b96cd722f62a157804dcc3b0d4909e3620bf1d" - integrity sha512-CFgPSKV+iydjO/8/hJVj251Hqp2PLcIa70j6H7sYqkwM8YJ+D3CA74Ufuj+yKtvDIntQPB/nLw4EHrHPcHOPjw== +puppeteer@22.13.1: + version "22.13.1" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-22.13.1.tgz#f8e4217919b438f18adb754e9d8414fef58fb3de" + integrity sha512-PwXLDQK5u83Fm5A7TGMq+9BR7iHDJ8a3h21PSsh/E6VfhxiKYkU7+tvGZNSCap6k3pCNDd9oNteVBEctcBalmQ== dependencies: - "@puppeteer/browsers" "2.2.3" - cosmiconfig "9.0.0" - devtools-protocol "0.0.1273771" - puppeteer-core "22.8.1" + "@puppeteer/browsers" "2.2.4" + cosmiconfig "^9.0.0" + devtools-protocol "0.0.1299070" + puppeteer-core "22.13.1" pure-rand@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== -q@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - qs@6.10.4: version "6.10.4" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.4.tgz#6a3003755add91c0ec9eacdc5f878b034e73f9e7" @@ -26414,10 +26488,10 @@ react-colorful@^5.1.2: resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.5.1.tgz#29d9c4e496f2ca784dd2bb5053a3a4340cfaf784" integrity sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg== -react-diff-view@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/react-diff-view/-/react-diff-view-3.2.0.tgz#8fbf04782d78423903a59202ce7533f6312c1cc3" - integrity sha512-p58XoqMxgt71ujpiDQTs9Za3nqTawt1E4bTzKsYSqr8I8br6cjQj1b66HxGnV8Yrc6MD6iQPqS1aZiFoGqEw+g== +react-diff-view@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/react-diff-view/-/react-diff-view-3.2.1.tgz#cc1473955fae999c1d486638c4425ceb48f3d465" + integrity sha512-JoDahgiyeReeH9W9lrI3Z4c4esbd/HNAOdThj6Pce/ZAukFBmXSbZ4Qv8ayo7yow+fTpRNfqtQ9gX5nArEi08w== dependencies: classnames "^2.3.2" diff-match-patch "^1.0.5" @@ -27608,14 +27682,14 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-in-the-middle@^7.0.1, require-in-the-middle@^7.1.1, require-in-the-middle@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.3.0.tgz#ce64a1083647dc07b3273b348357efac8a9945c9" - integrity sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw== +require-in-the-middle@^7.0.1, require-in-the-middle@^7.1.1, require-in-the-middle@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz#606977820d4b5f9be75e5a108ce34cfed25b3bb4" + integrity sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ== dependencies: - debug "^4.1.1" + debug "^4.3.5" module-details-from-path "^1.0.3" - resolve "^1.22.1" + resolve "^1.22.8" require-main-filename@^2.0.0: version "2.0.0" @@ -27716,21 +27790,21 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.22.3, resolve@^1.3.2, resolve@^1.9.0: - version "1.22.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.3.tgz#4b4055349ffb962600972da1fdc33c46a4eb3283" - integrity sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw== +resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.22.3, resolve@^1.22.8, resolve@^1.3.2, resolve@^1.9.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: - is-core-module "^2.12.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" resolve@^2.0.0-next.4: - version "2.0.0-next.4" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" - integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -27756,13 +27830,13 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -restore-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" - integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" + onetime "^7.0.0" + signal-exit "^4.1.0" resumer@^0.0.0: version "0.0.0" @@ -28288,22 +28362,15 @@ semver@7.5.4: dependencies: lru-cache "^6.0.0" -semver@7.6.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== - dependencies: - lru-cache "^6.0.0" - semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.2: - version "7.6.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" - integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== send@0.18.0: version "0.18.0" @@ -28717,7 +28784,7 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" -slice-ansi@^7.0.0: +slice-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9" integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg== @@ -29951,17 +30018,6 @@ tape@^5.0.1: string.prototype.trim "^1.2.1" through "^2.3.8" -tar-fs@3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.5.tgz#f954d77767e4e6edf973384e1eb95f8f81d64ed9" - integrity sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg== - dependencies: - pump "^3.0.0" - tar-stream "^3.1.5" - optionalDependencies: - bare-fs "^2.1.1" - bare-path "^2.1.0" - tar-fs@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -30114,7 +30170,7 @@ terser@^4.1.2, terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.26.0, terser@^5.3.4, terser@^5.31.2, terser@^5.9.0: +terser@^5.26.0, terser@^5.3.4, terser@^5.31.3, terser@^5.9.0: version "5.31.3" resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== @@ -30758,7 +30814,7 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@5, typescript@5.1.6, typescript@^3.3.3333, typescript@^5.0.4: +typescript@5, typescript@5.1.6, typescript@^3.3.3333, typescript@^5.0.4, typescript@^5.5.4: version "5.1.6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== @@ -30795,7 +30851,7 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -unbzip2-stream@1.4.3: +unbzip2-stream@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== @@ -31871,6 +31927,11 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" +walk-up-path@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-3.0.1.tgz#c8d78d5375b4966c717eb17ada73dbd41490e886" + integrity sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA== + walker@^1.0.7, walker@^1.0.8, walker@~1.0.5: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -32484,15 +32545,10 @@ write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.17.0: - version "8.17.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" - integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== - -ws@>=8.16.0, ws@^8.2.3, ws@^8.4.2, ws@^8.9.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" - integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@>=8.16.0, ws@^8.18.0, ws@^8.2.3, ws@^8.4.2, ws@^8.9.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== ws@^7.3.1, ws@^7.4.2: version "7.5.10" @@ -32686,19 +32742,6 @@ yargs@17.0.1: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@17.7.2, yargs@^17.0.1, yargs@^17.2.1, yargs@^17.3.1, yargs@^17.7.1, yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -32716,6 +32759,19 @@ yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^17.0.1, yargs@^17.2.1, yargs@^17.3.1, yargs@^17.7.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yargs@^3.15.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" @@ -32802,12 +32858,7 @@ zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.4, zod-to-json-schema@^3.22 resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.23.0.tgz#4fc60e88d3c709eedbfaae3f92f8a7bf786469f2" integrity sha512-az0uJ243PxsRIa2x1WmNE/pnuA05gUq/JB8Lwe1EDCCL/Fz9MgjYQ0fPlyc2Tcv6aF2ZA7WM5TWaRZVEFaAIag== -zod@3.22.4: - version "3.22.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" - integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== - -zod@^3.22.3, zod@^3.22.4, zod@^3.23.8: +zod@3.23.8, zod@^3.22.3, zod@^3.22.4, zod@^3.23.8: version "3.23.8" resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==